Skip to content

Commit

Permalink
Support for custom contexts in ContextCommand (#727)
Browse files Browse the repository at this point in the history
* added support for external context panes

* fixed a trailing space in settings

* fix for review from @hugsy

* fixed format string usage

Co-authored-by: hugsy <hugsy@users.noreply.github.com>

* added ability for dynamic titles and updated api documentation

* review fix 2

* Apply suggestions from code review

Co-authored-by: hugsy <hugsy@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: theguy147 <37738506+theguy147@users.noreply.github.com>

Co-authored-by: hugsy <hugsy@users.noreply.github.com>
Co-authored-by: theguy147 <37738506+theguy147@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 27, 2021
1 parent 55f9090 commit 3b0d9da
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 12 deletions.
37 changes: 37 additions & 0 deletions docs/api.md
Expand Up @@ -70,6 +70,43 @@ or add to your `~/.gdbinit`:
$ echo source /path/to/newcmd.py >> ~/.gdbinit
```

## Custom context panes ##

Sometimes you want something similar to a command to run on each break-like
event and display itself as a part of the GEF context. Here is a simple example
of how to make a custom context pane:

```python
__start_time__ = int(time.time())
def wasted_time_debugging():
gef_print("You have wasted {} seconds!".format(int(time.time()) - __start_time__))

def wasted_time_debugging_title():
return "wasted:time:debugging:{}".format(int(time.time()) - __start_time__)

register_external_context_pane("wasted_time_debugging", wasted_time_debugging, wasted_time_debugging_title)
```

Loading it in `GEF` is as easy as loading a command

```
gef➤ source /path/to/custom_context_pane.py
```

It can even be included in the same file as a Command.
Now on each break you will notice a new pane near the bottom of the context.
The order can be modified in the `GEF` context config.

### Context Pane API ###

The API demonstrated above requires very specific argument types:
`register_external_context_pane(pane_name, display_pane_function, pane_title_function)`

-`pane_name`: a string that will be used as the panes setting name
-`display_pane_function`: a function that uses `gef_print()` to print content
in the pane
-`pane_title_function`: a function that returns the string of the panes title

## API ##

Some of the most important parts of the API for creating new commands are
Expand Down
12 changes: 12 additions & 0 deletions docs/commands/context.md
Expand Up @@ -18,6 +18,18 @@ menu when hitting a breakpoint.
* The code context box shows the 10 (by default but can be tweaked) next
instructions to be executed.

### Adding custom context panes ###

As well as using the built-in context panes, you can add your own custom pane that
will be displayed at each `break`-like event with all the other panes. Custom panes
can be added using the API:

```python
register_external_context_pane(pane_name, display_pane_function, pane_title_function)
```

Check the [API](../api.md) documentation to see a full usage of the
registration API.

### Editing context layout ###

Expand Down
73 changes: 61 additions & 12 deletions gef.py
Expand Up @@ -4525,9 +4525,27 @@ def stop(self):


#
# Commands
# Context Panes
#

def register_external_context_pane(pane_name, display_pane_function, pane_title_function):
"""
Registering function for new GEF Context View.
pane_name: a string that has no spaces (used in settings)
display_pane_function: a function that uses gef_print() to print strings
pane_title_function: a function that returns a string, which will be displayed as the title
Example Usage:
def display_pane(): gef_print("Wow, I am a context pane!")
def pane_title(): return "example:pane"
register_external_context_pane("example_pane", display_pane, pane_title)
"""
__gef__.add_context_pane(pane_name, display_pane_function, pane_title_function)


#
# Commands
#

def register_external_command(obj):
"""Registering function for new GEF (sub-)command to GDB."""
Expand Down Expand Up @@ -4638,6 +4656,19 @@ def has_setting(self, name):
key = self.__get_setting_name(name)
return key in __config__

def update_setting(self, name, value, description=None):
"""Update the value of a setting without destroying the description"""
# make sure settings are always associated to the root command (which derives from GenericCommand)
if "GenericCommand" not in [x.__name__ for x in self.__class__.__bases__]:
return
key = self.__get_setting_name(name)
__config__[key][0] = value
__config__[key][1] = type(value)
if description:
__config__[key][3] = description
get_gef_setting.cache_clear()
return

def add_setting(self, name, value, description=""):
# make sure settings are always associated to the root command (which derives from GenericCommand)
if "GenericCommand" not in [x.__name__ for x in self.__class__.__bases__]:
Expand Down Expand Up @@ -8236,16 +8267,16 @@ def __init__(self):
self.add_setting("use_capstone", False, "Use capstone as disassembler in the code pane (instead of GDB)")

self.layout_mapping = {
"legend": self.show_legend,
"regs": self.context_regs,
"stack": self.context_stack,
"code": self.context_code,
"args": self.context_args,
"memory": self.context_memory,
"source": self.context_source,
"trace": self.context_trace,
"threads": self.context_threads,
"extra": self.context_additional_information,
"legend": (self.show_legend, None),
"regs": (self.context_regs, None),
"stack": (self.context_stack, None),
"code": (self.context_code, None),
"args": (self.context_args, None),
"memory": (self.context_memory, None),
"source": (self.context_source, None),
"trace": (self.context_trace, None),
"threads": (self.context_threads, None),
"extra": (self.context_additional_information, None),
}
return

Expand Down Expand Up @@ -8299,7 +8330,10 @@ def do_invoke(self, argv):
continue

try:
self.layout_mapping[section]()
display_pane_function, pane_title_function = self.layout_mapping[section]
if pane_title_function:
self.context_title(pane_title_function())
display_pane_function()
except gdb.MemoryError as e:
# a MemoryError will happen when $pc is corrupted (invalid address)
err(str(e))
Expand Down Expand Up @@ -10650,6 +10684,21 @@ def invoke(self, args, from_tty):
gdb.execute("gef help")
return

def add_context_pane(self, pane_name, display_pane_function, pane_title_function):
"""Add a new context pane to ContextCommand."""
for _, _, class_obj in self.loaded_commands:
if isinstance(class_obj, ContextCommand):
context_obj = class_obj
break

# assure users can toggle the new context
corrected_settings_name = pane_name.replace(" ", "_")
layout_settings = context_obj.get_setting("layout")
context_obj.update_setting("layout", "{} {}".format(layout_settings, corrected_settings_name))

# overload the printing of pane title
context_obj.layout_mapping[corrected_settings_name] = (display_pane_function, pane_title_function)

def load(self, initial=False):
"""Load all the commands and functions defined by GEF into GDB."""
nb_missing = 0
Expand Down

0 comments on commit 3b0d9da

Please sign in to comment.