Fix up archlinux glibc 2.42 tests (#3487)

* fix unsorted bin test

* fix tcache vis test for glibc 2.42

* remove unecessary gdb tests
pull/3394/head
k4lizen 2 days ago committed by GitHub
parent a2bc608387
commit d3bf95f3ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -27,22 +27,30 @@ async def test_heap_bins(ctrl: Controller) -> None:
# check if all bins are empty at first # check if all bins are empty at first
allocator = pwndbg.aglib.heap.current allocator = pwndbg.aglib.heap.current
assert allocator is not None
addr = pwndbg.aglib.symbol.lookup_symbol_addr("tcache_size") addr = pwndbg.aglib.symbol.lookup_symbol_addr("tcache_size")
assert addr is not None
tcache_size = allocator._request2size(pwndbg.aglib.memory.u64(addr)) tcache_size = allocator._request2size(pwndbg.aglib.memory.u64(addr))
addr = pwndbg.aglib.symbol.lookup_symbol_addr("tcache_count") addr = pwndbg.aglib.symbol.lookup_symbol_addr("tcache_count")
assert addr is not None
tcache_count = pwndbg.aglib.memory.u64(addr) tcache_count = pwndbg.aglib.memory.u64(addr)
addr = pwndbg.aglib.symbol.lookup_symbol_addr("fastbin_size") addr = pwndbg.aglib.symbol.lookup_symbol_addr("fastbin_size")
fastbin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr)) fastbin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr))
addr = pwndbg.aglib.symbol.lookup_symbol_addr("fastbin_count") addr = pwndbg.aglib.symbol.lookup_symbol_addr("fastbin_count")
assert addr is not None
fastbin_count = pwndbg.aglib.memory.u64(addr) fastbin_count = pwndbg.aglib.memory.u64(addr)
addr = pwndbg.aglib.symbol.lookup_symbol_addr("smallbin_size") addr = pwndbg.aglib.symbol.lookup_symbol_addr("smallbin_size")
assert addr is not None
smallbin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr)) smallbin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr))
addr = pwndbg.aglib.symbol.lookup_symbol_addr("smallbin_count") addr = pwndbg.aglib.symbol.lookup_symbol_addr("smallbin_count")
assert addr is not None
smallbin_count = pwndbg.aglib.memory.u64(addr) smallbin_count = pwndbg.aglib.memory.u64(addr)
addr = pwndbg.aglib.symbol.lookup_symbol_addr("largebin_size") addr = pwndbg.aglib.symbol.lookup_symbol_addr("largebin_size")
assert addr is not None
largebin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr)) largebin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr))
addr = pwndbg.aglib.symbol.lookup_symbol_addr("largebin_count") addr = pwndbg.aglib.symbol.lookup_symbol_addr("largebin_count")
assert addr is not None
largebin_count = pwndbg.aglib.memory.u64(addr) largebin_count = pwndbg.aglib.memory.u64(addr)
result = allocator.tcachebins() result = allocator.tcachebins()
@ -108,9 +116,13 @@ async def test_heap_bins(ctrl: Controller) -> None:
result = allocator.unsortedbin() result = allocator.unsortedbin()
assert result.bin_type == BinType.UNSORTED 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 ( assert (
len(result.bins["all"].fd_chain) == smallbin_count + 2 (fd_chain_len == smallbin_count + 2 and bk_chain_len == smallbin_count + 2)
and len(result.bins["all"].bk_chain) == 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 assert not result.bins["all"].is_corrupted
for addr in result.bins["all"].fd_chain[:-1]: for addr in result.bins["all"].fd_chain[:-1]:

@ -67,15 +67,45 @@ async def test_vis_heap_chunk_command(ctrl: Controller) -> None:
first_hexdump = await hexdump_16B(hex(heap_page.start)) 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 = [ expected = [
"", "",
f"{heap_iter(0):#x}\t0x0000000000000000\t{first_chunk_size | 1:#018x}\t{first_hexdump}", 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( if using_num_slots:
"%#x\t0x0000000000000000\t0x0000000000000000\t................" % heap_iter() # The tcache struct is made up of 2-byte num_slots values and 8-byte pointers to the starts
) # of the bins.
expected.append("%#x\t0x0000000000000000\t \t........" % heap_iter()) 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 assert result == expected
## This time using `default-visualize-chunk-number` to set `count`, to make sure that the config can work ## This time using `default-visualize-chunk-number` to set `count`, to make sure that the config can work

@ -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

@ -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
Loading…
Cancel
Save