diff --git a/docs/commands/arch.md b/docs/commands/arch.md new file mode 100644 index 000000000..df7b3ce8d --- /dev/null +++ b/docs/commands/arch.md @@ -0,0 +1,17 @@ +## Command `arch` + +`arch` manages the loaded architecture. + +There are 3 available sub-commands: + +- `list`: List the installed architectures. +- `get`: Print the currently loaded architecture, and why it is selected. +- `set`: Manually set the loaded architecture by providing its name as an argument, or let + gef do magic to detect the architecture by not providing arguments. + +> [!WARNING] +> Setting manually should be done as a last resort as GEF expects to find the architecture +> automatically. Force-setting the architecture can lead to unexpected behavior if not done correctly. + + +![arch](https://github.com/hugsy/gef/assets/590234/c4481a78-9311-43ba-929f-2817c5c9290e) diff --git a/gef.py b/gef.py index b8d310c33..64e6845b6 100644 --- a/gef.py +++ b/gef.py @@ -2277,6 +2277,7 @@ def get_zone_base_address(name: str) -> Optional[int]: # # Architecture classes # + @deprecated("Using the decorator `register_architecture` is unecessary") def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: return cls @@ -2290,7 +2291,10 @@ def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs): super().__init_subclass__(**kwargs) for key in getattr(cls, "aliases"): if issubclass(cls, Architecture): - __registered_architectures__[key] = cls + if isinstance(key, str): + __registered_architectures__[key.lower()] = cls + else: + __registered_architectures__[key] = cls return @@ -3785,6 +3789,7 @@ def reset_architecture(arch: Optional[str] = None) -> None: if arch: try: gef.arch = arches[arch]() + gef.arch_reason = "The architecture has been set manually" except KeyError: raise OSError(f"Specified arch {arch.upper()} is not supported") return @@ -3795,16 +3800,20 @@ def reset_architecture(arch: Optional[str] = None) -> None: preciser_arch = next((a for a in arches.values() if a.supports_gdb_arch(gdb_arch)), None) if preciser_arch: gef.arch = preciser_arch() + gef.arch_reason = "The architecture has been detected by GDB" return # last resort, use the info from elf header to find it from the known architectures if gef.binary and isinstance(gef.binary, Elf): try: gef.arch = arches[gef.binary.e_machine]() + gef.arch_reason = "The architecture has been detected via the ELF headers" except KeyError: raise OSError(f"CPU type is currently not supported: {gef.binary.e_machine}") return + warn("Did not find any way to guess the correct architecture :(") + @lru_cache() def cached_lookup_type(_type: str) -> Optional[gdb.Type]: @@ -4758,6 +4767,71 @@ def __set_repeat_count(self, argv: List[str], from_tty: bool) -> None: return +@register +class ArchCommand(GenericCommand): + """Manage the current loaded architecture.""" + + _cmdline_ = "arch" + _syntax_ = f"{_cmdline_} (list|get|set) ..." + _example_ = f"{_cmdline_} set X86" + + def __init__(self) -> None: + super().__init__(prefix=True) + return + + def do_invoke(self, argv: List[str]) -> None: + if not argv: + self.usage() + return + +@register +class ArchGetCommand(GenericCommand): + """Get the current loaded architecture.""" + + _cmdline_ = "arch get" + _syntax_ = f"{_cmdline_}" + _example_ = f"{_cmdline_}" + + 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}") + + +@register +class ArchSetCommand(GenericCommand): + """Set the current loaded architecture.""" + + _cmdline_ = "arch set" + _syntax_ = f"{_cmdline_} " + _example_ = f"{_cmdline_} X86" + + 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]: + return sorted(x for x in __registered_architectures__.keys() if + isinstance(x, str) and x.lower().startswith(text.lower().strip())) + +@register +class ArchListCommand(GenericCommand): + """List the available architectures.""" + + _cmdline_ = "arch list" + _syntax_ = f"{_cmdline_}" + _example_ = f"{_cmdline_}" + + 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: + continue + + gef_print(' ' + Color.yellowify(str(arch()))) + for alias in arch.aliases: + if isinstance(alias, str): + gef_print(f" {alias}") + + @register class VersionCommand(GenericCommand): """Display GEF version info.""" @@ -11554,6 +11628,7 @@ class Gef: def __init__(self) -> None: self.binary: Optional[FileFormat] = 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() self.ui = GefUiManager() self.libc = GefLibcManager() diff --git a/tests/commands/arch.py b/tests/commands/arch.py new file mode 100644 index 000000000..9fb31acc8 --- /dev/null +++ b/tests/commands/arch.py @@ -0,0 +1,45 @@ +""" +Arch commands test module +""" + +import pytest + +from tests.base import RemoteGefUnitTestGeneric +from tests.utils import ARCH + + +class ArchCommand(RemoteGefUnitTestGeneric): + """Class for `arch` command testing.""" + + @pytest.mark.skipif(ARCH != "x86_64", reason=f"Skipped for {ARCH}") + def test_cmd_arch_get(self): + gdb = self._gdb + + res = gdb.execute("arch get", to_string=True) + assert " Architecture(X86, 64, LITTLE_ENDIAN)" in res + assert " The architecture has been detected via the ELF headers" in res + + def test_cmd_arch_set(self): + gdb = self._gdb + + gdb.execute("arch set X86") + + res = gdb.execute("arch get", to_string=True) + assert " Architecture(X86, 32, LITTLE_ENDIAN)" in res + assert " The architecture has been set manually" in res + + + gdb.execute("arch set ppc") + + res = gdb.execute("arch get", to_string=True) + assert " Architecture(PPC, PPC32, LITTLE_ENDIAN)" in res + assert " The architecture has been set manually" in res + + def test_cmd_arch_list(self): + gdb = self._gdb + + res = gdb.execute("arch list", to_string=True) + assert "- GenericArchitecture" not in res + assert " Architecture(X86, 64, LITTLE_ENDIAN)" in res + assert " X86" in res + assert " X86_64" in res