mirror of https://github.com/pwndbg/pwndbg.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
116 lines
3.2 KiB
Python
116 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from typing import Callable
|
|
from typing import Coroutine
|
|
from typing import Dict
|
|
from typing import List
|
|
|
|
import coverage
|
|
import gdb
|
|
import pytest
|
|
|
|
from ... import host
|
|
|
|
|
|
class _GDBController(host.Controller):
|
|
async def launch(
|
|
self, binary_path: Path, args: List[str] = [], env: Dict[str, str] = {}
|
|
) -> None:
|
|
"""
|
|
Launch the given binary.
|
|
|
|
GDB hides the asynchronous heavy lifting from us, so this call is
|
|
synchronous.
|
|
"""
|
|
if not os.path.exists(binary_path):
|
|
pytest.skip(f"{os.path.basename(binary_path)} does not exist. Platform not supported.")
|
|
|
|
os.environ["PWNDBG_IN_TEST"] = "1"
|
|
gdb.execute(f"file {binary_path}")
|
|
gdb.execute("set exception-verbose on")
|
|
gdb.execute("set width 80")
|
|
gdb.execute("set context-reserve-lines never")
|
|
os.environ["COLUMNS"] = "80"
|
|
for k, v in env.items():
|
|
gdb.execute(f"set environment {k}={v}")
|
|
gdb.execute("starti " + " ".join(args))
|
|
|
|
async def cont(self) -> None:
|
|
gdb.execute("continue")
|
|
|
|
async def execute(self, command: str) -> None:
|
|
from pwndbg.dbg import Error
|
|
|
|
try:
|
|
gdb.execute(command)
|
|
except gdb.error as e:
|
|
raise Error(e)
|
|
|
|
async def execute_and_capture(self, command: str) -> str:
|
|
return gdb.execute(command, to_string=True)
|
|
|
|
async def step_instruction(self) -> None:
|
|
gdb.execute("stepi")
|
|
|
|
async def finish(self) -> None:
|
|
gdb.execute("finish")
|
|
|
|
async def select_thread(self, tid: int) -> None:
|
|
gdb.execute(f"thread {tid}")
|
|
|
|
async def disable_debuginfod(self) -> None:
|
|
gdb.execute("set debug-file-directory")
|
|
gdb.execute("set debuginfod enabled off")
|
|
|
|
|
|
def _start(outer: Callable[[host.Controller], Coroutine[Any, Any, None]]) -> None:
|
|
# The GDB controller is entirely synchronous, so keep advancing the
|
|
# corountine unconditionally until it ends..
|
|
coroutine = outer(_GDBController())
|
|
try:
|
|
coroutine.send(None)
|
|
except StopIteration:
|
|
pass
|
|
|
|
|
|
host.start = _start
|
|
|
|
# Start the test, proper.
|
|
use_pdb = os.environ.get("USE_PDB") == "1"
|
|
|
|
sys._pwndbg_unittest_run = True # type: ignore[attr-defined]
|
|
|
|
test = os.environ["PWNDBG_LAUNCH_TEST"]
|
|
|
|
args = [test, "-vvv", "-s", "--showlocals", "--color=yes"]
|
|
|
|
if use_pdb:
|
|
args.append("--pdb")
|
|
|
|
print(f"Launching pytest with args: {args}")
|
|
|
|
return_code = pytest.main(args)
|
|
|
|
if return_code != 0:
|
|
print("-" * 80)
|
|
print("If you want to debug tests locally, run ./tests.sh with the --pdb flag")
|
|
print("-" * 80)
|
|
|
|
# We must call these functions manually to flush the code coverage data to disk since the sys.exit() call
|
|
# might've been replaced by os._exit() in gdbinit.py.
|
|
# https://github.com/nedbat/coveragepy/issues/310
|
|
if (cov := coverage.Coverage.current()) is not None:
|
|
cov.stop()
|
|
cov.save()
|
|
|
|
# `sys.exit` triggers a GDB detach, while `os._exit` does not.
|
|
# This allows the debugging session to remain at the same PC location,
|
|
# which is useful for attaching to qemu-system multiple times.
|
|
sys.stdout.flush()
|
|
sys.stderr.flush()
|
|
os._exit(return_code)
|