Fix jemalloc and qemu tests on nixos (#2515)

* docs: Add missing testing toc entries and other minor formatting

* fix: Adjust nix dev shell packages to properly support jemalloc and qemu tests

* fix(jemalloc): Add more robust error handling to jemalloc commands and fix test

* fix: point JEMALLOC_PATH to correct jemalloc package path

* fix: Use correct aglib-compatible symbol resolution function

* fix: re-enable test_jemalloc_heap test and make slightly more forgiving
pull/2529/head
Aaron Adams 1 year ago committed by GitHub
parent 019ff1a625
commit f26453884f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -103,8 +103,8 @@ We use `qemu-system` for full system level emulation for our Linux kernel tests.
### Testing Under Nix ### Testing Under Nix
You will need to build a nix-compatible gdbinit.py file, which you can do with `nix build .#pwndbg-dev`. Then simply run You will need to build a nix-compatible `gdbinit.py` file, which you can do with `nix build .#pwndbg-dev`. Then simply
the test by adding the `--nix` flag: run the test by adding the `--nix` flag:
```bash ```bash
./tests.sh --nix [filter] ./tests.sh --nix [filter]

@ -1,4 +1,4 @@
# This should be kept in sync with setup-dev.sh and lint.sh requirements # Packages installed by this file should be kept in sync with setup-dev.sh and lint.sh requirements
{ {
pkgs ? # If pkgs is not defined, instantiate nixpkgs from locked commit pkgs ? # If pkgs is not defined, instantiate nixpkgs from locked commit
let let
@ -15,45 +15,81 @@
... ...
}: }:
let let
lib = pkgs.lib;
pyEnv = import ./pyenv.nix { pyEnv = import ./pyenv.nix {
inherit inherit
pkgs pkgs
lib
python3 python3
inputs inputs
isLLDB isLLDB
; ;
lib = pkgs.lib;
isDev = true; isDev = true;
}; };
jemalloc-static = pkgs.jemalloc.overrideAttrs (oldAttrs: {
version = "5.3.0"; # version match setup-dev.sh
configureFlags = (oldAttrs.configureFlags or [ ]) ++ [
"--enable-static"
"--disable-shared"
];
# debug symbols currently required for jemalloc.py type resolution
preBuild = ''
makeFlagsArray+=(CFLAGS="-O0 -g")
'';
postInstall = ''
${oldAttrs.postInstall or ""}
cp -v lib/libjemalloc.a $out/lib/
'';
dontStrip = true; # don't strip the debug symbols we added
});
in in
{ {
default = pkgs.mkShell { default = pkgs.mkShell {
NIX_CONFIG = "extra-experimental-features = nix-command flakes repl-flake"; NIX_CONFIG = "extra-experimental-features = nix-command flakes repl-flake";
# Anything not handled by the poetry env # Anything not handled by the poetry env
nativeBuildInputs = nativeBuildInputs =
(with pkgs; [ builtins.attrValues {
# from setup-dev.sh inherit (pkgs)
nasm # from setup-dev.sh
gcc nasm
curl gcc
gdb curl
parallel gdb
qemu parallel
netcat-openbsd qemu
zig_0_10 # matches setup-dev.sh netcat-openbsd
go zig_0_10 # version match setup-dev.sh
go
# from qemu-tests.sh
binutils
;
}
++ [
jemalloc-static
# from qemu-tests.sh
(pkgs.writeShellScriptBin "gdb-multiarch" ''
exec ${lib.getBin pkgs.gdb}/bin/gdb "$@"
'')
pkgs.pkgsCross.aarch64-multiplatform.buildPackages.binutils
pkgs.pkgsCross.riscv64.buildPackages.binutils
pkgs.pkgsCross.mipsel-linux-gnu.buildPackages.binutils
(pkgs.writeShellScriptBin "aarch64-linux-gnu-gcc" ''
exec ${lib.getBin pkgs.pkgsCross.aarch64-multiplatform.buildPackages.gcc}/bin/aarch64-unknown-linux-gnu-gcc "$@"
'')
(pkgs.writeShellScriptBin "riscv64-linux-gnu-gcc" ''
exec ${lib.getBin pkgs.pkgsCross.riscv64.buildPackages.gcc}/bin/riscv64-unknown-linux-gnu-gcc "$@"
'')
pyEnv pyEnv
]) ]
++ pkgs.lib.optionals isLLDB ( ++ pkgs.lib.optionals isLLDB [
with pkgs; pkgs.lldb_19
[ ];
lldb_19
]
);
shellHook = '' shellHook = ''
export PWNDBG_VENV_PATH="PWNDBG_PLEASE_SKIP_VENV" export PWNDBG_VENV_PATH="PWNDBG_PLEASE_SKIP_VENV"
export ZIGPATH="${pkgs.lib.getBin pkgs.zig_0_10}/bin/" export ZIGPATH="${pkgs.lib.getBin pkgs.zig_0_10}/bin/"
export JEMALLOC_PATH="${jemalloc-static}/lib/libjemalloc.a"
''; '';
}; };
} }

@ -192,6 +192,10 @@ class RTree:
def __init__(self, addr: int) -> None: def __init__(self, addr: int) -> None:
self._addr = addr self._addr = addr
rtree_s = pwndbg.aglib.typeinfo.load("struct rtree_s")
if rtree_s is None:
raise pwndbg.dbg_mod.Error("rtree_s type not found")
# self._Value = pwndbg.aglib.memory.poi(emap_s, self._addr) # self._Value = pwndbg.aglib.memory.poi(emap_s, self._addr)
# self._Value = pwndbg.aglib.memory.fetch_struct_as_dictionary( # self._Value = pwndbg.aglib.memory.fetch_struct_as_dictionary(
@ -203,14 +207,10 @@ class RTree:
self._extents = None self._extents = None
@staticmethod @staticmethod
def get_rtree() -> RTree | None: def get_rtree() -> RTree:
try: addr = pwndbg.dbg.selected_inferior().symbol_address_from_name("je_arena_emap_global")
addr = pwndbg.dbg.selected_inferior().symbol_address_from_name("je_arena_emap_global") if addr is None:
if addr is None: raise pwndbg.dbg_mod.Error("Required je_arena_emap_global symbol not found")
return None
except pwndbg.dbg_mod.Error:
return None
return RTree(addr) return RTree(addr)
@property @property
@ -261,9 +261,14 @@ class RTree:
- Jemalloc stores the extent address in the rtree as a node and to find a specific node we need a address key. - Jemalloc stores the extent address in the rtree as a node and to find a specific node we need a address key.
""" """
rtree_node_elm_s = pwndbg.aglib.typeinfo.load("struct rtree_node_elm_s") rtree_node_elm_s = pwndbg.aglib.typeinfo.load("struct rtree_node_elm_s")
if rtree_node_elm_s is None:
raise pwndbg.dbg_mod.Error("rtree_node_elm_s type not found")
rtree_leaf_elm_s = pwndbg.aglib.typeinfo.load("struct rtree_leaf_elm_s") rtree_leaf_elm_s = pwndbg.aglib.typeinfo.load("struct rtree_leaf_elm_s")
if rtree_leaf_elm_s is None:
raise pwndbg.dbg_mod.Error("rtree_leaf_elm_s type not found")
# Credits: 盏一's jegdb # Credits: 盏一's jegdb
# https://web.archive.org/web/20221114090949/https://github.com/hidva/hidva.github.io/blob/dev/_drafts/jegdb.py
# For subkey 0 # For subkey 0
subkey = self.__subkey(key, 1) subkey = self.__subkey(key, 1)
@ -327,7 +332,11 @@ class RTree:
extent_addresses = [] extent_addresses = []
rtree_node_elm_s = pwndbg.aglib.typeinfo.load("struct rtree_node_elm_s") rtree_node_elm_s = pwndbg.aglib.typeinfo.load("struct rtree_node_elm_s")
if rtree_node_elm_s is None:
raise pwndbg.dbg_mod.Error("rtree_node_elm_s type not found")
rtree_leaf_elm_s = pwndbg.aglib.typeinfo.load("struct rtree_leaf_elm_s") rtree_leaf_elm_s = pwndbg.aglib.typeinfo.load("struct rtree_leaf_elm_s")
if rtree_leaf_elm_s is None:
raise pwndbg.dbg_mod.Error("rtree_leaf_elm_s type not found")
max_subkeys = 1 << rtree_levels[RTREE_HEIGHT - 1][0]["bits"] max_subkeys = 1 << rtree_levels[RTREE_HEIGHT - 1][0]["bits"]
# print("max_subkeys: ", max_subkeys) # print("max_subkeys: ", max_subkeys)

@ -1590,7 +1590,9 @@ def jemalloc_find_extent(addr) -> None:
rtree = jemalloc.RTree.get_rtree() rtree = jemalloc.RTree.get_rtree()
extent = rtree.lookup_hard(addr) extent = rtree.lookup_hard(addr)
if extent is None:
print(message.error("ERROR: Extent not found"))
return
# print pointer address first, then extent address then extent information # print pointer address first, then extent address then extent information
print(f"Pointer Address: {hex(addr)}") print(f"Pointer Address: {hex(addr)}")
print(f"Extent Address: {hex(extent.extent_address)}") print(f"Extent Address: {hex(extent.extent_address)}")
@ -1639,6 +1641,9 @@ def jemalloc_heap() -> None:
rtree = jemalloc.RTree.get_rtree() rtree = jemalloc.RTree.get_rtree()
extents = rtree.extents extents = rtree.extents
if len(extents) == 0:
print(message.warn("No extents found"))
return
for extent in extents: for extent in extents:
# TODO: refactor so not create copies # TODO: refactor so not create copies
jemalloc_extent_info(extent.extent_address, header=False) jemalloc_extent_info(extent.extent_address, header=False)

@ -21,6 +21,10 @@ GO = go
SOURCES_GO = $(wildcard *.go) SOURCES_GO = $(wildcard *.go)
COMPILED_GO = $(SOURCES_GO:.go=.x86) $(SOURCES_GO:.go=.x64) COMPILED_GO = $(SOURCES_GO:.go=.x86) $(SOURCES_GO:.go=.x64)
ifndef JEMALLOC_PATH
JEMALLOC_PATH = /usr/local/lib/libjemalloc.a
endif
ifeq ($(TARGET), x86) ifeq ($(TARGET), x86)
CFLAGS += -m32 CFLAGS += -m32
endif endif
@ -95,11 +99,15 @@ heap_malloc_chunk.out: heap_malloc_chunk.c
heap_jemalloc_extent_info.out: heap_jemalloc_extent_info.c heap_jemalloc_extent_info.out: heap_jemalloc_extent_info.c
@echo "[+] Building heap_jemalloc_extent_info.out" @echo "[+] Building heap_jemalloc_extent_info.out"
${CC} -g -O0 -Wno-nonnull -Wno-unused-result -o heap_jemalloc_extent_info.out heap_jemalloc_extent_info.c -lpthread /usr/local/lib/libjemalloc.a -lm -lstdc++ -pthread -ldl ${CC} -g -O0 -Wno-nonnull -Wno-unused-result \
-o heap_jemalloc_extent_info.out heap_jemalloc_extent_info.c \
-lpthread ${JEMALLOC_PATH} -lm -lstdc++ -pthread -ldl
heap_jemalloc_heap.out: heap_jemalloc_heap.c heap_jemalloc_heap.out: heap_jemalloc_heap.c
@echo "[+] Building heap_jemalloc_heap.out" @echo "[+] Building heap_jemalloc_heap.out"
${CC} -g -O0 -Wno-nonnull -Wno-unused-result -o heap_jemalloc_heap.out heap_jemalloc_heap.c -lpthread /usr/local/lib/libjemalloc.a -lm -lstdc++ -pthread -ldl ${CC} -g -O0 -Wno-nonnull -Wno-unused-result \
-o heap_jemalloc_heap.out heap_jemalloc_heap.c \
-lpthread ${JEMALLOC_PATH} \-lm -lstdc++ -pthread -ldl
multiple_threads.out: multiple_threads.c multiple_threads.out: multiple_threads.c

@ -547,12 +547,15 @@ def test_jemalloc_extent_info(start_binary):
gdb.execute("break break_here") gdb.execute("break break_here")
gdb.execute("continue") gdb.execute("continue")
EXPECTED_EXTENT_ADDRESS = 0x7FFFF7A16580 find_extent_results = gdb.execute("jemalloc_find_extent ptr", to_string=True).splitlines()
extent_address = None
for line in find_extent_results:
if "Extent Address:" in line:
extent_address = int(line.split(" ")[-1], 16)
if extent_address is None:
raise ValueError("Could not find extent address")
# run jemalloc extent_info command # run jemalloc extent_info command
result = gdb.execute( result = gdb.execute(f"jemalloc_extent_info {extent_address}", to_string=True).splitlines()
f"jemalloc_extent_info {EXPECTED_EXTENT_ADDRESS}", to_string=True
).splitlines()
expected_output = [ expected_output = [
"Jemalloc extent info", "Jemalloc extent info",
@ -568,7 +571,6 @@ def test_jemalloc_extent_info(start_binary):
assert re.match(expected_output[i], result[i]) assert re.match(expected_output[i], result[i])
@pytest.mark.skip(reason="Output is resulting in duplicate extents")
def test_jemalloc_heap(start_binary): def test_jemalloc_heap(start_binary):
start_binary(HEAP_JEMALLOC_HEAP) start_binary(HEAP_JEMALLOC_HEAP)
gdb.execute("break break_here") gdb.execute("break break_here")
@ -582,14 +584,8 @@ def test_jemalloc_heap(start_binary):
"This command was tested only for jemalloc 5.3.0 and does not support lower versions", "This command was tested only for jemalloc 5.3.0 and does not support lower versions",
] ]
expected_output += [ # Extent sizes different depending on the system built (it would seem), so only check for the 0x8000 size,
"", # since it seems consistent. The output of an extent implies the rest of the command is working
"Allocated Address: " + re_match_valid_address,
r"Extent Address: " + re_match_valid_address,
"Size: 0x401000",
"Small class: False",
]
expected_output += [ expected_output += [
"", "",
"Allocated Address: " + re_match_valid_address, "Allocated Address: " + re_match_valid_address,
@ -598,21 +594,5 @@ def test_jemalloc_heap(start_binary):
"Small class: False", "Small class: False",
] ]
expected_output += [
"",
"Allocated Address: " + re_match_valid_address,
r"Extent Address: " + re_match_valid_address,
"Size: 0x8000",
"Small class: False",
]
expected_output += [
"",
"Allocated Address: " + re_match_valid_address,
r"Extent Address: " + re_match_valid_address,
"Size: 0x1f7000",
"Small class: False",
]
for i in range(len(expected_output)): for i in range(len(expected_output)):
assert re.match(expected_output[i], result[i]) assert re.match(expected_output[i], result[i])

Loading…
Cancel
Save