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.
233 lines
7.9 KiB
Python
233 lines
7.9 KiB
Python
"""
|
|
If the PWNDBG_DOCGEN_VERIFY environment variable
|
|
is set, then : Exit with non-zero exit status if the docs/functions/ files
|
|
aren't up to date with the sources. Don't modify anything.
|
|
|
|
If it isn't, this fixes up the docs/functions/ files to be up
|
|
to date with the information from the sources.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import textwrap
|
|
from typing import Dict
|
|
from typing import Tuple
|
|
|
|
from mdutils.mdutils import MdUtils
|
|
|
|
from scripts._docs.function_docs_common import BASE_PATH
|
|
from scripts._docs.function_docs_common import ExtractedFunction
|
|
from scripts._docs.function_docs_common import extracted_filename
|
|
from scripts._docs.gen_docs_generic import ALL_DEBUGGERS
|
|
from scripts._docs.gen_docs_generic import strip_ansi_color
|
|
from scripts._docs.gen_docs_generic import update_files_simple
|
|
from scripts._docs.gen_docs_generic import verify_existence
|
|
from scripts._docs.gen_docs_generic import verify_files_simple
|
|
|
|
INTRO_TEXT = """
|
|
Pwndbg provides a set of functions which can be used during expression evaluation to
|
|
quickly perform common calculations. These can even be passed to other commands as arguments.
|
|
Currently, they **only work in gdb**.
|
|
|
|
To see a list of all functions, including those built into GDB, use `help function`. To see
|
|
the help of any given function use `help function function_name`. Function invocation must
|
|
include a preceding $ sign and must include brackets. For instance, invoke the `environ`
|
|
function like so:
|
|
```
|
|
pwndbg> p $environ("LANG")
|
|
$2 = (signed char *) 0x7fffffffe6da "LANG=en_US.UTF-8"
|
|
```
|
|
If the result of the function is being passed to a Pwndbg command, make sure to either escape
|
|
the function argument's quotes, or put the whole function call in quotes.
|
|
```
|
|
pwndbg> tele $environ("LANG")
|
|
usage: telescope [-h] [-r] [-f] [-i] [address] [count]
|
|
telescope: error: argument address: debugger couldn't resolve argument '$environ(LANG)':
|
|
No symbol "LANG" in current context.
|
|
pwndbg> tele $environ(\\"LANG\\")
|
|
00:0000│ 0x7fffffffe6cf ◂— 'LANG=en_US.UTF-8'
|
|
01:0008│ 0x7fffffffe6d7 ◂— 'US.UTF-8'
|
|
02:0010│ 0x7fffffffe6df ◂— 0x4e49475542454400
|
|
[...]
|
|
pwndbg> tele '$environ("LANG")'
|
|
00:0000│ 0x7fffffffe6cf ◂— 'LANG=en_US.UTF-8'
|
|
01:0008│ 0x7fffffffe6d7 ◂— 'US.UTF-8'
|
|
02:0010│ 0x7fffffffe6df ◂— 0x4e49475542454400
|
|
[...]
|
|
```
|
|
## Pwndbg functions
|
|
""".strip()
|
|
|
|
|
|
def get_signature_markdown(func: ExtractedFunction, debugger: str):
|
|
func_signature_code = f"""
|
|
``` {{.python .no-copy}}
|
|
{func.name}{func.signature}
|
|
```
|
|
"""
|
|
if (
|
|
" object at " in func.signature or "<" in func.signature
|
|
): # '>' is valid in type annotation (->)
|
|
print(f'Signature of {func.name} (from {debugger}) is rendered as "{func.signature}",')
|
|
print(
|
|
"please edit the sanitize_signature() function (./scripts/_docs/extract_function_docs.py) to display the signature better in the docs."
|
|
)
|
|
sys.exit(5)
|
|
|
|
return func_signature_code
|
|
|
|
|
|
def convert_to_markdown(extracted: list[Tuple[str, list[ExtractedFunction]]]) -> Dict[str, str]:
|
|
"""
|
|
Returns:
|
|
A dict which maps filenames to their markdown contents.
|
|
It will have only one item (the index.md).
|
|
"""
|
|
markdowned = {}
|
|
|
|
mdFile = MdUtils(INDEX_PATH)
|
|
mdFile.new_header(level=1, title="Functions")
|
|
mdFile.new_paragraph(INTRO_TEXT)
|
|
|
|
all_functions: set[str] = set()
|
|
for _, funcs in extracted:
|
|
all_functions.update([func.name for func in funcs])
|
|
|
|
for func_name in sorted(all_functions):
|
|
# Make a (debugger name, function) list in case some
|
|
# debuggers disagree on what some function should
|
|
# display. We won't add debuggers that don't have the
|
|
# function.
|
|
func_variants: list[Tuple[str, ExtractedFunction]] = []
|
|
|
|
for debugger, dfuncs in extracted:
|
|
# Slow but whatever
|
|
for dfunc in dfuncs:
|
|
if func_name == dfunc.name:
|
|
func_variants.append((debugger, dfunc))
|
|
|
|
assert func_variants
|
|
|
|
mdFile.new_paragraph(f"### **{func_name}**")
|
|
# NOTE: We aren't saying anything about supported
|
|
# debuggers since all functions only work in gdb for now.
|
|
|
|
debuggers_agree = all(x[1] == func_variants[0][1] for x in func_variants)
|
|
|
|
if debuggers_agree:
|
|
mdFile.new_paragraph(get_signature_markdown(func_variants[0][1], debugger))
|
|
mdFile.new_paragraph(func_variants[0][1].docstring.replace("Example:", "#### Example"))
|
|
else:
|
|
for debugger, dfunc in sorted(func_variants):
|
|
# Content tabs
|
|
# https://squidfunk.github.io/mkdocs-material/reference/content-tabs/
|
|
mdFile.write(f'\n=== "{debugger.upper()}"')
|
|
|
|
sig = get_signature_markdown(dfunc, debugger)
|
|
sig = textwrap.indent(sig, " ")
|
|
mdFile.new_paragraph(sig)
|
|
|
|
docs = dfunc.docstring.replace("Example:", "#### Example")
|
|
docs = textwrap.indent(docs, " ")
|
|
mdFile.new_paragraph(docs)
|
|
|
|
mdFile.new_paragraph("----------")
|
|
|
|
hide_nav = "---\nhide:\n - navigation\n---\n"
|
|
autogen_warning = (
|
|
"<!-- THIS WHOLE FILE IS AUTOGENERATED. DO NOT MODIFY IT. See scripts/generate-docs.sh -->"
|
|
)
|
|
markdowned[INDEX_PATH] = (
|
|
hide_nav + autogen_warning + "\n" + strip_ansi_color(mdFile.get_md_text())
|
|
)
|
|
return markdowned
|
|
|
|
|
|
def read_extracted() -> list[Tuple[str, list[ExtractedFunction]]]:
|
|
"""
|
|
Read json files from disk.
|
|
|
|
Returns:
|
|
A list of tuples of the form: (debugger name, list of
|
|
convenience functions that debugger supports).
|
|
"""
|
|
|
|
result: list[Tuple[str, list[ExtractedFunction]]] = []
|
|
|
|
for debugger in ALL_DEBUGGERS:
|
|
filepath = extracted_filename(debugger)
|
|
print(f"Consuming {filepath}..")
|
|
|
|
with open(filepath, "r") as file:
|
|
raw_data = json.loads(file.read())
|
|
|
|
# Convert the dict objs to ExtractedFunction
|
|
data = [ExtractedFunction(**func) for func in raw_data]
|
|
|
|
result.append((debugger, data))
|
|
|
|
# We consumed the temporary file, we can delete it now.
|
|
os.remove(filepath)
|
|
|
|
return result
|
|
|
|
|
|
INDEX_PATH = os.path.join(BASE_PATH, "index.md")
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) > 1:
|
|
print("This script doesn't accept any arguments.")
|
|
print("See top of the file for usage.")
|
|
sys.exit(1)
|
|
|
|
just_verify = False
|
|
if os.getenv("PWNDBG_DOCGEN_VERIFY"):
|
|
just_verify = True
|
|
|
|
print("\n==== Function Documentation ====")
|
|
|
|
extracted = read_extracted()
|
|
markdowned = convert_to_markdown(extracted)
|
|
assert len(markdowned) == 1 # Only index.md
|
|
|
|
if just_verify:
|
|
print("Checking if all files are in place..")
|
|
missing, extra = verify_existence(list(markdowned.keys()), BASE_PATH)
|
|
if missing or extra:
|
|
print("To add mising files please run ./scripts/generate-docs.sh.")
|
|
print("To remove extra files please remove them manually.")
|
|
sys.exit(2)
|
|
print("Every file is where it should be!")
|
|
|
|
print("Verifying contents...")
|
|
err = verify_files_simple(markdowned)
|
|
if err:
|
|
print("VERIFICATION FAILED. The files differ from what would be auto-generated.")
|
|
print("Error:", err)
|
|
print("Please run ./scripts/generate-docs.sh from project root and commit the changes.")
|
|
sys.exit(3)
|
|
|
|
print("Verification successful!")
|
|
else:
|
|
print("Updating files...")
|
|
update_files_simple(markdowned)
|
|
print("Update successful.")
|
|
|
|
missing, extra = verify_existence(list(markdowned.keys()), BASE_PATH)
|
|
assert (
|
|
not missing
|
|
and "Some files (and not the index) are missing, which should be impossible."
|
|
)
|
|
|
|
if extra:
|
|
print("Please delete the extra files by hand.")
|
|
sys.exit(4)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|