diff --git a/pwndbg/commands/__init__.py b/pwndbg/commands/__init__.py index 65c9a538d..8fea86a9a 100644 --- a/pwndbg/commands/__init__.py +++ b/pwndbg/commands/__init__.py @@ -558,6 +558,7 @@ def load_commands(): import pwndbg.commands.search import pwndbg.commands.segments import pwndbg.commands.shell + import pwndbg.commands.slab import pwndbg.commands.stack import pwndbg.commands.start import pwndbg.commands.telescope diff --git a/pwndbg/commands/slab.py b/pwndbg/commands/slab.py new file mode 100644 index 000000000..878c176d7 --- /dev/null +++ b/pwndbg/commands/slab.py @@ -0,0 +1,42 @@ +import argparse + +from tabulate import tabulate + +import pwndbg.commands +import pwndbg.gdblib.kernel.slab +from pwndbg.gdblib.kernel.slab import oo_objects +from pwndbg.gdblib.kernel.slab import oo_order + +parser = argparse.ArgumentParser(description="Prints information about the SLUB allocator") +parser.add_argument( + "filter_", + metavar="filter", + type=str, + nargs="?", + help="Only show caches that contain the given filter string", +) + + +@pwndbg.commands.ArgparsedCommand(parser) +@pwndbg.commands.OnlyWhenQemuKernel +@pwndbg.commands.OnlyWithKernelDebugSyms +def slab(filter_=None): + results = [] + for cache in pwndbg.gdblib.kernel.slab.caches(): + name = pwndbg.gdblib.memory.string(cache["name"]).decode("ascii") + if filter_ and filter_ not in name: + continue + order = oo_order(int(cache["oo"]["x"])) + objects = oo_objects(int(cache["oo"]["x"])) + results.append( + [ + name, + objects, + int(cache["size"]), + int(cache["object_size"]), + int(cache["inuse"]), + order, + ] + ) + + print(tabulate(results, headers=["Name", "# Objects", "Size", "Obj Size", "# inuse", "order"])) diff --git a/pwndbg/gdblib/kernel/macros.py b/pwndbg/gdblib/kernel/macros.py new file mode 100644 index 000000000..738374dc2 --- /dev/null +++ b/pwndbg/gdblib/kernel/macros.py @@ -0,0 +1,20 @@ +import gdb + + +def offset_of(typename: str, fieldname: str): + ptr_type = gdb.lookup_type(typename).pointer() + dummy = gdb.Value(0).cast(ptr_type) + return int(dummy[fieldname].address) + + +def container_of(ptr, typename: str, fieldname: str): + ptr_type = gdb.lookup_type(typename).pointer() + obj_addr = int(ptr) - offset_of(typename, fieldname) + return gdb.Value(obj_addr).cast(ptr_type) + + +def for_each_entry(head, typename, field): + addr = head["next"] + while addr != head.address: + yield container_of(addr, typename, field) + addr = addr.dereference()["next"] diff --git a/pwndbg/gdblib/kernel/slab.py b/pwndbg/gdblib/kernel/slab.py new file mode 100644 index 000000000..d37fdf2f9 --- /dev/null +++ b/pwndbg/gdblib/kernel/slab.py @@ -0,0 +1,21 @@ +import gdb + +from pwndbg.gdblib.kernel.macros import for_each_entry + + +def caches(): + slab_caches = gdb.lookup_global_symbol("slab_caches").value() + for slab_cache in for_each_entry(slab_caches, "struct kmem_cache", "list"): + yield slab_cache + + +OO_SHIFT = 16 +OO_MASK = (1 << OO_SHIFT) - 1 + + +def oo_order(x: int) -> int: + return int(x) >> OO_SHIFT + + +def oo_objects(x: int) -> int: + return int(x) & OO_MASK diff --git a/requirements.txt b/requirements.txt index bcb339ce7..27f95f355 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,6 @@ pycparser==2.21 pyelftools==0.29 Pygments==2.13.0 ROPGadget==7.1 +tabulate==0.8.10 unicorn==2.0.1; python_version >= '3.7' unicorn==2.0.0rc7; python_version < '3.7'