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.
pwndbg/tests/host/gdb/__init__.py

109 lines
3.5 KiB
Python

from __future__ import annotations
import os
import re
import subprocess
import time
from pathlib import Path
from subprocess import CompletedProcess
from typing import List
from host import TestHost
from host import TestResult
from host import TestStatus
from host import _collection_from_pytest
from host import _result_from_pytest
class GDBTestHost(TestHost):
def __init__(
self,
pwndbg_root: Path,
pytest_root: Path,
binaries_root: Path,
gdb_path: Path,
):
self._pwndbg_root = pwndbg_root
self._pytest_root = pytest_root
self._binaries_root = binaries_root
self._gdb_path = gdb_path
def _run_gdb(
self,
target: Path,
gdb_args_before: List[str] = [],
env=None,
capture_output=True,
) -> CompletedProcess[str]:
env = os.environ if env is None else env
# Prepare the GDB command line.
gdb_args = ["--command", str(target)]
return subprocess.run(
[str(self._gdb_path), "--silent", "--nx"]
+ gdb_args_before
+ gdb_args
+ ["--eval-command", "quit"],
env=env,
capture_output=capture_output,
text=True,
cwd=self._pwndbg_root,
)
def run(
self,
case: str,
coverage_out: Path | None,
interactive: bool,
) -> TestResult:
# The test itself runs under GDB, spawned by this process, and prepared
# by the `pytests_launcher` script.
target = self._pwndbg_root / "tests" / "host" / "gdb" / "pytests_launcher.py"
gdb_args_before = []
if coverage_out is not None:
gdb_args_before = [
"-ex",
"py import sys;print(sys.path);import coverage;coverage.process_startup();",
]
# We pass parameters to `pytests_launcher` through environment variables.
env = os.environ.copy()
env["LANG"] = "en_US.UTF-8"
env["SRC_DIR"] = str(self._pwndbg_root)
env["COVERAGE_FILE"] = str(coverage_out)
env["COVERAGE_PROCESS_START"] = str(self._pwndbg_root / "pyproject.toml")
env["PWNDBG_LAUNCH_TEST"] = case
env["PWNDBG_DISABLE_COLORS"] = "1"
env["GDB_BIN_PATH"] = str(self._gdb_path)
env["TEST_BINARIES_ROOT"] = str(self._binaries_root)
env["TEST_PWNDBG_ROOT"] = str(self._pwndbg_root)
if interactive:
env["USE_PDB"] = "1"
# Run the test to completion and time it.
started_at = time.monotonic_ns()
result = self._run_gdb(
target, gdb_args_before=gdb_args_before, env=env, capture_output=not interactive
)
duration = time.monotonic_ns() - started_at
return _result_from_pytest(result, duration)
def collect(self) -> List[str]:
# NOTE: We run tests under GDB sessions and because of some cleanup/tests dependencies problems
# we decided to run each test in a separate GDB session
target = self._pwndbg_root / "tests" / "host" / "gdb" / "pytests_collect.py"
env = os.environ.copy()
env["TEST_BINARIES_ROOT"] = str(self._binaries_root)
env["TEST_PWNDBG_ROOT"] = str(self._pwndbg_root)
env["TESTS_PATH"] = str(self._pytest_root)
result = self._run_gdb(target, env=env)
names = _collection_from_pytest(result, self._pwndbg_root, self._pytest_root)
# We execute from Pwndbg root, so we need to prepend tests/ to the names.
return [f"tests/{name}" for name in names]