From d28c4a3eefcbdedbb4cd6477c9c4807b07b11fce Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 22 Sep 2024 10:42:44 -0700 Subject: [PATCH 01/38] added ruff linter + fixes --- gef.py | 303 +++++++++++++++++++++++++++++------------------------- ruff.toml | 2 + 2 files changed, 164 insertions(+), 141 deletions(-) create mode 100644 ruff.toml diff --git a/gef.py b/gef.py index af6ee583e..621270212 100644 --- a/gef.py +++ b/gef.py @@ -353,7 +353,8 @@ def calling_function() -> Optional[str]: try: stack_info = traceback.extract_stack()[-3] return stack_info.name - except: + except Exception as e: + dbg(f"traceback failed with {str(e)}") return None @@ -743,7 +744,12 @@ def _search_for_realpath(self) -> Optional[str]: @property def realpath(self) -> str: # when in a `gef-remote` session, realpath returns the path to the binary on the local disk, not remote - return self.path if gef.session.remote is None else self._search_for_realpath() + if gef.session.remote is None: + return self.path + default = self._search_for_realpath() + if default: + return default + raise FileNotFoundError def __str__(self) -> str: return (f"Section(start={self.page_start:#x}, end={self.page_end:#x}, " @@ -1594,7 +1600,7 @@ class ChunkFlags(enum.IntFlag): NON_MAIN_ARENA = 4 def __str__(self) -> str: - return f" | ".join([ + return " | ".join([ Color.greenify("PREV_INUSE") if self.value & self.PREV_INUSE else Color.redify("PREV_INUSE"), Color.greenify("IS_MMAPPED") if self.value & self.IS_MMAPPED else Color.redify("IS_MMAPPED"), Color.greenify("NON_MAIN_ARENA") if self.value & self.NON_MAIN_ARENA else Color.redify("NON_MAIN_ARENA") @@ -1881,9 +1887,9 @@ def _show_code_line(fname: str, idx: int) -> str: gef_print("") exc_type, exc_value, exc_traceback = sys.exc_info() - + exc_name = exc_type.__name__ if exc_type else "Unknown" gef_print(" Exception raised ".center(80, HORIZONTAL_LINE)) - gef_print(f"{Color.colorify(exc_type.__name__, 'bold underline red')}: {exc_value}") + gef_print(f"{Color.colorify(exc_name, 'bold underline red')}: {exc_value}") gef_print(" Detailed stacktrace ".center(80, HORIZONTAL_LINE)) for fs in traceback.extract_tb(exc_traceback)[::-1]: @@ -3516,7 +3522,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) or "" - return "ENABLE=" in response + return "ENABLE=1" in response @lru_cache() @@ -3674,19 +3680,19 @@ def is_hex(pattern: str) -> bool: return len(pattern) % 2 == 0 and all(c in string.hexdigits for c in pattern[2:]) -def continue_handler(_: "gdb.events.ContinueEvent") -> None: +def continue_handler(_: "gdb.ContinueEvent") -> None: """GDB event handler for new object continue cases.""" return -def hook_stop_handler(_: "gdb.events.StopEvent") -> None: +def hook_stop_handler(_: "gdb.StopEvent") -> None: """GDB event handler for stop cases.""" reset_all_caches() gdb.execute("context") return -def new_objfile_handler(evt: Optional["gdb.events.NewObjFileEvent"]) -> None: +def new_objfile_handler(evt: Optional["gdb.NewObjFileEvent"]) -> None: """GDB event handler for new object file cases.""" reset_all_caches() progspace = gdb.current_progspace() @@ -3721,7 +3727,7 @@ def new_objfile_handler(evt: Optional["gdb.events.NewObjFileEvent"]) -> None: return -def exit_handler(_: "gdb.events.ExitedEvent") -> None: +def exit_handler(_: "gdb.ExitedEvent") -> None: """GDB event handler for exit cases.""" global gef # flush the caches @@ -3752,13 +3758,13 @@ def exit_handler(_: "gdb.events.ExitedEvent") -> None: return -def memchanged_handler(_: "gdb.events.MemoryChangedEvent") -> None: +def memchanged_handler(_: "gdb.MemoryChangedEvent") -> None: """GDB event handler for mem changes cases.""" reset_all_caches() return -def regchanged_handler(_: "gdb.events.RegisterChangedEvent") -> None: +def regchanged_handler(_: "gdb.RegisterChangedEvent") -> None: """GDB event handler for reg changes cases.""" reset_all_caches() return @@ -3888,7 +3894,7 @@ def get_memory_alignment(in_bits: bool = False) -> int: try: return gdb.parse_and_eval("$pc").type.sizeof - except: + except Exception: pass raise OSError("GEF is running under an unsupported mode") @@ -4123,72 +4129,72 @@ def set_arch(arch: Optional[str] = None, _: Optional[str] = None) -> None: # @only_if_events_supported("cont") -def gef_on_continue_hook(func: Callable[["gdb.events.ContinueEvent"], None]) -> None: +def gef_on_continue_hook(func: Callable[["gdb.ContinueEvent"], None]) -> None: gdb.events.cont.connect(func) @only_if_events_supported("cont") -def gef_on_continue_unhook(func: Callable[["gdb.events.ThreadEvent"], None]) -> None: +def gef_on_continue_unhook(func: Callable[["gdb.ThreadEvent"], None]) -> None: gdb.events.cont.disconnect(func) @only_if_events_supported("stop") -def gef_on_stop_hook(func: Callable[["gdb.events.StopEvent"], None]) -> None: +def gef_on_stop_hook(func: Callable[["gdb.StopEvent"], None]) -> None: gdb.events.stop.connect(func) @only_if_events_supported("stop") -def gef_on_stop_unhook(func: Callable[["gdb.events.StopEvent"], None]) -> None: +def gef_on_stop_unhook(func: Callable[["gdb.StopEvent"], None]) -> None: gdb.events.stop.disconnect(func) @only_if_events_supported("exited") -def gef_on_exit_hook(func: Callable[["gdb.events.ExitedEvent"], None]) -> None: +def gef_on_exit_hook(func: Callable[["gdb.ExitedEvent"], None]) -> None: gdb.events.exited.connect(func) @only_if_events_supported("exited") -def gef_on_exit_unhook(func: Callable[["gdb.events.ExitedEvent"], None]) -> None: +def gef_on_exit_unhook(func: Callable[["gdb.ExitedEvent"], None]) -> None: gdb.events.exited.disconnect(func) @only_if_events_supported("new_objfile") -def gef_on_new_hook(func: Callable[["gdb.events.NewObjFileEvent"], None]) -> None: +def gef_on_new_hook(func: Callable[["gdb.NewObjFileEvent"], None]) -> None: gdb.events.new_objfile.connect(func) @only_if_events_supported("new_objfile") -def gef_on_new_unhook(func: Callable[["gdb.events.NewObjFileEvent"], None]) -> None: +def gef_on_new_unhook(func: Callable[["gdb.NewObjFileEvent"], None]) -> None: gdb.events.new_objfile.disconnect(func) @only_if_events_supported("clear_objfiles") -def gef_on_unload_objfile_hook(func: Callable[["gdb.events.ClearObjFilesEvent"], None]) -> None: +def gef_on_unload_objfile_hook(func: Callable[["gdb.ClearObjFilesEvent"], None]) -> None: gdb.events.clear_objfiles.connect(func) @only_if_events_supported("clear_objfiles") -def gef_on_unload_objfile_unhook(func: Callable[["gdb.events.ClearObjFilesEvent"], None]) -> None: +def gef_on_unload_objfile_unhook(func: Callable[["gdb.ClearObjFilesEvent"], None]) -> None: gdb.events.clear_objfiles.disconnect(func) @only_if_events_supported("memory_changed") -def gef_on_memchanged_hook(func: Callable[["gdb.events.MemoryChangedEvent"], None]) -> None: +def gef_on_memchanged_hook(func: Callable[["gdb.MemoryChangedEvent"], None]) -> None: gdb.events.memory_changed.connect(func) @only_if_events_supported("memory_changed") -def gef_on_memchanged_unhook(func: Callable[["gdb.events.MemoryChangedEvent"], None]) -> None: +def gef_on_memchanged_unhook(func: Callable[["gdb.MemoryChangedEvent"], None]) -> None: gdb.events.memory_changed.disconnect(func) @only_if_events_supported("register_changed") -def gef_on_regchanged_hook(func: Callable[["gdb.events.RegisterChangedEvent"], None]) -> None: +def gef_on_regchanged_hook(func: Callable[["gdb.RegisterChangedEvent"], None]) -> None: gdb.events.register_changed.connect(func) @only_if_events_supported("register_changed") -def gef_on_regchanged_unhook(func: Callable[["gdb.events.RegisterChangedEvent"], None]) -> None: +def gef_on_regchanged_unhook(func: Callable[["gdb.RegisterChangedEvent"], None]) -> None: gdb.events.register_changed.disconnect(func) @@ -4757,7 +4763,7 @@ def settings(self) -> List[str]: """Return the list of settings for this command.""" return list(iter(self)) - @deprecated(f"Use `self[setting_name]` instead") + @deprecated("Use `self[setting_name]` instead") def get_setting(self, name: str) -> Any: return self.__getitem__(name) @@ -4765,14 +4771,14 @@ def __getitem__(self, name: str) -> Any: key = self.__get_setting_name(name) return gef.config[key] - @deprecated(f"Use `setting_name in self` instead") + @deprecated("Use `setting_name in self` instead") def has_setting(self, name: str) -> bool: return self.__contains__(name) def __contains__(self, name: str) -> bool: return self.__get_setting_name(name) in gef.config - @deprecated(f"Use `self[setting_name] = value` instead") + @deprecated("Use `self[setting_name] = value` instead") def add_setting(self, name: str, value: Tuple[Any, type, str], description: str = "") -> None: return self.__setitem__(name, (value, description)) @@ -4797,7 +4803,7 @@ def __setitem__(self, name: str, value: Union["GefSetting", Tuple[Any, str]]) -> gef.config[key] = GefSetting(value[0], description=value[1]) return - @deprecated(f"Use `del self[setting_name]` instead") + @deprecated("Use `del self[setting_name]` instead") def del_setting(self, name: str) -> None: return self.__delitem__(name) @@ -5239,7 +5245,7 @@ def comp2_b(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):b}" gef_print("0b" + comp2_b(res)) gef_print(f"{binascii.unhexlify(s_i)}") gef_print(f"{binascii.unhexlify(s_i)[::-1]}") - except: + except Exception: pass return @@ -5463,6 +5469,7 @@ class GefThemeCommand(GenericCommand): _cmdline_ = "theme" _syntax_ = f"{_cmdline_} [KEY [VALUE]]" + _examples_ = (f"{_cmdline_} address_stack green") def __init__(self) -> None: super().__init__(self._cmdline_) @@ -5497,7 +5504,7 @@ def do_invoke(self, args: List[str]) -> None: return setting_name = args[0] - if not setting_name in self: + if setting_name not in self: err("Invalid key") return @@ -5506,8 +5513,8 @@ def do_invoke(self, args: List[str]) -> None: gef_print(f"{setting_name:40s}: {Color.colorify(value, value)}") return - colors = [color for color in args[1:] if color in Color.colors] - self[setting_name] = " ".join(colors) + colors = (color for color in args[1:] if color in Color.colors) + self[setting_name][0] = " ".join(colors) return @@ -5574,6 +5581,7 @@ def apply_at(self, address: int, max_depth: int, depth: int = 0) -> None: ptrsize = gef.arch.ptrsize unpack = u32 if ptrsize == 4 else u64 for field in _structure._fields_: + assert len(field) == 2 _name, _type = field _value = getattr(_structure, _name) _offset = getattr(self.class_type, _name).offset @@ -6127,7 +6135,7 @@ def search_pattern(self, pattern: str, section_name: str) -> None: for section in gef.memory.maps: if not section.permission & Permission.READ: continue if section.path == "[vvar]": continue - if not section_name in section.path: continue + if section_name not in section.path: continue start = section.page_start end = section.page_end - 1 @@ -6264,7 +6272,7 @@ def __init__(self) -> None: super().__init__(prefix=False) return - @parse_arguments({"host": "", "port": 0}, {"--pid": -1, "--qemu-user": False, "--qemu-binary": ""}) + @parse_arguments({"host": "", "port": 0}, {"--pid": 0, "--qemu-user": ""}) def do_invoke(self, _: List[str], **kwargs: Any) -> None: if gef.session.remote is not None: err("You already are in remote session. Close it first before opening a new one...") @@ -6277,15 +6285,15 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: return # qemu-user support - qemu_binary: Optional[pathlib.Path] = None if args.qemu_user: + dbg("Setting up qemu-user session") try: - qemu_binary = pathlib.Path(args.qemu_binary).expanduser().absolute() if args.qemu_binary else gef.session.file + qemu_binary = pathlib.Path(args.qemu_user).expanduser().absolute() if args.qemu_user else gef.session.file if not qemu_binary or not qemu_binary.exists(): - raise FileNotFoundError(f"{qemu_binary} does not exist") + raise FileNotFoundError(f"qemu-user session was specified, but binary '{qemu_binary}' does not exist") except Exception as e: err(f"Failed to initialize qemu-user mode, reason: {str(e)}") - return + raise e # Try to establish the remote session, throw on error # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which @@ -6411,8 +6419,8 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: try: last_insn = gef_instruction_n(address, num_items-1) last_addr = last_insn.address - except: - err(f"Cannot patch instruction at {address:#x} reaching unmapped area") + except Exception as e: + err(f"Cannot patch instruction at {address:#x} reaching unmapped area, reason: {e}") return total_bytes = (last_addr - address) + gef_get_instruction_at(last_addr).size() @@ -7739,10 +7747,10 @@ def context_regs(self) -> None: gdb.execute(f"registers {printable_registers}") return - widest = l = max(map(len, gef.arch.all_registers)) - l += 5 - l += gef.arch.ptrsize * 2 - nb = get_terminal_size()[1] // l + widest = curlen = max(map(len, gef.arch.all_registers)) + curlen += 5 + curlen += gef.arch.ptrsize * 2 + nb = get_terminal_size()[1] // curlen i = 1 line = "" changed_color = gef.config["theme.registers_value_changed"] @@ -7831,7 +7839,7 @@ def context_code(self) -> None: breakpoints = gdb.breakpoints() or [] # breakpoint.locations was introduced in gdb 13.1 if len(breakpoints) and hasattr(breakpoints[-1], "locations"): - bp_locations = [hex(location.address) for b in breakpoints for location in b.locations if location is not None] + bp_locations = [hex(location.address) for b in breakpoints for location in b.locations if location is not None] # type: ignore else: # location relies on the user setting the breakpoints with "b *{hex(address)}" bp_locations = [b.location for b in breakpoints if b.location and b.location.startswith("*")] @@ -7939,6 +7947,8 @@ def print_arguments_from_symbol(self, function_name: str, symbol: "gdb.Symbol") args = [] fields = symbol.type.fields() if symbol.type else [] for i, f in enumerate(fields): + if not f.type: + continue _value = gef.arch.get_ith_parameter(i, in_func=False)[1] _value = RIGHT_ARROW.join(dereference_from(_value)) _name = f.name or f"var_{i}" @@ -8030,9 +8040,8 @@ def context_source(self) -> None: if not symtab.is_valid(): return - fpath = symtab.fullname() - with open(fpath, "r") as f: - lines = [l.rstrip() for l in f.readlines()] + fpath = pathlib.Path(symtab.fullname()) + lines = [curline.rstrip() for curline in fpath.read_text().splitlines()] except Exception: return @@ -8455,23 +8464,23 @@ def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = "word": ("H", 2), } - r, l = formats[arrange_as] - fmt_str = f"{{base}}{VERTICAL_LINE}+{{offset:#06x}} {{sym}}{{val:#0{l*2+2}x}} {{text}}" - fmt_pack = f"{endianness!s}{r}" + formatter, width = formats[arrange_as] + fmt_str = f"{{base}}{VERTICAL_LINE}+{{offset:#06x}} {{sym}}{{val:#0{width*2+2}x}} {{text}}" + fmt_pack = f"{endianness!s}{formatter}" lines = [] i = 0 text = "" while i < length: - cur_addr = start_addr + (i + offset) * l + cur_addr = start_addr + (i + offset) * width sym = gdb_get_location_from_symbol(cur_addr) sym = f"<{sym[0]:s}+{sym[1]:04x}> " if sym else "" - mem = gef.memory.read(cur_addr, l) + mem = gef.memory.read(cur_addr, width) val = struct.unpack(fmt_pack, mem)[0] if show_ascii: text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in mem]) lines.append(fmt_str.format(base=Color.colorify(format_address(cur_addr), base_address_color), - offset=(i + offset) * l, sym=sym, val=val, text=text)) + offset=(i + offset) * width, sym=sym, val=val, text=text)) i += 1 return lines @@ -8573,9 +8582,9 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: var_name = values[0] try: values = str(gdb.parse_and_eval(var_name)).lstrip("{").rstrip("}").replace(",","").split(" ") - except: + except Exception as e: gef_print(f"Bad variable specified, check value with command: p {var_name}") - return + raise e d = str(gef.arch.endianness) for value in values: @@ -8761,7 +8770,7 @@ def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str: addrs = dereference_from(current_address) addr_l = format_address(int(addrs[0], 16)) ma = (memalign*2 + 2) - l = ( + line = ( f"{Color.colorify(addr_l, base_address_color)}{VERTICAL_LINE}" f"{base_offset+offset:+#07x}: {sep.join(addrs[1:]):{ma}s}" ) @@ -8775,10 +8784,10 @@ def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str: if register_hints: m = f"\t{LEFT_ARROW}{', '.join(list(register_hints))}" - l += Color.colorify(m, registers_color) + line += Color.colorify(m, registers_color) offset += memalign - return l + return line @only_if_gdb_running @parse_arguments({"address": "$sp"}, {("-r", "--reference"): "", ("-l", "--length"): 10}) @@ -8932,18 +8941,18 @@ def print_entry(self, entry: Section) -> None: elif entry.permission & Permission.READ and entry.permission & Permission.EXECUTE: line_color = gef.config["theme.address_code"] - l = [ + line_parts = [ Color.colorify(format_address(entry.page_start), line_color), Color.colorify(format_address(entry.page_end), line_color), Color.colorify(format_address(entry.offset), line_color), ] if entry.permission == Permission.ALL: - l.append(Color.colorify(str(entry.permission), "underline " + line_color)) + line_parts.append(Color.colorify(str(entry.permission), "underline " + line_color)) else: - l.append(Color.colorify(str(entry.permission), line_color)) + line_parts.append(Color.colorify(str(entry.permission), line_color)) - l.append(Color.colorify(entry.path, line_color)) - line = " ".join(l) + line_parts.append(Color.colorify(entry.path, line_color)) + line = " ".join(line_parts) gef_print(line) return @@ -8990,13 +8999,13 @@ def do_invoke(self, argv: List[str]) -> None: if filter_by_name and filter_by_name not in xfile.name: continue - l = [ + line_parts = [ format_address(xfile.zone_start), format_address(xfile.zone_end), f"{xfile.name:<21s}", xfile.filename, ] - gef_print(" ".join(l)) + gef_print(" ".join(line_parts)) return @@ -9680,7 +9689,7 @@ def dump_tracked_allocations(self) -> None: ok("No free() chunk tracked") return - def clean(self, _: "gdb.events.ExitedEvent") -> None: + def clean(self, _: "gdb.ExitedEvent") -> None: global gef ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Cleaning up") @@ -9911,6 +9920,7 @@ def __init__(self) -> None: 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.") + gef.config["gef.extra_python_package_paths"] = GefSetting("", str, "Semi-colon separate list of extra paths to include for python packages") self.commands : Dict[str, GenericCommand] = collections.OrderedDict() self.functions : Dict[str, GenericFunction] = collections.OrderedDict() @@ -10232,7 +10242,7 @@ def set_setting(self, argv: List[str]) -> bool: _type = gef.config.raw_entry(key).type # Attempt to parse specific values for known types - if _type == bool: + if _type is bool: if new_value.upper() in ("TRUE", "T", "1"): _newval = True elif new_value.upper() in ("FALSE", "F", "0"): @@ -10340,7 +10350,7 @@ def reload(self, quiet: bool): except Exception: continue new_value = cfg.get(section, optname) - if setting.type == bool: + if setting.type is bool: new_value = True if new_value.upper() in ("TRUE", "T", "1") else False setting.value = setting.type(new_value) @@ -10694,7 +10704,7 @@ def reset_caches(self) -> None: if not hasattr(obj, "cache_clear"): continue obj.cache_clear() - except: # we're reseting the cache here, we don't care if (or which) exception triggers + except Exception: # we're reseting the cache here, we don't care if (or which) exception triggers continue return @@ -10764,7 +10774,10 @@ def read_ascii_string(self, address: int) -> Optional[str]: @property def maps(self) -> List[Section]: if not self.__maps: - self.__maps = self.__parse_maps() + maps = self.__parse_maps() + if not maps: + raise RuntimeError("Failed to determine memory layout") + self.__maps = maps return self.__maps def __parse_maps(self) -> Optional[List[Section]]: @@ -10777,17 +10790,17 @@ def __parse_maps(self) -> Optional[List[Section]]: try: return list(self.parse_gdb_info_proc_maps()) - except: + except Exception: pass try: return list(self.parse_procfs_maps()) - except: + except Exception: pass try: return list(self.parse_monitor_info_mem()) - except: + except Exception: pass raise RuntimeError("Failed to get memory layout") @@ -10964,7 +10977,7 @@ def main_arena(self) -> Optional[GlibcArena]: # the initialization of `main_arena` also defined `selected_arena`, so # by default, `main_arena` == `selected_arena` self.selected_arena = self.__libc_main_arena - except: + except Exception: # the search for arena can fail when the session is not started pass return self.__libc_main_arena @@ -11030,7 +11043,8 @@ def find_main_arena_addr() -> int: try: dbg("Trying to bruteforce main_arena address") # setup search_range for `main_arena` to `.data` of glibc - search_filter = lambda f: "libc" in pathlib.Path(f.filename).name and f.name == ".data" + def search_filter(zone: Zone) -> bool: + return "libc" in pathlib.Path(zone.filename).name and zone.name == ".data" for dotdata in list(filter(search_filter, get_info_files())): search_range = range(dotdata.zone_start, dotdata.zone_end, alignment) @@ -11113,9 +11127,10 @@ def tidx2size(self, idx: int) -> int: def malloc_align_address(self, address: int) -> int: """Align addresses according to glibc's MALLOC_ALIGNMENT. See also Issue #689 on Github""" + def ceil(n: int) -> int: + return int(-1 * n // 1 * -1) malloc_alignment = self.malloc_alignment - ceil = lambda n: int(-1 * n // 1 * -1) - return malloc_alignment * ceil((address / malloc_alignment)) + return malloc_alignment * ceil((address // malloc_alignment)) class GefSetting: @@ -11387,12 +11402,13 @@ def __repr__(self): return f"RemoteMode = {str(self)} ({int(self)})" def prompt_string(self) -> str: - if self == GefRemoteSessionManager.RemoteMode.QEMU: - return Color.boldify("(qemu) ") - if self == GefRemoteSessionManager.RemoteMode.RR: - return Color.boldify("(rr) ") - if self == GefRemoteSessionManager.RemoteMode.GDBSERVER: - return Color.boldify("(remote) ") + match self: + case GefRemoteSessionManager.RemoteMode.QEMU: + return Color.boldify("(qemu) ") + case GefRemoteSessionManager.RemoteMode.RR: + return Color.boldify("(rr) ") + case GefRemoteSessionManager.RemoteMode.GDBSERVER: + return Color.boldify("(remote) ") raise AttributeError("Unknown value") def __init__(self, host: str, port: int, pid: int =-1, qemu: Optional[pathlib.Path] = None) -> None: @@ -11402,6 +11418,8 @@ def __init__(self, host: str, port: int, pid: int =-1, qemu: Optional[pathlib.Pa self.__local_root_fd = tempfile.TemporaryDirectory() self.__local_root_path = pathlib.Path(self.__local_root_fd.name) self.__qemu = qemu + if pid > 0: + self._pid = pid if self.__qemu is not None: self._mode = GefRemoteSessionManager.RemoteMode.QEMU @@ -11417,7 +11435,7 @@ def close(self) -> None: gef_on_new_hook(new_objfile_handler) except Exception as e: warn(f"Exception while restoring local context: {str(e)}") - return + raise e def __str__(self) -> str: return f"RemoteSession(target='{self.target}', local='{self.root}', pid={self.pid}, mode={self.mode})" @@ -11478,7 +11496,7 @@ def sync(self, src: str, dst: Optional[str] = None) -> bool: def connect(self, pid: int) -> bool: """Connect to remote target. If in extended mode, also attach to the given PID.""" # before anything, register our new hook to download files from the remote target - dbg(f"[remote] Installing new objfile handlers") + dbg("[remote] Installing new objfile handlers") try: gef_on_new_unhook(new_objfile_handler) except SystemError: @@ -11506,17 +11524,19 @@ def connect(self, pid: int) -> bool: def setup(self) -> bool: # setup remote adequately depending on remote or qemu mode - if self.mode == GefRemoteSessionManager.RemoteMode.QEMU: - dbg(f"Setting up as qemu session, target={self.__qemu}") - self.__setup_qemu() - elif self.mode == GefRemoteSessionManager.RemoteMode.RR: - dbg(f"Setting up as rr session") - self.__setup_rr() - elif self.mode == GefRemoteSessionManager.RemoteMode.GDBSERVER: - dbg(f"Setting up as remote session") - self.__setup_remote() - else: - raise Exception + match self.mode: + case GefRemoteSessionManager.RemoteMode.QEMU: + dbg(f"Setting up as qemu session, target={self.__qemu}") + self.__setup_qemu() + case GefRemoteSessionManager.RemoteMode.RR: + dbg("Setting up as rr session") + self.__setup_rr() + case GefRemoteSessionManager.RemoteMode.GDBSERVER: + dbg("Setting up as remote session") + self.__setup_remote() + case _: + raise ValueError + # refresh gef to consider the binary reset_all_caches() gef.binary = Elf(self.lfile) @@ -11581,7 +11601,7 @@ def __setup_rr(self) -> bool: self.__local_root_path = pathlib.Path("/") return True - def remote_objfile_event_handler(self, evt: "gdb.events.NewObjFileEvent") -> None: + def remote_objfile_event_handler(self, evt: "gdb.NewObjFileEvent") -> None: dbg(f"[remote] in remote_objfile_handler({evt.new_objfile.filename if evt else 'None'}))") if not evt or not evt.new_objfile.filename: return @@ -11710,10 +11730,30 @@ def reset_caches(self) -> None: def target_remote_posthook(): - if gef.session.remote_initializing: - return + conn = gdb.selected_inferior().connection + # if here, the connection should be established + if not isinstance(conn, gdb.RemoteTargetConnection): + raise EnvironmentError("Failed to create a remote session") + + # if gef.session.remote_initializing: + # dbg(f"remote session already initializing: {gef.session}") + # return + + print(f"{conn.description=}") + print(f"{conn.details=}") + print(f"{conn.type=}") + + assert conn.details + host, port = conn.details.split(":") + pid = ( + gdb.selected_inferior().pid + if False + else gdb.selected_thread().ptid[1] + ) + print(host, port, pid) + gef.session.remote = GefRemoteSessionManager(host, int(port), pid) + gef.session.remote._mode = GefRemoteSessionManager.RemoteMode.GDBSERVER - gef.session.remote = GefRemoteSessionManager("", 0) if not gef.session.remote.setup(): raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote}") @@ -11729,17 +11769,6 @@ def target_remote_posthook(): f"(with Python {'.'.join(map(str, PYTHON_MIN_VERSION))} or higher).") exit(1) - # When using a Python virtual environment, GDB still loads the system-installed Python - # so GEF doesn't load site-packages dir from environment - # In order to fix it, from the shell with venv activated we run the python binary, - # take and parse its path, add the path to the current python process using sys.path.extend - PYTHONBIN = which("python3") - PREFIX = gef_pystring(subprocess.check_output([PYTHONBIN, '-c', 'import os, sys;print((sys.prefix))'])).strip("\\n") - if PREFIX != sys.base_prefix: - SITE_PACKAGES_DIRS = subprocess.check_output( - [PYTHONBIN, "-c", "import os, sys;print(os.linesep.join(sys.path).strip())"]).decode("utf-8").split() - sys.path.extend(SITE_PACKAGES_DIRS) - # setup config gdb_initial_settings = ( "set confirm off", @@ -11771,6 +11800,14 @@ def target_remote_posthook(): # reload settings gdb.execute("gef restore") + # extend `sys.path` for venv compat + if gef.config["gef.extra_python_package_paths"]: + for path in map(pathlib.Path, gef.config["gef.extra_python_package_paths"].split(";")): + if not path.exists(): + warn(f"Skipping invalid directory {path}") + continue + sys.path.extend(str(path.absolute())) + # setup gdb prompt gdb.prompt_hook = __gef_prompt__ @@ -11789,32 +11826,16 @@ def target_remote_posthook(): GefTmuxSetup() - if GDB_VERSION > (9, 0): - disable_tr_overwrite_setting = "gef.disable_target_remote_overwrite" - - if not gef.config[disable_tr_overwrite_setting]: - warnmsg = ("Using `target remote` with GEF should work in most cases, " - "but use `gef-remote` if you can. You can disable the " - "overwrite of the `target remote` command by toggling " - f"`{disable_tr_overwrite_setting}` in the config.") - hook = f""" - define target hookpost-{{}} - pi target_remote_posthook() - context - pi if calling_function() != "connect": warn("{warnmsg}") - end - """ - - # Register a post-hook for `target remote` that initialize the remote session - gdb.execute(hook.format("remote")) - gdb.execute(hook.format("extended-remote")) - else: - errmsg = ("Using `target remote` does not work, use `gef-remote` " - f"instead. You can toggle `{disable_tr_overwrite_setting}` " - "if this is not desired.") - hook = f"""pi if calling_function() != "connect": err("{errmsg}")""" - gdb.execute(f"define target hook-remote\n{hook}\nend") - gdb.execute(f"define target hook-extended-remote\n{hook}\nend") + # Register a post-hook for `target remote` that initialize the remote session + hook = """ + define target hookpost-{} + pi target_remote_posthook() + context + pi res = calling_function(); if res != "connect": err("Failed to initialize remote session: " + str(res)) + end + """ + gdb.execute(hook.format("remote")) + gdb.execute(hook.format("extended-remote")) # restore saved breakpoints (if any) bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute() diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..6a3fc3001 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,2 @@ +[lint.per-file-ignores] +"gef.py" = ["E701"] \ No newline at end of file From cde95b309f124dc69cbc67646a5e3f9ea07746e1 Mon Sep 17 00:00:00 2001 From: hugsy Date: Tue, 24 Sep 2024 13:36:24 -0700 Subject: [PATCH 02/38] checkpoint --- gef.py | 338 +++++++++++++++++------------------ tests/base.py | 8 +- tests/commands/gef_remote.py | 2 +- tests/commands/theme.py | 2 +- 4 files changed, 173 insertions(+), 177 deletions(-) diff --git a/gef.py b/gef.py index 621270212..17d602c23 100644 --- a/gef.py +++ b/gef.py @@ -82,8 +82,8 @@ from functools import lru_cache 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, TypeVar, +from typing import (Any, ByteString, Callable, Generator, Iterable, + Iterator, NoReturn, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, cast) from urllib.request import urlopen @@ -101,7 +101,7 @@ def http_get(url: str) -> Optional[bytes]: return None -def update_gef(argv: List[str]) -> int: +def update_gef(argv: list[str]) -> int: """Obsolete. Use `gef.sh`.""" return -1 @@ -146,7 +146,7 @@ def update_gef(argv: List[str]) -> int: __registered_commands__ : Set[Type["GenericCommand"]] = set() __registered_functions__ : Set[Type["GenericFunction"]] = set() -__registered_architectures__ : Dict[Union["Elf.Abi", str], Type["Architecture"]] = {} +__registered_architectures__ : dict[Union["Elf.Abi", str], Type["Architecture"]] = {} __registered_file_formats__ : Set[ Type["FileFormat"] ] = set() GefMemoryMapProvider = Callable[[], Generator["Section", None, None]] @@ -363,7 +363,6 @@ def calling_function() -> Optional[str]: # def only_if_gdb_running(f: Callable) -> Callable: """Decorator wrapper to check if GDB is running.""" - @functools.wraps(f) def wrapper(*args: Any, **kwargs: Any) -> Any: if is_alive(): @@ -447,8 +446,8 @@ def FakeExit(*args: Any, **kwargs: Any) -> NoReturn: sys.exit = FakeExit -def parse_arguments(required_arguments: Dict[Union[str, Tuple[str, str]], Any], - optional_arguments: Dict[Union[str, Tuple[str, str]], Any]) -> Callable: +def parse_arguments(required_arguments: dict[Union[str, Tuple[str, str]], Any], + optional_arguments: dict[Union[str, Tuple[str, str]], Any]) -> Callable: """Argument parsing decorator.""" def int_wrapper(x: str) -> int: return int(x, 0) @@ -797,8 +796,8 @@ class FileFormat: name: str path: pathlib.Path entry_point: int - checksec: Dict[str, bool] - sections: List[FileFormatSection] + checksec: dict[str, bool] + sections: list[FileFormatSection] def __init__(self, path: Union[str, pathlib.Path]) -> None: raise NotImplementedError @@ -887,11 +886,11 @@ class OsAbi(enum.Enum): e_shstrndx: int path: pathlib.Path - phdrs : List["Phdr"] - shdrs : List["Shdr"] + phdrs : list["Phdr"] + shdrs : list["Shdr"] name: str = "ELF" - __checksec : Dict[str, bool] + __checksec : dict[str, bool] def __init__(self, path: Union[str, pathlib.Path]) -> None: """Instantiate an ELF object. A valid ELF must be provided, or an exception will be thrown.""" @@ -977,7 +976,7 @@ def is_valid(cls, path: pathlib.Path) -> bool: return u32(path.open("rb").read(4), e = Endianness.BIG_ENDIAN) == Elf.ELF_MAGIC @property - def checksec(self) -> Dict[str, bool]: + def checksec(self) -> dict[str, bool]: """Check the security property of the ELF binary. The following properties are: - Canary - NX @@ -1244,7 +1243,7 @@ def __str__(self) -> str: class Instruction: """GEF representation of a CPU instruction.""" - def __init__(self, address: int, location: str, mnemo: str, operands: List[str], opcodes: bytes) -> None: + def __init__(self, address: int, location: str, mnemo: str, operands: list[str], opcodes: bytes) -> None: self.address, self.location, self.mnemonic, self.operands, self.opcodes = \ address, location, mnemo, operands, opcodes return @@ -1549,7 +1548,7 @@ def heap_addr(self, allow_unaligned: bool = False) -> Optional[int]: return _addr return gef.heap.malloc_align_address(_addr) - def get_heap_info_list(self) -> Optional[List[GlibcHeapInfo]]: + def get_heap_info_list(self) -> Optional[list[GlibcHeapInfo]]: if self.is_main_arena(): return None heap_addr = self.get_heap_for_ptr(self.top) @@ -2237,7 +2236,7 @@ def gef_disassemble(addr: int, nb_insn: int, nb_prev: int = 0) -> Generator[Inst yield insn -def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> Union[str, List[str]]: +def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> Union[str, list[str]]: """Execute an external command and return the result.""" res = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=kwargs.get("shell", False)) return [gef_pystring(_) for _ in res.splitlines()] if as_list else gef_pystring(res) @@ -2259,7 +2258,7 @@ def gef_execute_gdb_script(commands: str) -> None: @deprecated("Use Elf(fname).checksec()") -def checksec(filename: str) -> Dict[str, bool]: +def checksec(filename: str) -> dict[str, bool]: return Elf(filename).checksec @@ -2311,7 +2310,7 @@ def is_little_endian() -> bool: return gef.arch.endianness == Endianness.LITTLE_ENDIAN -def flags_to_human(reg_value: int, value_table: Dict[int, str]) -> str: +def flags_to_human(reg_value: int, value_table: dict[int, str]) -> str: """Return a human readable string showing the flag states.""" flags = [] for bit_index, name in value_table.items(): @@ -2366,7 +2365,7 @@ class Architecture(ArchitectureBase): return_register: str flag_register: Optional[str] instruction_length: Optional[int] - flags_table: Dict[int, str] + flags_table: dict[int, str] syscall_register: Optional[str] syscall_instructions: Union[Tuple[()], Tuple[str, ...]] function_parameters: Union[Tuple[()], Tuple[str, ...]] @@ -3559,7 +3558,7 @@ def get_function_length(sym: str) -> int: @lru_cache() -def get_info_files() -> List[Zone]: +def get_info_files() -> list[Zone]: """Retrieve all the files loaded by debuggee.""" lines = (gdb.execute("info files", to_string=True) or "").splitlines() infos = [] @@ -3611,7 +3610,7 @@ def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Optiona err("Process is not running") return None - matches: Dict[str, Section] = dict() + matches: dict[str, Section] = dict() for sect in gef.memory.maps: filename = pathlib.Path(sect.path).name @@ -4116,7 +4115,7 @@ def get_register(regname) -> Optional[int]: @deprecated("Use `gef.memory.maps`") -def get_process_maps() -> List[Section]: +def get_process_maps() -> list[Section]: return gef.memory.maps @@ -4691,8 +4690,8 @@ class GenericCommand(gdb.Command): _cmdline_: str _syntax_: str - _example_: Union[str, List[str]] = "" - _aliases_: List[str] = [] + _example_: Union[str, list[str]] = "" + _aliases_: list[str] = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) @@ -4740,7 +4739,7 @@ def usage(self) -> None: err(f"Syntax\n{self._syntax_}") return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: raise NotImplementedError def pre_load(self) -> None: @@ -4759,7 +4758,7 @@ def __iter__(self) -> Generator[str, None, None]: yield key.replace(f"{self._cmdline_}.", "", 1) @property - def settings(self) -> List[str]: + def settings(self) -> list[str]: """Return the list of settings for this command.""" return list(iter(self)) @@ -4811,7 +4810,7 @@ def __delitem__(self, name: str) -> None: del gef.config[self.__get_setting_name(name)] return - def __set_repeat_count(self, argv: List[str], from_tty: bool) -> None: + def __set_repeat_count(self, argv: list[str], from_tty: bool) -> None: if not from_tty: self.repeat = False self.repeat_count = 0 @@ -4836,7 +4835,7 @@ def __init__(self) -> None: super().__init__(prefix=True) return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: self.usage() return @@ -4849,7 +4848,7 @@ class ArchGetCommand(GenericCommand): _syntax_ = f"{_cmdline_}" _example_ = f"{_cmdline_}" - def do_invoke(self, args: List[str]) -> None: + def do_invoke(self, args: list[str]) -> None: gef_print(f"{Color.greenify('Arch')}: {gef.arch}") gef_print(f"{Color.greenify('Reason')}: {gef.arch_reason}") @@ -4862,10 +4861,10 @@ class ArchSetCommand(GenericCommand): _syntax_ = f"{_cmdline_} " _example_ = f"{_cmdline_} X86" - def do_invoke(self, args: List[str]) -> None: + def do_invoke(self, args: list[str]) -> None: reset_architecture(args[0].lower() if args else None) - def complete(self, text: str, word: str) -> List[str]: + def complete(self, text: str, word: str) -> list[str]: return sorted(x for x in __registered_architectures__.keys() if isinstance(x, str) and x.lower().startswith(text.lower().strip())) @@ -4877,7 +4876,7 @@ class ArchListCommand(GenericCommand): _syntax_ = f"{_cmdline_}" _example_ = f"{_cmdline_}" - def do_invoke(self, args: List[str]) -> None: + def do_invoke(self, args: list[str]) -> None: gef_print(Color.greenify("Available architectures:")) for arch in sorted(set(__registered_architectures__.values()), key=lambda x: x.arch): if arch is GenericArchitecture: @@ -4897,7 +4896,7 @@ class VersionCommand(GenericCommand): _syntax_ = f"{_cmdline_}" _example_ = f"{_cmdline_}" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: gef_fpath = pathlib.Path(inspect.stack()[0][1]).expanduser().absolute() gef_dir = gef_fpath.parent with gef_fpath.open("rb") as f: @@ -4944,7 +4943,7 @@ def __init__(self) -> None: return @property - def format_matrix(self) -> Dict[int, Tuple[str, str, str]]: + def format_matrix(self) -> dict[int, Tuple[str, str, str]]: # `gef.arch.endianness` is a runtime property, should not be defined as a class property return { 8: (f"{gef.arch.endianness}B", "char", "db"), @@ -4955,7 +4954,7 @@ def format_matrix(self) -> Dict[int, Tuple[str, str, str]]: @only_if_gdb_running @parse_arguments({"location": "$pc", }, {("--length", "-l"): 256, "--bitlen": 0, "--lang": "py", "--clip": False,}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: """Default value for print-format command.""" args: argparse.Namespace = kwargs["arguments"] args.bitlen = args.bitlen or gef.arch.ptrsize * 2 @@ -5023,7 +5022,7 @@ def __init__(self) -> None: super().__init__(prefix=True) return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: self.usage() return @@ -5037,7 +5036,7 @@ class PieBreakpointCommand(GenericCommand): _syntax_ = f"{_cmdline_} OFFSET" @parse_arguments({"offset": ""}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] if not args.offset: self.usage() @@ -5069,7 +5068,7 @@ class PieInfoCommand(GenericCommand): _syntax_ = f"{_cmdline_} BREAKPOINT" @parse_arguments({"breakpoints": [-1,]}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] if args.breakpoints[0] == -1: # No breakpoint info needed @@ -5095,7 +5094,7 @@ class PieDeleteCommand(GenericCommand): _syntax_ = f"{_cmdline_} [BREAKPOINT]" @parse_arguments({"breakpoints": [-1,]}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: global gef args : argparse.Namespace = kwargs["arguments"] if args.breakpoints[0] == -1: @@ -5110,7 +5109,7 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: @staticmethod - def delete_bp(breakpoints: List[PieVirtualBreakpoint]) -> None: + def delete_bp(breakpoints: list[PieVirtualBreakpoint]) -> None: global gef for bp in breakpoints: # delete current real breakpoints if exists @@ -5128,7 +5127,7 @@ class PieRunCommand(GenericCommand): _cmdline_ = "pie run" _syntax_ = _cmdline_ - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: global gef fpath = get_filepath() if not fpath: @@ -5171,7 +5170,7 @@ class PieAttachCommand(GenericCommand): _cmdline_ = "pie attach" _syntax_ = f"{_cmdline_} PID" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: try: gdb.execute(f"attach {' '.join(argv)}", to_string=True) except gdb.error as e: @@ -5195,7 +5194,7 @@ class PieRemoteCommand(GenericCommand): _cmdline_ = "pie remote" _syntax_ = f"{_cmdline_} REMOTE" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: try: gdb.execute(f"gef-remote {' '.join(argv)}") except gdb.error as e: @@ -5221,7 +5220,7 @@ class SmartEvalCommand(GenericCommand): _example_ = (f"\n{_cmdline_} $pc+1" f"\n{_cmdline_} 0x00007ffff7a10000 0x00007ffff7bce000") - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: argc = len(argv) if argc == 1: self.evaluate(argv) @@ -5231,7 +5230,7 @@ def do_invoke(self, argv: List[str]) -> None: self.distance(argv) return - def evaluate(self, expr: List[str]) -> None: + def evaluate(self, expr: list[str]) -> None: def show_as_int(i: int) -> None: off = gef.arch.ptrsize*8 def comp2_x(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):x}" @@ -5268,7 +5267,7 @@ def comp2_b(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):b}" gef_print(" ".join(parsed_expr)) return - def distance(self, args: List[str]) -> None: + def distance(self, args: list[str]) -> None: try: x = int(args[0], 16) if is_hex(args[0]) else int(args[0]) y = int(args[1], 16) if is_hex(args[1]) else int(args[1]) @@ -5286,7 +5285,7 @@ class CanaryCommand(GenericCommand): _syntax_ = _cmdline_ @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: self.dont_repeat() fname = get_filepath() @@ -5321,7 +5320,7 @@ def __init__(self) -> None: @only_if_gdb_running @only_if_gdb_target_local - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: self.show_info_proc() self.show_ancestor() self.show_descendants() @@ -5329,7 +5328,7 @@ def do_invoke(self, argv: List[str]) -> None: self.show_connections() return - def get_state_of(self, pid: int) -> Dict[str, str]: + def get_state_of(self, pid: int) -> dict[str, str]: res = {} with open(f"/proc/{pid}/status", "r") as f: file = f.readlines() @@ -5345,7 +5344,7 @@ def get_cmdline_of(self, pid: int) -> str: def get_process_path_of(self, pid: int) -> str: return os.readlink(f"/proc/{pid}/exe") - def get_children_pids(self, pid: int) -> List[int]: + def get_children_pids(self, pid: int) -> list[int]: cmd = [gef.session.constants["ps"], "-o", "pid", "--ppid", f"{pid}", "--noheaders"] try: return [int(x) for x in gef_execute_external(cmd, as_list=True)] @@ -5400,7 +5399,7 @@ def show_fds(self) -> None: gef_print(f"\t{fullpath} {RIGHT_ARROW} {os.readlink(fullpath)}") return - def list_sockets(self, pid: int) -> List[int]: + def list_sockets(self, pid: int) -> list[int]: sockets = [] path = f"/proc/{pid:d}/fd" items = os.listdir(path) @@ -5469,7 +5468,7 @@ class GefThemeCommand(GenericCommand): _cmdline_ = "theme" _syntax_ = f"{_cmdline_} [KEY [VALUE]]" - _examples_ = (f"{_cmdline_} address_stack green") + _example_ = (f"{_cmdline_} address_stack green") def __init__(self) -> None: super().__init__(self._cmdline_) @@ -5492,7 +5491,7 @@ def __init__(self) -> None: self["source_current_line"] = ("green", "Color to use for the current code line in the source window") return - def do_invoke(self, args: List[str]) -> None: + def do_invoke(self, args: list[str]) -> None: self.dont_repeat() argc = len(args) @@ -5514,8 +5513,7 @@ def do_invoke(self, args: List[str]) -> None: return colors = (color for color in args[1:] if color in Color.colors) - self[setting_name][0] = " ".join(colors) - return + self[setting_name] = " ".join(colors) # type: ignore // this is valid since we overwrote __setitem__() class ExternalStructureManager: @@ -5534,7 +5532,7 @@ def __str__(self) -> str: return self.name def pprint(self) -> None: - res: List[str] = [] + res: list[str] = [] for _name, _type in self.class_type._fields_: # type: ignore size = ctypes.sizeof(_type) name = Color.colorify(_name, gef.config["pcustom.structure_name"]) @@ -5751,7 +5749,7 @@ def __init__(self) -> None: return @parse_arguments({"type": "", "address": ""}, {}) - def do_invoke(self, *_: Any, **kwargs: Dict[str, Any]) -> None: + def do_invoke(self, *_: Any, **kwargs: dict[str, Any]) -> None: args = cast(argparse.Namespace, kwargs["arguments"]) if not args.type: gdb.execute("pcustom list") @@ -5795,7 +5793,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, _: List) -> None: + def do_invoke(self, _: list) -> None: """Dump the list of all the structures and their respective.""" manager = ExternalStructureManager() info(f"Listing custom structures from '{manager.path}'") @@ -5820,7 +5818,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) == 0: self.usage() return @@ -5848,7 +5846,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) == 0: self.usage() return @@ -5892,7 +5890,7 @@ class ChangeFdCommand(GenericCommand): @only_if_gdb_running @only_if_gdb_target_local - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) != 2: self.usage() return @@ -5970,7 +5968,7 @@ class ScanSectionCommand(GenericCommand): _example_ = f"\n{_cmdline_} stack libc" @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) != 2: self.usage() return @@ -6067,7 +6065,7 @@ def print_loc(self, loc: Tuple[int, int, str]) -> None: gef_print(f""" {loc[0]:#x} - {loc[1]:#x} {RIGHT_ARROW} "{Color.pinkify(loc[2])}" """) return - def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> List[Tuple[int, int, str]]: + def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> list[Tuple[int, int, str]]: """Search a pattern within a range defined by arguments.""" _pattern = gef_pybytes(pattern) step = self["nr_pages_chunk"] * gef.session.pagesize @@ -6099,7 +6097,7 @@ def search_pattern_by_address(self, pattern: str, start_address: int, end_addres return locations - def search_binpattern_by_address(self, binpattern: bytes, start_address: int, end_address: int) -> List[Tuple[int, int, str]]: + def search_binpattern_by_address(self, binpattern: bytes, start_address: int, end_address: int) -> list[Tuple[int, int, str]]: """Search a binary pattern within a range defined by arguments.""" step = self["nr_pages_chunk"] * gef.session.pagesize @@ -6152,7 +6150,7 @@ def search_pattern(self, pattern: str, section_name: str) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: argc = len(argv) if argc < 1: self.usage() @@ -6217,7 +6215,7 @@ class FlagsCommand(GenericCommand): _example_ = (f"\n{_cmdline_}" f"\n{_cmdline_} +zero # sets ZERO flag") - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not gef.arch.flag_register: warn(f"The architecture {gef.arch.arch}:{gef.arch.mode} doesn't have flag register.") return @@ -6273,7 +6271,7 @@ def __init__(self) -> None: return @parse_arguments({"host": "", "port": 0}, {"--pid": 0, "--qemu-user": ""}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: if gef.session.remote is not None: err("You already are in remote session. Close it first before opening a new one...") return @@ -6335,7 +6333,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"address": "$pc"}, {"--n": 1}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] address = parse_address(args.address) num_instructions = args.n @@ -6364,7 +6362,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: target_addr = gef_next_instruction(parse_address("$pc")).address JustSilentStopBreakpoint("".join(["*", str(target_addr)])) gdb.execute("continue") @@ -6397,7 +6395,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"address": "$pc"}, {"--i": 1, "--b": False, "--f": False, "--n": False}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] address = parse_address(args.address) nop = gef.arch.nop_insn @@ -6480,7 +6478,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"address": ""}, {("-r", "--retval"): 0}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] loc = args.address if args.address else f"*{gef.arch.pc:#x}" StubBreakpoint(loc, args.retval) @@ -6499,7 +6497,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: self.usage() return @@ -6518,7 +6516,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"addr": ""}, {"--reset": False}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: global gef args: argparse.Namespace = kwargs["arguments"] @@ -6555,7 +6553,7 @@ class GlibcHeapArenaCommand(GenericCommand): _syntax_ = _cmdline_ @only_if_gdb_running - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: for arena in gef.heap.arenas: gef_print(str(arena)) return @@ -6575,7 +6573,7 @@ def __init__(self) -> None: @parse_arguments({"address": ""}, {"--allow-unaligned": False, "--number": 1}) @only_if_gdb_running - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] if not args.address: err("Missing chunk address") @@ -6680,7 +6678,7 @@ def __init__(self) -> None: @parse_arguments({"arena_address": ""}, {("--all", "-a"): False, "--allow-unaligned": False, "--min-size": 0, "--max-size": 0, ("--count", "-n"): -1, ("--summary", "-s"): False, "--resolve": False}) @only_if_gdb_running - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args = kwargs["arguments"] ctx = GlibcHeapWalkContext(print_arena=args.all, allow_unaligned=args.allow_unaligned, min_size=args.min_size, max_size=args.max_size, count=args.count, resolve_type=args.resolve, summary=args.summary) if args.all or not args.arena_address: @@ -6776,7 +6774,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: for bin_t in self._bin_types_: gdb.execute(f"heap bins {bin_t}") @@ -6839,7 +6837,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: # Determine if we are using libc with tcache built in (2.26+) if gef.libc.version and gef.libc.version < (2, 26): info("No Tcache in this version of libc") @@ -6936,7 +6934,7 @@ def find_tcache(self) -> int: tcache_addr = heap_base + 0x10 return tcache_addr - def check_thread_ids(self, tids: List[int]) -> List[int]: + def check_thread_ids(self, tids: list[int]) -> list[int]: """Return the subset of tids that are currently valid.""" existing_tids = set(t.num for t in gdb.selected_inferior().threads()) return list(set(tids) & existing_tids) @@ -7093,7 +7091,7 @@ def do_invoke(self, *_: Any, **kwargs: Any) -> None: arena_address = args.arena_address or f"{gef.heap.selected_arena.address:#x}" gef_print(titlify(f"Small Bins for arena at {arena_address}")) - bins: Dict[int, int] = {} + bins: dict[int, int] = {} heap_bins_cmd = gef.gdb.commands["heap bins"] assert isinstance (heap_bins_cmd, GlibcHeapBinsCommand) for i in range(1, 63): @@ -7151,7 +7149,7 @@ class DetailRegistersCommand(GenericCommand): @only_if_gdb_running @parse_arguments({"registers": [""]}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: unchanged_color = gef.config["theme.registers_register_name"] changed_color = gef.config["theme.registers_value_changed"] string_color = gef.config["theme.dereference_string"] @@ -7246,7 +7244,7 @@ def __init__(self) -> None: super().__init__(prefix=True) return - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: err("Missing sub-command (search|get)") self.usage() return @@ -7263,18 +7261,14 @@ class ShellcodeSearchCommand(GenericCommand): api_base = "http://shell-storm.org" search_url = f"{api_base}/api/?s=" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: err("Missing pattern to search") self.usage() return - self.search_shellcode(argv) - return - - def search_shellcode(self, search_options: List) -> None: # API : http://shell-storm.org/shellcode/ - args = "*".join(search_options) + args = "*".join(argv) res = http_get(self.search_url + args) if res is None: @@ -7312,7 +7306,7 @@ class ShellcodeGetCommand(GenericCommand): api_base = "http://shell-storm.org" get_url = f"{api_base}/shellcode/files/shellcode-{{:d}}.html" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) != 1: err("Missing ID to download") self.usage() @@ -7358,7 +7352,7 @@ def __init__(self) -> None: return @parse_arguments({"pattern": ""}, {"--attach": False, "--smart-scan": False}) - def do_invoke(self, _: List, **kwargs: Any) -> None: + def do_invoke(self, _: list, **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] do_attach = args.attach smart_scan = args.smart_scan @@ -7388,7 +7382,7 @@ def do_invoke(self, _: List, **kwargs: Any) -> None: return None - def get_processes(self) -> Generator[Dict[str, str], None, None]: + def get_processes(self) -> Generator[dict[str, str], None, None]: output = gef_execute_external(self["ps_command"].split(), True) names = [x.lower().replace("%", "") for x in output[0].split()] @@ -7421,7 +7415,7 @@ def __init__(self) -> None: return @parse_arguments({}, {"--filename": ""}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] if is_qemu_system(): @@ -7500,7 +7494,7 @@ def __init__(self) -> None: self["entrypoint_symbols"] = ("main _main __libc_start_main __uClibc_main start _start", "Possible symbols for entry points") return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: fpath = get_filepath() if fpath is None: warn("No executable to debug, use `file` to load a binary") @@ -7554,7 +7548,7 @@ def set_init_tbreak(self, addr: int) -> EntryBreakBreakpoint: bp = EntryBreakBreakpoint(f"*{addr:#x}") return bp - def set_init_tbreak_pie(self, addr: int, argv: List[str]) -> EntryBreakBreakpoint: + def set_init_tbreak_pie(self, addr: int, argv: list[str]) -> EntryBreakBreakpoint: warn("PIC binary detected, retrieving text base address") gdb.execute("set stop-on-solib-events 1") hide_context() @@ -7580,7 +7574,7 @@ def __init__(self) -> None: return @parse_arguments({"name": "", "address": "*$pc"}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] if not args.name: err("Missing name for breakpoint") @@ -7602,7 +7596,7 @@ class ContextCommand(GenericCommand): _syntax_ = f"{_cmdline_} [legend|regs|stack|code|args|memory|source|trace|threads|extra]" _aliases_ = ["ctx",] - old_registers: Dict[str, Optional[int]] = {} + old_registers: dict[str, Optional[int]] = {} def __init__(self) -> None: super().__init__() @@ -7630,7 +7624,7 @@ def __init__(self) -> None: self["libc_args"] = (False, "[DEPRECATED - Unused] Show libc function call args description") self["libc_args_path"] = ("", "[DEPRECATED - Unused] Path to libc function call args json files, provided via gef-extras") - self.layout_mapping: Dict[str, Tuple[Callable, Optional[Callable], Optional[Callable]]] = { + self.layout_mapping: dict[str, Tuple[Callable, Optional[Callable], Optional[Callable]]] = { "legend": (self.show_legend, None, None), "regs": (self.context_regs, None, None), "stack": (self.context_stack, None, None), @@ -7663,7 +7657,7 @@ def show_legend(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not self["enable"] or gef.ui.context_hidden: return @@ -7826,7 +7820,7 @@ def context_stack(self) -> None: return - def addr_has_breakpoint(self, address: int, bp_locations: List[str]) -> bool: + def addr_has_breakpoint(self, address: int, bp_locations: list[str]) -> bool: return any(hex(address) in b for b in bp_locations) def context_code(self) -> None: @@ -8026,7 +8020,7 @@ def __get_current_block_start_address() -> Optional[int]: gef_print(")") return - def line_has_breakpoint(self, file_name: str, line_number: int, bp_locations: List[str]) -> bool: + def line_has_breakpoint(self, file_name: str, line_number: int, bp_locations: list[str]) -> bool: filename_line = f"{file_name}:{line_number}" return any(filename_line in loc for loc in bp_locations) @@ -8306,7 +8300,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: self.usage() return @@ -8324,7 +8318,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) not in (1, 2, 3): self.usage() return @@ -8363,7 +8357,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: self.usage() return @@ -8384,7 +8378,7 @@ class MemoryWatchResetCommand(GenericCommand): _syntax_ = f"{_cmdline_}" @only_if_gdb_running - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: gef.ui.watches.clear() ok("Memory watches cleared") return @@ -8397,7 +8391,7 @@ class MemoryWatchListCommand(GenericCommand): _syntax_ = f"{_cmdline_}" @only_if_gdb_running - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: if not gef.ui.watches: info("No memory watches") return @@ -8425,7 +8419,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"address": "",}, {("--reverse", "-r"): False, ("--size", "-s"): 0}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: valid_formats = ["byte", "word", "dword", "qword"] if not self.format or self.format not in valid_formats: err("Invalid command") @@ -8452,7 +8446,7 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: gef_print("\n".join(lines)) return - def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = 0) -> List[str]: + def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = 0) -> list[str]: endianness = gef.arch.endianness base_address_color = gef.config["theme.dereference_base_address"] @@ -8563,7 +8557,7 @@ def __init__(self) -> None: @only_if_gdb_running @parse_arguments({"location": "", "values": ["", ]}, {}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] if not self.format or self.format not in self.SUPPORTED_SIZES: self.usage() @@ -8663,7 +8657,7 @@ class PatchStringCommand(GenericCommand): ] @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: argc = len(argv) if argc != 2: self.usage() @@ -8681,7 +8675,7 @@ def do_invoke(self, argv: List[str]) -> None: @lru_cache() -def dereference_from(address: int) -> List[str]: +def dereference_from(address: int) -> list[str]: if not is_alive(): return [format_address(address),] @@ -8791,7 +8785,7 @@ def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str: @only_if_gdb_running @parse_arguments({"address": "$sp"}, {("-r", "--reference"): "", ("-l", "--length"): 10}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] nb = args.length @@ -8845,7 +8839,7 @@ class ASLRCommand(GenericCommand): _cmdline_ = "aslr" _syntax_ = f"{_cmdline_} [(on|off)]" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: argc = len(argv) if argc == 0: @@ -8887,7 +8881,7 @@ class ResetCacheCommand(GenericCommand): _cmdline_ = "reset-cache" _syntax_ = _cmdline_ - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: reset_all_caches() return @@ -8902,7 +8896,7 @@ class VMMapCommand(GenericCommand): _example_ = f"{_cmdline_} libc" @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: vmmap = gef.memory.maps if not vmmap: err("No address mapping information found") @@ -8984,7 +8978,7 @@ class XFilesCommand(GenericCommand): _example_ = f"\n{_cmdline_} libc\n{_cmdline_} libc IO_vtables" @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: color = gef.config["theme.table_heading"] headers = ["Start", "End", "Name", "File"] gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<21s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color)) @@ -9022,7 +9016,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: err("At least one valid address must be specified") self.usage() @@ -9083,7 +9077,7 @@ def __init__(self) -> None: super().__init__(prefix=True) return - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: self.usage() return @@ -9098,7 +9092,7 @@ class XorMemoryDisplayCommand(GenericCommand): _example_ = f"{_cmdline_} $sp 16 41414141" @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) != 3: self.usage() return @@ -9127,7 +9121,7 @@ class XorMemoryPatchCommand(GenericCommand): _example_ = f"{_cmdline_} $sp 16 41414141" @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) != 3: self.usage() return @@ -9159,7 +9153,7 @@ def __init__(self) -> None: return @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) not in (1, 2): self.usage() return @@ -9246,7 +9240,7 @@ def __init__(self) -> None: self["length"] = (1024, "Default length of a cyclic buffer to generate") return - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: self.usage() return @@ -9265,7 +9259,7 @@ class PatternCreateCommand(GenericCommand): ] @parse_arguments({"length": 0}, {"-n": 0,}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] length = args.length or gef.config["pattern.length"] n = args.n or gef.arch.ptrsize @@ -9292,7 +9286,7 @@ class PatternSearchCommand(GenericCommand): @only_if_gdb_running @parse_arguments({"pattern": ""}, {("--period", "-n"): 0, ("--max-length", "-l"): 0}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args = kwargs["arguments"] if not args.pattern: warn("No pattern provided") @@ -9361,7 +9355,7 @@ def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_FILENAME) return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: argc = len(argv) if argc == 0: @@ -9426,7 +9420,7 @@ def build_line(self, name: str, color: str, address_val: int, got_address: int) @only_if_gdb_running @parse_arguments({"symbols": [""]}, {"--all": False}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] vmmap = gef.memory.maps mapfiles = [mapfile for mapfile in vmmap if @@ -9436,7 +9430,7 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: for mapfile in mapfiles: self.print_got_for(mapfile.path, mapfile.realpath, args.symbols) - def print_got_for(self, file: str, realpath: str, argv: List[str]) -> None: + def print_got_for(self, file: str, realpath: str, argv: list[str]) -> None: readelf = gef.session.constants["readelf"] elf_file = realpath @@ -9505,7 +9499,7 @@ def __init__(self) -> None: super().__init__(prefix=True) self["regex"] = (False, "Enable regex highlighting") - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: return self.usage() @@ -9527,7 +9521,7 @@ def print_highlight_table(self) -> None: f"{Color.colorify(color, color)}") return - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: return self.print_highlight_table() @@ -9538,7 +9532,7 @@ class HighlightClearCommand(GenericCommand): _aliases_ = ["hlc"] _syntax_ = _cmdline_ - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: return gef.ui.highlight_table.clear() @@ -9550,7 +9544,7 @@ class HighlightAddCommand(GenericCommand): _aliases_ = ["highlight set", "hla"] _example_ = f"{_cmdline_} 41414141 yellow" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) < 2: return self.usage() @@ -9573,7 +9567,7 @@ class HighlightRemoveCommand(GenericCommand): ] _example_ = f"{_cmdline_} remove 41414141" - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: return self.usage() @@ -9591,7 +9585,7 @@ class FormatStringSearchCommand(GenericCommand): _syntax_ = _cmdline_ _aliases_ = ["fmtstr-helper",] - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: dangerous_functions = { "printf": 0, "sprintf": 1, @@ -9641,7 +9635,7 @@ def __init__(self) -> None: @only_if_gdb_running @experimental_feature - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if not argv: self.setup() return @@ -9742,7 +9736,7 @@ def invoke(self, *args: Any) -> int: raise gdb.GdbError("No debugging session active") return self.do_invoke(args) - def arg_to_long(self, args: List, index: int, default: int = 0) -> int: + def arg_to_long(self, args: Any, index: int, default: int = 0) -> int: try: addr = args[index] return int(addr) if addr.address is None else int(addr.address) @@ -9759,7 +9753,7 @@ class StackOffsetFunction(GenericFunction): _function_ = "_stack" _syntax_ = f"${_function_}()" - def do_invoke(self, args: List) -> int: + def do_invoke(self, args: list) -> int: base = get_section_base_address("[stack]") if not base: raise gdb.GdbError("Stack not found") @@ -9773,7 +9767,7 @@ class HeapBaseFunction(GenericFunction): _function_ = "_heap" _syntax_ = f"${_function_}()" - def do_invoke(self, args: List) -> int: + def do_invoke(self, args: list[str]) -> int: base = gef.heap.base_address if not base: base = get_section_base_address("[heap]") @@ -9790,7 +9784,7 @@ class SectionBaseFunction(GenericFunction): _syntax_ = "$_base([filepath])" _example_ = "p $_base(\\\"/usr/lib/ld-2.33.so\\\")" - def do_invoke(self, args: List) -> int: + def do_invoke(self, args: list) -> int: addr = 0 try: name = args[0].string() @@ -9818,7 +9812,7 @@ class BssBaseFunction(GenericFunction): _syntax_ = f"${_function_}([OFFSET])" _example_ = "deref $_bss(0x20)" - def do_invoke(self, args: List) -> int: + def do_invoke(self, args: list) -> int: base = get_zone_base_address(".bss") if not base: raise gdb.GdbError("BSS not found") @@ -9832,7 +9826,7 @@ class GotBaseFunction(GenericFunction): _syntax_ = f"${_function_}([OFFSET])" _example_ = "deref $_got(0x20)" - def do_invoke(self, args: List) -> int: + def do_invoke(self, args: list) -> int: base = get_zone_base_address(".got") if not base: raise gdb.GdbError("GOT not found") @@ -9922,24 +9916,24 @@ def __init__(self) -> None: gef.config["gef.propagate_debug_exception"] = GefSetting(False, bool, "If true, when debug mode is enabled, Python exceptions will be propagated all the way.") gef.config["gef.extra_python_package_paths"] = GefSetting("", str, "Semi-colon separate list of extra paths to include for python packages") - self.commands : Dict[str, GenericCommand] = collections.OrderedDict() - self.functions : Dict[str, GenericFunction] = collections.OrderedDict() - self.missing: Dict[str, Exception] = {} + self.commands : dict[str, GenericCommand] = collections.OrderedDict() + self.functions : dict[str, GenericFunction] = collections.OrderedDict() + self.missing: dict[str, Exception] = {} return @property @deprecated() - def loaded_commands(self) -> List[Tuple[str, Type[GenericCommand], Any]]: + def loaded_commands(self) -> list[Tuple[str, Type[GenericCommand], Any]]: raise ObsoleteException("Obsolete loaded_commands") @property @deprecated() - def loaded_functions(self) -> List[Type[GenericFunction]]: + def loaded_functions(self) -> list[Type[GenericFunction]]: raise ObsoleteException("Obsolete loaded_functions") @property @deprecated() - def missing_commands(self) -> Dict[str, Exception]: + def missing_commands(self) -> dict[str, Exception]: raise ObsoleteException("Obsolete missing_commands") def setup(self) -> None: @@ -10221,7 +10215,7 @@ def print_settings(self) -> None: self.print_setting(x) return - def set_setting(self, argv: List[str]) -> bool: + def set_setting(self, argv: list[str]) -> bool: global gef key, new_value = argv @@ -10257,7 +10251,7 @@ def set_setting(self, argv: List[str]) -> bool: reset_all_caches() return True - def complete(self, text: str, word: str) -> List[str]: + def complete(self, text: str, word: str) -> list[str]: settings = sorted(gef.config) if text == "": @@ -10373,7 +10367,7 @@ def __init__(self) -> None: def invoke(self, args: Any, from_tty: bool) -> None: self.dont_repeat() - missing_commands: Dict[str, Exception] = gef.gdb.missing + missing_commands: dict[str, Exception] = gef.gdb.missing if not missing_commands: ok("No missing command") return @@ -10486,7 +10480,7 @@ def __init__(self) -> None: super().__init__(prefix=True) return - def do_invoke(self, _: List[str]) -> None: + def do_invoke(self, _: list[str]) -> None: self.usage() return @@ -10503,7 +10497,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: if len(argv) < 2: self.usage() return @@ -10522,7 +10516,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, argv: List[str]) -> None: + def do_invoke(self, argv: list[str]) -> None: global gef if len(argv) != 1: self.usage() @@ -10548,7 +10542,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, _: List[str]) -> 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}") @@ -10717,7 +10711,7 @@ def __init__(self) -> None: def reset_caches(self) -> None: super().reset_caches() - self.__maps: Optional[List[Section]] = None + self.__maps: Optional[list[Section]] = None return def write(self, address: int, buffer: ByteString, length: Optional[int] = None) -> None: @@ -10772,7 +10766,7 @@ def read_ascii_string(self, address: int) -> Optional[str]: return None @property - def maps(self) -> List[Section]: + def maps(self) -> list[Section]: if not self.__maps: maps = self.__parse_maps() if not maps: @@ -10780,7 +10774,7 @@ def maps(self) -> List[Section]: self.__maps = maps return self.__maps - def __parse_maps(self) -> Optional[List[Section]]: + def __parse_maps(self) -> Optional[list[Section]]: """Return the mapped memory sections. If the current arch has its maps method defined, then defer to that to generated maps, otherwise, try to figure it out from procfs, then info sections, then monitor info @@ -11077,7 +11071,7 @@ def selected_arena(self, value: GlibcArena) -> None: return @property - def arenas(self) -> Union[List, Iterator[GlibcArena]]: + def arenas(self) -> list | Iterator[GlibcArena]: if not self.main_arena: return [] return iter(self.main_arena) @@ -11098,7 +11092,7 @@ def base_address(self) -> Optional[int]: return self.__heap_base @property - def chunks(self) -> Union[List, Iterator]: + def chunks(self) -> list | Iterator: if not self.base_address: return [] return iter(GlibcChunk(self.base_address, from_base=True)) @@ -11127,20 +11121,20 @@ def tidx2size(self, idx: int) -> int: def malloc_align_address(self, address: int) -> int: """Align addresses according to glibc's MALLOC_ALIGNMENT. See also Issue #689 on Github""" - def ceil(n: int) -> int: + def ceil(n: float) -> int: return int(-1 * n // 1 * -1) malloc_alignment = self.malloc_alignment - return malloc_alignment * ceil((address // malloc_alignment)) + return malloc_alignment * ceil((address / malloc_alignment)) class GefSetting: """Basic class for storing gef settings as objects""" - def __init__(self, value: Any, cls: Optional[type] = None, description: Optional[str] = None, hooks: Optional[Dict[str, List[Callable]]] = None) -> None: + def __init__(self, value: Any, cls: Optional[type] = None, description: Optional[str] = None, hooks: Optional[dict[str, list[Callable]]] = None) -> None: self.value = value self.type = cls or type(value) self.description = description or "" - self.hooks: Dict[str, List[Callable]] = collections.defaultdict(list) + self.hooks: dict[str, list[Callable]] = collections.defaultdict(list) if not hooks: hooks = {"on_read": [], "on_write": [], "on_changed": []} @@ -11153,7 +11147,7 @@ def __str__(self) -> str: f"read_hooks={len(self.hooks['on_read'])}, write_hooks={len(self.hooks['on_write'])}, "\ f"changed_hooks={len(self.hooks['on_changed'])})" - def add_hook(self, access: str, funcs: List[Callable]): + def add_hook(self, access: str, funcs: list[Callable]): if access not in ("on_read", "on_write", "on_changed"): raise ValueError("invalid access type") for func in funcs: @@ -11241,13 +11235,13 @@ def __init__(self) -> None: self.remote_initializing: bool = False self.qemu_mode: bool = False self.convenience_vars_index: int = 0 - self.heap_allocated_chunks: List[Tuple[int, int]] = [] - self.heap_freed_chunks: List[Tuple[int, int]] = [] - self.heap_uaf_watchpoints: List[UafWatchpoint] = [] - self.pie_breakpoints: Dict[int, PieVirtualBreakpoint] = {} + self.heap_allocated_chunks: list[Tuple[int, int]] = [] + self.heap_freed_chunks: list[Tuple[int, int]] = [] + self.heap_uaf_watchpoints: list[UafWatchpoint] = [] + self.pie_breakpoints: dict[int, PieVirtualBreakpoint] = {} self.pie_counter: int = 1 - self.aliases: List[GefAlias] = [] - self.modules: List[FileFormat] = [] + self.aliases: list[GefAlias] = [] + self.modules: list[FileFormat] = [] self.constants = {} # a dict for runtime constants (like 3rd party file paths) for constant in ("python3", "readelf", "nm", "file", "ps"): self.constants[constant] = which(constant) @@ -11272,7 +11266,7 @@ def __repr__(self) -> str: return str(self) @property - def auxiliary_vector(self) -> Optional[Dict[str, int]]: + def auxiliary_vector(self) -> Optional[dict[str, int]]: if not is_alive(): return None if is_qemu_system(): @@ -11621,9 +11615,9 @@ def __init__(self) -> None: self.redirect_fd : Optional[TextIOWrapper] = None self.context_hidden = False self.stream_buffer : Optional[StringIO] = None - self.highlight_table: Dict[str, str] = {} - self.watches: Dict[int, Tuple[int, str]] = {} - self.context_messages: List[Tuple[str, str]] = [] + self.highlight_table: dict[str, str] = {} + self.watches: dict[int, Tuple[int, str]] = {} + self.context_messages: list[Tuple[str, str]] = [] return diff --git a/tests/base.py b/tests/base.py index e284dd0da..fc209be0c 100644 --- a/tests/base.py +++ b/tests/base.py @@ -110,6 +110,8 @@ def tearDown(self) -> None: @property def gdb_version(self) -> Tuple[int, int]: - res = [int(d) for d in re.search(r"(\d+)\D(\d+)", self._gdb.VERSION).groups()] - assert len(res) >= 2 - return tuple(res) + res = re.search(r"(\d+)\D(\d+)", self._gdb.VERSION) + assert res + groups = [int(d) for d in res.groups()] + assert len(groups) == 2 + return (groups[0], groups[1]) diff --git a/tests/commands/gef_remote.py b/tests/commands/gef_remote.py index 578547f8b..34fd0ab60 100644 --- a/tests/commands/gef_remote.py +++ b/tests/commands/gef_remote.py @@ -71,5 +71,5 @@ def test_cmd_target_remote(self): with gdbserver_session(port=port) as _: gdb.execute(f"target remote {GDBSERVER_DEFAULT_HOST}:{port}") res: str = root.eval("str(gef.session.remote)") - assert res.startswith(f"RemoteSession(target=':0', local='/tmp/") + assert res.startswith("RemoteSession(target=':0', local='/tmp/") assert res.endswith(f"pid={gef.session.pid}, mode={gdbserver_mode})") diff --git a/tests/commands/theme.py b/tests/commands/theme.py index a09cfaa6f..44744475e 100644 --- a/tests/commands/theme.py +++ b/tests/commands/theme.py @@ -41,5 +41,5 @@ def test_cmd_theme(self): gdb.execute(f"theme {t} {v}") - res = gdb.execute(f"theme ___I_DONT_EXIST___", to_string=True) + res = gdb.execute("theme ___I_DONT_EXIST___", to_string=True) self.assertIn("Invalid key", res) From b83586e0c0c25877ffd4d2619ebf854951cdb0f6 Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 1 Nov 2024 12:11:46 -0700 Subject: [PATCH 03/38] removed `Set` and `Tuple` types (replaced with `tuple` and `set`) --- gef.py | 132 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/gef.py b/gef.py index 17d602c23..bb6054ace 100644 --- a/gef.py +++ b/gef.py @@ -115,10 +115,10 @@ def update_gef(argv: list[str]) -> int: sys.exit(1) -GDB_MIN_VERSION: Tuple[int, int] = (8, 0) -PYTHON_MIN_VERSION: Tuple[int, int] = (3, 6) -PYTHON_VERSION: Tuple[int, int] = sys.version_info[0:2] -GDB_VERSION: Tuple[int, int] = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups())) # type:ignore +GDB_MIN_VERSION: tuple[int, int] = (8, 0) +PYTHON_MIN_VERSION: tuple[int, int] = (3, 6) +PYTHON_VERSION: tuple[int, int] = sys.version_info[0:2] +GDB_VERSION: tuple[int, int] = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups())) # type:ignore DEFAULT_PAGE_ALIGN_SHIFT = 12 DEFAULT_PAGE_SIZE = 1 << DEFAULT_PAGE_ALIGN_SHIFT @@ -144,10 +144,10 @@ def update_gef(argv: list[str]) -> int: GEF_PROMPT_ON = f"\001\033[1;32m\002{GEF_PROMPT}\001\033[0m\002" GEF_PROMPT_OFF = f"\001\033[1;31m\002{GEF_PROMPT}\001\033[0m\002" -__registered_commands__ : Set[Type["GenericCommand"]] = set() -__registered_functions__ : Set[Type["GenericFunction"]] = set() +__registered_commands__ : set[Type["GenericCommand"]] = set() +__registered_functions__ : set[Type["GenericFunction"]] = set() __registered_architectures__ : dict[Union["Elf.Abi", str], Type["Architecture"]] = {} -__registered_file_formats__ : Set[ Type["FileFormat"] ] = set() +__registered_file_formats__ : set[ Type["FileFormat"] ] = set() GefMemoryMapProvider = Callable[[], Generator["Section", None, None]] @@ -446,8 +446,8 @@ def FakeExit(*args: Any, **kwargs: Any) -> NoReturn: sys.exit = FakeExit -def parse_arguments(required_arguments: dict[Union[str, Tuple[str, str]], Any], - optional_arguments: dict[Union[str, Tuple[str, str]], Any]) -> Callable: +def parse_arguments(required_arguments: dict[Union[str, tuple[str, str]], Any], + optional_arguments: dict[Union[str, tuple[str, str]], Any]) -> Callable: """Argument parsing decorator.""" def int_wrapper(x: str) -> int: return int(x, 0) @@ -953,7 +953,7 @@ def __init__(self, path: Union[str, pathlib.Path]) -> None: def read(self, size: int) -> bytes: return self.fd.read(size) - def read_and_unpack(self, fmt: str) -> Tuple[Any, ...]: + def read_and_unpack(self, fmt: str) -> tuple[Any, ...]: size = struct.calcsize(fmt) data = self.fd.read(size) return struct.unpack(fmt, data) @@ -1523,7 +1523,7 @@ def fastbin(self, i: int) -> Optional["GlibcFastChunk"]: return None return GlibcFastChunk(addr + 2 * gef.arch.ptrsize) - def bin(self, i: int) -> Tuple[int, int]: + def bin(self, i: int) -> tuple[int, int]: idx = i * 2 fd = int(self.bins[idx]) bk = int(self.bins[idx + 1]) @@ -1824,7 +1824,7 @@ class GlibcTcacheChunk(GlibcFastChunk): pass @deprecated("Use GefLibcManager.find_libc_version()") -def get_libc_version() -> Tuple[int, ...]: +def get_libc_version() -> tuple[int, ...]: return GefLibcManager.find_libc_version() def titlify(text: str, color: Optional[str] = None, msg_color: Optional[str] = None) -> str: @@ -2091,7 +2091,7 @@ def gef_makedirs(path: str, mode: int = 0o755) -> pathlib.Path: @lru_cache() -def gdb_lookup_symbol(sym: str) -> Optional[Tuple[gdb.Symtab_and_line, ...]]: +def gdb_lookup_symbol(sym: str) -> Optional[tuple[gdb.Symtab_and_line, ...]]: """Fetch the proper symbol or None if not defined.""" try: res = gdb.decode_line(sym)[1] # pylint: disable=E1136 @@ -2100,7 +2100,7 @@ def gdb_lookup_symbol(sym: str) -> Optional[Tuple[gdb.Symtab_and_line, ...]]: return None @lru_cache(maxsize=512) -def gdb_get_location_from_symbol(address: int) -> Optional[Tuple[str, int]]: +def gdb_get_location_from_symbol(address: int) -> Optional[tuple[str, int]]: """Retrieve the location of the `address` argument from the symbol table. Return a tuple with the name and offset if found, None otherwise.""" # this is horrible, ugly hack and shitty perf... @@ -2340,7 +2340,7 @@ def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: class ArchitectureBase: """Class decorator for declaring an architecture to GEF.""" - aliases: Union[Tuple[()], Tuple[Union[str, Elf.Abi], ...]] = () + aliases: Union[tuple[()], tuple[Union[str, Elf.Abi], ...]] = () def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs): global __registered_architectures__ @@ -2360,20 +2360,20 @@ class Architecture(ArchitectureBase): # Mandatory defined attributes by inheriting classes arch: str mode: str - all_registers: Union[Tuple[()], Tuple[str, ...]] + all_registers: Union[tuple[()], tuple[str, ...]] nop_insn: bytes return_register: str flag_register: Optional[str] instruction_length: Optional[int] flags_table: dict[int, str] syscall_register: Optional[str] - syscall_instructions: Union[Tuple[()], Tuple[str, ...]] - function_parameters: Union[Tuple[()], Tuple[str, ...]] + syscall_instructions: Union[tuple[()], tuple[str, ...]] + function_parameters: Union[tuple[()], tuple[str, ...]] # Optionally defined attributes _ptrsize: Optional[int] = None _endianness: Optional[Endianness] = None - special_registers: Union[Tuple[()], Tuple[str, ...]] = () + special_registers: Union[tuple[()], tuple[str, ...]] = () maps: Optional[GefMemoryMapProvider] = None def __init_subclass__(cls, **kwargs): @@ -2409,7 +2409,7 @@ def is_ret(self, insn: Instruction) -> bool: def is_conditional_branch(self, insn: Instruction) -> bool: raise NotImplementedError - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: raise NotImplementedError def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: @@ -2488,7 +2488,7 @@ def endianness(self) -> Endianness: raise OSError(f"No valid endianess found in '{output}'") return self._endianness - def get_ith_parameter(self, i: int, in_func: bool = True) -> Tuple[str, Optional[int]]: + def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, Optional[int]]: """Retrieves the correct parameter used for the current function call.""" reg = self.function_parameters[i] val = self.register(reg) @@ -2563,7 +2563,7 @@ def ptrsize(self) -> int: def is_conditional_branch(self, insn: Instruction) -> bool: return insn.mnemonic.startswith("b") - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: def long_to_twos_complement(v: int) -> int: """Convert a python long value to its two's complement.""" if is_32bit(): @@ -2716,7 +2716,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: conditions = {"eq", "ne", "lt", "le", "gt", "ge", "vs", "vc", "mi", "pl", "hi", "ls", "cc", "cs"} return insn.mnemonic[-2:] in conditions - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: mnemo = insn.mnemonic # ref: https://www.davespace.co.uk/arm/introduction-to-arm/conditional.html flags = dict((self.flags_table[k], k) for k in self.flags_table) @@ -2882,7 +2882,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: branch_mnemos = {"cbnz", "cbz", "tbnz", "tbz"} return mnemo.startswith("b.") or mnemo in branch_mnemos - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: mnemo, operands = insn.mnemonic, insn.operands taken, reason = False, "" @@ -2914,7 +2914,7 @@ def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: class X86(Architecture): - aliases: Tuple[Union[str, Elf.Abi], ...] = ("X86", Elf.Abi.X86_32) + aliases: tuple[Union[str, Elf.Abi], ...] = ("X86", Elf.Abi.X86_32) arch = "X86" mode = "32" @@ -2969,7 +2969,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: } return mnemo in branch_mnemos - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: mnemo = insn.mnemonic # all kudos to fG! (https://github.com/gdbinit/Gdbinit/blob/master/gdbinit#L1654) flags = dict((self.flags_table[k], k) for k in self.flags_table) @@ -3042,7 +3042,7 @@ def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ] return "; ".join(insns) - def get_ith_parameter(self, i: int, in_func: bool = True) -> Tuple[str, Optional[int]]: + def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, Optional[int]]: if in_func: i += 1 # Account for RA being at the top of the stack sp = gef.arch.sp @@ -3149,7 +3149,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: branch_mnemos = {"beq", "bne", "ble", "blt", "bgt", "bge"} return mnemo in branch_mnemos - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: mnemo = insn.mnemonic flags = dict((self.flags_table[k], k) for k in self.flags_table) val = gef.arch.register(self.flag_register) @@ -3257,7 +3257,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: } return mnemo in branch_mnemos - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: mnemo = insn.mnemonic flags = dict((self.flags_table[k], k) for k in self.flags_table) val = gef.arch.register(self.flag_register) @@ -3358,7 +3358,7 @@ def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: class MIPS(Architecture): - aliases: Tuple[Union[str, Elf.Abi], ...] = ("MIPS", Elf.Abi.MIPS) + aliases: tuple[Union[str, Elf.Abi], ...] = ("MIPS", Elf.Abi.MIPS) arch = "MIPS" mode = "MIPS32" @@ -3393,7 +3393,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: branch_mnemos = {"beq", "bne", "beqz", "bnez", "bgtz", "bgez", "bltz", "blez"} return mnemo in branch_mnemos - def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: mnemo, ops = insn.mnemonic, insn.operands taken, reason = False, "" @@ -3769,7 +3769,7 @@ def regchanged_handler(_: "gdb.RegisterChangedEvent") -> None: return -def get_terminal_size() -> Tuple[int, int]: +def get_terminal_size() -> tuple[int, int]: """Return the current terminal size.""" if is_debug(): return 600, 100 @@ -4089,7 +4089,7 @@ def gef_getpagesize() -> int: @deprecated("Use `gef.session.canary`") -def gef_read_canary() -> Optional[Tuple[int, int]]: +def gef_read_canary() -> Optional[tuple[int, int]]: return gef.session.canary @@ -4561,7 +4561,7 @@ def stop(self) -> bool: return False # software watchpoints stop after the next statement (see - # https://sourceware.org/gdb/onlinedocs/gdb/Set-Watchpoints.html) + # https://sourceware.org/gdb/onlinedocs/gdb/set-Watchpoints.html) pc = gdb_get_nth_previous_instruction_address(gef.arch.pc, 2) assert pc insn = gef_current_instruction(pc) @@ -4778,10 +4778,10 @@ def __contains__(self, name: str) -> bool: return self.__get_setting_name(name) in gef.config @deprecated("Use `self[setting_name] = value` instead") - def add_setting(self, name: str, value: Tuple[Any, type, str], description: str = "") -> None: + def add_setting(self, name: str, value: tuple[Any, type, str], description: str = "") -> None: return self.__setitem__(name, (value, description)) - def __setitem__(self, name: str, value: Union["GefSetting", Tuple[Any, str]]) -> None: + def __setitem__(self, name: str, value: Union["GefSetting", tuple[Any, str]]) -> None: # make sure settings are always associated to the root command (which derives from GenericCommand) if "GenericCommand" not in [x.__name__ for x in self.__class__.__bases__]: return @@ -4855,7 +4855,7 @@ def do_invoke(self, args: list[str]) -> None: @register class ArchSetCommand(GenericCommand): - """Set the current loaded architecture.""" + """set the current loaded architecture.""" _cmdline_ = "arch set" _syntax_ = f"{_cmdline_} " @@ -4943,7 +4943,7 @@ def __init__(self) -> None: return @property - def format_matrix(self) -> dict[int, Tuple[str, str, str]]: + def format_matrix(self) -> dict[int, tuple[str, str, str]]: # `gef.arch.endianness` is a runtime property, should not be defined as a class property return { 8: (f"{gef.arch.endianness}B", "char", "db"), @@ -5030,7 +5030,7 @@ def do_invoke(self, argv: list[str]) -> None: @register class PieBreakpointCommand(GenericCommand): - """Set a PIE breakpoint at an offset from the target binaries base address.""" + """set a PIE breakpoint at an offset from the target binaries base address.""" _cmdline_ = "pie breakpoint" _syntax_ = f"{_cmdline_} OFFSET" @@ -5410,7 +5410,7 @@ def list_sockets(self, pid: int) -> list[int]: sockets.append(int(p)) return sockets - def parse_ip_port(self, addr: str) -> Tuple[str, int]: + def parse_ip_port(self, addr: str) -> tuple[str, int]: ip, port = addr.split(":") return socket.inet_ntoa(struct.pack(" str: for name, values in struct._values_: if name != item: continue if callable(values): - return values(value) + return str(values(value)) try: for val, desc in values: if value == val: return desc @@ -5709,14 +5709,14 @@ def path(self) -> pathlib.Path: return self._path @property - def structures(self) -> Generator[Tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"], None, None]: + def structures(self) -> Generator[tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"], None, None]: for module in self.modules.values(): for structure in module.values(): yield module, structure return @lru_cache() - def find(self, structure_name: str) -> Optional[Tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"]]: + def find(self, structure_name: str) -> Optional[tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"]]: """Return the module and structure for the given structure name; `None` if the structure name was not found.""" for module in self.modules.values(): if structure_name in module: @@ -5776,7 +5776,7 @@ def do_invoke(self, *_: Any, **kwargs: dict[str, Any]) -> None: structure.apply_at(address, self["max_depth"]) return - def explode_type(self, arg: str) -> Tuple[str, str]: + def explode_type(self, arg: str) -> tuple[str, str]: modname, structname = arg.split(":", 1) if ":" in arg else (arg, arg) structname = structname.split(".", 1)[0] if "." in structname else structname return modname, structname @@ -6061,11 +6061,11 @@ def print_section(self, section: Section) -> None: ok(title) return - def print_loc(self, loc: Tuple[int, int, str]) -> None: + def print_loc(self, loc: tuple[int, int, str]) -> None: gef_print(f""" {loc[0]:#x} - {loc[1]:#x} {RIGHT_ARROW} "{Color.pinkify(loc[2])}" """) return - def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> list[Tuple[int, int, str]]: + def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> list[tuple[int, int, str]]: """Search a pattern within a range defined by arguments.""" _pattern = gef_pybytes(pattern) step = self["nr_pages_chunk"] * gef.session.pagesize @@ -6097,7 +6097,7 @@ def search_pattern_by_address(self, pattern: str, start_address: int, end_addres return locations - def search_binpattern_by_address(self, binpattern: bytes, start_address: int, end_address: int) -> list[Tuple[int, int, str]]: + def search_binpattern_by_address(self, binpattern: bytes, start_address: int, end_address: int) -> list[tuple[int, int, str]]: """Search a binary pattern within a range defined by arguments.""" step = self["nr_pages_chunk"] * gef.session.pagesize @@ -6294,7 +6294,7 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: raise e # Try to establish the remote session, throw on error - # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which + # set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None # This prevents some spurious errors being thrown during startup gef.session.remote_initializing = True @@ -6504,7 +6504,7 @@ def do_invoke(self, _: list[str]) -> None: @register class GlibcHeapSetArenaCommand(GenericCommand): - """Set the address of the main_arena or the currently selected arena.""" + """set the address of the main_arena or the currently selected arena.""" _cmdline_ = "heap set-arena" _syntax_ = f"{_cmdline_} [address|&symbol]" @@ -6939,7 +6939,7 @@ def check_thread_ids(self, tids: list[int]) -> list[int]: existing_tids = set(t.num for t in gdb.selected_inferior().threads()) return list(set(tids) & existing_tids) - def tcachebin(self, tcache_base: int, i: int) -> Tuple[Optional[GlibcTcacheChunk], int]: + def tcachebin(self, tcache_base: int, i: int) -> tuple[Optional[GlibcTcacheChunk], int]: """Return the head chunk in tcache[i] and the number of chunks in the bin.""" if i >= self.TCACHE_MAX_BINS: err("Incorrect index value, index value must be between 0 and " @@ -7624,7 +7624,7 @@ def __init__(self) -> None: self["libc_args"] = (False, "[DEPRECATED - Unused] Show libc function call args description") self["libc_args_path"] = ("", "[DEPRECATED - Unused] Path to libc function call args json files, provided via gef-extras") - self.layout_mapping: dict[str, Tuple[Callable, Optional[Callable], Optional[Callable]]] = { + self.layout_mapping: dict[str, tuple[Callable, Optional[Callable], Optional[Callable]]] = { "legend": (self.show_legend, None, None), "regs": (self.context_regs, None, None), "stack": (self.context_stack, None, None), @@ -8668,7 +8668,7 @@ def do_invoke(self, argv: list[str]) -> None: try: msg_as_bytes = codecs.escape_decode(msg, "utf-8")[0] - gef.memory.write(addr, msg_as_bytes, len(msg_as_bytes)) + gef.memory.write(addr, msg_as_bytes, len(msg_as_bytes)) # type: ignore except (binascii.Error, gdb.error): err(f"Could not decode '\\xXX' encoded string \"{msg}\"") return @@ -9912,7 +9912,7 @@ def __init__(self) -> None: gef.config["gef.buffer"] = GefSetting(True, bool, "Internally buffer command output until completion") 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.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.") gef.config["gef.extra_python_package_paths"] = GefSetting("", str, "Semi-colon separate list of extra paths to include for python packages") @@ -9923,7 +9923,7 @@ def __init__(self) -> None: @property @deprecated() - def loaded_commands(self) -> list[Tuple[str, Type[GenericCommand], Any]]: + def loaded_commands(self) -> list[tuple[str, Type[GenericCommand], Any]]: raise ObsoleteException("Obsolete loaded_commands") @property @@ -10115,7 +10115,7 @@ def __rebuild(self) -> None: self.should_refresh = False return - def __add__(self, command: Tuple[str, GenericCommand]): + def __add__(self, command: tuple[str, GenericCommand]): """Add command to GEF documentation.""" cmd, class_obj = command if " " in cmd: @@ -10127,7 +10127,7 @@ def __add__(self, command: Tuple[str, GenericCommand]): self.docs.append(msg) return self - def __radd__(self, command: Tuple[str, GenericCommand]): + def __radd__(self, command: tuple[str, GenericCommand]): return self.__add__(command) def __str__(self) -> str: @@ -10460,7 +10460,7 @@ def invoke(self, args: Any, from_tty: bool) -> None: gdb.execute(f"{self.command} {args}", from_tty=from_tty) return - def lookup_command(self, cmd: str) -> Optional[Tuple[str, GenericCommand]]: + def lookup_command(self, cmd: str) -> Optional[tuple[str, GenericCommand]]: global gef for _name, _instance in gef.gdb.commands.items(): if cmd == _name: @@ -11235,8 +11235,8 @@ def __init__(self) -> None: self.remote_initializing: bool = False self.qemu_mode: bool = False self.convenience_vars_index: int = 0 - self.heap_allocated_chunks: list[Tuple[int, int]] = [] - self.heap_freed_chunks: list[Tuple[int, int]] = [] + self.heap_allocated_chunks: list[tuple[int, int]] = [] + self.heap_freed_chunks: list[tuple[int, int]] = [] self.heap_uaf_watchpoints: list[UafWatchpoint] = [] self.pie_breakpoints: dict[int, PieVirtualBreakpoint] = {} self.pie_counter: int = 1 @@ -11333,7 +11333,7 @@ def pagesize(self) -> int: return self._pagesize @property - def canary(self) -> Optional[Tuple[int, int]]: + def canary(self) -> Optional[tuple[int, int]]: """Return a tuple of the canary address and value, read from the canonical location if supported by the architecture. Otherwise, read from the auxiliary vector.""" @@ -11347,7 +11347,7 @@ def canary(self) -> Optional[Tuple[int, int]]: return canary, canary_location @property - def original_canary(self) -> Optional[Tuple[int, int]]: + def original_canary(self) -> Optional[tuple[int, int]]: """Return a tuple of the initial canary address and value, read from the auxiliary vector.""" auxval = self.auxiliary_vector @@ -11616,8 +11616,8 @@ def __init__(self) -> None: self.context_hidden = False self.stream_buffer : Optional[StringIO] = None self.highlight_table: dict[str, str] = {} - self.watches: dict[int, Tuple[int, str]] = {} - self.context_messages: list[Tuple[str, str]] = [] + self.watches: dict[int, tuple[int, str]] = {} + self.context_messages: list[tuple[str, str]] = [] return @@ -11627,7 +11627,7 @@ class GefLibcManager(GefManager): PATTERN_LIBC_VERSION_FILENAME = re.compile(r"libc6?[-_](\d+)\.(\d+)\.so") def __init__(self) -> None: - self._version : Optional[Tuple[int, int]] = None + self._version : Optional[tuple[int, int]] = None self._patch: Optional[int] = None self._release: Optional[str] = None return @@ -11636,7 +11636,7 @@ def __str__(self) -> str: return f"Libc(version='{self.version}')" @property - def version(self) -> Optional[Tuple[int, int]]: + def version(self) -> Optional[tuple[int, int]]: if not is_alive(): return None @@ -11654,7 +11654,7 @@ def version(self) -> Optional[Tuple[int, int]]: @staticmethod @lru_cache() - def find_libc_version() -> Tuple[int, int]: + def find_libc_version() -> tuple[int, int]: """Attempt to determine the libc version. This operation can be long.""" libc_sections = (m for m in gef.memory.maps if "libc" in m.path and m.permission & Permission.READ) for section in libc_sections: From 32311d7a910bd26f96347791617bc12e296d6c3c Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 1 Nov 2024 13:24:16 -0700 Subject: [PATCH 04/38] `Union` type -> `|` --- gef.py | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/gef.py b/gef.py index bb6054ace..97b6d02c2 100644 --- a/gef.py +++ b/gef.py @@ -83,8 +83,8 @@ from io import StringIO, TextIOWrapper from types import ModuleType from typing import (Any, ByteString, Callable, Generator, Iterable, - Iterator, NoReturn, Optional, Sequence, Set, Tuple, Type, TypeVar, - Union, cast) + Iterator, NoReturn, Optional, Sequence, Type, TypeVar, + cast) from urllib.request import urlopen @@ -146,7 +146,7 @@ def update_gef(argv: list[str]) -> int: __registered_commands__ : set[Type["GenericCommand"]] = set() __registered_functions__ : set[Type["GenericFunction"]] = set() -__registered_architectures__ : dict[Union["Elf.Abi", str], Type["Architecture"]] = {} +__registered_architectures__ : dict["Elf.Abi" | str, Type["Architecture"]] = {} __registered_file_formats__ : set[ Type["FileFormat"] ] = set() GefMemoryMapProvider = Callable[[], Generator["Section", None, None]] @@ -446,8 +446,8 @@ def FakeExit(*args: Any, **kwargs: Any) -> NoReturn: sys.exit = FakeExit -def parse_arguments(required_arguments: dict[Union[str, tuple[str, str]], Any], - optional_arguments: dict[Union[str, tuple[str, str]], Any]) -> Callable: +def parse_arguments(required_arguments: dict[str | tuple[str, str], Any], + optional_arguments: dict[str | tuple[str, str], Any]) -> Callable: """Argument parsing decorator.""" def int_wrapper(x: str) -> int: return int(x, 0) @@ -799,7 +799,7 @@ class FileFormat: checksec: dict[str, bool] sections: list[FileFormatSection] - def __init__(self, path: Union[str, pathlib.Path]) -> None: + def __init__(self, path: str | pathlib.Path) -> None: raise NotImplementedError def __init_subclass__(cls: Type["FileFormat"], **kwargs): @@ -892,7 +892,7 @@ class OsAbi(enum.Enum): __checksec : dict[str, bool] - def __init__(self, path: Union[str, pathlib.Path]) -> None: + def __init__(self, path: str | pathlib.Path) -> None: """Instantiate an ELF object. A valid ELF must be provided, or an exception will be thrown.""" if isinstance(path, str): @@ -1314,7 +1314,7 @@ class heap_info_cls(ctypes.Structure): heap_info_cls._fields_ = fields return heap_info_cls - def __init__(self, addr: Union[str, int]) -> None: + def __init__(self, addr: str | int) -> None: self.__address : int = parse_address(f"&{addr}") if isinstance(addr, str) else addr self.reset() return @@ -2236,7 +2236,7 @@ def gef_disassemble(addr: int, nb_insn: int, nb_prev: int = 0) -> Generator[Inst yield insn -def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> Union[str, list[str]]: +def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> str | list[str]: """Execute an external command and return the result.""" res = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=kwargs.get("shell", False)) return [gef_pystring(_) for _ in res.splitlines()] if as_list else gef_pystring(res) @@ -2340,7 +2340,7 @@ def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: class ArchitectureBase: """Class decorator for declaring an architecture to GEF.""" - aliases: Union[tuple[()], tuple[Union[str, Elf.Abi], ...]] = () + aliases: tuple[()] | tuple[str | Elf.Abi | ...] = () def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs): global __registered_architectures__ @@ -2360,20 +2360,20 @@ class Architecture(ArchitectureBase): # Mandatory defined attributes by inheriting classes arch: str mode: str - all_registers: Union[tuple[()], tuple[str, ...]] + all_registers: tuple[()] | tuple[str, ...] nop_insn: bytes return_register: str flag_register: Optional[str] instruction_length: Optional[int] flags_table: dict[int, str] syscall_register: Optional[str] - syscall_instructions: Union[tuple[()], tuple[str, ...]] - function_parameters: Union[tuple[()], tuple[str, ...]] + syscall_instructions: tuple[()] | tuple[str, ...] + function_parameters: tuple[()] | tuple[str, ...] # Optionally defined attributes _ptrsize: Optional[int] = None _endianness: Optional[Endianness] = None - special_registers: Union[tuple[()], tuple[str, ...]] = () + special_registers: tuple[()] | tuple[str, ...] = () maps: Optional[GefMemoryMapProvider] = None def __init_subclass__(cls, **kwargs): @@ -2914,7 +2914,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: class X86(Architecture): - aliases: tuple[Union[str, Elf.Abi], ...] = ("X86", Elf.Abi.X86_32) + aliases: tuple[str | Elf.Abi, ...] = ("X86", Elf.Abi.X86_32) arch = "X86" mode = "32" @@ -3358,7 +3358,7 @@ def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: class MIPS(Architecture): - aliases: tuple[Union[str, Elf.Abi], ...] = ("MIPS", Elf.Abi.MIPS) + aliases: tuple[str | Elf.Abi, ...] = ("MIPS", Elf.Abi.MIPS) arch = "MIPS" mode = "MIPS32" @@ -4026,7 +4026,7 @@ def dereference(addr: int) -> Optional["gdb.Value"]: return None -def gef_convenience(value: Union[str, bytes]) -> str: +def gef_convenience(value: str | bytes) -> str: """Defines a new convenience value.""" global gef var_name = f"$_gef{gef.session.convenience_vars_index:d}" @@ -4048,7 +4048,7 @@ def parse_string_range(s: str) -> Iterator[int]: @lru_cache() -def is_syscall(instruction: Union[Instruction,int]) -> bool: +def is_syscall(instruction: Instruction | int) -> bool: """Checks whether an instruction or address points to a system call.""" if isinstance(instruction, int): instruction = gef_current_instruction(instruction) @@ -4214,7 +4214,7 @@ def __init__(self, set_func: Callable[[int], str], vbp_num: int, addr: int) -> N self.bp_addr = 0 # this address might be a symbol, just to know where to break if isinstance(addr, int): - self.addr: Union[int, str] = hex(addr) + self.addr: int | str = hex(addr) else: self.addr = addr return @@ -4664,7 +4664,7 @@ def register_priority_command(cls: Type["GenericCommand"]) -> Type["GenericComma ValidCommandType = TypeVar("ValidCommandType", bound="GenericCommand") ValidFunctionType = TypeVar("ValidFunctionType", bound="GenericFunction") -def register(cls: Union[Type["ValidCommandType"], Type["ValidFunctionType"]]) -> Union[Type["ValidCommandType"], Type["ValidFunctionType"]]: +def register(cls: Type["ValidCommandType"] | Type["ValidFunctionType"]) -> Type["ValidCommandType"] | Type["ValidFunctionType"]: global __registered_commands__, __registered_functions__ if issubclass(cls, GenericCommand): assert hasattr(cls, "_cmdline_") @@ -4690,7 +4690,7 @@ class GenericCommand(gdb.Command): _cmdline_: str _syntax_: str - _example_: Union[str, list[str]] = "" + _example_: str | list[str] = "" _aliases_: list[str] = [] def __init_subclass__(cls, **kwargs): @@ -4781,7 +4781,7 @@ def __contains__(self, name: str) -> bool: def add_setting(self, name: str, value: tuple[Any, type, str], description: str = "") -> None: return self.__setitem__(name, (value, description)) - def __setitem__(self, name: str, value: Union["GefSetting", tuple[Any, str]]) -> None: + def __setitem__(self, name: str, value: "GefSetting" | tuple[Any, str]) -> None: # make sure settings are always associated to the root command (which derives from GenericCommand) if "GenericCommand" not in [x.__name__ for x in self.__class__.__bases__]: return @@ -6283,6 +6283,7 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: return # qemu-user support + qemu_binary = None if args.qemu_user: dbg("Setting up qemu-user session") try: From 741ce7cb846d3c8de74dd211f8cf699817d0fd08 Mon Sep 17 00:00:00 2001 From: hugsy Date: Fri, 1 Nov 2024 13:39:20 -0700 Subject: [PATCH 05/38] removed Tuple in tests --- tests/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/base.py b/tests/base.py index fc209be0c..868923d3a 100644 --- a/tests/base.py +++ b/tests/base.py @@ -5,7 +5,7 @@ import subprocess import tempfile import time -from typing import Tuple + import unittest import rpyc @@ -109,9 +109,9 @@ def tearDown(self) -> None: return super().tearDown() @property - def gdb_version(self) -> Tuple[int, int]: + def gdb_version(self) -> tuple[int, int]: res = re.search(r"(\d+)\D(\d+)", self._gdb.VERSION) assert res groups = [int(d) for d in res.groups()] assert len(groups) == 2 - return (groups[0], groups[1]) + return groups[0], groups[1] From f47f51e1c5126e03d7482e6bfdceeeac8e04e36f Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 2 Nov 2024 12:39:13 -0700 Subject: [PATCH 06/38] fixed type typo --- gef.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gef.py b/gef.py index 0dfcbc0ac..854076a15 100644 --- a/gef.py +++ b/gef.py @@ -146,8 +146,8 @@ def update_gef(argv: list[str]) -> int: __registered_commands__ : set[Type["GenericCommand"]] = set() __registered_functions__ : set[Type["GenericFunction"]] = set() -__registered_architectures__ : dict["Elf.Abi" | str, Type["Architecture"]] = {} -__registered_file_formats__ : set[ Type["FileFormat"] ] = set() +__registered_architectures__ : dict["Elf.Abi | str", Type["Architecture"]] = {} +__registered_file_formats__ : set[ Type["FileFormat"] ] = set() GefMemoryMapProvider = Callable[[], Generator["Section", None, None]] @@ -2337,7 +2337,7 @@ def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: class ArchitectureBase: """Class decorator for declaring an architecture to GEF.""" - aliases: tuple[()] | tuple[str | Elf.Abi | ...] = () + aliases: tuple[str | Elf.Abi, ...] def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs): global __registered_architectures__ @@ -8895,15 +8895,15 @@ class VMMapCommand(GenericCommand): @only_if_gdb_running @parse_arguments({"unknown_types": [""]}, {("--addr", "-a"): [""], ("--name", "-n"): [""]}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] vmmap = gef.memory.maps if not vmmap: err("No address mapping information found") return - addrs: Dict[str, int] = {x: parse_address(x) for x in args.addr} - names: List[str] = [x for x in args.name] + addrs: dict[str, int] = {x: parse_address(x) for x in args.addr} + names: list[str] = [x for x in args.name] for arg in args.unknown_types: if not arg: From d5d544db2fdc01fb38bd5fd4c07fd73afaebfd75 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 2 Nov 2024 13:28:54 -0700 Subject: [PATCH 07/38] `Optional` is optional --- gef.py | 319 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 154 insertions(+), 165 deletions(-) diff --git a/gef.py b/gef.py index 854076a15..af1151296 100644 --- a/gef.py +++ b/gef.py @@ -82,16 +82,15 @@ from functools import lru_cache from io import StringIO, TextIOWrapper from types import ModuleType -from typing import (Any, ByteString, Callable, Generator, Iterable, - Iterator, NoReturn, Optional, Sequence, Type, TypeVar, - cast) +from typing import (Any, ByteString, Callable, Generator, Iterable, Iterator, + NoReturn, Sequence, Type, TypeVar, cast) from urllib.request import urlopen GEF_DEFAULT_BRANCH = "main" GEF_EXTRAS_DEFAULT_BRANCH = "main" -def http_get(url: str) -> Optional[bytes]: +def http_get(url: str) -> bytes | None: """Basic HTTP wrapper for GET request. Return the body of the page if HTTP code is OK, otherwise return None.""" try: @@ -284,49 +283,49 @@ class ObsoleteException(Exception): pass class AlreadyRegisteredException(Exception): pass -def p8(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: +def p8(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: """Pack one byte respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.pack(f"{endian}B", x) if not s else struct.pack(f"{endian:s}b", x) -def p16(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: +def p16(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: """Pack one word respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.pack(f"{endian}H", x) if not s else struct.pack(f"{endian:s}h", x) -def p32(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: +def p32(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: """Pack one dword respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.pack(f"{endian}I", x) if not s else struct.pack(f"{endian:s}i", x) -def p64(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes: +def p64(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes: """Pack one qword respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.pack(f"{endian}Q", x) if not s else struct.pack(f"{endian:s}q", x) -def u8(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int: +def u8(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: """Unpack one byte respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.unpack(f"{endian}B", x)[0] if not s else struct.unpack(f"{endian:s}b", x)[0] -def u16(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int: +def u16(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: """Unpack one word respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.unpack(f"{endian}H", x)[0] if not s else struct.unpack(f"{endian:s}h", x)[0] -def u32(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int: +def u32(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: """Unpack one dword respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.unpack(f"{endian}I", x)[0] if not s else struct.unpack(f"{endian:s}i", x)[0] -def u64(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int: +def u64(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int: """Unpack one qword respecting the current architecture endianness.""" endian = e or gef.arch.endianness return struct.unpack(f"{endian}Q", x)[0] if not s else struct.unpack(f"{endian:s}q", x)[0] @@ -348,7 +347,7 @@ def is_alive() -> bool: return False -def calling_function() -> Optional[str]: +def calling_function() -> str | None: """Return the name of the calling function""" try: stack_info = traceback.extract_stack()[-3] @@ -452,7 +451,7 @@ def parse_arguments(required_arguments: dict[str | tuple[str, str], Any], def int_wrapper(x: str) -> int: return int(x, 0) - def decorator(f: Callable) -> Optional[Callable]: + def decorator(f: Callable) -> Callable | None: def wrapper(*args: Any, **kwargs: Any) -> Callable: parser = argparse.ArgumentParser(prog=args[0]._cmdline_, add_help=True) for argname in required_arguments: @@ -606,7 +605,7 @@ def is_in_stack_segment(self) -> bool: def is_in_heap_segment(self) -> bool: return hasattr(self.section, "path") and "[heap]" == self.section.path - def dereference(self) -> Optional[int]: + def dereference(self) -> int | None: addr = align_address(int(self.value)) derefed = dereference(addr) return None if derefed is None else int(derefed) @@ -693,7 +692,7 @@ def size(self) -> int: raise AttributeError return self.page_end - self.page_start - def _search_for_realpath_without_versions(self, path: pathlib.Path) -> Optional[str]: + def _search_for_realpath_without_versions(self, path: pathlib.Path) -> str | None: """Given a path, search for a file that exists without numeric suffixes.""" # Match the path string against a regex that will remove a suffix @@ -708,7 +707,7 @@ def _search_for_realpath_without_versions(self, path: pathlib.Path) -> Optional[ candidate = re.match(r"^(.*)\.(\d*)$", candidate) return None - def _search_for_realpath(self) -> Optional[str]: + def _search_for_realpath(self) -> str | None: """This function is a workaround for gdb bug #23764 path might be wrong for remote sessions, so try a simple search for files @@ -1199,7 +1198,7 @@ def _missing_(cls, _:int): sh_entsize: int name: str - def __init__(self, elf: Optional[Elf], off: int) -> None: + def __init__(self, elf: Elf | None, off: int) -> None: if elf is None: return elf.seek(off) @@ -1516,7 +1515,7 @@ def system_mem(self) -> int: def max_system_mem(self) -> int: return self.__arena.max_system_mem - def fastbin(self, i: int) -> Optional["GlibcFastChunk"]: + def fastbin(self, i: int) -> "GlibcFastChunk | None": """Return head chunk in fastbinsY[i].""" addr = int(self.fastbinsY[i]) if addr == 0: @@ -1537,7 +1536,7 @@ def bin_at(self, i) -> int: def is_main_arena(self) -> bool: return gef.heap.main_arena is not None and int(self) == int(gef.heap.main_arena) - def heap_addr(self, allow_unaligned: bool = False) -> Optional[int]: + def heap_addr(self, allow_unaligned: bool = False) -> int | None: if self.is_main_arena(): heap_section = gef.heap.base_address if not heap_section: @@ -1548,7 +1547,7 @@ def heap_addr(self, allow_unaligned: bool = False) -> Optional[int]: return _addr return gef.heap.malloc_align_address(_addr) - def get_heap_info_list(self) -> Optional[list[GlibcHeapInfo]]: + def get_heap_info_list(self) -> list[GlibcHeapInfo] | None: if self.is_main_arena(): return None heap_addr = self.get_heap_for_ptr(self.top) @@ -1827,7 +1826,7 @@ class GlibcTcacheChunk(GlibcFastChunk): def get_libc_version() -> tuple[int, ...]: return GefLibcManager.find_libc_version() -def titlify(text: str, color: Optional[str] = None, msg_color: Optional[str] = None) -> str: +def titlify(text: str, color: str | None = None, msg_color: str | None = None) -> str: """Print a centered title.""" _, cols = get_terminal_size() nb = (cols - len(text) - 2) // 2 @@ -2088,7 +2087,7 @@ def gef_makedirs(path: str, mode: int = 0o755) -> pathlib.Path: @lru_cache() -def gdb_lookup_symbol(sym: str) -> Optional[tuple[gdb.Symtab_and_line, ...]]: +def gdb_lookup_symbol(sym: str) -> tuple[gdb.Symtab_and_line, ...] | None: """Fetch the proper symbol or None if not defined.""" try: res = gdb.decode_line(sym)[1] # pylint: disable=E1136 @@ -2097,7 +2096,7 @@ def gdb_lookup_symbol(sym: str) -> Optional[tuple[gdb.Symtab_and_line, ...]]: return None @lru_cache(maxsize=512) -def gdb_get_location_from_symbol(address: int) -> Optional[tuple[str, int]]: +def gdb_get_location_from_symbol(address: int) -> tuple[str, int] | None: """Retrieve the location of the `address` argument from the symbol table. Return a tuple with the name and offset if found, None otherwise.""" # this is horrible, ugly hack and shitty perf... @@ -2148,7 +2147,7 @@ def gdb_disassemble(start_pc: int, **kwargs: int) -> Generator[Instruction, None yield Instruction(address, location, mnemo, operands, opcodes) -def gdb_get_nth_previous_instruction_address(addr: int, n: int) -> Optional[int]: +def gdb_get_nth_previous_instruction_address(addr: int, n: int) -> int | None: """Return the address (Integer) of the `n`-th instruction before `addr`.""" # fixed-length ABI if gef.arch.instruction_length: @@ -2288,7 +2287,7 @@ def get_arch() -> str: @deprecated("Use `gef.binary.entry_point` instead") -def get_entry_point() -> Optional[int]: +def get_entry_point() -> int | None: """Return the binary entry point.""" return gef.binary.entry_point if gef.binary else None @@ -2316,13 +2315,13 @@ def flags_to_human(reg_value: int, value_table: dict[int, str]) -> str: @lru_cache() -def get_section_base_address(name: str) -> Optional[int]: +def get_section_base_address(name: str) -> int | None: section = process_lookup_path(name) return section.page_start if section else None @lru_cache() -def get_zone_base_address(name: str) -> Optional[int]: +def get_zone_base_address(name: str) -> int | None: zone = file_lookup_name_path(name, get_filepath()) return zone.zone_start if zone else None @@ -2337,7 +2336,7 @@ def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: class ArchitectureBase: """Class decorator for declaring an architecture to GEF.""" - aliases: tuple[str | Elf.Abi, ...] + aliases: tuple[str | Elf.Abi, ...] = () def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs): global __registered_architectures__ @@ -2357,21 +2356,21 @@ class Architecture(ArchitectureBase): # Mandatory defined attributes by inheriting classes arch: str mode: str - all_registers: tuple[()] | tuple[str, ...] + all_registers: tuple[str, ...] nop_insn: bytes return_register: str - flag_register: Optional[str] - instruction_length: Optional[int] + flag_register: str | None + instruction_length: int | None flags_table: dict[int, str] - syscall_register: Optional[str] - syscall_instructions: tuple[()] | tuple[str, ...] - function_parameters: tuple[()] | tuple[str, ...] + syscall_register: str | None + syscall_instructions: tuple[str, ...] + function_parameters: tuple[str, ...] # Optionally defined attributes - _ptrsize: Optional[int] = None - _endianness: Optional[Endianness] = None + _ptrsize: int | None = None + _endianness: Endianness | None = None special_registers: tuple[()] | tuple[str, ...] = () - maps: Optional[GefMemoryMapProvider] = None + maps: GefMemoryMapProvider | None = None def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) @@ -2388,13 +2387,13 @@ def __repr__(self) -> str: return self.__str__() @staticmethod - def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: + def supports_gdb_arch(gdb_arch: str) -> bool | None: """If implemented by a child `Architecture`, this function dictates if the current class supports the loaded ELF file (which can be accessed via `gef.binary`). This callback function will override any assumption made by GEF to determine the architecture.""" return None - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: raise NotImplementedError def is_call(self, insn: Instruction) -> bool: @@ -2409,7 +2408,7 @@ def is_conditional_branch(self, insn: Instruction) -> bool: def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: raise NotImplementedError - def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: raise NotImplementedError def canary_address(self) -> int: @@ -2485,7 +2484,7 @@ def endianness(self) -> Endianness: raise OSError(f"No valid endianess found in '{output}'") return self._endianness - def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, Optional[int]]: + def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, int | None]: """Retrieves the correct parameter used for the current function call.""" reg = self.function_parameters[i] val = self.register(reg) @@ -2616,7 +2615,7 @@ def long_to_twos_complement(v: int) -> int: return taken, reason - def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: ra = None if self.is_ret(insn): ra = gef.arch.register("$ra") @@ -2626,7 +2625,7 @@ def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: ra = to_unsigned_long(older.pc()) return ra - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: # RISC-V has no flags registers, return an empty string to # preserve the Architecture API return "" @@ -2660,7 +2659,7 @@ def is_thumb(self) -> bool: return is_alive() and (self.cpsr & (1 << 5) == 1) @property - def pc(self) -> Optional[int]: + def pc(self) -> int | None: pc = gef.arch.register("$pc") if self.is_thumb(): pc += 1 @@ -2677,7 +2676,7 @@ def mode(self) -> str: return "THUMB" if self.is_thumb() else "ARM" @property - def instruction_length(self) -> Optional[int]: + def instruction_length(self) -> int | None: # Thumb instructions have variable-length (2 or 4-byte) return None if self.is_thumb() else 4 @@ -2702,7 +2701,7 @@ def is_ret(self, insn: Instruction) -> bool: return insn.operands[0] == "pc" return False - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: # https://www.botskool.com/user-pages/tutorials/electronics/arm-7-tutorial-part-1 if val is None: reg = self.flag_register @@ -2746,7 +2745,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: elif mnemo.endswith("cc"): taken, reason = not val&(1< Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: if not self.is_ret(insn): older = frame.older() if not older: @@ -2822,7 +2821,7 @@ def is_call(self, insn: Instruction) -> bool: call_mnemos = {"bl", "blr"} return mnemo in call_mnemos - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: # https://events.linuxfoundation.org/sites/events/files/slides/KoreaLinuxForum-2014.pdf reg = self.flag_register if not val: @@ -2942,7 +2941,7 @@ class X86(Architecture): _ptrsize = 4 _endianness = Endianness.LITTLE_ENDIAN - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: reg = self.flag_register if val is None: val = gef.arch.register(reg) @@ -3011,7 +3010,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: taken, reason = not val&(1< Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: ra = None if self.is_ret(insn): ra = dereference(gef.arch.sp) @@ -3039,7 +3038,7 @@ def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: ] return "; ".join(insns) - def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, Optional[int]]: + def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, int | None]: if in_func: i += 1 # Account for RA being at the top of the stack sp = gef.arch.sp @@ -3128,7 +3127,7 @@ class PowerPC(Architecture): _ptrsize = 4 - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: # https://www.cebix.net/downloads/bebox/pem32b.pdf (% 2.1.3) if val is None: reg = self.flag_register @@ -3159,7 +3158,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: elif mnemo == "bgt": taken, reason = bool(val&(1< Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: ra = None if self.is_ret(insn): ra = gef.arch.register("$lr") @@ -3232,7 +3231,7 @@ class SPARC(Architecture): syscall_register = "%g1" syscall_instructions = ("t 0x10",) - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: # https://www.gaisler.com/doc/sparcv8.pdf reg = self.flag_register if val is None: @@ -3278,7 +3277,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: elif mnemo == "bcc": taken, reason = val&(1< Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: ra = None if self.is_ret(insn): ra = gef.arch.register("$o7") @@ -3376,7 +3375,7 @@ class MIPS(Architecture): syscall_register = "$v0" syscall_instructions = ("syscall",) - def flag_register_to_human(self, val: Optional[int] = None) -> str: + def flag_register_to_human(self, val: int | None = None) -> str: return Color.colorify("No flag register", "yellow underline") def is_call(self, insn: Instruction) -> bool: @@ -3412,7 +3411,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: taken, reason = gef.arch.register(ops[0]) <= 0, f"{ops[0]} <= 0" return taken, reason - def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None: ra = None if self.is_ret(insn): ra = gef.arch.register("$ra") @@ -3446,7 +3445,7 @@ class MIPS64(MIPS): _ptrsize = 8 @staticmethod - def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: + def supports_gdb_arch(gdb_arch: str) -> bool | None: if not gef.binary or not isinstance(gef.binary, Elf): return False return gdb_arch.startswith("mips") and gef.binary.e_class == Elf.Class.ELF_64_BITS @@ -3501,7 +3500,7 @@ def to_unsigned_long(v: gdb.Value) -> int: return int(v.cast(gdb.Value(mask).type)) & mask -def get_path_from_info_proc() -> Optional[str]: +def get_path_from_info_proc() -> str | None: for x in (gdb.execute("info proc", to_string=True) or "").splitlines(): if x.startswith("exe = "): return x.split(" = ")[1].replace("'", "") @@ -3537,7 +3536,7 @@ def is_qemu_system() -> bool: return "received: \"\"" in response -def get_filepath() -> Optional[str]: +def get_filepath() -> str | None: """Return the local absolute path of the file currently debugged.""" if gef.session.remote: return str(gef.session.remote.lfile.absolute()) @@ -3581,7 +3580,7 @@ def get_info_files() -> list[Zone]: return infos -def process_lookup_address(address: int) -> Optional[Section]: +def process_lookup_address(address: int) -> Section | None: """Look up for an address in memory. Return an Address object if found, None otherwise.""" if not is_alive(): @@ -3600,7 +3599,7 @@ def process_lookup_address(address: int) -> Optional[Section]: @lru_cache() -def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Optional[Section]: +def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Section | None: """Look up for a path in the process memory mapping. Return a Section object if found, None otherwise.""" if not is_alive(): @@ -3632,7 +3631,7 @@ def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Optiona @lru_cache() -def file_lookup_name_path(name: str, path: str) -> Optional[Zone]: +def file_lookup_name_path(name: str, path: str) -> Zone | None: """Look up a file by name and path. Return a Zone object if found, None otherwise.""" for xfile in get_info_files(): @@ -3642,7 +3641,7 @@ def file_lookup_name_path(name: str, path: str) -> Optional[Zone]: @lru_cache() -def file_lookup_address(address: int) -> Optional[Zone]: +def file_lookup_address(address: int) -> Zone | None: """Look up for a file by its address. Return a Zone object if found, None otherwise.""" for info in get_info_files(): @@ -3688,7 +3687,7 @@ def hook_stop_handler(_: "gdb.StopEvent") -> None: return -def new_objfile_handler(evt: Optional["gdb.NewObjFileEvent"]) -> None: +def new_objfile_handler(evt: "gdb.NewObjFileEvent | None") -> None: """GDB event handler for new object file cases.""" reset_all_caches() progspace = gdb.current_progspace() @@ -3828,7 +3827,7 @@ def is_arch(arch: Elf.Abi) -> bool: return arch in gef.arch.aliases -def reset_architecture(arch: Optional[str] = None) -> None: +def reset_architecture(arch: str | None = None) -> None: """Sets the current architecture. If an architecture is explicitly specified by parameter, try to use that one. If this fails, an `OSError` exception will occur. @@ -3869,7 +3868,7 @@ def reset_architecture(arch: Optional[str] = None) -> None: @lru_cache() -def cached_lookup_type(_type: str) -> Optional[gdb.Type]: +def cached_lookup_type(_type: str) -> gdb.Type | None: try: return gdb.lookup_type(_type).strip_typedefs() except RuntimeError: @@ -3993,7 +3992,7 @@ def generate_cyclic_pattern(length: int, cycle: int = 4) -> bytearray: return bytearray(itertools.islice(de_bruijn(charset, cycle), length)) -def safe_parse_and_eval(value: str) -> Optional["gdb.Value"]: +def safe_parse_and_eval(value: str) -> "gdb.Value | None": """GEF wrapper for gdb.parse_and_eval(): this function returns None instead of raising gdb.error if the eval failed.""" try: @@ -4004,7 +4003,7 @@ def safe_parse_and_eval(value: str) -> Optional["gdb.Value"]: @lru_cache() -def dereference(addr: int) -> Optional["gdb.Value"]: +def dereference(addr: int) -> "gdb.Value | None": """GEF wrapper for gdb dereference function.""" try: ulong_t = cached_lookup_type(use_stdtype()) or \ @@ -4086,7 +4085,7 @@ def gef_getpagesize() -> int: @deprecated("Use `gef.session.canary`") -def gef_read_canary() -> Optional[tuple[int, int]]: +def gef_read_canary() -> tuple[int, int] | None: return gef.session.canary @@ -4102,12 +4101,12 @@ def get_filename() -> str: @deprecated("Use `gef.heap.main_arena`") -def get_glibc_arena() -> Optional[GlibcArena]: +def get_glibc_arena() -> GlibcArena | None: return gef.heap.main_arena @deprecated("Use `gef.arch.register(regname)`") -def get_register(regname) -> Optional[int]: +def get_register(regname) -> int | None: return gef.arch.register(regname) @@ -4117,7 +4116,7 @@ def get_process_maps() -> list[Section]: @deprecated("Use `reset_architecture`") -def set_arch(arch: Optional[str] = None, _: Optional[str] = None) -> None: +def set_arch(arch: str | None = None, _: str | None = None) -> None: return reset_architecture(arch) # @@ -4282,7 +4281,7 @@ def stop(self) -> bool: class StubBreakpoint(gdb.Breakpoint): """Create a breakpoint to permanently disable a call (fork/alarm/signal/etc.).""" - def __init__(self, func: str, retval: Optional[int]) -> None: + def __init__(self, func: str, retval: int | None) -> None: super().__init__(func, gdb.BP_BREAKPOINT, internal=False) self.func = func self.retval = retval @@ -4612,7 +4611,7 @@ def __init__(self, loc: str) -> None: # Context Panes # -def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]], condition : Optional[Callable[[], bool]] = None) -> None: +def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], str | None], condition : Callable[[], bool] | None = None) -> None: """ Registering function for new GEF Context View. pane_name: a string that has no spaces (used in settings) @@ -4633,7 +4632,7 @@ def pane_title(): gef.gdb.add_context_pane(pane_name, display_pane_function, pane_title_function, condition) return -def register_external_context_layout_mapping(current_pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]], condition : Optional[Callable[[], bool]] = None) -> None: +def register_external_context_layout_mapping(current_pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], str | None], condition : Callable[[], bool] | None = None) -> None: gef.gdb.add_context_layout_mapping(current_pane_name, display_pane_function, pane_title_function, condition) return @@ -4778,7 +4777,7 @@ def __contains__(self, name: str) -> bool: def add_setting(self, name: str, value: tuple[Any, type, str], description: str = "") -> None: return self.__setitem__(name, (value, description)) - def __setitem__(self, name: str, value: "GefSetting" | tuple[Any, str]) -> None: + def __setitem__(self, name: str, value: "GefSetting | tuple[Any, str]") -> None: # make sure settings are always associated to the root command (which derives from GenericCommand) if "GenericCommand" not in [x.__name__ for x in self.__class__.__bases__]: return @@ -5713,7 +5712,7 @@ def structures(self) -> Generator[tuple["ExternalStructureManager.Module", "Exte return @lru_cache() - def find(self, structure_name: str) -> Optional[tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"]]: + def find(self, structure_name: str) -> tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"] | None: """Return the module and structure for the given structure name; `None` if the structure name was not found.""" for module in self.modules.values(): if structure_name in module: @@ -6937,7 +6936,7 @@ def check_thread_ids(self, tids: list[int]) -> list[int]: existing_tids = set(t.num for t in gdb.selected_inferior().threads()) return list(set(tids) & existing_tids) - def tcachebin(self, tcache_base: int, i: int) -> tuple[Optional[GlibcTcacheChunk], int]: + def tcachebin(self, tcache_base: int, i: int) -> tuple[GlibcTcacheChunk | None, int]: """Return the head chunk in tcache[i] and the number of chunks in the bin.""" if i >= self.TCACHE_MAX_BINS: err("Incorrect index value, index value must be between 0 and " @@ -7594,7 +7593,7 @@ class ContextCommand(GenericCommand): _syntax_ = f"{_cmdline_} [legend|regs|stack|code|args|memory|source|trace|threads|extra]" _aliases_ = ["ctx",] - old_registers: dict[str, Optional[int]] = {} + old_registers: dict[str, int | None] = {} def __init__(self) -> None: super().__init__() @@ -7622,7 +7621,7 @@ def __init__(self) -> None: self["libc_args"] = (False, "[DEPRECATED - Unused] Show libc function call args description") self["libc_args_path"] = ("", "[DEPRECATED - Unused] Path to libc function call args json files, provided via gef-extras") - self.layout_mapping: dict[str, tuple[Callable, Optional[Callable], Optional[Callable]]] = { + self.layout_mapping: dict[str, tuple[Callable, Callable | None, Callable | None]] = { "legend": (self.show_legend, None, None), "regs": (self.context_regs, None, None), "stack": (self.context_stack, None, None), @@ -7705,7 +7704,7 @@ def do_invoke(self, argv: list[str]) -> None: disable_redirect_output() return - def context_title(self, m: Optional[str]) -> None: + def context_title(self, m: str | None) -> None: # allow for not displaying a title line if m is None: return @@ -7959,7 +7958,7 @@ def print_arguments_from_symbol(self, function_name: str, symbol: "gdb.Symbol") def print_guessed_arguments(self, function_name: str) -> None: """When no symbol, read the current basic block and look for "interesting" instructions.""" - def __get_current_block_start_address() -> Optional[int]: + def __get_current_block_start_address() -> int | None: pc = gef.arch.pc try: block = gdb.block_for_pc(pc) @@ -8411,7 +8410,7 @@ class HexdumpCommand(GenericCommand): def __init__(self) -> None: super().__init__(complete=gdb.COMPLETE_LOCATION, prefix=True) self["always_show_ascii"] = (False, "If true, hexdump will always display the ASCII dump") - self.format: Optional[str] = None + self.format: str | None = None self.__last_target = "$sp" return @@ -8550,7 +8549,7 @@ class PatchCommand(GenericCommand): def __init__(self) -> None: super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION) - self.format: Optional[str] = None + self.format: str | None = None return @only_if_gdb_running @@ -9945,8 +9944,8 @@ def __init__(self) -> None: gef.config["gef.propagate_debug_exception"] = GefSetting(False, bool, "If true, when debug mode is enabled, Python exceptions will be propagated all the way.") gef.config["gef.extra_python_package_paths"] = GefSetting("", str, "Semi-colon separate list of extra paths to include for python packages") - self.commands : dict[str, GenericCommand] = collections.OrderedDict() - self.functions : dict[str, GenericFunction] = collections.OrderedDict() + self.commands : dict[str, GenericCommand] = {} + self.functions : dict[str, GenericFunction] = {} self.missing: dict[str, Exception] = {} return @@ -9981,7 +9980,7 @@ def setup(self) -> None: GefRestoreCommand() return - def load_extra_plugins(self, extra_plugins_dir: Optional[pathlib.Path] = None) -> int: + def load_extra_plugins(self, extra_plugins_dir: pathlib.Path | None = None) -> int: """Load the plugins from the gef-extras setting. Returns the number of new plugins added.""" def load_plugin(fpath: pathlib.Path) -> bool: try: @@ -10035,7 +10034,7 @@ def invoke(self, args: Any, from_tty: bool) -> None: gdb.execute("gef help") return - def add_context_layout_mapping(self, current_pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Optional[Callable]) -> None: + def add_context_layout_mapping(self, current_pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Callable | None) -> None: """Add a new context layout mapping.""" context = self.commands["context"] assert isinstance(context, ContextCommand) @@ -10043,7 +10042,7 @@ def add_context_layout_mapping(self, current_pane_name: str, display_pane_functi # overload the printing of pane title context.layout_mapping[current_pane_name] = (display_pane_function, pane_title_function, condition) - def add_context_pane(self, pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Optional[Callable]) -> None: + def add_context_pane(self, pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Callable | None) -> None: """Add a new context pane to ContextCommand.""" context = self.commands["context"] assert isinstance(context, ContextCommand) @@ -10489,7 +10488,7 @@ def invoke(self, args: Any, from_tty: bool) -> None: gdb.execute(f"{self.command} {args}", from_tty=from_tty) return - def lookup_command(self, cmd: str) -> Optional[tuple[str, GenericCommand]]: + def lookup_command(self, cmd: str) -> tuple[str, GenericCommand] | None: global gef for _name, _instance in gef.gdb.commands.items(): if cmd == _name: @@ -10740,10 +10739,10 @@ def __init__(self) -> None: def reset_caches(self) -> None: super().reset_caches() - self.__maps: Optional[list[Section]] = None + self.__maps: list[Section] | None = None return - def write(self, address: int, buffer: ByteString, length: Optional[int] = None) -> None: + def write(self, address: int, buffer: ByteString, length: int | None = None) -> None: """Write `buffer` at address `address`.""" length = length or len(buffer) gdb.selected_inferior().write_memory(address, buffer, length) @@ -10762,7 +10761,7 @@ def read_integer(self, addr: int) -> int: def read_cstring(self, address: int, max_length: int = GEF_MAX_STRING_LENGTH, - encoding: Optional[str] = None) -> str: + encoding: str | None = None) -> str: """Return a C-string read from memory.""" encoding = encoding or "unicode-escape" length = min(address | (DEFAULT_PAGE_SIZE-1), max_length+1) @@ -10803,7 +10802,7 @@ def read_cstring(self, return f"{ustr[:max_length]}[...]" return ustr - def read_ascii_string(self, address: int) -> Optional[str]: + def read_ascii_string(self, address: int) -> str | None: """Read an ASCII string from memory""" cstr = self.read_cstring(address) if isinstance(cstr, str) and cstr and all(x in string.printable for x in cstr): @@ -10819,7 +10818,7 @@ def maps(self) -> list[Section]: self.__maps = maps return self.__maps - def __parse_maps(self) -> Optional[list[Section]]: + def __parse_maps(self) -> list[Section] | None: """Return the mapped memory sections. If the current arch has its maps method defined, then defer to that to generated maps, otherwise, try to figure it out from procfs, then info sections, then monitor info @@ -11002,13 +11001,13 @@ def __init__(self) -> None: return def reset_caches(self) -> None: - self.__libc_main_arena: Optional[GlibcArena] = None - self.__libc_selected_arena: Optional[GlibcArena] = None + self.__libc_main_arena: GlibcArena | None = None + self.__libc_selected_arena: GlibcArena | None = None self.__heap_base = None return @property - def main_arena(self) -> Optional[GlibcArena]: + def main_arena(self) -> GlibcArena | None: if not self.__libc_main_arena: try: __main_arena_addr = GefHeapManager.find_main_arena_addr() @@ -11104,7 +11103,7 @@ def search_filter(zone: Zone) -> bool: raise OSError(err_msg) @property - def selected_arena(self) -> Optional[GlibcArena]: + def selected_arena(self) -> GlibcArena | None: if not self.__libc_selected_arena: # `selected_arena` must default to `main_arena` self.__libc_selected_arena = self.main_arena @@ -11122,7 +11121,7 @@ def arenas(self) -> list | Iterator[GlibcArena]: return iter(self.main_arena) @property - def base_address(self) -> Optional[int]: + def base_address(self) -> int | None: if not self.__heap_base: base = 0 try: @@ -11175,7 +11174,7 @@ def ceil(n: float) -> int: class GefSetting: """Basic class for storing gef settings as objects""" - def __init__(self, value: Any, cls: Optional[type] = None, description: Optional[str] = None, hooks: Optional[dict[str, list[Callable]]] = None) -> None: + def __init__(self, value: Any, cls: type | None = None, description: str | None = None, hooks: dict[str, list[Callable]] | None = None) -> None: self.value = value self.type = cls or type(value) self.description = description or "" @@ -11276,7 +11275,7 @@ class GefSessionManager(GefManager): """Class managing the runtime properties of GEF. """ def __init__(self) -> None: self.reset_caches() - self.remote: Optional["GefRemoteSessionManager"] = None + self.remote: "GefRemoteSessionManager | None" = None self.remote_initializing: bool = False self.qemu_mode: bool = False self.convenience_vars_index: int = 0 @@ -11299,8 +11298,8 @@ def reset_caches(self) -> None: self._os = None self._pid = None self._file = None - self._maps: Optional[pathlib.Path] = None - self._root: Optional[pathlib.Path] = None + self._maps: pathlib.Path | None = None + self._root: pathlib.Path | None = None return def __str__(self) -> str: @@ -11311,7 +11310,7 @@ def __repr__(self) -> str: return str(self) @property - def auxiliary_vector(self) -> Optional[dict[str, int]]: + def auxiliary_vector(self) -> dict[str, int] | None: if not is_alive(): return None if is_qemu_system(): @@ -11351,7 +11350,7 @@ def pid(self) -> int: return self._pid @property - def file(self) -> Optional[pathlib.Path]: + def file(self) -> pathlib.Path | None: """Return a Path object of the target process.""" if self.remote is not None: return self.remote.file @@ -11363,7 +11362,7 @@ def file(self) -> Optional[pathlib.Path]: return self._file @property - def cwd(self) -> Optional[pathlib.Path]: + def cwd(self) -> pathlib.Path | None: if self.remote is not None: return self.remote.root return self.file.parent if self.file else None @@ -11378,7 +11377,7 @@ def pagesize(self) -> int: return self._pagesize @property - def canary(self) -> Optional[tuple[int, int]]: + def canary(self) -> tuple[int, int] | None: """Return a tuple of the canary address and value, read from the canonical location if supported by the architecture. Otherwise, read from the auxiliary vector.""" @@ -11392,7 +11391,7 @@ def canary(self) -> Optional[tuple[int, int]]: return canary, canary_location @property - def original_canary(self) -> Optional[tuple[int, int]]: + def original_canary(self) -> tuple[int, int] | None: """Return a tuple of the initial canary address and value, read from the auxiliary vector.""" auxval = self.auxiliary_vector @@ -11404,7 +11403,7 @@ def original_canary(self) -> Optional[tuple[int, int]]: return canary, canary_location @property - def maps(self) -> Optional[pathlib.Path]: + def maps(self) -> pathlib.Path | None: """Returns the Path to the procfs entry for the memory mapping.""" if not is_alive(): return None @@ -11416,7 +11415,7 @@ def maps(self) -> Optional[pathlib.Path]: return self._maps @property - def root(self) -> Optional[pathlib.Path]: + def root(self) -> pathlib.Path | None: """Returns the path to the process's root directory.""" if not is_alive(): return None @@ -11450,7 +11449,7 @@ def prompt_string(self) -> str: return Color.boldify("(remote) ") raise AttributeError("Unknown value") - def __init__(self, host: str, port: int, pid: int =-1, qemu: Optional[pathlib.Path] = None) -> None: + def __init__(self, host: str, port: int, pid: int =-1, qemu: pathlib.Path | None = None) -> None: super().__init__() self.__host = host self.__port = port @@ -11519,7 +11518,7 @@ def maps(self) -> pathlib.Path: def mode(self) -> RemoteMode: return self._mode - def sync(self, src: str, dst: Optional[str] = None) -> bool: + def sync(self, src: str, dst: str | None = None) -> bool: """Copy the `src` into the temporary chroot. If `dst` is provided, that path will be used instead of `src`.""" if not dst: @@ -11657,9 +11656,9 @@ def remote_objfile_event_handler(self, evt: "gdb.NewObjFileEvent") -> None: class GefUiManager(GefManager): """Class managing UI settings.""" def __init__(self) -> None: - self.redirect_fd : Optional[TextIOWrapper] = None + self.redirect_fd : TextIOWrapper | None = None self.context_hidden = False - self.stream_buffer : Optional[StringIO] = None + self.stream_buffer : StringIO | None = None self.highlight_table: dict[str, str] = {} self.watches: dict[int, tuple[int, str]] = {} self.context_messages: list[tuple[str, str]] = [] @@ -11672,16 +11671,16 @@ class GefLibcManager(GefManager): PATTERN_LIBC_VERSION_FILENAME = re.compile(r"libc6?[-_](\d+)\.(\d+)\.so") def __init__(self) -> None: - self._version : Optional[tuple[int, int]] = None - self._patch: Optional[int] = None - self._release: Optional[str] = None + self._version : tuple[int, int] | None = None + self._patch: int | None = None + self._release: str | None = None return def __str__(self) -> str: return f"Libc(version='{self.version}')" @property - def version(self) -> Optional[tuple[int, int]]: + def version(self) -> tuple[int, int] | None: if not is_alive(): return None @@ -11723,7 +11722,7 @@ def find_libc_version() -> tuple[int, int]: class Gef: """The GEF root class, which serves as a entrypoint for all the debugging session attributes (architecture, memory, settings, etc.).""" - binary: Optional[FileFormat] + binary: FileFormat | None arch: Architecture config : GefSettingsManager ui: GefUiManager @@ -11734,7 +11733,7 @@ class Gef: gdb: GefCommand def __init__(self) -> None: - self.binary: Optional[FileFormat] = None + self.binary: FileFormat | None = None self.arch: Architecture = GenericArchitecture() # see PR #516, will be reset by `new_objfile_handler` self.arch_reason: str = "This is the default architecture" self.config = GefSettingsManager() @@ -11769,32 +11768,14 @@ def reset_caches(self) -> None: def target_remote_posthook(): - conn = gdb.selected_inferior().connection - # if here, the connection should be established - if not isinstance(conn, gdb.RemoteTargetConnection): - raise EnvironmentError("Failed to create a remote session") - - # if gef.session.remote_initializing: - # dbg(f"remote session already initializing: {gef.session}") - # return - - print(f"{conn.description=}") - print(f"{conn.details=}") - print(f"{conn.type=}") - - assert conn.details - host, port = conn.details.split(":") - pid = ( - gdb.selected_inferior().pid - if False - else gdb.selected_thread().ptid[1] - ) - print(host, port, pid) - gef.session.remote = GefRemoteSessionManager(host, int(port), pid) - gef.session.remote._mode = GefRemoteSessionManager.RemoteMode.GDBSERVER + if gef.session.remote_initializing: + return + gef.session.remote = GefRemoteSessionManager("", 0) if not gef.session.remote.setup(): - raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote}") + raise EnvironmentError( + f"Failed to create a proper environment for {gef.session.remote}" + ) if __name__ == "__main__": if sys.version_info[0] == 2: @@ -11839,14 +11820,6 @@ def target_remote_posthook(): # reload settings gdb.execute("gef restore") - # extend `sys.path` for venv compat - if gef.config["gef.extra_python_package_paths"]: - for path in map(pathlib.Path, gef.config["gef.extra_python_package_paths"].split(";")): - if not path.exists(): - warn(f"Skipping invalid directory {path}") - continue - sys.path.extend(str(path.absolute())) - # setup gdb prompt gdb.prompt_hook = __gef_prompt__ @@ -11865,16 +11838,32 @@ def target_remote_posthook(): GefTmuxSetup() - # Register a post-hook for `target remote` that initialize the remote session - hook = """ - define target hookpost-{} - pi target_remote_posthook() - context - pi res = calling_function(); if res != "connect": err("Failed to initialize remote session: " + str(res)) - end - """ - gdb.execute(hook.format("remote")) - gdb.execute(hook.format("extended-remote")) + if GDB_VERSION > (9, 0): + disable_tr_overwrite_setting = "gef.disable_target_remote_overwrite" + + if not gef.config[disable_tr_overwrite_setting]: + warnmsg = ("Using `target remote` with GEF should work in most cases, " + "but use `gef-remote` if you can. You can disable the " + "overwrite of the `target remote` command by toggling " + f"`{disable_tr_overwrite_setting}` in the config.") + hook = f""" + define target hookpost-{{}} + pi target_remote_posthook() + context + pi if calling_function() != "connect": warn("{warnmsg}") + end + """ + + # Register a post-hook for `target remote` that initialize the remote session + gdb.execute(hook.format("remote")) + gdb.execute(hook.format("extended-remote")) + else: + errmsg = ("Using `target remote` does not work, use `gef-remote` " + f"instead. You can toggle `{disable_tr_overwrite_setting}` " + "if this is not desired.") + hook = f"""pi if calling_function() != "connect": err("{errmsg}")""" + gdb.execute(f"define target hook-remote\n{hook}\nend") + gdb.execute(f"define target hook-extended-remote\n{hook}\nend") # restore saved breakpoints (if any) bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute() From 60be6a15cbcf9a4aa083d7251193c5ba30955d62 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 2 Nov 2024 13:31:04 -0700 Subject: [PATCH 08/38] fixed ruff toml --- ruff.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruff.toml b/ruff.toml index 6a3fc3001..11ea9dbb8 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,2 +1,2 @@ [lint.per-file-ignores] -"gef.py" = ["E701"] \ No newline at end of file +"gef.py" = ["E701"] From dc289385b9776540a6f7c09473852ca90ab87394 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 2 Nov 2024 13:41:09 -0700 Subject: [PATCH 09/38] revert changes, only focus on py3.10 improvements --- gef.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/gef.py b/gef.py index af1151296..47506d3cd 100644 --- a/gef.py +++ b/gef.py @@ -6279,16 +6279,15 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: return # qemu-user support - qemu_binary = None + qemu_binary: pathlib.Path | None = None if args.qemu_user: - dbg("Setting up qemu-user session") try: qemu_binary = pathlib.Path(args.qemu_user).expanduser().absolute() if args.qemu_user else gef.session.file if not qemu_binary or not qemu_binary.exists(): raise FileNotFoundError(f"qemu-user session was specified, but binary '{qemu_binary}' does not exist") except Exception as e: err(f"Failed to initialize qemu-user mode, reason: {str(e)}") - raise e + return # Try to establish the remote session, throw on error # set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which @@ -11773,9 +11772,7 @@ def target_remote_posthook(): gef.session.remote = GefRemoteSessionManager("", 0) if not gef.session.remote.setup(): - raise EnvironmentError( - f"Failed to create a proper environment for {gef.session.remote}" - ) + raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote}") if __name__ == "__main__": if sys.version_info[0] == 2: From 417ac632afbbeadcce54e157ad4c4de8526ec852 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sat, 2 Nov 2024 14:11:23 -0700 Subject: [PATCH 10/38] final fixes --- gef.py | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/gef.py b/gef.py index 47506d3cd..08bed01fd 100644 --- a/gef.py +++ b/gef.py @@ -114,8 +114,8 @@ def update_gef(argv: list[str]) -> int: sys.exit(1) -GDB_MIN_VERSION: tuple[int, int] = (8, 0) -PYTHON_MIN_VERSION: tuple[int, int] = (3, 6) +GDB_MIN_VERSION: tuple[int, int] = (10, 0) +PYTHON_MIN_VERSION: tuple[int, int] = (3, 10) PYTHON_VERSION: tuple[int, int] = sys.version_info[0:2] GDB_VERSION: tuple[int, int] = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups())) # type:ignore @@ -143,10 +143,10 @@ def update_gef(argv: list[str]) -> int: GEF_PROMPT_ON = f"\001\033[1;32m\002{GEF_PROMPT}\001\033[0m\002" GEF_PROMPT_OFF = f"\001\033[1;31m\002{GEF_PROMPT}\001\033[0m\002" -__registered_commands__ : set[Type["GenericCommand"]] = set() -__registered_functions__ : set[Type["GenericFunction"]] = set() -__registered_architectures__ : dict["Elf.Abi | str", Type["Architecture"]] = {} -__registered_file_formats__ : set[ Type["FileFormat"] ] = set() +__registered_commands__ : set[Type["GenericCommand"]] = set() +__registered_functions__ : set[Type["GenericFunction"]] = set() +__registered_architectures__ : dict["Elf.Abi | str", Type["Architecture"]] = {} +__registered_file_formats__ : set[ Type["FileFormat"] ] = set() GefMemoryMapProvider = Callable[[], Generator["Section", None, None]] @@ -2910,7 +2910,7 @@ def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]: class X86(Architecture): - aliases: tuple[str | Elf.Abi, ...] = ("X86", Elf.Abi.X86_32) + aliases = ("X86", Elf.Abi.X86_32) arch = "X86" mode = "32" @@ -3354,7 +3354,7 @@ def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: class MIPS(Architecture): - aliases: tuple[str | Elf.Abi, ...] = ("MIPS", Elf.Abi.MIPS) + aliases = ("MIPS", Elf.Abi.MIPS) arch = "MIPS" mode = "MIPS32" @@ -3517,7 +3517,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) or "" - return "ENABLE=1" in response + return "ENABLE=" in response @lru_cache() @@ -4557,7 +4557,7 @@ def stop(self) -> bool: return False # software watchpoints stop after the next statement (see - # https://sourceware.org/gdb/onlinedocs/gdb/set-Watchpoints.html) + # https://sourceware.org/gdb/onlinedocs/gdb/Set-Watchpoints.html) pc = gdb_get_nth_previous_instruction_address(gef.arch.pc, 2) assert pc insn = gef_current_instruction(pc) @@ -4686,7 +4686,7 @@ class GenericCommand(gdb.Command): _cmdline_: str _syntax_: str - _example_: str | list[str] = "" + _example_: str | list[str] = "" _aliases_: list[str] = [] def __init_subclass__(cls, **kwargs): @@ -4851,7 +4851,7 @@ def do_invoke(self, args: list[str]) -> None: @register class ArchSetCommand(GenericCommand): - """set the current loaded architecture.""" + """Set the current loaded architecture.""" _cmdline_ = "arch set" _syntax_ = f"{_cmdline_} " @@ -5026,7 +5026,7 @@ def do_invoke(self, argv: list[str]) -> None: @register class PieBreakpointCommand(GenericCommand): - """set a PIE breakpoint at an offset from the target binaries base address.""" + """Set a PIE breakpoint at an offset from the target binaries base address.""" _cmdline_ = "pie breakpoint" _syntax_ = f"{_cmdline_} OFFSET" @@ -5789,7 +5789,7 @@ def __init__(self) -> None: super().__init__() return - def do_invoke(self, _: list) -> None: + def do_invoke(self, _: list[str]) -> None: """Dump the list of all the structures and their respective.""" manager = ExternalStructureManager() info(f"Listing custom structures from '{manager.path}'") @@ -6266,7 +6266,7 @@ def __init__(self) -> None: super().__init__(prefix=False) return - @parse_arguments({"host": "", "port": 0}, {"--pid": 0, "--qemu-user": ""}) + @parse_arguments({"host": "", "port": 0}, {"--pid": -1, "--qemu-user": False, "--qemu-binary": ""}) def do_invoke(self, _: list[str], **kwargs: Any) -> None: if gef.session.remote is not None: err("You already are in remote session. Close it first before opening a new one...") @@ -6282,15 +6282,15 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: qemu_binary: pathlib.Path | None = None if args.qemu_user: try: - qemu_binary = pathlib.Path(args.qemu_user).expanduser().absolute() if args.qemu_user else gef.session.file + qemu_binary = pathlib.Path(args.qemu_binary).expanduser().absolute() if args.qemu_binary else gef.session.file if not qemu_binary or not qemu_binary.exists(): - raise FileNotFoundError(f"qemu-user session was specified, but binary '{qemu_binary}' does not exist") + raise FileNotFoundError(f"{qemu_binary} does not exist") except Exception as e: err(f"Failed to initialize qemu-user mode, reason: {str(e)}") return # Try to establish the remote session, throw on error - # set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which + # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None # This prevents some spurious errors being thrown during startup gef.session.remote_initializing = True @@ -6500,7 +6500,7 @@ def do_invoke(self, _: list[str]) -> None: @register class GlibcHeapSetArenaCommand(GenericCommand): - """set the address of the main_arena or the currently selected arena.""" + """Set the address of the main_arena or the currently selected arena.""" _cmdline_ = "heap set-arena" _syntax_ = f"{_cmdline_} [address|&symbol]" @@ -8572,9 +8572,9 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: var_name = values[0] try: values = str(gdb.parse_and_eval(var_name)).lstrip("{").rstrip("}").replace(",","").split(" ") - except Exception as e: + except Exception: gef_print(f"Bad variable specified, check value with command: p {var_name}") - raise e + return d = str(gef.arch.endianness) for value in values: @@ -9939,9 +9939,8 @@ def __init__(self) -> None: gef.config["gef.buffer"] = GefSetting(True, bool, "Internally buffer command output until completion") 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.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.") - gef.config["gef.extra_python_package_paths"] = GefSetting("", str, "Semi-colon separate list of extra paths to include for python packages") self.commands : dict[str, GenericCommand] = {} self.functions : dict[str, GenericFunction] = {} @@ -10725,7 +10724,8 @@ def reset_caches(self) -> None: if not hasattr(obj, "cache_clear"): continue obj.cache_clear() - except Exception: # we're reseting the cache here, we don't care if (or which) exception triggers + except Exception: + # we're reseting the cache here, we don't care if (or which) exception triggers continue return @@ -11472,7 +11472,7 @@ def close(self) -> None: gef_on_new_hook(new_objfile_handler) except Exception as e: warn(f"Exception while restoring local context: {str(e)}") - raise e + raise def __str__(self) -> str: return f"RemoteSession(target='{self.target}', local='{self.root}', pid={self.pid}, mode={self.mode})" From 9fb1d716540736b939e22872ebfc8d66863b675e Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 3 Nov 2024 09:00:01 -0800 Subject: [PATCH 11/38] added `untracked` dir to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6ec4d8589..09f604883 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ debug.log htmlcov .benchmarks site/ +untracked/ From a27766176cee69f3c3a8d5418eda725ca3b763dc Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 3 Nov 2024 11:45:36 -0800 Subject: [PATCH 12/38] checkpoint: added new remote modes, gdbserver & gdbserver-multi work --- gef.py | 194 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 122 insertions(+), 72 deletions(-) diff --git a/gef.py b/gef.py index 08bed01fd..41cb61da6 100644 --- a/gef.py +++ b/gef.py @@ -273,11 +273,13 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: return wrapper -class ValidationError(Exception): pass # # Helpers # +class ValidationError(Exception): pass + +class InitializationError(Exception): pass class ObsoleteException(Exception): pass @@ -347,10 +349,10 @@ def is_alive() -> bool: return False -def calling_function() -> str | None: +def calling_function(frame: int = 3) -> str | None: """Return the name of the calling function""" try: - stack_info = traceback.extract_stack()[-3] + stack_info = traceback.extract_stack()[-frame] return stack_info.name except Exception as e: dbg(f"traceback failed with {str(e)}") @@ -3512,30 +3514,57 @@ def get_os() -> str: return gef.session.os -@lru_cache() -def is_qemu() -> bool: - if not is_remote_debug(): +def is_target_remote(conn: gdb.TargetConnection | None = None) -> bool: + "Returns True for `extended-remote` only." + _conn = conn or gdb.selected_inferior().connection + return isinstance(_conn, gdb.RemoteTargetConnection) and _conn.type == "remote" + + +def is_target_extended_remote(conn: gdb.TargetConnection | None = None) -> bool: + "Returns True for `extended-remote` only." + _conn = conn or gdb.selected_inferior().connection + return isinstance(_conn, gdb.RemoteTargetConnection) and _conn.type == "extended-remote" + + +def is_target_remote_or_extended(conn: gdb.TargetConnection | None = None) -> bool: + return is_target_remote(conn) or is_target_extended_remote(conn) + + +def is_running_under_qemu() -> bool: + "See https://www.qemu.org/docs/master/system/gdb.html " + if not is_target_remote(): return False 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(): +def is_running_under_qemu_user() -> bool: + if not is_running_under_qemu(): return False - response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" + response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" # Use `qAttached`? return "Text=" in response -@lru_cache() -def is_qemu_system() -> bool: - if not is_qemu(): +def is_running_under_qemu_system() -> bool: + if not is_running_under_qemu(): return False + # Use "maintenance packet qqemu.PhyMemMode"? response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" return "received: \"\"" in response +def is_running_in_gdbserver() -> bool: + if is_running_under_qemu(): + return False + return not is_running_under_qemu() + + +def is_running_in_rr() -> bool: + if not is_running_in_gdbserver(): + return False + return os.environ.get("GDB_UNDER_RR", None) == "1" + + def get_filepath() -> str | None: """Return the local absolute path of the file currently debugged.""" if gef.session.remote: @@ -3960,6 +3989,7 @@ def is_in_x86_kernel(address: int) -> bool: return (address >> memalign) == 0xF +@deprecated("Use `is_target_remote()`") def is_remote_debug() -> bool: """"Return True is the current debugging session is running through GDB remote session.""" return gef.session.remote_initializing or gef.session.remote is not None @@ -6294,7 +6324,10 @@ def do_invoke(self, _: list[str], **kwargs: Any) -> None: # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None # This prevents some spurious errors being thrown during startup gef.session.remote_initializing = True - session = GefRemoteSessionManager(args.host, args.port, args.pid, qemu_binary) + # session = GefRemoteSessionManager(args.host, args.port, args.pid, qemu_binary) + conn = gdb.selected_inferior().connection + assert isinstance(conn, gdb.RemoteTargetConnection) + session = GefRemoteSessionManager(conn) dbg(f"[remote] initializing remote session with {session.target} under {session.root}") if not session.connect(args.pid) or not session.setup(): @@ -7414,7 +7447,7 @@ def __init__(self) -> None: def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] - if is_qemu_system(): + if is_running_under_qemu_system(): err("Unsupported") return @@ -11312,7 +11345,7 @@ def __repr__(self) -> str: def auxiliary_vector(self) -> dict[str, int] | None: if not is_alive(): return None - if is_qemu_system(): + if is_running_under_qemu_system(): return None if not self._auxiliary_vector: auxiliary_vector = {} @@ -11431,6 +11464,9 @@ class RemoteMode(enum.IntEnum): GDBSERVER = 0 QEMU = 1 RR = 2 + GDBSERVER_MULTI = 3 + QEMU_USER = 4 + QEMU_SYSTEM = 5 def __str__(self): return self.name @@ -11440,30 +11476,48 @@ def __repr__(self): def prompt_string(self) -> str: match self: - case GefRemoteSessionManager.RemoteMode.QEMU: + case ( + GefRemoteSessionManager.RemoteMode.QEMU | + GefRemoteSessionManager.RemoteMode.QEMU_USER | + GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM + ): return Color.boldify("(qemu) ") case GefRemoteSessionManager.RemoteMode.RR: return Color.boldify("(rr) ") - case GefRemoteSessionManager.RemoteMode.GDBSERVER: + case ( + GefRemoteSessionManager.RemoteMode.GDBSERVER | + GefRemoteSessionManager.RemoteMode.GDBSERVER_MULTI + ): return Color.boldify("(remote) ") raise AttributeError("Unknown value") - def __init__(self, host: str, port: int, pid: int =-1, qemu: pathlib.Path | None = None) -> None: + @staticmethod + def init() -> "GefRemoteSessionManager.RemoteMode": + if is_running_under_qemu_system(): + return GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM + if is_running_under_qemu_user(): + return GefRemoteSessionManager.RemoteMode.QEMU_USER + if is_running_in_rr(): + return GefRemoteSessionManager.RemoteMode.RR + if is_running_in_gdbserver(): + if is_target_extended_remote(): + return GefRemoteSessionManager.RemoteMode.GDBSERVER_MULTI + return GefRemoteSessionManager.RemoteMode.GDBSERVER + raise AttributeError + + def __init__(self, conn: gdb.RemoteTargetConnection) -> None: super().__init__() - self.__host = host - self.__port = port + assert is_target_remote_or_extended() + remote_host = conn.details + assert remote_host + host, port = remote_host.split(":", 1) + self.__host = host or "localhost" + self.__port = int(port) self.__local_root_fd = tempfile.TemporaryDirectory() self.__local_root_path = pathlib.Path(self.__local_root_fd.name) - self.__qemu = qemu - if pid > 0: - self._pid = pid + self._mode = GefRemoteSessionManager.RemoteMode.init() - if self.__qemu is not None: - self._mode = GefRemoteSessionManager.RemoteMode.QEMU - elif os.environ.get("GDB_UNDER_RR", None) == "1": - self._mode = GefRemoteSessionManager.RemoteMode.RR - else: - self._mode = GefRemoteSessionManager.RemoteMode.GDBSERVER + self.setup() def close(self) -> None: self.__local_root_fd.cleanup() @@ -11475,7 +11529,11 @@ def close(self) -> None: raise def __str__(self) -> str: - return f"RemoteSession(target='{self.target}', local='{self.root}', pid={self.pid}, mode={self.mode})" + msg = f"RemoteSession(target='{self.target}', local='{self.root}', mode={self.mode}" + if self.mode == GefRemoteSessionManager.RemoteMode.GDBSERVER: + msg += f", pid={self.pid}" + msg += ")" + return msg def __repr__(self) -> str: return str(self) @@ -11561,16 +11619,18 @@ def connect(self, pid: int) -> bool: def setup(self) -> bool: # setup remote adequately depending on remote or qemu mode + dbg(f"Setting up the {self._mode} session") match self.mode: - case GefRemoteSessionManager.RemoteMode.QEMU: - dbg(f"Setting up as qemu session, target={self.__qemu}") - self.__setup_qemu() + case GefRemoteSessionManager.RemoteMode.QEMU_USER: + self.__setup_qemu_user() + case GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM: + raise Exception("TODO") case GefRemoteSessionManager.RemoteMode.RR: - dbg("Setting up as rr session") self.__setup_rr() case GefRemoteSessionManager.RemoteMode.GDBSERVER: - dbg("Setting up as remote session") self.__setup_remote() + case GefRemoteSessionManager.RemoteMode.GDBSERVER_MULTI: + pass case _: raise ValueError @@ -11580,13 +11640,13 @@ def setup(self) -> bool: reset_architecture() return True - def __setup_qemu(self) -> bool: + def __setup_qemu_user(self) -> bool: # setup emulated file in the chroot - assert self.__qemu - target = self.root / str(self.__qemu.parent).lstrip("/") + __qemu_target = pathlib.Path("foo") # TODO + target = self.root / str(__qemu_target.parent).lstrip("/") target.mkdir(parents=True, exist_ok=False) - shutil.copy2(self.__qemu, target) - self._file = self.__qemu + shutil.copy2(__qemu_target, target) + self._file = __qemu_target assert self.lfile.exists() # create a procfs @@ -11767,12 +11827,20 @@ def reset_caches(self) -> None: def target_remote_posthook(): - if gef.session.remote_initializing: - return + print(f"{is_target_remote()=}") + print(f"{is_target_remote_or_extended()=}") + print(f"{is_target_extended_remote()=}") + print(f"{is_running_under_qemu()=}") + print(f"{is_running_under_qemu_system()=}") + print(f"{is_running_under_qemu_user()=}") + print(f"{is_running_in_gdbserver()=}") + print(f"{is_running_in_rr()=}") + conn = gdb.selected_inferior().connection + if not isinstance(conn, gdb.RemoteTargetConnection): + raise TypeError("Expected type gdb.RemoteTargetConnection") + assert is_target_remote_or_extended(conn), "Target is not remote" + gef.session.remote = GefRemoteSessionManager(conn) - gef.session.remote = GefRemoteSessionManager("", 0) - if not gef.session.remote.setup(): - raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote}") if __name__ == "__main__": if sys.version_info[0] == 2: @@ -11835,32 +11903,14 @@ def target_remote_posthook(): GefTmuxSetup() - if GDB_VERSION > (9, 0): - disable_tr_overwrite_setting = "gef.disable_target_remote_overwrite" - - if not gef.config[disable_tr_overwrite_setting]: - warnmsg = ("Using `target remote` with GEF should work in most cases, " - "but use `gef-remote` if you can. You can disable the " - "overwrite of the `target remote` command by toggling " - f"`{disable_tr_overwrite_setting}` in the config.") - hook = f""" - define target hookpost-{{}} - pi target_remote_posthook() - context - pi if calling_function() != "connect": warn("{warnmsg}") - end - """ - - # Register a post-hook for `target remote` that initialize the remote session - gdb.execute(hook.format("remote")) - gdb.execute(hook.format("extended-remote")) - else: - errmsg = ("Using `target remote` does not work, use `gef-remote` " - f"instead. You can toggle `{disable_tr_overwrite_setting}` " - "if this is not desired.") - hook = f"""pi if calling_function() != "connect": err("{errmsg}")""" - gdb.execute(f"define target hook-remote\n{hook}\nend") - gdb.execute(f"define target hook-extended-remote\n{hook}\nend") + # Initialize `target *remote` post hooks + hook = """ + define target hookpost-{0} + pi target_remote_posthook() + end + """ + gdb.execute(hook.format("remote")) + gdb.execute(hook.format("extended-remote")) # restore saved breakpoints (if any) bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute() From 6d166f0a088f0098c8703e042ee0ec7af64b6311 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 3 Nov 2024 18:24:41 -0800 Subject: [PATCH 13/38] checkpoint: added very basic pytests --- gef.py | 57 +++++++++++++++------------ tests/api/gef_remote.py | 87 +++++++++++++++++++++++++++++++++++++++++ tests/base.py | 4 +- tests/utils.py | 30 ++++++++++++++ 4 files changed, 150 insertions(+), 28 deletions(-) create mode 100644 tests/api/gef_remote.py diff --git a/gef.py b/gef.py index 41cb61da6..4799c5e69 100644 --- a/gef.py +++ b/gef.py @@ -3530,7 +3530,7 @@ def is_target_remote_or_extended(conn: gdb.TargetConnection | None = None) -> bo return is_target_remote(conn) or is_target_extended_remote(conn) -def is_running_under_qemu() -> bool: +def is_running_in_qemu() -> bool: "See https://www.qemu.org/docs/master/system/gdb.html " if not is_target_remote(): return False @@ -3538,15 +3538,15 @@ def is_running_under_qemu() -> bool: return "ENABLE=" in response -def is_running_under_qemu_user() -> bool: - if not is_running_under_qemu(): +def is_running_in_qemu_user() -> bool: + if not is_running_in_qemu(): return False response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" # Use `qAttached`? return "Text=" in response -def is_running_under_qemu_system() -> bool: - if not is_running_under_qemu(): +def is_running_in_qemu_system() -> bool: + if not is_running_in_qemu(): return False # Use "maintenance packet qqemu.PhyMemMode"? response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" @@ -3554,9 +3554,7 @@ def is_running_under_qemu_system() -> bool: def is_running_in_gdbserver() -> bool: - if is_running_under_qemu(): - return False - return not is_running_under_qemu() + return not is_running_in_qemu() def is_running_in_rr() -> bool: @@ -7447,7 +7445,7 @@ def __init__(self) -> None: def do_invoke(self, _: list[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] - if is_running_under_qemu_system(): + if is_running_in_qemu_system(): err("Unsupported") return @@ -11345,7 +11343,7 @@ def __repr__(self) -> str: def auxiliary_vector(self) -> dict[str, int] | None: if not is_alive(): return None - if is_running_under_qemu_system(): + if is_running_in_qemu_system(): return None if not self._auxiliary_vector: auxiliary_vector = {} @@ -11493,9 +11491,9 @@ def prompt_string(self) -> str: @staticmethod def init() -> "GefRemoteSessionManager.RemoteMode": - if is_running_under_qemu_system(): + if is_running_in_qemu_system(): return GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM - if is_running_under_qemu_user(): + if is_running_in_qemu_user(): return GefRemoteSessionManager.RemoteMode.QEMU_USER if is_running_in_rr(): return GefRemoteSessionManager.RemoteMode.RR @@ -11619,7 +11617,7 @@ def connect(self, pid: int) -> bool: def setup(self) -> bool: # setup remote adequately depending on remote or qemu mode - dbg(f"Setting up the {self._mode} session") + info(f"Setting up remote session as '{self._mode}'") match self.mode: case GefRemoteSessionManager.RemoteMode.QEMU_USER: self.__setup_qemu_user() @@ -11826,21 +11824,24 @@ def reset_caches(self) -> None: return +def target_remote_hook(): + # disable the context until the session has been fully established + gef.config["context.enable"] = False + + def target_remote_posthook(): - print(f"{is_target_remote()=}") - print(f"{is_target_remote_or_extended()=}") - print(f"{is_target_extended_remote()=}") - print(f"{is_running_under_qemu()=}") - print(f"{is_running_under_qemu_system()=}") - print(f"{is_running_under_qemu_user()=}") - print(f"{is_running_in_gdbserver()=}") - print(f"{is_running_in_rr()=}") conn = gdb.selected_inferior().connection if not isinstance(conn, gdb.RemoteTargetConnection): raise TypeError("Expected type gdb.RemoteTargetConnection") assert is_target_remote_or_extended(conn), "Target is not remote" gef.session.remote = GefRemoteSessionManager(conn) + # re-enable context + gef.config["context.enable"] = True + + # if here, no exception was thrown, print context + gdb.execute("context") + if __name__ == "__main__": if sys.version_info[0] == 2: @@ -11903,14 +11904,18 @@ def target_remote_posthook(): GefTmuxSetup() - # Initialize `target *remote` post hooks + # Initialize `target *remote` pre/post hooks hook = """ - define target hookpost-{0} - pi target_remote_posthook() + define target hook{1}-{0} + pi target_remote_{1}hook() end """ - gdb.execute(hook.format("remote")) - gdb.execute(hook.format("extended-remote")) + # pre-hooks + gdb.execute(hook.format("remote", "")) + gdb.execute(hook.format("extended-remote", "")) + # post-hooks + gdb.execute(hook.format("remote", "post")) + gdb.execute(hook.format("extended-remote", "post")) # restore saved breakpoints (if any) bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute() diff --git a/tests/api/gef_remote.py b/tests/api/gef_remote.py new file mode 100644 index 000000000..fceb96546 --- /dev/null +++ b/tests/api/gef_remote.py @@ -0,0 +1,87 @@ +""" +`target remote/extended-remote` test module. +""" + + +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import debug_target, gdbserver_session, gdbserver_multi_session, get_random_port, qemuuser_session + + +class GefRemoteApi(RemoteGefUnitTestGeneric): + + def setUp(self) -> None: + self._target = debug_target("default") + return super().setUp() + + def test_gef_remote_test_gdbserver(self): + """Test `gdbserver file`""" + _gdb = self._gdb + _root = self._conn.root + port = get_random_port() + + with gdbserver_session(port=port): + assert not _root.eval("is_target_remote()") + assert not _root.eval("is_target_remote_or_extended()") + assert not _root.eval("is_running_in_gdbserver()") + + _gdb.execute(f"target remote :{port}") + + assert _root.eval("is_target_remote()") + assert _root.eval("is_target_remote_or_extended()") + assert _root.eval("is_running_in_gdbserver()") + + assert not _root.eval("is_target_extended_remote()") + assert not _root.eval("is_running_under_qemu()") + assert not _root.eval("is_running_under_qemu_system()") + assert not _root.eval("is_running_under_qemu_user()") + assert not _root.eval("is_running_in_rr()") + + def test_gef_remote_test_gdbserver_multi(self): + """Test `gdbserver --multi file`""" + _gdb = self._gdb + _root = self._conn.root + port = get_random_port() + + with gdbserver_multi_session(port=port): + assert not _root.eval("is_target_remote()") + assert not _root.eval("is_target_remote_or_extended()") + assert not _root.eval("is_running_in_gdbserver()") + + _gdb.execute(f"target extended-remote :{port}") + + assert _root.eval("is_target_remote()") + assert _root.eval("is_target_remote_or_extended()") + assert _root.eval("is_target_extended_remote()") + assert _root.eval("is_running_in_gdbserver()") + + assert not _root.eval("is_running_under_qemu()") + assert not _root.eval("is_running_under_qemu_system()") + assert not _root.eval("is_running_under_qemu_user()") + assert not _root.eval("is_running_in_rr()") + + def test_gef_remote_test_qemuuser(self): + """Test `qemu-user -g`""" + _gdb = self._gdb + _root = self._conn.root + port = get_random_port() + + with qemuuser_session(port=port): + assert not _root.eval("is_target_remote()") + assert not _root.eval("is_target_remote_or_extended()") + assert not _root.eval("is_running_in_gdbserver()") + + _gdb.execute(f"target remote :{port}") + + assert _root.eval("is_target_remote()") + assert _root.eval("is_target_remote_or_extended()") + assert _root.eval("is_running_under_qemu()") + assert _root.eval("is_running_under_qemu_user()") + + assert not _root.eval("is_target_extended_remote()") + assert not _root.eval("is_running_under_qemu_system()") + assert not _root.eval("is_running_in_gdbserver()") + assert not _root.eval("is_running_in_rr()") + + # TODO add tests for + # - [ ] qemu-system + # - [ ] rr diff --git a/tests/base.py b/tests/base.py index 868923d3a..e1584eaf7 100644 --- a/tests/base.py +++ b/tests/base.py @@ -10,7 +10,7 @@ import rpyc -from .utils import debug_target +from .utils import debug_target, get_random_port COVERAGE_DIR = os.getenv("COVERAGE_DIR", "") GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")).absolute() @@ -58,7 +58,7 @@ def __setup(self): # # Select a random tcp port for rpyc # - self._port = random.randint(1025, 65535) + self._port = get_random_port() self._commands = "" if COVERAGE_DIR: diff --git a/tests/utils.py b/tests/utils.py index f88d3b304..8b9247758 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -12,6 +12,7 @@ import subprocess import tempfile import time +import random from typing import Iterable, List, Optional, Union from urllib.request import urlopen @@ -112,6 +113,13 @@ def start_gdbserver( logging.debug(f"Starting {cmd}") return subprocess.Popen(cmd) +def start_gdbserver_multi( + host: str = GDBSERVER_DEFAULT_HOST, + port: int = GDBSERVER_DEFAULT_PORT, +) -> subprocess.Popen: + cmd = [GDBSERVER_BINARY, "--multi", f"{host}:{port}"] + logging.debug(f"Starting {cmd}") + return subprocess.Popen(cmd) def stop_gdbserver(gdbserver: subprocess.Popen) -> None: """Stop the gdbserver and wait until it is terminated if it was @@ -138,6 +146,17 @@ def gdbserver_session( finally: stop_gdbserver(sess) +@contextlib.contextmanager +def gdbserver_multi_session( + port: int = GDBSERVER_DEFAULT_PORT, + host: str = GDBSERVER_DEFAULT_HOST, +): + sess = start_gdbserver_multi(host, port) + try: + 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"), @@ -301,3 +320,14 @@ def p32(x: int) -> bytes: def p64(x: int) -> bytes: return struct.pack(" int: + global __available_ports + if len(__available_ports) < 2: + __available_ports = list( range(1024, 65535) ) + idx = random.choice(range(len(__available_ports))) + port = __available_ports[idx] + __available_ports.pop(idx) + return port From 771a598f649e8162477902759dbc968cc079c2e2 Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 4 Nov 2024 17:19:29 -0800 Subject: [PATCH 14/38] removed all obsolete code --- gef.py | 128 +++++------------------------------ tests/api/gef_remote.py | 41 ++++++++--- tests/commands/gef_remote.py | 45 +++++------- tests/utils.py | 7 +- 4 files changed, 70 insertions(+), 151 deletions(-) diff --git a/gef.py b/gef.py index 4799c5e69..092257cff 100644 --- a/gef.py +++ b/gef.py @@ -3554,13 +3554,12 @@ def is_running_in_qemu_system() -> bool: def is_running_in_gdbserver() -> bool: - return not is_running_in_qemu() + return is_target_remote_or_extended() and not is_running_in_qemu() def is_running_in_rr() -> bool: - if not is_running_in_gdbserver(): - return False - return os.environ.get("GDB_UNDER_RR", None) == "1" + return is_running_in_gdbserver() and \ + os.environ.get("GDB_UNDER_RR", None) == "1" def get_filepath() -> str | None: @@ -3774,7 +3773,7 @@ def exit_handler(_: "gdb.ExitedEvent") -> None: with bkp_fpath.open("w") as fd: for bp in list(gdb.breakpoints()): - if not bp.enabled or not bp.is_valid: + if not bp.enabled or not bp.is_valid(): continue fd.write(f"{'t' if bp.temporary else ''}break {bp.location}\n") return @@ -11519,12 +11518,6 @@ def __init__(self, conn: gdb.RemoteTargetConnection) -> None: def close(self) -> None: self.__local_root_fd.cleanup() - try: - gef_on_new_unhook(self.remote_objfile_event_handler) - gef_on_new_hook(new_objfile_handler) - except Exception as e: - warn(f"Exception while restoring local context: {str(e)}") - raise def __str__(self) -> str: msg = f"RemoteSession(target='{self.target}', local='{self.root}', mode={self.mode}" @@ -11574,46 +11567,10 @@ def mode(self) -> RemoteMode: return self._mode def sync(self, src: str, dst: str | None = None) -> bool: - """Copy the `src` into the temporary chroot. If `dst` is provided, that path will be - used instead of `src`.""" - if not dst: - dst = src - tgt = self.root / dst.lstrip("/") - if tgt.exists(): - return True - tgt.parent.mkdir(parents=True, exist_ok=True) - dbg(f"[remote] downloading '{src}' -> '{tgt}'") - gdb.execute(f"remote get '{src}' '{tgt.absolute()}'") - return tgt.exists() + raise DeprecationWarning def connect(self, pid: int) -> bool: - """Connect to remote target. If in extended mode, also attach to the given PID.""" - # before anything, register our new hook to download files from the remote target - dbg("[remote] Installing new objfile handlers") - try: - gef_on_new_unhook(new_objfile_handler) - except SystemError: - # the default objfile handler might already have been removed, ignore failure - pass - - gef_on_new_hook(self.remote_objfile_event_handler) - - # then attempt to connect - is_extended_mode = (pid > -1) - dbg(f"[remote] Enabling extended remote: {bool(is_extended_mode)}") - try: - with DisableContextOutputContext(): - cmd = f"target {'extended-' if is_extended_mode else ''}remote {self.target}" - dbg(f"[remote] Executing '{cmd}'") - gdb.execute(cmd) - if is_extended_mode: - gdb.execute(f"attach {pid:d}") - return True - except Exception as e: - err(f"Failed to connect to {self.target}: {e}") - - # a failure will trigger the cleanup, deleting our hook anyway - return False + raise DeprecationWarning def setup(self) -> bool: # setup remote adequately depending on remote or qemu mode @@ -11622,13 +11579,13 @@ def setup(self) -> bool: case GefRemoteSessionManager.RemoteMode.QEMU_USER: self.__setup_qemu_user() case GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM: - raise Exception("TODO") + self.__setup_qemu_system() case GefRemoteSessionManager.RemoteMode.RR: self.__setup_rr() case GefRemoteSessionManager.RemoteMode.GDBSERVER: self.__setup_remote() case GefRemoteSessionManager.RemoteMode.GDBSERVER_MULTI: - pass + self.__setup_remote_multi() case _: raise ValueError @@ -11638,77 +11595,26 @@ def setup(self) -> bool: reset_architecture() return True + def __setup_qemu_system(self) -> bool: + raise Exception("TODO") + return True + def __setup_qemu_user(self) -> bool: - # setup emulated file in the chroot - __qemu_target = pathlib.Path("foo") # TODO - target = self.root / str(__qemu_target.parent).lstrip("/") - target.mkdir(parents=True, exist_ok=False) - shutil.copy2(__qemu_target, target) - self._file = __qemu_target - assert self.lfile.exists() - - # create a procfs - procfs = self.root / f"proc/{self.pid}/" - procfs.mkdir(parents=True, exist_ok=True) - - ## /proc/pid/cmdline - cmdline = procfs / "cmdline" - if not cmdline.exists(): - with cmdline.open("w") as fd: - fd.write("") - - ## /proc/pid/environ - environ = procfs / "environ" - if not environ.exists(): - with environ.open("wb") as fd: - fd.write(b"PATH=/bin\x00HOME=/tmp\x00") - - ## /proc/pid/maps - maps = procfs / "maps" - if not maps.exists(): - with maps.open("w") as fd: - fname = self.file.absolute() - mem_range = "00000000-ffffffff" if is_32bit() else "0000000000000000-ffffffffffffffff" - fd.write(f"{mem_range} rwxp 00000000 00:00 0 {fname}\n") + self.__local_root_path = pathlib.Path("/") return True def __setup_remote(self) -> bool: - # get the file - fpath = f"/proc/{self.pid}/exe" - if not self.sync(fpath, str(self.file)): - err(f"'{fpath}' could not be fetched on the remote system.") - return False - - # pseudo procfs - for _file in ("maps", "environ", "cmdline"): - fpath = f"/proc/{self.pid}/{_file}" - if not self.sync(fpath): - err(f"'{fpath}' could not be fetched on the remote system.") - return False + self.__local_root_path = pathlib.Path("/") + return True + def __setup_remote_multi(self) -> bool: + self.__local_root_path = pathlib.Path("/") return True def __setup_rr(self) -> bool: - # - # Simply override the local root path, the binary must exist - # on the host. - # self.__local_root_path = pathlib.Path("/") return True - def remote_objfile_event_handler(self, evt: "gdb.NewObjFileEvent") -> None: - dbg(f"[remote] in remote_objfile_handler({evt.new_objfile.filename if evt else 'None'}))") - if not evt or not evt.new_objfile.filename: - return - if not evt.new_objfile.filename.startswith("target:") and not evt.new_objfile.filename.startswith("/"): - warn(f"[remote] skipping '{evt.new_objfile.filename}'") - return - if evt.new_objfile.filename.startswith("target:"): - src: str = evt.new_objfile.filename[len("target:"):] - if not self.sync(src): - raise FileNotFoundError(f"Failed to sync '{src}'") - return - class GefUiManager(GefManager): """Class managing UI settings.""" diff --git a/tests/api/gef_remote.py b/tests/api/gef_remote.py index fceb96546..35e673f6a 100644 --- a/tests/api/gef_remote.py +++ b/tests/api/gef_remote.py @@ -16,6 +16,7 @@ def setUp(self) -> None: def test_gef_remote_test_gdbserver(self): """Test `gdbserver file`""" _gdb = self._gdb + _gef = self._gef _root = self._conn.root port = get_random_port() @@ -23,22 +24,30 @@ def test_gef_remote_test_gdbserver(self): assert not _root.eval("is_target_remote()") assert not _root.eval("is_target_remote_or_extended()") assert not _root.eval("is_running_in_gdbserver()") + assert not _root.eval("is_running_in_rr()") + assert not _root.eval("is_running_in_qemu()") _gdb.execute(f"target remote :{port}") assert _root.eval("is_target_remote()") assert _root.eval("is_target_remote_or_extended()") assert _root.eval("is_running_in_gdbserver()") + assert _root.eval("is_running_in_gdbserver()") assert not _root.eval("is_target_extended_remote()") - assert not _root.eval("is_running_under_qemu()") - assert not _root.eval("is_running_under_qemu_system()") - assert not _root.eval("is_running_under_qemu_user()") + assert not _root.eval("is_running_in_qemu()") + assert not _root.eval("is_running_in_qemu_system()") + assert not _root.eval("is_running_in_qemu_user()") assert not _root.eval("is_running_in_rr()") + assert hasattr(_gef.session, "remote") + assert "GDBSERVER" in str(_gef.session.remote) + assert "GDBSERVER_MULTI" not in str(_gef.session.remote) + def test_gef_remote_test_gdbserver_multi(self): """Test `gdbserver --multi file`""" _gdb = self._gdb + _gef = self._gef _root = self._conn.root port = get_random_port() @@ -46,22 +55,31 @@ def test_gef_remote_test_gdbserver_multi(self): assert not _root.eval("is_target_remote()") assert not _root.eval("is_target_remote_or_extended()") assert not _root.eval("is_running_in_gdbserver()") + assert not _root.eval("is_running_in_rr()") + assert not _root.eval("is_running_in_qemu()") _gdb.execute(f"target extended-remote :{port}") + _gdb.execute(f"set remote exec-file {self._target}") + _gdb.execute(f"file {self._target}") + _gdb.execute(f"start {self._target}") - assert _root.eval("is_target_remote()") assert _root.eval("is_target_remote_or_extended()") assert _root.eval("is_target_extended_remote()") assert _root.eval("is_running_in_gdbserver()") + assert not _root.eval("is_target_remote()") - assert not _root.eval("is_running_under_qemu()") - assert not _root.eval("is_running_under_qemu_system()") - assert not _root.eval("is_running_under_qemu_user()") + assert not _root.eval("is_running_in_qemu()") + assert not _root.eval("is_running_in_qemu_system()") + assert not _root.eval("is_running_in_qemu_user()") assert not _root.eval("is_running_in_rr()") + assert hasattr(_gef.session, "remote") + assert "GDBSERVER_MULTI" in str(_gef.session.remote) + def test_gef_remote_test_qemuuser(self): """Test `qemu-user -g`""" _gdb = self._gdb + _gef = self._gef _root = self._conn.root port = get_random_port() @@ -74,14 +92,17 @@ def test_gef_remote_test_qemuuser(self): assert _root.eval("is_target_remote()") assert _root.eval("is_target_remote_or_extended()") - assert _root.eval("is_running_under_qemu()") - assert _root.eval("is_running_under_qemu_user()") + assert _root.eval("is_running_in_qemu()") + assert _root.eval("is_running_in_qemu_user()") assert not _root.eval("is_target_extended_remote()") - assert not _root.eval("is_running_under_qemu_system()") + assert not _root.eval("is_running_in_qemu_system()") assert not _root.eval("is_running_in_gdbserver()") assert not _root.eval("is_running_in_rr()") + assert hasattr(_gef.session, "remote") + assert "QEMU_USER" in str(_gef.session.remote) + # TODO add tests for # - [ ] qemu-system # - [ ] rr diff --git a/tests/commands/gef_remote.py b/tests/commands/gef_remote.py index 34fd0ab60..e5e1e54b2 100644 --- a/tests/commands/gef_remote.py +++ b/tests/commands/gef_remote.py @@ -11,6 +11,7 @@ ARCH, debug_target, gdbserver_session, + get_random_port, qemuuser_session, GDBSERVER_DEFAULT_HOST, ) @@ -26,50 +27,40 @@ def setUp(self) -> None: def test_cmd_gef_remote_gdbserver(self): gdb = self._gdb gef = self._gef - root = self._conn.root + port = get_random_port() gdbserver_mode = "GDBSERVER" - while True: - port = random.randint(1025, 65535) - if port != self._port: - break with gdbserver_session(port=port): - gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") - res: str = root.eval("str(gef.session.remote)") - assert res.startswith(f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/tmp/") - assert res.endswith(f"pid={gef.session.pid}, mode={gdbserver_mode})") + gdb.execute(f"target remote {GDBSERVER_DEFAULT_HOST}:{port}") + res: str = str(gef.session.remote) + assert res.startswith(f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/") + assert res.endswith(f"mode={gdbserver_mode}, pid={gef.session.pid})") @pytest.mark.slow @pytest.mark.skipif(ARCH not in ("x86_64",), reason=f"Skipped for {ARCH}") def test_cmd_gef_remote_qemu_user(self): gdb = self._gdb gef = self._gef - root = self._conn.root - qemu_mode = "QEMU" - while True: - port = random.randint(1025, 65535) - if port != self._port: - break + qemu_mode = "QEMU_USER" + port = get_random_port() with qemuuser_session(port=port): - cmd = f"gef-remote --qemu-user --qemu-binary {self._target} {GDBSERVER_DEFAULT_HOST} {port}" + cmd = f"target remote {GDBSERVER_DEFAULT_HOST}:{port}" gdb.execute(cmd) - res = root.eval("str(gef.session.remote)") - assert res.startswith(f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/tmp/") - assert res.endswith(f"pid={gef.session.pid}, mode={qemu_mode})") + res = str(gef.session.remote) + assert res.startswith(f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/") + assert res.endswith(f"mode={qemu_mode})") def test_cmd_target_remote(self): gdb = self._gdb gef = self._gef - root = self._conn.root gdbserver_mode = "GDBSERVER" - while True: - port = random.randint(1025, 65535) - if port != self._port: - break + port = get_random_port() with gdbserver_session(port=port) as _: gdb.execute(f"target remote {GDBSERVER_DEFAULT_HOST}:{port}") - res: str = root.eval("str(gef.session.remote)") - assert res.startswith("RemoteSession(target=':0', local='/tmp/") - assert res.endswith(f"pid={gef.session.pid}, mode={gdbserver_mode})") + res: str = str(gef.session.remote) + assert res.startswith( + f"RemoteSession(target='{GDBSERVER_DEFAULT_HOST}:{port}', local='/" + ) + assert res.endswith(f"mode={gdbserver_mode}, pid={gef.session.pid})") diff --git a/tests/utils.py b/tests/utils.py index 8b9247758..ee15db5b8 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -42,7 +42,8 @@ def which(program: str) -> pathlib.Path: STRIP_ANSI_DEFAULT = True GDBSERVER_DEFAULT_HOST = "localhost" GDBSERVER_DEFAULT_PORT = 1234 -GDBSERVER_BINARY = which("gdbserver") +GDBSERVER_BINARY: pathlib.Path = which("gdbserver") +GDBSERVER_STARTUP_DELAY_SEC : float = 0.5 assert GDBSERVER_BINARY.exists() QEMU_USER_X64_BINARY = which("qemu-x86_64") @@ -141,7 +142,7 @@ def gdbserver_session( ): sess = start_gdbserver(exe, host, port) try: - time.sleep(1) # forced delay to allow gdbserver to start listening + time.sleep(GDBSERVER_STARTUP_DELAY_SEC) yield sess finally: stop_gdbserver(sess) @@ -153,7 +154,7 @@ def gdbserver_multi_session( ): sess = start_gdbserver_multi(host, port) try: - time.sleep(1) # forced delay to allow gdbserver to start listening + time.sleep(GDBSERVER_STARTUP_DELAY_SEC) yield sess finally: stop_gdbserver(sess) From 79447be3f1879a63eabb659010ec0f9e6f29bffe Mon Sep 17 00:00:00 2001 From: hugsy Date: Mon, 4 Nov 2024 17:40:26 -0800 Subject: [PATCH 15/38] fixed `gef-remote` from tests, using only `target remote` --- tests/api/gef_memory.py | 30 +++++++---------------- tests/api/gef_session.py | 15 +++++------- tests/regressions/gdbserver_connection.py | 4 +-- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/tests/api/gef_memory.py b/tests/api/gef_memory.py index 6b0b6760d..d77ec0f1a 100644 --- a/tests/api/gef_memory.py +++ b/tests/api/gef_memory.py @@ -12,6 +12,7 @@ ARCH, debug_target, gdbserver_session, + get_random_port, qemuuser_session, GDBSERVER_DEFAULT_HOST, ) @@ -121,48 +122,35 @@ def test_func_parse_maps_local_procfs(self): @pytest.mark.slow def test_func_parse_maps_remote_gdbserver(self): gef, gdb = self._gef, self._gdb - # When in a gef-remote session `parse_gdb_info_proc_maps` should work to + # When in a remote session `parse_gdb_info_proc_maps` should work to # query the memory maps - while True: - port = random.randint(1025, 65535) - if port != self._port: - break + port = get_random_port() with pytest.raises(Exception): - gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") + gdb.execute(f"target remote :{port}") with gdbserver_session(port=port) as _: - gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") + gdb.execute(f"target remote :{port}") sections = gef.memory.maps assert len(sections) > 0 @pytest.mark.skipif(ARCH not in ("x86_64",), reason=f"Skipped for {ARCH}") def test_func_parse_maps_remote_qemu(self): gdb, gef = self._gdb, self._gef - # When in a gef-remote qemu-user session `parse_gdb_info_proc_maps` - # should work to query the memory maps - while True: - port = random.randint(1025, 65535) - if port != self._port: - break + port = get_random_port() with qemuuser_session(port=port) as _: - cmd = f"gef-remote --qemu-user --qemu-binary {self._target} {GDBSERVER_DEFAULT_HOST} {port}" + cmd = f"target remote :{port}" gdb.execute(cmd) sections = gef.memory.maps assert len(sections) > 0 def test_func_parse_maps_realpath(self): gef, gdb = self._gef, self._gdb - # When in a gef-remote session `parse_gdb_info_proc_maps` should work to - # query the memory maps - while True: - port = random.randint(1025, 65535) - if port != self._port: - break + port = get_random_port() with gdbserver_session(port=port) as _: - gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}") + gdb.execute(f"target remote :{port}") gdb.execute("b main") gdb.execute("continue") sections = gef.memory.maps diff --git a/tests/api/gef_session.py b/tests/api/gef_session.py index a24c04370..5ebc8a0c1 100644 --- a/tests/api/gef_session.py +++ b/tests/api/gef_session.py @@ -14,6 +14,7 @@ ARCH, debug_target, gdbserver_session, + get_random_port, qemuuser_session, GDBSERVER_DEFAULT_HOST, ) @@ -63,12 +64,11 @@ def test_root_dir_local(self): def test_root_dir_remote(self): gdb = self._gdb gdb.execute("start") - expected = os.stat("/") - host = GDBSERVER_DEFAULT_HOST - port = random.randint(1025, 65535) + port = get_random_port() + with gdbserver_session(port=port): - gdb.execute(f"gef-remote {host} {port}") + gdb.execute(f"target remote :{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 @@ -77,11 +77,8 @@ def test_root_dir_remote(self): @pytest.mark.skipif(ARCH not in ("x86_64",), reason=f"Skipped for {ARCH}") def test_root_dir_qemu(self): gdb, gef = self._gdb, self._gef + port = get_random_port() - host = GDBSERVER_DEFAULT_HOST - port = random.randint(1025, 65535) with qemuuser_session(port=port): - gdb.execute( - f"gef-remote --qemu-user --qemu-binary {self._target} {host} {port}" - ) + gdb.execute(f"target remote :{port}") assert re.search(r"\/proc\/[0-9]+/root", str(gef.session.root)) diff --git a/tests/regressions/gdbserver_connection.py b/tests/regressions/gdbserver_connection.py index 6c46d9659..244b4e55b 100644 --- a/tests/regressions/gdbserver_connection.py +++ b/tests/regressions/gdbserver_connection.py @@ -10,8 +10,8 @@ def test_can_establish_connection_to_gdbserver_again_after_disconnect(self): gdb = self._gdb with gdbserver_session(port=5001) as _, gdbserver_session(port=5002) as _: - gdb.execute("gef-remote 127.0.0.1 5001") + gdb.execute("target remote :5001") gdb.execute("detach") - gdb.execute("gef-remote 127.0.0.1 5002") + gdb.execute("target remote :5002") gdb.execute("continue") From ea873bbdfedacb15716ca2645c9e56c0cccaec0b Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Wed, 6 Nov 2024 20:06:25 -0800 Subject: [PATCH 16/38] use `NotImplementedError` --- gef.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gef.py b/gef.py index 092257cff..de041e3cc 100644 --- a/gef.py +++ b/gef.py @@ -11596,8 +11596,7 @@ def setup(self) -> bool: return True def __setup_qemu_system(self) -> bool: - raise Exception("TODO") - return True + raise NotImplementedError("TODO") def __setup_qemu_user(self) -> bool: self.__local_root_path = pathlib.Path("/") From 4ae683f8bd73231258892779cf27eab3550cc959 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Wed, 6 Nov 2024 20:22:56 -0800 Subject: [PATCH 17/38] plop --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 416626e93..c1a3a32b3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,7 @@ jobs: matrix: runner: [ubuntu-24.04, ubuntu-22.04] - name: "Run Unit tests on ${{ matrix.runner }}" + name: "Tests/${{ matrix.runner }}" runs-on: ${{ matrix.runner }} defaults: run: From 8130895c3b9edf6efb59c25cc726671c1a7073af Mon Sep 17 00:00:00 2001 From: hugsy Date: Wed, 6 Nov 2024 21:21:18 -0800 Subject: [PATCH 18/38] minor lint --- gef.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gef.py b/gef.py index de041e3cc..23f696776 100644 --- a/gef.py +++ b/gef.py @@ -3723,6 +3723,8 @@ def new_objfile_handler(evt: "gdb.NewObjFileEvent | None") -> None: path = progspace.filename else: raise RuntimeError("Cannot determine file path") + + assert path try: if gef.session.root and path.startswith("target:"): # If the process is in a container, replace the "target:" prefix @@ -11385,7 +11387,7 @@ def file(self) -> pathlib.Path | None: return self.remote.file progspace = gdb.current_progspace() assert progspace - fpath: str = progspace.filename + fpath: str = progspace.filename or "" if fpath and not self._file: self._file = pathlib.Path(fpath).expanduser() return self._file @@ -11548,7 +11550,7 @@ def file(self) -> pathlib.Path: if not filename: raise RuntimeError("No session started") start_idx = len("target:") if filename.startswith("target:") else 0 - self._file = pathlib.Path(progspace.filename[start_idx:]) + self._file = pathlib.Path(filename[start_idx:]) return self._file @property From 7973a3f279198965f3919b27b0d702dde7ff9cd1 Mon Sep 17 00:00:00 2001 From: hugsy Date: Wed, 6 Nov 2024 21:58:12 -0800 Subject: [PATCH 19/38] allow mock mem layout for old qemu versions --- gef.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/gef.py b/gef.py index 23f696776..1f017b6fe 100644 --- a/gef.py +++ b/gef.py @@ -10859,16 +10859,32 @@ def __parse_maps(self) -> list[Section] | None: try: return list(self.parse_gdb_info_proc_maps()) - except Exception: - pass + except Exception as e: + dbg(f"parse_gdb_info_proc_maps() failed, reason: {str(e)}") try: return list(self.parse_procfs_maps()) - except Exception: - pass + except Exception as e: + dbg(f"parse_procfs_maps() failed, reason: {str(e)}") try: return list(self.parse_monitor_info_mem()) + except Exception as e: + dbg(f"parse_monitor_info_mem() failed, reason: {str(e)}") + + try: + # as a very last resort, use a mock rwx memory layout only if a session is running + assert gef.binary and gef.session.pid + warn("Could not determine memory layout accurately, using mock layout") + fname = gef.binary.path + if is_32bit(): + page_start, page_end = 0x00000000, 0xffffffff + else: + page_start, page_end = 0x0000000000000000, 0xffffffffffffffff + return [Section(page_start=page_start, + page_end=page_end, + permission = Permission.ALL, + path = str(fname)),] except Exception: pass From 8cc223183ee53a722e478e75b28b3a997535799f Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 7 Nov 2024 17:56:40 -0800 Subject: [PATCH 20/38] added repr for Gef class --- gef.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gef.py b/gef.py index 1f017b6fe..dddc2f945 100644 --- a/gef.py +++ b/gef.py @@ -11724,6 +11724,12 @@ def __init__(self) -> None: def __str__(self) -> str: return f"Gef(binary='{self.binary or 'None'}', arch={self.arch})" + def __repr__(self) -> str: + binary = self.binary + arch = self.arch + session = self.session + return f"Gef({binary=:}, {arch=:}, {session=:})" + def reinitialize_managers(self) -> None: """Reinitialize the managers. Avoid calling this function directly, using `pi reset()` is preferred""" self.memory = GefMemoryManager() From 2ba9ac715a693f11a4c31162b2f363f87ad8f7f3 Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 7 Nov 2024 17:56:57 -0800 Subject: [PATCH 21/38] [tests] use `gdb-multiarch` by default --- tests/base.py | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/tests/base.py b/tests/base.py index e1584eaf7..07248174c 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,16 +1,14 @@ import os import pathlib -import random import re import subprocess -import tempfile import time import unittest import rpyc -from .utils import debug_target, get_random_port +from .utils import debug_target, get_random_port, which COVERAGE_DIR = os.getenv("COVERAGE_DIR", "") GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")).absolute() @@ -19,6 +17,7 @@ RPYC_PORT = 18812 RPYC_SPAWN_TIME = 1.0 RPYC_MAX_REMOTE_CONNECTION_ATTEMPTS = 5 +GDB_BINARY_PATH = which("gdb-multiarch") class RemoteGefUnitTestGeneric(unittest.TestCase): @@ -28,6 +27,7 @@ class RemoteGefUnitTestGeneric(unittest.TestCase): """ def setUp(self) -> None: + self._gdb_path = GDB_BINARY_PATH attempt = RPYC_MAX_REMOTE_CONNECTION_ATTEMPTS while True: try: @@ -71,25 +71,20 @@ def __setup(self): pi cov.start() """ - 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}) -""" - - self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) - self._initfile.write(self._commands) - self._initfile.flush() + # 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}", + # fmt: off + self._gdb_path, "-q", "-nx", + "-ex", f"source {GEF_PATH}", + "-ex", "gef config gef.debug True", + "-ex", "gef config gef.propagate_debug_exception True", + "-ex", "gef config gef.disable_color True", + "-ex", f"source {RPYC_GEF_PATH}", + "-ex", f"pi start_rpyc_service({self._port})", "--", + # fmt: off str(self._target.absolute()), # type: ignore pylint: disable=E1101 ] self._process = subprocess.Popen(self._command) From 437cbe213a61a1f0f098bfb342512d475896f959 Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 7 Nov 2024 17:57:18 -0800 Subject: [PATCH 22/38] added regression for issue #1131 --- tests/commands/gef_remote.py | 2 - .../1131_target_remote_registers.py | 39 +++++++++++++++++++ tests/utils.py | 34 +++++++++++----- 3 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 tests/regressions/1131_target_remote_registers.py diff --git a/tests/commands/gef_remote.py b/tests/commands/gef_remote.py index e5e1e54b2..2f8d5d299 100644 --- a/tests/commands/gef_remote.py +++ b/tests/commands/gef_remote.py @@ -2,8 +2,6 @@ `gef_remote` command test module """ -import random - import pytest from tests.base import RemoteGefUnitTestGeneric diff --git a/tests/regressions/1131_target_remote_registers.py b/tests/regressions/1131_target_remote_registers.py new file mode 100644 index 000000000..8d6512b79 --- /dev/null +++ b/tests/regressions/1131_target_remote_registers.py @@ -0,0 +1,39 @@ +import pathlib +import pytest +import os +import tempfile + +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import get_random_port, qemuuser_session + +URL = "https://github.com/user-attachments/files/16913262/repr.zip" + +@pytest.mark.slow +class MissingTargetRemoteRegisters(RemoteGefUnitTestGeneric): + """@ref https://github.com/hugsy/gef/pull/1131""" + + def setUp(self) -> None: + repro_script = f""" + wget -O {{0}}/repr.zip {URL} + unzip {{0}}/repr.zip -d {{0}} + """ + + self._tempdir = tempfile.TemporaryDirectory(prefix="gef-tests-") + self._tempdir_path = pathlib.Path(self._tempdir.name) + os.system(repro_script.format(self._tempdir_path)) + self._current_dir = self._tempdir_path / "repr" + os.chdir(self._current_dir) + self._target = self._current_dir / "chal" + return super().setUp() + + def test_target_remote_validate_post_hook_registers_display(self): + _gdb = self._gdb + _gef = self._gef + port = get_random_port() + + # cmd: ./qemu-mipsel-static -g 1234 -L ./target ./chal + with qemuuser_session(exe=self._target, port=port, qemu_exe=self._current_dir / "qemu-mipsel-static", args=["-L", str(self._current_dir / "target")]): + _gdb.execute(f"target remote :{port}") + + res = str(_gef.session.remote) + assert f"RemoteSession(target='localhost:{port}', local='/', mode=QEMU_USER)" in res diff --git a/tests/utils.py b/tests/utils.py index ee15db5b8..ab3f488a1 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -8,6 +8,7 @@ import os import pathlib import platform +import shutil import struct import subprocess import tempfile @@ -19,12 +20,10 @@ def which(program: str) -> pathlib.Path: - for path in os.environ["PATH"].split(os.pathsep): - dirname = pathlib.Path(path) - fpath = dirname / program - if os.access(fpath, os.X_OK): - return fpath - raise FileNotFoundError(f"Missing file `{program}`") + fpath = shutil.which(program) + if not fpath: + raise FileNotFoundError(f"Missing file `{program}`") + return pathlib.Path(fpath) TMPDIR = pathlib.Path(tempfile.gettempdir()) @@ -162,9 +161,16 @@ def gdbserver_multi_session( def start_qemuuser( exe: Union[str, pathlib.Path] = debug_target("default"), port: int = GDBSERVER_DEFAULT_PORT, + qemu_exe: pathlib.Path = QEMU_USER_X64_BINARY, + args: list[str] | None = None ) -> subprocess.Popen: + cmd = [qemu_exe, "-g", str(port)] + if args: + cmd.extend(args) + cmd.append(exe) + logging.info(f"Starting '{cmd}' in {qemu_exe} on :{port}") return subprocess.Popen( - [QEMU_USER_X64_BINARY, "-g", str(port), exe], + cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) @@ -179,9 +185,19 @@ def stop_qemuuser(process: subprocess.Popen) -> None: @contextlib.contextmanager def qemuuser_session(*args, **kwargs): exe = kwargs.get("exe", "") or debug_target("default") - port = kwargs.get("port", 0) or GDBSERVER_DEFAULT_PORT - sess = start_qemuuser(exe, port) + port = kwargs.get("port", GDBSERVER_DEFAULT_PORT) + qemu_exe = kwargs.get("qemu_exe", None) or QEMU_USER_X64_BINARY + args = kwargs.get("args", None) + if args: + # if specified, expect a list of strings + assert isinstance(args, list) + assert len(args) + for arg in args: + assert isinstance(arg, str) + + sess = start_qemuuser(exe, port=port, qemu_exe=qemu_exe, args=args) try: + time.sleep(GDBSERVER_STARTUP_DELAY_SEC) yield sess finally: stop_qemuuser(sess) From e5a02b1bbded56cc8afe0df479b7321b0a513350 Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 7 Nov 2024 18:04:31 -0800 Subject: [PATCH 23/38] constantify all the things --- tests/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/base.py b/tests/base.py index 07248174c..2752ea361 100644 --- a/tests/base.py +++ b/tests/base.py @@ -18,6 +18,7 @@ RPYC_SPAWN_TIME = 1.0 RPYC_MAX_REMOTE_CONNECTION_ATTEMPTS = 5 GDB_BINARY_PATH = which("gdb-multiarch") +RPYC_CONNECT_FAILURE_DELAY = 0.2 class RemoteGefUnitTestGeneric(unittest.TestCase): @@ -41,7 +42,7 @@ def setUp(self) -> None: attempt -= 1 if attempt == 0: raise - time.sleep(0.2) + time.sleep(RPYC_CONNECT_FAILURE_DELAY) continue self._gdb = self._conn.root.gdb From 68fcc1d007c5c00888f633a243e26c704174f346 Mon Sep 17 00:00:00 2001 From: hugsy Date: Thu, 7 Nov 2024 18:16:39 -0800 Subject: [PATCH 24/38] officially deprecating `gef-remote` --- docs/commands/gef-remote.md | 13 +++------- gef.py | 52 +++++++++---------------------------- 2 files changed, 15 insertions(+), 50 deletions(-) diff --git a/docs/commands/gef-remote.md b/docs/commands/gef-remote.md index 917601dd9..77cc79308 100644 --- a/docs/commands/gef-remote.md +++ b/docs/commands/gef-remote.md @@ -1,15 +1,8 @@ ## Command `gef-remote` -[`target remote`](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Debugging.html#Remote-Debugging) -is the traditional GDB way of debugging process or system remotely. However this command by itself -does a limited job (80's bandwith FTW) to collect more information about the target, making the -process of debugging more cumbersome. GEF greatly improves that state with the `gef-remote` command. - -📝 **Note**: If using GEF, `gef-remote` **must** be your way or debugging remote processes, never -`target remote`. Maintainers will provide minimal support or help if you decide to use the -traditional `target remote` command. For many reasons, you **should not** use `target remote` alone -with GEF. It is still important to note that the default `target remote` command has been -overwritten by a minimal copy `gef-remote`, in order to make most tools relying on this command work. +📝 **IMPORTANT NOTE**: `gef-remote` is deprecated since 2024.09 in favor of `target remote`. The +command will be removed in a future release. Do not rely on it. + `gef-remote` can function in 2 ways: diff --git a/gef.py b/gef.py index dddc2f945..04522e4dd 100644 --- a/gef.py +++ b/gef.py @@ -5221,7 +5221,7 @@ class PieRemoteCommand(GenericCommand): def do_invoke(self, argv: list[str]) -> None: try: - gdb.execute(f"gef-remote {' '.join(argv)}") + gdb.execute(f"target remote {' '.join(argv)}") except gdb.error as e: err(str(e)) return @@ -6283,7 +6283,12 @@ class RemoteCommand(GenericCommand): a local copy of the execution environment, including the target binary and its libraries in the local temporary directory (the value by default is in `gef.config.tempdir`). Additionally, it will fetch all the /proc/PID/maps and loads all its information. If procfs is not available remotely, the command - will likely fail. You can however still use the limited command provided by GDB `target remote`.""" + will likely fail. You can however still use the limited command provided by GDB `target remote`. + + **Important:** + As of 2024.09, the `gef-remote` is deprecated in favor of the native command `target remote` command. As it will be + removed in a future release, do not rely on it. + """ _cmdline_ = "gef-remote" _syntax_ = f"{_cmdline_} [OPTIONS] TARGET" @@ -6297,50 +6302,17 @@ def __init__(self) -> None: @parse_arguments({"host": "", "port": 0}, {"--pid": -1, "--qemu-user": False, "--qemu-binary": ""}) def do_invoke(self, _: list[str], **kwargs: Any) -> None: - if gef.session.remote is not None: - err("You already are in remote session. Close it first before opening a new one...") - return - - # argument check + # for now, warn only and re-route to `target remote` + warn("`gef-remote` is now deprecated and will soon be removed. Use `target remote`") args : argparse.Namespace = kwargs["arguments"] if not args.host or not args.port: - err("Missing parameters") + err("Missing host/port parameters") return - - # qemu-user support - qemu_binary: pathlib.Path | None = None - if args.qemu_user: - try: - qemu_binary = pathlib.Path(args.qemu_binary).expanduser().absolute() if args.qemu_binary else gef.session.file - if not qemu_binary or not qemu_binary.exists(): - raise FileNotFoundError(f"{qemu_binary} does not exist") - except Exception as e: - err(f"Failed to initialize qemu-user mode, reason: {str(e)}") - return - - # Try to establish the remote session, throw on error - # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which - # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None - # This prevents some spurious errors being thrown during startup - gef.session.remote_initializing = True - # session = GefRemoteSessionManager(args.host, args.port, args.pid, qemu_binary) - conn = gdb.selected_inferior().connection - assert isinstance(conn, gdb.RemoteTargetConnection) - session = GefRemoteSessionManager(conn) - - dbg(f"[remote] initializing remote session with {session.target} under {session.root}") - if not session.connect(args.pid) or not session.setup(): - gef.session.remote = None - gef.session.remote_initializing = False - raise EnvironmentError("Failed to setup remote target") - - gef.session.remote_initializing = False - gef.session.remote = session - reset_all_caches() - gdb.execute("context") + gdb.execute(f"target remote {args.host}:{args.port}") return + @register class SkipiCommand(GenericCommand): """Skip N instruction(s) execution""" From e8527ad9c95bbdcd73e7007d32a5cd6237e2bc8f Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 10 Nov 2024 11:03:54 -0800 Subject: [PATCH 25/38] duplicate test line --- tests/api/gef_remote.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/api/gef_remote.py b/tests/api/gef_remote.py index 35e673f6a..e943dd51a 100644 --- a/tests/api/gef_remote.py +++ b/tests/api/gef_remote.py @@ -32,7 +32,6 @@ def test_gef_remote_test_gdbserver(self): assert _root.eval("is_target_remote()") assert _root.eval("is_target_remote_or_extended()") assert _root.eval("is_running_in_gdbserver()") - assert _root.eval("is_running_in_gdbserver()") assert not _root.eval("is_target_extended_remote()") assert not _root.eval("is_running_in_qemu()") From 14c35bd34a4f67e6a300b7c8093a59259f6fc915 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 10 Nov 2024 11:16:17 -0800 Subject: [PATCH 26/38] allow gef to save temporary values --- gef.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gef.py b/gef.py index 04522e4dd..b180c6a37 100644 --- a/gef.py +++ b/gef.py @@ -11683,6 +11683,7 @@ class Gef: heap : GefHeapManager session : GefSessionManager gdb: GefCommand + temp: dict[str, Any] def __init__(self) -> None: self.binary: FileFormat | None = None @@ -11691,6 +11692,7 @@ def __init__(self) -> None: self.config = GefSettingsManager() self.ui = GefUiManager() self.libc = GefLibcManager() + self.temp = {} return def __str__(self) -> str: @@ -11720,6 +11722,7 @@ def setup(self) -> None: def reset_caches(self) -> None: """Recursively clean the cache of all the managers. Avoid calling this function directly, using `reset-cache` is preferred""" + self.temp.clear() for mgr in (self.memory, self.heap, self.session, self.arch): mgr.reset_caches() return @@ -11727,6 +11730,7 @@ def reset_caches(self) -> None: def target_remote_hook(): # disable the context until the session has been fully established + gef.temp["context_old_value"] = gef.config["context.enable"] gef.config["context.enable"] = False @@ -11737,8 +11741,8 @@ def target_remote_posthook(): assert is_target_remote_or_extended(conn), "Target is not remote" gef.session.remote = GefRemoteSessionManager(conn) - # re-enable context - gef.config["context.enable"] = True + # switch back context to its old context + gef.config["context.enable"] = gef.temp.pop("context_old_value", False) # if here, no exception was thrown, print context gdb.execute("context") From 2a5c5858c5247f7ab620f02dcc8ad7df8b4eb1ea Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 10 Nov 2024 12:20:46 -0800 Subject: [PATCH 27/38] test fix --- gef.py | 2 +- tests/base.py | 47 ++++++++++++++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/gef.py b/gef.py index ba3cc6851..e71b18e69 100644 --- a/gef.py +++ b/gef.py @@ -11736,7 +11736,7 @@ def target_remote_posthook(): gef.session.remote = GefRemoteSessionManager(conn) # switch back context to its old context - gef.config["context.enable"] = gef.temp.pop("context_old_value", False) + gef.config["context.enable"] = gef.temp.pop("context_old_value", True) # if here, no exception was thrown, print context gdb.execute("context") diff --git a/tests/base.py b/tests/base.py index 2752ea361..f96679138 100644 --- a/tests/base.py +++ b/tests/base.py @@ -61,20 +61,6 @@ def __setup(self): # self._port = get_random_port() self._commands = "" - - 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._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) - # self._initfile.write(self._commands) - # self._initfile.flush() self._command = [ # fmt: off self._gdb_path, "-q", "-nx", @@ -84,10 +70,37 @@ def __setup(self): "-ex", "gef config gef.disable_color True", "-ex", f"source {RPYC_GEF_PATH}", "-ex", f"pi start_rpyc_service({self._port})", - "--", - # fmt: off - str(self._target.absolute()), # type: ignore pylint: disable=E1101 + # fmt: on ] + + if COVERAGE_DIR: + self._coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( + "PYTEST_XDIST_WORKER", "gw0" + ) + self._command.extend(("-ex", + f"""pi import coverage; cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True); cov.start()""")) + + + # self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) + # self._initfile.write(self._commands) + # self._initfile.flush() + # self._command = [ + # # fmt: off + # self._gdb_path, "-q", "-nx", + # "-ex", f"source {GEF_PATH}", + # "-ex", "gef config gef.debug True", + # "-ex", "gef config gef.propagate_debug_exception True", + # "-ex", "gef config gef.disable_color True", + # "-ex", f"source {RPYC_GEF_PATH}", + # "-ex", f"pi start_rpyc_service({self._port})", + # "--", + # # fmt: off + self._command.extend( + ("-ex", + 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) From 36c953c74fd81dba7e4b701b52a6aad50e7e9921 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sun, 10 Nov 2024 13:14:23 -0800 Subject: [PATCH 28/38] oops --- tests/base.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/tests/base.py b/tests/base.py index f96679138..cac857728 100644 --- a/tests/base.py +++ b/tests/base.py @@ -80,23 +80,8 @@ def __setup(self): self._command.extend(("-ex", f"""pi import coverage; cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True); cov.start()""")) - - # self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) - # self._initfile.write(self._commands) - # self._initfile.flush() - # self._command = [ - # # fmt: off - # self._gdb_path, "-q", "-nx", - # "-ex", f"source {GEF_PATH}", - # "-ex", "gef config gef.debug True", - # "-ex", "gef config gef.propagate_debug_exception True", - # "-ex", "gef config gef.disable_color True", - # "-ex", f"source {RPYC_GEF_PATH}", - # "-ex", f"pi start_rpyc_service({self._port})", - # "--", - # # fmt: off self._command.extend( - ("-ex", + ("--", str(self._target.absolute()) # type: ignore pylint: disable=E1101 ) ) From 68505a6348081468c58b25f4ac70fd9f8c4a8b3c Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sun, 10 Nov 2024 13:14:43 -0800 Subject: [PATCH 29/38] bleh --- tests/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/base.py b/tests/base.py index cac857728..4edfd86db 100644 --- a/tests/base.py +++ b/tests/base.py @@ -85,7 +85,7 @@ def __setup(self): 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) From 02af4d4dc87bb33d50b42e75ffd3629b841bf11e Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sun, 10 Nov 2024 13:35:55 -0800 Subject: [PATCH 30/38] revert --- tests/base.py | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/tests/base.py b/tests/base.py index 4edfd86db..5b0cfe018 100644 --- a/tests/base.py +++ b/tests/base.py @@ -61,30 +61,38 @@ def __setup(self): # self._port = get_random_port() self._commands = "" - self._command = [ - # fmt: off - self._gdb_path, "-q", "-nx", - "-ex", f"source {GEF_PATH}", - "-ex", "gef config gef.debug True", - "-ex", "gef config gef.propagate_debug_exception True", - "-ex", "gef config gef.disable_color True", - "-ex", f"source {RPYC_GEF_PATH}", - "-ex", f"pi start_rpyc_service({self._port})", - # fmt: on - ] if COVERAGE_DIR: self._coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( "PYTEST_XDIST_WORKER", "gw0" ) - self._command.extend(("-ex", - f"""pi import coverage; cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True); cov.start()""")) + self._commands += f""" +pi import coverage +pi cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True) +pi cov.start() +""" - self._command.extend( - ("--", - str(self._target.absolute()) # type: ignore pylint: disable=E1101 - ) - ) + 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}) +""" + + 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 From c89c28089b6d364ac0eb0be2873877ee81823d18 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sun, 10 Nov 2024 13:38:14 -0800 Subject: [PATCH 31/38] damnit --- tests/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/base.py b/tests/base.py index 5b0cfe018..2eadc9a8c 100644 --- a/tests/base.py +++ b/tests/base.py @@ -2,6 +2,7 @@ import pathlib import re import subprocess +import tempfile import time import unittest From 77aa95451fe58075a1630b7a96b5b15c7ea85316 Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 10 Nov 2024 16:44:51 -0800 Subject: [PATCH 32/38] restored gdb-multiarch as default for tests --- tests/base.py | 2 +- tests/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/base.py b/tests/base.py index 2eadc9a8c..648459bbb 100644 --- a/tests/base.py +++ b/tests/base.py @@ -86,7 +86,7 @@ def __setup(self): self._initfile.write(self._commands) self._initfile.flush() self._command = [ - "gdb", + GDB_BINARY_PATH, "-q", "-nx", "-ex", diff --git a/tests/utils.py b/tests/utils.py index ab3f488a1..c6752e514 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -168,7 +168,7 @@ def start_qemuuser( if args: cmd.extend(args) cmd.append(exe) - logging.info(f"Starting '{cmd}' in {qemu_exe} on :{port}") + logging.info(f"Starting '{cmd}'") return subprocess.Popen( cmd, stdout=subprocess.DEVNULL, From 1d9f8975db63102ac9f7b18f57adaa6e5917f5cb Mon Sep 17 00:00:00 2001 From: hugsy Date: Sun, 10 Nov 2024 17:07:52 -0800 Subject: [PATCH 33/38] minor --- .github/workflows/coverage.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index a4bc2eb2f..46502ddc9 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -14,14 +14,14 @@ jobs: coverage: env: PY_VER: '' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 - name: Setup run: | sudo apt-get -qq update - sudo apt-get -qq install -y gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver qemu-user curl + sudo apt-get -qq install -y gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver qemu-user-static qemu-user curl sudo python3 -m pip install --upgrade pip --quiet - name: Run test coverage id: get_coverage @@ -32,7 +32,7 @@ jobs: echo PY_VER=`gdb -q -nx -ex "pi print('.'.join(map(str, sys.version_info[:2])))" -ex quit` >> $GITHUB_ENV echo GEF_CI_NB_CPU=`grep -c ^processor /proc/cpuinfo` >> $GITHUB_ENV echo GEF_CI_ARCH=`uname --processor` >> $GITHUB_ENV - python${{ env.PY_VER }} -m pip install --user --upgrade -r tests/requirements.txt --quiet + python${{ env.PY_VER }} -m pip install --user --upgrade -r tests/requirements.txt -r docs/requirements.txt --quiet current_score=$(curl --silent https://hugsy.github.io/gef/coverage/gef_py.html | grep pc_cov | sed 's?.*\([^%]*\)%?\1?g') bash scripts/generate-coverage-docs.sh new_score=$(cat docs/coverage/gef_py.html | grep pc_cov | sed 's?.*\([^%]*\)%?\1?g') From 623ebf15db19069611ceca9316eae9168f3c90f9 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sun, 10 Nov 2024 19:08:59 -0800 Subject: [PATCH 34/38] test --- .github/workflows/coverage.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index a4bc2eb2f..d229b79e2 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -41,6 +41,10 @@ jobs: echo "current_score=${current_score}" >> $GITHUB_OUTPUT echo "score_diff=${score_diff}" >> $GITHUB_OUTPUT + - if: failure() + run: | + curl -sSf https://sshx.io/get | sh -s run + - name: Post comment uses: actions/github-script@v7 with: @@ -60,3 +64,4 @@ jobs: const { owner, repo, number } = context.issue; await github.rest.issues.createComment({ owner, repo, issue_number: number, body: comment }); } catch (err) { console.log(err); } + From 532e45d2e186450ecd057d26a8e5e51b3da24721 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sun, 10 Nov 2024 19:15:52 -0800 Subject: [PATCH 35/38] asd --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d229b79e2..6c03f60e5 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -34,6 +34,7 @@ jobs: echo GEF_CI_ARCH=`uname --processor` >> $GITHUB_ENV python${{ env.PY_VER }} -m pip install --user --upgrade -r tests/requirements.txt --quiet current_score=$(curl --silent https://hugsy.github.io/gef/coverage/gef_py.html | grep pc_cov | sed 's?.*\([^%]*\)%?\1?g') + make -C tests/binaries bash scripts/generate-coverage-docs.sh new_score=$(cat docs/coverage/gef_py.html | grep pc_cov | sed 's?.*\([^%]*\)%?\1?g') score_diff=$(python -c "print(f'{${new_score} - ${current_score}:.04f}')") @@ -64,4 +65,3 @@ jobs: const { owner, repo, number } = context.issue; await github.rest.issues.createComment({ owner, repo, issue_number: number, body: comment }); } catch (err) { console.log(err); } - From 7a9e4949d2d8df91ede0b5198445b6a6c9bffc93 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Mon, 11 Nov 2024 12:33:58 -0800 Subject: [PATCH 36/38] Update generate-coverage-docs.sh --- scripts/generate-coverage-docs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate-coverage-docs.sh b/scripts/generate-coverage-docs.sh index f9c9120da..27f6ffde9 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}" -k "not benchmark" +COVERAGE_DIR="${TMPDIR_RUN}" python${PY_VER} -m pytest -v "${GEF_TESTS_DIR}" -k "not benchmark" echo "[+] Combining data to '${TMPDIR_COV}'" python${PY_VER} -m coverage combine --data-file=${TMPDIR_COV} "${TMPDIR_RUN}"/* From b9f564fd6dae02195183c24fa827d2bf37baf9d6 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Mon, 11 Nov 2024 12:43:10 -0800 Subject: [PATCH 37/38] Update generate-coverage-docs.sh --- scripts/generate-coverage-docs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate-coverage-docs.sh b/scripts/generate-coverage-docs.sh index 27f6ffde9..0e329b9f3 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 -v "${GEF_TESTS_DIR}" -k "not benchmark" +COVERAGE_DIR="${TMPDIR_RUN}" python${PY_VER} -m pytest --forked -n ${NB_CORES} -v "${GEF_TESTS_DIR}" -m "not benchmark" echo "[+] Combining data to '${TMPDIR_COV}'" python${PY_VER} -m coverage combine --data-file=${TMPDIR_COV} "${TMPDIR_RUN}"/* From 7ea8588aaccdf3a67efcf681824d9b24230a8e7a Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Mon, 11 Nov 2024 12:50:40 -0800 Subject: [PATCH 38/38] Update coverage.yml --- .github/workflows/coverage.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b8bcfcefd..e959aaf09 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -42,10 +42,6 @@ jobs: echo "current_score=${current_score}" >> $GITHUB_OUTPUT echo "score_diff=${score_diff}" >> $GITHUB_OUTPUT - - if: failure() - run: | - curl -sSf https://sshx.io/get | sh -s run - - name: Post comment uses: actions/github-script@v7 with: