From db27d18ee4a8788a4fe37394f3a8f5b83b587519 Mon Sep 17 00:00:00 2001 From: patryk4815 Date: Wed, 18 Jun 2025 18:44:25 +0200 Subject: [PATCH] =?UTF-8?q?Revert=20"mallocng:=20print=20information=20abo?= =?UTF-8?q?ut=20slot,=20group,=20meta=20at=20given=20address=20=E2=80=A6"?= =?UTF-8?q?=20(#3115)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 169f9373264027beca96c2eff4d9f0f5b70466c8. --- docs/commands/index.md | 3 - docs/commands/musl/mallocng-group.md | 25 -- docs/commands/musl/mallocng-meta.md | 25 -- docs/commands/musl/mallocng-slot-user.md | 26 -- pwndbg/aglib/heap/heap.py | 20 + pwndbg/aglib/heap/mallocng.py | 537 ----------------------- pwndbg/aglib/memory.py | 8 - pwndbg/aglib/typeinfo.py | 2 - pwndbg/commands/mallocng.py | 328 +------------- pwndbg/dbg/__init__.py | 2 +- pwndbg/lib/pretty_print.py | 70 +-- 11 files changed, 38 insertions(+), 1008 deletions(-) delete mode 100644 docs/commands/musl/mallocng-group.md delete mode 100644 docs/commands/musl/mallocng-meta.md delete mode 100644 docs/commands/musl/mallocng-slot-user.md delete mode 100644 pwndbg/aglib/heap/mallocng.py diff --git a/docs/commands/index.md b/docs/commands/index.md index dd32a27ed..24147e191 100644 --- a/docs/commands/index.md +++ b/docs/commands/index.md @@ -256,9 +256,6 @@ ## musl - [mallocng-explain](musl/mallocng-explain.md) - Gives a quick explanation of musl's mallocng allocator. -- [mallocng-group](musl/mallocng-group.md) - Print out information about a mallocng group at the given address. -- [mallocng-meta](musl/mallocng-meta.md) - Print out information about a mallocng group given the address of its meta. -- [mallocng-slot-user](musl/mallocng-slot-user.md) - Dump information about a mallocng slot, given its user address. diff --git a/docs/commands/musl/mallocng-group.md b/docs/commands/musl/mallocng-group.md deleted file mode 100644 index 17524226d..000000000 --- a/docs/commands/musl/mallocng-group.md +++ /dev/null @@ -1,25 +0,0 @@ - -# mallocng-group - -```text -usage: mallocng-group [-h] address - -``` - -Print out information about a mallocng group at the given address. - -**Alias:** ng-group -### Positional arguments - -|Positional Argument|Help| -| :--- | :--- | -|address|The address of the group object.| - -### Optional arguments - -|Short|Long|Help| -| :--- | :--- | :--- | -|-h|--help|show this help message and exit| - - - diff --git a/docs/commands/musl/mallocng-meta.md b/docs/commands/musl/mallocng-meta.md deleted file mode 100644 index ce4ec59e2..000000000 --- a/docs/commands/musl/mallocng-meta.md +++ /dev/null @@ -1,25 +0,0 @@ - -# mallocng-meta - -```text -usage: mallocng-meta [-h] address - -``` - -Print out information about a mallocng group given the address of its meta. - -**Alias:** ng-meta -### Positional arguments - -|Positional Argument|Help| -| :--- | :--- | -|address|The address of the meta object.| - -### Optional arguments - -|Short|Long|Help| -| :--- | :--- | :--- | -|-h|--help|show this help message and exit| - - - diff --git a/docs/commands/musl/mallocng-slot-user.md b/docs/commands/musl/mallocng-slot-user.md deleted file mode 100644 index 09f3cc484..000000000 --- a/docs/commands/musl/mallocng-slot-user.md +++ /dev/null @@ -1,26 +0,0 @@ - -# mallocng-slot-user - -```text -usage: mallocng-slot-user [-h] [-a] address - -``` - -Dump information about a mallocng slot, given its user address. - -**Alias:** ng-slotu -### Positional arguments - -|Positional Argument|Help| -| :--- | :--- | -|address|The start of user memory. Referred to as `p` in the source.| - -### Optional arguments - -|Short|Long|Help| -| :--- | :--- | :--- | -|-h|--help|show this help message and exit| -|-a|--all|Print out all information. Including meta and group data.| - - - diff --git a/pwndbg/aglib/heap/heap.py b/pwndbg/aglib/heap/heap.py index cf7467f5c..cb7577c89 100644 --- a/pwndbg/aglib/heap/heap.py +++ b/pwndbg/aglib/heap/heap.py @@ -1,9 +1,29 @@ from __future__ import annotations +from typing import Any + class MemoryAllocator: """Heap abstraction layer.""" + # This function isn't actually implemented anywhere. It originally returned + # `gdb.Breakpoint`, but, in order to facilitate the port to aglib, that + # type association was removed. It should be put back as soon as the + # Debugger-agnostic API gains the ability to set breakpoints. + # + # TODO: Change `Any` to the Debugger-agnostic breakpoint type when it gets created + + def summarize(self, address: int, **kwargs: Any) -> str: + """Returns a textual summary of the specified address. + + Arguments: + address: Address of the heap block to summarize. + + Returns: + A string. + """ + raise NotImplementedError() + def containing(self, address: int) -> int: """Returns the address of the allocation which contains 'address'. diff --git a/pwndbg/aglib/heap/mallocng.py b/pwndbg/aglib/heap/mallocng.py deleted file mode 100644 index adb385857..000000000 --- a/pwndbg/aglib/heap/mallocng.py +++ /dev/null @@ -1,537 +0,0 @@ -""" -Implements handling of musl's allocator mallocng. -https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng -""" - -from __future__ import annotations - -from typing import List - -import pwndbg -import pwndbg.aglib.arch -import pwndbg.aglib.memory as memory -import pwndbg.aglib.typeinfo - -# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L14 -# Slot granularity. -UNIT = 16 -# Size of in-band metadata. -IB = 4 - -# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/malloc.c#L12 -# Describes the possible sizes a slot can be. These are `/ UNIT`. -# fmt: off -size_classes: List[int] = [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 18, 20, - 25, 31, 36, 42, 50, 63, 72, 84, 102, 127, 146, - 170, 204, 255, 292, 340, 409, 511, 584, 682, 818, - 1023, 1169, 1364, 1637, 2047, 2340, 2730, 3276, - 4095, 4680, 5460, 6552, 8191, -] -# fmt: on - - -# Shorthand -def int_size(): - return pwndbg.aglib.typeinfo.sint.sizeof - - -class Group: - """ - A group is an array of slots. - - https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L17 - struct group { - struct meta *meta; - unsigned char active_idx:5; - char pad[UNIT - sizeof(struct meta *) - 1]; - unsigned char storage[]; - }; - """ - - def __init__(self, addr: int) -> None: - self.addr = addr - - self._meta = None - self._active_idx = None - - def preload(self) -> None: - """ - Read all the necessary process memory to populate the group's - fields. - - Do this if you know you will be using most of the - fields of the group. It will be faster, since we can do one - reads instead of two small ones. You may also catch - inaccessible memory exceptions here and not worry about it later. - - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - data = memory.read(self.addr, pwndbg.aglib.arch.ptrsize + 1) - self._meta = Meta(pwndbg.aglib.arch.unpack(data[: pwndbg.aglib.arch.ptrsize])) - self._active_idx = data[-1] & 0b11111 - - @property - def meta(self) -> Meta: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - if self._meta is None: - self._meta = Meta(memory.read_pointer_width(self.addr)) - - return self._meta - - @property - def active_idx(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - if self._active_idx is None: - self._active_idx = memory.u8(self.addr + pwndbg.aglib.arch.ptrsize) & 0b11111 - - return self._active_idx - - @property - def storage(self) -> int: - return self.addr + UNIT - - @property - def group_size(self) -> int: - """ - The size of this group, in bytes. - - Raises: - pwndbg.dbg_mod.Error: When reading meta fails. - """ - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/malloc.c#L234 - return self.meta.stride * self.meta.cnt + UNIT - - -class Slot: - """ - The "unit of allocation" (analogous to glibc's "chunk"). - There is no struct in the source code that describes it. - """ - - def __init__(self, p: int) -> None: - # The start of user memory. It may - # not be the actual start of the slot. - self.p: int = p - self._offset: int = None - self._idx: int = None - # Not exactly sure what this is. - self._check4: int = None - - self._group: Group = None - self._meta: Meta = None - self._reserved: int = None - - def preload(self) -> None: - """ - Read all the necessary process memory to populate the slot's - fields. - - Do this if you know you will be using most of the - fields of the slot. It will be faster, since we can do a few - big reads instead of many small ones. You may also catch - inaccessible memory exceptions here and not worry about it later. - - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - # Read all the in-band data. - inband_data = memory.read(self.p - 8, 8) - - self._check4 = inband_data[4] - if self._check4: - self._offset = int.from_bytes(inband_data[0:4], pwndbg.aglib.arch.endian, signed=False) - else: - self._offset = int.from_bytes(inband_data[6:8], pwndbg.aglib.arch.endian, signed=False) - idxv = inband_data[5] - if idxv != 255: - self._idx = idxv & 31 - else: - self._idx = 0 - - # Read the group's meta pointer. - _ = self.meta - # Need this loaded for lots of fields, - # but we will let it be since we want to be able to - # say stuff about this slot even with a corrupt meta. - # _ = self.meta.stride - - self._reserved = inband_data[5] >> 5 - if self._reserved == 5: - # self.end doesn't need a read. - self._reserved = memory.u32(self.end - 4) - - # All the other fields are calculated without - # memory reads. - - @property - def check4(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L134 - if self._check4 is None: - self._check4 = memory.u8(self.p - 4) - - return self._check4 - - @property - def offset(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L132 - if self._offset is None: - if self.check4: - # assert(!offset); - self._offest = memory.u32(self.p - 8) - # assert(offset > 0xffff); - else: - self._offset = memory.u16(self.p - 2) - - return self._offset - - @property - def idx(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L133 - if self._idx is None: - v = memory.u8(self.p - 3) - if v != 255: - self._idx = v & 31 - else: - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/donate.c#L29 - self._idx = 0 - - return self._idx - - @property - def group(self) -> Group: - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L139 - if self._group is None: - self._group = Group(self.p - UNIT * self.offset - UNIT) - - return self._group - - @property - def meta(self) -> Meta: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L140 - if self._meta is None: - self._meta = Meta(memory.read_pointer_width(self.group.addr)) - - return self._meta - - @property - def start(self) -> int: - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/free.c#L108 - return self.group.storage + self.meta.stride * self.idx - - @property - def end(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading meta fails. - """ - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/free.c#L109 - return self.start + self.meta.stride - IB - - @property - def reserved(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L161 - # Lots of asserts here.. - if self._reserved is None: - self._reserved = memory.u8(self.p - 3) >> 5 - if self._reserved == 5: - self._reserved = memory.u32(self.end - 4) - - return self._reserved - - @property - def nominal_size(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading meta fails. - """ - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L159 - return self.end - self.reserved - self.p - - @property - def user_size(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading meta fails. - """ - return self.nominal_size - - @property - def slack(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading meta fails. - """ - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L199 - return (self.meta.stride - self.nominal_size - IB) // UNIT - - @property - def internal_offset(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading meta fails. - """ - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L204 - # Not sure why musl saves it, it doesn't seem to use it. - # We can calculate it more easily than musl does: - return (self.p - self.start) // UNIT - - -class Meta: - """ - The metadata of a group. - - https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L24 - struct meta { - struct meta *prev, *next; - struct group *mem; - volatile int avail_mask, freed_mask; - uintptr_t last_idx:5; - uintptr_t freeable:1; - uintptr_t sizeclass:6; - uintptr_t maplen:8*sizeof(uintptr_t)-12; - }; - """ - - def __init__(self, addr: int) -> None: - self.addr: int = addr - - self._prev: int = None - self._next: int = None - self._mem: int = None - self._avail_mask: int = None - self._freed_mask: int = None - self._last_idx: int = None - self._freeable: int = None - self._sizeclass: int = None - self._maplen: int = None - - self._stride: int = None - - def preload(self) -> None: - """ - Read all the necessary process memory to populate the meta's - fields. - - Do this if you know you will be using most of the - fields of the meta. It will be faster, since we can do a one - big read instead of many small ones. You may also catch - inaccessible memory exceptions here and not worry about it later. - - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - ptrsize = pwndbg.aglib.arch.ptrsize - endian = pwndbg.aglib.arch.endian - - # Read the whole struct. - data = memory.read(self.addr, ptrsize * 3 + 2 * int_size() + 8 * ptrsize) - - cur_offset = 0 - self._prev = pwndbg.aglib.arch.unpack(data[cur_offset:ptrsize]) - cur_offset += ptrsize - self._next = pwndbg.aglib.arch.unpack(data[cur_offset : (cur_offset + ptrsize)]) - cur_offset += ptrsize - self._mem = pwndbg.aglib.arch.unpack(data[cur_offset : (cur_offset + ptrsize)]) - cur_offset += ptrsize - self._avail_mask = int.from_bytes( - data[cur_offset : (cur_offset + int_size())], endian, signed=False - ) - cur_offset += int_size() - self._freed_mask = int.from_bytes( - data[cur_offset : (cur_offset + int_size())], endian, signed=False - ) - cur_offset += int_size() - # I think this is how I should read a bitfield. - # http://mjfrazer.org/mjfrazer/bitfields/ - flags = int.from_bytes(data[cur_offset : (cur_offset + ptrsize)], endian, signed=False) - self._last_idx = flags & 0b11111 - self._freeable = (flags >> 5) & 1 - self._sizeclass = (flags >> 6) & 0b111111 - self._maplen = flags >> 12 - - # All the other fields are calculated without - # memory reads. - - @property - def prev(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - if self._prev is None: - self._prev = memory.read_pointer_width(self.addr) - - return self._prev - - @property - def next(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - if self._next is None: - self._next = memory.read_pointer_width(self.addr + pwndbg.aglib.arch.ptrsize) - - return self._next - - @property - def mem(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - if self._mem is None: - self._mem = memory.read_pointer_width(self.addr + pwndbg.aglib.arch.ptrsize * 2) - - return self._mem - - @property - def avail_mask(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - if self._avail_mask is None: - # While the type is technically a signed int, it makes more - # sense to interpret it as unsigned semantically. - self._avail_mask = memory.uint(self.addr + pwndbg.aglib.arch.ptrsize * 3) - - return self._avail_mask - - @property - def freed_mask(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - if self._freed_mask is None: - offset = pwndbg.aglib.arch.ptrsize * 3 + int_size() - # Technically signed. - self._freed_mask = memory.uint(self.addr + offset) - - return self._freed_mask - - @property - def last_idx(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - if self._last_idx is None: - offset = pwndbg.aglib.arch.ptrsize * 3 + int_size() * 2 - # reading pointer width so it works regardless of endianness - self._last_idx = memory.read_pointer_width(self.addr + offset) & 0b11111 - - return self._last_idx - - @property - def freeable(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - if self._freeable is None: - offset = pwndbg.aglib.arch.ptrsize * 3 + int_size() * 2 - self._freeable = (memory.read_pointer_width(self.addr + offset) >> 5) & 1 - - return self._freeable - - @property - def sizeclass(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - if self._sizeclass is None: - offset = pwndbg.aglib.arch.ptrsize * 3 + int_size() * 2 - self._sizeclass = (memory.read_pointer_width(self.addr + offset) >> 6) & 0b111111 - - return self._sizeclass - - @property - def maplen(self) -> int: - """ - Raises: - pwndbg.dbg_mod.Error: When reading memory fails. - """ - if self._maplen is None: - offset = pwndbg.aglib.arch.ptrsize * 3 + int_size() * 2 - self._maplen = memory.read_pointer_width(self.addr + offset) >> 12 - - return self._maplen - - @property - def stride(self): - """ - Returns -1 if sizeclass >= len(size_classes). - """ - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L175 - if self._stride is None: - if not self.last_idx and self.maplen: - self._stride = self.maplen * 4096 - UNIT - else: - if self.sizeclass < len(size_classes): - self._stride = UNIT * size_classes[self.sizeclass] - else: - # The meta is corrupted. - self._stride = -1 - - return self._stride - - @property - def cnt(self): - """ - Number of slots in the group. - """ - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/free.c#L60 - return self.last_idx + 1 - - @property - def slot_size(self): - """ - The size of a slot in this group, in bytes. - - Returns -1 if sizeclass >= len(size_classes). - """ - if self.sizeclass < len(size_classes): - return size_classes[self.sizeclass] * UNIT - else: - # The meta is corrupted. - return -1 - - -class MetaArea: - def __init__(self, addr: int) -> None: - self.addr = addr - - -class Mallocng: - pass diff --git a/pwndbg/aglib/memory.py b/pwndbg/aglib/memory.py index 67ae01192..67b128da5 100644 --- a/pwndbg/aglib/memory.py +++ b/pwndbg/aglib/memory.py @@ -269,14 +269,6 @@ def s64(addr: int) -> int: return readtype(pwndbg.aglib.typeinfo.int64, addr) -def sint(addr: int) -> int: - """ - Read one `signed int` from the specified - address. - """ - return readtype(pwndbg.aglib.typeinfo.sint, addr) - - def cast_pointer( type: pwndbg.dbg_mod.Type, addr: int | pwndbg.dbg_mod.Value ) -> pwndbg.dbg_mod.Value: diff --git a/pwndbg/aglib/typeinfo.py b/pwndbg/aglib/typeinfo.py index 4019a793d..ebc2e7df1 100644 --- a/pwndbg/aglib/typeinfo.py +++ b/pwndbg/aglib/typeinfo.py @@ -18,7 +18,6 @@ long: pwndbg.dbg_mod.Type uchar: pwndbg.dbg_mod.Type ushort: pwndbg.dbg_mod.Type uint: pwndbg.dbg_mod.Type -sint: pwndbg.dbg_mod.Type void: pwndbg.dbg_mod.Type uint8: pwndbg.dbg_mod.Type @@ -62,7 +61,6 @@ def update() -> None: module.uchar = lookup_types("unsigned char", "ubyte", "u8", "uint8") module.ushort = lookup_types("unsigned short", "ushort", "u16", "uint16", "uint16_t") module.uint = lookup_types("unsigned int", "uint", "u32", "uint32") - module.sint = lookup_types("signed int", "signed", "int") module.void = lookup_types("void", "()") module.uint8 = module.uchar diff --git a/pwndbg/commands/mallocng.py b/pwndbg/commands/mallocng.py index 221b722ff..6a2c2f686 100644 --- a/pwndbg/commands/mallocng.py +++ b/pwndbg/commands/mallocng.py @@ -4,17 +4,10 @@ Commands that help with debugging musl's allocator, mallocng. from __future__ import annotations -import argparse - import pwndbg -import pwndbg.aglib.heap.mallocng as mallocng -import pwndbg.aglib.memory as memory -import pwndbg.aglib.typeinfo as typeinfo +import pwndbg.aglib.heap import pwndbg.color as C -import pwndbg.color.message as message from pwndbg.commands import CommandCategory -from pwndbg.lib.pretty_print import Property -from pwndbg.lib.pretty_print import PropertyPrinter @pwndbg.commands.Command( @@ -151,322 +144,3 @@ def mallocng_explain() -> None: # TODO: explain what a slot looks like. print(txt) - - -def dump_group(group: mallocng.Group) -> str: - try: - # May fail on corrupt meta. - group_size = group.group_size - except pwndbg.dbg_mod.Error as e: - print(message.error(f"Error while reading meta: {e}")) - print(C.bold("Cannot determine group size.")) - group_size = -1 - - group_range = "@ " + C.memory.get(group.addr) - if group_size != -1: - group_range += " - " + C.memory.get(group.addr + group_size) - - pp = PropertyPrinter() - pp.start_section("group", group_range) - pp.set_padding(2) - pp.add( - [ - Property(name="meta", value=group.meta.addr, is_addr=True), - Property(name="active_idx", value=group.active_idx), - Property(name="storage", value=group.storage, is_addr=True, extra="start of slots"), - ] - ) - - if group_size != -1: - pp.write("---\n") - pp.set_padding(3) - pp.add( - [ - Property(name="group size", value=group_size), - ] - ) - - pp.end_section() - return pp.dump() - - -def dump_meta(meta: mallocng.Meta) -> str: - int_size = str(typeinfo.sint.sizeof * 8) - avail_binary = "0b" + format(meta.avail_mask, f"0{int_size}b") - freed_binary = "0b" + format(meta.freed_mask, f"0{int_size}b") - - pp = PropertyPrinter() - pp.start_section("meta", "@ " + C.memory.get(meta.addr)) - pp.set_padding(2) - pp.add( - [ - Property(name="prev", value=meta.prev, is_addr=True), - Property(name="next", value=meta.next, is_addr=True), - Property(name="mem", value=meta.mem, is_addr=True, extra="the group"), - Property(name="avail_mask", value=meta.avail_mask, extra=avail_binary), - Property(name="freed_mask", value=meta.freed_mask, extra=freed_binary), - Property(name="last_idx", value=meta.last_idx, extra="index of last slot"), - Property(name="freeable", value=str(bool(meta.freeable))), - Property(name="sizeclass", value=meta.sizeclass), - Property(name="maplen", value=meta.maplen), - ] - ) - pp.write("---\n") - pp.set_padding(3) - pp.add( - [ - Property(name="cnt", value=meta.cnt, extra="the number of slots"), - Property(name="slot size", value=meta.slot_size, extra='aka "stride"'), - ] - ) - pp.end_section() - - output = pp.dump() - - if not meta.freeable: - # When mapped object files contain unused memory, they are donated - # to the heap. See https://elixir.bootlin.com/musl/v1.2.5/source/ldso/dynlink.c#L600 - # and https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/donate.c#L36 . - # Only in this case is `meta.freeable = 0;` - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/donate.c#L25 - output += C.bold("\nGroup donated by ld as unused part of ") - - try: - mapping = pwndbg.aglib.vmmap.find(mallocng.Group(meta.mem).addr) - except pwndbg.dbg_mod.Error as e: - print(message.error(f"Could not fetch parent group: {e}")) - mapping = None - - if mapping is None: - output += C.red("") - else: - output += C.bold(f'"{mapping.objfile}"') - - output += C.bold(".\n") - - elif not meta.last_idx and meta.maplen: - # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L177 - output += C.bold("\nGroup allocated with mmap().\n") - else: - output += C.bold("\nGroup nested in slot of another group") - try: - parent_group = mallocng.Slot(mallocng.Group(meta.mem).addr).group.addr - output += " (" + C.memory.get(parent_group) + ")" - except pwndbg.dbg_mod.Error as e: - print(message.error(f"Could not fetch parent group: {e}")) - output += C.bold(".\n") - - return output - - -parser = argparse.ArgumentParser( - description=""" -Dump information about a mallocng slot, given its user address. - """, -) -parser.add_argument( - "address", - type=int, - help="The start of user memory. Referred to as `p` in the source.", -) -parser.add_argument( - "-a", - "--all", - action="store_true", - help="Print out all information. Including meta and group data.", -) - - -@pwndbg.commands.Command( - parser, - category=CommandCategory.MUSL, - aliases=["ng-slotu"], -) -@pwndbg.commands.OnlyWhenRunning -def mallocng_slot_user(address: int, all: bool) -> None: - if not memory.is_readable_address(address): - print(message.error(f"Address {address:#x} not readable.")) - return - - slot = mallocng.Slot(address) - - try: - slot.preload() - except pwndbg.dbg_mod.Error as e: - print(message.error(f"Error while reading slot: {e}")) - return - - read_success: bool = True - - try: - slot.group.preload() - except pwndbg.dbg_mod.Error as e: - print(message.error(f"Error while reading group: {e}")) - read_success = False - - try: - slot.meta.preload() - except pwndbg.dbg_mod.Error as e: - print(message.error(f"Error while reading meta: {e}")) - read_success = False - - if not read_success: - print(message.info("Only showing partial information.")) - all = False - - pp = PropertyPrinter() - - if not all: - pp.start_section("slab") - pp.set_padding(7) - if read_success: - pp.add( - [ - Property(name="group", value=slot.group.addr, is_addr=True), - Property(name="meta", value=slot.meta.addr, is_addr=True), - ] - ) - else: - pp.add( - [ - Property(name="group", value=slot.group.addr, is_addr=True), - ] - ) - pp.end_section() - - if read_success: - pp.start_section("general") - pp.set_padding(2) - pp.add( - [ - Property(name="start", value=slot.start, is_addr=True), - Property(name="user start", value=slot.p, is_addr=True, extra="aka `p`"), - Property(name="end", value=slot.end, is_addr=True, extra="start + stride - 4"), - Property( - name="stride", value=slot.meta.stride, extra="distance between adjacent slots" - ), - Property(name="user size", value=slot.user_size, extra='aka "nominal size", `n`'), - Property(name="slack", value=slot.slack, extra="slot's unused memory / 0x10"), - ] - ) - pp.end_section() - - pp.start_section("in-band") - pp.set_padding(4) - - reserved_extra = ["end - p - n", ""] - if slot.reserved >= 5: - reserved_extra[1] = "located near slot end" - if slot.reserved == 6: - reserved_extra.append("this slot is a nested group") - else: - reserved_extra[1] = "located in slot header" - - inband_group = [ - Property(name="offset", value=slot.offset, extra="distance to first slot / 0x10"), - Property(name="index", value=slot.idx, extra="index of slot in its group"), - Property(name="reserved", value=slot.reserved, extra=reserved_extra), - ] - - if read_success: - # While it is technically saved in-band, there is no way - # for us to locate it without metadata. - inband_group.append( - Property( - name="rnd-off", - value=slot.internal_offset, - extra="prevents double free, (p - start) / 0x10", - ), - ) - - pp.add(inband_group) - pp.end_section() - - pp.print() - - if all: - print(dump_group(slot.group), end="") - print(dump_meta(slot.meta), end="") - - -parser = argparse.ArgumentParser( - description=""" -Print out information about a mallocng group given the address of its meta. - """, -) -parser.add_argument( - "address", - type=int, - help="The address of the meta object.", -) - - -@pwndbg.commands.Command( - parser, - category=CommandCategory.MUSL, - aliases=["ng-meta"], -) -@pwndbg.commands.OnlyWhenRunning -def mallocng_meta(address: int) -> None: - if not memory.is_readable_address(address): - print(message.error(f"Address {address:#x} not readable.")) - return - - meta = mallocng.Meta(address) - - try: - meta.preload() - except pwndbg.dbg_mod.Error as e: - print(message.error(str(e))) - return - - try: - group = mallocng.Group(meta.mem) - group.preload() - print(dump_group(group), end="") - except pwndbg.dbg_mod.Error as e: - print(message.error(f"Failed loading group: {e}")) - - print(dump_meta(meta), end="") - - -parser = argparse.ArgumentParser( - description=""" -Print out information about a mallocng group at the given address. - """, -) -parser.add_argument( - "address", - type=int, - help="The address of the group object.", -) - - -@pwndbg.commands.Command( - parser, - category=CommandCategory.MUSL, - aliases=["ng-group"], -) -@pwndbg.commands.OnlyWhenRunning -def mallocng_group(address: int) -> None: - if not memory.is_readable_address(address): - print(message.error(f"Address {address:#x} not readable.")) - return - - group = mallocng.Group(address) - - try: - group.preload() - except pwndbg.dbg_mod.Error as e: - print(message.error(str(e))) - return - - print(dump_group(group), end="") - - try: - meta = group.meta - meta.preload() - print(dump_meta(meta), end="") - except pwndbg.dbg_mod.Error as e: - print(message.error(f"Failed loading meta: {e}")) - return diff --git a/pwndbg/dbg/__init__.py b/pwndbg/dbg/__init__.py index 14e26ce07..13e08da0b 100644 --- a/pwndbg/dbg/__init__.py +++ b/pwndbg/dbg/__init__.py @@ -922,7 +922,7 @@ class Value: def dereference(self) -> Value: """ - If this is a pointer value, dereferences the pointer and returns a new + If this is a poitner value, dereferences the pointer and returns a new instance of Value, containing the value pointed to by this pointer. """ raise NotImplementedError() diff --git a/pwndbg/lib/pretty_print.py b/pwndbg/lib/pretty_print.py index edf8fd046..f5df84ce3 100644 --- a/pwndbg/lib/pretty_print.py +++ b/pwndbg/lib/pretty_print.py @@ -20,7 +20,7 @@ class Property: name: str value: Any - extra: str | List[str] = "" + extra: str = "" is_addr: bool = False use_hex: bool = True @@ -61,54 +61,24 @@ class PropertyPrinter: """ Add a group of properties that should be aligned. """ - # Transform prop values to string representation - for prop in prop_group: - if isinstance(prop.value, int): - if prop.use_hex: - prop.value = hex(prop.value) - else: - prop.value = str(prop.value) - - # Get max lengths to calculate proper ljust - # + 1 to account for the ":" - max_name_len = max(len(prop.name) for prop in prop_group) + 1 - # max_value_len = max(len(prop.value) for prop in prop_group) - # Use constant so it works between different groups - max_value_len = 16 - - indentation_str = self.indent_level * self.indent_size * " " - padding_str = self.padding * " " - name_pad_str = max_name_len * " " - val_pad_str = max_value_len * " " - extra_list_pad_str = indentation_str + name_pad_str + padding_str + val_pad_str + max_name_len = max(len(self.name_color_func(prop.name)) for prop in prop_group) for prop in prop_group: - self.text += ( - indentation_str - + color.ljust_colored(self.name_color_func(prop.name) + ":", max_name_len) - + padding_str - ) + self.text += self.indent_level * self.indent_size * " " + colored_name = self.name_color_func(prop.name) + ":" + self.text += colored_name.ljust(max_name_len + 1, " ") + self.text += self.padding * " " if prop.is_addr: - base = 16 if prop.use_hex else 10 - colored_val = color.memory.get(int(prop.value, base)) + self.text += color.memory.get(prop.value) else: - colored_val = self.value_color_func(prop.value) - - self.text += color.ljust_colored(colored_val, max_value_len) - - if isinstance(prop.extra, str): - self.text += " " + prop.extra - else: - # list of strings, we want each one under the other - assert isinstance(prop.extra, list) - assert len(prop.extra) > 1 + if isinstance(prop.value, int) and prop.use_hex: + val = hex(prop.value) + else: + val = prop.value + self.text += self.value_color_func(val) - self.text += " " + prop.extra[0] - for i in range(1, len(prop.extra)): - self.text += "\n" - self.text += extra_list_pad_str - self.text += " " + prop.extra[i] + self.text += " " + prop.extra self.text += "\n" @@ -122,7 +92,7 @@ class PropertyPrinter: """ Print the built up string. """ - print(self.text, end="") + print(self.text) def clear(self) -> None: """ @@ -149,22 +119,14 @@ class PropertyPrinter: """ self.text += string - def start_section(self, title: str, preamble: str = "") -> None: + def start_section(self, title: str) -> None: """ Start a named section of properties that will have increased indentation. Don't forget to call end_section()! """ - self.text += " " * self.indent_level * self.indent_size - self.text += self.section_color_func(title) - - if preamble: - self.text += "\n" - self.text += " " * (self.indent_level + 1) * self.indent_size - self.text += preamble - - self.text += "\n" + self.text += self.section_color_func(title) + "\n" self.indent() def end_section(self) -> None: