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

106 lines
3.3 KiB
Python

from __future__ import annotations
import os
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 _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: str,
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 = ["-ex", f"py import sys,os; sys.path.insert(0, os.getcwd()); import {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:
gdb_args_before = []
if coverage_out is not None:
gdb_args_before = [
"-ex",
"py 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["NO_COLOR"] = "1"
env["GDB_BIN_PATH"] = str(self._gdb_path)
env["TEST_BINARIES_ROOT"] = str(self._binaries_root)
if interactive:
env["USE_PDB"] = "1"
# Run the test to completion and time it.
started_at = time.monotonic_ns()
# The test itself runs under GDB, spawned by this process, and prepared
# by the `pytests_launcher` script.
result = self._run_gdb(
"tests.host.gdb.pytests_launcher",
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
env = os.environ.copy()
env["TEST_BINARIES_ROOT"] = str(self._binaries_root)
env["TESTS_PATH"] = str(self._pytest_root)
result = self._run_gdb("tests.host.gdb.pytests_collect", 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]