From d3bf95f3ba81786151657bf0a4073cd7013fac76 Mon Sep 17 00:00:00 2001 From: k4lizen <124312252+k4lizen@users.noreply.github.com> Date: Fri, 12 Dec 2025 04:15:27 +0100 Subject: [PATCH] Fix up archlinux glibc 2.42 tests (#3487) * fix unsorted bin test * fix tcache vis test for glibc 2.42 * remove unecessary gdb tests --- .../library/dbg/tests/heap/test_heap_bins.py | 16 +- .../dbg/tests/heap/test_vis_heap_chunks.py | 40 +- .../library/gdb/tests/heap/test_heap_bins.py | 514 ------------------ .../gdb/tests/heap/test_vis_heap_chunks.py | 235 -------- 4 files changed, 49 insertions(+), 756 deletions(-) delete mode 100644 tests/library/gdb/tests/heap/test_heap_bins.py delete mode 100644 tests/library/gdb/tests/heap/test_vis_heap_chunks.py diff --git a/tests/library/dbg/tests/heap/test_heap_bins.py b/tests/library/dbg/tests/heap/test_heap_bins.py index d07ed15bb..f370699d2 100644 --- a/tests/library/dbg/tests/heap/test_heap_bins.py +++ b/tests/library/dbg/tests/heap/test_heap_bins.py @@ -27,22 +27,30 @@ async def test_heap_bins(ctrl: Controller) -> None: # check if all bins are empty at first allocator = pwndbg.aglib.heap.current + assert allocator is not None addr = pwndbg.aglib.symbol.lookup_symbol_addr("tcache_size") + assert addr is not None tcache_size = allocator._request2size(pwndbg.aglib.memory.u64(addr)) addr = pwndbg.aglib.symbol.lookup_symbol_addr("tcache_count") + assert addr is not None tcache_count = pwndbg.aglib.memory.u64(addr) addr = pwndbg.aglib.symbol.lookup_symbol_addr("fastbin_size") fastbin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr)) addr = pwndbg.aglib.symbol.lookup_symbol_addr("fastbin_count") + assert addr is not None fastbin_count = pwndbg.aglib.memory.u64(addr) addr = pwndbg.aglib.symbol.lookup_symbol_addr("smallbin_size") + assert addr is not None smallbin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr)) addr = pwndbg.aglib.symbol.lookup_symbol_addr("smallbin_count") + assert addr is not None smallbin_count = pwndbg.aglib.memory.u64(addr) addr = pwndbg.aglib.symbol.lookup_symbol_addr("largebin_size") + assert addr is not None largebin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr)) addr = pwndbg.aglib.symbol.lookup_symbol_addr("largebin_count") + assert addr is not None largebin_count = pwndbg.aglib.memory.u64(addr) result = allocator.tcachebins() @@ -108,9 +116,13 @@ async def test_heap_bins(ctrl: Controller) -> None: result = allocator.unsortedbin() assert result.bin_type == BinType.UNSORTED + fd_chain_len = len(result.bins["all"].fd_chain) + bk_chain_len = len(result.bins["all"].bk_chain) assert ( - len(result.bins["all"].fd_chain) == smallbin_count + 2 - and len(result.bins["all"].bk_chain) == smallbin_count + 2 + (fd_chain_len == smallbin_count + 2 and bk_chain_len == smallbin_count + 2) + # Since glibc 2.42, freed small-bin-sized chunks go directly to the smallbin instead of going + # to the unsorted bin. + or (fd_chain_len == 1 and bk_chain_len == 1) ) assert not result.bins["all"].is_corrupted for addr in result.bins["all"].fd_chain[:-1]: diff --git a/tests/library/dbg/tests/heap/test_vis_heap_chunks.py b/tests/library/dbg/tests/heap/test_vis_heap_chunks.py index af45482b0..6f7c9a701 100644 --- a/tests/library/dbg/tests/heap/test_vis_heap_chunks.py +++ b/tests/library/dbg/tests/heap/test_vis_heap_chunks.py @@ -67,15 +67,45 @@ async def test_vis_heap_chunk_command(ctrl: Controller) -> None: first_hexdump = await hexdump_16B(hex(heap_page.start)) + # Since glibc 2.42 we don't store the amount of chunks in the tcache bin, but rather + # the amount of chunks still needed to fill the bin. + num_slots_check = pwndbg.aglib.memory.u8(heap_page.start + pwndbg.aglib.arch.ptrsize * 2) + using_num_slots = num_slots_check == 7 + expected = [ "", f"{heap_iter(0):#x}\t0x0000000000000000\t{first_chunk_size | 1:#018x}\t{first_hexdump}", ] - for _ in range(first_chunk_size // 16 - 1): - expected.append( - "%#x\t0x0000000000000000\t0x0000000000000000\t................" % heap_iter() - ) - expected.append("%#x\t0x0000000000000000\t \t........" % heap_iter()) + + if using_num_slots: + # The tcache struct is made up of 2-byte num_slots values and 8-byte pointers to the starts + # of the bins. + ntcachebins: int = first_chunk_size // (2 + 8) + nslotslines: float = ntcachebins * 2 / 0x10 + nptrlines: int = first_chunk_size // 0x10 - int(nslotslines) + + for _ in range(int(nslotslines)): + expected.append( + "%#x\t0x0007000700070007\t0x0007000700070007\t................" % heap_iter() + ) + if nslotslines - int(nslotslines) == 0.5: + expected.append( + "%#x\t0x0007000700070007\t0x0000000000000000\t................" % heap_iter() + ) + nptrlines -= 1 + for _ in range(nptrlines - 1): + expected.append( + "%#x\t0x0000000000000000\t0x0000000000000000\t................" % heap_iter() + ) + expected.append("%#x\t0x0000000000000000\t \t........" % heap_iter()) + + else: + for _ in range(first_chunk_size // 16 - 1): + expected.append( + "%#x\t0x0000000000000000\t0x0000000000000000\t................" % heap_iter() + ) + expected.append("%#x\t0x0000000000000000\t \t........" % heap_iter()) + assert result == expected ## This time using `default-visualize-chunk-number` to set `count`, to make sure that the config can work diff --git a/tests/library/gdb/tests/heap/test_heap_bins.py b/tests/library/gdb/tests/heap/test_heap_bins.py deleted file mode 100644 index 6d5d68f1a..000000000 --- a/tests/library/gdb/tests/heap/test_heap_bins.py +++ /dev/null @@ -1,514 +0,0 @@ -from __future__ import annotations - -import gdb -import pytest - -import pwndbg.aglib.heap -import pwndbg.aglib.memory -import pwndbg.aglib.symbol -import pwndbg.aglib.vmmap -import pwndbg.dbg -from pwndbg.aglib.heap.ptmalloc import BinType - -from .. import get_binary - -BINARY = get_binary("heap_bins.native.out") - - -def test_heap_bins(start_binary): - """ - Tests pwndbg.aglib.heap bins commands - """ - start_binary(BINARY) - gdb.execute("set context-output /dev/null") - gdb.execute("b breakpoint", to_string=True) - - # check if all bins are empty at first - gdb.execute("continue") - allocator = pwndbg.aglib.heap.current - - addr = pwndbg.aglib.symbol.lookup_symbol_addr("tcache_size") - tcache_size = allocator._request2size(pwndbg.aglib.memory.u64(addr)) - addr = pwndbg.aglib.symbol.lookup_symbol_addr("tcache_count") - tcache_count = pwndbg.aglib.memory.u64(addr) - addr = pwndbg.aglib.symbol.lookup_symbol_addr("fastbin_size") - fastbin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr)) - addr = pwndbg.aglib.symbol.lookup_symbol_addr("fastbin_count") - fastbin_count = pwndbg.aglib.memory.u64(addr) - addr = pwndbg.aglib.symbol.lookup_symbol_addr("smallbin_size") - smallbin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr)) - addr = pwndbg.aglib.symbol.lookup_symbol_addr("smallbin_count") - smallbin_count = pwndbg.aglib.memory.u64(addr) - addr = pwndbg.aglib.symbol.lookup_symbol_addr("largebin_size") - largebin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr)) - addr = pwndbg.aglib.symbol.lookup_symbol_addr("largebin_count") - largebin_count = pwndbg.aglib.memory.u64(addr) - - result = allocator.tcachebins() - assert result.bin_type == BinType.TCACHE - assert tcache_size in result.bins - assert result.bins[tcache_size].bk_chain is None and len(result.bins[tcache_size].fd_chain) == 1 - - result = allocator.fastbins() - assert result.bin_type == BinType.FAST - assert fastbin_size in result.bins - assert len(result.bins[fastbin_size].fd_chain) == 1 - - result = allocator.unsortedbin() - assert result.bin_type == BinType.UNSORTED - assert len(result.bins["all"].fd_chain) == 1 - assert not result.bins["all"].is_corrupted - - result = allocator.smallbins() - assert result.bin_type == BinType.SMALL - assert smallbin_size in result.bins - assert ( - len(result.bins[smallbin_size].fd_chain) == 1 - and len(result.bins[smallbin_size].bk_chain) == 1 - ) - assert not result.bins[smallbin_size].is_corrupted - - result = allocator.largebins() - assert result.bin_type == BinType.LARGE - largebin_size = list(result.bins.items())[allocator.largebin_index(largebin_size) - 64][0] - assert largebin_size in result.bins - assert ( - len(result.bins[largebin_size].fd_chain) == 1 - and len(result.bins[largebin_size].bk_chain) == 1 - ) - assert not result.bins[largebin_size].is_corrupted - - # check tcache - gdb.execute("continue") - - result = allocator.tcachebins() - assert result.bin_type == BinType.TCACHE - assert tcache_size in result.bins - assert ( - result.bins[tcache_size].count == tcache_count - and len(result.bins[tcache_size].fd_chain) == tcache_count + 1 - ) - for addr in result.bins[tcache_size].fd_chain[:-1]: - assert pwndbg.aglib.vmmap.find(addr) - - # check fastbin - gdb.execute("continue") - - result = allocator.fastbins() - assert result.bin_type == BinType.FAST - assert (fastbin_size in result.bins) and ( - len(result.bins[fastbin_size].fd_chain) == fastbin_count + 1 - ) - for addr in result.bins[fastbin_size].fd_chain[:-1]: - assert pwndbg.aglib.vmmap.find(addr) - - # check unsortedbin - gdb.execute("continue") - - result = allocator.unsortedbin() - assert result.bin_type == BinType.UNSORTED - assert ( - len(result.bins["all"].fd_chain) == smallbin_count + 2 - and len(result.bins["all"].bk_chain) == smallbin_count + 2 - ) - assert not result.bins["all"].is_corrupted - for addr in result.bins["all"].fd_chain[:-1]: - assert pwndbg.aglib.vmmap.find(addr) - for addr in result.bins["all"].bk_chain[:-1]: - assert pwndbg.aglib.vmmap.find(addr) - - # check smallbins - gdb.execute("continue") - - result = allocator.smallbins() - assert result.bin_type == "smallbins" - assert ( - len(result.bins[smallbin_size].fd_chain) == smallbin_count + 2 - and len(result.bins[smallbin_size].bk_chain) == smallbin_count + 2 - ) - assert not result.bins[smallbin_size].is_corrupted - for addr in result.bins[smallbin_size].fd_chain[:-1]: - assert pwndbg.aglib.vmmap.find(addr) - for addr in result.bins[smallbin_size].bk_chain[:-1]: - assert pwndbg.aglib.vmmap.find(addr) - - # check largebins - gdb.execute("continue") - - result = allocator.largebins() - assert result.bin_type == BinType.LARGE - assert ( - len(result.bins[largebin_size].fd_chain) == largebin_count + 2 - and len(result.bins[largebin_size].bk_chain) == largebin_count + 2 - ) - assert not result.bins[largebin_size].is_corrupted - for addr in result.bins[largebin_size].fd_chain[:-1]: - assert pwndbg.aglib.vmmap.find(addr) - for addr in result.bins[largebin_size].bk_chain[:-1]: - assert pwndbg.aglib.vmmap.find(addr) - - # check corrupted - gdb.execute("continue") - result = allocator.smallbins() - assert result.bin_type == BinType.SMALL - assert result.bins[smallbin_size].is_corrupted - - result = allocator.largebins() - assert result.bin_type == BinType.LARGE - assert result.bins[largebin_size].is_corrupted - - gdb.execute("bins") - - -def test_largebins_size_range_64bit(start_binary): - """ - Ensure the "largebins" command displays the correct largebin size ranges. - This test targets 64-bit architectures. - """ - start_binary(get_binary("initialized_heap.x86-64.out")) - gdb.execute("break break_here") - gdb.execute("continue") - - command_output = gdb.execute("largebins --verbose", to_string=True).splitlines()[1:] - - expected = [ - "0x400-0x430", - "0x440-0x470", - "0x480-0x4b0", - "0x4c0-0x4f0", - "0x500-0x530", - "0x540-0x570", - "0x580-0x5b0", - "0x5c0-0x5f0", - "0x600-0x630", - "0x640-0x670", - "0x680-0x6b0", - "0x6c0-0x6f0", - "0x700-0x730", - "0x740-0x770", - "0x780-0x7b0", - "0x7c0-0x7f0", - "0x800-0x830", - "0x840-0x870", - "0x880-0x8b0", - "0x8c0-0x8f0", - "0x900-0x930", - "0x940-0x970", - "0x980-0x9b0", - "0x9c0-0x9f0", - "0xa00-0xa30", - "0xa40-0xa70", - "0xa80-0xab0", - "0xac0-0xaf0", - "0xb00-0xb30", - "0xb40-0xb70", - "0xb80-0xbb0", - "0xbc0-0xbf0", - "0xc00-0xc30", - "0xc40-0xdf0", - "0xe00-0xff0", - "0x1000-0x11f0", - "0x1200-0x13f0", - "0x1400-0x15f0", - "0x1600-0x17f0", - "0x1800-0x19f0", - "0x1a00-0x1bf0", - "0x1c00-0x1df0", - "0x1e00-0x1ff0", - "0x2000-0x21f0", - "0x2200-0x23f0", - "0x2400-0x25f0", - "0x2600-0x27f0", - "0x2800-0x29f0", - "0x2a00-0x2ff0", - "0x3000-0x3ff0", - "0x4000-0x4ff0", - "0x5000-0x5ff0", - "0x6000-0x6ff0", - "0x7000-0x7ff0", - "0x8000-0x8ff0", - "0x9000-0x9ff0", - "0xa000-0xfff0", - "0x10000-0x17ff0", - "0x18000-0x1fff0", - "0x20000-0x27ff0", - "0x28000-0x3fff0", - "0x40000-0x7fff0", - "0x80000-∞", - ] - - for bin_index, size_range in enumerate(command_output): - assert size_range.split(":")[0] == expected[bin_index] - - -def test_largebins_size_range_32bit_big(start_binary): - """ - Ensure the "largebins" command displays the correct largebin size ranges. - This test targets 32-bit architectures with MALLOC_ALIGNMENT == 16. - """ - try: - start_binary(get_binary("initialized_heap_big.i386.out")) - except gdb.error: - pytest.skip("Test not supported on this platform.") - - gdb.execute("break break_here") - gdb.execute("continue") - - command_output = gdb.execute("largebins --verbose", to_string=True).splitlines()[1:] - - expected = [ - "0x3f0-0x3f0", - "0x400-0x430", - "0x440-0x470", - "0x480-0x4b0", - "0x4c0-0x4f0", - "0x500-0x530", - "0x540-0x570", - "0x580-0x5b0", - "0x5c0-0x5f0", - "0x600-0x630", - "0x640-0x670", - "0x680-0x6b0", - "0x6c0-0x6f0", - "0x700-0x730", - "0x740-0x770", - "0x780-0x7b0", - "0x7c0-0x7f0", - "0x800-0x830", - "0x840-0x870", - "0x880-0x8b0", - "0x8c0-0x8f0", - "0x900-0x930", - "0x940-0x970", - "0x980-0x9b0", - "0x9c0-0x9f0", - "0xa00-0xa30", - "0xa40-0xa70", - "0xa80-0xab0", - "0xac0-0xaf0", - "0xb00-0xb30", - "0xb40-0xb70", - "0xb80-0xb70", # Largebin 31 (bin 95) is unused, but its size is used to calculate the previous bin's maximum chunk size. - "0xb80-0xbf0", - "0xc00-0xdf0", - "0xe00-0xff0", - "0x1000-0x11f0", - "0x1200-0x13f0", - "0x1400-0x15f0", - "0x1600-0x17f0", - "0x1800-0x19f0", - "0x1a00-0x1bf0", - "0x1c00-0x1df0", - "0x1e00-0x1ff0", - "0x2000-0x21f0", - "0x2200-0x23f0", - "0x2400-0x25f0", - "0x2600-0x27f0", - "0x2800-0x29f0", - "0x2a00-0x2ff0", - "0x3000-0x3ff0", - "0x4000-0x4ff0", - "0x5000-0x5ff0", - "0x6000-0x6ff0", - "0x7000-0x7ff0", - "0x8000-0x8ff0", - "0x9000-0x9ff0", - "0xa000-0xfff0", - "0x10000-0x17ff0", - "0x18000-0x1fff0", - "0x20000-0x27ff0", - "0x28000-0x3fff0", - "0x40000-0x7fff0", - "0x80000-∞", - ] - - for bin_index, size_range in enumerate(command_output): - assert size_range.split(":")[0] == expected[bin_index] - - -def test_smallbins_sizes_64bit(start_binary): - """ - Ensure the "smallbins" command displays the correct smallbin sizes. - This test targets 64-bit architectures. - """ - start_binary(get_binary("initialized_heap.x86-64.out")) - gdb.execute("break break_here") - gdb.execute("continue") - - command_output = gdb.execute("smallbins --verbose", to_string=True).splitlines()[1:] - - expected = [ - "0x20", - "0x30", - "0x40", - "0x50", - "0x60", - "0x70", - "0x80", - "0x90", - "0xa0", - "0xb0", - "0xc0", - "0xd0", - "0xe0", - "0xf0", - "0x100", - "0x110", - "0x120", - "0x130", - "0x140", - "0x150", - "0x160", - "0x170", - "0x180", - "0x190", - "0x1a0", - "0x1b0", - "0x1c0", - "0x1d0", - "0x1e0", - "0x1f0", - "0x200", - "0x210", - "0x220", - "0x230", - "0x240", - "0x250", - "0x260", - "0x270", - "0x280", - "0x290", - "0x2a0", - "0x2b0", - "0x2c0", - "0x2d0", - "0x2e0", - "0x2f0", - "0x300", - "0x310", - "0x320", - "0x330", - "0x340", - "0x350", - "0x360", - "0x370", - "0x380", - "0x390", - "0x3a0", - "0x3b0", - "0x3c0", - "0x3d0", - "0x3e0", - "0x3f0", - ] - - for bin_index, bin_size in enumerate(command_output): - assert bin_size.split(":")[0] == expected[bin_index] - - -def test_smallbins_sizes_32bit_big(start_binary): - """ - Ensure the "smallbins" command displays the correct smallbin sizes. - This test targets 32-bit architectures with MALLOC_ALIGNMENT == 16. - """ - try: - start_binary(get_binary("initialized_heap_big.i386.out")) - except gdb.error: - pytest.skip("Test not supported on this platform.") - - gdb.execute("break break_here") - gdb.execute("continue") - - command_output = gdb.execute("smallbins --verbose", to_string=True).splitlines()[1:] - - expected = [ - "0x10", - "0x20", - "0x30", - "0x40", - "0x50", - "0x60", - "0x70", - "0x80", - "0x90", - "0xa0", - "0xb0", - "0xc0", - "0xd0", - "0xe0", - "0xf0", - "0x100", - "0x110", - "0x120", - "0x130", - "0x140", - "0x150", - "0x160", - "0x170", - "0x180", - "0x190", - "0x1a0", - "0x1b0", - "0x1c0", - "0x1d0", - "0x1e0", - "0x1f0", - "0x200", - "0x210", - "0x220", - "0x230", - "0x240", - "0x250", - "0x260", - "0x270", - "0x280", - "0x290", - "0x2a0", - "0x2b0", - "0x2c0", - "0x2d0", - "0x2e0", - "0x2f0", - "0x300", - "0x310", - "0x320", - "0x330", - "0x340", - "0x350", - "0x360", - "0x370", - "0x380", - "0x390", - "0x3a0", - "0x3b0", - "0x3c0", - "0x3d0", - "0x3e0", - ] - - for bin_index, bin_size in enumerate(command_output): - assert bin_size.split(":")[0] == expected[bin_index] - - -def test_heap_corruption_low_dereference(start_binary): - """ - Tests that the bins corruption check doesn't report - corrupted bins when heap-dereference-limit is less - than the number of chunks in a bin. - """ - - start_binary(BINARY) - gdb.execute("set context-output /dev/null") - gdb.execute("b breakpoint", to_string=True) - - gdb.execute("continue") - gdb.execute("continue") - gdb.execute("continue") - gdb.execute("continue") - - # unsorted bin now has 3 chunks - - gdb.execute("set heap-dereference-limit 1") - - bins_output = gdb.execute("bins", to_string=True) - assert "corrupted" not in bins_output diff --git a/tests/library/gdb/tests/heap/test_vis_heap_chunks.py b/tests/library/gdb/tests/heap/test_vis_heap_chunks.py deleted file mode 100644 index dd3f51dd1..000000000 --- a/tests/library/gdb/tests/heap/test_vis_heap_chunks.py +++ /dev/null @@ -1,235 +0,0 @@ -from __future__ import annotations - -import gdb - -import pwndbg.aglib.arch -import pwndbg.aglib.memory -import pwndbg.aglib.vmmap - -from .. import get_binary - -HEAP_VIS = get_binary("heap_vis.native.out") - - -def test_vis_heap_chunk_command(start_binary): - start_binary(HEAP_VIS) - gdb.execute("break break_here") - gdb.execute("continue") - - # TODO/FIXME: Shall we have a standard method to do this kind of filtering? - # Note that we have `pages_filter` in pwndbg/pwndbg/commands/vmmap.py heh - heap_page = next(page for page in pwndbg.aglib.vmmap.get() if page.objfile == "[heap]") - - first_chunk_size = pwndbg.aglib.memory.u64(heap_page.start + pwndbg.aglib.arch.ptrsize) - - # Just a sanity check... - assert (heap_page.start & 0xFFF) == 0 - - result = gdb.execute("vis-heap-chunk 1", to_string=True).splitlines() - - # We will use `heap_addr` variable to fill in proper addresses below - heap_addr = heap_page.start - - # We sometimes need that value, so let's cache it - dq2 = None - - def heap_iter(offset=0x10): - nonlocal heap_addr - heap_addr += offset - return heap_addr - - def hexdump_16B(gdb_symbol): - from pwndbg.commands.ptmalloc2 import bin_ascii - - first, second = gdb.execute(f"x/16xb {gdb_symbol}", to_string=True).splitlines() - first = [int(v, 16) for v in first.split(":")[1].split("\t")[1:]] - second = [int(v, 16) for v in second.split(":")[1].split("\t")[1:]] - - return bin_ascii(first + second) - - def vis_heap_line(heap_iter_offset=0x10, suffix=""): - """Returns data to format a vis_heap_chunk line""" - addr = heap_iter(heap_iter_offset) - hexdump = hexdump_16B(addr) - - nonlocal dq2 - dq1, dq2 = map(pwndbg.aglib.memory.u64, (addr, addr + 8)) - - formatted = f"{addr:#x}\t{dq1:#018x}\t{dq2:#018x}\t{hexdump}" - formatted += suffix - - return formatted - - first_hexdump = hexdump_16B(hex(heap_page.start)) - - expected = [ - "", - f"{heap_iter(0):#x}\t0x0000000000000000\t{first_chunk_size | 1:#018x}\t{first_hexdump}", - ] - for _ in range(first_chunk_size // 16 - 1): - expected.append( - "%#x\t0x0000000000000000\t0x0000000000000000\t................" % heap_iter() - ) - expected.append("%#x\t0x0000000000000000\t \t........" % heap_iter()) - assert result == expected - - ## This time using `default-visualize-chunk-number` to set `count`, to make sure that the config can work - gdb.execute("set default-visualize-chunk-number 1") - assert pwndbg.config.default_visualize_chunk_number == 1 - result = gdb.execute("vis-heap-chunk", to_string=True).splitlines() - # No parameters were passed and top isn't reached so help text is shown - no_params_help = "Not all chunks were shown, see `vis --help` for more information." - assert result == expected + [no_params_help] - gdb.execute( - "set default-visualize-chunk-number %d" - % pwndbg.config.default_visualize_chunk_number.default - ) - - del result - - ## Test vis_heap_chunk with count=2 - result2 = gdb.execute("vis-heap-chunk 2", to_string=True).splitlines() - - # Note: we copy expected here but we truncate last line as it is easier - # to provide it in full here - expected2 = expected[:-1] + [ - "%#x\t0x0000000000000000\t0x0000000000000021\t........!......." % heap_iter(0), - "%#x\t0x0000000000000000\t0x0000000000000000\t................" % heap_iter(), - "%#x\t0x0000000000000000\t \t........" % heap_iter(), - ] - assert result2 == expected2 - - del expected - del result2 - - ## Test vis_heap_chunk with count=3 - result3 = gdb.execute("vis-heap-chunk 3", to_string=True).splitlines() - - # Note: we copy expected here but we truncate last line as it is easier - # to provide it in full here - expected3 = expected2[:-1] + [ - "%#x\t0x0000000000000000\t0x0000000000000021\t........!......." % heap_iter(0), - "%#x\t0x0000000000000000\t0x0000000000000000\t................" % heap_iter(), - vis_heap_line(suffix="\t <-- Top chunk"), - ] - assert result3 == expected3 - - del expected2 - del result3 - - ## Test vis_heap_chunk with count=4 - result4 = gdb.execute("vis-heap-chunk 4", to_string=True).splitlines() - - # Since on this breakpoint we only have 4 chunks, the output should probably be the same? - # TODO/FIXME: Shall we maybe print user that there are only 3 chunks? - assert result4 == expected3 - - del result4 - - ## Test vis_heap_chunk with no flags - result_all = gdb.execute("vis-heap-chunk", to_string=True).splitlines() - assert result_all == expected3 - - del result_all - - # Continue, so that another allocation is made - gdb.execute("continue") - - ## Test vis_heap_chunk with count=4 again - result4_b = gdb.execute("vis-heap-chunk 4", to_string=True).splitlines() - - expected4_b = expected3[:-1] + [ - "%#x\t0x0000000000000000\t0x0000000000000031\t........1......." % heap_iter(0), - "%#x\t0x0000000000000000\t0x0000000000000000\t................" % heap_iter(), - "%#x\t0x0000000000000000\t0x0000000000000000\t................" % heap_iter(), - vis_heap_line(suffix="\t <-- Top chunk"), - ] - - assert result4_b == expected4_b - - del expected3 - del result4_b - - ## Test vis_heap_chunk with no flags - result_all2 = gdb.execute("vis-heap-chunk", to_string=True).splitlines() - assert result_all2 == expected4_b - - del result_all2 - del expected4_b - - ## Continue, so that alloc[1] is freed - gdb.execute("continue") - - result_all3 = gdb.execute("vis-heap-chunk", to_string=True).splitlines() - - # The tcache chunks have two fields: next and key - # We are fetching it from the glibc's TLS tcache variable :) - tcache_next = int(gdb.parse_and_eval("tcache->entries[0]->next")) - tcache_key = int(gdb.parse_and_eval("tcache->entries[0]->key")) - - tcache_hexdump = hexdump_16B("tcache->entries[0]") - freed_chunk = "{:#x}\t{:#018x}\t{:#018x}\t{}\t ".format( - heap_iter(-0x40), - tcache_next, - tcache_key, - tcache_hexdump, - ) - freed_chunk += "<-- tcachebins[0x20][0/1]" - - heap_addr = heap_page.start - - expected_all3 = [""] - - # Add the biggest chunk, the one from libc - expected_all3.append(vis_heap_line(0)) - - last_chunk_size = dq2 - for _ in range(last_chunk_size // 16): - expected_all3.append(vis_heap_line()) - - last_chunk_size = dq2 - for _ in range(last_chunk_size // 16): - expected_all3.append(vis_heap_line()) - expected_all3.append(vis_heap_line(suffix="\t <-- tcachebins[0x20][0/1]")) - - expected_all3.append(vis_heap_line()) - last_chunk_size = dq2 - for _ in range(last_chunk_size // 16 - 1): - expected_all3.append(vis_heap_line()) - expected_all3.append(vis_heap_line(suffix="\t <-- Top chunk")) - - assert result_all3 == expected_all3 - - del result_all3 - del expected_all3 - - # Continue, malloc two large chunks and free one - gdb.execute("continue") - - # Get default result without max-visualize-chunk-size setting - default_result = gdb.execute("vis-heap-chunk", to_string=True).splitlines() - assert len(default_result) > 0x300 - - # Set max display size to 100 (no "0x" for misalignment) - gdb.execute("set max-visualize-chunk-size 100") - - omitted_result = gdb.execute("vis-heap-chunk", to_string=True).splitlines() - assert len(omitted_result) < 0x30 - for omitted_line in omitted_result: - assert omitted_line in default_result or set(omitted_line) == {"."} - - no_truncate_result = gdb.execute("vis-heap-chunk -n", to_string=True).splitlines() - assert no_truncate_result == default_result - - del default_result - del omitted_result - del no_truncate_result - - # Continue, mock overflow changing the chunk size - gdb.execute("continue") - - overflow_result = gdb.execute("vis-heap-chunk", to_string=True) - assert "\t0x0000000000000000\t0x4141414141414141\t........AAAAAAAA" in overflow_result - assert len(overflow_result.splitlines()) < 0x500 - - del overflow_result