mirror of https://github.com/pwndbg/pwndbg.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
9.6 KiB
Markdown
188 lines
9.6 KiB
Markdown
# Adding a Command
|
|
## Command skeleton
|
|
To add a command to pwndbg, create a new python file in `pwndbg/commands/my_command.py` where `my_command` is the name of the command you want to add. The most basic command looks like this:
|
|
```python
|
|
import argparse
|
|
import pwndbg.commands
|
|
|
|
parser = argparse.ArgumentParser(description="Command description.")
|
|
parser.add_argument("arg", type=str, help="An example argument.")
|
|
|
|
@pwndbg.commands.Command(parser, category=pwndbg.commands.CommandCategory.MISC)
|
|
def my_command(arg: str) -> None:
|
|
"""Print the argument"""
|
|
print(f"Argument is {arg}")
|
|
```
|
|
Next, import this file in the `load_commands` function in `pwndbg/commands/__init__.py`.
|
|
|
|
That's all you need to get it working!
|
|
```text
|
|
pwndbg> my-command foo
|
|
Argument is foo
|
|
```
|
|
## Getting started
|
|
Let's see what arguments the `@pwndbg.commands.Command` decorator takes. It is defined in `pwndbg/commands/__init__.py`:
|
|
```python
|
|
def __init__(
|
|
self,
|
|
parser_or_desc: argparse.ArgumentParser | str,
|
|
*, # All further parameters are not positional
|
|
category: CommandCategory,
|
|
command_name: str | None = None,
|
|
aliases: List[str] = [],
|
|
examples: str = "",
|
|
notes: str = "",
|
|
only_debuggers: Set[pwndbg.dbg_mod.DebuggerType] = None,
|
|
exclude_debuggers: Set[pwndbg.dbg_mod.DebuggerType] = None,
|
|
) -> None:
|
|
# ...
|
|
```
|
|
We will cover the first four arguments now, and come back to the rest later.
|
|
|
|
If your command takes no arguments you can pass the description of the command as the first argument (`parser_or_desc`) to the constructor. Otherwise you will be passing an [`argparse.ArgumentParser`](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser) object there.
|
|
|
|
The only other required argument is `category`. The `category` determines how commands are grouped together in the output of the [`pwndbg`](https://pwndbg.re/pwndbg/dev/commands/pwndbg/pwndbg/) command and in the [documentation](https://pwndbg.re/pwndbg/dev/commands/). Peruse the list of all commands inside a debugger (by running the `pwndbg` command) and decide in which category your command fits best. The enum of all command categories is defined at the top of the `pwndbg/commands/__init__.py` file.
|
|
### Picking a command name
|
|
Next, the `command_name` argument. It is optional because if it is not specified the command name will be the same as the function you used to define the command (except the underscores are replaced with dashes). As such, it is generally not needed to specify this argument.
|
|
|
|
That being said, it is important to pick a good name for your command. Ideally your command name should be one to two words that are *not* delimited by a dash (e.g. `errno`, `libcinfo`, `buddydump`) since that is easiest to remember and type.
|
|
|
|
If your command is porting behavior from some other debugger or tool, you should consider using the same name they use so users don't need to relearn it when switching.
|
|
|
|
If the command name contains three or more words, you should use dashes to make it more legible. If that is the case, or if the name is long, consider providing an alias that makes it quicker to type (like `vis-heap-chunks [vis]`).
|
|
|
|
You provide aliases to a command by specifying a list of strings to the `aliases` argument. Again, you may provide aliases to help users transitioning from other tools/debuggers (e.g. `nearpc [pdisass, u]`).
|
|
## The arguments your command will take
|
|
We are using [`argparse.ArgumentParser`](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser) from the python standard library to define command arguments. Take a look at the python documentation to see how it works. Let's take a look at an example from the source (the [`setflag`](https://pwndbg.re/pwndbg/dev/commands/register/setflag/) command):
|
|
```python
|
|
parser = argparse.ArgumentParser(description="Modify the flags register.")
|
|
|
|
parser.add_argument(
|
|
"flag",
|
|
type=str,
|
|
help="Flag for which you want to change the value"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"value",
|
|
type=int,
|
|
help="Value to which you want to set the flag - only valid options are 0 and 1",
|
|
)
|
|
```
|
|
For usage inside pwndbg, to instantiate an `argparse.ArgumentParser` object, you must pass the `description` argument and may pass the `epilog` argument. Everything else, including `prog`, `usage`, `formatter_class` etc. will be set up by pwndbg (by the `@pwndbg.commands.Command` decorator). Here we see only the `description` was provided.
|
|
|
|
Add arguments to your command with [`parser.add_argument`](https://docs.python.org/3/library/argparse.html#the-add-argument-method). Again, consult the python documentation for an explanation. One nice thing specific to pwndbg is that by setting an argument's `type` to `int`, it will also accept debugger values and symbols that can resolve to an int. For instance:
|
|
```python
|
|
pwndbg> setflag ZF (1-1)
|
|
Set flag ZF=0 in flag register eflags (old val=0x206, new val=0x206)
|
|
pwndbg> setflag ZF $rdi
|
|
Set flag ZF=1 in flag register eflags (old val=0x246, new val=0x246)
|
|
pwndbg> setflag ZF (int)main^(int)main
|
|
Set flag ZF=0 in flag register eflags (old val=0x246, new val=0x206)
|
|
```
|
|
Be careful when deciding which arguments are positional, and which are optional. Especially take care if you have positional arguments which are not required, think about which of those will be specified more often by users and put them first.
|
|
|
|
Your function signature should match the arguments you defined with argparse (and their order!), unsurprisingly the `setflag` function has this signature:
|
|
```python
|
|
def setflag(flag: str, value: int) -> None:
|
|
```
|
|
You can see the help of your command with `my_command -h` or `help my_command`, so for `setflag`:
|
|
```
|
|
pwndbg> help setflag
|
|
usage: setflag [-h] flag value
|
|
|
|
Modify the flags register.
|
|
|
|
positional arguments:
|
|
flag Flag for which you want to change the value
|
|
value Value to which you want to set the flag - only valid options are 0 and 1
|
|
|
|
options:
|
|
-h, --help show this help message and exit
|
|
|
|
Examples:
|
|
On X86/X64:
|
|
setflag ZF 1 -- set zero flag
|
|
setflag CF 0 -- unset carry flag
|
|
|
|
On ARM:
|
|
setflag Z 0 -- unset the Z cpsr/xpsr flag
|
|
|
|
To see flags registers:
|
|
info reg eflags -- on x86/x64
|
|
info reg cpsr/xpsr -- on ARM (specific register may vary)
|
|
|
|
Notes:
|
|
This command supports flags registers that are defined for architectures in the pwndbg/regs.py file.
|
|
|
|
Alias: flag
|
|
```
|
|
Eh? Where is all that extra text coming from? Well the `Alias: flag` line is being automatically generated by pwndbg but...
|
|
## Examples, notes, and debugger support
|
|
Coming back to the arguments of the `pwndbg.commands.Command` constructor:
|
|
```python
|
|
def __init__(
|
|
self,
|
|
parser_or_desc: argparse.ArgumentParser | str,
|
|
*, # All further parameters are not positional
|
|
category: CommandCategory,
|
|
command_name: str | None = None,
|
|
aliases: List[str] = [],
|
|
examples: str = "", # <--- we left off here
|
|
notes: str = "",
|
|
only_debuggers: Set[pwndbg.dbg_mod.DebuggerType] = None,
|
|
exclude_debuggers: Set[pwndbg.dbg_mod.DebuggerType] = None,
|
|
) -> None:
|
|
# ...
|
|
```
|
|
You may supply the `examples` and `notes` arguments to add additional text at the end of the command's help. It is defined like so for `setflag`:
|
|
```python
|
|
@pwndbg.commands.Command(
|
|
parser,
|
|
aliases=["flag"],
|
|
category=CommandCategory.REGISTER,
|
|
examples="""
|
|
On X86/X64:
|
|
setflag ZF 1 -- set zero flag
|
|
setflag CF 0 -- unset carry flag
|
|
|
|
On ARM:
|
|
setflag Z 0 -- unset the Z cpsr/xpsr flag
|
|
|
|
To see flags registers:
|
|
info reg eflags -- on x86/x64
|
|
info reg cpsr/xpsr -- on ARM (specific register may vary)
|
|
""",
|
|
notes="""
|
|
This command supports flags registers that are defined for architectures in the pwndbg/regs.py file.
|
|
""",
|
|
)
|
|
@pwndbg.commands.OnlyWhenRunning
|
|
def setflag(flag: str, value: int) -> None:
|
|
# ....
|
|
```
|
|
When writing this (and the command description for that matter), you should consider what it will [look like in the documentation](https://pwndbg.re/pwndbg/dev/commands/register/setflag/) after being parsed as markdown.
|
|
|
|
As for `only_debuggers` and `exclude_debuggers`, you must use (usually one of) them if your command does not work an all debuggers that pwndbg supports. For instance, if it uses some features from `pwndbg.gdblib` (which should be avoided if at all possible). In such a case, you probably also need to conditionally import it in the `load_commands` function.
|
|
## Can your command be invoked all the time?
|
|
In most cases a command cannot be legally invoked at every moment in a debugging session, or for every debugging session. For instance, you can't use heap commands if the heap isn't initialized yet, you can't use kernel commands if you're not debugging a kernel.
|
|
|
|
To make sure these cases are properly handled, pwndbg provides certain decorators. They are defined in `pwndbg/commands/__init__.py`. Check the source to see an up-to-date list, but here are some important ones:
|
|
```
|
|
OnlyWhenRunning
|
|
OnlyWhenLocal
|
|
OnlyWithFile
|
|
OnlyWhenQemuKernel
|
|
OnlyWhenUserspace
|
|
OnlyWithKernelDebugSyms
|
|
OnlyWhenPagingEnabled
|
|
OnlyWithTcache
|
|
OnlyWhenHeapIsInitialized
|
|
OnlyWithResolvedHeapSyms
|
|
```
|
|
Feel free to add more of these decorators yourself!
|
|
|
|
Another very important one is `OnlyWithArch`, defined in `pwndbg/aglib/proc.py`. Does your command work on all architectures? If not, make sure to specify this decorator and pass in the architectures which you do support.
|
|
## Actually implementing the command
|
|
There is no single right way to do it. You will want to read the source of some similar commands and see how they work. Check out the [general developer notes](dev-notes.md), and feel free to ask a question on the [discord server](https://discord.gg/x47DssnGwm). Good luck!
|