diff --git a/.github/workflows/docs-dev.yml b/.github/workflows/docs-dev.yml index d752530f9..8bf38d70f 100644 --- a/.github/workflows/docs-dev.yml +++ b/.github/workflows/docs-dev.yml @@ -21,14 +21,6 @@ jobs: - name: Install dependencies run: uv sync --group docs --all-extras - - name: Install lldb - run: | - # https://pwndbg.re/pwndbg/dev/contributing/setup-pwndbg-dev/#running-with-lldb - sudo apt update - sudo apt install -y gdb lldb-19 liblldb-19-dev - echo "/usr/lib/llvm-19/bin/" >> $GITHUB_PATH - echo "/usr/lib/llvm-19/bin/lldb-server" >> $GITHUB_PATH - - name: Verify docs are up to date with source run: | ./scripts/verify-docs.sh diff --git a/.pwndbg_root b/.pwndbg_root new file mode 100644 index 000000000..e69de29bb diff --git a/docs/tutorials/env-vars.md b/docs/tutorials/env-vars.md index e45148652..f5220941e 100644 --- a/docs/tutorials/env-vars.md +++ b/docs/tutorials/env-vars.md @@ -6,7 +6,7 @@ Pwndbg relies on several environment variables to customize its behavior. Below - `EDITOR`, `VISUAL`: Used by the `cymbol` command to open an editor. - `HOME`, `XDG_CACHE_HOME`: Used by `lib.tempfile` to determine temporary file locations. - `PWNDBG_VENV_PATH`: Specifies the virtual environment path for Pwndbg. - - Set to `PWNDBG_PLEASE_SKIP_VENV` if you don't want Pwndbg to use a python virtual environment. You can also get this behaviour by creating a file named `.skip-venv` in the project root. + - Set to `PWNDBG_PLEASE_SKIP_VENV` if you don't want Pwndbg to use a python virtual environment. This effectively disables the use of `uv` in the project. - `PWNDBG_DISABLE_COLORS`: Disables colored output in Pwndbg. - `PWNDBG_LOGLEVEL`: Initial log level to use for log messages. diff --git a/gdbinit.py b/gdbinit.py index d04b1ed0b..913bd0cd9 100644 --- a/gdbinit.py +++ b/gdbinit.py @@ -1,113 +1,10 @@ from __future__ import annotations -import cProfile -import hashlib -import importlib.abc -import logging import os -import shutil import site -import subprocess import sys -import time -import traceback from glob import glob from pathlib import Path -from typing import List -from typing import Tuple - -import gdb - - -# Fix gdb readline bug: https://github.com/pwndbg/pwndbg/issues/2232#issuecomment-2542564965 -class GdbRemoveReadlineFinder(importlib.abc.MetaPathFinder): - def find_spec(self, fullname, path=None, target=None): - if fullname == "readline": - raise ImportError("readline module disabled under GDB") - return None - - -sys.meta_path.insert(0, GdbRemoveReadlineFinder()) - - -def hash_file(file_path: str | Path) -> str: - with open(file_path, "rb") as f: - file_hash = hashlib.sha256() - while True: - chunk = f.read(8192) - if not chunk: - break - file_hash.update(chunk) - return file_hash.hexdigest() - - -def run_uv_install( - binary_path: os.PathLike[str], src_root: Path, dev: bool = False -) -> Tuple[str, str, int]: - # We don't want to quietly uninstall dependencies by just specifying - # `--extra gdb` so we will be conservative and pull all extras in. - command: List[str] = [str(binary_path), "sync", "--all-extras"] - if dev: - command.append("--all-groups") - logging.debug(f"Updating deps with command: {' '.join(command)}") - result = subprocess.run(command, capture_output=True, text=True, cwd=src_root) - return result.stdout.strip(), result.stderr.strip(), result.returncode - - -def find_uv(venv_path: Path) -> Path | None: - binary_path = shutil.which("uv", path=venv_path / "bin") - if binary_path is not None: - return Path(binary_path) - - return None - - -def is_dev_mode(venv_path: Path) -> bool: - # If "dev.marker" exists in the venv directory, the user ran setup-dev.sh and is - # considered a developer - return (venv_path / "dev.marker").exists() - - -def update_deps(src_root: Path, venv_path: Path) -> None: - uv_lock_hash_path = venv_path / "uv.lock.hash" - - current_hash = hash_file(src_root / "uv.lock") - logging.debug(f"Current uv.lock hash: {current_hash}") - - stored_hash = None - if uv_lock_hash_path.exists(): - stored_hash = uv_lock_hash_path.read_text().strip() - logging.debug(f"Stored uv.lock hash: {stored_hash}") - else: - logging.debug("No stored hash found") - - # If the hashes don't match, update the dependencies - if current_hash == stored_hash: - return - - print("Detected outdated Pwndbg dependencies (uv.lock). Updating.") - uv_path = find_uv(venv_path) - if uv_path is None: - print( - "'uv' was not found on the $PATH. Please ensure it is installed and on the path, " - "or run `./setup.sh` to manually update Python dependencies." - ) - return - - dev_mode = is_dev_mode(venv_path) - stdout, stderr, return_code = run_uv_install(uv_path, src_root, dev=dev_mode) - if return_code == 0: - uv_lock_hash_path.write_text(current_hash) - - # Only print the uv output if anything was actually updated - if "No dependencies to install or update" not in stdout: - # The output is usually long and ends up paginated. This - # normally gets disabled later during initialization, but in - # this case we disable it here to avoid pagination. - gdb.execute("set pagination off", to_string=True) - print(stdout) - else: - print(stderr, file=sys.stderr) def fixup_paths(src_root: Path, venv_path: Path): @@ -129,7 +26,8 @@ def fixup_paths(src_root: Path, venv_path: Path): sys.path.insert(0, str(src_root)) # Push virtualenv's site-packages to the front - sys.path.remove(site_pkgs_path) + if site_pkgs_path in sys.path: + sys.path.remove(site_pkgs_path) sys.path.insert(1, site_pkgs_path) @@ -141,123 +39,20 @@ def get_venv_path(src_root: Path): return src_root / ".venv" -def skip_venv(src_root) -> bool: - return ( - os.environ.get("PWNDBG_VENV_PATH") == "PWNDBG_PLEASE_SKIP_VENV" - or (src_root / ".skip-venv").exists() - ) - - -def init_logger(): - log_level_env = os.environ.get("PWNDBG_LOGLEVEL", "WARNING") - log_level = getattr(logging, log_level_env.upper()) - - root_logger = logging.getLogger() - root_logger.setLevel(log_level) - - # Add a custom StreamHandler we will use to customize log message formatting. We - # configure the handler later, after pwndbg has been imported. - handler = logging.StreamHandler() - root_logger.addHandler(handler) - - return handler - - -def check_doubleload(): - if "pwndbg" in sys.modules: - print( - "Detected double-loading of Pwndbg (likely from both .gdbinit and the Pwndbg portable build)." - ) - print( - "To fix this, please remove the line 'source your-path/gdbinit.py' from your .gdbinit file." - ) - sys.exit(1) - - -def rewire_exit(): - major_ver = int(gdb.VERSION.split(".")[0]) - if major_ver <= 15: - # On certain verions of gdb (used on ubuntu 24.04) using sys.exit() can cause - # a segfault. See: - # https://github.com/pwndbg/pwndbg/pull/2900#issuecomment-2825456636 - # https://sourceware.org/bugzilla/show_bug.cgi?id=31946 - def _patched_exit(exit_code): - # argparse requires a SystemExit exception, otherwise our CLI commands will exit incorrectly on invalid arguments - stack_list = traceback.extract_stack(limit=2) - if len(stack_list) == 2: - p = stack_list[0] - if p.filename.endswith("/argparse.py"): - raise SystemExit() - - sys.stdout.flush() - sys.stderr.flush() - os._exit(exit_code) - - sys.exit = _patched_exit - - def main() -> None: - profiler = cProfile.Profile() - - start_time = None - if os.environ.get("PWNDBG_PROFILE") == "1": - start_time = time.time() - profiler.enable() - - rewire_exit() - check_doubleload() - - handler = init_logger() - src_root = Path(__file__).parent.resolve() - if not skip_venv(src_root): - venv_path = get_venv_path(src_root) - if not venv_path.exists(): - print(f"Cannot find Pwndbg virtualenv directory: {venv_path}. Please re-run setup.sh") - sys.exit(1) - no_auto_update = os.getenv("PWNDBG_NO_AUTOUPDATE") - if no_auto_update is None: - update_deps(src_root, venv_path) - fixup_paths(src_root, venv_path) - - # Force UTF-8 encoding (to_string=True to skip output appearing to the user) - try: - gdb.execute("set target-wide-charset UTF-8", to_string=True) - gdb.execute("set charset UTF-8", to_string=True) - except gdb.error as e: - print(f"Warning: Cannot set gdb charset: '{e}'") - - # Add the original stdout methods back to gdb._GdbOutputFile for pwnlib colors - sys.stdout.isatty = sys.__stdout__.isatty - sys.stdout.fileno = sys.__stdout__.fileno - - import pwndbg # noqa: F811 - import pwndbg.dbg.gdb - - pwndbg.dbg = pwndbg.dbg_mod.gdb.GDB() - pwndbg.dbg.setup() - - import pwndbg.log - import pwndbg.profiling - - # ColorFormatter relies on pwndbg being loaded, so we can't set it up until now - handler.setFormatter(pwndbg.log.ColorFormatter()) - - pwndbg.profiling.init(profiler, start_time) - if os.environ.get("PWNDBG_PROFILE") == "1": - pwndbg.profiling.profiler.stop("pwndbg-load.pstats") - pwndbg.profiling.profiler.start() + venv_path = get_venv_path(src_root) + if not venv_path.exists(): + print( + f"Cannot find Pwndbg virtualenv directory: {venv_path}. Please re-run setup.sh", + flush=True, + ) + os._exit(1) + fixup_paths(src_root, venv_path) + from pwndbginit.gdbinit import main_try -# We wrap everything in try/except so that we can exit GDB with an error code -# This is used by tests to check if gdbinit.py failed -try: - main() + main_try() - # We've already imported this in `main`, but we reimport it here so that it's - # available at the global scope when some starts a Python interpreter in GDB - import pwndbg # noqa: F401 -except Exception: - print(traceback.format_exc(), file=sys.stderr) - sys.exit(1) +main() diff --git a/lint.sh b/lint.sh index 525f0cf72..3e15007e6 100755 --- a/lint.sh +++ b/lint.sh @@ -32,7 +32,7 @@ done set -o xtrace -LINT_FILES="pwndbg tests *.py scripts" +LINT_FILES="pwndbg pwndbginit tests *.py scripts" call_shfmt() { local FLAGS=$1 @@ -71,5 +71,5 @@ $UV_RUN_LINT vermin -vvv --no-tips -t=3.10- --eval-annotations --violations ${LI # mypy is run in a separate step on GitHub Actions if [[ -z "$GITHUB_ACTIONS" ]]; then - $UV_RUN_LINT mypy pwndbg gdbinit.py lldbinit.py pwndbg-lldb.py tests/host + $UV_RUN_LINT mypy pwndbg pwndbginit tests/host fi diff --git a/nix/devshell.nix b/nix/devshell.nix index 674d31fa5..fa1d74abf 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -61,7 +61,6 @@ in nasm gcc curl - gdb parallel qemu zig_0_13 # version match setup-dev.sh @@ -76,21 +75,11 @@ in } ++ [ jemalloc-static + pkgs.gdb pyEnv - (pkgs.writeShellScriptBin "pwndbg" '' - exec ${lib.getBin pkgs.gdb}/bin/gdb --quiet --nx --init-command="$REPO_ROOT/gdbinit.py" $@ - '') ] ++ pkgs.lib.optionals isLLDB [ pkgs.lldb_20 - (pkgs.writeShellScriptBin "pwndbg-lldb" ( - (lib.optionalString (!pkgs.stdenv.isDarwin) '' - export LLDB_DEBUGSERVER_PATH=${lib.makeBinPath [ pkgs.lldb_20 ]}/lldb-server - '') - + '' - exec ${lib.getBin pyEnv}/bin/python3 $REPO_ROOT/pwndbg-lldb.py $@ - '' - )) ]; shellHook = '' export PWNDBG_NO_AUTOUPDATE=1 diff --git a/nix/pwndbg.nix b/nix/pwndbg.nix index c998cf4a1..b4048e281 100644 --- a/nix/pwndbg.nix +++ b/nix/pwndbg.nix @@ -10,14 +10,13 @@ }: let lib = pkgs.lib; - binPath = lib.makeBinPath ( + extraPackags = [ python3.pkgs.pwntools # ref: https://github.com/pwndbg/pwndbg/blob/2023.07.17/pwndbg/wrappers/checksec.py#L8 ] ++ lib.optionals pkgs.stdenv.isLinux [ python3.pkgs.ropper # ref: https://github.com/pwndbg/pwndbg/blob/2023.07.17/pwndbg/commands/ropper.py#L30 - ] - ); + ]; pyEnv = import ./pyenv.nix { inherit @@ -37,100 +36,38 @@ let in version; - pwndbg = pkgs.callPackage ( - { - stdenv, - makeWrapper, - }: - let - pwndbgName = if isLLDB then "pwndbg-lldb" else "pwndbg"; - in - stdenv.mkDerivation { - name = pwndbgName; - version = pwndbgVersion; - - src = lib.sourceByRegex inputs.self ( - [ - "pwndbg" - "pwndbg/.*" - ] - ++ ( - if isLLDB then - [ - "lldbinit.py" - "pwndbg-lldb.py" - ] - else - [ - "gdbinit.py" - ] - ) - ); - - nativeBuildInputs = [ makeWrapper ]; - buildInputs = [ pyEnv ]; - - installPhase = - let - fix_init_script = - { target, line }: - '' - # Build self-contained init script for lazy loading from vanilla gdb - # I purposely use insert() so I can re-import during development without having to restart gdb - sed "${line} i import sys, os\n\ - sys.path.insert(0, '${pyEnv}/${python3.sitePackages}')\n\ - sys.path.insert(0, '$out/share/pwndbg/')\n\ - os.environ['PATH'] += ':${binPath}'\n" -i ${target} - ''; - in - ( - if isLLDB then - '' - mkdir -p $out/share/pwndbg - mkdir -p $out/bin + pwndbg_gdb = + pkgs.runCommand "pwndbg" + { + version = pwndbgVersion; + nativeBuildInputs = [ pkgs.pkgsBuildHost.makeWrapper ]; + } + '' + mkdir -p $out/bin/ + makeWrapper ${pyEnv}/bin/pwndbg $out/bin/pwndbg \ + --prefix PATH : ${lib.makeBinPath ([ gdb ] ++ extraPackags)} + ''; - cp -r lldbinit.py pwndbg $out/share/pwndbg - cp pwndbg-lldb.py $out/bin/${pwndbgName} + pwndbg_lldb = + pkgs.runCommand "pwndbg-lldb" + { + version = pwndbgVersion; + nativeBuildInputs = [ pkgs.pkgsBuildHost.makeWrapper ]; + } + '' + mkdir -p $out/bin/ + makeWrapper ${pyEnv}/bin/pwndbg-lldb $out/bin/pwndbg-lldb \ + --prefix PATH : ${lib.makeBinPath ([ lldb ] ++ extraPackags)} + ''; - ${fix_init_script { - target = "$out/bin/${pwndbgName}"; - line = "4"; - }} - - touch $out/share/pwndbg/.skip-venv - wrapProgram $out/bin/${pwndbgName} \ - --prefix PATH : ${lib.makeBinPath [ lldb ]} \ - '' - + (lib.optionalString (!stdenv.isDarwin) '' - --set LLDB_DEBUGSERVER_PATH ${lib.makeBinPath [ lldb ]}/lldb-server \ - '') - + '' - --set PWNDBG_LLDBINIT_DIR $out/share/pwndbg - '' - else - '' - mkdir -p $out/share/pwndbg - - cp -r gdbinit.py pwndbg $out/share/pwndbg - ${fix_init_script { - target = "$out/share/pwndbg/gdbinit.py"; - line = "2"; - }} - - touch $out/share/pwndbg/.skip-venv - makeWrapper ${gdb}/bin/gdb $out/bin/${pwndbgName} \ - --add-flags "--quiet --nx --init-command=$out/share/pwndbg/gdbinit.py" - '' - ); - - meta = { - pwndbgVenv = pyEnv; - python3 = python3; - gdb = gdb; - lldb = lldb; - isLLDB = isLLDB; - }; - } - ) { }; + pwndbg_final = (if isLLDB then pwndbg_lldb else pwndbg_gdb) // { + meta = { + pwndbgVenv = pyEnv; + python3 = python3; + gdb = gdb; + lldb = lldb; + isLLDB = isLLDB; + }; + }; in -pwndbg +pwndbg_final diff --git a/nix/pyenv.nix b/nix/pyenv.nix index 86f2d6572..0e7617327 100644 --- a/nix/pyenv.nix +++ b/nix/pyenv.nix @@ -134,6 +134,8 @@ let paramiko = dummy; pip = dummy; uv = dummy; + gdb-for-pwndbg = dummy; + lldb-for-pwndbg = dummy; psutil = pkgs.callPackage ( { @@ -336,6 +338,9 @@ let ++ lib.optionals isLLDB [ "lldb" ] + ++ lib.optionals (!isLLDB) [ + "gdb" + ] ++ lib.optionals isDev [ "dev" "tests" diff --git a/pwndbg/lib/version.py b/pwndbg/lib/version.py index e07823717..9152b9691 100644 --- a/pwndbg/lib/version.py +++ b/pwndbg/lib/version.py @@ -9,8 +9,8 @@ def build_id() -> str: Returns pwndbg commit id if git is available. """ pwndbg_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - # If we install pwndbg into site-packages, then `gdbinit.py` is missing. - if not os.path.exists(os.path.join(pwndbg_dir, "gdbinit.py")): + # If we install pwndbg into site-packages, then `.pwndbg_root` is missing. + if not os.path.exists(os.path.join(pwndbg_dir, ".pwndbg_root")): return "" try: diff --git a/pwndbginit/__init__.py b/pwndbginit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lldbinit.py b/pwndbginit/common.py similarity index 53% rename from lldbinit.py rename to pwndbginit/common.py index 5d9848332..ec04554aa 100644 --- a/lldbinit.py +++ b/pwndbginit/common.py @@ -1,20 +1,15 @@ from __future__ import annotations -import cProfile import hashlib +import logging import os import shutil -import site import subprocess import sys -import time -from glob import glob from pathlib import Path from typing import List from typing import Tuple -import lldb - def hash_file(file_path: str | Path) -> str: with open(file_path, "rb") as f: @@ -31,10 +26,11 @@ def run_uv_install( binary_path: os.PathLike[str], src_root: Path, dev: bool = False ) -> Tuple[str, str, int]: # We don't want to quietly uninstall dependencies by just specifying - # `--extra lldb` so we will be conservative and pull all extras in. + # `--extra gdb` so we will be conservative and pull all extras in. command: List[str] = [str(binary_path), "sync", "--all-extras"] if dev: command.append("--all-groups") + logging.debug(f"Updating deps with command: {' '.join(command)}") result = subprocess.run(command, capture_output=True, text=True, cwd=src_root) return result.stdout.strip(), result.stderr.strip(), result.returncode @@ -57,10 +53,14 @@ def update_deps(src_root: Path, venv_path: Path) -> None: uv_lock_hash_path = venv_path / "uv.lock.hash" current_hash = hash_file(src_root / "uv.lock") + logging.debug(f"Current uv.lock hash: {current_hash}") stored_hash = None if uv_lock_hash_path.exists(): stored_hash = uv_lock_hash_path.read_text().strip() + logging.debug(f"Stored uv.lock hash: {stored_hash}") + else: + logging.debug("No stored hash found") # If the hashes don't match, update the dependencies if current_hash == stored_hash: @@ -87,30 +87,6 @@ def update_deps(src_root: Path, venv_path: Path) -> None: print(stderr, file=sys.stderr) -def fixup_paths(src_root: Path, venv_path: Path): - site_pkgs_path = glob(str(venv_path / "lib/*/site-packages"))[0] - - # add virtualenv's site-packages to sys.path and run .pth files - site.addsitedir(site_pkgs_path) - - # remove existing, system-level site-packages from sys.path - for site_packages in site.getsitepackages(): - if site_packages in sys.path: - sys.path.remove(site_packages) - - # Set virtualenv's bin path (needed for utility tools like ropper, pwntools etc) - bin_path = str(venv_path / "bin") - os.environ["PATH"] = bin_path + os.pathsep + os.environ.get("PATH", "") - - # Add pwndbg directory to sys.path so it can be imported - sys.path.insert(0, str(src_root)) - - # Push virtualenv's site-packages to the front - if site_pkgs_path in sys.path: - sys.path.remove(site_pkgs_path) - sys.path.insert(1, site_pkgs_path) - - def get_venv_path(src_root: Path): venv_path_env = os.environ.get("PWNDBG_VENV_PATH") if venv_path_env: @@ -122,46 +98,25 @@ def get_venv_path(src_root: Path): def skip_venv(src_root) -> bool: return ( os.environ.get("PWNDBG_VENV_PATH") == "PWNDBG_PLEASE_SKIP_VENV" - or (src_root / ".skip-venv").exists() + or not (src_root / ".pwndbg_root").exists() ) -def main(debugger: lldb.SBDebugger, major: int, minor: int, debug: bool = False) -> None: - if "pwndbg" in sys.modules: - print("Detected double-loading of Pwndbg.") - print("This should not happen. Please report this issue if you're not sure how to fix it.") - sys.exit(1) - - profiler = cProfile.Profile() - - start_time = None - if os.environ.get("PWNDBG_PROFILE") == "1": - start_time = time.time() - profiler.enable() - - src_root = Path(__file__).parent.resolve() - if not skip_venv(src_root): - venv_path = get_venv_path(src_root) - if not venv_path.exists(): - print(f"Cannot find Pwndbg virtualenv directory: {venv_path}. Please re-run setup.sh") - sys.exit(1) - - no_auto_update = os.getenv("PWNDBG_NO_AUTOUPDATE") - if no_auto_update is None: - update_deps(src_root, venv_path) - fixup_paths(src_root, venv_path) - - import pwndbg # noqa: F811 - import pwndbg.dbg.lldb - - pwndbg.dbg_mod.lldb.LLDB_VERSION = (major, minor) +def verify_venv(): + src_root = Path(__file__).parent.parent.resolve() + if skip_venv(src_root): + return - pwndbg.dbg = pwndbg.dbg_mod.lldb.LLDB() - pwndbg.dbg.setup(debugger, __name__, debug=debug) + venv_path = get_venv_path(src_root) + if not venv_path.exists(): + print( + f"Cannot find Pwndbg virtualenv directory: {venv_path}. Please re-run setup.sh", + flush=True, + ) + os._exit(1) - import pwndbg.profiling + no_auto_update = os.getenv("PWNDBG_NO_AUTOUPDATE") is not None + if no_auto_update: + return - pwndbg.profiling.init(profiler, start_time) - if os.environ.get("PWNDBG_PROFILE") == "1": - pwndbg.profiling.profiler.stop("pwndbg-load.pstats") - pwndbg.profiling.profiler.start() + update_deps(src_root, venv_path) diff --git a/pwndbginit/gdbinit.py b/pwndbginit/gdbinit.py new file mode 100644 index 000000000..4c5ae515b --- /dev/null +++ b/pwndbginit/gdbinit.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +import cProfile +import logging +import os +import sys +import time +import traceback + +import gdb + +from pwndbginit import gdbpatches # noqa: F401 +from pwndbginit.common import verify_venv + + +def init_logger(): + log_level_env = os.environ.get("PWNDBG_LOGLEVEL", "WARNING") + log_level = getattr(logging, log_level_env.upper()) + + root_logger = logging.getLogger() + root_logger.setLevel(log_level) + + # Add a custom StreamHandler we will use to customize log message formatting. We + # configure the handler later, after pwndbg has been imported. + handler = logging.StreamHandler() + root_logger.addHandler(handler) + + return handler + + +def check_doubleload(): + if "pwndbg" in sys.modules: + print( + "Detected double-loading of Pwndbg (likely from both .gdbinit and the Pwndbg portable build)." + ) + print( + "To fix this, please remove the line 'source your-path/gdbinit.py' from your .gdbinit file." + ) + sys.exit(1) + + +def main() -> None: + handler = init_logger() + profiler = cProfile.Profile() + + start_time = None + if os.environ.get("PWNDBG_PROFILE") == "1": + start_time = time.time() + profiler.enable() + + check_doubleload() + verify_venv() + + # Force UTF-8 encoding (to_string=True to skip output appearing to the user) + try: + gdb.execute("set target-wide-charset UTF-8", to_string=True) + gdb.execute("set charset UTF-8", to_string=True) + except gdb.error as e: + print(f"Warning: Cannot set gdb charset: '{e}'") + + import pwndbg # noqa: F811 + import pwndbg.dbg.gdb + + pwndbg.dbg = pwndbg.dbg_mod.gdb.GDB() + pwndbg.dbg.setup() + + import pwndbg.log + import pwndbg.profiling + + # ColorFormatter relies on pwndbg being loaded, so we can't set it up until now + handler.setFormatter(pwndbg.log.ColorFormatter()) + + pwndbg.profiling.init(profiler, start_time) + if os.environ.get("PWNDBG_PROFILE") == "1": + pwndbg.profiling.profiler.stop("pwndbg-load.pstats") + pwndbg.profiling.profiler.start() + + # We need reimport it here so that it's available at the global scope + # when some starts a Python interpreter in GDB + gdb.execute("py import pwndbg") + + +def main_try(): + # We wrap everything in try/except so that we can exit GDB with an error code + # This is used by tests to check if gdbinit.py failed + try: + main() + except Exception: + print(traceback.format_exc(), file=sys.stderr, flush=True) + os._exit(1) diff --git a/pwndbginit/gdbpatches.py b/pwndbginit/gdbpatches.py new file mode 100644 index 000000000..5db6b1adc --- /dev/null +++ b/pwndbginit/gdbpatches.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import importlib.abc +import os +import sys +import traceback + +import gdb + + +def fix_exit(): + major_ver = int(gdb.VERSION.split(".")[0]) + if major_ver <= 15: + # On certain verions of gdb (used on ubuntu 24.04) using sys.exit() can cause + # a segfault. See: + # https://github.com/pwndbg/pwndbg/pull/2900#issuecomment-2825456636 + # https://sourceware.org/bugzilla/show_bug.cgi?id=31946 + def _patched_exit(exit_code): + # argparse requires a SystemExit exception, otherwise our CLI commands will exit incorrectly on invalid arguments + stack_list = traceback.extract_stack(limit=2) + if len(stack_list) == 2: + p = stack_list[0] + if p.filename.endswith("/argparse.py"): + raise SystemExit() + + sys.stdout.flush() + sys.stderr.flush() + os._exit(exit_code) + + sys.exit = _patched_exit + + +def fix_stdout(): + # Add the original stdout methods back to gdb._GdbOutputFile for pwnlib colors + sys.stdout.isatty = sys.__stdout__.isatty + sys.stdout.fileno = sys.__stdout__.fileno + + +def fix_readline(): + # Fix gdb readline bug: https://github.com/pwndbg/pwndbg/issues/2232#issuecomment-2542564965 + class GdbRemoveReadlineFinder(importlib.abc.MetaPathFinder): + def find_spec(self, fullname, path=None, target=None): + if fullname == "readline": + raise ImportError("readline module disabled under GDB") + return None + + sys.meta_path.insert(0, GdbRemoveReadlineFinder()) + + +fix_stdout() +fix_readline() +fix_exit() diff --git a/pwndbginit/lldbinit.py b/pwndbginit/lldbinit.py new file mode 100644 index 000000000..d6e193b36 --- /dev/null +++ b/pwndbginit/lldbinit.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +import cProfile +import os +import sys +import time + +import lldb + +from pwndbginit.common import verify_venv + + +def main(debugger: lldb.SBDebugger, major: int, minor: int, debug: bool = False) -> None: + if "pwndbg" in sys.modules: + print("Detected double-loading of Pwndbg.") + print("This should not happen. Please report this issue if you're not sure how to fix it.") + sys.exit(1) + + verify_venv() + profiler = cProfile.Profile() + + start_time = None + if os.environ.get("PWNDBG_PROFILE") == "1": + start_time = time.time() + profiler.enable() + + import pwndbg # noqa: F811 + import pwndbg.dbg.lldb + + pwndbg.dbg_mod.lldb.LLDB_VERSION = (major, minor) + + pwndbg.dbg = pwndbg.dbg_mod.lldb.LLDB() + pwndbg.dbg.setup(debugger, "pwndbglldbhandler", debug=debug) + + import pwndbg.profiling + + pwndbg.profiling.init(profiler, start_time) + if os.environ.get("PWNDBG_PROFILE") == "1": + pwndbg.profiling.profiler.stop("pwndbg-load.pstats") + pwndbg.profiling.profiler.start() diff --git a/pwndbginit/pwndbg_gdb.py b/pwndbginit/pwndbg_gdb.py new file mode 100755 index 000000000..4011be9a0 --- /dev/null +++ b/pwndbginit/pwndbg_gdb.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import os +import shutil +import subprocess +import sys +import sysconfig +from typing import Tuple + + +def get_gdb_version(path: str) -> Tuple[str, str]: + result = subprocess.run( + [ + path, + "-nx", + "--batch", + "-iex", + "py import sysconfig; print(sysconfig.get_config_var('INSTSONAME'), sysconfig.get_config_var('VERSION'))", + ], + capture_output=True, + text=True, + ) + return tuple(result.stdout.strip().split(" ", 2)) + + +def get_venv_bin_path(): + bin_dir = "Scripts" if os.name == "nt" else "bin" + return os.path.join(sys.prefix, bin_dir) + + +def prepend_venv_bin_to_path(): + # Set virtualenv's bin path (needed for utility tools like ropper, pwntools etc) + venv_bin = get_venv_bin_path() + path_elements = os.environ.get("PATH", "").split(os.pathsep) + if venv_bin in path_elements: + return + + path_elements.insert(0, venv_bin) + os.environ["PATH"] = os.pathsep.join(path_elements) + + +def main(): + prepend_venv_bin_to_path() + + gdb_argv = [ + sys.argv[0], + "-q", + "-nx", + "-iex", + "py import pwndbginit.gdbinit; pwndbginit.gdbinit.main_try()", + *sys.argv[1:], + ] + sys.argv = gdb_argv + + try: + from gdb_for_pwndbg.gdb import main + + main() + return + except ImportError: + pass + + gdb_path = shutil.which("gdb") + if not gdb_path: + print("ERROR: Could not find 'gdb' binary") + sys.exit(1) + + envs = os.environ.copy() + envs["PYTHONNOUSERSITE"] = "1" + envs["PYTHONPATH"] = ":".join(sys.path) + envs["PYTHONHOME"] = f"{sys.prefix}:{sys.exec_prefix}" + + expected = (sysconfig.get_config_var("INSTSONAME"), sysconfig.get_config_var("VERSION")) + have = get_gdb_version(gdb_path) + if have != expected: + print( + f"ERROR: GDB is compiled for Python {have}, but your Python interpreter is {expected}" + ) + sys.exit(1) + + os.execve(gdb_path, sys.argv, env=envs) + + +if __name__ == "__main__": + main() diff --git a/pwndbg-lldb.py b/pwndbginit/pwndbg_lldb.py similarity index 81% rename from pwndbg-lldb.py rename to pwndbginit/pwndbg_lldb.py index 211ef40b4..e3fdbd6b6 100755 --- a/pwndbg-lldb.py +++ b/pwndbginit/pwndbg_lldb.py @@ -5,6 +5,7 @@ from __future__ import annotations import argparse import os import re +import shutil import subprocess import sys from typing import List @@ -58,16 +59,30 @@ def find_lldb_python_path() -> str: return folder -if __name__ == "__main__": +def get_venv_bin_path(): + bin_dir = "Scripts" if os.name == "nt" else "bin" + return os.path.join(sys.prefix, bin_dir) + + +def prepend_venv_bin_to_path(): + # Set virtualenv's bin path (needed for utility tools like ropper, pwntools etc) + venv_bin = get_venv_bin_path() + path_elements = os.environ.get("PATH", "").split(os.pathsep) + if venv_bin in path_elements: + return + + path_elements.insert(0, venv_bin) + os.environ["PATH"] = os.pathsep.join(path_elements) + + +def main(): + prepend_venv_bin_to_path() + args = PARSER.parse_args() debug = args.verbose - # Find the path for the LLDB Python bindings. - path = find_lldb_python_path() - sys.path.append(path) - - if debug: - print(f"[-] Launcher: LLDB Python path: {path}") + if sys.platform == "linux" and "LLDB_DEBUGSERVER_PATH" not in os.environ: + os.environ["LLDB_DEBUGSERVER_PATH"] = shutil.which("lldb-server") # Older LLDB versions crash newer versions of CPython on import, so check # for it, and stop early with an error message. @@ -82,35 +97,26 @@ if __name__ == "__main__": print("LLDB 18 and earlier is incompatible with Python 3.12 and later", file=sys.stderr) sys.exit(1) - # Start up LLDB and create a new debugger object. - import lldb + try: + import lldb + except ImportError: + # Find the path for the LLDB Python bindings. + path = find_lldb_python_path() + sys.path.append(path) + if debug: + print(f"[-] Launcher: LLDB Python path: {path}") + import lldb + # Start up LLDB and create a new debugger object. lldb.SBDebugger.Initialize() debugger = lldb.SBDebugger.Create() - # Resolve the location of lldbinit.py based on the environment, if needed. - lldbinit_dir = os.path.dirname(sys.argv[0]) - if "PWNDBG_LLDBINIT_DIR" in os.environ: - lldbinit_dir = os.environ["PWNDBG_LLDBINIT_DIR"] - lldbinit_dir = os.path.abspath(lldbinit_dir) - lldbinit_path = os.path.join(lldbinit_dir, "lldbinit.py") - - if debug: - print(f"[-] Launcher: Importing main LLDB module at '{lldbinit_path}'") - - if not os.path.exists(lldbinit_path): - print(f"Could not find '{lldbinit_path}, please specify it with PWNDBG_LLDBINIT_DIR") - sys.exit(1) - - if lldbinit_path not in sys.path: - sys.path.append(lldbinit_dir) + from pwndbginit import lldbinit + from pwndbginit import pwndbglldbhandler - # Load the lldbinit module we just found. - debugger.HandleCommand(f"command script import {lldbinit_path}") + debugger.HandleCommand(f"command script import {pwndbglldbhandler.__file__}") # Initialize the debugger, proper. - import lldbinit - if debug: print("[-] Launcher: Initializing Pwndbg") lldbinit.main(debugger, lldb_version[0], lldb_version[1], debug=debug) @@ -175,3 +181,7 @@ if __name__ == "__main__": # Dispose of our debugger and terminate LLDB. lldb.SBDebugger.Destroy(debugger) lldb.SBDebugger.Terminate() + + +if __name__ == "__main__": + main() diff --git a/pwndbginit/pwndbglldbhandler.py b/pwndbginit/pwndbglldbhandler.py new file mode 100644 index 000000000..e7fd1e3be --- /dev/null +++ b/pwndbginit/pwndbglldbhandler.py @@ -0,0 +1 @@ +# This file is used just to handle lldb-commands, it must be empty diff --git a/pyproject.toml b/pyproject.toml index e5abb8dd0..bd8eef981 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,9 +34,12 @@ dependencies = [ lldb = [ # The LLDB REPL requires readline. 'gnureadline>=8.2.10,<9; sys_platform != "win32"', - 'pyreadline3>=3.5.4,<4; sys_platform == "win32"' + 'pyreadline3>=3.5.4,<4; sys_platform == "win32"', + "lldb-for-pwndbg>=21.1.6", +] +gdb = [ + "gdb-for-pwndbg>=16.2.6", ] -gdb = [] [dependency-groups] dev = [ @@ -128,10 +131,14 @@ builtins-ignorelist = [ ] [tool.hatch.build.targets.sdist] -include = ["/pwndbg"] +include = ["/pwndbg", "/pwndbginit"] [tool.hatch.build.targets.wheel] -include = ["/pwndbg"] +include = ["/pwndbg", "/pwndbginit"] + +[project.scripts] +pwndbg = "pwndbginit.pwndbg_gdb:main" +pwndbg-lldb = "pwndbginit.pwndbg_lldb:main" [build-system] requires = ["hatchling"] diff --git a/scripts/_docs/extract-all-docs.sh b/scripts/_docs/extract-all-docs.sh index 4940b6010..e1261daf7 100755 --- a/scripts/_docs/extract-all-docs.sh +++ b/scripts/_docs/extract-all-docs.sh @@ -1,23 +1,5 @@ #!/usr/bin/env bash -# Check that supported LLDB is installed. -if ! command -v lldb &> /dev/null; then - echo "Cannot reliably extract information from sources because LLDB" - echo "is not installed. See installation instructions:" - echo "https://pwndbg.re/pwndbg/dev/contributing/setup-pwndbg-dev/#running-with-lldb" - exit 3 -else - version=$(lldb --version | awk '{print $3}') - major_version=${version%%.*} - - if [ "$major_version" -lt 19 ]; then - echo "Cannot reliably extract information from sources because your LLDB" - echo "version (${version}) is too old. Supported is LLDB >= 19. See installation instructions:" - echo "https://pwndbg.re/pwndbg/dev/contributing/setup-pwndbg-dev/#running-with-lldb" - exit 4 - fi -fi - source "$(dirname "$0")/../common.sh" cd $PWNDBG_ABS_PATH @@ -26,16 +8,16 @@ cd $PWNDBG_ABS_PATH # the documentation. Do this from each debugger. export PWNDBG_DOCGEN_DBGNAME="gdb" -$UV_RUN_DOCS gdb --batch -nx -ix ./gdbinit.py \ +$UV_RUN_DOCS pwndbg -nx --batch \ -iex "set exception-verbose on" \ -ix ./scripts/_docs/extract_command_docs.py \ -ix ./scripts/_docs/extract_configuration_docs.py \ -ix ./scripts/_docs/extract_function_docs.py \ - -nx || exit 1 + || exit 1 export PWNDBG_DOCGEN_DBGNAME="lldb" { - $UV_RUN_DOCS python pwndbg-lldb.py << EOF + $UV_RUN_DOCS pwndbg-lldb << EOF set show-tips off command script import ./scripts/_docs/extract_command_docs.py command script import ./scripts/_docs/extract_configuration_docs.py diff --git a/scripts/common.sh b/scripts/common.sh index 433fb79ff..c9f4ed85f 100644 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -10,7 +10,7 @@ if [[ -z "${PWNDBG_VENV_PATH}" ]]; then PWNDBG_VENV_PATH="${PWNDBG_ABS_PATH}/.venv" fi -if [[ "$PWNDBG_VENV_PATH" == "PWNDBG_PLEASE_SKIP_VENV" || -f "${PWNDBG_ABS_PATH}/.skip-venv" || "$PWNDBG_NO_UV" == "1" ]]; then +if [[ "$PWNDBG_VENV_PATH" == "PWNDBG_PLEASE_SKIP_VENV" || "$PWNDBG_NO_UV" == "1" ]]; then # We are using the dependencies as installed on the system # so we shouldn't use uv (and can't, since it's not installed). UV="" diff --git a/setup-dev.sh b/setup-dev.sh index fd56c7823..ab0d65363 100755 --- a/setup-dev.sh +++ b/setup-dev.sh @@ -349,25 +349,3 @@ if linux; then configure_venv fi fi - -# LLDB is needed for docs generation. Tell the user -# if they don't have it installed or if it's an unsupported version. -if ! command -v lldb &> /dev/null; then - echo "WARNING: lldb not found in PATH, some functionality" - echo "(e.g. docs generation) will not be available." - echo "See installation instructions:" - echo "https://pwndbg.re/pwndbg/dev/contributing/setup-pwndbg-dev/#running-with-lldb" -else - version=$(lldb --version | awk '{print $3}') - major_version=${version%%.*} - - if [ "$major_version" -ge 19 ]; then - echo "Supported LLDB installed. All good!" - else - echo "WARNING: lldb found in PATH, but the version is too old." - echo "Installed: ${version}. Supported: >= 19." - echo "Some functionality (e.g. docs generation) will not be available." - echo "See installation instructions:" - echo "https://pwndbg.re/pwndbg/dev/contributing/setup-pwndbg-dev/#running-with-lldb" - fi -fi diff --git a/tests/host/__init__.py b/tests/host/__init__.py index 8c93f3895..6cf59c718 100644 --- a/tests/host/__init__.py +++ b/tests/host/__init__.py @@ -133,40 +133,31 @@ def get_gdb_host(args: argparse.Namespace, local_pwndbg_root: Path) -> TestHost: """ if args.nix: # Use pwndbg, as build by nix. - use_gdbinit = False gdb_path = local_pwndbg_root / "result" / "bin" / "pwndbg" if not gdb_path.exists(): print("ERROR: No nix-compatible pwndbg found. Run nix build .#pwndbg-dev") sys.exit(1) elif args.group == Group.CROSS_ARCH_USER: - # Cross-arch requires 'gdb-multiarch'. - use_gdbinit = True - if (gdb_multiarch := shutil.which("gdb-multiarch")) is not None: - gdb_path = Path(gdb_multiarch) + # Some systems don't ship 'gdb-multiarch', but support multiple + # architectures in their regular binaries. Try the regular GDB. + supports_arches = "py import os; archs = ['i386', 'aarch64', 'arm', 'mips', 'riscv', 'sparc']; os._exit(3) if len([arch for arch in archs if arch in gdb.architecture_names()]) == len(archs) else os._exit(2)" + + gdb_path_str = shutil.which("pwndbg") + if gdb_path_str is None: + print("ERROR: No 'pwndbg' executables in path") + sys.exit(1) + + result = subprocess.run([gdb_path_str, "-nx", "-ex", supports_arches], capture_output=True) + # GDB supports cross architecture targets + if result.returncode == 3: + gdb_path = Path(gdb_path_str) else: - # Some systems don't ship 'gdb-multiarch', but support multiple - # architectures in their regular binaries. Try the regular GDB. - supports_arches = "py import os; archs = ['i386', 'aarch64', 'arm', 'mips', 'riscv', 'sparc']; os._exit(3) if len([arch for arch in archs if arch in gdb.architecture_names()]) == len(archs) else os._exit(2)" - - gdb_path_str = shutil.which("gdb") - if gdb_path_str is None: - print("ERROR: No 'gdb-multiarch' or 'gdb' executables in path") - sys.exit(1) - - result = subprocess.run([gdb_path_str, "-ex", supports_arches], capture_output=True) - # GDB supports cross architecture targets - if result.returncode == 3: - gdb_path = Path(gdb_path_str) - else: - print( - "ERROR: 'gdb-multiarch' not found, and 'gdb' does not support cross architecture targets" - ) - sys.exit(1) + print("ERROR: 'pwndbg' does not support cross architecture targets") + sys.exit(1) else: # Use the regular system GDB. - use_gdbinit = True - gdb_path_str = shutil.which("gdb") + gdb_path_str = shutil.which("pwndbg") if gdb_path_str is None: print("ERROR: No 'gdb' executable in path") sys.exit(1) @@ -179,7 +170,6 @@ def get_gdb_host(args: argparse.Namespace, local_pwndbg_root: Path) -> TestHost: local_pwndbg_root / args.group.library(), local_pwndbg_root / args.group.binary_dir(), gdb_path, - use_gdbinit, ) diff --git a/tests/host/gdb/__init__.py b/tests/host/gdb/__init__.py index 3d4054f2c..d1c9598a2 100644 --- a/tests/host/gdb/__init__.py +++ b/tests/host/gdb/__init__.py @@ -20,13 +20,11 @@ class GDBTestHost(TestHost): pytest_root: Path, binaries_root: Path, gdb_path: Path, - use_gdbinit: bool, ): self._pwndbg_root = pwndbg_root self._pytest_root = pytest_root self._binaries_root = binaries_root self._gdb_path = gdb_path - self._use_gdbinit = use_gdbinit def _run_gdb( self, @@ -39,10 +37,9 @@ class GDBTestHost(TestHost): # Prepare the GDB command line. gdb_args = ["--command", str(target)] - if self._use_gdbinit: - gdb_args.extend(["--init-command", str(self._pwndbg_root / "gdbinit.py")]) + return subprocess.run( - [str(self._gdb_path), "--silent", "--nx", "--nh"] + [str(self._gdb_path), "--silent", "--nx"] + gdb_args_before + gdb_args + ["--eval-command", "quit"], @@ -77,10 +74,8 @@ class GDBTestHost(TestHost): env["COVERAGE_PROCESS_START"] = str(self._pwndbg_root / "pyproject.toml") env["PWNDBG_LAUNCH_TEST"] = case env["PWNDBG_DISABLE_COLORS"] = "1" - env["GDB_INIT_PATH"] = str(self._pwndbg_root / "gdbinit.py") env["GDB_BIN_PATH"] = str(self._gdb_path) env["TEST_BINARIES_ROOT"] = str(self._binaries_root) - env["TEST_USE_GDBINIT"] = "1" if self._use_gdbinit else "0" if interactive: env["USE_PDB"] = "1" diff --git a/tests/library/gdb/tests/utils.py b/tests/library/gdb/tests/utils.py index b410c6e2f..78a1b7885 100644 --- a/tests/library/gdb/tests/utils.py +++ b/tests/library/gdb/tests/utils.py @@ -5,9 +5,7 @@ import os import re import subprocess -gdb_init_path = os.environ.get("GDB_INIT_PATH", "../gdbinit.py") -gdb_bin_path = os.environ.get("GDB_BIN_PATH", "gdb") -TEST_USE_GDBINIT = os.environ.get("TEST_USE_GDBINIT", "0") +gdb_bin_path = os.environ.get("GDB_BIN_PATH", "pwndbg") def run_gdb_with_script( @@ -25,14 +23,11 @@ def run_gdb_with_script( pybefore = ([pybefore] if isinstance(pybefore, str) else pybefore) or [] pyafter = ([pyafter] if isinstance(pyafter, str) else pyafter) or [] - command = [gdb_bin_path, "--silent", "--nx", "--nh"] + command = [gdb_bin_path, "--silent", "--nx"] for cmd in pybefore: command += ["--init-eval-command", cmd] - if gdb_init_path and TEST_USE_GDBINIT == "1": - command += ["--command", gdb_init_path] - if binary: command += [binary] diff --git a/tests/library/qemu-system/system-tests.sh b/tests/library/qemu-system/system-tests.sh index c210405d5..657f4dfad 100755 --- a/tests/library/qemu-system/system-tests.sh +++ b/tests/library/qemu-system/system-tests.sh @@ -6,7 +6,6 @@ set -o pipefail source "$(dirname "$0")/../../../scripts/common.sh" ROOT_DIR=$PWNDBG_ABS_PATH -GDB_INIT_PATH="$ROOT_DIR/gdbinit.py" COVERAGERC_PATH="$ROOT_DIR/pyproject.toml" VMLINUX_LIST=($(basename -a "${TESTING_KERNEL_IMAGES_DIR}"/vmlinux*)) @@ -135,23 +134,19 @@ run_gdb() { exit 1 fi else - gdb_load_pwndbg=(--command "$GDB_INIT_PATH") + gdb_load_pwndbg=() - if [[ "${arch}" == x86_64 ]]; then - GDB=gdb - else - GDB=gdb-multiarch - fi + GDB=pwndbg fi if [ $should_drop_to_pdb -eq 1 ]; then - # $GDB --nx --nh "${gdb_load_pwndbg[@]}" \ + # $GDB --nx "${gdb_load_pwndbg[@]}" \ # -ex "set exception-verbose on" "$@" echo "Run: " - echo "$GDB --nx --nh ${gdb_load_pwndbg[@]} -ex \"set exception-debugger on\" -ex \"file ${TESTING_KERNEL_IMAGES_DIR}/vmlinux-${kernel_type}-${kernel_version}-${arch}\" -ex \"target remote :${GDB_PORT}\"" + echo "$GDB --nx ${gdb_load_pwndbg[@]} -ex \"set exception-debugger on\" -ex \"file ${TESTING_KERNEL_IMAGES_DIR}/vmlinux-${kernel_type}-${kernel_version}-${arch}\" -ex \"target remote :${GDB_PORT}\"" read -p "Press enter to continue" else - $UV_RUN $GDB --silent --nx --nh "${gdb_load_pwndbg[@]}" \ + $UV_RUN_TEST $GDB --silent --nx "${gdb_load_pwndbg[@]}" \ -ex "set exception-verbose on" "$@" -ex "quit" 2> /dev/null fi return $? diff --git a/uv.lock b/uv.lock index b67e8469e..c25417d7e 100644 --- a/uv.lock +++ b/uv.lock @@ -445,6 +445,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164, upload-time = "2025-01-21T20:04:47.734Z" }, ] +[[package]] +name = "gdb-for-pwndbg" +version = "16.2.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/62/1ea477664656fc0168f6711593f806621a5bf11928dc89e98c55d79a2061/gdb_for_pwndbg-16.2.6-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:3419c23a5626bf1205f038dad1910587657eb200ce312b411f7a3f817d3055cf", size = 9566628, upload-time = "2025-06-21T00:33:05.063Z" }, + { url = "https://files.pythonhosted.org/packages/42/bd/9b4ffa675a3c09bfb2ab8d40d6f335f8d037e2c6aef7de182df3311d2268/gdb_for_pwndbg-16.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4b4662a490bd5efb5dea2f67e92b3ab3dea55711db46ca762dc3ae585eb1f25c", size = 8926465, upload-time = "2025-06-20T23:56:33.087Z" }, + { url = "https://files.pythonhosted.org/packages/05/82/ea63ae8470638776d01b65078b057d0f98319a678ec01fab37cdc0eb73c4/gdb_for_pwndbg-16.2.6-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ec2ce78e6f467a37367931b9a89dd327e7ff9e5f9f2875e2ad9c2bbc130e7a56", size = 10370599, upload-time = "2025-06-20T23:51:53.476Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ef/1362f839d0185efa2a3066524e5d6de090d0ae15c69113b53923166dba52/gdb_for_pwndbg-16.2.6-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c4492dc4d351a30f8056d159c1393a67c1a71e6f255befe8e31b038602108e8a", size = 10772789, upload-time = "2025-06-21T00:20:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/96/18/f725ee11625831b3e8c372a06e3167f17c6cdb595fa70e4cbfc1f0fad5ab/gdb_for_pwndbg-16.2.6-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:28e338c6d1686d6bf5806a20b4a0976f7e235e7fae4c07ab54b92324ad393b20", size = 9507625, upload-time = "2025-06-21T00:33:06.653Z" }, + { url = "https://files.pythonhosted.org/packages/6d/78/92cf4d29496a651649d512096d8b32c57f844ba4ddea6e99e8f91e755080/gdb_for_pwndbg-16.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eb32a6492c47127fcc72b19d96b8c8f7f8c939391ac1cbe25429fb502ce6df4f", size = 8868503, upload-time = "2025-06-20T23:56:34.626Z" }, + { url = "https://files.pythonhosted.org/packages/4d/2e/c557da35be3809057ecb07706b9b327ddc4eda14ce533c7ff17fc37966e9/gdb_for_pwndbg-16.2.6-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bd3ca455f9071506cf5afcde54e434677f5610fc2383888c4991f6b25e941d83", size = 10370642, upload-time = "2025-06-20T23:51:55.549Z" }, + { url = "https://files.pythonhosted.org/packages/43/e2/960414fc5e5226e9dc705501b28b39de289430b13b5b79fa957694a7c838/gdb_for_pwndbg-16.2.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:52b275e16aa56036180621ed5c339c3e0ce48797ff2992348e7739ace91e086e", size = 10772221, upload-time = "2025-06-21T00:20:58.524Z" }, + { url = "https://files.pythonhosted.org/packages/f1/5f/1e03daf58fdd2502a06e2c2baddaf7053f36dcb646c1a16627691d38d06b/gdb_for_pwndbg-16.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b555ea708b25f51fb9fa3e23a3ec97ba7ddabc98ff2da3ba16c1d417ac5a047d", size = 9509413, upload-time = "2025-06-21T00:33:08.617Z" }, + { url = "https://files.pythonhosted.org/packages/d9/40/b52f2cd54f511decaf7d933059847552babb2b6097f710890287b6de4124/gdb_for_pwndbg-16.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2dff1d2e6ffbe0097eb6c9119755d626d758a6415010e7a931b84c435616f868", size = 8872898, upload-time = "2025-06-20T23:56:36.488Z" }, + { url = "https://files.pythonhosted.org/packages/53/c6/870a1e737681d18a0974cb5a6c3d3160ab0c2762e24ca2d63eacf15f2974/gdb_for_pwndbg-16.2.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:86a465af1dd20db9ca51d6b854d153ab20fed9f731c5e02aef96b61b7b0e9a67", size = 10370761, upload-time = "2025-06-20T23:51:57.445Z" }, + { url = "https://files.pythonhosted.org/packages/26/99/2dc67b26c462d49bf9c76c33917b321ddf3e4e8e5702040ef42fc221a51b/gdb_for_pwndbg-16.2.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:8ce931356f2d572baafa461b0f6f6b60b0ce6f551ed0ebae3e9f468d479596db", size = 10775617, upload-time = "2025-06-21T00:21:00.64Z" }, + { url = "https://files.pythonhosted.org/packages/b8/42/0196a76fbeb1e1abf319ed19e145628a5b99b3381ea6a93f63a9f240c5c3/gdb_for_pwndbg-16.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9d6e8981d6e9e7f9e4469dc203904249e6cc0113b662489b9ab7c7d45cf93099", size = 9509319, upload-time = "2025-06-21T00:33:10.247Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/de6307ed842d11e9f77232ba85450825afd535387545173d22285783dc97/gdb_for_pwndbg-16.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6fbcf374854b59daa6e607321f187e04126866c1b469f7d08cedd371458d8b3a", size = 8872560, upload-time = "2025-06-20T23:56:38.03Z" }, + { url = "https://files.pythonhosted.org/packages/dc/be/91f627b25fe9f53c65300e479c4c03d78626a33d4779df2558a800409a1d/gdb_for_pwndbg-16.2.6-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:6640e6996b7f908d6546f4945939bf08d9106e2289b03d58c5e780ba7b7693a0", size = 10370635, upload-time = "2025-06-20T23:51:59.014Z" }, + { url = "https://files.pythonhosted.org/packages/d4/2b/5611f627dfecb579b573fbe9498eaf56c362b320e85d4c4a6fc661270a5b/gdb_for_pwndbg-16.2.6-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:7b1aabafc806ddf3cb7a14626049134396d55975ada022eb9262b52fb039d023", size = 10775314, upload-time = "2025-06-21T00:21:02.444Z" }, + { url = "https://files.pythonhosted.org/packages/16/0b/2c02585d824521322f8562362b46a097e0429364dfe63119cd043f2c0513/gdb_for_pwndbg-16.2.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ef16fb07dfb69939907d3c7123b2c6506765b0847ab804f7039ee162d380e770", size = 9509613, upload-time = "2025-06-21T00:33:12.168Z" }, + { url = "https://files.pythonhosted.org/packages/24/04/6635e680ff564728813fcdde5d6894708c8cb0733ea2e5dc86d1d27253d7/gdb_for_pwndbg-16.2.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5acf4fe2a606bba5c352889eda2840b29ea4f05dd1df33e25ee5d58b58c44c75", size = 8871613, upload-time = "2025-06-20T23:56:39.506Z" }, + { url = "https://files.pythonhosted.org/packages/14/ec/0c614233df49fefea248e4e34e3441f815eb727876db44af5750840274d8/gdb_for_pwndbg-16.2.6-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:bf98ab117d0b9dea808a162cf815e9b954e7a528f39550805f26c11ffcb534d6", size = 10371486, upload-time = "2025-06-20T23:52:01.092Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/9744c1b546d556d17dbd7b77b6ffc55ef55f22589e95b0644154763ae2af/gdb_for_pwndbg-16.2.6-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:998c33ae5888d2053f01b08e66ab1a4ddd2dd0d67e086f0f195113068b544e50", size = 10775521, upload-time = "2025-06-21T00:21:04.667Z" }, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -647,6 +674,33 @@ version = "3.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5e/73/e01e4c5e11ad0494f4407a3f623ad4d87714909f50b17a06ed121034ff6e/jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc", size = 13925, upload-time = "2022-01-16T20:35:59.13Z" } +[[package]] +name = "lldb-for-pwndbg" +version = "21.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/73/098d652f01f25b073a0948ae76407328d86085ca335663d3b073786e1a34/lldb_for_pwndbg-21.1.6-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:26f0e67c2d178447e5ab48a902f4e683b1737ce628e1961ef9dd08d754ccf22b", size = 51328759, upload-time = "2025-06-21T03:32:12.875Z" }, + { url = "https://files.pythonhosted.org/packages/57/95/5085b0a213fc16caf7c8c1f36e5b1968d0a4edadaaebceb37f6f0a70f88f/lldb_for_pwndbg-21.1.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1f02516b5212c6c8db352fd52cd69c3cc259abf4010fb0e3c8969a3cb515c1d9", size = 49041162, upload-time = "2025-06-21T03:17:35.13Z" }, + { url = "https://files.pythonhosted.org/packages/47/40/862c3ccc547eadb431631cd071e6ebf2295b658c030ca417ec09670056c8/lldb_for_pwndbg-21.1.6-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:8f6544ed6fc4ce7ab883e3532bba15f7ca8e0938d1ee1f11b29b89751b70fff2", size = 61864873, upload-time = "2025-06-21T01:34:07.088Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3b/ec5b4cfaad2b3f0b9dd5fb73debadd12517c9450b8dbcfefa013d902306a/lldb_for_pwndbg-21.1.6-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:9db2de4ce2e2c8ae874f85f2f9012c7934f6db0ce5002f6c6fc615f648c4608c", size = 63479959, upload-time = "2025-06-21T02:33:06.264Z" }, + { url = "https://files.pythonhosted.org/packages/f7/30/3908eeb40c364ecced27dd47c62a2b2aa3ac6098296a2840bfcc1a87c9a5/lldb_for_pwndbg-21.1.6-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:df8555c6b0a54c080ed4a7905764711ee4047419921abc6ccb88cc00b8b7aad7", size = 51328788, upload-time = "2025-06-21T02:11:03.275Z" }, + { url = "https://files.pythonhosted.org/packages/79/46/73d7eb0905fe3fd012a01eaf493188d3ded298bb3768dbd40de7169c53bd/lldb_for_pwndbg-21.1.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:322351d66d5cf9da2acb6af8b54dc9c089d67ee536d590b7a6f1626205454bb2", size = 49042027, upload-time = "2025-06-21T01:11:54.985Z" }, + { url = "https://files.pythonhosted.org/packages/2f/69/cde7560b23f5487bf77c9e9410f823bb3a2e66970ecc9605c562042653da/lldb_for_pwndbg-21.1.6-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:20c18b4c8d1ca3cd72652b3e1c2e887a1d690b7325815030bccfc387cd5bdcac", size = 61864970, upload-time = "2025-06-21T01:33:27.861Z" }, + { url = "https://files.pythonhosted.org/packages/c3/3f/5bece6c56f0da4069a75683df0d802ea99a5c655df37ea31fc6cc8b703dd/lldb_for_pwndbg-21.1.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:3b59ef4bb58d85dc189baf243cbe9c999f62f224f6e90f75a94b308c9834f161", size = 63479939, upload-time = "2025-06-21T02:33:35.455Z" }, + { url = "https://files.pythonhosted.org/packages/c1/a4/5dae9dffae06801a42f2f21b3c9b55dee42e4af39aa66655d8601db3ef41/lldb_for_pwndbg-21.1.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:95eb493d5039eae2fe472f6f90e38bddec8736b67a922c128dc476078fbb7d1e", size = 51334493, upload-time = "2025-06-21T02:17:53.867Z" }, + { url = "https://files.pythonhosted.org/packages/74/c9/22aa3664c9f26fa0fcf73c97f433ac85c19768dd4e78325915f785ebf81d/lldb_for_pwndbg-21.1.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ffaa159ffee102a256dcebba96be8b340f7bd2df3128551ad1a7a1d42369d5b", size = 49040572, upload-time = "2025-06-21T03:16:24.476Z" }, + { url = "https://files.pythonhosted.org/packages/d9/03/f5d204aa3a2d55a93809021dfefcb7d36365f1cf676cc1bb782b999ae53d/lldb_for_pwndbg-21.1.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e1baa5eba96f4a98bb6f969a31e0e66f208c1dd79f7ceb15a63872f926e458c3", size = 61868081, upload-time = "2025-06-21T01:33:54.812Z" }, + { url = "https://files.pythonhosted.org/packages/02/04/230e9c752b1aea1e7a057d37ac846c214d379c0c90c38f43ec043f685eee/lldb_for_pwndbg-21.1.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ec8765769182faaa520180f3eeeb10db0fa2fbbd0fd6c3d53adb20c7380713fb", size = 63482490, upload-time = "2025-06-21T02:34:40.229Z" }, + { url = "https://files.pythonhosted.org/packages/1e/49/b2635d5a7208834a424988a2189a4c3998c4b265ac56100c0a90335e4ccf/lldb_for_pwndbg-21.1.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:40a5cb02af8ae8523fcff348cf1508d57f95db9f6eb5df835532f7a5fabfc004", size = 51335177, upload-time = "2025-06-21T01:57:48.322Z" }, + { url = "https://files.pythonhosted.org/packages/02/72/616715547dd75ddfeb44442c6cacbddb0731498ea9361674dae8dde6efd9/lldb_for_pwndbg-21.1.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6be469993255d76b8915a37b697c421307a736262c8e2ff6259e1aa7fbc45139", size = 49040350, upload-time = "2025-06-21T02:15:03.288Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3d/395c60d8a9280b05f47c27c951cd6eb949b277d710071ea2c6bfbddb4296/lldb_for_pwndbg-21.1.6-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:d36d87d3961852e835ebf542f80d494bf5a3c565b07829b1947ac940fbc1a354", size = 61865868, upload-time = "2025-06-21T01:36:03.207Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7a/cce245a6c0e54048a9662a8cc6ea72c5c82a5f1826196ec014482b38d799/lldb_for_pwndbg-21.1.6-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:fe99e73b5d3c74ff64049b57eb59a0e9d1c85f1f8603ce0cfde4c2284042dda2", size = 63482224, upload-time = "2025-06-21T02:32:18.325Z" }, + { url = "https://files.pythonhosted.org/packages/24/a4/70ce7bee6104fd299c85d9f20f911169e5fd72a9abdeda7fb060f6f1d167/lldb_for_pwndbg-21.1.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172cc2a3cac07693aa76c7c68cc3e16929322899572599a6a66537ae24f4d10a", size = 51334764, upload-time = "2025-06-21T04:28:45.136Z" }, + { url = "https://files.pythonhosted.org/packages/a9/01/6543f27307c1b2a4e1bdbf9e28721affbcaf6e8f950e4cead148e23b57c1/lldb_for_pwndbg-21.1.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:52979645b92ee8e1a9f05e579952240e9930200d590b9ab6fedcd1b90d474fbd", size = 49042237, upload-time = "2025-06-21T01:38:18.508Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9d/b678e50da95a76f073f0a4cdce22d5b9310804e838a32c25452dcc5c1591/lldb_for_pwndbg-21.1.6-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:abd14e8ec43c40054450b2cf52090ee92bb500ac8423bb13b86171a3bb4775da", size = 61863596, upload-time = "2025-06-21T01:37:13.644Z" }, + { url = "https://files.pythonhosted.org/packages/fa/80/33084781f4f5f7a4b56bad8d3cddfb431358f84fdc6c2723647b8ba45e95/lldb_for_pwndbg-21.1.6-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:472906c8f9d2389c08b4c479e3912f95bd90a5ba51f42d7727f837a7d269ebb4", size = 63482322, upload-time = "2025-06-21T02:32:09.511Z" }, +] + [[package]] name = "mako" version = "1.3.9" @@ -1258,8 +1312,12 @@ dependencies = [ ] [package.optional-dependencies] +gdb = [ + { name = "gdb-for-pwndbg" }, +] lldb = [ { name = "gnureadline", marker = "sys_platform != 'win32'" }, + { name = "lldb-for-pwndbg" }, { name = "pyreadline3", marker = "sys_platform == 'win32'" }, ] @@ -1303,8 +1361,10 @@ tests = [ [package.metadata] requires-dist = [ { name = "capstone", specifier = "==6.0.0a4" }, + { name = "gdb-for-pwndbg", marker = "extra == 'gdb'", specifier = ">=16.2.6" }, { name = "gnureadline", marker = "sys_platform != 'win32' and extra == 'lldb'", specifier = ">=8.2.10,<9" }, { name = "ipython", specifier = ">=8.27.0,<9" }, + { name = "lldb-for-pwndbg", marker = "extra == 'lldb'", specifier = ">=21.1.6" }, { name = "psutil", specifier = ">=6.1.1,<7" }, { name = "pt", git = "https://github.com/martinradev/gdb-pt-dump?rev=50227bda0b6332e94027f811a15879588de6d5cb" }, { name = "pwntools", specifier = ">=4.14.0,<5" },