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

Add ARM Cortex-M OpenOCD arch, command, and session manager #83

Merged
merged 8 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 21 additions & 18 deletions archs/arm-blackmagicprobe.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import gdb

assert 'gef' in globals(), "This file must be source after gef.py"


class ARMBlackMagicProbe(ARM):
arch = "ARMBlackMagicProbe"
Expand All @@ -31,29 +33,25 @@ def maps():

@register
class BMPRemoteCommand(GenericCommand):
"""GDB `target remote` command on steroids. This command will use the remote procfs to create
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`."""
"""This command is intended to replace `gef-remote` to connect to a
BlackMagicProbe. It uses a special session manager that knows how to
connect and manage the server running over a tty."""

_cmdline_ = "gef-bmp-remote"
_syntax_ = f"{_cmdline_} [OPTIONS] TTY"
_example_ = [
f"{_cmdline_} --scan /dev/ttyUSB1"
f"{_cmdline_} --scan /dev/ttyUSB1 --power"
f"{_cmdline_} --scan /dev/ttyUSB1 --power --keep-power"
_example_ = [f"{_cmdline_} --scan /dev/ttyUSB1",
f"{_cmdline_} --scan /dev/ttyUSB1 --power",
f"{_cmdline_} --scan /dev/ttyUSB1 --power --keep-power",
f"{_cmdline_} --file /path/to/binary.elf --attach 1 /dev/ttyUSB1",
f"{_cmdline_} --file /path/to/binary.elf --attach 1 --power /dev/ttyUSB1",
]
f"{_cmdline_} --file /path/to/binary.elf --attach 1 --power /dev/ttyUSB1"]

def __init__(self) -> None:
super().__init__(prefix=False)
return

@parse_arguments({"tty": ""}, {"--file": "", "--attach": "", "--power": False,
"--keep-power": False, "--scan": False})
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're already in a remote session. Close it first before opening a new one...")
return
Expand Down Expand Up @@ -95,8 +93,8 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None:


class GefBMPRemoteSessionManager(GefRemoteSessionManager):
"""Class for managing remote sessions with GEF. It will create a temporary environment
designed to clone the remote one."""
"""This subclass of GefRemoteSessionManager specially handles the
intricacies involved with connecting to a BlackMagicProbe."""
def __init__(self, tty: str="", file: str="", attach: int=1,
scan: bool=False, power: bool=False, keep_power: bool=False) -> None:
self.__tty = tty
Expand All @@ -106,7 +104,12 @@ def __init__(self, tty: str="", file: str="", attach: int=1,
self.__power = power
self.__keep_power = keep_power
self.__local_root_fd = tempfile.TemporaryDirectory()
self.__local_root_path = pathlib.Path(self.__local_root_fd.name)
self.__local_root_path = Path(self.__local_root_fd.name)
class BMPMode():
def prompt_string(self) -> str:
return Color.boldify("(BMP) ")

self._mode = BMPMode()

def __str__(self) -> str:
return f"BMPRemoteSessionManager(tty='{self.__tty}', file='{self.__file}', attach={self.__attach})"
Expand All @@ -123,7 +126,7 @@ def close(self) -> None:
return

@property
def root(self) -> pathlib.Path:
def root(self) -> Path:
return self.__local_root_path.absolute()

@property
Expand All @@ -135,9 +138,9 @@ def sync(self, src: str, dst: Optional[str] = None) -> bool:
return None

@property
def file(self) -> Optional[pathlib.Path]:
def file(self) -> Optional[Path]:
if self.__file:
return pathlib.Path(self.__file).expanduser()
return Path(self.__file).expanduser()
return None

def connect(self) -> bool:
Expand Down
177 changes: 177 additions & 0 deletions archs/arm-openocd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
"""
ARM through OpenOCD support for GEF

To use, source this file *after* gef

Author: Grazfather
"""

from typing import Optional
from pathlib import Path

import gdb

Grazfather marked this conversation as resolved.
Show resolved Hide resolved
assert 'gef' in globals(), "This file must be source after gef.py"


class ARMOpenOCD(ARM):
arch = "ARMOpenOCD"
aliases = ("ARMOpenOCD",)
all_registers = ("$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6",
"$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp",
"$lr", "$pc", "$xPSR")
flag_register = "$xPSR"
@staticmethod
def supports_gdb_arch(arch: str) -> Optional[bool]:
if "arm" in arch and arch.endswith("-m"):
hugsy marked this conversation as resolved.
Show resolved Hide resolved
return True
return None

@staticmethod
def maps():
yield from GefMemoryManager.parse_info_mem()
Grazfather marked this conversation as resolved.
Show resolved Hide resolved


@register
class OpenOCDRemoteCommand(GenericCommand):
"""This command is intended to replace `gef-remote` to connect to an
OpenOCD-hosted gdbserver. It uses a special session manager that knows how
to connect and manage the server."""

_cmdline_ = "gef-openocd-remote"
_syntax_ = f"{_cmdline_} [OPTIONS] HOST PORT"
_example_ = [f"{_cmdline_} --file /path/to/binary.elf localhost 3333",
f"{_cmdline_} localhost 3333"]

def __init__(self) -> None:
super().__init__(prefix=False)
return

@parse_arguments({"host": "", "port": 0}, {"--file": ""})
def do_invoke(self, _: list[str], **kwargs: Any) -> None:
if gef.session.remote is not None:
err("You're already in a remote session. Close it first before opening a new one...")
return

# argument check
args: argparse.Namespace = kwargs["arguments"]
if not args.host or not args.port:
err("Missing parameters")
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 = GefOpenOCDRemoteSessionManager(args.host, args.port, args.file)

dbg(f"[remote] initializing remote session with {session.target} under {session.root}")

# Connect can return false if it wants us to disconnect
if not session.connect():
gef.session.remote = None
gef.session.remote_initializing = False
return
if 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")
return


# We CANNOT use the normal session manager because it assumes we have a PID
class GefOpenOCDRemoteSessionManager(GefRemoteSessionManager):
"""This subclass of GefRemoteSessionManager specially handles the
intricacies involved with connecting to an OpenOCD-hosted GDB server.
Specifically, it does not have the concept of PIDs which we need to work
around."""
def __init__(self, host: str, port: str, file: str="") -> None:
self.__host = host
self.__port = port
self.__file = file
self.__local_root_fd = tempfile.TemporaryDirectory()
self.__local_root_path = Path(self.__local_root_fd.name)
class OpenOCDMode():
def prompt_string(self) -> str:
return Color.boldify("(OpenOCD) ")

self._mode = OpenOCDMode()

def __str__(self) -> str:
return f"OpenOCDRemoteSessionManager(='{self.__tty}', file='{self.__file}', attach={self.__attach})"

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)}")
return

@property
def target(self) -> str:
return f"{self.__host}:{self.__port}"

@property
def root(self) -> Path:
return self.__local_root_path.absolute()

def sync(self, src: str, dst: Optional[str] = None) -> bool:
# We cannot sync from this target
return None

@property
def file(self) -> Optional[Path]:
if self.__file:
return Path(self.__file).expanduser()
return None

def connect(self) -> 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")
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)

# Connect
with DisableContextOutputContext():
self._gdb_execute(f"target extended-remote {self.target}")

try:
with DisableContextOutputContext():
if self.file:
self._gdb_execute(f"file '{self.file}'")
except Exception as e:
err(f"Failed to connect to {self.target}: {e}")
# a failure will trigger the cleanup, deleting our hook
return False

return True

def setup(self) -> bool:
dbg(f"Setting up as remote session")

# refresh gef to consider the binary
reset_all_caches()
if self.file:
gef.binary = Elf(self.file)
# We'd like to set this earlier, but we can't because of this bug
# https://sourceware.org/bugzilla/show_bug.cgi?id=31303
reset_architecture("ARMOpenOCD")
return True

def _gdb_execute(self, cmd):
dbg(f"[remote] Executing '{cmd}'")
gdb.execute(cmd)
4 changes: 4 additions & 0 deletions docs/archs/arm-openocd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## ARMOpenOCD

The ARM OpenOCD architecture is a special arcthtecture used with the `gef-openocd-remote` command.
Please read the [documentation](../commands/gef-openocd-remote.md) for the command.
14 changes: 14 additions & 0 deletions docs/commands/gef-openocd-remote.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Command gef-openocd-remote

The `gef-openocd-command` is used with the [`ARMOpenOCD`](../../archs/arm-openocd.py] architecture.

The [arm-openocd.py](../../archs/arm-openocd.py) script adds an easy way to extend the `gef-remote`
functionality to easily debug ARM targets using a OpenOCD gdbserver. It creates a custom ARM-derived
`Architecture`, as well as the `gef-openocd-remote` command, which lets you easily connect to the
target, optionally loading the accompanying ELF binary.

### Usage

```bash
gef-openocd-remote localhost 3333 --file /path/to/elf
```
Loading