diff --git a/DEVELOPING.md b/DEVELOPING.md index fa1de7f18..df216091e 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -103,8 +103,8 @@ We use `qemu-system` for full system level emulation for our Linux kernel tests. ### 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 -the test by adding the `--nix` flag: +You will need to build a nix-compatible `gdbinit.py` file, which you can do with `nix build .#pwndbg-dev`. Then simply +run the test by adding the `--nix` flag: ```bash ./tests.sh --nix [filter] diff --git a/nix/devshell.nix b/nix/devshell.nix index 793a92399..ade080d01 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -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 let @@ -15,45 +15,81 @@ ... }: let + lib = pkgs.lib; pyEnv = import ./pyenv.nix { inherit pkgs + lib python3 inputs isLLDB ; - lib = pkgs.lib; 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 { default = pkgs.mkShell { NIX_CONFIG = "extra-experimental-features = nix-command flakes repl-flake"; # Anything not handled by the poetry env nativeBuildInputs = - (with pkgs; [ - # from setup-dev.sh - nasm - gcc - curl - gdb - parallel - qemu - netcat-openbsd - zig_0_10 # matches setup-dev.sh - go + builtins.attrValues { + inherit (pkgs) + # from setup-dev.sh + nasm + gcc + curl + gdb + parallel + qemu + netcat-openbsd + 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 - ]) - ++ pkgs.lib.optionals isLLDB ( - with pkgs; - [ - lldb_19 - ] - ); + ] + ++ pkgs.lib.optionals isLLDB [ + pkgs.lldb_19 + ]; shellHook = '' export PWNDBG_VENV_PATH="PWNDBG_PLEASE_SKIP_VENV" export ZIGPATH="${pkgs.lib.getBin pkgs.zig_0_10}/bin/" + export JEMALLOC_PATH="${jemalloc-static}/lib/libjemalloc.a" ''; }; } diff --git a/pwndbg/aglib/heap/jemalloc.py b/pwndbg/aglib/heap/jemalloc.py index 327df8bd5..2e3ba6ed9 100644 --- a/pwndbg/aglib/heap/jemalloc.py +++ b/pwndbg/aglib/heap/jemalloc.py @@ -192,6 +192,10 @@ class RTree: def __init__(self, addr: int) -> None: 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.fetch_struct_as_dictionary( @@ -203,14 +207,10 @@ class RTree: self._extents = None @staticmethod - def get_rtree() -> RTree | None: - try: - addr = pwndbg.dbg.selected_inferior().symbol_address_from_name("je_arena_emap_global") - if addr is None: - return None - except pwndbg.dbg_mod.Error: - return None - + def get_rtree() -> RTree: + addr = pwndbg.dbg.selected_inferior().symbol_address_from_name("je_arena_emap_global") + if addr is None: + raise pwndbg.dbg_mod.Error("Required je_arena_emap_global symbol not found") return RTree(addr) @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. """ 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") + if rtree_leaf_elm_s is None: + raise pwndbg.dbg_mod.Error("rtree_leaf_elm_s type not found") # Credits: 盏一's jegdb + # https://web.archive.org/web/20221114090949/https://github.com/hidva/hidva.github.io/blob/dev/_drafts/jegdb.py # For subkey 0 subkey = self.__subkey(key, 1) @@ -327,7 +332,11 @@ class RTree: extent_addresses = [] 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") + 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"] # print("max_subkeys: ", max_subkeys) diff --git a/pwndbg/commands/heap.py b/pwndbg/commands/heap.py index c89f472e1..4899f3f3c 100644 --- a/pwndbg/commands/heap.py +++ b/pwndbg/commands/heap.py @@ -1590,7 +1590,9 @@ def jemalloc_find_extent(addr) -> None: rtree = jemalloc.RTree.get_rtree() 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(f"Pointer Address: {hex(addr)}") print(f"Extent Address: {hex(extent.extent_address)}") @@ -1639,6 +1641,9 @@ def jemalloc_heap() -> None: rtree = jemalloc.RTree.get_rtree() extents = rtree.extents + if len(extents) == 0: + print(message.warn("No extents found")) + return for extent in extents: # TODO: refactor so not create copies jemalloc_extent_info(extent.extent_address, header=False) diff --git a/tests/gdb-tests/tests/binaries/makefile b/tests/gdb-tests/tests/binaries/makefile index b394d8044..5adf621ca 100644 --- a/tests/gdb-tests/tests/binaries/makefile +++ b/tests/gdb-tests/tests/binaries/makefile @@ -21,6 +21,10 @@ GO = go SOURCES_GO = $(wildcard *.go) COMPILED_GO = $(SOURCES_GO:.go=.x86) $(SOURCES_GO:.go=.x64) +ifndef JEMALLOC_PATH +JEMALLOC_PATH = /usr/local/lib/libjemalloc.a +endif + ifeq ($(TARGET), x86) CFLAGS += -m32 endif @@ -95,11 +99,15 @@ heap_malloc_chunk.out: heap_malloc_chunk.c heap_jemalloc_extent_info.out: heap_jemalloc_extent_info.c @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 @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 diff --git a/tests/gdb-tests/tests/heap/test_heap.py b/tests/gdb-tests/tests/heap/test_heap.py index 52ea62e51..35a9cf125 100644 --- a/tests/gdb-tests/tests/heap/test_heap.py +++ b/tests/gdb-tests/tests/heap/test_heap.py @@ -547,12 +547,15 @@ def test_jemalloc_extent_info(start_binary): gdb.execute("break break_here") 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 - result = gdb.execute( - f"jemalloc_extent_info {EXPECTED_EXTENT_ADDRESS}", to_string=True - ).splitlines() + result = gdb.execute(f"jemalloc_extent_info {extent_address}", to_string=True).splitlines() expected_output = [ "Jemalloc extent info", @@ -568,7 +571,6 @@ def test_jemalloc_extent_info(start_binary): assert re.match(expected_output[i], result[i]) -@pytest.mark.skip(reason="Output is resulting in duplicate extents") def test_jemalloc_heap(start_binary): start_binary(HEAP_JEMALLOC_HEAP) 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", ] - expected_output += [ - "", - "Allocated Address: " + re_match_valid_address, - r"Extent Address: " + re_match_valid_address, - "Size: 0x401000", - "Small class: False", - ] - + # 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 expected_output += [ "", "Allocated Address: " + re_match_valid_address, @@ -598,21 +594,5 @@ def test_jemalloc_heap(start_binary): "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)): assert re.match(expected_output[i], result[i])