Skip to content

Commit

Permalink
[cmd] Add a arch command for managing gef.arch (#1114)
Browse files Browse the repository at this point in the history
New command in gef: `arch` with available subcommands:
  - `get`  -> prints the current arch and the reason
  - `set` -> manually set the current arch (or auto-detect if no arg is provided)
  - `list` -> list available architectures

Implements this: #1002

---------

Co-authored-by: crazy hugsy <[email protected]>
  • Loading branch information
ValekoZ and hugsy authored Jun 2, 2024
1 parent 85008e2 commit cfda901
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 1 deletion.
17 changes: 17 additions & 0 deletions docs/commands/arch.md
Original file line number Diff line number Diff line change
@@ -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)
77 changes: 76 additions & 1 deletion gef.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand Down Expand Up @@ -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
Expand All @@ -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]:
Expand Down Expand Up @@ -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_} <arch>"
_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."""
Expand Down Expand Up @@ -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()
Expand Down
45 changes: 45 additions & 0 deletions tests/commands/arch.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit cfda901

Please sign in to comment.