Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cmd] Add a arch command for managing gef.arch #1114

Merged
merged 26 commits into from
Jun 2, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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`: This prints the currently loaded architecture, and why it is selected.
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved
- `set`: You can manually set the loaded architecture by providing its name as an argument, or let
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved
gef do magic to detect the architecture by not providing arguments.
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved

> [!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)
74 changes: 73 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,68 @@ 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]:
hugsy marked this conversation as resolved.
Show resolved Hide resolved
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 != GenericArchitecture:
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved
gef_print(' ' + Color.yellowify(str(arch())))
for alias in filter(lambda x: isinstance(x, str), arch.aliases):
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved
gef_print(f" {alias}")


@register
class VersionCommand(GenericCommand):
"""Display GEF version info."""
Expand Down Expand Up @@ -11554,6 +11625,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 default architecture"
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved
self.config = GefSettingsManager()
self.ui = GefUiManager()
self.libc = GefLibcManager()
Expand Down
46 changes: 46 additions & 0 deletions tests/commands/arch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
Arch commands test module
"""

from tests.base import RemoteGefUnitTestGeneric
from tests.utils import ARCH
import pytest
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved


class ArchCommand(RemoteGefUnitTestGeneric):
"""Generic class for command testing, that defines all helpers"""
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved

def setUp(self) -> None:
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved
return super().setUp()

@pytest.mark.skipif(ARCH != "x86_64", reason=f"Skipped for {ARCH}")
def test_cmd_arch_get(self):
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved
gdb = self._gdb

res = gdb.execute("arch get", to_string=True)
self.assertIn(" Architecture(X86, 64, LITTLE_ENDIAN)", res)
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved
self.assertIn(" The architecture has been detected via the ELF headers", res)

def test_cmd_arch_set(self):
gdb = self._gdb

gdb.execute("arch set X86")
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved

res = gdb.execute("arch get", to_string=True)
self.assertIn(" Architecture(X86, 32, LITTLE_ENDIAN)", res)
self.assertIn(" The architecture has been set manually", res)


gdb.execute("arch set ppc")
hugsy marked this conversation as resolved.
Show resolved Hide resolved

res = gdb.execute("arch get", to_string=True)
self.assertIn(" Architecture(PPC, PPC32, LITTLE_ENDIAN)", res)
self.assertIn(" The architecture has been set manually", res)

def test_cmd_arch_list(self):
gdb = self._gdb

res = gdb.execute("arch list", to_string=True)
self.assertNotIn("- GenericArchitecture", res)
self.assertIn("- X86", res)
self.assertIn("- X86_64", res)
Loading