Pwndbg has support for visualizing the allocator state of glibc malloc. This feature has evolved over the years and is currently in a really good place UX-wise, providing many useful ways to perform allocator inspection.
My GSoC 2025 project was about adding support for visualizing the state of the allocator that musl uses. In particular, the "new" allocator implementation: mallocng (released around 2020).
Glibc malloc is not the only allocator that Pwndbg supports. In userspace, jemalloc is supported (the implementation comes from a previous GSoC!) and in kernel space the linux SLUB allocator is supported. The current Pwndbg design essentially completely separates the allocator implementations. They track state separately, are invoked by separate commands, and controlled by separate configuration parameters. I decided to follow suit and implement mallocng support in line with that pattern, however, refactoring some code to allow for a generic allocator API may be interesting for future work.
Allocator implementations deal with various custom structures. Although the names of these structures vary from implementation to implementation, their purpose is usually analogous. For instance, the "unit of allocation" i.e. the thing that contains user data and often some in-band metadata is called a "chunk" in glibc malloc and a "slot" in mallocng. Often, the "unit of allocation" structures are part of a larger structure called a slab, or in mallocng's case a "group". The metadata of a group is stored in a structure called a "meta" which is located in a different memory region from its group (so user data is separated from metadata, which improves security). These metas are part of a larger slab-type structure called a "meta area".
One of the first things that an allocator inspector has to implement, is its internal representation of these structures. In particular we want to be able to say "I know there is a group at address xxyyzz, give me a python object that represents that group". It is not completely obvious how to go about doing this. Ideally, we would like to leverage the actual C definition of the struct which exists in the allocator's source code, for instance:
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;
};To do this, the jemalloc implementation leverages Pwndbg's aglib API like so:
rtree_s = pwndbg.aglib.typeinfo.load("struct rtree_s")
self._Value = pwndbg.aglib.memory.get_typed_pointer_value("struct rtree_s", self._addr)However, this essentially works by hooking into the debugger's (LLDB or GDB) value/type API. Because of this jemalloc inspection only works when the program has full debugging symbols, which is a limitation I wanted to avoid. A potential idea could be to create the type ourselves if it doesn't exist, and load it into the debugger. This would allow us to use the existing nice aglib API. However, the support for creating custom types and loading them into the debugger doesn't exist in GDB, so this wasn't an option.
The approach that the glibc inspector takes does allow it to work without debugging symbols (and on different versions of glibc), but it is quite complex. It involves using ctypes, a wrapper CStruct2GDB, a utility FakeGDBField etc. I wanted to avoid these GDBisms (even though the inspector does work on LLDB), and write an implementation that is more friendly to the debugger-agnostic Pwndbg. Lastly, the glibc inspector uses polymorphism to invoke different code depending on whether debug symbols are present or not. I deemed all this overkill for my use-case, as the mallocng structures are quite simple, and haven't changed
the implementation's inception. I decided to have just one implementation which always assumes we have no debug symbols, and make that as robust as possible.
The SLAB allocator handles the problem of loading structures from memory by leveraging Pwndbg's infrastructure around the add-symbol-file GDB command. This however means that it has to actively compile the C struct definitions, for whatever processor architecture / ABI is being debugged. At the time I was writing the mallocng implementation, this seemed hard to do portably, so I decided not to go down that route. Since then however, we started using zig as a python package for the compilation, so this idea may be revisited.
The idea I went with was just reading the bytes into a python array, and then reparsing them into the necessary structure types. So for instance
# Read the whole struct.
data = memory.read(self.addr, Meta.sizeof())
cur_offset = 0
def next_int(size: int, signed: bool = False) -> int:
nonlocal cur_offset
val = int.from_bytes(data[cur_offset : (cur_offset + size)], endian, signed=signed)
cur_offset += size
return val
self._prev = next_int(ptrsize)
self._next = next_int(ptrsize)
self._mem = next_int(ptrsize), and this idea works quite well keeping the code portable, simple, and allows it to work without any debugging information.
That said we now have four different allocator inspectors, all rolling their own way to read structures from memory. Ideally one method would be made robust, with a nice API, and then reused for each of them - this may be interesting future work.
The "inspecting allocator state" part is fairly straightforward once the mallocng implementation is understood. The visualization aspect however provides some creative freedom.
The first most obvious thing to do is to provide a way to dump the allocator state:

Next, we want a way to see the structures at a given memory address:

I took care to provide nice aliases to the commands, and iron out some pain points I sometimes have with the glibc inspector. For instance I provide ng-slots and ng-slotu which both do the same thing, except the first one expects the allocator-internal start of the slot, and the second one expects the user start (i.e. the thing that malloc will return).
The thing I'm most proud of though is the ng-vis command. It mirrors the vis command from the glibc inspector, and is probably my favorite command in Pwndbg. Here is what my take on it looks like for mallocng:

Everything is color-coded, allowing you to easily see where slots start and end, what their allocation state is, where their in-band metadata is etc. The user-data portion of a slot is bolded as well, to make it stand out more on slots that use cycling.
I would say the mallocng support is fully complete. The commands work well even under hostile conditions (no debugging symbols, stripped, trying to visualize a slot even on memory where there isn't a slot, or where pointers are corrupt), and you can visualize everything I could think of that might be useful.
The tests for these commands are also fully implemented, and Matt helped port them to the debugger-agnostic testing framework; see https://lobisomem.gay/2025/08/28/GSoC-2025.html for his work on the testing framework.
While I would consider the user experience to be complete, some work could be done on cleaning up the way allocator inspection is implemented in general. One way is to use the knowledge that allocators have some similar designs, and we usually want to visualize them in standard / similar ways. The other way is by tackling the "load custom structure from memory" issue which is more generic.
Here is the list of pull requests that I authored for this project:
- Facility for formatting command output
- mallocng: print information about slot, group, meta at given address
- Add mallocng-explain command
- mallocng: add ng-find command
- Reland "mallocng: print information about slot, group, meta at given address"
- mallocng: Various cleanups
- mallocng: Explain what a slot looks like in ng-explain
- mallocng: add ng-slots command
- mallocng: implement inspection of non-allocated slots
- mallocng: Add ng-metaarea and ng-ctx commands
- mallocng: Various fixups 2
- mallocng: Add tests for mallocng commands
- mallocng: Allow users to index into groups and metaareas
- mallocng: Add ng-vis command
- mallocng: Show slot statusline when printing meta
- mallocng: Implement ng-dump command to dump heap state
- mallocng(kinda): Pretty printer refactor