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 all 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`: 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]:
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 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):
ValekoZ marked this conversation as resolved.
Show resolved Hide resolved
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")
hugsy marked this conversation as resolved.
Show resolved Hide resolved

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
Loading