Skip to content

Commit

Permalink
* Addresses most of the concerns raised in the review of PR #661
Browse files Browse the repository at this point in the history
* Added the possibility to use short flags
* Updated the documentation to provide relevant examples
  • Loading branch information
hugsy committed Jun 20, 2021
1 parent 782dd88 commit 5d167f8
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 80 deletions.
166 changes: 105 additions & 61 deletions docs/api.md
Expand Up @@ -62,11 +62,11 @@ section of the script, by invoking the global function
`register_external_command()`.

Now you have a new GEF command which you can load, either from cli:
```
```bash
gef➤ source /path/to/newcmd.py
```
or add to your `~/.gdbinit`:
```
```bash
$ echo source /path/to/newcmd.py >> ~/.gdbinit
```

Expand All @@ -77,7 +77,7 @@ mentioned (but not limited to) below. To see the full help of a function, open
GDB and GEF, and use the embedded Python interpreter's `help` command. For
example:

```
```bash
gef➤ pi help(Architecture)
```

Expand All @@ -90,137 +90,181 @@ $ gdb -q -ex 'pi help(hexdump)' -ex quit

### Globals ###

```
```python
register_external_command()
```
> Procedure to add the new GEF command
Procedure to add the new GEF command

---

```
```python
current_arch
```
> Global variable associated with the architecture of the currently debugged
> process. The variable is an instance of the `Architecture` class (see below).
Global variable associated with the architecture of the currently debugged process. The variable is an instance of the `Architecture` class (see below).

```
---

```python
read_memory(addr, length=0x10)
```
> Returns a `length` long byte array with a copy of the process memory read
> from `addr`.
Returns a `length` long byte array with a copy of the process memory read from `addr`.

```
---

```python
write_memory(addr, buffer, length=0x10)
```
> Writes `buffer` to memory at address `addr`.
Writes `buffer` to memory at address `addr`.

---

```
```python
read_int_from_memory(addr)
```
> Reads the size of an integer from `addr`, and unpacks it correctly (based on
> the current arch's endianness)

```
Reads the size of an integer from `addr`, and unpacks it correctly (based on the current arch's endianness)

---

```python
read_cstring_from_memory(addr)
```
> Return a NULL-terminated array of bytes, from `addr`.
Return a NULL-terminated array of bytes, from `addr`.

---

```
```python
get_register(register_name)
```
> Returns the value of given register.

Returns the value of given register.

```
---

```python
get_process_maps()
```
> Returns an array of Section objects (see below) corresponding to the current
> memory layout of the process.
Returns an array of Section objects (see below) corresponding to the current memory layout of the process.

---

```
```python
gef_disassemble(addr, nb_insn, from_top=False)
```
> Disassemble `nb_insn` instructions after `addr`. If `from_top` is False
> (default), it will also disassemble the `nb_insn` instructions before `addr`.
> Return an iterator of Instruction objects (see below).
Disassemble `nb_insn` instructions after `addr`. If `from_top` is False (default), it will also disassemble the `nb_insn` instructions before `addr`. Return an iterator of Instruction objects (see below).

---

```
```python
ok(msg)
info(msg)
warn(msg)
err(msg)
```
> Logging functions

Logging functions

```
---

```python
gef_on_continue_hook
gef_on_continue_unhook
```
> Takes a callback function FUNC as parameter: add/remove a call to FUNC
> when GDB continues execution.
Takes a callback function FUNC as parameter: add/remove a call to `FUNC` when GDB continues execution.

```
---

```python
gef_on_stop_hook
gef_on_stop_unhook
```
> Takes a callback function FUNC as parameter: add/remove a call to FUNC
> when GDB stops execution (breakpoints, watchpoints, interrupt, signal, etc.).

```
Takes a callback function FUNC as parameter: add/remove a call to `FUNC` when GDB stops execution (breakpoints, watchpoints, interrupt, signal, etc.).

---

```python
gef_on_new_hook
gef_on_new_unhook
```
> Takes a callback function FUNC as parameter: add/remove a call to FUNC
> when GDB loads a new binary.

```
Takes a callback function FUNC as parameter: add/remove a call to `FUNC` when GDB loads a new binary.

---

```python
gef_on_exit_hook
gef_on_exit_unhook
```
> Takes a callback function FUNC as parameter: add/remove a call to FUNC
> when GDB exits an inferior.

Takes a callback function FUNC as parameter: add/remove a call to `FUNC` when GDB exits an inferior.


### Decorators ###

```
```python
@only_if_gdb_running
```
> Modifies a function to only execute if a GDB session is running. A GDB
> session is running if:
>
> * a PID exists for the targeted binary
> * GDB is running on a coredump of a binary

Modifies a function to only execute if a GDB session is running. A GDB session is running if:
* a PID exists for the targeted binary
* GDB is running on a coredump of a binary

```
---

```python
@only_if_gdb_target_local
```
> Checks if the current GDB session is local i.e. not debugging using GDB
> `remote`.
Checks if the current GDB session is local i.e. not debugging using GDB `remote`.

---

```
```python
@only_if_gdb_version_higher_than( (MAJOR, MINOR) )
```
> Checks if the GDB version is higher or equal to the MAJOR and MINOR provided
> as arguments (both as Integers). This is required since some commands/API of
> GDB are only present in the very latest version of GDB.

Checks if the GDB version is higher or equal to the MAJOR and MINOR providedas arguments (both as Integers). This is required since some commands/API ofGDB are only present in the very latest version of GDB.

```
---

```python
@parse_arguments( {"required_argument_1": DefaultValue1, ...}, {"--optional-argument-1": DefaultValue1, ...} )
```
> This decorator aims to facilitate the argument passing to a command. If added, it will use
> the `argparse` module to parse arguments, and will store them in the `kwargs["arguments"]` of
> the calling function (therefore the function **must** have `*args, **kwargs` added to its
> signature). Argument type is inferred directly from the default value **except** for boolean,
> where a value of `True` corresponds to `argparse`'s `store_true` action. For more details on
> `argparse`, refer to its Python doc.

This decorator aims to facilitate the argument passing to a command. If added, it will use the `argparse` module to parse arguments, and will store them in the `kwargs["arguments"]` of the calling function (therefore the function **must** have `*args, **kwargs` added to its signature). Argument type is inferred directly from the default value **except** for boolean, where a value of `True` corresponds to `argparse`'s `store_true` action. For more details on `argparse`, refer to its Python documentation.

Argument flags are also supported, allowing to write simpler version of the flag such as

```python
@parse_arguments( {}, {("--long-argument", "-l"): value, } )
```

A basic example would be as follow:

```python
class MyCommand(GenericCommand):
[...]

@parse_arguments({"foo": 1}, {"--bleh": "", ("--blah", "-l): True})
def do_invoke(self, argv, *args, **kwargs):
args = kwargs["arguments"]
if args.foo == 1: ...
if args.blah == True: ...
```

When the user enters the following command:

```
gef➤ mycommand --blah 42
```

The function `MyCommand!do_invoke()` can use the command line argument value

```python
args.foo --> 42 # from user input
args.bleh --> "" # default value
args.blah --> True # from user input
```


### Classes ###
Expand Down
36 changes: 17 additions & 19 deletions gef.py
Expand Up @@ -2609,12 +2609,13 @@ def wrapper(*args, **kwargs):
for argname in required_arguments:
argvalue = required_arguments[argname]
argtype = type(argvalue)
if argtype == int:
if argtype is int:
argtype = int_wrapper

if argname.startswith("-"):
argname_is_list = isinstance(argname, list) or isinstance(argname, tuple)
if not argname_is_list and argname.startswith("-"):
# optional args
if argtype == bool:
if argtype is bool:
parser.add_argument(argname, action="store_true" if argvalue else "store_false")
else:
parser.add_argument(argname, type=argtype, required=True, default=argvalue)
Expand All @@ -2628,20 +2629,22 @@ def wrapper(*args, **kwargs):
parser.add_argument(argname, type=argtype, default=argvalue, nargs=nargs)

for argname in optional_arguments:
if not argname.startswith("-"):
argname_is_list = isinstance(argname, list) or isinstance(argname, tuple)
if not argname_is_list and not argname.startswith("-"):
# refuse positional arguments
continue
argvalue = optional_arguments[argname]
argtype = type(argvalue)
if argtype == int:
if not argname_is_list:
argname = [argname,]
if argtype is int:
argtype = int_wrapper
if argtype == bool:
parser.add_argument(argname, action="store_true" if argvalue else "store_false")
if argtype is bool:
parser.add_argument(*argname, action="store_true" if argvalue else "store_false")
else:
parser.add_argument(argname, type=argtype, default=argvalue)
parser.add_argument(*argname, type=argtype, default=argvalue)

_, cmd_args = args[0], args[1:]
parsed_args = parser.parse_args(*cmd_args)
parsed_args = parser.parse_args(*(args[1:]))
kwargs["arguments"] = parsed_args
return f(*args, **kwargs)
return wrapper
Expand All @@ -2657,7 +2660,7 @@ def copy_to_clipboard(data):
pbcopy = which("pbcopy")
prog = [pbcopy]
else:
raise NotImplementedError("Unsupported OS")
raise NotImplementedError("paste: Unsupported OS")

p = subprocess.Popen(prog, stdin=subprocess.PIPE)
p.stdin.write(data)
Expand Down Expand Up @@ -3545,12 +3548,7 @@ def db(t, p):
def generate_cyclic_pattern(length, cycle=4):
"""Create a `length` byte bytearray of a de Bruijn cyclic pattern."""
charset = bytearray(b"abcdefghijklmnopqrstuvwxyz")
pattern = bytearray()
for i, c in enumerate(de_bruijn(charset, cycle)):
if i == length:
break
pattern.append(c)
return pattern
return bytearray(itertools.islice(de_bruijn(charset, cycle), length))


def safe_parse_and_eval(value):
Expand Down Expand Up @@ -4343,7 +4341,7 @@ def __init__(self):
return

@only_if_gdb_running
@parse_arguments({"location": "$pc", }, {"--length": 256, "--bitlen": 0, "--lang": "py", "--clip": True,})
@parse_arguments({"location": "$pc", }, {("--length", "-l"): 256, "--bitlen": 0, "--lang": "py", "--clip": True,})
def do_invoke(self, argv, *args, **kwargs):
"""Default value for print-format command."""
args = kwargs["arguments"]
Expand Down Expand Up @@ -10174,7 +10172,7 @@ def add_command_to_doc(self, command):
doc = getattr(class_name, "__doc__", "").lstrip()
doc = "\n ".join(doc.split("\n"))
aliases = " (alias: {:s})".format(", ".join(class_name._aliases_)) if hasattr(class_name, "_aliases_") else ""
msg = "{cmd:<25s} -- {help:s}{aliases:s}".format(cmd=cmd, help=Color.greenify(doc), aliases=aliases)
msg = "{cmd:<25s} -- {help:s}{aliases:s}".format(cmd=cmd, help=doc, aliases=aliases)
self.docs.append(msg)
return

Expand Down

0 comments on commit 5d167f8

Please sign in to comment.