diff --git a/pwndbg/aglib/dt.py b/pwndbg/aglib/dt.py new file mode 100644 index 000000000..9b08e242d --- /dev/null +++ b/pwndbg/aglib/dt.py @@ -0,0 +1,138 @@ +""" +Prints structures in a manner similar to Windbg's "dt" command. +""" + +from __future__ import annotations + +from typing import List + +import pwndbg +import pwndbg.aglib.memory +import pwndbg.aglib.typeinfo +import pwndbg.dbg + + +def _field_to_human( + f: pwndbg.dbg_mod.TypeField | pwndbg.dbg_mod.Value | pwndbg.dbg_mod.Type, +) -> str: + if isinstance(f, pwndbg.dbg_mod.TypeField): + t = f.type + elif isinstance(f, pwndbg.dbg_mod.Type): + t = f + elif isinstance(f, pwndbg.dbg_mod.Value): + t = f.type + else: + raise NotImplementedError("unknown type") + + return t.name + + +def dt( + name: str = "", + addr: int | pwndbg.dbg_mod.Value | None = None, + obj: pwndbg.dbg_mod.Value | None = None, +) -> str: + """ + Dump out a structure type Windbg style. + """ + # Return value is a list of strings.of + # We concatenate at the end. + rv: List[str] = [] + + if obj and not name: + t = obj.type + while t.code == pwndbg.dbg_mod.TypeCode.POINTER: + t = t.target() + obj = obj.dereference() + name = str(t) + + # Lookup the type name specified by the user + else: + t = pwndbg.aglib.typeinfo.load(name) + + if not t: + return "" + + # If it's not a struct (e.g. int or char*), bail + if t.code not in ( + pwndbg.dbg_mod.TypeCode.STRUCT, + pwndbg.dbg_mod.TypeCode.TYPEDEF, + pwndbg.dbg_mod.TypeCode.UNION, + ): + return f"Not a structure: {t}" + + # If an address was specified, create a Value of the + # specified type at that address. + if addr is not None: + obj = pwndbg.aglib.memory.get_typed_pointer_value(t, addr) + + # Header, optionally include the name + header = name + if obj: + header = f"{header} @ {hex(int(obj.address))}" + rv.append(header) + + if t.strip_typedefs().code == pwndbg.dbg_mod.TypeCode.ARRAY: + return "Arrays not supported yet" + + if t.strip_typedefs().code not in ( + pwndbg.dbg_mod.TypeCode.STRUCT, + pwndbg.dbg_mod.TypeCode.UNION, + ): + newobj = obj + if not newobj: + newobj = pwndbg.dbg.selected_inferior().create_value(0, t) + + iter_fields = [(field.name, field) for field in newobj.type.fields()] + else: + iter_fields = [(field.name, field) for field in t.fields()] + + for field_name, field in iter_fields: + # Offset into the parent structure + offset = field.bitpos // 8 + bitpos = field.bitpos % 8 + ftype = field.type.strip_typedefs() + extra = _field_to_human(field) + + if obj and obj.type.strip_typedefs().code in ( + pwndbg.dbg_mod.TypeCode.STRUCT, + pwndbg.dbg_mod.TypeCode.UNION, + ): + obj_value = obj[field_name] + if ftype.code == pwndbg.dbg_mod.TypeCode.INT: + extra = hex(int(obj_value)) + elif ( + ftype.code in (pwndbg.dbg_mod.TypeCode.POINTER, pwndbg.dbg_mod.TypeCode.ARRAY) + and ftype.target() == pwndbg.aglib.typeinfo.uchar + ): + data = pwndbg.aglib.memory.read(int(obj_value.address), ftype.sizeof) + extra = " ".join("%02x" % b for b in data) + else: + extra = obj_value.value_to_human_readable() + + # Adjust trailing lines in 'extra' to line up + # This is necessary when there are nested structures. + # Ideally we'd expand recursively if the type is complex. + extra_lines: List[str] = [] + for i, line in enumerate(str(extra).splitlines()): + if i == 0: + extra_lines.append(line) + else: + extra_lines.append(35 * " " + line) + extra = "\n".join(extra_lines) + + bitpos_str = "" if not bitpos else (".%i" % bitpos) + + if obj: + line = " 0x%016x +0x%04x%s %-20s : %s" % ( + int(obj.address) + offset, + offset, + bitpos_str, + field_name, + extra, + ) + else: + line = " +0x%04x%s %-20s : %s" % (offset, bitpos_str, field_name, extra) + rv.append(line) + + return "\n".join(rv) diff --git a/pwndbg/commands/__init__.py b/pwndbg/commands/__init__.py index 1ecde68a2..ab41c7bc3 100644 --- a/pwndbg/commands/__init__.py +++ b/pwndbg/commands/__init__.py @@ -233,7 +233,7 @@ def fix( # no debugger-agnostic architecture functions. Those will come later. # # TODO: Port architecutre functions and `pwndbg.gdblib.regs.fix` to debugger-agnostic API and remove this. - arg = pwndbg.gdblib.regs.fix(arg) + arg = pwndbg.aglib.regs.fix(arg) return target.evaluate_expression(arg) except Exception as e: ex = e @@ -692,7 +692,6 @@ def load_commands() -> None: import pwndbg.commands.binja import pwndbg.commands.branch import pwndbg.commands.cymbol - import pwndbg.commands.dt import pwndbg.commands.godbg import pwndbg.commands.got import pwndbg.commands.got_tracking @@ -732,6 +731,7 @@ def load_commands() -> None: import pwndbg.commands.cyclic import pwndbg.commands.dev import pwndbg.commands.distance + import pwndbg.commands.dt import pwndbg.commands.dumpargs import pwndbg.commands.elf import pwndbg.commands.flags diff --git a/pwndbg/commands/dt.py b/pwndbg/commands/dt.py index 02fee03ca..f27dec886 100644 --- a/pwndbg/commands/dt.py +++ b/pwndbg/commands/dt.py @@ -2,12 +2,11 @@ from __future__ import annotations import argparse -import gdb - +import pwndbg +import pwndbg.aglib.dt +import pwndbg.aglib.vmmap import pwndbg.color import pwndbg.commands -import pwndbg.gdblib.dt -import pwndbg.gdblib.vmmap parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, @@ -24,7 +23,7 @@ parser.add_argument( @pwndbg.commands.ArgparsedCommand(parser) -def dt(typename: str, address: int | gdb.Value | None = None) -> None: +def dt(typename: str, address: int | pwndbg.dbg_mod.Value | None = None) -> None: """ Dump out information on a type (e.g. ucontext_t). @@ -32,4 +31,5 @@ def dt(typename: str, address: int | gdb.Value | None = None) -> None: """ if address is not None: address = pwndbg.commands.fix(str(address)) - print(pwndbg.gdblib.dt.dt(typename, addr=address)) + + print(pwndbg.aglib.dt.dt(typename, addr=address)) diff --git a/pwndbg/dbg/__init__.py b/pwndbg/dbg/__init__.py index c7723f987..06dc4401e 100644 --- a/pwndbg/dbg/__init__.py +++ b/pwndbg/dbg/__init__.py @@ -583,6 +583,16 @@ class Type: Class representing a type in the context of an inferior process. """ + @property + def name(self) -> str: + """ + Returns the name of this type, eg: + - char [16] + - int + - char * + - void * + """ + @property def sizeof(self) -> int: """ diff --git a/pwndbg/dbg/gdb.py b/pwndbg/dbg/gdb.py index 9cdaa54a3..932ce658e 100644 --- a/pwndbg/dbg/gdb.py +++ b/pwndbg/dbg/gdb.py @@ -914,6 +914,11 @@ class GDBType(pwndbg.dbg_mod.Type): return self.inner == other.inner + @property + @override + def name(self) -> str: + return str(self.inner) + @property @override def sizeof(self) -> int: diff --git a/pwndbg/dbg/lldb/__init__.py b/pwndbg/dbg/lldb/__init__.py index e601b8afd..92de09a92 100644 --- a/pwndbg/dbg/lldb/__init__.py +++ b/pwndbg/dbg/lldb/__init__.py @@ -327,6 +327,10 @@ class LLDBType(pwndbg.dbg_mod.Type): return self.inner == other.inner + @property + def name(self) -> str: + return self.inner.name + @property @override def sizeof(self) -> int: diff --git a/pwndbg/gdblib/dt.py b/pwndbg/gdblib/dt.py deleted file mode 100644 index 4d59c1d19..000000000 --- a/pwndbg/gdblib/dt.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -Prints structures in a manner similar to Windbg's "dt" command. -""" - -from __future__ import annotations - -import re -from typing import List - -import gdb - -import pwndbg.gdblib.memory -import pwndbg.gdblib.typeinfo - - -def get_type(v: gdb.Value) -> str: - t = v.type - while not t.name: - if t.code == gdb.TYPE_CODE_PTR: - t = t.target() - return t.name - - -def get_typename(t: gdb.Type) -> str: - return str(t) - - -def get_arrsize(f: gdb.Value) -> int: - t = f.type - if t.code != gdb.TYPE_CODE_ARRAY: - return 0 - t2 = t.target() - return int(t.sizeof / t2.sizeof) - - -def get_field_by_name(obj: gdb.Value, field: str) -> gdb.Value: - # Dereference once - if obj.type.code == gdb.TYPE_CODE_PTR: - obj = obj.dereference() - for f in re.split(r"(->|\.|\[\d+\])", field): - if not f: - continue - if f == "->": - obj = obj.dereference() - elif f == ".": - pass - elif f.startswith("["): - n = int(f.strip("[]")) - obj = obj.cast(obj.dereference().type.pointer()) - obj += n - obj = obj.dereference() - else: - obj = obj[f] - return obj - - -def happy(typename: str) -> str: - prefix = "" - if "unsigned" in typename: - prefix = "u" - typename = typename.replace("unsigned ", "") - return ( - prefix - + { - "char": "char", - "short int": "short", - "long int": "long", - "int": "int", - "long long": "longlong", - "float": "float", - "double": "double", - }[typename] - ) - - -def dt(name: str = "", addr: int | gdb.Value | None = None, obj: gdb.Value | None = None) -> str: - """ - Dump out a structure type Windbg style. - """ - # Return value is a list of strings.of - # We concatenate at the end. - rv: List[str] = [] - - if obj and not name: - t = obj.type - while t.code == (gdb.TYPE_CODE_PTR): - t = t.target() - obj = obj.dereference() - name = str(t) - - # Lookup the type name specified by the user - else: - t = pwndbg.gdblib.typeinfo.load(name) - - if not t: - return "" - - # If it's not a struct (e.g. int or char*), bail - if t.code not in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_TYPEDEF, gdb.TYPE_CODE_UNION): - raise Exception(f"Not a structure: {t}") - - # If an address was specified, create a Value of the - # specified type at that address. - if addr is not None: - obj = pwndbg.gdblib.memory.get_typed_pointer_value(t, addr) - - # Header, optionally include the name - header = name - if obj: - header = f"{header} @ {hex(int(obj.address))}" - rv.append(header) - - if t.strip_typedefs().code == gdb.TYPE_CODE_ARRAY: - return "Arrays not supported yet" - if t.strip_typedefs().code not in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION): - t = {name: obj or gdb.Value(0).cast(t)} - - for name, field in t.items(): - # Offset into the parent structure - o = getattr(field, "bitpos", 0) // 8 - b = getattr(field, "bitpos", 0) % 8 - extra = str(field.type) - ftype = field.type.strip_typedefs() - - if obj and obj.type.strip_typedefs().code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION): - v = obj[name] - - if ftype.code == gdb.TYPE_CODE_INT: - v = hex(int(v)) - if ( - ftype.code in (gdb.TYPE_CODE_PTR, gdb.TYPE_CODE_ARRAY) - and ftype.target() == pwndbg.gdblib.typeinfo.uchar - ): - data = pwndbg.gdblib.memory.read(int(v.address), ftype.sizeof) - v = " ".join("%02x" % b for b in data) - - extra = v - - # Adjust trailing lines in 'extra' to line up - # This is necessary when there are nested structures. - # Ideally we'd expand recursively if the type is complex. - extra_lines: List[str] = [] - for i, line in enumerate(str(extra).splitlines()): - if i == 0: - extra_lines.append(line) - else: - extra_lines.append(35 * " " + line) - extra = "\n".join(extra_lines) - - bitpos = "" if not b else (".%i" % b) - - if obj: - line = " 0x%016x +0x%04x%s %-20s : %s" % ( - int(obj.address) + o, - o, - bitpos, - name, - extra, - ) - else: - line = " +0x%04x%s %-20s : %s" % (o, bitpos, name, extra) - rv.append(line) - - return "\n".join(rv) diff --git a/pyproject.toml b/pyproject.toml index 7c64ef530..df7ce421a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,7 +90,6 @@ module = [ "pwndbg.commands.reload", "pwndbg.commands.version", "pwndbg.exception", - "pwndbg.gdblib.dt", "pwndbg.aglib.dynamic", "pwndbg.gdblib.events", "pwndbg.gdblib.got", diff --git a/tests/gdb-tests/tests/test_command_dt.py b/tests/gdb-tests/tests/test_command_dt.py index fdaf340d7..3fc5b2652 100644 --- a/tests/gdb-tests/tests/test_command_dt.py +++ b/tests/gdb-tests/tests/test_command_dt.py @@ -22,8 +22,8 @@ def test_command_dt_works_with_address(start_binary): exp_regex = ( "struct tcache_perthread_struct @ 0x[0-9a-f]+\n" - " 0x[0-9a-f]+ \\+0x0000 counts : uint16_t \\[64\\]\n" - " 0x[0-9a-f]+ \\+0x[0-9a-f]{4} entries : tcache_entry \\*\\[64\\]\n" + " 0x[0-9a-f]+ \\+0x0000 counts : \\{[0-9]+, [0-9]+ \\}\n" + " 0x[0-9a-f]+ \\+0x[0-9a-f]{4} entries : \\{0x[0-9a-f]+, 0x[0-9a-f]+ \\}\n" ) assert re.match(exp_regex, out) diff --git a/tests/gdb-tests/tests/test_cymbol.py b/tests/gdb-tests/tests/test_cymbol.py index 376ff9d59..188d317db 100644 --- a/tests/gdb-tests/tests/test_cymbol.py +++ b/tests/gdb-tests/tests/test_cymbol.py @@ -2,11 +2,11 @@ from __future__ import annotations import os +import pwndbg.aglib.dt import pwndbg.dbg if pwndbg.dbg.is_gdblib_available(): import pwndbg.commands.cymbol - import pwndbg.gdblib.dt import tests @@ -25,7 +25,7 @@ def create_symbol_file(symbol, source): def check_symbol_existance(symbol_type): try: - pwndbg.gdblib.dt.dt(symbol_type) + pwndbg.aglib.dt.dt(symbol_type) except Exception as exception: # In case it is an AttributeError symbol_type doesn't exists. assert isinstance(exception, AttributeError) @@ -62,7 +62,7 @@ def test_cymbol(start_binary): " +0x0004 b : char [16]\n" " +0x0018 c : char *\n" " +0x0020 d : void *" - ) == pwndbg.gdblib.dt.dt("example_t").strip() + ) == pwndbg.aglib.dt.dt("example_t").strip() # Test whether unload_loaded_symbol() works properly. pwndbg.commands.cymbol.unload_loaded_symbol("example")