diff --git a/docs/contributing/adding-a-parameter.md b/docs/contributing/adding-a-parameter.md index 699993243..920251a8a 100644 --- a/docs/contributing/adding-a-parameter.md +++ b/docs/contributing/adding-a-parameter.md @@ -1,20 +1,174 @@ # Adding a Configuration Option +Configuration options are also called "parameters" in the source. Let's take a look at an existing parameter `gdb-workaround-stop-event` defined in `pwndbg/gdblib/events.py`. +```python +DISABLED = "disabled" +DISABLED_DEADLOCK = "disabled-deadlock" +ENABLED = "enabled" + +gdb_workaround_stop_event = config.add_param( + "gdb-workaround-stop-event", + DISABLED, + "asynchronous stop events to improve 'commands' functionality", + help_docstring=f""" +Note that this may cause unexpected behavior with pwndbg or gdb.execute. + +Values explained: ++ `{DISABLED}` - Disable the workaround (default). ++ `{DISABLED_DEADLOCK}` - Disable only deadlock detection; deadlocks may still occur. ++ `{ENABLED}` - Enable asynchronous stop events; gdb.execute may behave unexpectedly (asynchronously). + """, + param_class=pwndbg.lib.config.PARAM_ENUM, + enum_sequence=[DISABLED, DISABLED_DEADLOCK, ENABLED], +) +``` +To understand it, let's also look at the signature of the `Config.add_param` function defined in `pwndbg.lib.config.py`: ```python -import pwndbg + def add_param( + self, + name: str, + default: Any, + set_show_doc: str, + *, + help_docstring: str = "", + param_class: int | None = None, + enum_sequence: Sequence[str] | None = None, + scope: Scope = Scope.config, + ) -> Parameter: + # ... +``` +So, the first argument specifies the name by which the parameter will be used inside the debugger. The second argument specifies the default value of the parameter. +## set_show_doc +The third argument is a very brief description of what the parameter is for. The argument is called `set_show_doc` due to how it is used in GDB. +```text +pwndbg> set gdb-workaround-stop-event enabled +Set asynchronous stop events to improve 'commands' functionality to 'enabled'. + |------------------------------------------------------------| +``` +```text +pwndbg> show gdb-workaround-stop-event +Asynchronous stop events to improve 'commands' functionality is 'enabled'. [...] +|-----------------------------------------------------------| +``` +It is therefore recommended to use a noun phrase rather than describe an action. However, it sometimes may be necessary to break this rule to retain the brevity of the description. -pwndbg.config.add_param("config-name", False, "example configuration option") +The `set_show_doc` argument should be short because it is displayed with the `config` family of commands. +```text +pwndbg> config +Name Documentation Value (Default) +---------------------------------------------------------------------------------------------------------------------------- +ai-anthropic-api-key Anthropic API key '' +ai-history-size maximum number of questions and answers to keep in the prompt 3 +ai-max-tokens the maximum number of tokens to return in the response 100 +ai-model the name of the large language model to query 'gpt-3.5-turbo' +ai-ollama-endpoint Ollama API endpoint '' +ai-openai-api-key OpenAI API key '' +ai-show-usage whether to show how many tokens are used with each OpenAI API call off +ai-stack-depth rows of stack context to include in the prompt for the ai command 16 +ai-temperature the temperature specification for the LLM query 0 +attachp-resolution-method how to determine the process to attach when multiple candidates exists 'ask' +auto-explore-auxv stack exploration for AUXV information; it may be really slow 'warn' +auto-explore-pages whether to try to infer page permissions when memory maps are missing 'warn' +auto-explore-stack stack exploration; it may be really slow 'warn' +auto-save-search automatically pass --save to "search" command off +bn-autosync whether to automatically run bn-sync every step off ``` +Because of the various contexts in which a parameter can be show, the first letter of the `set_show_doc` string should be lowercase (unless the first word is a name or an abbreviation) and there should be no punctuation at the end. This way, pwndbg and gdb can more easily modify the string to fit it into these contexts. +## help_docstring +While `help_docstring` is not mandatory, it is highly recommended to use it. Put a detailed explanation of what the parameter does here, and explain any caveats. This string does not have a size limit and is shown with the following command in GDB: +```text +pwndbg> help set gdb-workaround-stop-event +Set asynchronous stop events to improve 'commands' functionality. +Note that this may cause unexpected behavior with pwndbg or gdb.execute. + +Values explained: -`pwndbg.config.config_name` will now refer to the value of the configuration option, and it will default to `False` if not set. ++ `disabled` - Disable the workaround (default). ++ `disabled-deadlock` - Disable only deadlock detection; deadlocks may still occur. ++ `enabled` - Enable asynchronous stop events; gdb.execute may behave unexpectedly (asynchronously). -## Configuration Docstrings (GDB) +Default: 'disabled' +Valid values: 'disabled', 'disabled-deadlock', 'enabled' +``` +Note that the last two lines are automatically generated by pwndbg. +!!! note + There is currently no way to display this string nor the `set_show_doc` string in LLDB. + +When writing this explanation, it is important to take into account how it will be displayed [in the documentation](https://pwndbg.re/pwndbg/dev/configuration/) after being parsed as markdown. See what `gdb-workaround-stop-event` looks like here: https://pwndbg.re/pwndbg/dev/configuration/config/#gdb-workaround-stop-event . If there wasn't an empty line between `Values explained:` and ``+ `disabled`..`` the list wouldn't have rendered properly. +## param_class +This argument describes the type of the parameter. It will be used by GDB to perform input validation when the parameter is being set so it is important to set this to the correct value. The possible values are defined in `pwndbg.lib.config`, use the most restrictive one that fits: +```python +# Boolean value. True or False, same as in Python. +PARAM_BOOLEAN = 0 +# Boolean value, or 'auto'. +PARAM_AUTO_BOOLEAN = 1 +# Signed integer value. Disallows zero. +PARAM_INTEGER = 2 +# Signed integer value. +PARAM_ZINTEGER = 3 +# Unsigned integer value. Disallows zero. +PARAM_UINTEGER = 4 +# Unsigned integer value. +PARAM_ZUINTEGER = 5 +# Unlimited ZUINTEGER. +PARAM_ZUINTEGER_UNLIMITED = 6 +# String value. Accepts escape sequences. +PARAM_STRING = 7 +# String value, accepts only one of a number of possible values, specified at +# parameter creation. +PARAM_ENUM = 8 +# String value corresponding to the name of a file, if present. +PARAM_OPTIONAL_FILENAME = 9 +``` +For more information (for instance about what `None` or `"unlimited"` mean) see https://sourceware.org/gdb/current/onlinedocs/gdb.html/Parameters-In-Python.html . +### enum_sequence +If the `param_class` is set to `pwndbg.lib.config.PARAM_ENUM` then the `enum_sequence` argument must be supplied as well. It should constitute an array of legal values. GDB and (our) LLDB (driver) won't allow setting the parameter to any other value. The legal values will be automatically displayed at the end of `help_docstring` as previously shown. -TODO: There are many places GDB shows docstrings, and they show up slightly differently in each place, we should give examples of this +If it isn't immediately obvious what the enum values do, explain them in `help_docstring` using same format that `gdb-workaround-stop-event` uses. +## scope +The `scope` argument has the default value of `pwndbg.lib.config.Scope.config` and is used to group parameters. The legal values are: +```python +class Scope(Enum): + # If you want to add another scope here, don't forget to add + # a command which prints it! + config = 1 + theme = 2 + heap = 3 +``` +The parameters of each scope are printed using a different command. The `config` scope is printed with [`config`](https://pwndbg.re/pwndbg/dev/commands/pwndbg/config/), the `heap` scope is printed with [`heap-config`](https://pwndbg.re/pwndbg/dev/commands/pwndbg/heap-config/) and the `theme` scope is printed with [`theme`](https://pwndbg.re/pwndbg/dev/commands/pwndbg/theme/). The `config` and `theme` scopes also have corresponding [`configfile`](https://pwndbg.re/pwndbg/dev/commands/pwndbg/configfile/) and [`themefile`](https://pwndbg.re/pwndbg/dev/commands/pwndbg/themefile/) commands which export the values of all the parameters from those scopes. +### The `theme` scope +You should never directly pass this scope to `pwndbg.config.add_param`. Instead use the `pwndbg.color.theme.add_param` and `pwndbg.color.theme.add_color_param` wrapper commands like this: +```python +# pwndbg/aglib/nearpc.py +nearpc_branch_marker = pwndbg.color.theme.add_param( + "nearpc-branch-marker", " ↓", "branch marker line for nearpc command" +) +``` +```python +# pwndbg/color/context.py +config_highlight_color = theme.add_color_param( + "highlight-color", "green,bold", "color added to highlights like source/pc" +) +``` +## Using the parameter in code +Usually when a parameter is defined its value is also set to a variable, for instance `gdb_workaround_stop_event = ...` in the initial example. This isn't necessary, as all registered parameters are available as `pwndbg.config.` so in our example, we could also access the `gdb-workaround-stop-event` parameter as `pwndbg.config.gdb_workaround_stop_event`. -* When using `pwndbg.config.add_param` to add a new config, there are a few things to keep in mind: - * For the `set_show_doc` parameter, it is best to use a noun phrase like "the value of something" to ensure that the output is grammatically correct. - * For the `help_docstring` parameter, you can use the output of `help set follow-fork-mode` as a guide for formatting the documentation string if the config is an enum type. - * For the `param_class` parameter - * See the [documentation](https://sourceware.org/gdb/onlinedocs/gdb/Parameters-In-Python.html) for more information. - * If you use `gdb.PARAM_ENUM` as `param_class`, you must pass a list of strings to the `enum_sequence` parameter. +That being said, defining the variable can reduce code verbosity: +```python +# pwndbg/aglib/godbg.py +line_width = pwndbg.config.add_param( + "go-dump-line-width", 80, "the soft line width for go-dump pretty printing" +) +``` +Since the variable is scoped to the `godbg.py` file, its name can be short, and we don't have to write `pwndbg.config.go_dump_line_width` every time. +### Using color parameters +Note that the `theme.add_color_param()` function returns a `ColorParameter` object instead of a `Parameter`. The parameter should be used via its `color_function()` method: +```python +# pwndbg/aglib/godbg.py +def fmt_debug(self, val: str, default: str = "") -> str: + if self.debug: + return debug_color.color_function(val) + else: + return default +``` +Though you will also see `generateColorFunction(debug_color)(val)` being used in the code to the same effect. \ No newline at end of file diff --git a/docs/contributing/index.md b/docs/contributing/index.md index 5a438fca2..e2d9ee79c 100644 --- a/docs/contributing/index.md +++ b/docs/contributing/index.md @@ -12,32 +12,22 @@ For common tasks see: + [Adding a configuration option](adding-a-parameter.md) + [Improving annotations](improving-annotations.md) -Regardless of the contents of your PR, you will need to [lint](#linting) and [test](#running-tests) your code -so make sure to read those sections. It is also likely you will need to [update the documentation](#updating-documentation). - -Read [General developer notes](2-dev-notes.md) to get more familiar with the various systems in place in -pwndbg. If you have any questions don't hesitate to ask us on our [discord server](https://discord.gg/x47DssnGwm)! +Regardless of the contents of your PR, you will need to [lint](#linting) and [test](#running-tests) your code so make sure to read those sections. It is also likely you will need to [update the documentation](#updating-documentation). +Read [General developer notes](2-dev-notes.md) to get more familiar with the various systems in place in pwndbg. If you have any questions don't hesitate to ask us on our [discord server](https://discord.gg/x47DssnGwm)! ## Linting -The `lint.sh` script runs isort, ruff, shfmt, and vermin. isort and ruff (mostly) are able to automatically fix -any issues they detect. You may apply all available fixes by running +The `lint.sh` script runs isort, ruff, shfmt, and vermin. isort and ruff (mostly) are able to automatically fix any issues they detect. You may apply all available fixes by running ```{.bash .copy} ./lint.sh -f ``` !!! note - You can find the configuration files for these tools in `pyproject.toml` or by checking the arguments - passed inside `lint.sh`. - -When submitting a PR, the continuous integration (CI) job defined in `.github/workflows/lint.yml` will verify -that running `./lint.sh` succeeds, otherwise the job will fail and we won't be able to merge your PR. + You can find the configuration files for these tools in `pyproject.toml` or by checking the arguments passed inside `lint.sh`. -It is recommended to enable the pre-push git hook to run the lint if you haven't already done so. You may -re-run `./setup-dev.sh` to set it. +When submitting a PR, the continuous integration (CI) job defined in `.github/workflows/lint.yml` will verify that running `./lint.sh` succeeds, otherwise the job will fail and we won't be able to merge your PR. +It is recommended to enable the pre-push git hook to run the lint if you haven't already done so. You may re-run `./setup-dev.sh` to set it. ## Running tests -Your PR will not be merged without passing the testing CI. Moreover, it is highly recommended you write a new -test or update an existing test whenever adding new functionality to pwndbg. To see how to do this, check out -[Writing tests](3-writing-tests.md). +Your PR will not be merged without passing the testing CI. Moreover, it is highly recommended you write a new test or update an existing test whenever adding new functionality to pwndbg. To see how to do this, check out [Writing tests](3-writing-tests.md). To run the tests in the same environment as the testing CI, you can use the following docker commands. ```{.bash .copy} @@ -48,31 +38,27 @@ docker compose run --rm --build ubuntu24.04-mount ./qemu-tests.sh # Unit tests docker compose run --rm --build ubuntu24.04-mount ./unit-tests.sh ``` -This comes in handy particularly for cross-architecture tests because the docker environment has all the -cross-compilers installed. The active `pwndbg` directory is mounted, preventing the need for a full rebuild -whenever you update the codebase. +This comes in handy particularly for cross-architecture tests because the docker environment has all the cross-compilers installed. The active `pwndbg` directory is mounted, preventing the need for a full rebuild whenever you update the codebase. -Remove the `-mount` if you want the tests to run from a clean slate (no files are mounted, meaning all binaries -are recompiled each time). +Remove the `-mount` if you want the tests to run from a clean slate (no files are mounted, meaning all binaries are recompiled each time). -If you wish to focus on some failing tests, you can filter the tests to run by providing an argument to the -script, such as ` ./tests.sh heap`, which will only run tests that contain "heap" in the name. See -`./tests.sh --help` for more information and other options. You can also do this with the cross-arch tests. +If you wish to focus on some failing tests, you can filter the tests to run by providing an argument to the script, such as ` ./tests.sh heap`, which will only run tests that contain "heap" in the name. See `./tests.sh --help` for more information and other options. You can also do this with the cross-arch tests. If you want to, you may also [run the tests with nix](#running-tests-with-nix) or [run them bare](#running-without-docker). TODO: Create a script for running kernel tests instead of running them with `./tests/qemu-tests/tests.sh`. #### Running tests with nix -You will need to build a nix-compatible `gdbinit.py` file, which you can do with `nix build .#pwndbg-dev`. Then -simply run the test by adding the `--nix` flag: +You will need to build a nix-compatible `gdbinit.py` file, which you can do with +```{.bash .copy} +nix build .#pwndbg-dev +``` +Then simply run the test by adding the `--nix` flag: ```{.bash .copy} ./tests.sh --nix [filter] ``` - #### Running without docker -If you wish to improve pwndbg support for your distribution (or the testing infrastructure) you may run the -testing suite without the docker container. +If you wish to improve pwndbg support for your distribution (or the testing infrastructure) you may run the testing suite without the docker container. The commands are analogous to the docker commands. ```{.bash .copy} @@ -84,8 +70,7 @@ The commands are analogous to the docker commands. ./unit-tests.sh ``` -To run the kernel tests you will need to install the appropriate qemu-system packages for your distribution. -Then download the kernel images with +To run the kernel tests you will need to install the appropriate qemu-system packages for your distribution. Then download the kernel images with ```{.bash .copy} ./tests/qemu-tests/download_images.sh ``` diff --git a/pwndbg/gdblib/config.py b/pwndbg/gdblib/config.py index a1f9cbe25..4e21afc48 100644 --- a/pwndbg/gdblib/config.py +++ b/pwndbg/gdblib/config.py @@ -28,14 +28,14 @@ import pwndbg.lib.config CLASS_MAPPING = { pwndbg.lib.config.PARAM_BOOLEAN: gdb.PARAM_BOOLEAN, pwndbg.lib.config.PARAM_AUTO_BOOLEAN: gdb.PARAM_AUTO_BOOLEAN, + pwndbg.lib.config.PARAM_INTEGER: gdb.PARAM_INTEGER, pwndbg.lib.config.PARAM_ZINTEGER: gdb.PARAM_ZINTEGER, - pwndbg.lib.config.PARAM_STRING: gdb.PARAM_STRING, + pwndbg.lib.config.PARAM_UINTEGER: gdb.PARAM_UINTEGER, pwndbg.lib.config.PARAM_ZUINTEGER: gdb.PARAM_ZUINTEGER, + pwndbg.lib.config.PARAM_ZUINTEGER_UNLIMITED: gdb.PARAM_ZUINTEGER_UNLIMITED, + pwndbg.lib.config.PARAM_STRING: gdb.PARAM_STRING, pwndbg.lib.config.PARAM_ENUM: gdb.PARAM_ENUM, pwndbg.lib.config.PARAM_OPTIONAL_FILENAME: gdb.PARAM_OPTIONAL_FILENAME, - pwndbg.lib.config.PARAM_ZUINTEGER_UNLIMITED: gdb.PARAM_ZUINTEGER_UNLIMITED, - pwndbg.lib.config.PARAM_INTEGER: gdb.PARAM_INTEGER, - pwndbg.lib.config.PARAM_UINTEGER: gdb.PARAM_UINTEGER, } diff --git a/pwndbg/gdblib/events.py b/pwndbg/gdblib/events.py index 8f81e1630..0a8a23b3a 100644 --- a/pwndbg/gdblib/events.py +++ b/pwndbg/gdblib/events.py @@ -32,6 +32,7 @@ DISABLED_DEADLOCK = "disabled-deadlock" ENABLED = "enabled" debug = config.add_param("debug-events", False, "display internal event debugging info") + gdb_workaround_stop_event = config.add_param( "gdb-workaround-stop-event", DISABLED, diff --git a/pwndbg/lib/config.py b/pwndbg/lib/config.py index 40a1e61a5..58cbb391e 100644 --- a/pwndbg/lib/config.py +++ b/pwndbg/lib/config.py @@ -15,25 +15,25 @@ T = TypeVar("T") # Boolean value. True or False, same as in Python. PARAM_BOOLEAN = 0 +# Boolean value, or 'auto'. +PARAM_AUTO_BOOLEAN = 1 +# Signed integer value. Disallows zero. +PARAM_INTEGER = 2 # Signed integer value. -PARAM_ZINTEGER = 1 -# String value. Accepts escape sequences. -PARAM_STRING = 2 +PARAM_ZINTEGER = 3 +# Unsigned integer value. Disallows zero. +PARAM_UINTEGER = 4 # Unsigned integer value. -PARAM_ZUINTEGER = 3 +PARAM_ZUINTEGER = 5 +# Unlimited ZUINTEGER. +PARAM_ZUINTEGER_UNLIMITED = 6 +# String value. Accepts escape sequences. +PARAM_STRING = 7 # String value, accepts only one of a number of possible values, specified at # parameter creation. -PARAM_ENUM = 4 +PARAM_ENUM = 8 # String value corresponding to the name of a file, if present. -PARAM_OPTIONAL_FILENAME = 5 -# Boolean value, or 'auto'. -PARAM_AUTO_BOOLEAN = 6 -# Unlimited ZUINTEGER. -PARAM_ZUINTEGER_UNLIMITED = 7 -# Signed integer value. Disallows zero. -PARAM_INTEGER = 8 -# Unsigned integer value. Disallows zero. -PARAM_UINTEGER = 9 +PARAM_OPTIONAL_FILENAME = 9 PARAM_CLASSES = { # The Python boolean values, True and False are the only valid values. @@ -247,7 +247,7 @@ class Config: assert set_show_doc[-1] != "." and "Don't end set_show_doc with punctuation." assert ( HELP_DEFAULT_PREFIX not in help_docstring - and f"Having the string '{HELP_DEFAULT_PREFIX }' in the help_docstring " + and f"Having the string '{HELP_DEFAULT_PREFIX}' in the help_docstring " "messes with documentation generation. Please remove it, it is automatically generated." ) assert (