From 6622be9e6675d3dc2841cb909e1e6fe2ef5edd19 Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 5 Jan 2024 16:44:04 -0800 Subject: [PATCH 01/35] minor pylint fixes in gef --- .pylintrc | 20 ++++++++++++- gef.py | 89 +++++++++++++++++++++++++++++++++---------------------- 2 files changed, 73 insertions(+), 36 deletions(-) diff --git a/.pylintrc b/.pylintrc index 8d6c69538..1a9979b4e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -409,7 +409,25 @@ confidence= ; anomalous-backslash-in-string, ; bad-open-mode -enable = F,E,unreachable,duplicate-key,unnecessary-semicolon,unused-variable,binary-op-exception,bad-format-string,anomalous-backslash-in-string,bad-open-mode,dangerous-default-value,trailing-whitespace,unneeded-not,singleton-comparison,unused-import,line-too-long,multiple-statements,consider-using-f-string,global-variable-not-assigned +enable = + F, + E, + anomalous-backslash-in-string, + bad-format-string, + bad-open-mode, + consider-using-f-string, + dangerous-default-value, + duplicate-key, + global-variable-not-assigned + line-too-long, + singleton-comparison, + trailing-whitespace, + unnecessary-semicolon, + unneeded-not, + unreachable, + unused-import, + unused-variable, + binary-op-exception disable = all [REPORTS] diff --git a/gef.py b/gef.py index 5c2dbfd24..e804ce6fb 100644 --- a/gef.py +++ b/gef.py @@ -293,6 +293,10 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: # Helpers # +class ObsoleteException(Exception): pass + +class AlreadyRegisteredException(Exception): pass + def p8(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: """Pack one byte respecting the current architecture endianness.""" endian = e or gef.arch.endianness @@ -729,7 +733,7 @@ class FileFormat: sections: List[FileFormatSection] def __init__(self, path: Union[str, pathlib.Path]) -> None: - raise NotImplemented + raise NotImplementedError def __init_subclass__(cls: Type["FileFormat"], **kwargs): global __registered_file_formats__ @@ -742,8 +746,8 @@ def __init_subclass__(cls: Type["FileFormat"], **kwargs): return @classmethod - def is_valid(cls, path: pathlib.Path) -> bool: - raise NotImplemented + def is_valid(cls, _: pathlib.Path) -> bool: + raise NotImplementedError def __str__(self) -> str: return f"{self.name}('{self.path.absolute()}', entry @ {self.entry_point:#x})" @@ -1956,7 +1960,7 @@ def __exit__(self, *exc: Any) -> None: class RedirectOutputContext: def __init__(self, to_file: str = "/dev/null") -> None: - if " " in to_file: raise ValueEror("Target filepath cannot contain spaces") + if " " in to_file: raise ValueError("Target filepath cannot contain spaces") self.redirection_target_file = to_file return @@ -1977,7 +1981,7 @@ def __exit__(self, *exc: Any) -> None: def enable_redirect_output(to_file: str = "/dev/null") -> None: """Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`.""" - if " " in to_file: raise ValueEror("Target filepath cannot contain spaces") + if " " in to_file: raise ValueError("Target filepath cannot contain spaces") gdb.execute("set logging overwrite") gdb.execute(f"set logging file {to_file}") gdb.execute("set logging redirect on") @@ -2292,6 +2296,9 @@ def __init_subclass__(cls, **kwargs): def __str__(self) -> str: return f"Architecture({self.arch}, {self.mode or 'None'}, {repr(self.endianness)})" + def __repr__(self) -> str: + return f"Architecture({self.arch}, {self.mode or 'None'}, {repr(self.endianness)})" + @staticmethod def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: """If implemented by a child `Architecture`, this function dictates if the current class @@ -2834,7 +2841,7 @@ class X86(Architecture): def flag_register_to_human(self, val: Optional[int] = None) -> str: reg = self.flag_register - if not val: + if val is None: val = gef.arch.register(reg) return flags_to_human(val, self.flags_table) @@ -3017,7 +3024,7 @@ class PowerPC(Architecture): def flag_register_to_human(self, val: Optional[int] = None) -> str: # https://www.cebix.net/downloads/bebox/pem32b.pdf (% 2.1.3) - if not val: + if val is None: reg = self.flag_register val = gef.arch.register(reg) return flags_to_human(val, self.flags_table) @@ -3120,7 +3127,7 @@ class SPARC(Architecture): def flag_register_to_human(self, val: Optional[int] = None) -> str: # https://www.gaisler.com/doc/sparcv8.pdf reg = self.flag_register - if not val: + if val is None: val = gef.arch.register(reg) return flags_to_human(val, self.flags_table) @@ -3396,7 +3403,7 @@ def is_qemu() -> bool: if not is_remote_debug(): return False response = gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False) - return "ENABLE=" in response + return isinstance(response, str) and "ENABLE=" in response @lru_cache() @@ -3404,7 +3411,7 @@ def is_qemu_usermode() -> bool: if not is_qemu(): return False response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) - return "Text=" in response + return isinstance(response, str) and "Text=" in response @lru_cache() @@ -3412,7 +3419,7 @@ def is_qemu_system() -> bool: if not is_qemu(): return False response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) - return "received: \"\"" in response + return isinstance(response, str) and "received: \"\"" in response def get_filepath() -> Optional[str]: @@ -4077,8 +4084,9 @@ def instantiate(self, base: int) -> None: try: res = gdb.execute(self.set_func(base), to_string=True) + if not res: return except gdb.error as e: - err(e) + err(str(e)) return if "Breakpoint" not in res: @@ -4499,16 +4507,18 @@ def register_priority_command(cls: Type["GenericCommand"]) -> Type["GenericComma def register(cls: Union[Type["GenericCommand"], Type["GenericFunction"]]) -> Union[Type["GenericCommand"], Type["GenericFunction"]]: global __registered_commands__, __registered_functions__ if issubclass(cls, GenericCommand): - assert( hasattr(cls, "_cmdline_")) - assert( hasattr(cls, "do_invoke")) - assert( all(map(lambda x: x._cmdline_ != cls._cmdline_, __registered_commands__))) + assert hasattr(cls, "_cmdline_") + assert hasattr(cls, "do_invoke") + if any(map(lambda x: x._cmdline_ == cls._cmdline_, __registered_commands__)): + raise AlreadyRegisteredException(cls._cmdline_) __registered_commands__.add(cls) return cls if issubclass(cls, GenericFunction): - assert( hasattr(cls, "_function_")) - assert( hasattr(cls, "invoke")) - assert( all(map(lambda x: x._function_ != cls._function_, __registered_functions__))) + assert hasattr(cls, "_function_") + assert hasattr(cls, "invoke") + if any(map(lambda x: x._function_ == cls._function_, __registered_functions__)): + raise AlreadyRegisteredException(cls._function_) __registered_functions__.add(cls) return cls @@ -4636,7 +4646,7 @@ def __set_repeat_count(self, argv: List[str], from_tty: bool) -> None: self.repeat_count = 0 return - command = gdb.execute("show commands", to_string=True).strip().split("\n")[-1] + command = (gdb.execute("show commands", to_string=True) or "").strip().split("\n")[-1] self.repeat = self.__last_command == command self.repeat_count = self.repeat_count + 1 if self.repeat else 0 self.__last_command = command @@ -4750,9 +4760,9 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: out = f"var buf = [{sdata}]" elif args.lang == "asm": asm_type = self.format_matrix[args.bitlen][2] - out = "buf {0} {1}".format(asm_type, sdata) + out = f"buf {asm_type} {sdata}" elif args.lang == "hex": - out = binascii.hexlify(gef.memory.read(start_addr, end_addr-start_addr)).decode() + out = gef.memory.read(start_addr, end_addr-start_addr).hex() else: raise ValueError(f"Invalid format: {args.lang}") @@ -4929,7 +4939,7 @@ def do_invoke(self, argv: List[str]) -> None: try: gdb.execute(f"attach {' '.join(argv)}", to_string=True) except gdb.error as e: - err(e) + err(str(e)) return # after attach, we are stopped so that we can # get base address to modify our breakpoint @@ -7786,7 +7796,6 @@ def context_source(self) -> None: show_full_path_max = self["show_full_source_file_name_max_len"] show_basename_path_max = self["show_basename_source_file_name_max_len"] - show_prefix_path_len = self["show_prefix_source_path_name_len"] nb_line = self["nb_lines_code"] fn = symtab.filename @@ -7875,6 +7884,7 @@ def context_trace(self) -> None: frames = [current_frame] while current_frame != orig_frame: current_frame = current_frame.older() + if not current_frame: break frames.append(current_frame) nb_backtrace_before = self["nb_lines_backtrace_before"] @@ -9623,7 +9633,7 @@ def __init__(self) -> None: gef.config["gef.autosave_breakpoints_file"] = GefSetting("", str, "Automatically save and restore breakpoints") gef.config["gef.disable_target_remote_overwrite"] = GefSetting(False, bool, "Disable the overwrite of `target remote`") plugins_dir = GefSetting("", str, "Autoload additional GEF commands from external directory", hooks={"on_write": GefSetting.no_spaces}) - plugins_dir.add_hook("on_write", lambda _: self.load_extra_plugins()) + plugins_dir.add_hook("on_write", lambda _: self.reload_extra_plugins()) gef.config["gef.extra_plugins_dir"] = plugins_dir gef.config["gef.disable_color"] = GefSetting(False, bool, "Disable all colors in GEF") gef.config["gef.tempdir"] = GefSetting(GEF_TEMP_DIR, str, "Directory to use for temporary/cache content", hooks={"on_write": GefSetting.no_spaces}) @@ -9639,19 +9649,19 @@ def __init__(self) -> None: return @property + @deprecated() def loaded_commands(self) -> List[Tuple[str, Type[GenericCommand], Any]]: - print("Obsolete loaded_commands") - raise + raise ObsoleteException("Obsolete loaded_commands") @property + @deprecated() def loaded_functions(self) -> List[Type[GenericFunction]]: - print("Obsolete loaded_functions") - raise + raise ObsoleteException("Obsolete loaded_functions") @property + @deprecated() def missing_commands(self) -> Dict[str, Exception]: - print("Obsolete missing_commands") - raise + raise ObsoleteException("Obsolete missing_commands") def setup(self) -> None: self.load() @@ -9673,6 +9683,8 @@ def load_plugin(fpath: pathlib.Path) -> bool: try: dbg(f"Loading '{fpath}'") gdb.execute(f"source {fpath}") + except AlreadyRegisteredException: + pass except Exception as e: warn(f"Exception while loading {fpath}: {str(e)}") return False @@ -9714,6 +9726,12 @@ def load_plugin(fpath: pathlib.Path) -> bool: err(f"failed: {e}") return nb_added + def reload_extra_plugins(self) -> int: + try: + return self.load_extra_plugins() + except: + return -1 + @property def loaded_command_names(self) -> Iterable[str]: print("obsolete loaded_command_names") @@ -11313,11 +11331,11 @@ def target_remote_posthook(): try: pyenv = which("pyenv") - PYENV_ROOT = gef_pystring(subprocess.check_output([pyenv, "root"]).strip()) - PYENV_VERSION = gef_pystring(subprocess.check_output([pyenv, "version-name"]).strip()) - site_packages_dir = os.path.join(PYENV_ROOT, "versions", PYENV_VERSION, "lib", - f"python{PYENV_VERSION[:3]}", "site-packages") - site.addsitedir(site_packages_dir) + pyenv_root = gef_pystring(subprocess.check_output([pyenv, "root"]).strip()) + pyenv_version = gef_pystring(subprocess.check_output([pyenv, "version-name"]).strip()) + site_packages_dir = pathlib.Path(pyenv_root) / f"versions/{pyenv_version}/lib/python{pyenv_version[:3]}/site-packages" + assert site_packages_dir.is_dir() + site.addsitedir(str(site_packages_dir.absolute())) except FileNotFoundError: pass @@ -11353,6 +11371,7 @@ def target_remote_posthook(): # load GEF, set up the managers and load the plugins, functions, reset() + assert isinstance(gef, Gef) gef.gdb.load() gef.gdb.show_banner() From 68787c3c52bb0795ec61ff43e31e0436e85554f0 Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 5 Jan 2024 16:44:36 -0800 Subject: [PATCH 02/35] adding new debug script for rpyc --- scripts/remote_debug.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 scripts/remote_debug.py diff --git a/scripts/remote_debug.py b/scripts/remote_debug.py new file mode 100644 index 000000000..fe39556be --- /dev/null +++ b/scripts/remote_debug.py @@ -0,0 +1,36 @@ +import logging + +import rpyc +import rpyc.core.protocol +import rpyc.utils.server + +RPYC_PORT = 18812 + + +class RemoteDebugService(rpyc.Service): + def on_connect(self, conn: rpyc.core.protocol.Connection): + logging.info(f"connect open: {str(conn)}") + return + + def on_disconnect(self, conn: rpyc.core.protocol.Connection): + logging.info(f"connection closed: {str(conn)}") + return + + def exposed_eval(self, cmd): + return eval(cmd) + + exposed_gdb = gdb + + exposed_gef = gef + + +def start_rpyc_service(port: int = RPYC_PORT): + logging.info(f"RPYC service listening on tcp/{port}") + svc = rpyc.utils.server.OneShotServer( + RemoteDebugService, + port=port, + protocol_config={ + "allow_public_attrs": True, + }, + ) + svc.start() From df101d06310b64728a0d7dd695814741b17ddf07 Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 5 Jan 2024 16:45:57 -0800 Subject: [PATCH 03/35] added new test base class `RemoteGefUnitTestGeneric` --- tests/requirements.txt | 1 + tests/utils.py | 349 +++++++++++++++++++++++++++++------------ 2 files changed, 253 insertions(+), 97 deletions(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index a467cb9e2..7d21692d8 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -6,3 +6,4 @@ pytest-benchmark pytest-forked coverage pre-commit +rpyc diff --git a/tests/utils.py b/tests/utils.py index 371d953e6..fd234b73e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -7,15 +7,19 @@ import os import pathlib import platform +import random import re +import rpyc +import struct import subprocess import tempfile import time import unittest import warnings -from typing import Dict, Iterable, List, Optional, Union +from typing import Iterable, List, Optional, Union from urllib.request import urlopen + TMPDIR = pathlib.Path(tempfile.gettempdir()) ARCH = (os.getenv("GEF_CI_ARCH") or platform.machine()).lower() BIN_SH = pathlib.Path("/bin/sh") @@ -28,65 +32,130 @@ GEF_DEFAULT_PROMPT = "gef➤ " GEF_DEFAULT_TEMPDIR = "/tmp/gef" GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")) +RPYC_GEF_PATH = GEF_PATH.parent / "scripts/remote_debug.py" +RPYC_HOST = "localhost" +RPYC_PORT = 18812 STRIP_ANSI_DEFAULT = True GDBSERVER_DEFAULT_HOST = "localhost" GDBSERVER_DEFAULT_PORT = 1234 CommandType = Union[str, Iterable[str]] + +ERROR_INACTIVE_SESSION_MESSAGE = "[*] No debugging session active\n" +WARNING_DEPRECATION_MESSAGE = "is deprecated and will be removed in a feature release." + + class Color(enum.Enum): """Used to colorify terminal output.""" - NORMAL = "\x1b[0m" - GRAY = "\x1b[1;38;5;240m" - LIGHT_GRAY = "\x1b[0;37m" - RED = "\x1b[31m" - GREEN = "\x1b[32m" - YELLOW = "\x1b[33m" - BLUE = "\x1b[34m" - PINK = "\x1b[35m" - CYAN = "\x1b[36m" - BOLD = "\x1b[1m" - UNDERLINE = "\x1b[4m" - UNDERLINE_OFF = "\x1b[24m" - HIGHLIGHT = "\x1b[3m" - HIGHLIGHT_OFF = "\x1b[23m" - BLINK = "\x1b[5m" - BLINK_OFF = "\x1b[25m" + + NORMAL = "\x1b[0m" + GRAY = "\x1b[1;38;5;240m" + LIGHT_GRAY = "\x1b[0;37m" + RED = "\x1b[31m" + GREEN = "\x1b[32m" + YELLOW = "\x1b[33m" + BLUE = "\x1b[34m" + PINK = "\x1b[35m" + CYAN = "\x1b[36m" + BOLD = "\x1b[1m" + UNDERLINE = "\x1b[4m" + UNDERLINE_OFF = "\x1b[24m" + HIGHLIGHT = "\x1b[3m" + HIGHLIGHT_OFF = "\x1b[23m" + BLINK = "\x1b[5m" + BLINK_OFF = "\x1b[25m" class GdbAssertionError(AssertionError): pass +class RemoteGefUnitTestGeneric(unittest.TestCase): + """ """ + + def setUp(self) -> None: + if not hasattr(self, "_target"): + setattr(self, "_target", debug_target("default")) + else: + assert isinstance(self._target, pathlib.Path) # type: ignore pylint: disable=E1101 + assert self._target.exists() # type: ignore pylint: disable=E1101 + self._port = random.randint(1025, 65535) + self._command = [ + "gdb", + "-q", + "-nx", + "-ex", + f"source {GEF_PATH}", + "-ex", + "gef config gef.debug True", + "-ex", + "gef config gef.disable_color True", + # TODO add coverage check + "-ex", + f"source {RPYC_GEF_PATH}", + "-ex", + f"pi start_rpyc_service({self._port})", + ] + self._command.append("--") + self._command.append( + str(self._target.absolute()), # type: ignore pylint: disable=E1101 + ) + self._process = subprocess.Popen(self._command) + assert self._process.pid > 0 + time.sleep(0.5) + self._conn = rpyc.connect( + RPYC_HOST, + self._port, + ) + self._gdb = self._conn.root.gdb + self._gef = self._conn.root.gef + return super().setUp() + + def tearDown(self) -> None: + self._conn.close() + self._process.terminate() + return super().tearDown() + + class GefUnitTestGeneric(unittest.TestCase): """Generic class for command testing, that defines all helpers""" @staticmethod def assertException(buf): """Assert that GEF raised an Exception.""" - if not ("Python Exception <" in buf - or "Traceback" in buf - or "'gdb.error'" in buf - or "Exception raised" in buf - or "failed to execute properly, reason:" in buf): + if not ( + "Python Exception <" in buf + or "Traceback" in buf + or "'gdb.error'" in buf + or "Exception raised" in buf + or "failed to execute properly, reason:" in buf + ): raise GdbAssertionError("GDB Exception expected, not raised") @staticmethod def assertNoException(buf): """Assert that no Exception was raised from GEF.""" - if ("Python Exception <" in buf - or "Traceback" in buf - or "'gdb.error'" in buf - or "Exception raised" in buf - or "failed to execute properly, reason:" in buf): + if ( + "Python Exception <" in buf + or "Traceback" in buf + or "'gdb.error'" in buf + or "Exception raised" in buf + or "failed to execute properly, reason:" in buf + ): raise GdbAssertionError(f"Unexpected GDB Exception raised in {buf}") if "is deprecated and will be removed in a feature release." in buf: - lines = [l for l in buf.splitlines() - if "is deprecated and will be removed in a feature release." in l] + lines = [ + l + for l in buf.splitlines() + if "is deprecated and will be removed in a feature release." in l + ] deprecated_api_names = {x.split()[1] for x in lines} warnings.warn( - UserWarning(f"Use of deprecated API(s): {', '.join(deprecated_api_names)}") + UserWarning( + f"Use of deprecated API(s): {', '.join(deprecated_api_names)}" + ) ) @staticmethod @@ -108,9 +177,13 @@ def ansi_clean(s: str) -> str: return ansi_escape.sub("", s) -def gdb_run_cmd(cmd: CommandType, before: CommandType = (), after: CommandType = (), - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi: bool = STRIP_ANSI_DEFAULT) -> str: +def gdb_run_cmd( + cmd: CommandType, + before: CommandType = (), + after: CommandType = (), + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi: bool = STRIP_ANSI_DEFAULT, +) -> str: """Execute a command inside GDB. `before` and `after` are lists of commands to be executed before (resp. after) the command to test.""" @@ -121,17 +194,23 @@ def _add_command(commands: CommandType) -> List[str]: command = ["gdb", "-q", "-nx"] if COVERAGE_DIR: - coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv("PYTEST_XDIST_WORKER", "gw0") - command += _add_command([ - "pi from coverage import Coverage", - f"pi cov = Coverage(data_file=\"{coverage_file}\"," - "auto_data=True, branch=True)", - "pi cov.start()", - ]) - command += _add_command([ - f"source {GEF_PATH}", - "gef config gef.debug True", - ]) + coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( + "PYTEST_XDIST_WORKER", "gw0" + ) + command += _add_command( + [ + "pi from coverage import Coverage", + f'pi cov = Coverage(data_file="{coverage_file}",' + "auto_data=True, branch=True)", + "pi cov.start()", + ] + ) + command += _add_command( + [ + f"source {GEF_PATH}", + "gef config gef.debug True", + ] + ) command += _add_command(before) command += _add_command(cmd) command += _add_command(after) @@ -139,7 +218,9 @@ def _add_command(commands: CommandType) -> List[str]: command += _add_command(["pi cov.stop()", "pi cov.save()"]) command += ["-ex", "quit", "--", str(target)] - lines = subprocess.check_output(command, stderr=subprocess.STDOUT).strip().splitlines() + lines = ( + subprocess.check_output(command, stderr=subprocess.STDOUT).strip().splitlines() + ) output = b"\n".join(lines) result = None @@ -163,62 +244,96 @@ def _add_command(commands: CommandType) -> List[str]: return result -def gdb_run_silent_cmd(cmd: CommandType, before: CommandType = (), after: CommandType = (), - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi: bool = STRIP_ANSI_DEFAULT) -> str: +def gdb_run_silent_cmd( + cmd: CommandType, + before: CommandType = (), + after: CommandType = (), + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi: bool = STRIP_ANSI_DEFAULT, +) -> str: """Disable the output and run entirely the `target` binary.""" - before = [*before, "gef config context.clear_screen False", - "gef config context.layout '-code -stack'", - "run"] + before = [ + *before, + "gef config context.clear_screen False", + "gef config context.layout '-code -stack'", + "run", + ] return gdb_run_cmd(cmd, before, after, target, strip_ansi) -def gdb_run_cmd_last_line(cmd: CommandType, before: CommandType = (), after: CommandType = (), - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi: bool = STRIP_ANSI_DEFAULT) -> str: +def gdb_run_cmd_last_line( + cmd: CommandType, + before: CommandType = (), + after: CommandType = (), + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi: bool = STRIP_ANSI_DEFAULT, +) -> str: """Execute a command in GDB, and return only the last line of its output.""" return gdb_run_cmd(cmd, before, after, target, strip_ansi).splitlines()[-1] -def gdb_start_silent_cmd(cmd: CommandType, before: CommandType = (), after: CommandType = (), - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi: bool = STRIP_ANSI_DEFAULT, - context: str = DEFAULT_CONTEXT) -> str: +def gdb_start_silent_cmd( + cmd: CommandType, + before: CommandType = (), + after: CommandType = (), + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi: bool = STRIP_ANSI_DEFAULT, + context: str = DEFAULT_CONTEXT, +) -> str: """Execute a command in GDB by starting an execution context. This command disables the `context` and sets a tbreak at the most convenient entry point.""" - before = [*before, "gef config context.clear_screen False", - f"gef config context.layout '{context}'", - "entry-break"] + before = [ + *before, + "gef config context.clear_screen False", + f"gef config context.layout '{context}'", + "entry-break", + ] return gdb_run_cmd(cmd, before, after, target, strip_ansi) -def gdb_start_silent_cmd_last_line(cmd: CommandType, before: CommandType = (), - after: CommandType = (), - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi=STRIP_ANSI_DEFAULT) -> str: +def gdb_start_silent_cmd_last_line( + cmd: CommandType, + before: CommandType = (), + after: CommandType = (), + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi=STRIP_ANSI_DEFAULT, +) -> str: """Execute `gdb_start_silent_cmd()` and return only the last line of its output.""" return gdb_start_silent_cmd(cmd, before, after, target, strip_ansi).splitlines()[-1] -def gdb_test_python_method(meth: str, before: str = "", after: str = "", - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi: bool = STRIP_ANSI_DEFAULT) -> str: +def gdb_test_python_method( + meth: str, + before: str = "", + after: str = "", + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi: bool = STRIP_ANSI_DEFAULT, +) -> str: brk = before + ";" if before else "" cmd = f"pi {brk}print({meth});{after}" return gdb_start_silent_cmd(cmd, target=target, strip_ansi=strip_ansi) -def gdb_time_python_method(meth: str, setup: str, - py_before: str = "", py_after: str = "", - before: CommandType = (), after: CommandType = (), - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi: bool = STRIP_ANSI_DEFAULT, number: int = 1000) -> float: +def gdb_time_python_method( + meth: str, + setup: str, + py_before: str = "", + py_after: str = "", + before: CommandType = (), + after: CommandType = (), + target: pathlib.Path = DEFAULT_TARGET, + strip_ansi: bool = STRIP_ANSI_DEFAULT, + number: int = 1000, +) -> float: brk = py_before + ";" if py_before else "" - cmd = f"""pi import timeit;{brk}print(timeit.timeit("{meth}", """\ - f"""setup="{setup}", number={number}));{py_after}""" - lines = gdb_run_cmd(cmd, before=before, after=after, - target=target, strip_ansi=strip_ansi).splitlines() + cmd = ( + f"""pi import timeit;{brk}print(timeit.timeit("{meth}", """ + f"""setup="{setup}", number={number}));{py_after}""" + ) + lines = gdb_run_cmd( + cmd, before=before, after=after, target=target, strip_ansi=strip_ansi + ).splitlines() return float(lines[-1]) @@ -231,9 +346,11 @@ def debug_target(name: str, extension: str = ".out") -> pathlib.Path: return target -def start_gdbserver(exe: Union[str, pathlib.Path] = debug_target("default"), - host: str = GDBSERVER_DEFAULT_HOST, - port: int = GDBSERVER_DEFAULT_PORT) -> subprocess.Popen: +def start_gdbserver( + exe: Union[str, pathlib.Path] = debug_target("default"), + host: str = GDBSERVER_DEFAULT_HOST, + port: int = GDBSERVER_DEFAULT_PORT, +) -> subprocess.Popen: """Start a gdbserver on the target binary. Args: @@ -243,8 +360,11 @@ def start_gdbserver(exe: Union[str, pathlib.Path] = debug_target("default"), Returns: subprocess.Popen: a Popen object for the gdbserver process. """ - return subprocess.Popen(["gdbserver", f"{host}:{port}", exe], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + return subprocess.Popen( + ["gdbserver", f"{host}:{port}", exe], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) def stop_gdbserver(gdbserver: subprocess.Popen) -> None: @@ -266,16 +386,21 @@ def gdbserver_session(*args, **kwargs): port = kwargs.get("port", GDBSERVER_DEFAULT_PORT) sess = start_gdbserver(exe, host, port) try: - time.sleep(1) # forced delay to allow gdbserver to start listening + time.sleep(1) # forced delay to allow gdbserver to start listening yield sess finally: stop_gdbserver(sess) -def start_qemuuser(exe: Union[str, pathlib.Path] = debug_target("default"), - port: int = GDBSERVER_DEFAULT_PORT) -> subprocess.Popen: - return subprocess.Popen(["qemu-x86_64", "-g", str(port), exe], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) +def start_qemuuser( + exe: Union[str, pathlib.Path] = debug_target("default"), + port: int = GDBSERVER_DEFAULT_PORT, +) -> subprocess.Popen: + return subprocess.Popen( + ["qemu-x86_64", "-g", str(port), exe], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) def stop_qemuuser(process: subprocess.Popen) -> None: @@ -295,7 +420,6 @@ def qemuuser_session(*args, **kwargs): stop_qemuuser(sess) - def find_symbol(binary: pathlib.Path, symbol: str) -> int: """Find a symbol by name in a ELF binary using `objdump`. The expect output syntax for `objdump` is: @@ -316,9 +440,13 @@ def find_symbol(binary: pathlib.Path, symbol: str) -> int: KeyError if the symbol is not found """ name = symbol.encode("utf8") - for line in [x.strip().split() for x in subprocess.check_output(["objdump", "-t", binary]).splitlines() if len(x.strip())]: - if line[-1] == name: - return int(line[0], 0x10) + for line in [ + x.strip().split() + for x in subprocess.check_output(["objdump", "-t", binary]).splitlines() + if len(x.strip()) + ]: + if line[-1] == name: + return int(line[0], 0x10) raise KeyError(f"`{symbol}` not found in {binary}") @@ -333,11 +461,7 @@ def findlines(substring: str, buffer: str) -> List[str]: Returns: List[str] """ - return [ - line.strip() - for line in buffer.splitlines() - if substring in line.strip() - ] + return [line.strip() for line in buffer.splitlines() if substring in line.strip()] def removeafter(substring: str, buffer: str, included: bool = False) -> str: @@ -384,7 +508,6 @@ def removeuntil(substring: str, buffer: str, included: bool = False) -> str: return buffer[idx:] - def download_file(url: str) -> Optional[bytes]: """Download a file from the internet. @@ -399,3 +522,35 @@ def download_file(url: str) -> Optional[bytes]: return http.read() if http.getcode() == 200 else None except Exception: return None + + +def u8(x): + return struct.unpack(" Date: Fri, 5 Jan 2024 16:51:41 -0800 Subject: [PATCH 04/35] first batch of scripts transfert --- tests/api/deprecated.py | 19 ++++++++----------- tests/api/gef_arch.py | 37 ++++++++++++++++++++++++++----------- tests/commands/canary.py | 34 ++++++++++++++++++---------------- 3 files changed, 52 insertions(+), 38 deletions(-) diff --git a/tests/api/deprecated.py b/tests/api/deprecated.py index 319a58cc0..815e0f1af 100644 --- a/tests/api/deprecated.py +++ b/tests/api/deprecated.py @@ -2,18 +2,18 @@ test module for deprecated functions """ -import pytest -from tests.utils import ( - gdb_test_python_method, - GefUnitTestGeneric, -) -class GefFuncDeprecatedApi(GefUnitTestGeneric): +from tests.utils import WARNING_DEPRECATION_MESSAGE, RemoteGefUnitTestGeneric + + +class GefFuncDeprecatedApi(RemoteGefUnitTestGeneric): """Test class for deprecated functions and variables. Each of those tests expect to receive a deprecation warning.""" def test_deprecated_elf_values(self): + gdb = self._gdb + old_stuff = ( "Elf.X86_64", "Elf.X86_32", @@ -28,8 +28,5 @@ def test_deprecated_elf_values(self): ) for item in old_stuff: - with pytest.warns(Warning) as record: - res = gdb_test_python_method(item) - self.assertNoException(res) - if not record: - pytest.fail(f"Expected a warning for '{item}'!") + output = gdb.execute(f"pi {item}", to_string=True) + self.assertIn(WARNING_DEPRECATION_MESSAGE, output) diff --git a/tests/api/gef_arch.py b/tests/api/gef_arch.py index fab74049d..34cda68da 100644 --- a/tests/api/gef_arch.py +++ b/tests/api/gef_arch.py @@ -5,20 +5,35 @@ import pytest -from tests.utils import ARCH, gdb_test_python_method -from tests.utils import GefUnitTestGeneric +from tests.utils import ARCH, is_64b, debug_target, RemoteGefUnitTestGeneric -class GefArchApi(GefUnitTestGeneric): +class GefArchApi(RemoteGefUnitTestGeneric): """`gef.arch` test module.""" - def test_func_gef_arch_ptrsize(self): - res = gdb_test_python_method("gef.arch.ptrsize") - self.assertIn(res.splitlines()[-1], ("4", "8")) + def setUp(self) -> None: + self._target = debug_target("default") + return super().setUp() + def test_api_gef_arch_ptrsize(self): + if is_64b(): + self.assertEqual(self._gef.arch.ptrsize, 8) + else: + self.assertEqual(self._gef.arch.ptrsize, 4) - @pytest.mark.skipif(ARCH not in ["x86_64", "i686"], reason=f"Skipped for {ARCH}") - def test_func_reset_architecture(self): - res = gdb_test_python_method("gef.arch.arch, gef.arch.mode", before="reset_architecture()") - res = (res.splitlines()[-1]) - self.assertIn("X86", res) + @pytest.mark.skipif(ARCH != "x86_64", reason=f"Skipped for {ARCH}") + def test_api_gef_arch_x86_64(self): + arch = self._gef.arch + self.assertEqual(arch.arch, "X86") + self.assertEqual(arch.mode, "64") + + self._gdb.execute("start") + assert arch.flag_register_to_human(0).startswith("[zero carry parity adjust") + assert arch.flag_register_to_human(None).lower().startswith("[zero carry parity adjust") + + + @pytest.mark.skipif(ARCH != "i686", reason=f"Skipped for {ARCH}") + def test_api_gef_arch_x86(self): + arch = self._gef.arch + self.assertEqual(arch.arch, "X86") + self.assertEqual(arch.mode, "32") diff --git a/tests/commands/canary.py b/tests/commands/canary.py index a0ce3be23..5562516ae 100644 --- a/tests/commands/canary.py +++ b/tests/commands/canary.py @@ -3,29 +3,31 @@ """ -from tests.utils import gdb_start_silent_cmd, gdb_run_cmd, debug_target, gdb_test_python_method -from tests.utils import GefUnitTestGeneric +from tests.utils import ARCH, ERROR_INACTIVE_SESSION_MESSAGE, debug_target, p64 +from tests.utils import RemoteGefUnitTestGeneric import pytest -import platform -ARCH = platform.machine() - -class CanaryCommand(GefUnitTestGeneric): +class CanaryCommand(RemoteGefUnitTestGeneric): """`canary` command test module""" + def setUp(self) -> None: + self._target = debug_target("canary") + return super().setUp() + def test_cmd_canary(self): - self.assertFailIfInactiveSession(gdb_run_cmd("canary")) - res = gdb_start_silent_cmd("canary", target=debug_target("canary")) - self.assertNoException(res) + self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE, self._gdb.execute("canary", to_string=True)) + self._gdb.execute("start") + res = self._gdb.execute("canary", to_string=True) self.assertIn("The canary of process", res) - res = gdb_test_python_method("gef.session.canary[0] == gef.session.original_canary[0]") - self.assertNoException(res) - self.assertIn("True", res) + self.assertEqual(self._gef.session.canary[0], self._gef.session.original_canary[0]) + @pytest.mark.skipif(ARCH != "x86_64", reason=f"Not implemented for {ARCH}") def test_overwrite_canary(self): - patch = r"pi gef.memory.write(gef.arch.canary_address(), p64(0xdeadbeef))" - res = gdb_start_silent_cmd(patch, target=debug_target("canary"), after=["canary"]) - self.assertNoException(res) - self.assertIn("0xdeadbeef", res) + gdb, gef = self._gdb, self._gef + + gdb.execute("start") + gef.memory.write(gef.arch.canary_address(), p64(0xdeadbeef)) + res = gef.memory.read(gef.arch.canary_address(), gef.arch.ptrsize) + self.assertEqual(0xdeadbeef, res) From 0a239683b097c634081c0280eb32811a9bb942ea Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 5 Jan 2024 17:12:42 -0800 Subject: [PATCH 05/35] added coverage conditions --- tests/utils.py | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index fd234b73e..198d64343 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -31,7 +31,7 @@ DEFAULT_TARGET = TMPDIR / "default.out" GEF_DEFAULT_PROMPT = "gef➤ " GEF_DEFAULT_TEMPDIR = "/tmp/gef" -GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")) +GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")).absolute() RPYC_GEF_PATH = GEF_PATH.parent / "scripts/remote_debug.py" RPYC_HOST = "localhost" RPYC_PORT = 18812 @@ -75,32 +75,45 @@ class RemoteGefUnitTestGeneric(unittest.TestCase): """ """ def setUp(self) -> None: + self._coverage_file = None if not hasattr(self, "_target"): setattr(self, "_target", debug_target("default")) else: assert isinstance(self._target, pathlib.Path) # type: ignore pylint: disable=E1101 assert self._target.exists() # type: ignore pylint: disable=E1101 self._port = random.randint(1025, 65535) + self._commands = f""" +source {GEF_PATH} +gef config gef.debug True +gef config gef.disable_color True +source {RPYC_GEF_PATH} +""" + if COVERAGE_DIR: + self._coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( + "PYTEST_XDIST_WORKER", "gw0" + ) + self._commands += f""" +pi import coverage +pi cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True) +pi cov.start() +""" + + self._commands += f""" +pi start_rpyc_service({self._port}) +""" + + self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) + self._initfile.write(self._commands) + self._initfile.flush() self._command = [ "gdb", "-q", "-nx", "-ex", - f"source {GEF_PATH}", - "-ex", - "gef config gef.debug True", - "-ex", - "gef config gef.disable_color True", - # TODO add coverage check - "-ex", - f"source {RPYC_GEF_PATH}", - "-ex", - f"pi start_rpyc_service({self._port})", - ] - self._command.append("--") - self._command.append( + f"source {self._initfile.name}", + "--", str(self._target.absolute()), # type: ignore pylint: disable=E1101 - ) + ] self._process = subprocess.Popen(self._command) assert self._process.pid > 0 time.sleep(0.5) @@ -113,6 +126,9 @@ def setUp(self) -> None: return super().setUp() def tearDown(self) -> None: + if COVERAGE_DIR: + self._gdb.execute("pi cov.stop()") + self._gdb.execute("pi cov.save()") self._conn.close() self._process.terminate() return super().tearDown() From be5ae3c8bd724fce8d044f031845d792465e990b Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 6 Jan 2024 09:22:01 -0800 Subject: [PATCH 06/35] removed `update_gef` and added a new setting to allow progation of exception when in debug mode --- gef.py | 35 ++++++++++++++++++----------------- tests/utils.py | 10 ++++++---- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/gef.py b/gef.py index e804ce6fb..32b69f1a3 100644 --- a/gef.py +++ b/gef.py @@ -102,29 +102,17 @@ def http_get(url: str) -> Optional[bytes]: def update_gef(argv: List[str]) -> int: - """Try to update `gef` to the latest version pushed on GitHub main branch. - Return 0 on success, 1 on failure. """ - ver = GEF_DEFAULT_BRANCH - latest_gef_data = http_get(f"https://raw.githubusercontent.com/hugsy/gef/{ver}/scripts/gef.sh") - if not latest_gef_data: - print("[-] Failed to get remote gef") - return 1 - with tempfile.NamedTemporaryFile(suffix=".sh") as fd: - fd.write(latest_gef_data) - fd.flush() - fpath = pathlib.Path(fd.name) - return subprocess.run(["bash", fpath, ver], stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL).returncode + """Obsolete. Use the `gef.sh`.""" + return -1 try: import gdb # type:ignore except ImportError: - # if out of gdb, the only action allowed is to update gef.py if len(sys.argv) >= 2 and sys.argv[1].lower() in ("--update", "--upgrade"): - sys.exit(update_gef(sys.argv[2:])) + print("[-] `update_gef` is obsolete. Use the `gef.sh` script to update gef from the command line.") print("[-] gef cannot run as standalone") - sys.exit(0) + sys.exit(1) GDB_MIN_VERSION = (8, 0) @@ -703,6 +691,15 @@ def __str__(self) -> str: return (f"Section(page_start={self.page_start:#x}, page_end={self.page_end:#x}, " f"permissions={self.permission!s})") + def __eq__(self, other: "Section") -> bool: + return \ + self.page_start == other.page_start and \ + self.page_end == other.page_end and \ + self.offset == other.offset and \ + self.permission == other.permission and \ + self.inode == other.inode and \ + self.path == other.path + Zone = collections.namedtuple("Zone", ["name", "zone_start", "zone_end", "filename"]) @@ -4567,6 +4564,8 @@ def invoke(self, args: str, from_tty: bool) -> None: # catching generic Exception, but rather specific ones. This is allows a much cleaner use. if is_debug(): show_last_exception() + if gef.config["gef.propagate_debug_exception"] is True: + raise else: err(f"Command '{self._cmdline_}' failed to execute properly, reason: {e}") return @@ -5821,7 +5820,7 @@ def search_pattern_by_address(self, pattern: str, start_address: int, end_addres try: mem = gef.memory.read(chunk_addr, chunk_size) - except gdb.MemoryError as e: + except gdb.MemoryError: return [] for match in re.finditer(_pattern, mem): @@ -9642,6 +9641,7 @@ def __init__(self) -> None: gef.config["gef.bruteforce_main_arena"] = GefSetting(False, bool, "Allow bruteforcing main_arena symbol if everything else fails") gef.config["gef.libc_version"] = GefSetting("", str, "Specify libc version when auto-detection fails") gef.config["gef.main_arena_offset"] = GefSetting("", str, "Offset from libc base address to main_arena symbol (int or hex). Set to empty string to disable.") + gef.config["gef.propagate_debug_exception"] = GefSetting(False, bool, "If true, when debug mode is enabled, Python exceptions will be propagated all the way.") self.commands : Dict[str, GenericCommand] = collections.OrderedDict() self.functions : Dict[str, GenericFunction] = collections.OrderedDict() @@ -10825,6 +10825,7 @@ def add_hook(self, access, func): if not callable(func): raise ValueError("hook is not callable") self.hooks[access].append(func) + return self @staticmethod def no_spaces(value): diff --git a/tests/utils.py b/tests/utils.py index 198d64343..ac29bef45 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -85,6 +85,7 @@ def setUp(self) -> None: self._commands = f""" source {GEF_PATH} gef config gef.debug True +gef config gef.propagate_debug_exception True gef config gef.disable_color True source {RPYC_GEF_PATH} """ @@ -396,10 +397,11 @@ def stop_gdbserver(gdbserver: subprocess.Popen) -> None: @contextlib.contextmanager -def gdbserver_session(*args, **kwargs): - exe = kwargs.get("exe", "") or debug_target("default") - host = kwargs.get("host", GDBSERVER_DEFAULT_HOST) - port = kwargs.get("port", GDBSERVER_DEFAULT_PORT) +def gdbserver_session( + port: int = GDBSERVER_DEFAULT_PORT, + host: str = GDBSERVER_DEFAULT_HOST, + exe: Union[str, pathlib.Path] = debug_target("default"), +): sess = start_gdbserver(exe, host, port) try: time.sleep(1) # forced delay to allow gdbserver to start listening From 6892f4ff7ca69d2e5941a2c2df85069cdd5b7442 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 6 Jan 2024 09:22:43 -0800 Subject: [PATCH 07/35] finished porting tests/api --- tests/api/gef_disasemble.py | 48 ++++++--- tests/api/gef_heap.py | 45 ++++---- tests/api/gef_session.py | 94 +++++++++-------- tests/api/misc.py | 203 ++++++++++++++++++------------------ 4 files changed, 212 insertions(+), 178 deletions(-) diff --git a/tests/api/gef_disasemble.py b/tests/api/gef_disasemble.py index 329ac6a18..378d395f7 100644 --- a/tests/api/gef_disasemble.py +++ b/tests/api/gef_disasemble.py @@ -4,27 +4,47 @@ import pytest -from tests.utils import ARCH, debug_target, gdb_run_silent_cmd -from tests.utils import GefUnitTestGeneric +from tests.utils import ARCH, debug_target, RemoteGefUnitTestGeneric -class GefDisassembleApiFunction(GefUnitTestGeneric): +class GefDisassembleApiFunction(RemoteGefUnitTestGeneric): """`gef_disassemble` function test module.""" + def setUp(self) -> None: + self._target = debug_target("mmap-known-address") + return super().setUp() + @pytest.mark.skipif(ARCH not in ("x86_64", "i686"), reason=f"Skipped for {ARCH}") def test_func_gef_disassemble(self): - cmd = "gef_disassemble(0x2337100, 4, 4)" - res = gdb_run_silent_cmd(f"pi os.linesep.join([str(i) for i in {cmd}])", target=debug_target("mmap-known-address")) - self.assertNoException(res) - self.assertIn( - ' 0x23370fc int3 \\n 0x23370fd int3 \\n 0x23370fe int3 \\n 0x23370ff int3 \\n 0x2337100 int3 \\n 0x2337101 int3 \\n 0x2337102 int3 \\n 0x2337103 int3 ', res) + self._gdb.execute("run") + output = self._conn.root.eval("list(map(str,gef_disassemble(0x2337100, 4, 4)))") + expected = [ + " 0x23370fc int3 ", + " 0x23370fd int3 ", + " 0x23370fe int3 ", + " 0x23370ff int3 ", + " 0x2337100 int3 ", + " 0x2337101 int3 ", + " 0x2337102 int3 ", + " 0x2337103 int3 ", + ] + assert len(output) == len(expected) + for i in range(len(output)): + assert output[i] == expected[i] @pytest.mark.skipif(ARCH not in ("x86_64", "i686"), reason=f"Skipped for {ARCH}") def test_func_gef_disassemble_page_border(self): # Regression test for issue #922 - cmd = "gef_disassemble(0x2337000, 4, 4)" - res = gdb_run_silent_cmd( - f"pi os.linesep.join([str(i) for i in {cmd}])", target=debug_target("mmap-known-address")) - self.assertNoException(res) - self.assertIn( - '0x2337000 int3 \\n 0x2337001 int3 \\n 0x2337002 int3 \\n 0x2337003 int3 ', res) + self._gdb.execute("run") + output = self._conn.root.eval( + "list(map(str, gef_disassemble(0x2337000, 4, 4)))" + ) + expected = [ + " 0x2337000 int3 ", + " 0x2337001 int3 ", + " 0x2337002 int3 ", + " 0x2337003 int3 ", + ] + assert len(output) == len(expected) + for i in range(len(output)): + assert output[i] == expected[i] diff --git a/tests/api/gef_heap.py b/tests/api/gef_heap.py index 1a338b035..dbf33d671 100644 --- a/tests/api/gef_heap.py +++ b/tests/api/gef_heap.py @@ -5,18 +5,18 @@ import pytest import random -from tests.utils import ARCH, debug_target, gdb_test_python_method, is_64b -from tests.utils import GefUnitTestGeneric - - -def result_as_int(res: str) -> int: - return int(gdb_test_python_method(res, target=debug_target("heap")).splitlines()[-1]) +from tests.utils import ARCH, debug_target, is_64b +from tests.utils import RemoteGefUnitTestGeneric TCACHE_BINS = 64 -class GefHeapApi(GefUnitTestGeneric): +class GefHeapApi(RemoteGefUnitTestGeneric): """`gef.heap` test module.""" + def setUp(self) -> None: + self._target = debug_target("heap") + return super().setUp() + # from https://elixir.bootlin.com/glibc/latest/source/malloc/malloc.c#L326 # With rounding and alignment, the bins are... # idx 0 bytes 0..24 (64-bit) or 0..12 (32-bit) @@ -44,35 +44,44 @@ def valid_sizes(self): def test_func_gef_heap_tidx2size(self): + gdb, gef = self._gdb, self._gef + gdb.execute("run") for _ in range(5): idx = random.choice(range(TCACHE_BINS)) - size = result_as_int(f"gef.heap.tidx2size({idx})") - self.assertIn(size, self.valid_sizes, f"idx={idx}") + size = gef.heap.tidx2size(idx) + assert size in self.valid_sizes, f"idx={idx}" def test_func_gef_heap_csize2tidx(self): + gdb, gef = self._gdb, self._gef + gdb.execute("run") for _ in range(5): size = random.randint(0, 1032 if ARCH == "i686" or is_64b() else 516) - idx = result_as_int(f"gef.heap.csize2tidx({size})") - self.assertIn(idx, range(TCACHE_BINS), f"size={size}") + idx = gef.heap.csize2tidx(size) + assert idx in range(TCACHE_BINS), f"size={size}" @pytest.mark.skipif(ARCH not in ("x86_64",), reason=f"Skipped for {ARCH}") def test_func_gef_heap_malloc_align_address(self): + gdb, gef = self._gdb, self._gef + gdb.execute("run") values = ( (0x08, 0x10), (0x11, 0x20), (0x23, 0x30), (0x13371337, 0x13371340), ) + for x, y in values: - res = result_as_int(f"gef.heap.malloc_align_address({x})") - self.assertEqual(res, y) + res = gef.heap.malloc_align_address(x) + assert res == y def test_class_glibcarena_main_arena(self): - addr1 = result_as_int("GlibcArena('main_arena').addr") - addr2 = result_as_int("search_for_main_arena()") - addr3 = result_as_int("int(gef.heap.main_arena)") - self.assertEqual(addr1, addr2) - self.assertEqual(addr2, addr3) + gdb, gef = self._gdb, self._gef + gdb.execute("run") + addr1 = self._conn.root.eval("GlibcArena('main_arena').addr") + addr2 = self._conn.root.eval("search_for_main_arena()") + addr3 = gef.heap.main_arena + assert addr1 == addr2 + assert addr2 == addr3 diff --git a/tests/api/gef_session.py b/tests/api/gef_session.py index c5b190380..a6b7f5091 100644 --- a/tests/api/gef_session.py +++ b/tests/api/gef_session.py @@ -2,79 +2,83 @@ `gef.session` test module. """ - +import pathlib import os import random -import subprocess + from tests.utils import ( - TMPDIR, - gdb_test_python_method, debug_target, - GefUnitTestGeneric, + RemoteGefUnitTestGeneric, gdbserver_session, - gdb_run_cmd, qemuuser_session, - GDBSERVER_DEFAULT_HOST + GDBSERVER_DEFAULT_HOST, ) import re -class GefSessionApi(GefUnitTestGeneric): +class GefSessionApi(RemoteGefUnitTestGeneric): """`gef.session` test module.""" - def test_func_get_filepath(self): - res = gdb_test_python_method("gef.session.file", target=debug_target("default")) - self.assertNoException(res) - target = TMPDIR / "foo bar" - subprocess.call(["cp", debug_target("default"), target]) - res = gdb_test_python_method("gef.session.file", target=target) - self.assertNoException(res) - subprocess.call(["rm", target]) + def setUp(self) -> None: + self._target = debug_target("default") + return super().setUp() + def test_func_get_filepath(self): + gdb, gef = self._gdb, self._gef + gdb.execute("start") + assert isinstance(gef.session.file, pathlib.Path) + assert str(gef.session.file.absolute()) == str(self._target.absolute()) def test_func_get_pid(self): - res = gdb_test_python_method("gef.session.pid", target=debug_target("default")) - self.assertNoException(res) - self.assertTrue(int(res.splitlines()[-1])) + gdb, gef = self._gdb, self._gef + gdb.execute("start") + pid_from_gdb = int( + gdb.execute("info proc", to_string=True).splitlines()[0].split()[1] + ) + assert gef.session.pid == pid_from_gdb def test_func_auxiliary_vector(self): - func = "gef.session.auxiliary_vector" - res = gdb_test_python_method(func, target=debug_target("default")) - self.assertNoException(res) - # we need at least ("AT_PLATFORM", "AT_EXECFN") right now - self.assertTrue("'AT_PLATFORM'" in res) - self.assertTrue("'AT_EXECFN':" in res) - self.assertFalse("'AT_WHATEVER':" in res) + gdb, gef = self._gdb, self._gef + gdb.execute("start") + + assert "AT_PLATFORM" in gef.session.auxiliary_vector + assert "AT_EXECFN" in gef.session.auxiliary_vector + assert "AT_WHATEVER" not in gef.session.auxiliary_vector + assert gef.session.auxiliary_vector["AT_PAGESZ"] == 0x1000 def test_root_dir_local(self): - func = "(s.st_dev, s.st_ino)" - res = gdb_test_python_method(func, target=debug_target("default"), before="s=os.stat(gef.session.root)") - self.assertNoException(res) - st_dev, st_ino = eval(res.split("\n")[-1]) - stat_root = os.stat("/") + gdb, gef = self._gdb, self._gef + gdb.execute("start") + + assert gef.session.root + result = self._conn.root.eval("os.stat(gef.session.root)") + expected = os.stat("/") # Check that the `/` directory and the `session.root` directory are the same - assert (stat_root.st_dev == st_dev) and (stat_root.st_ino == st_ino) + assert (expected.st_dev == result.st_dev) and (expected.st_ino == result.st_ino) def test_root_dir_remote(self): - func = "(s.st_dev, s.st_ino)" - stat_root = os.stat("/") + gdb = self._gdb + gdb.execute("start") + + expected = os.stat("/") host = GDBSERVER_DEFAULT_HOST port = random.randint(1025, 65535) - before = [f"gef-remote {host} {port}", "pi s=os.stat(gef.session.root)"] with gdbserver_session(port=port): - res = gdb_run_cmd(f"pi {func}", target=debug_target("default"), before=before) - self.assertNoException(res) - st_dev, st_ino = eval(res.split("\n")[-1]) - assert (stat_root.st_dev == st_dev) and (stat_root.st_ino == st_ino) + gdb.execute(f"gef-remote {host} {port}") + result = self._conn.root.eval("os.stat(gef.session.root)") + assert (expected.st_dev == result.st_dev) and ( + expected.st_ino == result.st_ino + ) def test_root_dir_qemu(self): + gdb, gef = self._gdb, self._gef + gdb.execute("start") + host = GDBSERVER_DEFAULT_HOST port = random.randint(1025, 65535) with qemuuser_session(port=port): - target = debug_target("default") - before = [ - f"gef-remote --qemu-user --qemu-binary {target} {host} {port}"] - res = gdb_run_cmd(f"pi gef.session.root", target=debug_target("default"), before=before) - self.assertNoException(res) - assert re.search(r"\/proc\/[0-9]+/root", res) + gdb.execute( + f"gef-remote --qemu-user --qemu-binary {self._target} {host} {port}" + ) + assert re.search(r"\/proc\/[0-9]+/root", str(gef.session.root)) diff --git a/tests/api/misc.py b/tests/api/misc.py index d8de03f90..b72ec8501 100644 --- a/tests/api/misc.py +++ b/tests/api/misc.py @@ -3,137 +3,138 @@ """ import pathlib -import tempfile -import subprocess -import os import pytest +import random from tests.utils import ( debug_target, - gdb_start_silent_cmd, - gdb_test_python_method, - gdb_run_cmd, gdbserver_session, qemuuser_session, - GefUnitTestGeneric, + RemoteGefUnitTestGeneric, GDBSERVER_DEFAULT_HOST, - GDBSERVER_DEFAULT_PORT, ) -class MiscFunctionTest(GefUnitTestGeneric): +class MiscFunctionTest(RemoteGefUnitTestGeneric): """Tests GEF internal functions.""" + def setUp(self) -> None: + self._target = debug_target("default") + return super().setUp() + def test_func_which(self): - res = gdb_test_python_method("which('gdb')") - lines = res.splitlines() - self.assertIn("/gdb", lines[-1]) - res = gdb_test_python_method("which('__IDontExist__')") - self.assertIn("Missing file `__IDontExist__`", res) + root = self._conn.root + + res = root.eval("which('gdb')") + assert isinstance(res, pathlib.Path) + assert res.name == "gdb" + + with pytest.raises(FileNotFoundError): + root.eval("which('__IDontExist__')") def test_func_gef_convenience(self): - func = "gef_convenience('meh')" - res = gdb_test_python_method(func, target=debug_target("default")) - self.assertNoException(res) + root = self._conn.root + root.eval("gef_convenience('meh')") def test_func_parse_address(self): - func = "parse_address('main+0x4')" - res = gdb_test_python_method(func) - self.assertNoException(res) + root = self._conn.root + assert isinstance(root.eval("parse_address('main+0x4')"), int) - func = "parse_address('meh')" - res = gdb_test_python_method(func) - self.assertException(res) + with pytest.raises(Exception): + root.eval("parse_address('meh')") def test_func_parse_permissions(self): - func = "Permission.from_info_sections('ALLOC LOAD READONLY CODE HAS_CONTENTS')" - res = gdb_test_python_method(func) - self.assertNoException(res) - - func = "Permission.from_process_maps('r--')" - res = gdb_test_python_method(func) - self.assertNoException(res) + root = self._conn.root + expected_values = [ + ( + "Permission.from_info_sections('ALLOC LOAD READONLY CODE HAS_CONTENTS')", + "r-x", + ), + ("Permission.from_process_maps('r--')", "r--"), + ("Permission.from_monitor_info_mem('-r-')", "r--"), + ("Permission.from_info_mem('rw')", "rw-"), + ] + for cmd, expected in expected_values: + assert str(root.eval(cmd)) == expected + + def test_func_parse_maps_local_procfs(self): + root, gdb, gef = self._conn.root, self._gdb, self._gef + + with pytest.raises(FileNotFoundError): + root.eval("list(GefMemoryManager.parse_procfs_maps())") + + gdb.execute("start") + + sections = root.eval("list(GefMemoryManager.parse_procfs_maps())") + for section in sections: + assert section.page_start & ~0xFFF + assert section.page_end & ~0xFFF - func = "Permission.from_monitor_info_mem('-r-')" - res = gdb_test_python_method(func) - self.assertNoException(res) - - func = "Permission.from_info_mem('rw')" - res = gdb_test_python_method(func) - self.assertNoException(res) + # The parse maps function should automatically get called when we start + # up, and we should be able to view the maps via the `gef.memory.maps` + # property. So check the alias + assert gef.memory.maps == sections - def test_func_parse_maps(self): - func = "list(GefMemoryManager.parse_procfs_maps())" - res = gdb_test_python_method(func) - self.assertNoException(res) - assert "Section" in res + def test_func_parse_maps_local_info_section(self): + root, gdb = self._conn.root, self._gdb + gdb.execute("start") - func = "list(GefMemoryManager.parse_gdb_info_sections())" - res = gdb_test_python_method(func) - self.assertNoException(res) - assert "Section" in res + sections = root.eval("list(GefMemoryManager.parse_gdb_info_sections())") + assert len(sections) > 0 + def test_func_parse_maps_remote_gdbserver(self): + root, gdb = self._conn.root, self._gdb # When in a gef-remote session `parse_gdb_info_sections` should work to # query the memory maps - port = GDBSERVER_DEFAULT_PORT + 1 - before = [f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}"] + while True: + port = random.randint(1025, 65535) + if port != self._port: + break + + with pytest.raises(Exception): + gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") + with gdbserver_session(port=port) as _: - func = "list(GefMemoryManager.parse_gdb_info_sections())" - res = gdb_test_python_method(func) - self.assertNoException(res) - assert "Section" in res + gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") + sections = root.eval("list(GefMemoryManager.parse_gdb_info_sections())") + assert len(sections) > 0 + def test_func_parse_maps_remote_qemu(self): + root, gdb = self._conn.root, self._gdb # When in a gef-remote qemu-user session `parse_gdb_info_sections` # should work to query the memory maps - port = GDBSERVER_DEFAULT_PORT + 2 - target = debug_target("default") - before = [ - f"gef-remote --qemu-user --qemu-binary {target} {GDBSERVER_DEFAULT_HOST} {port}"] - with qemuuser_session(port=port) as _: - func = "list(GefMemoryManager.parse_gdb_info_sections())" - res = gdb_test_python_method(func) - self.assertNoException(res) - assert "Section" in res - - # Running the _parse_maps method should just find the correct one - func = "list(GefMemoryManager._parse_maps())" - res = gdb_test_python_method(func) - self.assertNoException(res) - assert "Section" in res - - # The parse maps function should automatically get called when we start - # up, and we should be able to view the maps via the `gef.memory.maps` - # property. - func = "gef.memory.maps" - res = gdb_test_python_method(func) - self.assertNoException(res) - assert "Section" in res - - - @pytest.mark.slow - @pytest.mark.online - @pytest.mark.skip - def test_func_update_gef(self): - bkp_home = os.environ["HOME"] - for branch in ("main", ): - with tempfile.TemporaryDirectory() as tmpdir: - dirpath = pathlib.Path(tmpdir) - os.environ["HOME"] = str(dirpath.absolute()) - url = f"https://api.github.com/repos/hugsy/gef/git/ref/heads/{branch}" - cmd = f"""wget -q -O- {url} | grep '"sha"' | tr -s ' ' | """ \ - """cut -d ' ' -f 3 | tr -d ',' | tr -d '"' """ - ref = subprocess.check_output(cmd, shell=True).decode("utf-8").strip() - res = gdb_test_python_method(f"update_gef(['--{branch}'])") - retcode = int(res.splitlines()[-1]) - self.assertEqual(retcode, 0) - home = pathlib.Path().home() - self.assertEqual(open(f"{home}/.gdbinit", "r").read(), f"source ~/.gef-{ref}.py\n") - fpath = home / f".gef-{ref}.py" - self.assertTrue(fpath.exists()) - os.environ["HOME"] = bkp_home + while True: + port = random.randint(1025, 65535) + if port != self._port: + break + with qemuuser_session(port=port) as _: + cmd = f"gef-remote --qemu-user --qemu-binary {self._target} {GDBSERVER_DEFAULT_HOST} {port}" + gdb.execute(cmd) + sections = root.eval("list(GefMemoryManager.parse_gdb_info_sections())") + assert len(sections) > 0 def test_func_show_last_exception(self): - cmd = "hexdump byte *0" - res = gdb_start_silent_cmd(cmd, before=("gef config gef.debug 1",)) - self.assertException(res) + gdb = self._gdb + gdb.execute("start") + + # + # Test debug info collection + # + gdb.execute("gef config gef.debug True") + gdb.execute("gef config gef.propagate_debug_exception False") + output: str = gdb.execute("hexdump byte *0", to_string=True) + for title in ( + "Exception raised", + "Version", + "Last 10 GDB commands", + "Runtime environment", + ): + assert title in output + + # + # Test exception propagation + # + gdb.execute("gef config gef.propagate_debug_exception True") + with pytest.raises(Exception): + gdb.execute("hexdump byte *0") From 678c5059697d9bd6f5d4e307d1ad2fd6b77f9e7e Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 6 Jan 2024 09:25:37 -0800 Subject: [PATCH 08/35] typo --- tests/api/{gef_disasemble.py => gef_disassemble.py} | 0 tests/api/gef_session.py | 1 - 2 files changed, 1 deletion(-) rename tests/api/{gef_disasemble.py => gef_disassemble.py} (100%) diff --git a/tests/api/gef_disasemble.py b/tests/api/gef_disassemble.py similarity index 100% rename from tests/api/gef_disasemble.py rename to tests/api/gef_disassemble.py diff --git a/tests/api/gef_session.py b/tests/api/gef_session.py index a6b7f5091..76ea1ce32 100644 --- a/tests/api/gef_session.py +++ b/tests/api/gef_session.py @@ -73,7 +73,6 @@ def test_root_dir_remote(self): def test_root_dir_qemu(self): gdb, gef = self._gdb, self._gef - gdb.execute("start") host = GDBSERVER_DEFAULT_HOST port = random.randint(1025, 65535) From 46f0e85760518d4726db395b4388a62abdef26e3 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 6 Jan 2024 09:48:35 -0800 Subject: [PATCH 09/35] increased spawn time for rpyc --- tests/requirements.txt | 2 +- tests/utils.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index 7d21692d8..42ad3252b 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -6,4 +6,4 @@ pytest-benchmark pytest-forked coverage pre-commit -rpyc +rpyc>=5 diff --git a/tests/utils.py b/tests/utils.py index ac29bef45..fb864dfa8 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -35,6 +35,7 @@ RPYC_GEF_PATH = GEF_PATH.parent / "scripts/remote_debug.py" RPYC_HOST = "localhost" RPYC_PORT = 18812 +RPYC_SPAWN_TIME = 1.0 STRIP_ANSI_DEFAULT = True GDBSERVER_DEFAULT_HOST = "localhost" GDBSERVER_DEFAULT_PORT = 1234 @@ -117,7 +118,7 @@ def setUp(self) -> None: ] self._process = subprocess.Popen(self._command) assert self._process.pid > 0 - time.sleep(0.5) + time.sleep(RPYC_SPAWN_TIME) self._conn = rpyc.connect( RPYC_HOST, self._port, From fecf5ff3f377d1ee1b8bd014b767e1cddb4a3472 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 6 Jan 2024 12:06:56 -0800 Subject: [PATCH 10/35] docs + `tests.utils.RemoteGefUnitTestGeneric` -> `tests.base.RemoteGefUnitTestGeneric` --- docs/testing.md | 42 +++++++++++++----- tests/api/deprecated.py | 4 +- tests/api/gef_arch.py | 10 +++-- tests/api/gef_disassemble.py | 3 +- tests/api/gef_heap.py | 2 +- tests/api/gef_session.py | 7 +-- tests/api/misc.py | 4 +- tests/base.py | 85 ++++++++++++++++++++++++++++++++++++ tests/utils.py | 69 ----------------------------- 9 files changed, 135 insertions(+), 91 deletions(-) create mode 100644 tests/base.py diff --git a/docs/testing.md b/docs/testing.md index 6dc3f5951..6b5babfc9 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -49,8 +49,11 @@ You can then use `pytest` directly to help you fix each error specifically. GEF entirely relies on [`pytest`](https://pytest.org) for its testing. Refer to the project documentation for details. -Adding a new command __requires__ for extensive testing in a new dedicated test module that should -be located in `/root/of/gef/tests/commands/my_new_command.py` +Adding new code __requires__ extensive testing. Tests can be added in their own module in the +`tests/` folder. For example, if adding a new command to `gef`, a new test module should be created +and located in `/root/of/gef/tests/commands/my_new_command.py`. The test class __must__ inherit +`tests.base.RemoteGefUnitTestGeneric`. This class allows to manipulate gdb and gef through rpyc +under their respective `self._gdb` and `self._gef` attributes. A skeleton of a test module would look something like: @@ -60,24 +63,41 @@ A skeleton of a test module would look something like: """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd, gdb_start_silent_cmd +from tests.utils import RemoteGefUnitTestGeneric -class MyCommandCommand(GefUnitTestGeneric): +class MyCommandCommand(RemoteGefUnitTestGeneric): """`my-command` command test module""" + def setUp(self) -> None: + # By default, tests will be executed against the default.out binary + # You can change this behavior in the `setUp` function + self._target = debug_target("my-custom-binary-for-tests") + return super().setUp() + def test_cmd_my_command(self): - # `my-command` is expected to fail if the session is not active - self.assertFailIfInactiveSession(gdb_run_cmd("my-command")) + # some convenience variables + root, gdb, gef = self._conn.root, self._gdb, self._gef + + # You can then interact with any command from gdb or any class/function/variable from gef + # For instance: - # `my-command` should never throw an exception in GDB when running - res = gdb_start_silent_cmd("my-command") - self.assertNoException(res) + # * tests that `my-command` is expected to fail if the session is not active + output = gdb.execute("my-command", to_string=True) + assert output == ERROR_INACTIVE_SESSION_MESSAGE - # it also must print out a "Hello World" message - self.assertIn("Hello World", res) + # * `my-command` must print "Hello World" message when executed in running context + gdb.execute("start") + output = gdb.execute("my-command", to_string=True) + assert "Hello World" == output ``` +You might want to refer to the following documentations: + +* [`pytest`](https://docs.pytest.org/en/) +* [`gdb Python API`](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Python-API.html) +* (maybe) [`rpyc`](https://rpyc.readthedocs.io/en/latest/) + When running your test, you can summon `pytest` with the `--pdb` flag to enter the python testing environment to help you get more information about the reason of failure. diff --git a/tests/api/deprecated.py b/tests/api/deprecated.py index 815e0f1af..8d7eabb76 100644 --- a/tests/api/deprecated.py +++ b/tests/api/deprecated.py @@ -3,8 +3,8 @@ """ - -from tests.utils import WARNING_DEPRECATION_MESSAGE, RemoteGefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import WARNING_DEPRECATION_MESSAGE class GefFuncDeprecatedApi(RemoteGefUnitTestGeneric): diff --git a/tests/api/gef_arch.py b/tests/api/gef_arch.py index 34cda68da..48fb78090 100644 --- a/tests/api/gef_arch.py +++ b/tests/api/gef_arch.py @@ -5,7 +5,8 @@ import pytest -from tests.utils import ARCH, is_64b, debug_target, RemoteGefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ARCH, is_64b, debug_target class GefArchApi(RemoteGefUnitTestGeneric): @@ -29,8 +30,11 @@ def test_api_gef_arch_x86_64(self): self._gdb.execute("start") assert arch.flag_register_to_human(0).startswith("[zero carry parity adjust") - assert arch.flag_register_to_human(None).lower().startswith("[zero carry parity adjust") - + assert ( + arch.flag_register_to_human(None) + .lower() + .startswith("[zero carry parity adjust") + ) @pytest.mark.skipif(ARCH != "i686", reason=f"Skipped for {ARCH}") def test_api_gef_arch_x86(self): diff --git a/tests/api/gef_disassemble.py b/tests/api/gef_disassemble.py index 378d395f7..0d339e46d 100644 --- a/tests/api/gef_disassemble.py +++ b/tests/api/gef_disassemble.py @@ -4,7 +4,8 @@ import pytest -from tests.utils import ARCH, debug_target, RemoteGefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ARCH, debug_target class GefDisassembleApiFunction(RemoteGefUnitTestGeneric): diff --git a/tests/api/gef_heap.py b/tests/api/gef_heap.py index dbf33d671..ece33c841 100644 --- a/tests/api/gef_heap.py +++ b/tests/api/gef_heap.py @@ -5,8 +5,8 @@ import pytest import random +from tests.base import RemoteGefUnitTestGeneric from tests.utils import ARCH, debug_target, is_64b -from tests.utils import RemoteGefUnitTestGeneric TCACHE_BINS = 64 diff --git a/tests/api/gef_session.py b/tests/api/gef_session.py index 76ea1ce32..0a0e2ce7b 100644 --- a/tests/api/gef_session.py +++ b/tests/api/gef_session.py @@ -2,18 +2,19 @@ `gef.session` test module. """ -import pathlib import os +import pathlib import random +import re + +from tests.base import RemoteGefUnitTestGeneric from tests.utils import ( debug_target, - RemoteGefUnitTestGeneric, gdbserver_session, qemuuser_session, GDBSERVER_DEFAULT_HOST, ) -import re class GefSessionApi(RemoteGefUnitTestGeneric): diff --git a/tests/api/misc.py b/tests/api/misc.py index b72ec8501..ebc4abacf 100644 --- a/tests/api/misc.py +++ b/tests/api/misc.py @@ -6,11 +6,12 @@ import pytest import random +from tests.base import RemoteGefUnitTestGeneric + from tests.utils import ( debug_target, gdbserver_session, qemuuser_session, - RemoteGefUnitTestGeneric, GDBSERVER_DEFAULT_HOST, ) @@ -82,6 +83,7 @@ def test_func_parse_maps_local_info_section(self): sections = root.eval("list(GefMemoryManager.parse_gdb_info_sections())") assert len(sections) > 0 + @pytest.mark.slow def test_func_parse_maps_remote_gdbserver(self): root, gdb = self._conn.root, self._gdb # When in a gef-remote session `parse_gdb_info_sections` should work to diff --git a/tests/base.py b/tests/base.py new file mode 100644 index 000000000..7fa72758e --- /dev/null +++ b/tests/base.py @@ -0,0 +1,85 @@ +import os +import pathlib +import random +import subprocess +import tempfile +import time +import unittest + +import rpyc + +from .utils import debug_target + +COVERAGE_DIR = os.getenv("COVERAGE_DIR", "") +GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")).absolute() +RPYC_GEF_PATH = GEF_PATH.parent / "scripts/remote_debug.py" +RPYC_HOST = "localhost" +RPYC_PORT = 18812 +RPYC_SPAWN_TIME = 1.0 + + +class RemoteGefUnitTestGeneric(unittest.TestCase): + """ + The base class for GEF test cases. This will create the `rpyc` environment to programatically interact with + GDB and GEF in the test. + """ + + def setUp(self) -> None: + self._coverage_file = None + if not hasattr(self, "_target"): + setattr(self, "_target", debug_target("default")) + else: + assert isinstance(self._target, pathlib.Path) # type: ignore pylint: disable=E1101 + assert self._target.exists() # type: ignore pylint: disable=E1101 + self._port = random.randint(1025, 65535) + self._commands = f""" +source {GEF_PATH} +gef config gef.debug True +gef config gef.propagate_debug_exception True +gef config gef.disable_color True +source {RPYC_GEF_PATH} +""" + if COVERAGE_DIR: + self._coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( + "PYTEST_XDIST_WORKER", "gw0" + ) + self._commands += f""" +pi import coverage +pi cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True) +pi cov.start() +""" + + self._commands += f""" +pi start_rpyc_service({self._port}) +""" + + self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) + self._initfile.write(self._commands) + self._initfile.flush() + self._command = [ + "gdb", + "-q", + "-nx", + "-ex", + f"source {self._initfile.name}", + "--", + str(self._target.absolute()), # type: ignore pylint: disable=E1101 + ] + self._process = subprocess.Popen(self._command) + assert self._process.pid > 0 + time.sleep(RPYC_SPAWN_TIME) + self._conn = rpyc.connect( + RPYC_HOST, + self._port, + ) + self._gdb = self._conn.root.gdb + self._gef = self._conn.root.gef + return super().setUp() + + def tearDown(self) -> None: + if COVERAGE_DIR: + self._gdb.execute("pi cov.stop()") + self._gdb.execute("pi cov.save()") + self._conn.close() + self._process.terminate() + return super().tearDown() diff --git a/tests/utils.py b/tests/utils.py index fb864dfa8..e99fca0ba 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -7,9 +7,7 @@ import os import pathlib import platform -import random import re -import rpyc import struct import subprocess import tempfile @@ -32,10 +30,6 @@ GEF_DEFAULT_PROMPT = "gef➤ " GEF_DEFAULT_TEMPDIR = "/tmp/gef" GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")).absolute() -RPYC_GEF_PATH = GEF_PATH.parent / "scripts/remote_debug.py" -RPYC_HOST = "localhost" -RPYC_PORT = 18812 -RPYC_SPAWN_TIME = 1.0 STRIP_ANSI_DEFAULT = True GDBSERVER_DEFAULT_HOST = "localhost" GDBSERVER_DEFAULT_PORT = 1234 @@ -72,69 +66,6 @@ class GdbAssertionError(AssertionError): pass -class RemoteGefUnitTestGeneric(unittest.TestCase): - """ """ - - def setUp(self) -> None: - self._coverage_file = None - if not hasattr(self, "_target"): - setattr(self, "_target", debug_target("default")) - else: - assert isinstance(self._target, pathlib.Path) # type: ignore pylint: disable=E1101 - assert self._target.exists() # type: ignore pylint: disable=E1101 - self._port = random.randint(1025, 65535) - self._commands = f""" -source {GEF_PATH} -gef config gef.debug True -gef config gef.propagate_debug_exception True -gef config gef.disable_color True -source {RPYC_GEF_PATH} -""" - if COVERAGE_DIR: - self._coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( - "PYTEST_XDIST_WORKER", "gw0" - ) - self._commands += f""" -pi import coverage -pi cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True) -pi cov.start() -""" - - self._commands += f""" -pi start_rpyc_service({self._port}) -""" - - self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) - self._initfile.write(self._commands) - self._initfile.flush() - self._command = [ - "gdb", - "-q", - "-nx", - "-ex", - f"source {self._initfile.name}", - "--", - str(self._target.absolute()), # type: ignore pylint: disable=E1101 - ] - self._process = subprocess.Popen(self._command) - assert self._process.pid > 0 - time.sleep(RPYC_SPAWN_TIME) - self._conn = rpyc.connect( - RPYC_HOST, - self._port, - ) - self._gdb = self._conn.root.gdb - self._gef = self._conn.root.gef - return super().setUp() - - def tearDown(self) -> None: - if COVERAGE_DIR: - self._gdb.execute("pi cov.stop()") - self._gdb.execute("pi cov.save()") - self._conn.close() - self._process.terminate() - return super().tearDown() - class GefUnitTestGeneric(unittest.TestCase): """Generic class for command testing, that defines all helpers""" From 38f258cf63d864420909cc2d17d128edf5986e41 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 6 Jan 2024 13:35:21 -0800 Subject: [PATCH 11/35] updated tests/config --- tests/api/deprecated.py | 2 +- tests/api/gef_arch.py | 8 +-- tests/base.py | 1 + tests/commands/canary.py | 8 +-- tests/config/__init__.py | 108 +++++++++++++++++++++++---------------- tests/utils.py | 3 +- 6 files changed, 75 insertions(+), 55 deletions(-) diff --git a/tests/api/deprecated.py b/tests/api/deprecated.py index 8d7eabb76..cd3b485ba 100644 --- a/tests/api/deprecated.py +++ b/tests/api/deprecated.py @@ -29,4 +29,4 @@ def test_deprecated_elf_values(self): for item in old_stuff: output = gdb.execute(f"pi {item}", to_string=True) - self.assertIn(WARNING_DEPRECATION_MESSAGE, output) + assert WARNING_DEPRECATION_MESSAGE in output diff --git a/tests/api/gef_arch.py b/tests/api/gef_arch.py index 48fb78090..0df2e08aa 100644 --- a/tests/api/gef_arch.py +++ b/tests/api/gef_arch.py @@ -25,8 +25,8 @@ def test_api_gef_arch_ptrsize(self): @pytest.mark.skipif(ARCH != "x86_64", reason=f"Skipped for {ARCH}") def test_api_gef_arch_x86_64(self): arch = self._gef.arch - self.assertEqual(arch.arch, "X86") - self.assertEqual(arch.mode, "64") + assert arch.arch == "X86" + assert arch.mode == "64" self._gdb.execute("start") assert arch.flag_register_to_human(0).startswith("[zero carry parity adjust") @@ -39,5 +39,5 @@ def test_api_gef_arch_x86_64(self): @pytest.mark.skipif(ARCH != "i686", reason=f"Skipped for {ARCH}") def test_api_gef_arch_x86(self): arch = self._gef.arch - self.assertEqual(arch.arch, "X86") - self.assertEqual(arch.mode, "32") + assert arch.arch == "X86" + assert arch.mode == "32" diff --git a/tests/base.py b/tests/base.py index 7fa72758e..0a86216ed 100644 --- a/tests/base.py +++ b/tests/base.py @@ -43,6 +43,7 @@ def setUp(self) -> None: self._coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( "PYTEST_XDIST_WORKER", "gw0" ) + assert self._coverage_file.exists() self._commands += f""" pi import coverage pi cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True) diff --git a/tests/commands/canary.py b/tests/commands/canary.py index 5562516ae..5fb234e45 100644 --- a/tests/commands/canary.py +++ b/tests/commands/canary.py @@ -4,7 +4,7 @@ from tests.utils import ARCH, ERROR_INACTIVE_SESSION_MESSAGE, debug_target, p64 -from tests.utils import RemoteGefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric import pytest class CanaryCommand(RemoteGefUnitTestGeneric): @@ -19,8 +19,8 @@ def test_cmd_canary(self): self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE, self._gdb.execute("canary", to_string=True)) self._gdb.execute("start") res = self._gdb.execute("canary", to_string=True) - self.assertIn("The canary of process", res) - self.assertEqual(self._gef.session.canary[0], self._gef.session.original_canary[0]) + assert "The canary of process" in res + assert self._gef.session.canary[0] == self._gef.session.original_canary[0] @pytest.mark.skipif(ARCH != "x86_64", reason=f"Not implemented for {ARCH}") @@ -30,4 +30,4 @@ def test_overwrite_canary(self): gdb.execute("start") gef.memory.write(gef.arch.canary_address(), p64(0xdeadbeef)) res = gef.memory.read(gef.arch.canary_address(), gef.arch.ptrsize) - self.assertEqual(0xdeadbeef, res) + assert 0xdeadbeef == res diff --git a/tests/config/__init__.py b/tests/config/__init__.py index d2f59d67f..b509dfc12 100644 --- a/tests/config/__init__.py +++ b/tests/config/__init__.py @@ -2,70 +2,88 @@ Test GEF configuration parameters. """ -from tests.utils import gdb_run_cmd, gdb_start_silent_cmd -from tests.utils import GefUnitTestGeneric +from typing import List +import pytest +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import GEF_RIGHT_ARROW -class TestGefConfigUnit(GefUnitTestGeneric): - """Test GEF configuration parameters.""" +class TestGefConfigUnit(RemoteGefUnitTestGeneric): + """Test GEF configuration parameters.""" def test_config_show_opcodes_size(self): """Check opcodes are correctly shown.""" - res = gdb_run_cmd("entry-break", before=["gef config context.show_opcodes_size 4"]) - self.assertNoException(res) - self.assertGreater(len(res.splitlines()), 1) + gdb = self._gdb + + max_opcode_len = 4 + # + # Only show the code pane, limit the opcode size to 4 bytes (8 chars) + # + gdb.execute("gef config context.layout code") + gdb.execute(f"gef config context.show_opcodes_size {max_opcode_len}") - # output format: 0xaddress opcode mnemo [operands, ...] + gdb.execute("start") + lines: List[str] = gdb.execute("context", to_string=True).splitlines()[1:-1] + self.assertGreater(len(lines), 1) + + # + # For each line, check the format + # + # output format: 0xaddress opcode[...] mnemo [operands, ...] # example: 0x5555555546b2 897dec mov DWORD PTR [rbp-0x14], edi - self.assertRegex(res, r"(0x([0-9a-f]{2})+)\s+(([0-9a-f]{2})+)\s+<[^>]+>\s+(.*)") + # + for line in lines: + parts = line.replace(GEF_RIGHT_ARROW, "").split() + opcode: str = parts[1].replace("...", "").lower() + assert ( + len(opcode) <= max_opcode_len * 2 + ), f"Invalid length for {opcode=}: {len(opcode)}" + assert all(map(lambda c: c in "0123456789abcdef", opcode)) def test_config_hook_validator(self): """Check that a GefSetting hook can prevent setting a config.""" - res = gdb_run_cmd("gef config gef.tempdir '/tmp/path with space'") + gdb = self._gdb + + res = gdb.execute( + "gef config gef.tempdir '/tmp/path with space'", to_string=True + ) # Validators just use `err` to print an error - self.assertNoException(res) self.assertRegex(res, r"[!].+Cannot set.+setting cannot contain spaces") - res = gdb_run_cmd("gef config gef.tempdir '/tmp/valid-path'") - self.assertNoException(res) - self.assertNotIn("[!]", res) + gdb.execute("gef config gef.tempdir '/tmp/valid-path'") + res = gdb.execute("gef config gef.tempdir", to_string=True).splitlines()[1] + assert res == 'gef.tempdir (str) = "/tmp/valid-path"' def test_config_type_validator(self): """Check that a GefSetting type can prevent setting a config.""" - res = gdb_run_cmd("gef config gef.debug invalid") - self.assertNoException(res) + gdb = self._gdb + + res = gdb.execute("gef config gef.debug invalid", to_string=True) self.assertRegex(res, r"[!].+expects type 'bool'") - res = gdb_run_cmd("gef config gef.debug true") - self.assertNoException(res) - self.assertNotIn("[!]", res) - res = gdb_run_cmd("gef config gef.debug 1") - self.assertNoException(res) - self.assertNotIn("[!]", res) - res = gdb_run_cmd("gef config gef.debug F") - self.assertNoException(res) - self.assertNotIn("[!]", res) - res = gdb_run_cmd("gef config gef.debug 0") - self.assertNoException(res) - self.assertNotIn("[!]", res) + gdb.execute("gef config gef.debug true") + gdb.execute("gef config gef.debug 1") + gdb.execute("gef config gef.debug F") + gdb.execute("gef config gef.debug 0") + + output = gdb.execute("gef config gef.debug 'fooo'", to_string=True).strip() + assert output == "[!] 'gef.debug' expects type 'bool', got str: reason cannot parse 'fooo' as bool" + def test_config_libc_version(self): """Check setting libc version.""" - res = gdb_run_cmd("gef config gef.libc_version") - self.assertNoException(res) - self.assertNotIn("[!]", res) - - res = gdb_run_cmd("gef config gef.libc_version", before=["gef config gef.libc_version 2.31"]) - self.assertNoException(res) - self.assertNotIn("[!]", res) - self.assertIn('gef.libc_version (str) = "2.31"', res) - - res = gdb_run_cmd("gef config gef.libc_version", before=["gef config gef.libc_version 2.31", "gef config gef.libc_version ''"]) - self.assertNoException(res) - self.assertNotIn("[!]", res) - self.assertIn('gef.libc_version (str) = ""', res) - - res = gdb_start_silent_cmd("python print(gef.libc.version)", before=["gef config gef.libc_version 2.31"]) - self.assertNoException(res) - self.assertNotIn("[!]", res) + gdb = self._gdb + + # + # When starting, should be empty + # + res = gdb.execute("gef config gef.libc_version", to_string=True).splitlines()[1] + assert res == 'gef.libc_version (str) = ""' + + gdb.execute("gef config gef.libc_version 2.31") + gdb.execute("start") + + res = gdb.execute("gef config gef.libc_version 2.31", to_string=True) + res = gdb.execute("gef config gef.libc_version", to_string=True).splitlines()[1] + assert res == 'gef.libc_version (str) = "2.31"' diff --git a/tests/utils.py b/tests/utils.py index e99fca0ba..f2735623b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -34,6 +34,8 @@ GDBSERVER_DEFAULT_HOST = "localhost" GDBSERVER_DEFAULT_PORT = 1234 +GEF_RIGHT_ARROW = " → " + CommandType = Union[str, Iterable[str]] @@ -66,7 +68,6 @@ class GdbAssertionError(AssertionError): pass - class GefUnitTestGeneric(unittest.TestCase): """Generic class for command testing, that defines all helpers""" From d1893a9f7d07dab0532e82dd2e81b43d9de90c60 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 6 Jan 2024 13:53:24 -0800 Subject: [PATCH 12/35] added commands/aliases --- gef.py | 18 ++++++++----- tests/commands/aliases.py | 54 +++++++++++++++++++++++++++------------ 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/gef.py b/gef.py index 32b69f1a3..ef16f6236 100644 --- a/gef.py +++ b/gef.py @@ -10161,11 +10161,11 @@ def __init__(self, alias: str, command: str, completer_class: int = gdb.COMPLETE if not p: return - if any(x for x in gef.session.aliases if x._alias == alias): + if any(x for x in gef.session.aliases if x.alias == alias): return - self._command = command - self._alias = alias + self.command = command + self.alias = alias c = command.split()[0] r = self.lookup_command(c) self.__doc__ = f"Alias for '{Color.greenify(command)}'" @@ -10180,8 +10180,14 @@ def __init__(self, alias: str, command: str, completer_class: int = gdb.COMPLETE gef.session.aliases.append(self) return + def __repr__(self) -> str: + return f"GefAlias(from={self.alias}, to={self.command})" + + def __str__(self) -> str: + return f"GefAlias(from={self.alias}, to={self.command})" + def invoke(self, args: Any, from_tty: bool) -> None: - gdb.execute(f"{self._command} {args}", from_tty=from_tty) + gdb.execute(f"{self.command} {args}", from_tty=from_tty) return def lookup_command(self, cmd: str) -> Optional[Tuple[str, GenericCommand]]: @@ -10246,7 +10252,7 @@ def do_invoke(self, argv: List[str]) -> None: self.usage() return try: - alias_to_remove = next(filter(lambda x: x._alias == argv[0], gef.session.aliases)) + alias_to_remove = next(filter(lambda x: x.alias == argv[0], gef.session.aliases)) gef.session.aliases.remove(alias_to_remove) except (ValueError, StopIteration): err(f"{argv[0]} not found in aliases.") @@ -10269,7 +10275,7 @@ def __init__(self) -> None: def do_invoke(self, _: List[str]) -> None: ok("Aliases defined:") for a in gef.session.aliases: - gef_print(f"{a._alias:30s} {RIGHT_ARROW} {a._command}") + gef_print(f"{a.alias:30s} {RIGHT_ARROW} {a.command}") return diff --git a/tests/commands/aliases.py b/tests/commands/aliases.py index e4d4e2293..5cf564343 100644 --- a/tests/commands/aliases.py +++ b/tests/commands/aliases.py @@ -2,26 +2,46 @@ `aliases` command test module """ +from tests.base import RemoteGefUnitTestGeneric -from tests.utils import gdb_start_silent_cmd -from tests.utils import GefUnitTestGeneric - -class AliasesCommand(GefUnitTestGeneric): +class AliasesCommand(RemoteGefUnitTestGeneric): """`aliases` command test module""" - def test_cmd_aliases(self): - # test add functionality - add_res = gdb_start_silent_cmd("aliases add alias_function_test example") - self.assertNoException(add_res) + def test_cmd_aliases_add(self): + gdb = self._gdb + gef = self._gef + + initial_nb = len(gef.session.aliases) + gdb.execute("aliases add alias_function_test example") + assert initial_nb == len(gef.session.aliases) - 1 + + def test_cmd_aliases_list(self): + gdb = self._gdb + gef = self._gef + + gdb.execute("aliases add alias_function_test example") # test list functionality - list_res = gdb_start_silent_cmd("aliases ls", - before=["aliases add alias_function_test example"]) - self.assertNoException(list_res) - self.assertIn("alias_function_test", list_res) + list_res = gdb.execute("aliases ls", to_string=True) + assert "alias_function_test" in list_res + + matches = [x for x in gef.session.aliases if x.alias == "alias_function_test"] + assert len(matches) == 1 + assert matches[0].command == "example" + + def test_cmd_aliases_rm(self): + gdb = self._gdb + gef = self._gef + + gdb.execute("aliases add alias_function_test example") + matches = [x for x in gef.session.aliases if x.alias == "alias_function_test"] + assert len(matches) == 1 + assert matches[0].command == "example" + # test rm functionality - rm_res = gdb_start_silent_cmd("aliases ls", - before=["aliases add alias_function_test example", - "aliases rm alias_function_test"]) - self.assertNoException(rm_res) - self.assertNotIn("alias_function_test", rm_res) + gdb.execute("aliases rm alias_function_test") + rm_res = gdb.execute("aliases ls", to_string=True).splitlines() + assert any(map(lambda line: "alias_function_test" not in line, rm_res)) + + matches = [x for x in gef.session.aliases if x.alias == "alias_function_test"] + assert len(matches) == 0 From 6b509441b6b83534626891ca11f7d9905079391d Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 6 Jan 2024 14:07:41 -0800 Subject: [PATCH 13/35] added commands/alsr --- tests/commands/aslr.py | 57 ++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/tests/commands/aslr.py b/tests/commands/aslr.py index b5b298894..e5370cbea 100644 --- a/tests/commands/aslr.py +++ b/tests/commands/aslr.py @@ -3,48 +3,45 @@ """ -from tests.utils import GefUnitTestGeneric, findlines, gdb_start_silent_cmd, removeuntil +from tests.base import RemoteGefUnitTestGeneric -class AslrCommand(GefUnitTestGeneric): +class AslrCommand(RemoteGefUnitTestGeneric): """`aslr` command test module""" - cmd = "aslr" + def __is_alsr_on_gdb(self): + gdb = self._gdb + gdb_output = gdb.execute("show disable-randomization", to_string=True).strip() + return gdb_output.endswith("off.") # i.e. disabled def test_cmd_aslr_show(self): - # show - res = gdb_start_silent_cmd(self.cmd, after=("show disable-randomization",)) - self.assertNoException(res) - self.assertIn("ASLR is currently ", res) - self.assertEqual(res.count("ASLR is currently "), 1) - - # compare - pattern = "ASLR is currently " - cmd_output = removeuntil(pattern, findlines(pattern, res)[0]) - pattern = "virtual address space is " - gdb_output = findlines(pattern, res)[0].split()[-1] - if gdb_output == "on.": - self.assertEqual(cmd_output, "disabled") - else: - self.assertEqual(cmd_output, "enabled") + gdb = self._gdb + + gef_output = gdb.execute(self.cmd, to_string=True).strip() + # basic check + if self.__is_alsr_on_gdb(): + assert gef_output == "ASLR is currently enabled" + else: + assert gef_output == "ASLR is currently disabled" def test_cmd_aslr_toggle(self): + gdb = self._gdb + # current value - res = gdb_start_silent_cmd(self.cmd) - pattern = "ASLR is currently " - default_value = removeuntil(pattern, findlines(pattern, res)[0]) + enabled = self.__is_alsr_on_gdb() # toggle - if default_value == "enabled": - res = gdb_start_silent_cmd(f"{self.cmd} off", after=(f"{self.cmd}")) - cmd_output = removeuntil(pattern, findlines(pattern, res)[0]) - self.assertEqual(cmd_output, "disabled") - elif default_value == "disabled": - res = gdb_start_silent_cmd(f"{self.cmd} on", after=(f"{self.cmd}")) - cmd_output = removeuntil(pattern, findlines(pattern, res)[0]) - self.assertEqual(cmd_output, "enabled") + if enabled: + # switch off and check + gdb.execute(f"{self.cmd} off") + res = gdb.execute(self.cmd, to_string=True).strip() + assert res == "ASLR is currently disabled" + else: - raise Exception(f"incorrect value: {default_value}") + # switch on and check + gdb.execute(f"{self.cmd} on") + res = gdb.execute(self.cmd, to_string=True).strip() + assert res == "ASLR is currently enabled" From 145cd89a918661438dac7266dd48b2d31bc5dae9 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 13:11:23 -0800 Subject: [PATCH 14/35] added tests/functions --- tests/functions/elf_sections.py | 97 ++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/tests/functions/elf_sections.py b/tests/functions/elf_sections.py index 0dcdebff9..c3f114373 100644 --- a/tests/functions/elf_sections.py +++ b/tests/functions/elf_sections.py @@ -3,79 +3,102 @@ """ -from tests.utils import debug_target, gdb_run_cmd, gdb_run_silent_cmd, gdb_start_silent_cmd, is_64b -from tests.utils import GefUnitTestGeneric +import pytest +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE, debug_target, is_64b +from tests.base import RemoteGefUnitTestGeneric -class ElfSectionGdbFunction(GefUnitTestGeneric): - """GDB functions test module""" +class ElfSectionGdbFunction(RemoteGefUnitTestGeneric): + """GDB functions test module""" def test_func_base(self): """`$_base()` GDB function test""" + gdb = self._gdb cmd = "x/s $_base()" - self.assertFailIfInactiveSession(gdb_run_cmd(cmd)) - res = gdb_start_silent_cmd(cmd) - self.assertNoException(res) + + with pytest.raises(Exception, match="No debugging session active"): + gdb.execute(cmd) + + gdb.execute("start") + res = gdb.execute(cmd, to_string=True) self.assertIn("\\177ELF", res) addr = res.splitlines()[-1].split()[0][:-1] - cmd = "x/s $_base(\"libc\")" - res = gdb_start_silent_cmd(cmd) - self.assertNoException(res) + cmd = 'x/s $_base("libc")' + res = gdb.execute(cmd, to_string=True) self.assertIn("\\177ELF", res) addr2 = res.splitlines()[-1].split()[0][:-1] self.assertNotEqual(addr, addr2) + def test_func_stack(self): + """`$_stack()` GDB function test""" + gdb = self._gdb + cmd = "deref $_stack()" + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + gdb.execute("start") + res = gdb.execute(cmd, to_string=True) + if is_64b(): + self.assertRegex(res, r"\+0x0*20: *0x0000000000000000\n") + else: + self.assertRegex(res, r"\+0x0.*20: *0x00000000\n") + + +class ElfSectionGdbFunctionBss(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = debug_target("bss") + return super().setUp() def test_func_bss(self): """`$_bss()` GDB function test""" + gdb = self._gdb cmd = "deref $_bss()" - target = debug_target("bss") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) self.assertIn("Hello world!", res) +class ElfSectionGdbFunctionHeap(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = debug_target("heap") + return super().setUp() + def test_func_got(self): """`$_got()` GDB function test""" + gdb = self._gdb cmd = "deref $_got()" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) - self.assertIn("malloc", res) + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) + self.assertIn("malloc", res) def test_func_heap(self): """`$_heap()` GDB function test""" + gdb = self._gdb cmd = "deref $_heap()" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) if is_64b(): self.assertIn("+0x0048:", res) else: self.assertIn("+0x0024:", res) cmd = "deref $_heap(0x10+0x10)" - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) if is_64b(): self.assertIn("+0x0048:", res) else: self.assertIn("+0x0024:", res) - - - def test_func_stack(self): - """`$_stack()` GDB function test""" - cmd = "deref $_stack()" - self.assertFailIfInactiveSession(gdb_run_cmd(cmd)) - res = gdb_start_silent_cmd(cmd) - self.assertNoException(res) - if is_64b(): - self.assertRegex(res, r"\+0x0*20: *0x0000000000000000\n") - else: - self.assertRegex(res, r"\+0x0.*20: *0x00000000\n") From 72037307254f98274758b598119fdc0c190cde49 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 13:19:12 -0800 Subject: [PATCH 15/35] ported tests/commands (failing) --- tests/commands/canary.py | 11 +- tests/commands/checksec.py | 53 ++- tests/commands/context.py | 4 +- tests/commands/dereference.py | 74 ++-- tests/commands/edit_flags.py | 63 ++- tests/commands/elf_info.py | 8 +- tests/commands/entry_break.py | 13 +- tests/commands/format_string_helper.py | 24 +- tests/commands/functions.py | 11 +- tests/commands/gef.py | 93 ++-- tests/commands/gef_remote.py | 69 +-- tests/commands/got.py | 19 +- tests/commands/heap.py | 362 +++++++++------- tests/commands/heap_analysis.py | 22 +- tests/commands/hexdump.py | 23 +- tests/commands/highlight.py | 21 +- tests/commands/hijack_fd.py | 8 +- tests/commands/ksymaddr.py | 10 +- tests/commands/memory.py | 109 +++-- tests/commands/name_break.py | 18 +- tests/commands/nop.py | 400 ++++++++---------- tests/commands/patch.py | 72 ++-- tests/commands/pattern.py | 82 ++-- tests/commands/pcustom.py | 53 +-- tests/commands/pie.py | 44 +- tests/commands/print_format.py | 55 +-- tests/commands/process_search.py | 41 +- tests/commands/process_status.py | 17 +- tests/commands/registers.py | 32 +- tests/commands/reset_cache.py | 11 +- tests/commands/scan.py | 24 +- tests/commands/search_pattern.py | 37 +- tests/commands/shellcode.py | 28 +- tests/commands/skipi.py | 67 ++- tests/commands/smart_eval.py | 12 +- tests/commands/stub.py | 28 +- tests/commands/theme.py | 20 +- tests/commands/trace_run.py | 19 +- tests/commands/version.py | 10 +- tests/commands/vmmap.py | 21 +- tests/commands/xfiles.py | 17 +- tests/commands/xinfo.py | 32 +- tests/commands/xor_memory.py | 26 +- tests/regressions/gdbserver_connection.py | 2 +- tests/regressions/registers_register_order.py | 5 +- tests/utils.py | 18 +- 46 files changed, 1193 insertions(+), 995 deletions(-) diff --git a/tests/commands/canary.py b/tests/commands/canary.py index 5fb234e45..e6456dbb3 100644 --- a/tests/commands/canary.py +++ b/tests/commands/canary.py @@ -3,9 +3,8 @@ """ -from tests.utils import ARCH, ERROR_INACTIVE_SESSION_MESSAGE, debug_target, p64 +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE, debug_target, p64, p32, is_64b from tests.base import RemoteGefUnitTestGeneric -import pytest class CanaryCommand(RemoteGefUnitTestGeneric): """`canary` command test module""" @@ -16,18 +15,20 @@ def setUp(self) -> None: def test_cmd_canary(self): - self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE, self._gdb.execute("canary", to_string=True)) + assert ERROR_INACTIVE_SESSION_MESSAGE == self._gdb.execute("canary", to_string=True) self._gdb.execute("start") res = self._gdb.execute("canary", to_string=True) assert "The canary of process" in res assert self._gef.session.canary[0] == self._gef.session.original_canary[0] - @pytest.mark.skipif(ARCH != "x86_64", reason=f"Not implemented for {ARCH}") def test_overwrite_canary(self): gdb, gef = self._gdb, self._gef gdb.execute("start") - gef.memory.write(gef.arch.canary_address(), p64(0xdeadbeef)) + if is_64b(): + gef.memory.write(gef.arch.canary_address(), p64(0xdeadbeef)) + else: + gef.memory.write(gef.arch.canary_address(), p32(0xdeadbeef)) res = gef.memory.read(gef.arch.canary_address(), gef.arch.ptrsize) assert 0xdeadbeef == res diff --git a/tests/commands/checksec.py b/tests/commands/checksec.py index 4b2a194ba..087f20eb2 100644 --- a/tests/commands/checksec.py +++ b/tests/commands/checksec.py @@ -2,29 +2,46 @@ checksec command test module """ -from tests.utils import ( - gdb_run_cmd, - debug_target, - GefUnitTestGeneric -) +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import debug_target -class ChecksecCommand(GefUnitTestGeneric): +class ChecksecCommandNoCanary(RemoteGefUnitTestGeneric): """`checksec` command test module""" + def setUp(self) -> None: + self._target = debug_target("checksec-no-canary") + return super().setUp() + + def test_cmd_checksec(self): + gdb = self._gdb + gef = self._gef + res = gdb.execute("checksec", to_string=True) + assert "Canary : ✘" in res + assert gef.binary.checksec["Canary"] == False + + +class ChecksecCommandNoNx(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = debug_target("checksec-no-nx") + return super().setUp() + def test_cmd_checksec(self): - cmd = "checksec" - res = gdb_run_cmd(cmd) - self.assertNoException(res) + gdb = self._gdb + gef = self._gef + res = gdb.execute("checksec", to_string=True) + assert "NX : ✘" in res + assert gef.binary.checksec["NX"] == False - target = debug_target("checksec-no-canary") - res = gdb_run_cmd(cmd, target=target) - self.assertIn("Canary : ✘", res) - target = debug_target("checksec-no-nx") - res = gdb_run_cmd(cmd, target=target) - self.assertIn("NX : ✘", res) +class ChecksecCommandNoPie(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = debug_target("checksec-no-pie") + return super().setUp() - target = debug_target("checksec-no-pie") - res = gdb_run_cmd(cmd, target=target) - self.assertIn("PIE : ✘", res) + def test_cmd_checksec(self): + gdb = self._gdb + gef = self._gef + res = gdb.execute("checksec", to_string=True) + assert "PIE : ✘" in res + assert gef.binary.checksec["PIE"] == False diff --git a/tests/commands/context.py b/tests/commands/context.py index cec52f238..04929089b 100644 --- a/tests/commands/context.py +++ b/tests/commands/context.py @@ -3,10 +3,10 @@ """ -from tests.utils import GefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric -class ContextCommand(GefUnitTestGeneric): +class ContextCommand(RemoteGefUnitTestGeneric): """`context` command test module""" diff --git a/tests/commands/dereference.py b/tests/commands/dereference.py index 79bb19068..57d7b62fb 100644 --- a/tests/commands/dereference.py +++ b/tests/commands/dereference.py @@ -2,38 +2,46 @@ dereference command test module """ +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE -from tests.utils import gdb_run_cmd, gdb_start_silent_cmd -from tests.utils import GefUnitTestGeneric - -class DereferenceCommand(GefUnitTestGeneric): +class DereferenceCommand(RemoteGefUnitTestGeneric): """`dereference` command test module""" - def test_cmd_dereference(self): - self.assertFailIfInactiveSession(gdb_run_cmd("dereference")) + gdb = self._gdb + + assert ( + gdb.execute("dereference", to_string=True) == ERROR_INACTIVE_SESSION_MESSAGE + ) + + gdb.execute("start") - res = gdb_start_silent_cmd("dereference $sp") - self.assertNoException(res) + res = gdb.execute("dereference $sp", to_string=True) self.assertTrue(len(res.splitlines()) > 2) - res = gdb_start_silent_cmd("dereference 0x0") - self.assertNoException(res) + res = gdb.execute("dereference 0x0", to_string=True) self.assertIn("Unmapped address", res) - def test_cmd_dereference_forwards(self): - self.assertFailIfInactiveSession(gdb_run_cmd("dereference")) + gdb = self._gdb - cmd = "dereference $sp -l 2" - setup = [ + assert ( + gdb.execute("dereference", to_string=True) == ERROR_INACTIVE_SESSION_MESSAGE + ) + + gdb.execute("start") + + for setup in [ "gef config context.grow_stack_down False", "set {char[9]} ($sp+0x8) = { 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00 }", - "set {char[9]} ($sp-0x8) = { 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x00 }" - ] - res = gdb_start_silent_cmd(cmd=setup, after=cmd) - self.assertNoException(res) + "set {char[9]} ($sp-0x8) = { 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x00 }", + ]: + gdb.execute(setup) + + cmd = "dereference $sp -l 2" + res = gdb.execute(cmd, to_string=True) """ Assuming the default config of grow_stack_down = False, $sp should look like this: @@ -42,21 +50,27 @@ def test_cmd_dereference_forwards(self): Hence, we want to look at the last line of the output """ res = res.splitlines()[-1] - self.assertIn("AAAAAAAA", res) - self.assertNotIn("BBBBBBBB", res) - + assert "AAAAAAAA" in res + assert "BBBBBBBB" not in res def test_cmd_dereference_backwards(self): - self.assertFailIfInactiveSession(gdb_run_cmd("dereference")) + gdb = self._gdb - cmd = "dereference $sp -l -2" - setup = [ + assert ( + gdb.execute("dereference", to_string=True) == ERROR_INACTIVE_SESSION_MESSAGE + ) + + gdb.execute("start") + + for setup in [ "gef config context.grow_stack_down False", "set {char[9]} ($sp+0x8) = { 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00 }", - "set {char[9]} ($sp-0x8) = { 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x00 }" - ] - res = gdb_start_silent_cmd(cmd=setup, after=cmd) - self.assertNoException(res) + "set {char[9]} ($sp-0x8) = { 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x00 }", + ]: + gdb.execute(setup) + + cmd = "dereference $sp -l -2" + res = gdb.execute(cmd, to_string=True) """ Assuming the default config of grow_stack_down = False, $sp should look like this: @@ -65,5 +79,5 @@ def test_cmd_dereference_backwards(self): Hence, we want to look at the second last line of the output """ res = res.splitlines()[-2] - self.assertNotIn("AAAAAAAA", res) - self.assertIn("BBBBBBBB", res) + assert "AAAAAAAA" not in res + assert "BBBBBBBB" in res diff --git a/tests/commands/edit_flags.py b/tests/commands/edit_flags.py index f2e0d1621..ae521591d 100644 --- a/tests/commands/edit_flags.py +++ b/tests/commands/edit_flags.py @@ -5,48 +5,45 @@ import pytest -from tests.utils import ( - ARCH, - GefUnitTestGeneric, - gdb_start_silent_cmd_last_line, - gdb_start_silent_cmd, -) +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ARCH -@pytest.mark.skipif(ARCH not in ["i686", "x86_64", "armv7l", "aarch64"], - reason=f"Skipped for {ARCH}") -class EditFlagsCommand(GefUnitTestGeneric): +@pytest.mark.skipif(ARCH in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") +class EditFlagsCommand(RemoteGefUnitTestGeneric): """`edit-flags` command test module""" - def setUp(self) -> None: - res = gdb_start_silent_cmd_last_line("edit-flags") - self.assertNoException(res) - flags = res[1:-1].split() - self.flag_name = "carry" - self.initial_value = [f for f in flags if f.lower() == self.flag_name][0] - return super().setUp() + def test_cmd_edit_flags_disable(self): + gdb = self._gdb + gef = self._gef + with pytest.raises(gdb.error): + gdb.execute("edit-flags") - def test_cmd_edit_flags_disable(self): - res = gdb_start_silent_cmd_last_line("edit-flags", - after=(f"edit-flags +{self.flag_name}", - f"edit-flags -{self.flag_name}")) - self.assertNoException(res) - self.assertIn(self.flag_name.lower(), res) + gdb.execute("start") + res: str = gdb.execute("edit-flags", to_string=True).strip() + assert res.startswith("[") and res.endswith("]") + # pick first flag + idx, name = next(gef.arch.flags_table) + gdb.execute(f"edit-flags -{name}") + assert gef.arch.register(gef.arch.flag_register) & (1 << idx) == 0 def test_cmd_edit_flags_enable(self): - res = gdb_start_silent_cmd("edit-flags", - after=(f"edit-flags -{self.flag_name}", - f"edit-flags +{self.flag_name}")) - self.assertNoException(res) - self.assertIn(self.flag_name.upper(), res) + gdb = self._gdb + gef = self._gef + gdb.execute("start") + idx, name = next(gef.arch.flags_table) + gdb.execute(f"edit-flags +{name}") + assert gef.arch.register(gef.arch.flag_register) & (1 << idx) != 0 def test_cmd_edit_flags_toggle(self): - res = gdb_start_silent_cmd_last_line(f"edit-flags ~{self.flag_name}") - self.assertNoException(res) - if self.initial_value == self.flag_name.upper(): - self.assertIn(self.flag_name.lower(), res) - else: - self.assertIn(self.flag_name.upper(), res) + gdb = self._gdb + gef = self._gef + + idx, name = next(gef.arch.flags_table) + init_val = gef.arch.register(gef.arch.flag_register) & (1 << idx) + gdb.execute(f"edit-flags ~{name}") + new_val = gef.arch.register(gef.arch.flag_register) & (1 << idx) + assert init_val != new_val diff --git a/tests/commands/elf_info.py b/tests/commands/elf_info.py index 1a6708dd3..91901b9b5 100644 --- a/tests/commands/elf_info.py +++ b/tests/commands/elf_info.py @@ -3,14 +3,14 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd +from tests.base import RemoteGefUnitTestGeneric -class ElfInfoCommand(GefUnitTestGeneric): +class ElfInfoCommand(RemoteGefUnitTestGeneric): """`elf-info` command test module""" def test_cmd_elf_info(self): - res = gdb_run_cmd("elf-info") - self.assertNoException(res) + gdb = self._gdb + res = gdb.execute("elf-info", to_string=True) self.assertIn("7f 45 4c 46", res) diff --git a/tests/commands/entry_break.py b/tests/commands/entry_break.py index 34ad43dc3..8a4250a9a 100644 --- a/tests/commands/entry_break.py +++ b/tests/commands/entry_break.py @@ -3,17 +3,14 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd +from tests.base import RemoteGefUnitTestGeneric -class EntryBreakCommand(GefUnitTestGeneric): + +class EntryBreakCommand(RemoteGefUnitTestGeneric): """`entry-break` command test module""" def test_cmd_entry_break(self): - res = gdb_run_cmd("entry-break") - self.assertNoException(res) - - res = gdb_run_cmd("entry-break", after=("entry-break",)) - self.assertNoException(res) - self.assertIn("gdb is already running", res) + res = self._gdb.execute("entry-break", to_string=True).strip() + assert "gdb is already running" in res diff --git a/tests/commands/format_string_helper.py b/tests/commands/format_string_helper.py index 7fb6b0821..3218631ce 100644 --- a/tests/commands/format_string_helper.py +++ b/tests/commands/format_string_helper.py @@ -3,20 +3,22 @@ """ -from tests.utils import debug_target, gdb_run_cmd -from tests.utils import GefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import debug_target -class FormatStringHelperCommand(GefUnitTestGeneric): + +class FormatStringHelperCommand(RemoteGefUnitTestGeneric): """`format-string-helper` command test module""" + def setUp(self) -> None: + self._target = debug_target("format-string-helper") + return super().setUp() def test_cmd_format_string_helper(self): - cmd = "format-string-helper" - target = debug_target("format-string-helper") - res = gdb_run_cmd(cmd, - after=["set args testtest", - "run",], - target=target) - self.assertNoException(res) - self.assertIn("Possible insecure format string:", res) + gdb = self._gdb + + gdb.execute("set args testtest") + gdb.execute("run") + res = gdb.execute("format-string-helper", to_string=True) + assert "Possible insecure format string:" in res diff --git a/tests/commands/functions.py b/tests/commands/functions.py index dbdd7fe7e..82f5a504a 100644 --- a/tests/commands/functions.py +++ b/tests/commands/functions.py @@ -3,16 +3,13 @@ """ -from tests.utils import gdb_run_cmd -from tests.utils import GefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric -class FunctionsCommand(GefUnitTestGeneric): +class FunctionsCommand(RemoteGefUnitTestGeneric): """`functions` command test module""" - def test_cmd_functions(self): - cmd = "functions" - res = gdb_run_cmd(cmd) - self.assertNoException(res) + gdb = self._gdb + res = gdb.execute("functions", to_string=True) self.assertIn("$_heap", res) diff --git a/tests/commands/gef.py b/tests/commands/gef.py index e35ba20cd..27398e5de 100644 --- a/tests/commands/gef.py +++ b/tests/commands/gef.py @@ -5,27 +5,21 @@ import pytest import pathlib -from tests.utils import ( - gdb_run_cmd, - GefUnitTestGeneric, - gdb_start_silent_cmd_last_line, - removeuntil, -) +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import removeuntil -class GefCommand(GefUnitTestGeneric): +class GefCommand(RemoteGefUnitTestGeneric): """`gef` command test module""" - def test_cmd_gef(self): - res = gdb_run_cmd("gef") - self.assertNoException(res) + gdb = self._gdb + res = gdb.execute("gef", to_string=True) self.assertIn("GEF - GDB Enhanced Features", res) - def test_cmd_gef_config(self): - res = gdb_run_cmd("gef config") - self.assertNoException(res) + gdb = self._gdb + res = gdb.execute("gef config", to_string=True) self.assertIn("GEF configuration settings", res) known_patterns = ( @@ -43,27 +37,27 @@ def test_cmd_gef_config(self): for pattern in known_patterns: self.assertIn(pattern, res) - def test_cmd_gef_config_get(self): - res = gdb_run_cmd("gef config gef.debug") - self.assertNoException(res) + gdb = self._gdb + res = gdb.execute("gef config gef.debug") self.assertIn("GEF configuration setting: gef.debug", res) # the `True` is automatically set by `gdb_run_cmd` so we know it's there - self.assertIn("""gef.debug (bool) = True\n\nDescription:\n\tEnable debug mode for gef""", - res) - + self.assertIn( + """gef.debug (bool) = True\n\nDescription:\n\tEnable debug mode for gef""", + res, + ) def test_cmd_gef_config_set(self): - res = gdb_start_silent_cmd_last_line("gef config gef.debug 0", - after=("pi print(is_debug())", )) - self.assertNoException(res) - self.assertEqual("False", res) + gdb = self._gdb + root = self._conn.root + assert root.is_debug() == True + gdb.execute("gef config gef.debug 0") + assert root.is_debug() == False def test_cmd_gef_help(self): - res = gdb_run_cmd("help gef") - self.assertNoException(res) - + gdb = self._gdb + res = gdb.execute("help gef", to_string=True) known_patterns = ( "gef config", "gef help", @@ -77,46 +71,53 @@ def test_cmd_gef_help(self): for pattern in known_patterns: self.assertIn(pattern, res) - def test_cmd_gef_run_and_run(self): - res = gdb_run_cmd("gef set args $_gef0", - before=("pattern create -n 4", ), - after=("show args")) - self.assertNoException(res) - self.assertIn("aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaan", res) - - res = gdb_run_cmd("gef set args $_gef42", - before=("pattern create -n 4", ), - after=("show args")) - self.assertException(res) + gdb = self._gdb + + # valid + pattern = gdb.execute("pattern create -n 4", to_string=True).splitlines()[2] + assert len(pattern) == 1024 + res = gdb.execute("gef set args $_gef0") + res = gdb.execute("show args", to_string=True) + assert ( + res + == f'Argument list to give program being debugged when it is started is "{pattern}".' + ) + # invalid + with pytest.raises(Exception): + gdb.execute("gef set args $_gef42") def test_cmd_gef_save(self): + root = self._conn.root + gdb = self._gdb + # check - res = gdb_run_cmd("gef save") - self.assertNoException(res) + res = gdb.execute("gef save", to_string=True) self.assertIn("Configuration saved to '", res) gefrc_file = removeuntil("Configuration saved to '", res.rstrip("'")) + assert gefrc_file == root.eval("str(GEF_RC)") # set & check for name in ("AAAABBBBCCCCDDDD", "gef"): - res = gdb_run_cmd("gef save", before=(f"gef config gef.tempdir /tmp/{name}", )) - self.assertNoException(res) + gdb.execute(f"gef config gef.tempdir /tmp/{name}") + res = gdb.execute("gef save") with pathlib.Path(gefrc_file).open() as f: config = f.read() - self.assertIn(f'tempdir = /tmp/{name}\n', config) - + self.assertIn(f"tempdir = /tmp/{name}\n", config) @pytest.mark.online def test_cmd_gef_install(self): + gdb = self._gdb test_commands = ("skel", "windbg", "stack") - res = gdb_run_cmd(f"gef install {' '.join(test_commands)}") - self.assertNoException(res) + res = gdb.execute(f"gef install {' '.join(test_commands)}", to_string=True) # we install 3 plugins, the pattern must be found 3 times pattern = "Installed file" for i in range(len(test_commands)): idx = res.find(pattern) - self.assertNotEqual(-1, idx, f"Check {i}/{3} failed: missing '{pattern}' in\n{res}") + self.assertNotEqual( + -1, idx, f"Check {i}/{3} failed: missing '{pattern}' in\n{res}" + ) self.assertIn("new command(s) available", res) res = res[idx:] diff --git a/tests/commands/gef_remote.py b/tests/commands/gef_remote.py index 5627b3e26..7884994ca 100644 --- a/tests/commands/gef_remote.py +++ b/tests/commands/gef_remote.py @@ -2,54 +2,65 @@ `gef_remote` command test module """ +import random +import pytest + +from tests.base import RemoteGefUnitTestGeneric from tests.utils import ( - GefUnitTestGeneric, debug_target, - gdb_run_cmd, gdbserver_session, qemuuser_session, GDBSERVER_DEFAULT_HOST, - GDBSERVER_DEFAULT_PORT, ) -class GefRemoteCommand(GefUnitTestGeneric): +class GefRemoteCommand(RemoteGefUnitTestGeneric): """`gef_remote` command test module""" + def setUp(self) -> None: + self._target = debug_target("default") + return super().setUp() def test_cmd_gef_remote(self): - port = GDBSERVER_DEFAULT_PORT + 1 - before = [f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}"] - with gdbserver_session(port=port) as _: - res = gdb_run_cmd( - "pi print(gef.session.remote)", before=before) - self.assertNoException(res) + gdb = self._gdb + while True: + port = random.randint(1025, 65535) + if port != self._port: break + + gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") + + with gdbserver_session(port=port): + res = gdb.execute( + "pi print(gef.session.remote)", to_string=True) self.assertIn( f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/tmp/", res) self.assertIn(", qemu_user=False)", res) - + @pytest.mark.slow def test_cmd_gef_remote_qemu_user(self): - port = GDBSERVER_DEFAULT_PORT + 2 - target = debug_target("default") - before = [ - f"gef-remote --qemu-user --qemu-binary {target} {GDBSERVER_DEFAULT_HOST} {port}"] - with qemuuser_session(port=port) as _: - res = gdb_run_cmd( - "pi print(gef.session.remote)", before=before) - self.assertNoException(res) - self.assertIn( - f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/tmp/", res) - self.assertIn(", qemu_user=True)", res) + gdb = self._gdb + while True: + port = random.randint(1025, 65535) + if port != self._port: break + + gdb.execute( + f"gef-remote --qemu-user --qemu-binary {self._target} {GDBSERVER_DEFAULT_HOST} {port}") + + with qemuuser_session(port=port): + res = gdb.execute( + "pi print(gef.session.remote)", to_string=True) + assert f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/tmp/" in res + assert ", qemu_user=True)" in res def test_cmd_target_remote(self): - port = GDBSERVER_DEFAULT_PORT + 3 - before = [f"target remote {GDBSERVER_DEFAULT_HOST}:{port}"] + gdb = self._gdb + while True: + port = random.randint(1025, 65535) + if port != self._port: break + + gdb.execute(f"target remote {GDBSERVER_DEFAULT_HOST}:{port}") with gdbserver_session(port=port) as _: - res = gdb_run_cmd( - "pi print(gef.session.remote)", before=before) - self.assertNoException(res) - self.assertIn( - f"RemoteSession(target=':0', local='/tmp/", res) + res = gdb.execute("pi print(gef.session.remote)", to_string=True) + self.assertIn(f"RemoteSession(target=':0', local='/tmp/", res) self.assertIn(", qemu_user=False)", res) diff --git a/tests/commands/got.py b/tests/commands/got.py index e4eafdd66..470f7f898 100644 --- a/tests/commands/got.py +++ b/tests/commands/got.py @@ -4,28 +4,31 @@ import pytest +from tests.base import RemoteGefUnitTestGeneric + from tests.utils import ( ARCH, + ERROR_INACTIVE_SESSION_MESSAGE, debug_target, - gdb_run_cmd, - gdb_start_silent_cmd, - GefUnitTestGeneric, ) @pytest.mark.skipif(ARCH in ("ppc64le",), reason=f"Skipped for {ARCH}") -class GotCommand(GefUnitTestGeneric): +class GotCommand(RemoteGefUnitTestGeneric): """`got` command test module""" def test_cmd_got(self): - cmd = "got" + gdb = self._gdb + target = debug_target("format-string-helper") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_start_silent_cmd(cmd, target=target) + self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE,gdb.execute("got", to_string=True)) + + gdb.execute("start") + res = gdb.execute("got", to_string=True) self.assertIn("printf", res) self.assertIn("strcpy", res) - res = gdb_start_silent_cmd("got printf", target=target) + res = gdb.execute("got printf", target=target, to_string=True) self.assertIn("printf", res) self.assertNotIn("strcpy", res) diff --git a/tests/commands/heap.py b/tests/commands/heap.py index bd5314945..483ab23ae 100644 --- a/tests/commands/heap.py +++ b/tests/commands/heap.py @@ -2,80 +2,174 @@ Heap commands test module """ -from tests.utils import (ARCH, GefUnitTestGeneric, debug_target, findlines, - gdb_run_cmd, gdb_run_silent_cmd, gdb_start_silent_cmd, - is_32b, is_64b) - - -class HeapCommand(GefUnitTestGeneric): +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ( + ARCH, + ERROR_INACTIVE_SESSION_MESSAGE, + debug_target, + findlines, + is_32b, + is_64b, +) + + +class HeapCommand(RemoteGefUnitTestGeneric): """Generic class for command testing, that defines all helpers""" + def setUp(self) -> None: - # ensure those values reflects the allocations in the C source - self.expected_tcache_bin_size = 0x20 if ARCH == "i686" or is_64b() else 0x18 - self.expected_small_bin_size = 0x20 if ARCH == "i686" or is_64b() else 0x18 - self.expected_large_bin_size = 0x420 if ARCH == "i686" or is_64b() else 0x418 - self.expected_unsorted_bin_size = 0x430 if ARCH == "i686" or is_64b() else 0x428 + self._target = debug_target("heap") return super().setUp() - def test_cmd_heap_arenas(self): + gdb = self._gdb cmd = "heap arenas" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_start_silent_cmd(cmd, target=target) - self.assertNoException(res) - self.assertIn("Arena(base=", res) + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + gdb.execute("start") + res = gdb.execute(cmd, to_string=True) + self.assertIn("Arena(base=", res) def test_cmd_heap_set_arena(self): + gdb = self._gdb cmd = "heap set-arena &main_arena" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target, after=["heap arenas"]) - self.assertNoException(res) - self.assertIn("Arena(base=", res) + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + gdb.execute("run") + gdb.execute(cmd) + res = gdb.execute("heap arenas") + self.assertIn("Arena(base=", res) def test_cmd_heap_chunk_no_arg(self): + gdb = self._gdb cmd = "heap chunk p1" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) - self.assertIn("PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA", res) + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) + self.assertIn("PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA", res) def test_cmd_heap_chunk_with_number(self): - target = debug_target("heap") + gdb = self._gdb cmd = "heap chunk --number 2 p1" - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + + gdb.execute("run") + res: str = gdb.execute(cmd, to_string=True) chunklines = findlines("Chunk(addr=", res) self.assertEqual(len(chunklines), 2) - def test_cmd_heap_chunks(self): + gdb = self._gdb cmd = "heap chunks" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) self.assertIn("Chunk(addr=", res) self.assertIn("top chunk", res) + def test_cmd_heap_chunks_summary(self): + gdb = self._gdb + cmd = "heap chunks --summary" + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) + self.assertIn("== Chunk distribution by size", res) + self.assertIn("== Chunk distribution by flag", res) + + def test_cmd_heap_chunks_min_size_filter(self): + gdb = self._gdb + cmd = "heap chunks --min-size 16" + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) + self.assertIn("Chunk(addr=", res) + + cmd = "heap chunks --min-size 1048576" + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) + self.assertNotIn("Chunk(addr=", res) + + def test_cmd_heap_chunks_max_size_filter(self): + gdb = self._gdb + cmd = "heap chunks --max-size 160" + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) + self.assertIn("Chunk(addr=", res) + + cmd = "heap chunks --max-size 16" + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) + self.assertNotIn("Chunk(addr=", res) + + def test_cmd_heap_chunks_with_count(self): + gdb = self._gdb + cmd = "heap chunks --count 1" + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) + self.assertIn("Chunk(addr=", res) + + cmd = "heap chunks --count 0" + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) + self.assertNotIn("Chunk(addr=", res) + + +class HeapCommandNonMain(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = debug_target("heap-non-main") + return super().setUp() + + def test_cmd_heap_chunks(self): + gdb = self._gdb cmd = "heap chunks" - target = debug_target("heap-non-main") - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) + + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) self.assertIn("Chunk(addr=", res) self.assertIn("top chunk", res) chunks = [line for line in res.splitlines() if "Chunk(addr=" in line] cmd = "python gdb.execute(f'heap chunks {int(list(gef.heap.arenas)[1]):#x}')" - target = debug_target("heap-non-main") - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) + + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) self.assertNotIn("using '&main_arena' instead", res) self.assertIn("Chunk(addr=", res) self.assertIn("top chunk", res) @@ -83,141 +177,128 @@ def test_cmd_heap_chunks(self): # make sure that the chunks of each arena are distinct self.assertNotEqual(chunks, non_main_chunks) + def test_cmd_heap_bins_non_main(self): + gdb = self._gdb + gef = self._gef + next_arena: int = gef.heap.main_arena.next + cmd = f"python gdb.execute(f'heap bins fast {next_arena:#x}')" + gdb.execute("set environment GLIBC_TUNABLES glibc.malloc.tcache_count=0") + res = gdb.execute(cmd, to_string=True) + self.assertIn("size=0x20", res) + + def test_cmd_heap_bins_tcache(self): + gdb = self._gdb + cmd = "heap bins tcache" + res = gdb.execute(cmd, to_string=True) + tcachelines = findlines("Tcachebins[idx=", res) + self.assertEqual(len(tcachelines), 1) + if ARCH in ("i686",): + self.assertIn("Tcachebins[idx=1, size=0x20, count=1]", tcachelines[0]) + elif is_32b(): + self.assertIn("Tcachebins[idx=2, size=0x20, count=1]", tcachelines[0]) + else: + self.assertIn("Tcachebins[idx=0, size=0x20, count=1]", tcachelines[0]) + + +class HeapCommandMultipleHeaps(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = debug_target("heap-multiple-heaps") + return super().setUp() def test_cmd_heap_chunks_mult_heaps(self): + gdb = self._gdb + + gdb.execute("run") py_cmd = 'gdb.execute(f"heap set-arena {int(list(gef.heap.arenas)[1]):#x}")' - before = ['run', 'python ' + py_cmd] + + gdb.execute(f"python {py_cmd}") cmd = "heap chunks" - target = debug_target("heap-multiple-heaps") - res = gdb_run_silent_cmd(cmd, before=before, target=target) - self.assertNoException(res) + res = gdb.execute(cmd, to_string=True) self.assertIn("Chunk(addr=", res) self.assertIn("top chunk", res) - def test_cmd_heap_chunks_summary(self): - cmd = "heap chunks --summary" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) - self.assertIn("== Chunk distribution by size", res) - self.assertIn("== Chunk distribution by flag", res) + +class HeapCommandClass(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = debug_target("class") + return super().setUp() def test_cmd_heap_chunks_summary_with_type_resolved(self): + gdb = self._gdb cmd = "heap chunks --summary --resolve" - target = debug_target("class") - res = gdb_run_silent_cmd(cmd, target=target, before=["b B::Run()"]) - self.assertNoException(res) + + gdb.execute("run") + gdb.execute("b B::Run()") + res = gdb.execute(cmd, to_string=True) self.assertIn("== Chunk distribution by size", res) self.assertIn("B", res) - def test_cmd_heap_chunks_min_size_filter(self): - cmd = "heap chunks --min-size 16" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) - self.assertIn("Chunk(addr=", res) - cmd = "heap chunks --min-size 1048576" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) - self.assertNotIn("Chunk(addr=", res) - - def test_cmd_heap_chunks_max_size_filter(self): - cmd = "heap chunks --max-size 160" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) - self.assertIn("Chunk(addr=", res) - - cmd = "heap chunks --max-size 16" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) - self.assertNotIn("Chunk(addr=", res) - - def test_cmd_heap_chunks_with_count(self): - cmd = "heap chunks --count 1" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) - self.assertIn("Chunk(addr=", res) - - cmd = "heap chunks --count 0" - target = debug_target("heap") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, target=target)) - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) - self.assertNotIn("Chunk(addr=", res) +class HeapCommandFastBins(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = debug_target("heap-fastbins") + return super().setUp() def test_cmd_heap_bins_fast(self): + gdb = self._gdb cmd = "heap bins fast" - before = ["set environment GLIBC_TUNABLES glibc.malloc.tcache_count=0"] - target = debug_target("heap-fastbins") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd, before=before, target=target)) - res = gdb_run_silent_cmd(cmd, before=before, target=target) - self.assertNoException(res) + gdb.execute("set environment GLIBC_TUNABLES glibc.malloc.tcache_count=0") + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + res = gdb.execute(cmd, to_string=True) # ensure fastbins is populated self.assertIn("Fastbins[idx=0, size=", res) self.assertIn("Chunk(addr=", res) +class HeapCommandBins(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = debug_target("heap-bins") + self.expected_large_bin_size = 0x420 if ARCH == "i686" or is_64b() else 0x418 + self.expected_small_bin_size = 0x20 if ARCH == "i686" or is_64b() else 0x18 + self.expected_unsorted_bin_size = 0x430 if ARCH == "i686" or is_64b() else 0x428 + return super().setUp() + def test_cmd_heap_bins_large(self): + gdb = self._gdb cmd = "heap bins large" - target = debug_target("heap-bins") - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) + res = gdb.execute(cmd, to_string=True) self.assertIn("Found 1 chunks in 1 large non-empty bins", res) self.assertIn("Chunk(addr=", res) self.assertIn(f"size={self.expected_large_bin_size:#x}", res) - - def test_cmd_heap_bins_non_main(self): - cmd = "python gdb.execute(f'heap bins fast {gef.heap.main_arena.next:#x}')" - before = ["set environment GLIBC_TUNABLES glibc.malloc.tcache_count=0"] - target = debug_target("heap-non-main") - res = gdb_run_silent_cmd(cmd, before=before, target=target) - self.assertNoException(res) - self.assertIn("size=0x20", res) - - def test_cmd_heap_bins_small(self): + gdb = self._gdb cmd = "heap bins small" before = ["set environment GLIBC_TUNABLES glibc.malloc.tcache_count=0"] target = debug_target("heap-bins") - res = gdb_run_silent_cmd(cmd, before=before, target=target) - self.assertNoException(res) + res = gdb.execute(cmd, before=before, target=target) self.assertIn("Found 1 chunks in 1 small non-empty bins", res) self.assertIn("Chunk(addr=", res) self.assertIn(f"size={self.expected_small_bin_size:#x}", res) + def test_cmd_heap_bins_unsorted(self): + gdb = self._gdb + cmd = "heap bins unsorted" + target = debug_target("heap-bins") + res = gdb.execute(cmd, target=target) + self.assertIn("Found 1 chunks in unsorted bin", res) + self.assertIn("Chunk(addr=", res) + self.assertIn(f"size={self.expected_unsorted_bin_size:#x}", res) - def test_cmd_heap_bins_tcache(self): - cmd = "heap bins tcache" - target = debug_target("heap-non-main") - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) - tcachelines = findlines("Tcachebins[idx=", res) - self.assertEqual(len(tcachelines), 1) - if ARCH in ("i686",): - self.assertIn("Tcachebins[idx=1, size=0x20, count=1]", tcachelines[0]) - elif is_32b(): - self.assertIn("Tcachebins[idx=2, size=0x20, count=1]", tcachelines[0]) - else: - self.assertIn("Tcachebins[idx=0, size=0x20, count=1]", tcachelines[0]) +class HeapCommandTcache(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = debug_target("heap-tcache") + self.expected_tcache_bin_size = 0x20 if ARCH == "i686" or is_64b() else 0x18 + return super().setUp() def test_cmd_heap_bins_tcache_all(self): + gdb = self._gdb cmd = "heap bins tcache all" - target = debug_target("heap-tcache") - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) + + res = gdb.execute(cmd, to_string=True) # ensure there's 2 tcachebins tcachelines = findlines("Tcachebins[idx=", res) self.assertEqual(len(tcachelines), 2) @@ -230,12 +311,3 @@ def test_cmd_heap_bins_tcache_all(self): else: self.assertIn("Tcachebins[idx=0, size=0x20, count=3]", tcachelines[0]) self.assertIn("Tcachebins[idx=1, size=0x30, count=3]", tcachelines[1]) - - def test_cmd_heap_bins_unsorted(self): - cmd = "heap bins unsorted" - target = debug_target("heap-bins") - res = gdb_run_silent_cmd(cmd, target=target) - self.assertNoException(res) - self.assertIn("Found 1 chunks in unsorted bin", res) - self.assertIn("Chunk(addr=", res) - self.assertIn(f"size={self.expected_unsorted_bin_size:#x}", res) diff --git a/tests/commands/heap_analysis.py b/tests/commands/heap_analysis.py index d0c55cde3..7436d6d19 100644 --- a/tests/commands/heap_analysis.py +++ b/tests/commands/heap_analysis.py @@ -3,20 +3,28 @@ """ -from tests.utils import debug_target, gdb_run_cmd, gdb_start_silent_cmd -from tests.utils import GefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE, debug_target -class HeapAnalysisCommand(GefUnitTestGeneric): +class HeapAnalysisCommand(RemoteGefUnitTestGeneric): """`heap-analysis` command test module""" + def setUp(self) -> None: + self._target = debug_target("heap-analysis") + return super().setUp() def test_cmd_heap_analysis(self): + gdb = self._gdb + cmd = "heap-analysis-helper" - target = debug_target("heap-analysis") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd)) - res = gdb_start_silent_cmd(cmd, after=["continue"], target=target) - self.assertNoException(res) + + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + + gdb.execute("start") + res = gdb.execute(cmd, after=["continue"], to_string=True) self.assertIn("Tracking", res) self.assertIn("correctly setup", res) self.assertIn("malloc(16)=", res) diff --git a/tests/commands/hexdump.py b/tests/commands/hexdump.py index fb51cd3f6..17308e866 100644 --- a/tests/commands/hexdump.py +++ b/tests/commands/hexdump.py @@ -3,21 +3,20 @@ """ -from tests.utils import gdb_run_cmd, gdb_start_silent_cmd -from tests.utils import GefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE -class HexdumpCommand(GefUnitTestGeneric): +class HexdumpCommand(RemoteGefUnitTestGeneric): """`hexdump` command test module""" def test_cmd_hexdump(self): - self.assertFailIfInactiveSession(gdb_run_cmd("hexdump $pc")) - res = gdb_start_silent_cmd("hexdump qword $pc") - self.assertNoException(res) - res = gdb_start_silent_cmd("hexdump dword $pc -s 1") - self.assertNoException(res) - res = gdb_start_silent_cmd("hexdump word $pc -s 5 -r") - self.assertNoException(res) - res = gdb_start_silent_cmd("hexdump byte $sp -s 32") - self.assertNoException(res) + gdb = self._gdb + self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE,gdb.execute("hexdump $pc", to_string=True)) + + gdb.execute("start") + res = gdb.execute("hexdump qword $pc", to_string=True) + res = gdb.execute("hexdump dword $pc -s 1", to_string=True) + res = gdb.execute("hexdump word $pc -s 5 -r", to_string=True) + res = gdb.execute("hexdump byte $sp -s 32", to_string=True) diff --git a/tests/commands/highlight.py b/tests/commands/highlight.py index e11430883..0ff68e835 100644 --- a/tests/commands/highlight.py +++ b/tests/commands/highlight.py @@ -3,26 +3,29 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_start_silent_cmd, Color +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import Color, ansi_clean -class HighlightCommand(GefUnitTestGeneric): +class HighlightCommand(RemoteGefUnitTestGeneric): """`highlight` command test module""" - def test_cmd_highlight(self): - cmds = [ + gdb = self._gdb + + gdb.execute("start") + + for cmd in [ "highlight add 41414141 yellow", "highlight add 42424242 blue", "highlight add 43434343 green", "highlight add 44444444 pink", 'patch string $sp "AAAABBBBCCCCDDDD"', - "hexdump qword $sp -s 2" - ] - - res = gdb_start_silent_cmd('', after=cmds, strip_ansi=False) + "hexdump qword $sp -s 2", + ]: + gdb.execute(cmd) - self.assertNoException(res) + res = ansi_clean(gdb.execute("context", to_string=True)) self.assertIn(f"{Color.YELLOW.value}41414141{Color.NORMAL.value}", res) self.assertIn(f"{Color.BLUE.value}42424242{Color.NORMAL.value}", res) self.assertIn(f"{Color.GREEN.value}43434343{Color.NORMAL.value}", res) diff --git a/tests/commands/hijack_fd.py b/tests/commands/hijack_fd.py index 50955e6fb..704c09426 100644 --- a/tests/commands/hijack_fd.py +++ b/tests/commands/hijack_fd.py @@ -3,10 +3,10 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd +from tests.base import RemoteGefUnitTestGeneric -class HijackFdCommand(GefUnitTestGeneric): +class HijackFdCommand(RemoteGefUnitTestGeneric): """`hijack-fd` command test module""" @@ -14,5 +14,5 @@ class HijackFdCommand(GefUnitTestGeneric): def test_cmd_hijack_fd(self): - res = gdb_run_cmd(f"{self.cmd}") - self.assertNoException(res) + gdb = self._gdb + res = gdb.execute(f"{self.cmd}", to_string=True) diff --git a/tests/commands/ksymaddr.py b/tests/commands/ksymaddr.py index 8e0a44c8d..604032daf 100644 --- a/tests/commands/ksymaddr.py +++ b/tests/commands/ksymaddr.py @@ -3,17 +3,15 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd +from tests.base import RemoteGefUnitTestGeneric -class KsymaddrCommand(GefUnitTestGeneric): +class KsymaddrCommand(RemoteGefUnitTestGeneric): """`ksymaddr` command test module""" - cmd = "ksymaddr" - def test_cmd_ksymaddr(self): - res = gdb_run_cmd(f"{self.cmd} prepare_kernel_cred") - self.assertNoException(res) + gdb = self._gdb + res = gdb.execute(f"{self.cmd} prepare_kernel_cred", to_string=True) self.assertIn("Found matching symbol for 'prepare_kernel_cred'", res) diff --git a/tests/commands/memory.py b/tests/commands/memory.py index 0550dcc07..490f3f551 100644 --- a/tests/commands/memory.py +++ b/tests/commands/memory.py @@ -2,7 +2,9 @@ Memory commands test module """ +from tests.base import RemoteGefUnitTestGeneric from tests.utils import ( + ERROR_INACTIVE_SESSION_MESSAGE, GefUnitTestGeneric, gdb_run_cmd, gdb_start_silent_cmd, @@ -10,54 +12,79 @@ ) -class MemoryCommand(GefUnitTestGeneric): - """ `memory` command testing module""" - - - def test_cmd_memory_watch(self): - self.assertFailIfInactiveSession(gdb_run_cmd("memory watch $pc")) - res = gdb_start_silent_cmd("memory watch $pc 0x100 byte") - self.assertNoException(res) - res = gdb_start_silent_cmd("memory watch $pc 0x40 word") - self.assertNoException(res) - res = gdb_start_silent_cmd("memory watch $pc 0x30 dword") - self.assertNoException(res) - res = gdb_start_silent_cmd("memory watch $pc 0x20 qword") - self.assertNoException(res) - res = gdb_start_silent_cmd("memory watch $pc 0x8 pointers") - self.assertNoException(res) - res = gdb_start_silent_cmd("memory watch $pc") - self.assertNoException(res) - target = debug_target("memwatch") - res = gdb_start_silent_cmd("memory watch &myglobal", - before=["set args 0xdeadbeef"], - after=["continue"], - target=target, - context='memory') +class MemoryCommand(RemoteGefUnitTestGeneric): + """`memory` command testing module""" + + def setUp(self) -> None: + self._target = debug_target("memwatch") + return super().setUp() + + def test_cmd_memory_watch_basic(self): + gdb = self._gdb + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, + gdb.execute("memory watch $pc", to_string=True), + ) + gdb.execute("start") + + # basic syntax checks + gdb.execute("memory watch $pc 0x100 byte") + gdb.execute("memory watch $pc 0x40 word") + gdb.execute("memory watch $pc 0x30 dword") + gdb.execute("memory watch $pc 0x20 qword") + gdb.execute("memory watch $pc 0x8 pointers") + gdb.execute("memory watch $pc") + + def test_cmd_memory_watch_global_variable(self): + gdb = self._gdb + + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, + gdb.execute("memory unwatch $pc", to_string=True), + ) + + gdb.execute("set args 0xdeadbeef") + gdb.execute("memory watch &myglobal") + gdb.execute("gef config context.layout memory") + gdb.execute("run") + + res: str = gdb.execute("context", to_string=True) self.assertIn("deadbeef", res) self.assertNotIn("cafebabe", res) - res = gdb_start_silent_cmd("memory watch &myglobal", - before=["set args 0xcafebabe"], - after=["continue"], - target=target, - context="memory") + + gdb.execute("continue") + + gdb.execute("set args 0xcafebabe") + gdb.execute("memory watch &myglobal") + gdb.execute("gef config context.layout memory") + gdb.execute("run") + + res: str = gdb.execute("context", to_string=True) self.assertIn("cafebabe", res) self.assertNotIn("deadbeef", res) - def test_cmd_memory_unwatch(self): - self.assertFailIfInactiveSession(gdb_run_cmd("memory unwatch $pc")) - res = gdb_start_silent_cmd("memory unwatch $pc") - self.assertNoException(res) - + gdb = self._gdb + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, + gdb.execute("memory unwatch $pc", to_string=True), + ) + gdb.execute("start") + gdb.execute("memory unwatch $pc", to_string=True) def test_cmd_memory_list(self): - self.assertFailIfInactiveSession(gdb_run_cmd("memory list")) - res = gdb_start_silent_cmd("memory list") - self.assertNoException(res) - + gdb = self._gdb + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute("memory list", to_string=True) + ) + gdb.execute("start") + res = gdb.execute("memory list", to_string=True).strip() + assert "[+] No memory watches" == res def test_cmd_memory_reset(self): - self.assertFailIfInactiveSession(gdb_run_cmd("memory reset")) - res = gdb_start_silent_cmd("memory reset") - self.assertNoException(res) + gdb = self._gdb + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute("memory reset", to_string=True) + ) + gdb.execute("start") + res = gdb.execute("memory reset", to_string=True) diff --git a/tests/commands/name_break.py b/tests/commands/name_break.py index d7f0b1de9..3983432ff 100644 --- a/tests/commands/name_break.py +++ b/tests/commands/name_break.py @@ -3,21 +3,17 @@ """ -from tests.utils import gdb_run_cmd, gdb_start_silent_cmd -from tests.utils import GefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric -class NameBreakCommand(GefUnitTestGeneric): +class NameBreakCommand(RemoteGefUnitTestGeneric): """`name-break` command test module""" - def test_cmd_name_break(self): - res = gdb_run_cmd("nb foobar *main+10") - self.assertNoException(res) - - res = gdb_run_cmd("nb foobar *0xcafebabe") - self.assertNoException(res) + gdb = self._gdb + res = gdb.execute("nb foobar *main+10", to_string=True) + res = gdb.execute("nb foobar *0xcafebabe", to_string=True) self.assertIn("at 0xcafebabe", res) - res = gdb_start_silent_cmd("nb foobar") - self.assertNoException(res) + res = gdb.execute("start") + gdb.execute("nb foobar", to_string=True) diff --git a/tests/commands/nop.py b/tests/commands/nop.py index 59b98ac9a..d3c877243 100644 --- a/tests/commands/nop.py +++ b/tests/commands/nop.py @@ -4,309 +4,269 @@ import pytest -from tests.utils import (ARCH, GefUnitTestGeneric, debug_target, findlines, - gdb_run_cmd, gdb_run_silent_cmd, gdb_start_silent_cmd) - - -class NopCommand(GefUnitTestGeneric): +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ( + ARCH, + ERROR_INACTIVE_SESSION_MESSAGE, + debug_target, + p16, + p32, + p64, + u16, + u32, + u64, +) + + +class NopCommand(RemoteGefUnitTestGeneric): """`nop` command test module""" - cmd = "nop" - def test_cmd_nop_inactive(self): - res = gdb_run_cmd(f"{self.cmd}") - self.assertFailIfInactiveSession(res) + gdb = self._gdb + res = gdb.execute(f"{self.cmd}", to_string=True) + self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE, res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_no_arg(self): + gdb = self._gdb + gef = self._gef - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p32(0xfeebfeeb))", - after=( - self.cmd, - "pi print(gef.memory.read(gef.arch.pc, 4))", - ) - ) - self.assertNoException(res) - self.assertIn(r"\x90\x90\xeb\xfe", res) - + gdb.execute("start") + gef.memory.write(gef.arch.pc, p32(0xFEEBFEEB)) + gdb.execute(self.cmd) + res = u32(gef.memory.read(gef.arch.pc, 4)) + self.assertEqual(0xFEEBFEEB, res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_check_b_and_n_same_time(self): - - res = gdb_start_silent_cmd(f"{self.cmd} --b --n") - self.assertNoException(res) - self.assertIn(r"--b and --n cannot be specified at the same time.", res) - + gdb = self._gdb + gdb.execute("start") + res = gdb.execute(f"{self.cmd} --b --n", to_string=True).strip() + self.assertEqual("[!] --b and --n cannot be specified at the same time.", res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_no_arg_break_instruction(self): - res = gdb_start_silent_cmd( - (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", - "pi gef.memory.write(gef.arch.pc, p32(0xfeebfeeb))"), - - after=( - self.cmd, - "pi print(gef.memory.read(gef.arch.pc, 4))", - ) - ) - self.assertNoException(res) - self.assertIn(r"will result in LAST-NOP (byte nr 0x2)", res) - self.assertNotIn(r"\x90\x90\xeb\xfe", res) + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.arch.nop_insn = b"\x90\x91\x92" + gef.memory.write(gef.arch.pc, p32(0xFEEBFEEB)) + + res = gdb.execute(self.cmd, to_string=True).strip() + mem = u32(gef.memory.read(gef.arch.pc, 4)) + self.assertIn(r"will result in LAST-NOP (byte nr 0x2)", res) + self.assertNotEqual(0xFEEB9090, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_force_arg_break_instruction(self): - res = gdb_start_silent_cmd( - (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", - "pi gef.memory.write(gef.arch.pc, p32(0xfeebfeeb))"), - - after=( - f"{self.cmd} --f", - "pi print(gef.memory.read(gef.arch.pc, 4))", - ) - ) - self.assertNoException(res) + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.arch.nop_insn = b"\x90\x91\x92" + gef.memory.write(gef.arch.pc, p32(0xFEEBFEEB)) + res = gdb.execute(f"{self.cmd} --f", to_string=True).strip() + mem = gef.memory.read(gef.arch.pc, 4) self.assertIn(r"will result in LAST-NOP (byte nr 0x2)", res) - self.assertIn(r"\x90\x91\xeb\xfe", res) - + self.assertEqual(0xFEEB9190, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_i_arg(self): - - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc+1, p64(0xfeebfeebfeebfeeb))", - after=( - f"{self.cmd} --i 2 $pc+1", - "pi print(gef.memory.read(gef.arch.pc+1, 8))", - ) - ) - self.assertNoException(res) - self.assertIn(r"\x90\x90\x90\x90\xeb\xfe\xeb\xfe", res) - + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.memory.write(gef.arch.pc + 1, p64(0xFEEBFEEBFEEBFEEB)) + res = gdb.execute(f"{self.cmd} --i 2 $pc+1", to_string=True) + mem = u64(gef.memory.read(gef.arch.pc + 1, 8)) + self.assertEqual(0xFEEBFEEB90909090, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_i_arg_reaching_unmapped_area(self): - - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc+1, p64(0xfeebfeebfeebfeeb))", - after=( - f"{self.cmd} --i 2000000000000000000000000000000000000 $pc+1", - "pi print(gef.memory.read(gef.arch.pc+1, 8))", - ) + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.memory.write(gef.arch.pc + 1, p64(0xFEEBFEEBFEEBFEEB)) + res = gdb.execute( + f"{self.cmd} --i 2000000000000000000000000000000000000 $pc+1", + to_string=True, ) + mem = u64(gef.memory.read(gef.arch.pc + 1, 8)) self.assertIn(r"reaching unmapped area", res) - self.assertNoException(res) - self.assertNotIn(r"\x90\x90\x90\x90\xeb\xfe\xeb\xfe", res) - - - @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") - def test_cmd_nop_invalid_end_address(self): - res = gdb_run_silent_cmd( - f"{self.cmd} --i 5 0x1337000+0x1000-4", - target=debug_target("mmap-known-address") - ) - self.assertNoException(res) - self.assertIn("reaching unmapped area", res) - + self.assertNotEqual(0xFEEBFEEB90909090, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_nop(self): - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p32(0x9191))", - after=( - f"{self.cmd} --n", - "pi print(gef.memory.read(gef.arch.pc, 2))", - ) - ) - self.assertIn(r"\x90\x91", res) - self.assertNoException(res) - + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.memory.write(gef.arch.pc, p32(0x9191)) + res = gdb.execute(f"{self.cmd} --n", to_string=True) + mem = u16(gef.memory.read(gef.arch.pc, 2)) + self.assertEqual(0x9190, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_nop_break_instruction(self): - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p16(0xfeeb))", - after=( - f"{self.cmd} --n", - "pi print(gef.memory.read(gef.arch.pc, 2))", - ) - ) + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.memory.write(gef.arch.pc, p16(0xFEEB)) + res = gdb.execute(f"{self.cmd} --n", to_string=True) + mem = u16(gef.memory.read(gef.arch.pc, 2)) self.assertIn(r"will result in LAST-INSTRUCTION", res) - self.assertIn(r"b'\xeb\xfe'", res) - self.assertNoException(res) - + self.assertEqual(0xFEEB, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_nop_break_instruction_force(self): - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p16(0xfeeb))", - after=( - f"{self.cmd} --n --f", - "pi print(gef.memory.read(gef.arch.pc, 2))", - ) - ) + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.memory.write(gef.arch.pc, p16(0xFEEB)) + res = gdb.execute(f"{self.cmd} --n --f", to_string=True) + mem = u16(gef.memory.read(gef.arch.pc, 2)) self.assertIn(r"will result in LAST-INSTRUCTION", res) - self.assertIn(r"b'\x90\xfe'", res) - self.assertNoException(res) - + self.assertEqual(0xFE90, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_nop_arg(self): - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))", - after=( - f"{self.cmd} --i 4 --n", - "pi print(gef.memory.read(gef.arch.pc, 8))", - ) - ) - self.assertIn(r"b'\x90\x90\x90\x90\xeb\xfe\xeb\xfe'", res) - self.assertNoException(res) - + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.memory.write(gef.arch.pc, p64(0xFEEBFEEBFEEBFEEB)) + res = gdb.execute(f"{self.cmd} --i 4 --n", to_string=True) + mem = u64(gef.memory.read(gef.arch.pc, 8)) + self.assertEqual(0xFEEBFEEB90909090, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_nop_arg_multibnop_breaks(self): - res = gdb_start_silent_cmd( - (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", - "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))"), - - after=( - f"{self.cmd} --n", - "pi print(gef.memory.read(gef.arch.pc, 8))", - ) - ) - self.assertNoException(res) + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.arch.nop_insn = b"\x90\x91\x92" + gef.memory.write(gef.arch.pc, p64(0xFEEBFEEBFEEBFEEB)) + res = gdb.execute(f"{self.cmd} --n", to_string=True) + mem = u64(gef.memory.read(gef.arch.pc, 8)) self.assertIn(r"will result in LAST-INSTRUCTION", res) - self.assertIn(r"b'\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe'", res) - + self.assertEqual(0xFEEBFEEBFEEBFEEB, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_nop_arg_multibnop_breaks_force(self): - res = gdb_start_silent_cmd( - (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", - "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))"), - - after=( - f"{self.cmd} --n --f", - "pi print(gef.memory.read(gef.arch.pc, 8))", - ) - ) - self.assertNoException(res) + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.arch.nop_insn = b"\x90\x91\x92" + gef.memory.write(gef.arch.pc, p64(0xFEEBFEEBFEEBFEEB)) + res = gdb.execute(f"{self.cmd} --n --f", to_string=True) + mem = u64(gef.memory.read(gef.arch.pc, 8)) self.assertIn(r"will result in LAST-INSTRUCTION", res) - self.assertIn(r"b'\x90\x91\x92\xfe\xeb\xfe\xeb\xfe'", res) - + self.assertEqual(0xFEEBFEEB929190, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes(self): - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p16(0x9191))", - after=( - f"{self.cmd} --b", - "pi print(gef.memory.read(gef.arch.pc, 2))", - ) + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.memory.write(gef.arch.pc, p16(0x9191)) + res = gdb.execute( + f"{self.cmd} --b", + to_string=True, ) - - self.assertIn(r"\x90\x91", res) - self.assertNoException(res) - + mem = u16(gef.memory.read(gef.arch.pc, 2)) + self.assertEqual(0x9190, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes_break_instruction(self): - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p16(0xfeeb))", - after=( - f"{self.cmd} --b", - "pi print(gef.memory.read(gef.arch.pc, 2))", - ) - ) - + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.memory.write(gef.arch.pc, p16(0xFEEB)) + res = gdb.execute(f"{self.cmd} --b", to_string=True) + mem = u16(gef.memory.read(gef.arch.pc, 2)) self.assertIn(r"will result in LAST-INSTRUCTION", res) - self.assertIn(r"b'\xeb\xfe'", res) - self.assertNoException(res) - + self.assertEqual(0xFEEB, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes_break_instruction_force(self): - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p16(0xfeeb))", - after=( - f"{self.cmd} --b --f", - "pi print(gef.memory.read(gef.arch.pc, 2))", - ) - ) + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.memory.write(gef.arch.pc, p16(0xFEEB)) + res = gdb.execute(f"{self.cmd} --b --f", to_string=True) + mem = u16(gef.memory.read(gef.arch.pc, 2)) self.assertIn(r"will result in LAST-INSTRUCTION", res) self.assertIn(r"b'\x90\xfe'", res) - self.assertNoException(res) - @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes_arg(self): - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))", - after=( - f"{self.cmd} --i 2 --b --f", - "pi print(gef.memory.read(gef.arch.pc, 8))", - ) - ) - self.assertIn(r"b'\x90\x90\xeb\xfe\xeb\xfe\xeb\xfe'", res) - self.assertNoException(res) - + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.memory.write(gef.arch.pc, p64(0xFEEBFEEBFEEBFEEB)) + res = gdb.execute(f"{self.cmd} --i 2 --b --f", to_string=True) + mem = u64(gef.memory.read(gef.arch.pc, 8)) + self.assertEqual(0xFEEBFEEBFEEB9090, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes_arg_nops_no_fit(self): - res = gdb_start_silent_cmd( - (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", - "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))"), - - after=( - f"{self.cmd} --i 4 --b", - "pi print(gef.memory.read(gef.arch.pc, 8))", - ) - ) - self.assertIn(r"b'\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe'", res) + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.arch.nop_insn = b"\x90\x91\x92" + gef.memory.write(gef.arch.pc, p64(0xFEEBFEEBFEEBFEEB)) + res = gdb.execute(f"{self.cmd} --i 4 --b", to_string=True) self.assertIn(r"will result in LAST-NOP (byte nr 0x1)", res) - self.assertNoException(res) - + mem = u64(gef.memory.read(gef.arch.pc, 8)) + self.assertEqual(0xFEEBFEEBFEEBFEEB, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes_arg_nops_no_fit_force(self): - res = gdb_start_silent_cmd( - (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", - "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))"), - - after=( - f"{self.cmd} --i 5 --b --f", - "pi print(gef.memory.read(gef.arch.pc, 8))", - ) - ) - self.assertIn(r"b'\x90\x91\x92\x90\x91\xfe\xeb\xfe'", res) + gdb = self._gdb + gef = self._gef + gdb.execute("start") + gef.arch.nop_insn = b"\x90\x91\x92" + gef.memory.write(gef.arch.pc, p64(0xFEEBFEEBFEEBFEEB)) + res = gdb.execute(f"{self.cmd} --i 5 --b --f", to_string=True) + mem = gef.memory.read(gef.arch.pc, 8) self.assertIn(r"will result in LAST-NOP (byte nr 0x2)", res) self.assertIn(r"will result in LAST-INSTRUCTION", res) - self.assertNoException(res) + self.assertEqual(0xFEEBFE9190929190, mem) + +class NopCommandMmapKnownAddress(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = (debug_target("mmap-known-address"),) + return super().setUp() + + @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") + def test_cmd_nop_invalid_end_address(self): + gdb = self._gdb + gdb.execute("run") + res = gdb.execute(f"nop --i 5 0x1337000+0x1000-4", to_string=True) + self.assertIn("reaching unmapped area", res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_as_bytes_invalid_end_address(self): + gdb = self._gdb + gef = self._gef # Make sure we error out if writing nops into an unmapped or RO area - res = gdb_run_silent_cmd( - f"{self.cmd} --b --i 5 0x1337000+0x1000-4", - target=debug_target("mmap-known-address") + gdb.execute("run") + res = gdb.execute(f"nop --b --i 5 0x1337000+0x1000-4", to_string=True) + self.assertIn( + "Cannot patch instruction at 0x1337ffc: reaching unmapped area", res ) - self.assertNoException(res) - self.assertIn("Cannot patch instruction at 0x1337ffc: reaching unmapped area", res) # We had an off-by-one bug where we couldn't write the last byte before # an unmapped area. Make sure that we can now. - res = gdb_run_silent_cmd( - f"{self.cmd} --b --i 4 0x1337000+0x1000-4", - target=debug_target("mmap-known-address"), - after="pi print(f'*** *mem={u32(gef.memory.read(0x1337ffc, 4)):#x}')", + res = gdb.execute("nop --b --i 4 0x1337000+0x1000-4", to_string=True) + self.assertNotIn( + "Cannot patch instruction at 0x1337ffc: reaching unmapped area", res ) - self.assertNoException(res) - self.assertNotIn("Cannot patch instruction at 0x1337ffc: reaching unmapped area", res) - lines = findlines("*** *mem=", res) - self.assertEqual(len(lines), 1) - self.assertEqual(lines[0], "*** *mem=0x90909090") + # after="pi print(f'*** *mem={u32(gef.memory.read(0x1337ffc, 4)):#x}')", + + mem = u32(gef.memory.read(0x1337FFC, 4)) + self.assertEqual(0x90909090, mem) + # lines = findlines("*** *mem=", res) + # self.assertEqual(len(lines), 1) + # self.assertEqual(lines[0], "*** *mem=0x90909090") diff --git a/tests/commands/patch.py b/tests/commands/patch.py index 5db8c56e8..bf00a25b0 100644 --- a/tests/commands/patch.py +++ b/tests/commands/patch.py @@ -3,60 +3,74 @@ """ -from tests.utils import debug_target, gdb_run_cmd, gdb_run_silent_cmd, gdb_start_silent_cmd_last_line -from tests.utils import GefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE, debug_target -class PatchCommand(GefUnitTestGeneric): + +class PatchCommand(RemoteGefUnitTestGeneric): """`patch` command test module""" def test_cmd_patch(self): - self.assertFailIfInactiveSession(gdb_run_cmd("patch")) + gdb = self._gdb + self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE,gdb.execute("patch", to_string=True)) def test_cmd_patch_byte(self): - res = gdb_start_silent_cmd_last_line("patch byte $pc 0xcc", after=["display/8bx $pc",]) - self.assertNoException(res) + gdb = self._gdb + gdb.execute("start") + gdb.execute("patch byte $pc 0xcc") + res = gdb.execute("display/8bx $pc", to_string=True).strip() self.assertRegex(res, r"0xcc\s*0x[^c]{2}") def test_cmd_patch_byte_bytearray(self): - res = gdb_start_silent_cmd_last_line("set $_gef69 = { 0xcc, 0xcc }", after=["patch byte $pc $_gef69", "display/8bx $pc",]) - self.assertNoException(res) + gdb = self._gdb + gdb.execute("start") + gdb.execute("set $_gef69 = { 0xcc, 0xcc }") + res = gdb.execute("patch byte $pc $_gef69", "display/8bx $pc", to_string=True).strip() self.assertRegex(res, r"(0xcc\s*)(\1)0x[^c]{2}") def test_cmd_patch_word(self): - res = gdb_start_silent_cmd_last_line("patch word $pc 0xcccc", after=["display/8bx $pc",]) - self.assertNoException(res) + gdb = self._gdb + gdb.execute("start") + res = gdb.execute("patch word $pc 0xcccc") + res = gdb.execute("display/8bx $pc", to_string=True).strip() self.assertRegex(res, r"(0xcc\s*)(\1)0x[^c]{2}") def test_cmd_patch_dword(self): - res = gdb_start_silent_cmd_last_line("patch dword $pc 0xcccccccc", - after=["display/8bx $pc",]) - self.assertNoException(res) + gdb = self._gdb + gdb.execute("start") + gdb.execute("patch dword $pc 0xcccccccc") + res = gdb.execute("display/8bx $pc", to_string=True).strip() self.assertRegex(res, r"(0xcc\s*)(\1\1\1)0x[^c]{2}") - def test_cmd_patch_qword(self): - res = gdb_start_silent_cmd_last_line("patch qword $pc 0xcccccccccccccccc", - after=["display/8bx $pc",]) - self.assertNoException(res) + gdb = self._gdb + gdb.execute("start") + gdb.execute("patch qword $pc 0xcccccccccccccccc") + res = gdb.execute("display/8bx $pc", to_string=True).strip() self.assertRegex(res, r"(0xcc\s*)(\1\1\1\1\1\1)0xcc") + def test_cmd_patch_string(self): + gdb = self._gdb + gdb.execute("patch string $sp \"Gef!Gef!Gef!Gef!\"") + res = gdb.execute("grep Gef!Gef!Gef!Gef!", to_string=True).strip() + self.assertIn("Gef!Gef!Gef!Gef!", res) + +class PatchCommandBss(RemoteGefUnitTestGeneric): + + def setUp(self) -> None: + self._target = debug_target("bss") + return super().setUp() + def test_cmd_patch_qword_symbol(self): - target = debug_target("bss") - before = gdb_run_silent_cmd("deref -l 1 $sp", target=target) - after = gdb_run_silent_cmd("patch qword $sp &msg", after=["deref -l 1 $sp"], target=target) - self.assertNoException(before) - self.assertNoException(after) + gdb = self._gdb + gdb.execute("run") + before = gdb.execute("deref -l 1 $sp", to_string=True) + gdb.execute("patch qword $sp &msg") + after = gdb.execute("deref -l 1 $sp", to_string=True) self.assertNotIn("Hello world!", before) self.assertIn("Hello world!", after) - - - def test_cmd_patch_string(self): - res = gdb_start_silent_cmd_last_line("patch string $sp \"Gef!Gef!Gef!Gef!\"", - after=["grep Gef!Gef!Gef!Gef!",]) - self.assertNoException(res) - self.assertIn("Gef!Gef!Gef!Gef!", res) diff --git a/tests/commands/pattern.py b/tests/commands/pattern.py index a981bb690..5daca25ed 100644 --- a/tests/commands/pattern.py +++ b/tests/commands/pattern.py @@ -3,28 +3,33 @@ """ import pytest -from tests.utils import ARCH, GefUnitTestGeneric, debug_target, gdb_run_cmd, is_64b +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ARCH, debug_target, is_64b -class PatternCommand(GefUnitTestGeneric): +class PatternCommand(RemoteGefUnitTestGeneric): """`pattern` command test module""" + def setUp(self) -> None: + self._target = debug_target("pattern") + return super().setUp() + def test_cmd_pattern_create(self): + gdb = self._gdb cmd = "pattern create -n 4 32" - res = gdb_run_cmd(cmd) - self.assertNoException(res) + res = gdb.execute(cmd, to_string=True).strip() self.assertIn("aaaabaaacaaadaaaeaaaf", res) cmd = "pattern create -n 8 32" - res = gdb_run_cmd(cmd) - self.assertNoException(res) + res = gdb.execute(cmd, to_string=True).strip() self.assertIn("aaaaaaaabaaaaaaacaaaaaaadaaaaaaa", res) - - @pytest.mark.skipif(ARCH not in ("x86_64", "aarch64", "i686", "armv7l"), - reason=f"Skipped for {ARCH}") + @pytest.mark.skipif( + ARCH not in ("x86_64", "aarch64", "i686", "armv7l"), + reason=f"Skipped for {ARCH}", + ) def test_cmd_pattern_search(self): - target = debug_target("pattern") + gdb = self._gdb if ARCH == "aarch64": lookup_register = "$x30" expected_offsets = (16, 16, 5, 9) @@ -41,39 +46,48 @@ def test_cmd_pattern_search(self): else: raise ValueError("Invalid architecture") - #0 + # 0 cmd = f"pattern search -n 4 {lookup_register}" - before = ("set args aaaabaaacaaadaaaeaaafaaagaaahaaa", "run") - res = gdb_run_cmd(cmd, before=before, target=target) - self.assertNoException(res) - self.assertIn(f"Found at offset {expected_offsets[0]} (little-endian search) likely", res) + gdb.execute("set args aaaabaaacaaadaaaeaaafaaagaaahaaa") + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) + self.assertIn( + f"Found at offset {expected_offsets[0]} (little-endian search) likely", res + ) - #1 + # 1 if is_64b(): cmd = f"pattern search -n 8 {lookup_register}" - before = ("set args aaaaaaaabaaaaaaacaaaaaaadaaaaaaa", "run") - res = gdb_run_cmd(cmd, before=before, target=target) - self.assertNoException(res) - self.assertIn(f"Found at offset {expected_offsets[1]} (little-endian search) likely", res) + gdb.execute("set args aaaaaaaabaaaaaaacaaaaaaadaaaaaaa", "run") + res = gdb.execute(cmd, to_string=True) + self.assertIn( + f"Found at offset {expected_offsets[1]} (little-endian search) likely", + res, + ) - #2 + # 2 cmd = "pattern search -n 4 caaa" - before = ("set args aaaabaaacaaadaaaeaaafaaagaaahaaa", "run") - res = gdb_run_cmd(cmd, before=before, target=target) - self.assertNoException(res) - self.assertIn(f"Found at offset {expected_offsets[2]} (little-endian search) likely", res) + gdb.execute("set args aaaabaaacaaadaaaeaaafaaagaaahaaa") + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) + self.assertIn( + f"Found at offset {expected_offsets[2]} (little-endian search) likely", res + ) - #3 + # 3 if is_64b(): cmd = "pattern search -n 8 caaaaaaa" - before = ("set args aaaaaaaabaaaaaaacaaaaaaadaaaaaaa", "run") - res = gdb_run_cmd(cmd, before=before, target=target) - self.assertNoException(res) - self.assertIn(f"Found at offset {expected_offsets[3]} (little-endian search) likely", res) + gdb.execute("set args aaaaaaaabaaaaaaacaaaaaaadaaaaaaa") + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) + self.assertIn( + f"Found at offset {expected_offsets[3]} (little-endian search) likely", + res, + ) - #4 + # 4 cmd = "pattern search -n 4 JUNK" - before = ("set args aaaabaaacaaadaaaeaaafaaagaaahaaa", "run") - res = gdb_run_cmd(cmd, before=before, target=target) - self.assertNoException(res) + gdb.execute("set args aaaabaaacaaadaaaeaaafaaagaaahaaa") + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) self.assertIn(f"not found", res) diff --git a/tests/commands/pcustom.py b/tests/commands/pcustom.py index 9b66205c9..2131db27a 100644 --- a/tests/commands/pcustom.py +++ b/tests/commands/pcustom.py @@ -5,13 +5,12 @@ import tempfile import pathlib +from tests.base import RemoteGefUnitTestGeneric + from tests.utils import ( - gdb_run_cmd, - gdb_run_silent_cmd, is_64b, debug_target, GEF_DEFAULT_TEMPDIR, - GefUnitTestGeneric, ) @@ -23,10 +22,15 @@ class goo_t(Structure): """ -class PcustomCommand(GefUnitTestGeneric): +class PcustomCommand(RemoteGefUnitTestGeneric): """`pcustom` command test module""" + def setUp(self) -> None: + self._target=debug_target("pcustom") + return super().setUp() + def test_cmd_pcustom(self): + gdb = self._gdb with tempfile.TemporaryDirectory(prefix=GEF_DEFAULT_TEMPDIR) as dd: dirpath = pathlib.Path(dd).absolute() @@ -35,26 +39,27 @@ def test_cmd_pcustom(self): fd.seek(0) fd.flush() - res = gdb_run_cmd("gef config pcustom.struct_path", - before=[f"gef config pcustom.struct_path {dirpath}",]) - self.assertNoException(res) + gdb.execute(f"gef config pcustom.struct_path {dirpath}") + gdb.execute("run") + res = gdb.execute("gef config pcustom.struct_path", to_string=True) self.assertIn(f"pcustom.struct_path (str) = \"{dirpath}\"", res) - res = gdb_run_cmd("pcustom", before=[f"gef config pcustom.struct_path {dirpath}",]) - self.assertNoException(res) - structline = [x for x in res.splitlines() if x.startswith(f" → {dirpath}") ][0] + gdb.execute(f"gef config pcustom.struct_path {dirpath}") + res = gdb.execute("pcustom", to_string=True) + structline = [x for x in res.splitlines() if x.startswith(f" → {dirpath}", to_string=True) ][0] self.assertIn("goo_t", structline) self.assertIn("foo_t", structline) # bad structure name with address - res = gdb_run_cmd("pcustom meh_t 0x1337100", - before=[f"gef config pcustom.struct_path {dirpath}",]) - self.assertNoException(res) + gdb.execute(f"gef config pcustom.struct_path {dirpath}") + gdb.execute("run") + res = gdb.execute("pcustom meh_t 0x1337100", to_string=True) self.assertIn("Session is not active", res) def test_cmd_pcustom_show(self): + gdb = self._gdb with tempfile.TemporaryDirectory(prefix=GEF_DEFAULT_TEMPDIR) as dd: dirpath = pathlib.Path(dd).absolute() @@ -64,9 +69,9 @@ def test_cmd_pcustom_show(self): fd.flush() # no address - res = gdb_run_cmd("pcustom foo_t", - before=[f"gef config pcustom.struct_path {dirpath}",]) - self.assertNoException(res) + gdb.execute(f"gef config pcustom.struct_path {dirpath}") + gdb.execute("run") + res = gdb.execute("pcustom foo_t", to_string=True) if is_64b(): self.assertIn("0000 a c_int /* size=0x4 */", res) self.assertIn("0004 b c_int /* size=0x4 */", res) @@ -75,9 +80,9 @@ def test_cmd_pcustom_show(self): self.assertIn("0004 b c_long /* size=0x4 */", res) # with address - res = gdb_run_silent_cmd("pcustom goo_t 0x1337100", target=debug_target("pcustom"), - before=[f"gef config pcustom.struct_path {dirpath}",]) - self.assertNoException(res) + gdb.execute(f"gef config pcustom.struct_path {dirpath}") + gdb.execute("run") + res = gdb.execute("pcustom goo_t 0x1337100", to_string=True) if is_64b(): self.assertIn(f"""0x1337100+0x00 a : 3 (c_int) 0x1337100+0x04 b : 4 (c_int) @@ -96,13 +101,11 @@ def test_cmd_pcustom_show(self): 0x1337100+0x10 e : 13 (c_long)""", res) # bad structure name - res = gdb_run_cmd("pcustom meh_t", - before=[f"gef config pcustom.struct_path {dirpath}",]) - self.assertNoException(res) + gdb.execute(f"gef config pcustom.struct_path {dirpath}") + res = gdb.execute("pcustom meh_t", to_string=True) self.assertIn("No structure named 'meh_t' found", res) # bad structure name with address - res = gdb_run_silent_cmd("pcustom meh_t 0x1337100", target=debug_target("pcustom"), - before=[f"gef config pcustom.struct_path {dirpath}",]) - self.assertNoException(res) + gdb.execute(f"gef config pcustom.struct_path {dirpath}") + res = gdb.execute("pcustom meh_t 0x1337100", to_string=True) self.assertIn("No structure named 'meh_t' found", res) diff --git a/tests/commands/pie.py b/tests/commands/pie.py index 82bf6c79d..4e694ae57 100644 --- a/tests/commands/pie.py +++ b/tests/commands/pie.py @@ -2,52 +2,54 @@ `pie` command test module """ -from tests.utils import (GefUnitTestGeneric, debug_target, find_symbol, gdb_run_cmd, - removeuntil) +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import debug_target, find_symbol, removeuntil -class PieCommand(GefUnitTestGeneric): +class PieCommand(RemoteGefUnitTestGeneric): """`pie` command test module""" - def setUp(self) -> None: target = debug_target("default") self.pie_offset = find_symbol(target, "main") self.assertGreater(self.pie_offset, 0) return super().setUp() - def test_cmd_pie(self): - res = gdb_run_cmd("pie") - self.assertNoException(res) + gdb = self._gdb + res = gdb.execute("pie", to_string=True) self.assertIn("pie (breakpoint|info|delete|run|attach|remote)", res) - res = gdb_run_cmd("pie info 42") - self.assertNoException(res) - res = gdb_run_cmd("pie delete 42") - self.assertNoException(res) - + gdb.execute("pie info 42") + res = gdb.execute("pie delete 42", to_string=True) + assert res def test_cmd_pie_breakpoint_check(self): + gdb = self._gdb + # breakpoint at a random instruction and check - res = gdb_run_cmd(f"pie breakpoint {self.pie_offset}", after=("pie info")) - self.assertNoException(res) + gdb.execute(f"pie breakpoint {self.pie_offset}") + res = gdb.execute("pie info", to_string=True) last_line_addr = res.splitlines()[-1].strip().split() self.assertEqual(last_line_addr[0], "1") self.assertEqual(last_line_addr[-1], hex(self.pie_offset)) - def test_cmd_pie_breakpoint_delete(self): - res = gdb_run_cmd(f"pie breakpoint {self.pie_offset}", after=("pie delete 1", "pie info")) - self.assertNoException(res) + gdb = self._gdb + gdb.execute(f"pie breakpoint {self.pie_offset}") + gdb.execute("pie delete 1") + res = gdb.execute("pie info", to_string=True) self.assertNotIn(hex(self.pie_offset), res) - def test_cmd_pie_breakpoint_run(self): + gdb = self._gdb # breakpoint at a random instruction and run - res = gdb_run_cmd("pie run", before=(f"pie breakpoint {self.pie_offset}",)) - self.assertNoException(res) + gdb.execute(f"pie breakpoint {self.pie_offset}") + res = gdb.execute( + "pie run", + to_string=True + ) # check we stopped for a breakpoint - res = removeuntil("Name: \"default.out\", stopped ", res).splitlines()[0] + res = removeuntil('Name: "default.out", stopped ', res).splitlines()[0] self.assertIn("in main (), reason: BREAKPOINT", res) # check the mask of the breakpoint address address = int(res.split()[0], 16) diff --git a/tests/commands/print_format.py b/tests/commands/print_format.py index 6686199ac..3dc5dd408 100644 --- a/tests/commands/print_format.py +++ b/tests/commands/print_format.py @@ -3,38 +3,41 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd, gdb_start_silent_cmd +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE -class PrintFormatCommand(GefUnitTestGeneric): +class PrintFormatCommand(RemoteGefUnitTestGeneric): """`print-format` command test module""" - def test_cmd_print_format(self): - self.assertFailIfInactiveSession(gdb_run_cmd("print-format")) - res = gdb_start_silent_cmd("print-format $sp") - self.assertNoException(res) - self.assertIn("buf = [" , res) - res = gdb_start_silent_cmd("print-format --lang js $sp") - self.assertNoException(res) - self.assertIn("var buf = [" , res) - res = gdb_start_silent_cmd("set *((int*)$sp) = 0x41414141", - after=["print-format --lang hex $sp"]) - self.assertNoException(res) + gdb = self._gdb + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute("print-format", to_string=True) + ) + gdb.execute("start") + res = gdb.execute("print-format $sp", to_string=True) + self.assertIn("buf = [", res) + res = gdb.execute("start") + gdb.execute("print-format --lang js $sp", to_string=True) + self.assertIn("var buf = [", res) + res = gdb.execute("start") + gdb.execute("set *((int*)$sp, to_string=True) = 0x41414141") + res = gdb.execute("print-format --lang hex $sp", to_string=True) self.assertIn("41414141", res, f"{res}") - res = gdb_start_silent_cmd("print-format --lang iDontExist $sp") - self.assertNoException(res) - self.assertIn("Language must be in:" , res) - + res = gdb.execute("start") + gdb.execute("print-format --lang iDontExist $sp", to_string=True) + self.assertIn("Language must be in:", res) def test_cmd_print_format_bytearray(self): - res = gdb_start_silent_cmd("set *((int*)$sp) = 0x41414141", - after=["print-format --lang bytearray -l 4 $sp"]) - self.assertNoException(res) - gef_var = res.split('$_gef')[1].split("'")[0] + gdb = self._gdb + res = gdb.execute("start") + gdb.execute("set *((int*)$sp, to_string=True) = 0x41414141") + res = gdb.execute("print-format --lang bytearray -l 4 $sp", to_string=True) + gef_var = res.split("$_gef")[1].split("'")[0] self.assertTrue("\x41\x41\x41\x41" in res) - res = gdb_start_silent_cmd("set *((int*)$sp) = 0x41414141", - after=["print-format --lang bytearray -l 4 $sp", "p $_gef" + gef_var]) - self.assertNoException(res) - self.assertIn( - f"Saved data b'AAAA'... in '$_gef{gef_var}'", res) + res = gdb.execute("start") + gdb.execute("set *((int*)$sp, to_string=True) = 0x41414141") + gdb.execute("print-format --lang bytearray -l 4 $sp") + res = gdb.execute("p $_gef" + gef_var, to_string=True) + self.assertIn(f"Saved data b'AAAA'... in '$_gef{gef_var}'", res) diff --git a/tests/commands/process_search.py b/tests/commands/process_search.py index 4fe58e985..33faa4eed 100644 --- a/tests/commands/process_search.py +++ b/tests/commands/process_search.py @@ -3,27 +3,34 @@ """ -from tests.utils import debug_target, gdb_start_silent_cmd -from tests.utils import GefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import debug_target -class ProcessSearchCommand(GefUnitTestGeneric): +class ProcessSearchCommand(RemoteGefUnitTestGeneric): """`process-search` command test module""" - - def test_cmd_process_search(self): - target = debug_target("pattern") - res = gdb_start_silent_cmd("process-search", target=target, - before=["set args w00tw00t"]) - self.assertNoException(res) - self.assertIn(str(target), res) - - res = gdb_start_silent_cmd("process-search gdb.*fakefake", - target=target, before=["set args w00tw00t"]) - self.assertNoException(res) + def setUp(self) -> None: + self._target = debug_target("pattern") + return super().setUp() + + def test_cmd_process_search1(self): + gdb = self._gdb + gdb.execute("set args w00tw00t") + gdb.execute("start") + res = gdb.execute("process-search", to_string=True) + self.assertIn(str(self._target), res) + + def test_cmd_process_search2(self): + gdb = self._gdb + gdb.execute("set args w00tw00t") + gdb.execute("start") + res = gdb.execute("process-search gdb.*fakefake", to_string=True) self.assertIn("gdb", res) - res = gdb_start_silent_cmd("process-search --smart-scan gdb.*fakefake", - target=target, before=["set args w00tw00t"]) - self.assertNoException(res) + def test_cmd_process_search3(self): + gdb = self._gdb + gdb.execute("set args w00tw00t") + gdb.execute("start") + res = gdb.execute("process-search --smart-scan gdb.*fakefake", to_string=True) self.assertNotIn("gdb", res) diff --git a/tests/commands/process_status.py b/tests/commands/process_status.py index 1a7d91e06..ae9ff5f2b 100644 --- a/tests/commands/process_status.py +++ b/tests/commands/process_status.py @@ -3,18 +3,21 @@ """ -from tests.utils import gdb_run_cmd, gdb_start_silent_cmd -from tests.utils import GefUnitTestGeneric +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE -class ProcessStatusCommand(GefUnitTestGeneric): +class ProcessStatusCommand(RemoteGefUnitTestGeneric): """`process-status` command test module""" - def test_cmd_process_status(self): - self.assertFailIfInactiveSession(gdb_run_cmd("process-status")) - res = gdb_start_silent_cmd("process-status") - self.assertNoException(res) + gdb = self._gdb + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, + gdb.execute("process-status", to_string=True), + ) + gdb.execute("start") + res = gdb.execute("process-status", to_string=True) self.assertIn("Process Information", res) self.assertIn("No child process", res) self.assertIn("No open connections", res) diff --git a/tests/commands/registers.py b/tests/commands/registers.py index 3e9e3da36..3de10c5c2 100644 --- a/tests/commands/registers.py +++ b/tests/commands/registers.py @@ -4,29 +4,37 @@ import pytest -from tests.utils import ARCH, GefUnitTestGeneric, gdb_run_cmd, gdb_start_silent_cmd +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ARCH, ERROR_INACTIVE_SESSION_MESSAGE -class RegistersCommand(GefUnitTestGeneric): - """`registers` command test module""" +class RegistersCommand(RemoteGefUnitTestGeneric): + """`registers` command test module""" - @pytest.mark.skipif(ARCH not in ["aarch64", "armv7l", "x86_64", "i686"], - reason=f"Skipped for {ARCH}") + @pytest.mark.skipif( + ARCH not in ["aarch64", "armv7l", "x86_64", "i686"], + reason=f"Skipped for {ARCH}", + ) def test_cmd_registers(self): - self.assertFailIfInactiveSession(gdb_run_cmd("registers")) - res = gdb_start_silent_cmd("registers") - self.assertNoException(res) - if ARCH in ("aarch64",): + gdb = self._gdb + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute("registers", to_string=True) + ) + + gdb.execute("start") + res = gdb.execute("registers", to_string=True) + + if ARCH in ("aarch64"): self.assertIn("$x0", res) self.assertIn("$cpsr", res) - elif ARCH in ("armv7l", ): + elif ARCH in ("armv7l",): self.assertIn("$r0", res) self.assertIn("$lr", res) self.assertIn("$cpsr", res) - elif ARCH in ("x86_64", ): + elif ARCH in ("x86_64",): self.assertIn("$rax", res) self.assertIn("$eflags", res) - elif ARCH in ("i686", ): + elif ARCH in ("i686",): self.assertIn("$eax", res) self.assertIn("$eflags", res) diff --git a/tests/commands/reset_cache.py b/tests/commands/reset_cache.py index fda8d4106..9d9c2ecef 100644 --- a/tests/commands/reset_cache.py +++ b/tests/commands/reset_cache.py @@ -3,13 +3,16 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_start_silent_cmd +from tests.base import RemoteGefUnitTestGeneric -class ResetCacheCommand(GefUnitTestGeneric): +class ResetCacheCommand(RemoteGefUnitTestGeneric): """`reset-cache` command test module""" def test_cmd_reset_cache(self): - res = gdb_start_silent_cmd("reset-cache") - self.assertNoException(res) + gdb = self._gdb + gdb.execute("start") + res = gdb.execute("reset-cache", to_string=True) + assert res + # TODO improve diff --git a/tests/commands/scan.py b/tests/commands/scan.py index 54b5b9f60..b32c636d7 100644 --- a/tests/commands/scan.py +++ b/tests/commands/scan.py @@ -3,21 +3,27 @@ """ -from tests.utils import GefUnitTestGeneric, debug_target, gdb_run_cmd, gdb_start_silent_cmd +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE, debug_target -class ScanCommand(GefUnitTestGeneric): +class ScanCommand(RemoteGefUnitTestGeneric): """`scan` command test module""" + def setUp(self) -> None: + self._target = debug_target("checksec-no-pie") + return super().setUp() def test_cmd_scan(self): + gdb = self._gdb cmd = "scan libc stack" - target = debug_target("checksec-no-pie") - self.assertFailIfInactiveSession(gdb_run_cmd(cmd)) - res = gdb_start_silent_cmd(cmd, target=target) - self.assertNoException(res) - self.assertIn(str(target), res) + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + gdb.execute("start") + res = gdb.execute(cmd, to_string=True) + self.assertIn(str(self._target), res) - res = gdb_start_silent_cmd("scan binary libc") - self.assertNoException(res) + gdb.execute("start") + res = gdb.execute("scan binary libc", to_string=True) self.assertIn("__libc_start_main", res) diff --git a/tests/commands/search_pattern.py b/tests/commands/search_pattern.py index c8c3be6d1..278dff613 100644 --- a/tests/commands/search_pattern.py +++ b/tests/commands/search_pattern.py @@ -3,26 +3,37 @@ """ -from tests.utils import BIN_SH, GefUnitTestGeneric, gdb_run_cmd, gdb_start_silent_cmd, gdb_start_silent_cmd_last_line +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import BIN_SH, ERROR_INACTIVE_SESSION_MESSAGE -class SearchPatternCommand(GefUnitTestGeneric): +class SearchPatternCommand(RemoteGefUnitTestGeneric): """`search_pattern` command test module""" - def test_cmd_search_pattern(self): - self.assertFailIfInactiveSession(gdb_run_cmd(f"grep {BIN_SH}")) - res = gdb_start_silent_cmd(f"grep {BIN_SH}") - self.assertNoException(res) + gdb = self._gdb + cmd = f"grep {BIN_SH}" + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + gdb.execute("start") + res = gdb.execute(f"grep {BIN_SH}", to_string=True) self.assertIn("0x", res) def test_cmd_search_pattern_regex(self): - res = gdb_start_silent_cmd_last_line("set {char[6]} $sp = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x00 }", -after=[r"search-pattern --regex $sp $sp+7 ([\\x20-\\x7E]{2,})(?=\\x00)",]) - self.assertNoException(res) + gdb = self._gdb + gdb.execute("start") + gdb.execute("set {char[6]} $sp = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x00 }") + res = gdb.execute( + r"search-pattern --regex $sp $sp+7 ([\\x20-\\x7E]{2,})(?=\\x00)", + to_string=True + ) self.assertTrue(r"b'ABCDE'" in res) + # this should not match because binary string is not null ended: - res = gdb_start_silent_cmd_last_line("set {char[6]} $sp = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x03 }", -after=[r"search-pattern --regex $sp $sp+7 ([\\x20-\\x7E]{2,})(?=\\x00)",]) - self.assertNoException(res) - self.assertTrue(r"b'ABCDE'" not in res) + res = gdb.execute("set {char[6]} $sp = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x03 }") + gdb.execute( + r"search-pattern --regex $sp $sp+7 ([\\x20-\\x7E]{2,})(?=\\x00)", + to_string=True + ) + self.assertNotIn(r"b'ABCDE'", res) diff --git a/tests/commands/shellcode.py b/tests/commands/shellcode.py index c1db51aeb..85495996c 100644 --- a/tests/commands/shellcode.py +++ b/tests/commands/shellcode.py @@ -2,40 +2,42 @@ Shellcode commands test module """ import pytest +from tests.base import RemoteGefUnitTestGeneric -from tests.utils import gdb_start_silent_cmd, GefUnitTestGeneric, BIN_SH +from tests.utils import BIN_SH -class ShellcodeCommand(GefUnitTestGeneric): +class ShellcodeCommand(RemoteGefUnitTestGeneric): """`shellcode` command test module""" def test_cmd_shellcode(self): - res = gdb_start_silent_cmd("shellcode") - self.assertNoException(res) + gdb = self._gdb + gdb.execute("start") + res = gdb.execute("shellcode", to_string=True) self.assertIn("Missing sub-command (search|get)", res) - @pytest.mark.online @pytest.mark.slow def test_cmd_shellcode_search(self): + gdb = self._gdb cmd = f"shellcode search execve {BIN_SH}" - res = gdb_start_silent_cmd(cmd) - self.assertNoException(res) + gdb.execute("start") + res = gdb.execute(cmd, to_string=True) self.assertIn(f"setuid(0) + execve({BIN_SH}) 49 bytes", res) - @pytest.mark.online @pytest.mark.slow def test_cmd_shellcode_get_ok(self): - res = gdb_start_silent_cmd("shellcode get 77") - self.assertNoException(res) + gdb = self._gdb + gdb.execute("start") + res = gdb.execute("shellcode get 77", to_string=True) self.assertIn("Shellcode written to ", res) - @pytest.mark.online @pytest.mark.slow def test_cmd_shellcode_get_nok(self): + gdb = self._gdb n = 1111111111111 - res = gdb_start_silent_cmd(f"shellcode get {n}") - self.assertNoException(res) + gdb.execute("start") + res = gdb.execute(f"shellcode get {n}", to_string=True) self.assertIn(f"Failed to fetch shellcode #{n}", res) diff --git a/tests/commands/skipi.py b/tests/commands/skipi.py index 79f6faa2c..7975a1624 100644 --- a/tests/commands/skipi.py +++ b/tests/commands/skipi.py @@ -4,59 +4,54 @@ import pytest -from tests.utils import (ARCH, GefUnitTestGeneric, debug_target, findlines, - gdb_run_cmd, gdb_run_silent_cmd, gdb_start_silent_cmd) +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ARCH, ERROR_INACTIVE_SESSION_MESSAGE, p32, p64, u16, u32 -class SkipiCommand(GefUnitTestGeneric): +class SkipiCommand(RemoteGefUnitTestGeneric): """`skipi` command test module""" - cmd = "skipi" - def test_cmd_nop_inactive(self): - res = gdb_run_cmd(f"{self.cmd}") - self.assertFailIfInactiveSession(res) - + gdb = self._gdb + res = gdb.execute(f"{self.cmd}", to_string=True) + self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE, res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_skipi_no_arg(self): + gdb = self._gdb + gef = self._gef - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p32(0x9090feeb))", # 1 short jumps to pc + 2 nops - after=( - self.cmd, - "pi print(gef.memory.read(gef.arch.pc, 2))", # read 2 bytes - ) - ) - self.assertNoException(res) - self.assertIn(r"\x90\x90", res) # 2 nops - + gdb.execute("start") + gef.memory.write(gef.arch.pc, p32(0x9090FEEB)) + res = gdb.execute(self.cmd, to_string=True) + assert res + mem = u16(gef.memory.read(gef.arch.pc, 2)) # read 2 bytes + self.assertEqual(0x9090, mem) # 2 nops @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_skipi_skip_two_instructions(self): + gdb = self._gdb + gef = self._gef - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p64(0x90909090feebfeeb))", # 2 short jumps to pc + 4 nops - after=( - f"{self.cmd} --n 2", - "pi print(gef.memory.read(gef.arch.pc, 4))", # read 4 bytes - ) - ) - self.assertNoException(res) - self.assertIn(r"\x90\x90\x90\x90", res) # 4 nops - + gdb.execute("start") + gef.memory.write(gef.arch.pc, p64(0x90909090FEEBFEEB)) + res = gdb.execute(f"{self.cmd} --n 2", to_string=True) + assert res + mem = u32(gef.memory.read(gef.arch.pc, 4)) # read 4 bytes + self.assertEqual(0x90909090, mem) # 4 nops @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_skipi_two_instructions_from_location(self): + gdb = self._gdb + gef = self._gef - res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p64(0x9090feebfeebfeeb))", # 2 short jumps to pc + 2 nops - after=( - f"{self.cmd} $pc+2 --n 2", # from the second short jump - "pi print(gef.memory.read(gef.arch.pc, 2))", # read 2 bytes - ) + gdb.execute("start") + gef.memory.write(gef.arch.pc, p64(0x9090FEEBFEEBFEEB)) + res = gdb.execute( + f"{self.cmd} $pc+2 --n 2", to_string=True # from the second short jump ) - self.assertNoException(res) - self.assertIn(r"\x90\x90", res) # 2 nops + assert res + mem = u16(gef.memory.read(gef.arch.pc, 2)) # read 2 bytes + self.assertEqual(0x9090, mem) # 2 nops diff --git a/tests/commands/smart_eval.py b/tests/commands/smart_eval.py index 923bba458..e4ae004e0 100644 --- a/tests/commands/smart_eval.py +++ b/tests/commands/smart_eval.py @@ -3,21 +3,23 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_start_silent_cmd +from tests.base import RemoteGefUnitTestGeneric -class SmartEvalCommand(GefUnitTestGeneric): +class SmartEvalCommand(RemoteGefUnitTestGeneric): """`smart_eval` command test module""" def test_cmd_smart_eval(self): + gdb = self._gdb examples = ( ("$ $pc+1", ""), ("$ -0x1000", "-4096"), ("$ 0x00007ffff7812000 0x00007ffff79a7000", "1658880"), ("$ 1658880", "0b110010101000000000000"), ) + + gdb.execute("start") for cmd, expected_value in examples: - res = gdb_start_silent_cmd(cmd) - self.assertNoException(res) - self.assertIn(expected_value, res) + res = gdb.execute(cmd, to_string=True).strip() + self.assertEqual(expected_value, res) diff --git a/tests/commands/stub.py b/tests/commands/stub.py index b87db50f7..a6e339a0d 100644 --- a/tests/commands/stub.py +++ b/tests/commands/stub.py @@ -3,20 +3,28 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd, gdb_start_silent_cmd +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE -class StubCommand(GefUnitTestGeneric): +class StubCommand(RemoteGefUnitTestGeneric): """`stub` command test module""" - def test_cmd_stub(self): + gdb = self._gdb # due to compiler optimizations printf might be converted to puts - cmds = ["stub printf", "stub puts"] - self.assertFailIfInactiveSession(gdb_run_cmd(cmds)) - res = gdb_start_silent_cmd("continue") - self.assertNoException(res) + cmds = ("stub printf", "stub puts") + for cmd in cmds: + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ) + + gdb.execute("start") + res = gdb.execute("continue", to_string=True) self.assertIn("Hello World!", res) - res = gdb_start_silent_cmd(cmds, after=["continue"]) - self.assertNoException(res) - self.assertNotIn("Hello World!", res) + + for cmd in cmds: + gdb.execute("start") + res = gdb.execute(cmd, to_string=True) + self.assertNotIn("Hello World!", res) + gdb.execute("continue") diff --git a/tests/commands/theme.py b/tests/commands/theme.py index 7c6ca07df..02c906623 100644 --- a/tests/commands/theme.py +++ b/tests/commands/theme.py @@ -3,16 +3,15 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd +from tests.base import RemoteGefUnitTestGeneric -class ThemeCommand(GefUnitTestGeneric): +class ThemeCommand(RemoteGefUnitTestGeneric): """`theme` command test module""" - def test_cmd_theme(self): - res = gdb_run_cmd("theme") - self.assertNoException(res) + gdb = self._gdb + res = gdb.execute("theme", to_string=True) possible_themes = ( "context_title_line", "context_title_message", @@ -34,16 +33,13 @@ def test_cmd_theme(self): ) for t in possible_themes: # testing command viewing - res = gdb_run_cmd(f"theme {t}") - self.assertNoException(res) + res = gdb.execute(f"theme {t}", to_string=True) self.assertNotIn("Invalid key", res, f"Invalid key '{t}'") # testing command setting v = "blue blah 10 -1 0xfff bold" - res = gdb_run_cmd(f"theme {t} {v}") - self.assertNoException(res) + res = gdb.execute(f"theme {t} {v}", to_string=True) + assert res - res = gdb_run_cmd(f"theme ___I_DONT_EXIST___") - self.assertNoException(res) + res = gdb.execute(f"theme ___I_DONT_EXIST___", to_string=True) self.assertIn("Invalid key", res) - return diff --git a/tests/commands/trace_run.py b/tests/commands/trace_run.py index 5b49016b6..34b561353 100644 --- a/tests/commands/trace_run.py +++ b/tests/commands/trace_run.py @@ -3,22 +3,21 @@ """ -from tests.utils import TMPDIR, GefUnitTestGeneric, gdb_run_cmd, gdb_start_silent_cmd +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import TMPDIR, ERROR_INACTIVE_SESSION_MESSAGE -class TraceRunCommand(GefUnitTestGeneric): +class TraceRunCommand(RemoteGefUnitTestGeneric): """`trace-run` command test module""" - def test_cmd_trace_run(self): + gdb = self._gdb cmd = "trace-run" - res = gdb_run_cmd(cmd) - self.assertFailIfInactiveSession(res) + res = gdb.execute(cmd, to_string=True) + self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE, res) cmd = "trace-run $pc+1" - res = gdb_start_silent_cmd( - cmd, - before=[f"gef config trace-run.tracefile_prefix {TMPDIR / 'gef-trace-'}"] - ) - self.assertNoException(res) + gdb.execute("start") + gdb.execute(f"gef config trace-run.tracefile_prefix {TMPDIR / 'gef-trace-'}") + res = gdb.execute(cmd, to_string=True) self.assertIn("Tracing from", res) diff --git a/tests/commands/version.py b/tests/commands/version.py index 7dd48c10f..332abb6ae 100644 --- a/tests/commands/version.py +++ b/tests/commands/version.py @@ -3,16 +3,14 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd +from tests.base import RemoteGefUnitTestGeneric -class VersionCommand(GefUnitTestGeneric): +class VersionCommand(RemoteGefUnitTestGeneric): """`version` command test module""" - cmd = "version" - def test_cmd_version(self): - res = gdb_run_cmd(self.cmd) - self.assertNoException(res) + gdb = self._gdb + gdb.execute(self.cmd) diff --git a/tests/commands/vmmap.py b/tests/commands/vmmap.py index 225011b96..50dfeacaa 100644 --- a/tests/commands/vmmap.py +++ b/tests/commands/vmmap.py @@ -3,18 +3,21 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd, gdb_start_silent_cmd +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE -class VmmapCommand(GefUnitTestGeneric): +class VmmapCommand(RemoteGefUnitTestGeneric): """`vmmap` command test module""" def test_cmd_vmmap(self): - self.assertFailIfInactiveSession(gdb_run_cmd("vmmap")) - res = gdb_start_silent_cmd("vmmap") - self.assertNoException(res) - self.assertTrue(len(res.splitlines()) > 1) + gdb = self._gdb + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute("vmmap", to_string=True) + ) + gdb.execute("start") + res = gdb.execute("vmmap", to_string=True) + self.assertGreater(len(res.splitlines()), 1) - res = gdb_start_silent_cmd("vmmap stack") - self.assertNoException(res) - self.assertTrue(len(res.splitlines()) > 1) + res = gdb.execute("vmmap stack", to_string=True) + self.assertGreater(len(res.splitlines()), 1) diff --git a/tests/commands/xfiles.py b/tests/commands/xfiles.py index 8551d11b2..0833da06c 100644 --- a/tests/commands/xfiles.py +++ b/tests/commands/xfiles.py @@ -3,15 +3,18 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd, gdb_start_silent_cmd +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE -class XfilesCommand(GefUnitTestGeneric): +class XfilesCommand(RemoteGefUnitTestGeneric): """`xfiles` command test module""" - def test_cmd_xfiles(self): - self.assertFailIfInactiveSession(gdb_run_cmd("xfiles")) - res = gdb_start_silent_cmd("xfiles") - self.assertNoException(res) - self.assertTrue(len(res.splitlines()) >= 3) + gdb = self._gdb + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute("xfiles", to_string=True) + ) + gdb.execute("start") + res = gdb.execute("xfiles", to_string=True) + self.assertGreaterEqual(len(res.splitlines()) , 3) diff --git a/tests/commands/xinfo.py b/tests/commands/xinfo.py index ea50e23b9..7d53aa27d 100644 --- a/tests/commands/xinfo.py +++ b/tests/commands/xinfo.py @@ -3,25 +3,35 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd, gdb_start_silent_cmd, gdb_run_silent_cmd, debug_target +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE, debug_target -class XinfoCommand(GefUnitTestGeneric): +class XinfoCommand(RemoteGefUnitTestGeneric): """`xinfo` command test module""" - def test_cmd_xinfo(self): - self.assertFailIfInactiveSession(gdb_run_cmd("xinfo $sp")) - res = gdb_start_silent_cmd("xinfo") + gdb = self._gdb + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute("xinfo $sp", to_string=True) + ) + + gdb.execute("start") + res = gdb.execute("xinfo", to_string=True) self.assertIn("At least one valid address must be specified", res) - res = gdb_start_silent_cmd("xinfo $sp") - self.assertNoException(res) - self.assertTrue(len(res.splitlines()) >= 7) + res = gdb.execute("xinfo $sp", to_string=True) + self.assertGreaterEqual(len(res.splitlines()), 7) + + +class XinfoCommandClass(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = debug_target("class") + return super().setUp() def test_cmd_xinfo_on_class(self): + gdb = self._gdb cmd = "xinfo $pc+4" - target = debug_target("class") - res = gdb_run_silent_cmd(cmd, target=target, before=["b *'B::Run'"]) - self.assertNoException(res) + gdb.execute("b *'B::Run'") + res = gdb.execute(cmd, to_string=True) self.assertIn("Symbol: B::Run()+4", res) diff --git a/tests/commands/xor_memory.py b/tests/commands/xor_memory.py index c68b15665..3aa4fc2e9 100644 --- a/tests/commands/xor_memory.py +++ b/tests/commands/xor_memory.py @@ -3,24 +3,28 @@ """ -from tests.utils import GefUnitTestGeneric, gdb_run_cmd, gdb_start_silent_cmd +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE - -class XorMemoryCommand(GefUnitTestGeneric): +class XorMemoryCommand(RemoteGefUnitTestGeneric): """`xor-memory` command test module""" def test_cmd_xor_memory_display(self): + gdb = self._gdb cmd = "xor-memory display $sp 0x10 0x41" - self.assertFailIfInactiveSession(gdb_run_cmd(cmd)) - res = gdb_start_silent_cmd(cmd) - self.assertNoException(res) - self.assertIn("Original block", res) - self.assertIn("XOR-ed block", res) + self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True)) + + gdb.execute("start") + res = gdb.execute(cmd) + assert "Original block" in res + assert "XOR-ed block" in res def test_cmd_xor_memory_patch(self): + gdb = self._gdb + gdb.execute("start") + cmd = "xor-memory patch $sp 0x10 0x41" - res = gdb_start_silent_cmd(cmd) - self.assertNoException(res) - self.assertIn("Patching XOR-ing ", res) + res = gdb.execute(cmd, to_string=True) + assert "Patching XOR-ing " in res diff --git a/tests/regressions/gdbserver_connection.py b/tests/regressions/gdbserver_connection.py index da3784581..2c0421bea 100644 --- a/tests/regressions/gdbserver_connection.py +++ b/tests/regressions/gdbserver_connection.py @@ -5,7 +5,7 @@ ) -class RegressionGdbserverConnection(GefUnitTestGeneric): +class RegressionGdbserverConnection(RemoteGefUnitTestGeneric): def test_can_establish_connection_to_gdbserver_again_after_disconnect(self): """Ensure that gdb can connect to a gdbserver again after disconnecting (PR #896).""" diff --git a/tests/regressions/registers_register_order.py b/tests/regressions/registers_register_order.py index a8845d6c2..a183634a8 100644 --- a/tests/regressions/registers_register_order.py +++ b/tests/regressions/registers_register_order.py @@ -9,7 +9,7 @@ ) -class RegressionRegisterOrder(GefUnitTestGeneric): +class RegressionRegisterOrder(RemoteGefUnitTestGeneric): """Tests for regression in the order registers are displayed by `registers` .""" @pytest.mark.skipif(ARCH not in ["x86_64", "i686"], reason=f"Skipped for {ARCH}") @@ -25,7 +25,8 @@ def test_registers_show_registers_in_correct_order(self): "$r13", "$r14", "$r15", "$eflags", "$cs"] else: raise ValueError("Unknown architecture") - lines = gdb_start_silent_cmd(cmd).splitlines()[-len(registers_in_correct_order):] + lines = gdb.execute("start") +gdb.execute(cmd).splitlines()[-len(registers_in_correct_order, to_string=True):] lines = [line.split()[0].replace(':', '') for line in lines] self.assertEqual(registers_in_correct_order, lines) diff --git a/tests/utils.py b/tests/utils.py index f2735623b..deb50a9b4 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -475,33 +475,33 @@ def download_file(url: str) -> Optional[bytes]: return None -def u8(x): +def u8(x: bytes) -> int: return struct.unpack(" int: return struct.unpack(" int: return struct.unpack(" int: return struct.unpack(" bytes: + return struct.pack(" bytes: return struct.pack(" bytes: return struct.pack(" bytes: return struct.pack(" Date: Sun, 7 Jan 2024 13:36:35 -0800 Subject: [PATCH 16/35] test fixes 1/N --- gef.py | 2 +- tests/commands/canary.py | 4 ++-- tests/commands/entry_break.py | 11 ++++++++--- tests/commands/format_string_helper.py | 7 +++++-- tests/commands/gef.py | 14 ++++++++------ tests/utils.py | 8 ++++---- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/gef.py b/gef.py index ef16f6236..565434c7d 100644 --- a/gef.py +++ b/gef.py @@ -10025,7 +10025,7 @@ def invoke(self, args: Any, from_tty: bool) -> None: # save the aliases cfg.add_section("aliases") for alias in gef.session.aliases: - cfg.set("aliases", alias._alias, alias._command) + cfg.set("aliases", alias.alias, alias.command) with GEF_RC.open("w") as fd: cfg.write(fd) diff --git a/tests/commands/canary.py b/tests/commands/canary.py index e6456dbb3..840664658 100644 --- a/tests/commands/canary.py +++ b/tests/commands/canary.py @@ -3,7 +3,7 @@ """ -from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE, debug_target, p64, p32, is_64b +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE, debug_target, p64, p32, is_64b, u32 from tests.base import RemoteGefUnitTestGeneric class CanaryCommand(RemoteGefUnitTestGeneric): @@ -30,5 +30,5 @@ def test_overwrite_canary(self): gef.memory.write(gef.arch.canary_address(), p64(0xdeadbeef)) else: gef.memory.write(gef.arch.canary_address(), p32(0xdeadbeef)) - res = gef.memory.read(gef.arch.canary_address(), gef.arch.ptrsize) + res = u32(gef.memory.read(gef.arch.canary_address(), gef.arch.ptrsize)) assert 0xdeadbeef == res diff --git a/tests/commands/entry_break.py b/tests/commands/entry_break.py index 8a4250a9a..96a94965c 100644 --- a/tests/commands/entry_break.py +++ b/tests/commands/entry_break.py @@ -6,11 +6,16 @@ from tests.base import RemoteGefUnitTestGeneric - class EntryBreakCommand(RemoteGefUnitTestGeneric): """`entry-break` command test module""" - def test_cmd_entry_break(self): - res = self._gdb.execute("entry-break", to_string=True).strip() + gdb = self._gdb + + # run once (ok) + res = gdb.execute("entry-break", to_string=True).strip() + assert res.startswith("[+] Breaking at") + + # re-run while session running (nok) + res = gdb.execute("entry-break", to_string=True).strip() assert "gdb is already running" in res diff --git a/tests/commands/format_string_helper.py b/tests/commands/format_string_helper.py index 3218631ce..6d11fcdcd 100644 --- a/tests/commands/format_string_helper.py +++ b/tests/commands/format_string_helper.py @@ -7,7 +7,6 @@ from tests.utils import debug_target - class FormatStringHelperCommand(RemoteGefUnitTestGeneric): """`format-string-helper` command test module""" @@ -19,6 +18,10 @@ def test_cmd_format_string_helper(self): gdb = self._gdb gdb.execute("set args testtest") - gdb.execute("run") res = gdb.execute("format-string-helper", to_string=True) + assert res.endswith( + "[+] Enabled 5 FormatString breakpoints\n" + ) + + res = gdb.execute("run", to_string=True) assert "Possible insecure format string:" in res diff --git a/tests/commands/gef.py b/tests/commands/gef.py index 27398e5de..b8b56a6df 100644 --- a/tests/commands/gef.py +++ b/tests/commands/gef.py @@ -39,7 +39,7 @@ def test_cmd_gef_config(self): def test_cmd_gef_config_get(self): gdb = self._gdb - res = gdb.execute("gef config gef.debug") + res = gdb.execute("gef config gef.debug", to_string=True) self.assertIn("GEF configuration setting: gef.debug", res) # the `True` is automatically set by `gdb_run_cmd` so we know it's there self.assertIn( @@ -51,9 +51,11 @@ def test_cmd_gef_config_set(self): gdb = self._gdb root = self._conn.root - assert root.is_debug() == True + gdb.execute("gef config gef.debug 1") + assert root.eval("is_debug()") + gdb.execute("gef config gef.debug 0") - assert root.is_debug() == False + assert not root.eval("is_debug()") def test_cmd_gef_help(self): gdb = self._gdb @@ -75,10 +77,10 @@ def test_cmd_gef_run_and_run(self): gdb = self._gdb # valid - pattern = gdb.execute("pattern create -n 4", to_string=True).splitlines()[2] + pattern = gdb.execute("pattern create -n 4", to_string=True).splitlines()[1] assert len(pattern) == 1024 res = gdb.execute("gef set args $_gef0") - res = gdb.execute("show args", to_string=True) + res = gdb.execute("show args", to_string=True).strip() assert ( res == f'Argument list to give program being debugged when it is started is "{pattern}".' @@ -93,7 +95,7 @@ def test_cmd_gef_save(self): gdb = self._gdb # check - res = gdb.execute("gef save", to_string=True) + res = gdb.execute("gef save", to_string=True).strip() self.assertIn("Configuration saved to '", res) gefrc_file = removeuntil("Configuration saved to '", res.rstrip("'")) diff --git a/tests/utils.py b/tests/utils.py index deb50a9b4..19b67f616 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -476,19 +476,19 @@ def download_file(url: str) -> Optional[bytes]: def u8(x: bytes) -> int: - return struct.unpack(" int: - return struct.unpack(" int: - return struct.unpack(" int: - return struct.unpack(" bytes: From f49cf52900d048dd0b84c537639c6ca882e7e7b4 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 13:59:43 -0800 Subject: [PATCH 17/35] test fixes 2/N --- tests/commands/edit_flags.py | 17 +++++++++++------ tests/commands/gef_remote.py | 21 +++++++++++---------- tests/commands/got.py | 7 +++++-- tests/commands/heap_analysis.py | 4 +++- tests/commands/highlight.py | 6 ++++-- tests/commands/memory.py | 4 ++++ tests/commands/name_break.py | 1 + 7 files changed, 39 insertions(+), 21 deletions(-) diff --git a/tests/commands/edit_flags.py b/tests/commands/edit_flags.py index ae521591d..e082587f2 100644 --- a/tests/commands/edit_flags.py +++ b/tests/commands/edit_flags.py @@ -6,10 +6,10 @@ import pytest from tests.base import RemoteGefUnitTestGeneric -from tests.utils import ARCH +from tests.utils import ARCH, ERROR_INACTIVE_SESSION_MESSAGE -@pytest.mark.skipif(ARCH in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") +@pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") class EditFlagsCommand(RemoteGefUnitTestGeneric): """`edit-flags` command test module""" @@ -17,7 +17,7 @@ def test_cmd_edit_flags_disable(self): gdb = self._gdb gef = self._gef - with pytest.raises(gdb.error): + with pytest.raises(Exception, match="No debugging session active"): gdb.execute("edit-flags") gdb.execute("start") @@ -25,7 +25,8 @@ def test_cmd_edit_flags_disable(self): assert res.startswith("[") and res.endswith("]") # pick first flag - idx, name = next(gef.arch.flags_table) + idx = list(gef.arch.flags_table.keys())[0] + name = gef.arch.flags_table[idx] gdb.execute(f"edit-flags -{name}") assert gef.arch.register(gef.arch.flag_register) & (1 << idx) == 0 @@ -34,7 +35,8 @@ def test_cmd_edit_flags_enable(self): gef = self._gef gdb.execute("start") - idx, name = next(gef.arch.flags_table) + idx = list(gef.arch.flags_table.keys())[0] + name = gef.arch.flags_table[idx] gdb.execute(f"edit-flags +{name}") assert gef.arch.register(gef.arch.flag_register) & (1 << idx) != 0 @@ -42,8 +44,11 @@ def test_cmd_edit_flags_toggle(self): gdb = self._gdb gef = self._gef - idx, name = next(gef.arch.flags_table) + gdb.execute("start") + idx = list(gef.arch.flags_table.keys())[0] + name = gef.arch.flags_table[idx] init_val = gef.arch.register(gef.arch.flag_register) & (1 << idx) + gdb.execute(f"edit-flags ~{name}") new_val = gef.arch.register(gef.arch.flag_register) & (1 << idx) assert init_val != new_val diff --git a/tests/commands/gef_remote.py b/tests/commands/gef_remote.py index 7884994ca..d2ca7d026 100644 --- a/tests/commands/gef_remote.py +++ b/tests/commands/gef_remote.py @@ -23,44 +23,45 @@ def setUp(self) -> None: def test_cmd_gef_remote(self): gdb = self._gdb + root = self._conn.root while True: port = random.randint(1025, 65535) if port != self._port: break - gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") - with gdbserver_session(port=port): - res = gdb.execute( - "pi print(gef.session.remote)", to_string=True) + gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") + res = root.eval("str(gef.session.remote)") self.assertIn( f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/tmp/", res) self.assertIn(", qemu_user=False)", res) + @pytest.mark.slow def test_cmd_gef_remote_qemu_user(self): gdb = self._gdb + root = self._conn.root while True: port = random.randint(1025, 65535) if port != self._port: break - gdb.execute( - f"gef-remote --qemu-user --qemu-binary {self._target} {GDBSERVER_DEFAULT_HOST} {port}") with qemuuser_session(port=port): - res = gdb.execute( - "pi print(gef.session.remote)", to_string=True) + cmd = f"gef-remote --qemu-user --qemu-binary {self._target} {GDBSERVER_DEFAULT_HOST} {port}" + gdb.execute(cmd) + res = root.eval("str(gef.session.remote)") assert f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/tmp/" in res assert ", qemu_user=True)" in res def test_cmd_target_remote(self): gdb = self._gdb + root = self._conn.root while True: port = random.randint(1025, 65535) if port != self._port: break - gdb.execute(f"target remote {GDBSERVER_DEFAULT_HOST}:{port}") with gdbserver_session(port=port) as _: - res = gdb.execute("pi print(gef.session.remote)", to_string=True) + gdb.execute(f"target remote {GDBSERVER_DEFAULT_HOST}:{port}") + res = root.eval("str(gef.session.remote)") self.assertIn(f"RemoteSession(target=':0', local='/tmp/", res) self.assertIn(", qemu_user=False)", res) diff --git a/tests/commands/got.py b/tests/commands/got.py index 470f7f898..29e49f60d 100644 --- a/tests/commands/got.py +++ b/tests/commands/got.py @@ -17,11 +17,14 @@ class GotCommand(RemoteGefUnitTestGeneric): """`got` command test module""" + def setUp(self) -> None: + self._target = debug_target("format-string-helper") + return super().setUp() + def test_cmd_got(self): gdb = self._gdb - target = debug_target("format-string-helper") self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE,gdb.execute("got", to_string=True)) gdb.execute("start") @@ -29,6 +32,6 @@ def test_cmd_got(self): self.assertIn("printf", res) self.assertIn("strcpy", res) - res = gdb.execute("got printf", target=target, to_string=True) + res = gdb.execute("got printf", to_string=True) self.assertIn("printf", res) self.assertNotIn("strcpy", res) diff --git a/tests/commands/heap_analysis.py b/tests/commands/heap_analysis.py index 7436d6d19..1b7b6250b 100644 --- a/tests/commands/heap_analysis.py +++ b/tests/commands/heap_analysis.py @@ -24,9 +24,11 @@ def test_cmd_heap_analysis(self): ) gdb.execute("start") - res = gdb.execute(cmd, after=["continue"], to_string=True) + res = gdb.execute(cmd, to_string=True) self.assertIn("Tracking", res) self.assertIn("correctly setup", res) + + res = gdb.execute("continue", to_string=True) self.assertIn("malloc(16)=", res) self.assertIn("calloc(32)=", res) addr = int(res.split("calloc(32)=")[1].split("\n")[0], 0) diff --git a/tests/commands/highlight.py b/tests/commands/highlight.py index 0ff68e835..615d02750 100644 --- a/tests/commands/highlight.py +++ b/tests/commands/highlight.py @@ -4,7 +4,7 @@ from tests.base import RemoteGefUnitTestGeneric -from tests.utils import Color, ansi_clean +from tests.utils import Color class HighlightCommand(RemoteGefUnitTestGeneric): @@ -13,6 +13,8 @@ class HighlightCommand(RemoteGefUnitTestGeneric): def test_cmd_highlight(self): gdb = self._gdb + gdb.execute("gef config context.layout stack") + gdb.execute("gef config gef.disable_color 0") gdb.execute("start") for cmd in [ @@ -25,7 +27,7 @@ def test_cmd_highlight(self): ]: gdb.execute(cmd) - res = ansi_clean(gdb.execute("context", to_string=True)) + res = gdb.execute("context", to_string=True) self.assertIn(f"{Color.YELLOW.value}41414141{Color.NORMAL.value}", res) self.assertIn(f"{Color.BLUE.value}42424242{Color.NORMAL.value}", res) self.assertIn(f"{Color.GREEN.value}43434343{Color.NORMAL.value}", res) diff --git a/tests/commands/memory.py b/tests/commands/memory.py index 490f3f551..b8d15f2da 100644 --- a/tests/commands/memory.py +++ b/tests/commands/memory.py @@ -21,10 +21,12 @@ def setUp(self) -> None: def test_cmd_memory_watch_basic(self): gdb = self._gdb + self.assertEqual( ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute("memory watch $pc", to_string=True), ) + gdb.execute("start") # basic syntax checks @@ -43,6 +45,8 @@ def test_cmd_memory_watch_global_variable(self): gdb.execute("memory unwatch $pc", to_string=True), ) + gdb.execute("start") + gdb.execute("set args 0xdeadbeef") gdb.execute("memory watch &myglobal") gdb.execute("gef config context.layout memory") diff --git a/tests/commands/name_break.py b/tests/commands/name_break.py index 3983432ff..2155b53ef 100644 --- a/tests/commands/name_break.py +++ b/tests/commands/name_break.py @@ -11,6 +11,7 @@ class NameBreakCommand(RemoteGefUnitTestGeneric): def test_cmd_name_break(self): gdb = self._gdb + gdb.execute("start") res = gdb.execute("nb foobar *main+10", to_string=True) res = gdb.execute("nb foobar *0xcafebabe", to_string=True) self.assertIn("at 0xcafebabe", res) From 4afa908015e36fbec9736e48300a506b537f358a Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 14:13:41 -0800 Subject: [PATCH 18/35] test fixes 3/N --- gef.py | 2 +- tests/commands/patch.py | 44 ++++++++++++++++++++++------------------- tests/utils.py | 2 +- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/gef.py b/gef.py index 565434c7d..14fb2774e 100644 --- a/gef.py +++ b/gef.py @@ -692,7 +692,7 @@ def __str__(self) -> str: f"permissions={self.permission!s})") def __eq__(self, other: "Section") -> bool: - return \ + return other and \ self.page_start == other.page_start and \ self.page_end == other.page_end and \ self.offset == other.offset and \ diff --git a/tests/commands/patch.py b/tests/commands/patch.py index bf00a25b0..324c6064d 100644 --- a/tests/commands/patch.py +++ b/tests/commands/patch.py @@ -4,64 +4,68 @@ from tests.base import RemoteGefUnitTestGeneric -from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE, debug_target - +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE, debug_target, u16, u32, u64, u8 class PatchCommand(RemoteGefUnitTestGeneric): """`patch` command test module""" - def test_cmd_patch(self): gdb = self._gdb - self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE,gdb.execute("patch", to_string=True)) - + self.assertEqual( + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute("patch", to_string=True) + ) def test_cmd_patch_byte(self): gdb = self._gdb + gef = self._gef gdb.execute("start") gdb.execute("patch byte $pc 0xcc") - res = gdb.execute("display/8bx $pc", to_string=True).strip() - self.assertRegex(res, r"0xcc\s*0x[^c]{2}") + mem = u8(gef.memory.read(gef.arch.pc, 1)) + self.assertEqual(mem, 0xCC) def test_cmd_patch_byte_bytearray(self): gdb = self._gdb + gef = self._gef gdb.execute("start") gdb.execute("set $_gef69 = { 0xcc, 0xcc }") - res = gdb.execute("patch byte $pc $_gef69", "display/8bx $pc", to_string=True).strip() - self.assertRegex(res, r"(0xcc\s*)(\1)0x[^c]{2}") + gdb.execute("patch byte $pc $_gef69") + mem = u16(gef.memory.read(gef.arch.pc, 2)) + self.assertEqual(mem, 0xCCCC) def test_cmd_patch_word(self): gdb = self._gdb + gef = self._gef gdb.execute("start") - res = gdb.execute("patch word $pc 0xcccc") - res = gdb.execute("display/8bx $pc", to_string=True).strip() - self.assertRegex(res, r"(0xcc\s*)(\1)0x[^c]{2}") - + gdb.execute("patch word $pc 0xcccc") + mem = u16(gef.memory.read(gef.arch.pc, 2)) + self.assertEqual(mem, 0xCCCC) def test_cmd_patch_dword(self): gdb = self._gdb + gef = self._gef gdb.execute("start") gdb.execute("patch dword $pc 0xcccccccc") - res = gdb.execute("display/8bx $pc", to_string=True).strip() - self.assertRegex(res, r"(0xcc\s*)(\1\1\1)0x[^c]{2}") + mem = u32(gef.memory.read(gef.arch.pc, 4)) + self.assertEqual(mem, 0xCCCCCCCC) def test_cmd_patch_qword(self): gdb = self._gdb + gef = self._gef gdb.execute("start") gdb.execute("patch qword $pc 0xcccccccccccccccc") - res = gdb.execute("display/8bx $pc", to_string=True).strip() - self.assertRegex(res, r"(0xcc\s*)(\1\1\1\1\1\1)0xcc") - + mem = u64(gef.memory.read(gef.arch.pc, 8)) + self.assertEqual(mem, 0xCCCCCCCCCCCCCCCC) def test_cmd_patch_string(self): gdb = self._gdb - gdb.execute("patch string $sp \"Gef!Gef!Gef!Gef!\"") + gdb.execute("start") + gdb.execute('patch string $sp "Gef!Gef!Gef!Gef!"') res = gdb.execute("grep Gef!Gef!Gef!Gef!", to_string=True).strip() self.assertIn("Gef!Gef!Gef!Gef!", res) -class PatchCommandBss(RemoteGefUnitTestGeneric): +class PatchCommandBss(RemoteGefUnitTestGeneric): def setUp(self) -> None: self._target = debug_target("bss") return super().setUp() diff --git a/tests/utils.py b/tests/utils.py index 19b67f616..0cd9a86e1 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -476,7 +476,7 @@ def download_file(url: str) -> Optional[bytes]: def u8(x: bytes) -> int: - return struct.unpack(" int: From ea7693c19f499f226d5465676233240770e917e4 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 14:53:59 -0800 Subject: [PATCH 19/35] test fixes 4/N --- gef.py | 2 +- tests/commands/pattern.py | 3 +- tests/commands/pcustom.py | 91 ++++++++++++++++++++++++++------------- 3 files changed, 63 insertions(+), 33 deletions(-) diff --git a/gef.py b/gef.py index 14fb2774e..5ed0399ff 100644 --- a/gef.py +++ b/gef.py @@ -5554,7 +5554,7 @@ def do_invoke(self, _: List) -> None: struct_color = gef.config["pcustom.structure_type"] filename_color = gef.config["pcustom.structure_name"] for module in manager.modules.values(): - __modules = ", ".join([Color.colorify(structure_name, struct_color) for structure_name in module.values()]) + __modules = ", ".join([Color.colorify(str(structure), struct_color) for structure in module.values()]) __filename = Color.colorify(str(module.path), filename_color) gef_print(f"{RIGHT_ARROW} {__filename} ({__modules})") return diff --git a/tests/commands/pattern.py b/tests/commands/pattern.py index 5daca25ed..4f5f8f02a 100644 --- a/tests/commands/pattern.py +++ b/tests/commands/pattern.py @@ -58,7 +58,8 @@ def test_cmd_pattern_search(self): # 1 if is_64b(): cmd = f"pattern search -n 8 {lookup_register}" - gdb.execute("set args aaaaaaaabaaaaaaacaaaaaaadaaaaaaa", "run") + gdb.execute("set args aaaaaaaabaaaaaaacaaaaaaadaaaaaaa") + gdb.execute("run") res = gdb.execute(cmd, to_string=True) self.assertIn( f"Found at offset {expected_offsets[1]} (little-endian search) likely", diff --git a/tests/commands/pcustom.py b/tests/commands/pcustom.py index 2131db27a..de4692ff1 100644 --- a/tests/commands/pcustom.py +++ b/tests/commands/pcustom.py @@ -26,7 +26,7 @@ class PcustomCommand(RemoteGefUnitTestGeneric): """`pcustom` command test module""" def setUp(self) -> None: - self._target=debug_target("pcustom") + self._target = debug_target("pcustom") return super().setUp() def test_cmd_pcustom(self): @@ -34,36 +34,41 @@ def test_cmd_pcustom(self): with tempfile.TemporaryDirectory(prefix=GEF_DEFAULT_TEMPDIR) as dd: dirpath = pathlib.Path(dd).absolute() - with tempfile.NamedTemporaryFile(dir = dirpath, suffix=".py") as fd: + with tempfile.NamedTemporaryFile(dir=dirpath, suffix=".py") as fd: fd.write(struct) fd.seek(0) fd.flush() + fpath = pathlib.Path(fd.name) + # + # Assign the struct_path setting + # gdb.execute(f"gef config pcustom.struct_path {dirpath}") - gdb.execute("run") res = gdb.execute("gef config pcustom.struct_path", to_string=True) - self.assertIn(f"pcustom.struct_path (str) = \"{dirpath}\"", res) - - gdb.execute(f"gef config pcustom.struct_path {dirpath}") - res = gdb.execute("pcustom", to_string=True) - structline = [x for x in res.splitlines() if x.startswith(f" → {dirpath}", to_string=True) ][0] - self.assertIn("goo_t", structline) - self.assertIn("foo_t", structline) - - # bad structure name with address - gdb.execute(f"gef config pcustom.struct_path {dirpath}") + self.assertIn(f'pcustom.struct_path (str) = "{dirpath}"', res) + + # + # List the structures in the files inside dirpath + # + lines = gdb.execute("pcustom list", to_string=True).splitlines()[1:] + assert len(lines) == 1 + assert lines[0] == f" → {fpath} (foo_t, goo_t)" + + # + # Test with a bad structure name with address + # gdb.execute("run") - res = gdb.execute("pcustom meh_t 0x1337100", to_string=True) - self.assertIn("Session is not active", res) - - + bad_struct_name = "meh_t" + res = gdb.execute(f"pcustom {bad_struct_name} 0x1337100", to_string=True).strip() + self.assertEqual(f"[!] No structure named '{bad_struct_name}' found", res) + print(res) def test_cmd_pcustom_show(self): gdb = self._gdb with tempfile.TemporaryDirectory(prefix=GEF_DEFAULT_TEMPDIR) as dd: dirpath = pathlib.Path(dd).absolute() - with tempfile.NamedTemporaryFile(dir = dirpath, suffix=".py") as fd: + with tempfile.NamedTemporaryFile(dir=dirpath, suffix=".py") as fd: fd.write(struct) fd.seek(0) fd.flush() @@ -71,34 +76,58 @@ def test_cmd_pcustom_show(self): # no address gdb.execute(f"gef config pcustom.struct_path {dirpath}") gdb.execute("run") - res = gdb.execute("pcustom foo_t", to_string=True) + lines = gdb.execute("pcustom foo_t", to_string=True).splitlines() if is_64b(): - self.assertIn("0000 a c_int /* size=0x4 */", res) - self.assertIn("0004 b c_int /* size=0x4 */", res) + self.assertEqual( + "0000 a c_int /* size=0x4 */", + lines[0], + ) + self.assertEqual( + "0004 b c_int /* size=0x4 */", + lines[1], + ) else: - self.assertIn("0000 a c_long /* size=0x4 */", res) - self.assertIn("0004 b c_long /* size=0x4 */", res) + self.assertEqual( + "0000 a c_long /* size=0x4 */", + lines[0], + ) + self.assertEqual( + "0004 b c_long /* size=0x4 */", + lines[1], + ) # with address gdb.execute(f"gef config pcustom.struct_path {dirpath}") gdb.execute("run") res = gdb.execute("pcustom goo_t 0x1337100", to_string=True) if is_64b(): - self.assertIn(f"""0x1337100+0x00 a : 3 (c_int) + self.assertIn( + f"""0x1337100+0x00 a : 3 (c_int) 0x1337100+0x04 b : 4 (c_int) -0x1337100+0x08 c : """, res) - self.assertIn(f""" 0x1337000+0x00 a : 1 (c_int) +0x1337100+0x08 c : """, + res, + ) + self.assertIn( + f""" 0x1337000+0x00 a : 1 (c_int) 0x1337000+0x04 b : 2 (c_int) 0x1337100+0x10 d : 12 (c_int) -0x1337100+0x14 e : 13 (c_int)""", res) +0x1337100+0x14 e : 13 (c_int)""", + res, + ) else: - self.assertIn(f"""0x1337100+0x00 a : 3 (c_long) + self.assertIn( + f"""0x1337100+0x00 a : 3 (c_long) 0x1337100+0x04 b : 4 (c_long) -0x1337100+0x08 c : """, res) - self.assertIn(f""" 0x1337000+0x00 a : 1 (c_long) +0x1337100+0x08 c : """, + res, + ) + self.assertIn( + f""" 0x1337000+0x00 a : 1 (c_long) 0x1337000+0x04 b : 2 (c_long) 0x1337100+0x0c d : 12 (c_long) -0x1337100+0x10 e : 13 (c_long)""", res) +0x1337100+0x10 e : 13 (c_long)""", + res, + ) # bad structure name gdb.execute(f"gef config pcustom.struct_path {dirpath}") From 9b929bb7b5874663c5017bf5d5f0a8fb0eff10cf Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 15:29:13 -0800 Subject: [PATCH 20/35] test fixes 5/N --- tests/commands/pie.py | 4 ++-- tests/commands/print_format.py | 40 ++++++++++++++++++-------------- tests/commands/process_search.py | 17 +++++++++----- tests/commands/reset_cache.py | 2 +- tests/commands/search_pattern.py | 4 ++-- tests/commands/smart_eval.py | 8 ++++--- tests/commands/stub.py | 2 +- tests/commands/xinfo.py | 5 ++-- tests/commands/xor_memory.py | 2 +- 9 files changed, 49 insertions(+), 35 deletions(-) diff --git a/tests/commands/pie.py b/tests/commands/pie.py index 4e694ae57..6b0d6fd59 100644 --- a/tests/commands/pie.py +++ b/tests/commands/pie.py @@ -20,8 +20,8 @@ def test_cmd_pie(self): res = gdb.execute("pie", to_string=True) self.assertIn("pie (breakpoint|info|delete|run|attach|remote)", res) gdb.execute("pie info 42") - res = gdb.execute("pie delete 42", to_string=True) - assert res + res = gdb.execute("pie delete 42", to_string=True).strip() + assert not res def test_cmd_pie_breakpoint_check(self): gdb = self._gdb diff --git a/tests/commands/print_format.py b/tests/commands/print_format.py index 3dc5dd408..5704c9569 100644 --- a/tests/commands/print_format.py +++ b/tests/commands/print_format.py @@ -4,7 +4,7 @@ from tests.base import RemoteGefUnitTestGeneric -from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE +from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE, p32 class PrintFormatCommand(RemoteGefUnitTestGeneric): @@ -12,32 +12,38 @@ class PrintFormatCommand(RemoteGefUnitTestGeneric): def test_cmd_print_format(self): gdb = self._gdb + gef = self._gef + self.assertEqual( ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute("print-format", to_string=True) ) + gdb.execute("start") - res = gdb.execute("print-format $sp", to_string=True) + + res = gdb.execute("print-format $sp", to_string=True).strip() self.assertIn("buf = [", res) - res = gdb.execute("start") - gdb.execute("print-format --lang js $sp", to_string=True) + + res = gdb.execute("print-format --lang js $sp", to_string=True).strip() self.assertIn("var buf = [", res) - res = gdb.execute("start") - gdb.execute("set *((int*)$sp, to_string=True) = 0x41414141") - res = gdb.execute("print-format --lang hex $sp", to_string=True) + + gef.memory.write(gef.arch.sp, p32(0x41414141)) + res = gdb.execute("print-format --lang hex $sp", to_string=True).strip() self.assertIn("41414141", res, f"{res}") - res = gdb.execute("start") - gdb.execute("print-format --lang iDontExist $sp", to_string=True) + + res = gdb.execute("print-format --lang iDontExist $sp", to_string=True).strip() self.assertIn("Language must be in:", res) def test_cmd_print_format_bytearray(self): gdb = self._gdb + gef = self._gef + res = gdb.execute("start") - gdb.execute("set *((int*)$sp, to_string=True) = 0x41414141") - res = gdb.execute("print-format --lang bytearray -l 4 $sp", to_string=True) + + gef.memory.write(gef.arch.sp, p32(0x41414141)) + + res = gdb.execute("print-format --lang bytearray -l 4 $sp", to_string=True).strip() gef_var = res.split("$_gef")[1].split("'")[0] - self.assertTrue("\x41\x41\x41\x41" in res) - res = gdb.execute("start") - gdb.execute("set *((int*)$sp, to_string=True) = 0x41414141") - gdb.execute("print-format --lang bytearray -l 4 $sp") - res = gdb.execute("p $_gef" + gef_var, to_string=True) - self.assertIn(f"Saved data b'AAAA'... in '$_gef{gef_var}'", res) + self.assertEqual(f"Saved data b'AAAA'... in '$_gef{gef_var}'", res) + + res = gdb.execute(f"p $_gef{gef_var}", to_string=True).strip() + self.assertEqual("$1 = {0x41, 0x41, 0x41, 0x41}", res) diff --git a/tests/commands/process_search.py b/tests/commands/process_search.py index 33faa4eed..a9eba50c0 100644 --- a/tests/commands/process_search.py +++ b/tests/commands/process_search.py @@ -21,16 +21,21 @@ def test_cmd_process_search1(self): res = gdb.execute("process-search", to_string=True) self.assertIn(str(self._target), res) - def test_cmd_process_search2(self): + def test_cmd_process_search_wildcart(self): gdb = self._gdb gdb.execute("set args w00tw00t") gdb.execute("start") - res = gdb.execute("process-search gdb.*fakefake", to_string=True) - self.assertIn("gdb", res) + lines = gdb.execute("process-search gdb.*fakefake", to_string=True).splitlines() + self.assertEqual(len(lines), 0) - def test_cmd_process_search3(self): + lines = gdb.execute( + f"process-search gdb.*", to_string=True + ).splitlines() + self.assertGreaterEqual(len(lines), 1) + + def test_cmd_process_search_smartscan(self): gdb = self._gdb gdb.execute("set args w00tw00t") gdb.execute("start") - res = gdb.execute("process-search --smart-scan gdb.*fakefake", to_string=True) - self.assertNotIn("gdb", res) + lines = gdb.execute("process-search gdb.*fakefake", to_string=True).splitlines() + self.assertEqual(len(lines), 0) diff --git a/tests/commands/reset_cache.py b/tests/commands/reset_cache.py index 9d9c2ecef..ac589f5d9 100644 --- a/tests/commands/reset_cache.py +++ b/tests/commands/reset_cache.py @@ -14,5 +14,5 @@ def test_cmd_reset_cache(self): gdb = self._gdb gdb.execute("start") res = gdb.execute("reset-cache", to_string=True) - assert res + assert not res # TODO improve diff --git a/tests/commands/search_pattern.py b/tests/commands/search_pattern.py index 278dff613..7985be456 100644 --- a/tests/commands/search_pattern.py +++ b/tests/commands/search_pattern.py @@ -31,8 +31,8 @@ def test_cmd_search_pattern_regex(self): self.assertTrue(r"b'ABCDE'" in res) # this should not match because binary string is not null ended: - res = gdb.execute("set {char[6]} $sp = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x03 }") - gdb.execute( + gdb.execute("set {char[6]} $sp = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x03 }") + res = gdb.execute( r"search-pattern --regex $sp $sp+7 ([\\x20-\\x7E]{2,})(?=\\x00)", to_string=True ) diff --git a/tests/commands/smart_eval.py b/tests/commands/smart_eval.py index e4ae004e0..5e59bd48d 100644 --- a/tests/commands/smart_eval.py +++ b/tests/commands/smart_eval.py @@ -12,14 +12,16 @@ class SmartEvalCommand(RemoteGefUnitTestGeneric): def test_cmd_smart_eval(self): gdb = self._gdb + gef = self._gef + + gdb.execute("start") examples = ( - ("$ $pc+1", ""), + ("$ $pc+1", str(gef.arch.pc+1)), ("$ -0x1000", "-4096"), ("$ 0x00007ffff7812000 0x00007ffff79a7000", "1658880"), ("$ 1658880", "0b110010101000000000000"), ) - gdb.execute("start") for cmd, expected_value in examples: res = gdb.execute(cmd, to_string=True).strip() - self.assertEqual(expected_value, res) + self.assertIn(expected_value, res) diff --git a/tests/commands/stub.py b/tests/commands/stub.py index a6e339a0d..357c5471f 100644 --- a/tests/commands/stub.py +++ b/tests/commands/stub.py @@ -20,7 +20,7 @@ def test_cmd_stub(self): ) gdb.execute("start") - res = gdb.execute("continue", to_string=True) + res = gdb.execute("continue", to_string=True).strip() self.assertIn("Hello World!", res) for cmd in cmds: diff --git a/tests/commands/xinfo.py b/tests/commands/xinfo.py index 7d53aa27d..c6029dadf 100644 --- a/tests/commands/xinfo.py +++ b/tests/commands/xinfo.py @@ -20,8 +20,8 @@ def test_cmd_xinfo(self): res = gdb.execute("xinfo", to_string=True) self.assertIn("At least one valid address must be specified", res) - res = gdb.execute("xinfo $sp", to_string=True) - self.assertGreaterEqual(len(res.splitlines()), 7) + lines = gdb.execute("xinfo $sp", to_string=True).splitlines() + self.assertGreaterEqual(len(lines), 6) class XinfoCommandClass(RemoteGefUnitTestGeneric): @@ -33,5 +33,6 @@ def test_cmd_xinfo_on_class(self): gdb = self._gdb cmd = "xinfo $pc+4" gdb.execute("b *'B::Run'") + gdb.execute("run") res = gdb.execute(cmd, to_string=True) self.assertIn("Symbol: B::Run()+4", res) diff --git a/tests/commands/xor_memory.py b/tests/commands/xor_memory.py index 3aa4fc2e9..a727a74cb 100644 --- a/tests/commands/xor_memory.py +++ b/tests/commands/xor_memory.py @@ -16,7 +16,7 @@ def test_cmd_xor_memory_display(self): self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True)) gdb.execute("start") - res = gdb.execute(cmd) + res = gdb.execute(cmd, to_string=True) assert "Original block" in res assert "XOR-ed block" in res From c9d7a92971d39f5b7c9b6dd03e93944e1f73a165 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 16:16:08 -0800 Subject: [PATCH 21/35] test fixes 6/N --- tests/commands/stub.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/tests/commands/stub.py b/tests/commands/stub.py index 357c5471f..9edc5e7e9 100644 --- a/tests/commands/stub.py +++ b/tests/commands/stub.py @@ -3,6 +3,7 @@ """ +import pytest from tests.base import RemoteGefUnitTestGeneric from tests.utils import ERROR_INACTIVE_SESSION_MESSAGE @@ -10,6 +11,10 @@ class StubCommand(RemoteGefUnitTestGeneric): """`stub` command test module""" + @pytest.fixture(autouse=True) + def capfd(self, capfd): + self.capfd = capfd + def test_cmd_stub(self): gdb = self._gdb # due to compiler optimizations printf might be converted to puts @@ -19,12 +24,28 @@ def test_cmd_stub(self): ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) ) + # + # Sanity Check - no exception + # gdb.execute("start") - res = gdb.execute("continue", to_string=True).strip() - self.assertIn("Hello World!", res) + captured = self.capfd.readouterr() + gdb.execute("continue") + captured = self.capfd.readouterr() + + assert "Hello World!" in captured.out + # + # Make sure the prints are stubbed out + # + gdb.execute("start") for cmd in cmds: - gdb.execute("start") - res = gdb.execute(cmd, to_string=True) - self.assertNotIn("Hello World!", res) - gdb.execute("continue") + gdb.execute(f"{cmd} --retval 42") + assert len(gdb.breakpoints()) == len(cmds) + + # + # Check again, make sure the stdout buffer is emptied + # + captured = self.capfd.readouterr() + gdb.execute("continue") + captured = self.capfd.readouterr() + assert "Hello World!" not in captured.out From 86b6c92dbc131920438c720b59c1a7d73db40b68 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 16:31:00 -0800 Subject: [PATCH 22/35] test fixes 7/N --- tests/commands/nop.py | 27 ++++++++++++++------------- tests/commands/theme.py | 4 ++-- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/tests/commands/nop.py b/tests/commands/nop.py index d3c877243..c7100266c 100644 --- a/tests/commands/nop.py +++ b/tests/commands/nop.py @@ -38,7 +38,7 @@ def test_cmd_nop_no_arg(self): gef.memory.write(gef.arch.pc, p32(0xFEEBFEEB)) gdb.execute(self.cmd) res = u32(gef.memory.read(gef.arch.pc, 4)) - self.assertEqual(0xFEEBFEEB, res) + self.assertEqual(0xFEEB9090, res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_check_b_and_n_same_time(self): @@ -53,7 +53,7 @@ def test_cmd_nop_no_arg_break_instruction(self): gef = self._gef gdb.execute("start") - gef.arch.nop_insn = b"\x90\x91\x92" + gdb.execute('pi gef.arch.nop_insn=b"\\x90\\x91\\x92"') gef.memory.write(gef.arch.pc, p32(0xFEEBFEEB)) res = gdb.execute(self.cmd, to_string=True).strip() @@ -65,11 +65,12 @@ def test_cmd_nop_no_arg_break_instruction(self): def test_cmd_nop_force_arg_break_instruction(self): gdb = self._gdb gef = self._gef + gdb.execute("start") - gef.arch.nop_insn = b"\x90\x91\x92" + gdb.execute('pi gef.arch.nop_insn=b"\\x90\\x91\\x92"') gef.memory.write(gef.arch.pc, p32(0xFEEBFEEB)) res = gdb.execute(f"{self.cmd} --f", to_string=True).strip() - mem = gef.memory.read(gef.arch.pc, 4) + mem = u32(gef.memory.read(gef.arch.pc, 4)) self.assertIn(r"will result in LAST-NOP (byte nr 0x2)", res) self.assertEqual(0xFEEB9190, mem) @@ -79,7 +80,7 @@ def test_cmd_nop_i_arg(self): gef = self._gef gdb.execute("start") gef.memory.write(gef.arch.pc + 1, p64(0xFEEBFEEBFEEBFEEB)) - res = gdb.execute(f"{self.cmd} --i 2 $pc+1", to_string=True) + gdb.execute(f"{self.cmd} --i 2 $pc+1") mem = u64(gef.memory.read(gef.arch.pc + 1, 8)) self.assertEqual(0xFEEBFEEB90909090, mem) @@ -135,7 +136,7 @@ def test_cmd_nop_nop_arg(self): gef = self._gef gdb.execute("start") gef.memory.write(gef.arch.pc, p64(0xFEEBFEEBFEEBFEEB)) - res = gdb.execute(f"{self.cmd} --i 4 --n", to_string=True) + gdb.execute(f"{self.cmd} --i 4 --n") mem = u64(gef.memory.read(gef.arch.pc, 8)) self.assertEqual(0xFEEBFEEB90909090, mem) @@ -144,7 +145,7 @@ def test_cmd_nop_nop_arg_multibnop_breaks(self): gdb = self._gdb gef = self._gef gdb.execute("start") - gef.arch.nop_insn = b"\x90\x91\x92" + gdb.execute('pi gef.arch.nop_insn = b"\\x90\\x91\\x92"') gef.memory.write(gef.arch.pc, p64(0xFEEBFEEBFEEBFEEB)) res = gdb.execute(f"{self.cmd} --n", to_string=True) mem = u64(gef.memory.read(gef.arch.pc, 8)) @@ -156,12 +157,12 @@ def test_cmd_nop_nop_arg_multibnop_breaks_force(self): gdb = self._gdb gef = self._gef gdb.execute("start") - gef.arch.nop_insn = b"\x90\x91\x92" + gdb.execute('pi gef.arch.nop_insn = b"\\x90\\x91\\x92"') gef.memory.write(gef.arch.pc, p64(0xFEEBFEEBFEEBFEEB)) res = gdb.execute(f"{self.cmd} --n --f", to_string=True) mem = u64(gef.memory.read(gef.arch.pc, 8)) self.assertIn(r"will result in LAST-INSTRUCTION", res) - self.assertEqual(0xFEEBFEEB929190, mem) + self.assertEqual(0xFEEBFEEBFE929190, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes(self): @@ -196,7 +197,7 @@ def test_cmd_nop_bytes_break_instruction_force(self): res = gdb.execute(f"{self.cmd} --b --f", to_string=True) mem = u16(gef.memory.read(gef.arch.pc, 2)) self.assertIn(r"will result in LAST-INSTRUCTION", res) - self.assertIn(r"b'\x90\xfe'", res) + self.assertEqual(0xfe90, mem) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes_arg(self): @@ -213,7 +214,7 @@ def test_cmd_nop_bytes_arg_nops_no_fit(self): gdb = self._gdb gef = self._gef gdb.execute("start") - gef.arch.nop_insn = b"\x90\x91\x92" + gdb.execute('pi gef.arch.nop_insn = b"\\x90\\x91\\x92"') gef.memory.write(gef.arch.pc, p64(0xFEEBFEEBFEEBFEEB)) res = gdb.execute(f"{self.cmd} --i 4 --b", to_string=True) self.assertIn(r"will result in LAST-NOP (byte nr 0x1)", res) @@ -225,10 +226,10 @@ def test_cmd_nop_bytes_arg_nops_no_fit_force(self): gdb = self._gdb gef = self._gef gdb.execute("start") - gef.arch.nop_insn = b"\x90\x91\x92" + gdb.execute('pi gef.arch.nop_insn = b"\\x90\\x91\\x92"') gef.memory.write(gef.arch.pc, p64(0xFEEBFEEBFEEBFEEB)) res = gdb.execute(f"{self.cmd} --i 5 --b --f", to_string=True) - mem = gef.memory.read(gef.arch.pc, 8) + mem = u64(gef.memory.read(gef.arch.pc, 8)) self.assertIn(r"will result in LAST-NOP (byte nr 0x2)", res) self.assertIn(r"will result in LAST-INSTRUCTION", res) self.assertEqual(0xFEEBFE9190929190, mem) diff --git a/tests/commands/theme.py b/tests/commands/theme.py index 02c906623..a09cfaa6f 100644 --- a/tests/commands/theme.py +++ b/tests/commands/theme.py @@ -38,8 +38,8 @@ def test_cmd_theme(self): # testing command setting v = "blue blah 10 -1 0xfff bold" - res = gdb.execute(f"theme {t} {v}", to_string=True) - assert res + gdb.execute(f"theme {t} {v}") + res = gdb.execute(f"theme ___I_DONT_EXIST___", to_string=True) self.assertIn("Invalid key", res) From d8e7e3dbc30fa32d83c5da89aeabb1f3c44596ad Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 17:24:23 -0800 Subject: [PATCH 23/35] test fixes 8/8 - all passes --- tests/commands/heap.py | 51 +++++++++++++++++++++--------------------- tests/commands/nop.py | 2 +- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/tests/commands/heap.py b/tests/commands/heap.py index 483ab23ae..b3bce6f23 100644 --- a/tests/commands/heap.py +++ b/tests/commands/heap.py @@ -40,7 +40,7 @@ def test_cmd_heap_set_arena(self): gdb.execute("run") gdb.execute(cmd) - res = gdb.execute("heap arenas") + res = gdb.execute("heap arenas", to_string=True) self.assertIn("Arena(base=", res) def test_cmd_heap_chunk_no_arg(self): @@ -92,21 +92,17 @@ def test_cmd_heap_chunks_summary(self): def test_cmd_heap_chunks_min_size_filter(self): gdb = self._gdb - cmd = "heap chunks --min-size 16" self.assertEqual( - ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) + ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute("heap chunks", to_string=True) ) gdb.execute("run") + + cmd = "heap chunks --min-size 16" res = gdb.execute(cmd, to_string=True) self.assertIn("Chunk(addr=", res) cmd = "heap chunks --min-size 1048576" - self.assertEqual( - ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) - ) - - gdb.execute("run") res = gdb.execute(cmd, to_string=True) self.assertNotIn("Chunk(addr=", res) @@ -118,13 +114,11 @@ def test_cmd_heap_chunks_max_size_filter(self): ) gdb.execute("run") + res = gdb.execute(cmd, to_string=True) self.assertIn("Chunk(addr=", res) cmd = "heap chunks --max-size 16" - self.assertEqual( - ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) - ) gdb.execute("run") res = gdb.execute(cmd, to_string=True) @@ -142,9 +136,6 @@ def test_cmd_heap_chunks_with_count(self): self.assertIn("Chunk(addr=", res) cmd = "heap chunks --count 0" - self.assertEqual( - ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) - ) gdb.execute("run") res = gdb.execute(cmd, to_string=True) @@ -180,14 +171,18 @@ def test_cmd_heap_chunks(self): def test_cmd_heap_bins_non_main(self): gdb = self._gdb gef = self._gef + gdb.execute("set environment GLIBC_TUNABLES glibc.malloc.tcache_count=0") + gdb.execute("run") + next_arena: int = gef.heap.main_arena.next cmd = f"python gdb.execute(f'heap bins fast {next_arena:#x}')" - gdb.execute("set environment GLIBC_TUNABLES glibc.malloc.tcache_count=0") res = gdb.execute(cmd, to_string=True) self.assertIn("size=0x20", res) def test_cmd_heap_bins_tcache(self): gdb = self._gdb + gdb.execute("run") + cmd = "heap bins tcache" res = gdb.execute(cmd, to_string=True) tcachelines = findlines("Tcachebins[idx=", res) @@ -226,12 +221,13 @@ def setUp(self) -> None: def test_cmd_heap_chunks_summary_with_type_resolved(self): gdb = self._gdb cmd = "heap chunks --summary --resolve" - - gdb.execute("run") gdb.execute("b B::Run()") - res = gdb.execute(cmd, to_string=True) - self.assertIn("== Chunk distribution by size", res) - self.assertIn("B", res) + gdb.execute("run") + lines = gdb.execute(cmd, to_string=True).splitlines() + assert len(lines) > 0 + self.assertEqual("== Chunk distribution by size ==", lines[0]) + self.assertIn("== Chunk distribution by flag ==", lines) + assert any( map(lambda x: "B" in x, lines)) class HeapCommandFastBins(RemoteGefUnitTestGeneric): @@ -246,6 +242,7 @@ def test_cmd_heap_bins_fast(self): self.assertEqual( ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) ) + gdb.execute("run") res = gdb.execute(cmd, to_string=True) # ensure fastbins is populated self.assertIn("Fastbins[idx=0, size=", res) @@ -262,6 +259,7 @@ def setUp(self) -> None: def test_cmd_heap_bins_large(self): gdb = self._gdb + gdb.execute("run") cmd = "heap bins large" res = gdb.execute(cmd, to_string=True) self.assertIn("Found 1 chunks in 1 large non-empty bins", res) @@ -271,18 +269,18 @@ def test_cmd_heap_bins_large(self): def test_cmd_heap_bins_small(self): gdb = self._gdb cmd = "heap bins small" - before = ["set environment GLIBC_TUNABLES glibc.malloc.tcache_count=0"] - target = debug_target("heap-bins") - res = gdb.execute(cmd, before=before, target=target) + gdb.execute("set environment GLIBC_TUNABLES glibc.malloc.tcache_count=0") + gdb.execute("run") + res = gdb.execute(cmd, to_string=True) self.assertIn("Found 1 chunks in 1 small non-empty bins", res) self.assertIn("Chunk(addr=", res) self.assertIn(f"size={self.expected_small_bin_size:#x}", res) def test_cmd_heap_bins_unsorted(self): gdb = self._gdb + gdb.execute("run") cmd = "heap bins unsorted" - target = debug_target("heap-bins") - res = gdb.execute(cmd, target=target) + res = gdb.execute(cmd, to_string=True) self.assertIn("Found 1 chunks in unsorted bin", res) self.assertIn("Chunk(addr=", res) self.assertIn(f"size={self.expected_unsorted_bin_size:#x}", res) @@ -296,8 +294,9 @@ def setUp(self) -> None: def test_cmd_heap_bins_tcache_all(self): gdb = self._gdb - cmd = "heap bins tcache all" + gdb.execute("run") + cmd = "heap bins tcache all" res = gdb.execute(cmd, to_string=True) # ensure there's 2 tcachebins tcachelines = findlines("Tcachebins[idx=", res) diff --git a/tests/commands/nop.py b/tests/commands/nop.py index c7100266c..979bf0f00 100644 --- a/tests/commands/nop.py +++ b/tests/commands/nop.py @@ -237,7 +237,7 @@ def test_cmd_nop_bytes_arg_nops_no_fit_force(self): class NopCommandMmapKnownAddress(RemoteGefUnitTestGeneric): def setUp(self) -> None: - self._target = (debug_target("mmap-known-address"),) + self._target = debug_target("mmap-known-address") return super().setUp() @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") From 563b2ad18c1ade2665d62936e8ee28bc16a97c2c Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 17:37:58 -0800 Subject: [PATCH 24/35] added tests/regressions --- tests/regressions/registers_register_order.py | 89 +++++++++++++------ 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/tests/regressions/registers_register_order.py b/tests/regressions/registers_register_order.py index a183634a8..c3a3407aa 100644 --- a/tests/regressions/registers_register_order.py +++ b/tests/regressions/registers_register_order.py @@ -1,44 +1,81 @@ import pytest -from tests.utils import ( - GefUnitTestGeneric, - gdb_start_silent_cmd, - gdb_run_silent_cmd, - debug_target, - ARCH -) +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import debug_target, ARCH class RegressionRegisterOrder(RemoteGefUnitTestGeneric): """Tests for regression in the order registers are displayed by `registers` .""" - @pytest.mark.skipif(ARCH not in ["x86_64", "i686"], reason=f"Skipped for {ARCH}") + @pytest.mark.skipif(ARCH not in ("x86_64", "i686"), reason=f"Skipped for {ARCH}") def test_registers_show_registers_in_correct_order(self): """Ensure the registers are printed in the correct order (PR #670).""" - cmd = "registers" + gdb = self._gdb + if ARCH == "i686": - registers_in_correct_order = ["$eax", "$ebx", "$ecx", "$edx", "$esp", "$ebp", "$esi", - "$edi", "$eip", "$eflags", "$cs"] + registers_in_correct_order = [ + "$eax", + "$ebx", + "$ecx", + "$edx", + "$esp", + "$ebp", + "$esi", + "$edi", + "$eip", + "$eflags", + "$cs", + ] elif ARCH == "x86_64": - registers_in_correct_order = ["$rax", "$rbx", "$rcx", "$rdx", "$rsp", "$rbp", "$rsi", - "$rdi", "$rip", "$r8", "$r9", "$r10", "$r11", "$r12", - "$r13", "$r14", "$r15", "$eflags", "$cs"] + registers_in_correct_order = [ + "$rax", + "$rbx", + "$rcx", + "$rdx", + "$rsp", + "$rbp", + "$rsi", + "$rdi", + "$rip", + "$r8", + "$r9", + "$r10", + "$r11", + "$r12", + "$r13", + "$r14", + "$r15", + "$eflags", + "$cs", + ] else: raise ValueError("Unknown architecture") - lines = gdb.execute("start") -gdb.execute(cmd).splitlines()[-len(registers_in_correct_order, to_string=True):] - lines = [line.split()[0].replace(':', '') for line in lines] + + gdb.execute("start") + lines = gdb.execute("registers", to_string=True).splitlines() + lines = lines[-len(registers_in_correct_order) :] + lines = [line.split()[0].replace(":", "") for line in lines] self.assertEqual(registers_in_correct_order, lines) - @pytest.mark.skipif(ARCH not in ["x86_64",], reason=f"Skipped for {ARCH}") +class RegressionRegisterOrderNested(RemoteGefUnitTestGeneric): + def setUp(self) -> None: + self._target = debug_target("nested") + return super().setUp() + + @pytest.mark.skipif( + ARCH not in ("x86_64",), + reason=f"Skipped for {ARCH}", + ) def test_context_correct_registers_refresh_with_frames(self): """Ensure registers are correctly refreshed when changing frame (PR #668)""" - target = debug_target("nested") - lines = gdb_run_silent_cmd("registers", after=["frame 5", "registers"], - target=target).splitlines() - rips = [x for x in lines if x.startswith("$rip")] - self.assertEqual(len(rips), 2) # we must have only 2 entries - self.assertNotEqual(rips[0], rips[1]) # they must be different - self.assertIn(" Date: Sun, 7 Jan 2024 17:44:01 -0800 Subject: [PATCH 25/35] added some docs --- docs/testing.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/testing.md b/docs/testing.md index 6b5babfc9..8fd8a045e 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -104,6 +104,33 @@ environment to help you get more information about the reason of failure. One of the most convenient ways to test `gef` properly is using the `pytest` integration of modern editors such as VisualStudio Code or PyCharm. Without proper tests, new code will not be integrated. +Also note that GEF can be remotely control using the script `scripts/remote_debug.py` as such: + +```text +$ gdb -q -nx +(gdb) source /path/to/gef/gef.py +[...] +gef➤ source /path/to/gef/scripts/remote_debug.py +gef➤ pi start_rpyc_service(4444) +``` + +Here RPyC will be started on the local host, and bound to the TCP port 4444. We can now connect +using a regular Python REPL: + +```text +>>> import rpyc +>>> c = rpyc.connect("localhost", 4444) +>>> gdb = c.root._gdb +>>> gef = c.root._gef +# We can now fully control the remote GDB +>>> gdb.execute("file /bin/ls") +>>> gdb.execute("start") +>>> print(hex(gef.arch.pc)) +0x55555555aab0 +>>> print(hex(gef.arch.sp)) +0x7fffffffdcf0 +``` + ### Linting GEF You can use the Makefile at the root of the project to get the proper linting settings. For most From 3c124dccbf698849b1f6229d98c71276951f80b0 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 18:03:22 -0800 Subject: [PATCH 26/35] fixed coverage --- scripts/generate-coverage-docs.sh | 2 +- tests/base.py | 1 - tests/regressions/gdbserver_connection.py | 12 +++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/scripts/generate-coverage-docs.sh b/scripts/generate-coverage-docs.sh index f92a4588c..f9c9120da 100644 --- a/scripts/generate-coverage-docs.sh +++ b/scripts/generate-coverage-docs.sh @@ -13,7 +13,7 @@ PY_VER=$(gdb -q -nx -ex 'pi print(f"{sys.version_info.major}.{sys.version_info.m rm -f -- "${GEF_DOCS_DIR}"/* echo "[+] Generating coverage report in '${TMPDIR_RUN}'" -COVERAGE_DIR="${TMPDIR_RUN}" python${PY_VER} -m pytest -n ${NB_CORES} "${GEF_TESTS_DIR}" +COVERAGE_DIR="${TMPDIR_RUN}" python${PY_VER} -m pytest -n ${NB_CORES} "${GEF_TESTS_DIR}" -k "not benchmark" echo "[+] Combining data to '${TMPDIR_COV}'" python${PY_VER} -m coverage combine --data-file=${TMPDIR_COV} "${TMPDIR_RUN}"/* diff --git a/tests/base.py b/tests/base.py index 0a86216ed..7fa72758e 100644 --- a/tests/base.py +++ b/tests/base.py @@ -43,7 +43,6 @@ def setUp(self) -> None: self._coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( "PYTEST_XDIST_WORKER", "gw0" ) - assert self._coverage_file.exists() self._commands += f""" pi import coverage pi cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True) diff --git a/tests/regressions/gdbserver_connection.py b/tests/regressions/gdbserver_connection.py index 2c0421bea..6c46d9659 100644 --- a/tests/regressions/gdbserver_connection.py +++ b/tests/regressions/gdbserver_connection.py @@ -1,6 +1,5 @@ +from tests.base import RemoteGefUnitTestGeneric from tests.utils import ( - GefUnitTestGeneric, - gdb_run_cmd, gdbserver_session, ) @@ -8,8 +7,11 @@ class RegressionGdbserverConnection(RemoteGefUnitTestGeneric): def test_can_establish_connection_to_gdbserver_again_after_disconnect(self): """Ensure that gdb can connect to a gdbserver again after disconnecting (PR #896).""" + gdb = self._gdb with gdbserver_session(port=5001) as _, gdbserver_session(port=5002) as _: - buf = gdb_run_cmd("gef-remote 127.0.0.1 5001", - after=["detach", "gef-remote 127.0.0.1 5002", "continue"]) - self.assertNoException(buf) + gdb.execute("gef-remote 127.0.0.1 5001") + gdb.execute("detach") + + gdb.execute("gef-remote 127.0.0.1 5002") + gdb.execute("continue") From 21326f7c7c8ad9a604bd74cb3713b33b1ba3ea18 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 19:15:02 -0800 Subject: [PATCH 27/35] fixed ordering --- tests/base.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/base.py b/tests/base.py index 7fa72758e..222a3745a 100644 --- a/tests/base.py +++ b/tests/base.py @@ -32,13 +32,8 @@ def setUp(self) -> None: assert isinstance(self._target, pathlib.Path) # type: ignore pylint: disable=E1101 assert self._target.exists() # type: ignore pylint: disable=E1101 self._port = random.randint(1025, 65535) - self._commands = f""" -source {GEF_PATH} -gef config gef.debug True -gef config gef.propagate_debug_exception True -gef config gef.disable_color True -source {RPYC_GEF_PATH} -""" + self._commands = "" + if COVERAGE_DIR: self._coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( "PYTEST_XDIST_WORKER", "gw0" @@ -50,6 +45,11 @@ def setUp(self) -> None: """ self._commands += f""" +source {GEF_PATH} +gef config gef.debug True +gef config gef.propagate_debug_exception True +gef config gef.disable_color True +source {RPYC_GEF_PATH} pi start_rpyc_service({self._port}) """ From 38c1c6db6fa79a10e32bd0b5889cd1747210512d Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 7 Jan 2024 19:19:41 -0800 Subject: [PATCH 28/35] coverage ignore failure --- .github/workflows/coverage.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 291490f7e..b1237fae7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -80,10 +80,10 @@ jobs: * ${(forbidden_words.length === 0 || forbidden_words[0] === '') ? "**does not** include forbidden words" : "includes the forbidden words:" + forbidden_words.join(", ")} `; - const { owner, repo, number } = context.issue; - await github.rest.issues.createComment({ owner, repo, issue_number: number, body: comment }); - try { + const { owner, repo, number } = context.issue; + await github.rest.issues.createComment({ owner, repo, issue_number: number, body: comment }); + if(docs_changes > 0) { await github.rest.issues.addLabels({ owner: owner, @@ -92,9 +92,7 @@ jobs: labels: ['documentation'] }); } - } catch (err) { console.log(err); } - try { if(tests_changes > 0) { await github.rest.issues.addLabels({ owner: owner, From cf824ace101abfe7b1da391266b4a370b5f14984 Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 8 Jan 2024 14:22:04 -0800 Subject: [PATCH 29/35] minor fix + added deprecation checks --- gef.py | 63 +++++++++++------------------------------ tests/api/deprecated.py | 13 +++++++++ tests/commands/nop.py | 9 ++++-- 3 files changed, 35 insertions(+), 50 deletions(-) diff --git a/gef.py b/gef.py index 5ed0399ff..6459c6fb7 100644 --- a/gef.py +++ b/gef.py @@ -84,10 +84,13 @@ from io import StringIO, TextIOWrapper from types import ModuleType from typing import (Any, ByteString, Callable, Dict, Generator, Iterable, - Iterator, List, NoReturn, Optional, Sequence, Set, Tuple, Type, - Union) + Iterator, List, Literal, NoReturn, Optional, Sequence, Set, Tuple, Type, + Union, TYPE_CHECKING) from urllib.request import urlopen +if TYPE_CHECKING: + import gdb + GEF_DEFAULT_BRANCH = "main" GEF_EXTRAS_DEFAULT_BRANCH = "main" @@ -3399,24 +3402,24 @@ def get_os() -> str: def is_qemu() -> bool: if not is_remote_debug(): return False - response = gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False) - return isinstance(response, str) and "ENABLE=" in response + response = gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False) or "" + return "ENABLE=" in response @lru_cache() def is_qemu_usermode() -> bool: if not is_qemu(): return False - response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) - return isinstance(response, str) and "Text=" in response + response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" + return "Text=" in response @lru_cache() def is_qemu_system() -> bool: if not is_qemu(): return False - response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) - return isinstance(response, str) and "received: \"\"" in response + response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" + return "received: \"\"" in response def get_filepath() -> Optional[str]: @@ -3606,7 +3609,7 @@ def exit_handler(_: "gdb.ExitedEvent") -> None: warn(f"{bkp_fpath} exists, content will be overwritten") with bkp_fpath.open("w") as fd: - for bp in gdb.breakpoints(): + for bp in list(gdb.breakpoints()): if not bp.enabled or not bp.is_valid: continue fd.write(f"{'t' if bp.temporary else ''}break {bp.location}\n") @@ -4080,7 +4083,7 @@ def instantiate(self, base: int) -> None: self.destroy() try: - res = gdb.execute(self.set_func(base), to_string=True) + res = gdb.execute(self.set_func(base), to_string=True) or "" if not res: return except gdb.error as e: err(str(e)) @@ -4089,6 +4092,7 @@ def instantiate(self, base: int) -> None: if "Breakpoint" not in res: err(res) return + res_list = res.split() self.bp_num = res_list[1] self.bp_addr = res_list[3] @@ -6849,41 +6853,6 @@ def do_invoke(self, *_: Any, **kwargs: Any) -> None: return -@register -class SolveKernelSymbolCommand(GenericCommand): - """Solve kernel symbols from kallsyms table.""" - - _cmdline_ = "ksymaddr" - _syntax_ = f"{_cmdline_} SymbolToSearch" - _example_ = f"{_cmdline_} prepare_creds" - - @parse_arguments({"symbol": ""}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: - def hex_to_int(num): - try: - return int(num, 16) - except ValueError: - return 0 - args : argparse.Namespace = kwargs["arguments"] - if not args.symbol: - self.usage() - return - sym = args.symbol - with open("/proc/kallsyms", "r") as f: - syms = [line.strip().split(" ", 2) for line in f] - matches = [(hex_to_int(addr), sym_t, " ".join(name.split())) for addr, sym_t, name in syms if sym in name] - for addr, sym_t, name in matches: - if sym == name.split()[0]: - ok(f"Found matching symbol for '{name}' at {addr:#x} (type={sym_t})") - else: - warn(f"Found partial match for '{sym}' at {addr:#x} (type={sym_t}): {name}") - if not matches: - err(f"No match for '{sym}'") - elif matches[0][0] == 0: - err("Check that you have the correct permissions to view kernel symbol addresses") - return - - @register class DetailRegistersCommand(GenericCommand): """Display full details on one, many or all registers value from current architecture.""" @@ -7176,7 +7145,7 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: try: elf = Elf(filename) - except ValueError as ve: + except ValueError: err(f"`{filename}` is an invalid value for ELF file") return @@ -10926,7 +10895,7 @@ def auxiliary_vector(self) -> Optional[Dict[str, int]]: return None if not self._auxiliary_vector: auxiliary_vector = {} - auxv_info = gdb.execute("info auxv", to_string=True) + auxv_info = gdb.execute("info auxv", to_string=True) or "" if not auxv_info or "failed" in auxv_info: err("Failed to query auxiliary variables") return None diff --git a/tests/api/deprecated.py b/tests/api/deprecated.py index cd3b485ba..f99522b96 100644 --- a/tests/api/deprecated.py +++ b/tests/api/deprecated.py @@ -3,6 +3,7 @@ """ +import pytest from tests.base import RemoteGefUnitTestGeneric from tests.utils import WARNING_DEPRECATION_MESSAGE @@ -30,3 +31,15 @@ def test_deprecated_elf_values(self): for item in old_stuff: output = gdb.execute(f"pi {item}", to_string=True) assert WARNING_DEPRECATION_MESSAGE in output + + def test_deprecated_gef_attributes(self): + root = self._conn.root + old_attributes = ( + "gef.gdb.loaded_commands", + "gef.gdb.loaded_functions", + "gef.gdb.missing_commands", + ) + + for i in old_attributes: + with pytest.raises(Exception, match="ObsoleteException"): + root.eval(i) diff --git a/tests/commands/nop.py b/tests/commands/nop.py index 979bf0f00..b404318a9 100644 --- a/tests/commands/nop.py +++ b/tests/commands/nop.py @@ -104,7 +104,8 @@ def test_cmd_nop_nop(self): gef = self._gef gdb.execute("start") gef.memory.write(gef.arch.pc, p32(0x9191)) - res = gdb.execute(f"{self.cmd} --n", to_string=True) + res = gdb.execute(f"{self.cmd} --n", to_string=True) or "" + assert res mem = u16(gef.memory.read(gef.arch.pc, 2)) self.assertEqual(0x9190, mem) @@ -173,7 +174,8 @@ def test_cmd_nop_bytes(self): res = gdb.execute( f"{self.cmd} --b", to_string=True, - ) + ) or "" + assert res mem = u16(gef.memory.read(gef.arch.pc, 2)) self.assertEqual(0x9190, mem) @@ -205,7 +207,8 @@ def test_cmd_nop_bytes_arg(self): gef = self._gef gdb.execute("start") gef.memory.write(gef.arch.pc, p64(0xFEEBFEEBFEEBFEEB)) - res = gdb.execute(f"{self.cmd} --i 2 --b --f", to_string=True) + res = gdb.execute(f"{self.cmd} --i 2 --b --f", to_string=True) or "" + assert res mem = u64(gef.memory.read(gef.arch.pc, 8)) self.assertEqual(0xFEEBFEEBFEEB9090, mem) From 550309dc7a341fd0d4ccfc75f52177072124a43d Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Mon, 8 Jan 2024 14:24:00 -0800 Subject: [PATCH 30/35] Apply suggestions from code review Co-authored-by: Grazfather --- docs/testing.md | 4 ++-- tests/base.py | 2 +- tests/commands/aliases.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/testing.md b/docs/testing.md index 8fd8a045e..c6d1584eb 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -52,7 +52,7 @@ documentation for details. Adding new code __requires__ extensive testing. Tests can be added in their own module in the `tests/` folder. For example, if adding a new command to `gef`, a new test module should be created and located in `/root/of/gef/tests/commands/my_new_command.py`. The test class __must__ inherit -`tests.base.RemoteGefUnitTestGeneric`. This class allows to manipulate gdb and gef through rpyc +`tests.base.RemoteGefUnitTestGeneric`. This class allows one to manipulate gdb and gef through rpyc under their respective `self._gdb` and `self._gef` attributes. A skeleton of a test module would look something like: @@ -104,7 +104,7 @@ environment to help you get more information about the reason of failure. One of the most convenient ways to test `gef` properly is using the `pytest` integration of modern editors such as VisualStudio Code or PyCharm. Without proper tests, new code will not be integrated. -Also note that GEF can be remotely control using the script `scripts/remote_debug.py` as such: +Also note that GEF can be remotely controlled using the script `scripts/remote_debug.py` as such: ```text $ gdb -q -nx diff --git a/tests/base.py b/tests/base.py index 222a3745a..6a6aec159 100644 --- a/tests/base.py +++ b/tests/base.py @@ -20,7 +20,7 @@ class RemoteGefUnitTestGeneric(unittest.TestCase): """ - The base class for GEF test cases. This will create the `rpyc` environment to programatically interact with + The base class for GEF test cases. This will create the `rpyc` environment to programmatically interact with GDB and GEF in the test. """ diff --git a/tests/commands/aliases.py b/tests/commands/aliases.py index 5cf564343..aa3f324f6 100644 --- a/tests/commands/aliases.py +++ b/tests/commands/aliases.py @@ -14,7 +14,7 @@ def test_cmd_aliases_add(self): initial_nb = len(gef.session.aliases) gdb.execute("aliases add alias_function_test example") - assert initial_nb == len(gef.session.aliases) - 1 + assert initial_nb + 1 == len(gef.session.aliases) def test_cmd_aliases_list(self): gdb = self._gdb From c3bc2bcfed7469d0db317f4ef7c915b09567774d Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 8 Jan 2024 14:31:14 -0800 Subject: [PATCH 31/35] moved ksymaddr test to extras + removed unused code --- tests/commands/ksymaddr.py | 17 ---------- tests/commands/memory.py | 3 -- tests/utils.py | 63 -------------------------------------- 3 files changed, 83 deletions(-) delete mode 100644 tests/commands/ksymaddr.py diff --git a/tests/commands/ksymaddr.py b/tests/commands/ksymaddr.py deleted file mode 100644 index 604032daf..000000000 --- a/tests/commands/ksymaddr.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -`ksymaddr` command test module -""" - - -from tests.base import RemoteGefUnitTestGeneric - - -class KsymaddrCommand(RemoteGefUnitTestGeneric): - """`ksymaddr` command test module""" - - cmd = "ksymaddr" - - def test_cmd_ksymaddr(self): - gdb = self._gdb - res = gdb.execute(f"{self.cmd} prepare_kernel_cred", to_string=True) - self.assertIn("Found matching symbol for 'prepare_kernel_cred'", res) diff --git a/tests/commands/memory.py b/tests/commands/memory.py index b8d15f2da..21550157f 100644 --- a/tests/commands/memory.py +++ b/tests/commands/memory.py @@ -5,9 +5,6 @@ from tests.base import RemoteGefUnitTestGeneric from tests.utils import ( ERROR_INACTIVE_SESSION_MESSAGE, - GefUnitTestGeneric, - gdb_run_cmd, - gdb_start_silent_cmd, debug_target, ) diff --git a/tests/utils.py b/tests/utils.py index 0cd9a86e1..75af73b92 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -12,8 +12,6 @@ import subprocess import tempfile import time -import unittest -import warnings from typing import Iterable, List, Optional, Union from urllib.request import urlopen @@ -64,56 +62,6 @@ class Color(enum.Enum): BLINK_OFF = "\x1b[25m" -class GdbAssertionError(AssertionError): - pass - - -class GefUnitTestGeneric(unittest.TestCase): - """Generic class for command testing, that defines all helpers""" - - @staticmethod - def assertException(buf): - """Assert that GEF raised an Exception.""" - if not ( - "Python Exception <" in buf - or "Traceback" in buf - or "'gdb.error'" in buf - or "Exception raised" in buf - or "failed to execute properly, reason:" in buf - ): - raise GdbAssertionError("GDB Exception expected, not raised") - - @staticmethod - def assertNoException(buf): - """Assert that no Exception was raised from GEF.""" - if ( - "Python Exception <" in buf - or "Traceback" in buf - or "'gdb.error'" in buf - or "Exception raised" in buf - or "failed to execute properly, reason:" in buf - ): - raise GdbAssertionError(f"Unexpected GDB Exception raised in {buf}") - - if "is deprecated and will be removed in a feature release." in buf: - lines = [ - l - for l in buf.splitlines() - if "is deprecated and will be removed in a feature release." in l - ] - deprecated_api_names = {x.split()[1] for x in lines} - warnings.warn( - UserWarning( - f"Use of deprecated API(s): {', '.join(deprecated_api_names)}" - ) - ) - - @staticmethod - def assertFailIfInactiveSession(buf): - if "No debugging session active" not in buf: - raise AssertionError("No debugging session inactive warning") - - def is_64b() -> bool: return ARCH in CI_VALID_ARCHITECTURES_64B @@ -242,17 +190,6 @@ def gdb_start_silent_cmd( return gdb_run_cmd(cmd, before, after, target, strip_ansi) -def gdb_start_silent_cmd_last_line( - cmd: CommandType, - before: CommandType = (), - after: CommandType = (), - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi=STRIP_ANSI_DEFAULT, -) -> str: - """Execute `gdb_start_silent_cmd()` and return only the last line of its output.""" - return gdb_start_silent_cmd(cmd, before, after, target, strip_ansi).splitlines()[-1] - - def gdb_test_python_method( meth: str, before: str = "", From 22ed93b6ad735c2e9eace9acb8e6e3832d7c0475 Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 8 Jan 2024 15:59:37 -0800 Subject: [PATCH 32/35] simplify gef_extra plugins loading function --- gef.py | 56 +++++++++++++++++++++++--------------------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/gef.py b/gef.py index 6459c6fb7..1c3bf035a 100644 --- a/gef.py +++ b/gef.py @@ -9659,41 +9659,31 @@ def load_plugin(fpath: pathlib.Path) -> bool: return False return True - nb_added = -1 - start_time = time.perf_counter() - try: + def load_plugins_from_directory(plugin_directory: pathlib.Path): + nb_added = -1 nb_inital = len(__registered_commands__) - directories: List[str] = gef.config["gef.extra_plugins_dir"].split(";") or [] - for d in directories: - d = d.strip() - if not d: continue - directory = pathlib.Path(d).expanduser() - if not directory.is_dir(): continue - sys.path.append(str(directory.absolute())) - for entry in directory.iterdir(): - if entry.is_dir(): - if entry.name in ('gdb', 'gef', '__pycache__'): continue - load_plugin(entry / "__init__.py") - else: - if entry.suffix != ".py": continue - if entry.name == "__init__.py": continue - load_plugin(entry) - - nb_added = len(__registered_commands__) - nb_inital - if nb_added > 0: - self.load() - nb_failed = len(__registered_commands__) - len(self.commands) - end_time = time.perf_counter() - load_time = end_time - start_time - ok(f"{Color.colorify(str(nb_added), 'bold green')} extra commands added from " - f"'{Color.colorify(', '.join(directories), 'bold blue')}' in {load_time:.2f} seconds") - if nb_failed != 0: - warn(f"{Color.colorify(str(nb_failed), 'bold light_gray')} extra commands/functions failed to be added. " - "Check `gef missing` to know why") + start_time = time.perf_counter() + for entry in plugin_directory.glob("**/*.py"): + load_plugin(entry) - except gdb.error as e: - err(f"failed: {e}") - return nb_added + try: + nb_added = len(__registered_commands__) - nb_inital + if nb_added > 0: + self.load() + nb_failed = len(__registered_commands__) - len(self.commands) + load_time = time.perf_counter() - start_time + ok(f"{Color.colorify(str(nb_added), 'bold green')} extra commands added " \ + f"in {load_time:.2f} seconds") + if nb_failed != 0: + warn(f"{Color.colorify(str(nb_failed), 'bold light_gray')} extra commands/functions failed to be added. " + "Check `gef missing` to know why") + except gdb.error as e: + err(f"failed: {e}") + return nb_added + directory = gef.config["gef.extra_plugins_dir"] or "" + if not directory: + return 0 + return load_plugins_from_directory(pathlib.Path(directory).expanduser().absolute()) def reload_extra_plugins(self) -> int: try: From 96f3cd2c64be7152a4a475f7dd20d458898c8728 Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 8 Jan 2024 19:34:57 -0800 Subject: [PATCH 33/35] added retry in case of port collision --- tests/base.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/tests/base.py b/tests/base.py index 6a6aec159..3bf4556a3 100644 --- a/tests/base.py +++ b/tests/base.py @@ -16,6 +16,7 @@ RPYC_HOST = "localhost" RPYC_PORT = 18812 RPYC_SPAWN_TIME = 1.0 +RPYC_MAX_REMOTE_CONNECTION_ATTEMPTS = 5 class RemoteGefUnitTestGeneric(unittest.TestCase): @@ -25,12 +26,37 @@ class RemoteGefUnitTestGeneric(unittest.TestCase): """ def setUp(self) -> None: - self._coverage_file = None + + attempt = RPYC_MAX_REMOTE_CONNECTION_ATTEMPTS + while True: + try: + # + # Port collisions can happen, allow a few retries + # + self._coverage_file = None + self.__setup() + break + except ConnectionRefusedError: + attempt -= 1 + if attempt == 0: + raise + time.sleep(0.2) + continue + + self._gdb = self._conn.root.gdb + self._gef = self._conn.root.gef + return super().setUp() + + def __setup(self): if not hasattr(self, "_target"): setattr(self, "_target", debug_target("default")) else: assert isinstance(self._target, pathlib.Path) # type: ignore pylint: disable=E1101 assert self._target.exists() # type: ignore pylint: disable=E1101 + + # + # Select a random tcp port for rpyc + # self._port = random.randint(1025, 65535) self._commands = "" @@ -72,9 +98,6 @@ def setUp(self) -> None: RPYC_HOST, self._port, ) - self._gdb = self._conn.root.gdb - self._gef = self._conn.root.gef - return super().setUp() def tearDown(self) -> None: if COVERAGE_DIR: From 7aa266d68facb7b4ac0e746c503d85b7454cf141 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Mon, 8 Jan 2024 19:38:05 -0800 Subject: [PATCH 34/35] Apply suggestions from code review Co-authored-by: Grazfather --- gef.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gef.py b/gef.py index 1c3bf035a..3300b1c85 100644 --- a/gef.py +++ b/gef.py @@ -105,7 +105,7 @@ def http_get(url: str) -> Optional[bytes]: def update_gef(argv: List[str]) -> int: - """Obsolete. Use the `gef.sh`.""" + """Obsolete. Use `gef.sh`.""" return -1 @@ -2297,7 +2297,7 @@ def __str__(self) -> str: return f"Architecture({self.arch}, {self.mode or 'None'}, {repr(self.endianness)})" def __repr__(self) -> str: - return f"Architecture({self.arch}, {self.mode or 'None'}, {repr(self.endianness)})" + return self.__str__() @staticmethod def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: From e54b9eb3430c5e9c41dcfa1e888bfe1b470457fe Mon Sep 17 00:00:00 2001 From: hugsy Date: Tue, 9 Jan 2024 11:42:51 -0800 Subject: [PATCH 35/35] =?UTF-8?q?forgot=20benchmark=20=F0=9F=A4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/commands/gef.py | 1 - tests/perf/benchmark.py | 66 ++++++++--------- tests/utils.py | 154 ---------------------------------------- 3 files changed, 33 insertions(+), 188 deletions(-) diff --git a/tests/commands/gef.py b/tests/commands/gef.py index b8b56a6df..9f8de2aec 100644 --- a/tests/commands/gef.py +++ b/tests/commands/gef.py @@ -41,7 +41,6 @@ def test_cmd_gef_config_get(self): gdb = self._gdb res = gdb.execute("gef config gef.debug", to_string=True) self.assertIn("GEF configuration setting: gef.debug", res) - # the `True` is automatically set by `gdb_run_cmd` so we know it's there self.assertIn( """gef.debug (bool) = True\n\nDescription:\n\tEnable debug mode for gef""", res, diff --git a/tests/perf/benchmark.py b/tests/perf/benchmark.py index 738f4db3e..2b49b3f39 100644 --- a/tests/perf/benchmark.py +++ b/tests/perf/benchmark.py @@ -4,36 +4,36 @@ import pytest -from tests.utils import gdb_test_python_method, gdb_time_python_method, gdb_start_silent_cmd - - -def time_baseline(benchmark): - benchmark(gdb_test_python_method, "") - - -def time_elf_parsing(benchmark): - benchmark(gdb_test_python_method, "Elf('/bin/ls')") - - -def time_cmd_context(benchmark): - benchmark(gdb_start_silent_cmd, "context") - - -def _time_elf_parsing_using_timeit(): - with pytest.raises(ValueError): - res = gdb_time_python_method( - "Elf('/bin/ls')", - "from __main__ import Elf" - ) - pytest.fail(f"execution_time={res}s") - - -def _time_cmd_context_using_timeit(): - with pytest.raises(ValueError): - res = gdb_time_python_method( - "gdb.execute('context')", - "import gdb", - before=("entry-break",), - number=100 - ) - pytest.fail(f"execution_time={res}s") +from ..base import RemoteGefUnitTestGeneric + + +class BenchmarkBasicApi(RemoteGefUnitTestGeneric): + @pytest.fixture(autouse=True) + def benchmark(self, benchmark): + self.__benchmark = benchmark + + @pytest.mark.benchmark + def test_cmd_context(self): + gdb = self._gdb + gdb.execute("start") + self.__benchmark(gdb.execute, "context") + + @pytest.mark.benchmark + def test_gef_memory_maps(self): + gdb = self._gdb + gdb.execute("start") + gef = self._gef + assert self.__benchmark + + def vmmap(): + return gef.memory.maps + + self.__benchmark(vmmap) + + @pytest.mark.benchmark + def test_elf_parsing(self): + root = self._conn.root + ElfCls = root.eval("Elf") + assert ElfCls + assert self.__benchmark + self.__benchmark(ElfCls, "/bin/ls") diff --git a/tests/utils.py b/tests/utils.py index 75af73b92..fc35c77b1 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -70,160 +70,6 @@ def is_32b() -> bool: return ARCH in CI_VALID_ARCHITECTURES_32B -def ansi_clean(s: str) -> str: - ansi_escape = re.compile(r"(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]") - return ansi_escape.sub("", s) - - -def gdb_run_cmd( - cmd: CommandType, - before: CommandType = (), - after: CommandType = (), - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi: bool = STRIP_ANSI_DEFAULT, -) -> str: - """Execute a command inside GDB. `before` and `after` are lists of commands to be executed - before (resp. after) the command to test.""" - - def _add_command(commands: CommandType) -> List[str]: - if isinstance(commands, str): - commands = [commands] - return [_str for cmd in commands for _str in ["-ex", cmd]] - - command = ["gdb", "-q", "-nx"] - if COVERAGE_DIR: - coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( - "PYTEST_XDIST_WORKER", "gw0" - ) - command += _add_command( - [ - "pi from coverage import Coverage", - f'pi cov = Coverage(data_file="{coverage_file}",' - "auto_data=True, branch=True)", - "pi cov.start()", - ] - ) - command += _add_command( - [ - f"source {GEF_PATH}", - "gef config gef.debug True", - ] - ) - command += _add_command(before) - command += _add_command(cmd) - command += _add_command(after) - if COVERAGE_DIR: - command += _add_command(["pi cov.stop()", "pi cov.save()"]) - command += ["-ex", "quit", "--", str(target)] - - lines = ( - subprocess.check_output(command, stderr=subprocess.STDOUT).strip().splitlines() - ) - output = b"\n".join(lines) - result = None - - # The following is necessary because ANSI escape sequences might have been - # added in the middle of multibyte characters, e.g. \x1b[H\x1b[2J is added - # into the middle of \xe2\x94\x80 to become \xe2\x1b[H\x1b[2J\x94\x80 which - # causes a UnicodeDecodeError when trying to decode \xe2. Such broken - # multibyte characters would need to be removed, otherwise the test will - # result in an error. - while not result: - try: - result = output.decode("utf-8") - except UnicodeDecodeError as ude: - faulty_idx_start = int(ude.start) - faulty_idx_end = int(ude.end) - output = output[:faulty_idx_start] + output[faulty_idx_end:] - - if strip_ansi: - result = ansi_clean(result) - - return result - - -def gdb_run_silent_cmd( - cmd: CommandType, - before: CommandType = (), - after: CommandType = (), - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi: bool = STRIP_ANSI_DEFAULT, -) -> str: - """Disable the output and run entirely the `target` binary.""" - before = [ - *before, - "gef config context.clear_screen False", - "gef config context.layout '-code -stack'", - "run", - ] - return gdb_run_cmd(cmd, before, after, target, strip_ansi) - - -def gdb_run_cmd_last_line( - cmd: CommandType, - before: CommandType = (), - after: CommandType = (), - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi: bool = STRIP_ANSI_DEFAULT, -) -> str: - """Execute a command in GDB, and return only the last line of its output.""" - return gdb_run_cmd(cmd, before, after, target, strip_ansi).splitlines()[-1] - - -def gdb_start_silent_cmd( - cmd: CommandType, - before: CommandType = (), - after: CommandType = (), - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi: bool = STRIP_ANSI_DEFAULT, - context: str = DEFAULT_CONTEXT, -) -> str: - """Execute a command in GDB by starting an execution context. This command - disables the `context` and sets a tbreak at the most convenient entry - point.""" - before = [ - *before, - "gef config context.clear_screen False", - f"gef config context.layout '{context}'", - "entry-break", - ] - return gdb_run_cmd(cmd, before, after, target, strip_ansi) - - -def gdb_test_python_method( - meth: str, - before: str = "", - after: str = "", - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi: bool = STRIP_ANSI_DEFAULT, -) -> str: - brk = before + ";" if before else "" - cmd = f"pi {brk}print({meth});{after}" - return gdb_start_silent_cmd(cmd, target=target, strip_ansi=strip_ansi) - - -def gdb_time_python_method( - meth: str, - setup: str, - py_before: str = "", - py_after: str = "", - before: CommandType = (), - after: CommandType = (), - target: pathlib.Path = DEFAULT_TARGET, - strip_ansi: bool = STRIP_ANSI_DEFAULT, - number: int = 1000, -) -> float: - brk = py_before + ";" if py_before else "" - cmd = ( - f"""pi import timeit;{brk}print(timeit.timeit("{meth}", """ - f"""setup="{setup}", number={number}));{py_after}""" - ) - lines = gdb_run_cmd( - cmd, before=before, after=after, target=target, strip_ansi=strip_ansi - ).splitlines() - return float(lines[-1]) - - def debug_target(name: str, extension: str = ".out") -> pathlib.Path: target = TMPDIR / f"{name}{extension}" if not target.exists():