Register Access
The Registers subsystem reads and writes CPU registers by name using OpenOCD’s reg command. It includes convenience accessors for common ARM Cortex-M registers. Access it through session.registers.
Reading a single register
Section titled “Reading a single register”read() takes a register name and returns its integer value:
async def read(name: str) -> intasync with Session.connect() as ocd: await ocd.target.halt()
pc = await ocd.registers.read("pc") r0 = await ocd.registers.read("r0") xpsr = await ocd.registers.read("xPSR")
print(f"PC = 0x{pc:08X}") print(f"r0 = 0x{r0:08X}") print(f"xPSR = 0x{xpsr:08X}")with Session.connect_sync() as ocd: ocd.target.halt()
pc = ocd.registers.read("pc") r0 = ocd.registers.read("r0") xpsr = ocd.registers.read("xPSR")
print(f"PC = 0x{pc:08X}") print(f"r0 = 0x{r0:08X}") print(f"xPSR = 0x{xpsr:08X}")Internally, read("pc") sends the command reg pc and parses the response:
pc (/32): 0x08001234The regex pattern matches the register name, bit width, and hex value from this format.
Writing a register
Section titled “Writing a register”write() sets a register to a specific value:
async def write(name: str, value: int) -> Noneasync with Session.connect() as ocd: await ocd.target.halt()
# Set r0 to a test value await ocd.registers.write("r0", 0x42)
# Move PC to a different address await ocd.registers.write("pc", 0x08001000)
await ocd.target.resume()with Session.connect_sync() as ocd: ocd.target.halt()
ocd.registers.write("r0", 0x42) ocd.registers.write("pc", 0x08001000)
ocd.target.resume()The value is sent as a hex string: reg r0 0x42.
Reading multiple registers
Section titled “Reading multiple registers”read_many
Section titled “read_many”read_many() reads several registers by name and returns a dictionary:
async def read_many(names: list[str]) -> dict[str, int]async with Session.connect() as ocd: await ocd.target.halt()
values = await ocd.registers.read_many(["r0", "r1", "r2", "r3"]) for name, val in values.items(): print(f" {name} = 0x{val:08X}")with Session.connect_sync() as ocd: ocd.target.halt()
values = ocd.registers.read_many(["r0", "r1", "r2", "r3"]) for name, val in values.items(): print(f" {name} = 0x{val:08X}")This issues one reg <name> command per register sequentially. For reading all registers at once, use read_all() instead.
read_all
Section titled “read_all”read_all() reads every register in a single reg command and returns a dictionary of Register dataclasses:
async def read_all() -> dict[str, Register]async with Session.connect() as ocd: await ocd.target.halt()
all_regs = await ocd.registers.read_all()
for name, reg in all_regs.items(): dirty = " (dirty)" if reg.dirty else "" print(f" ({reg.number:>3d}) {reg.name:<12s} /{reg.size:<3d} = 0x{reg.value:08X}{dirty}")with Session.connect_sync() as ocd: ocd.target.halt()
all_regs = ocd.registers.read_all()
for name, reg in all_regs.items(): dirty = " (dirty)" if reg.dirty else "" print(f" ({reg.number:>3d}) {reg.name:<12s} /{reg.size:<3d} = 0x{reg.value:08X}{dirty}")The Register dataclass
Section titled “The Register dataclass”read_all() returns Register objects, a frozen dataclass with five fields:
@dataclass(frozen=True)class Register: name: str # Register name (e.g. "r0", "pc", "xPSR") number: int # Register number in OpenOCD's numbering value: int # Current value size: int # Width in bits (e.g. 32) dirty: bool # Whether the value has been modified since last commitThe dirty flag indicates that the register value has been written by the debugger but not yet committed to the target. This happens when you write a register and then inspect it before resuming.
OpenOCD’s reg (list all) output looks like:
(0) r0 (/32): 0x00000000(1) r1 (/32): 0x00000042...(16) xPSR (/32): 0x61000000 (dirty)The library parses each line using a regex that extracts the register number, name, bit width, value, and optional dirty flag.
ARM Cortex-M shortcuts
Section titled “ARM Cortex-M shortcuts”For the most commonly accessed ARM Cortex-M registers, convenience methods are provided:
| Method | Equivalent |
|---|---|
pc() | read("pc") |
sp() | read("sp") |
lr() | read("lr") |
xpsr() | read("xPSR") |
async with Session.connect() as ocd: await ocd.target.halt()
pc = await ocd.registers.pc() sp = await ocd.registers.sp() lr = await ocd.registers.lr() xpsr = await ocd.registers.xpsr()
print(f"PC = 0x{pc:08X}") print(f"SP = 0x{sp:08X}") print(f"LR = 0x{lr:08X}") print(f"xPSR = 0x{xpsr:08X}")with Session.connect_sync() as ocd: ocd.target.halt()
pc = ocd.registers.pc() sp = ocd.registers.sp() lr = ocd.registers.lr() xpsr = ocd.registers.xpsr()
print(f"PC = 0x{pc:08X}") print(f"SP = 0x{sp:08X}") print(f"LR = 0x{lr:08X}") print(f"xPSR = 0x{xpsr:08X}")These are thin wrappers that call read() with the appropriate register name. They exist for readability and to avoid typos in register name strings.
Practical example: stack trace inspection
Section titled “Practical example: stack trace inspection”Read the stack pointer and inspect the stack contents alongside register values:
import asynciofrom openocd import Session
async def inspect_stack(): async with Session.connect() as ocd: await ocd.target.halt()
sp = await ocd.registers.sp() pc = await ocd.registers.pc() lr = await ocd.registers.lr()
print(f"PC = 0x{pc:08X}") print(f"LR = 0x{lr:08X}") print(f"SP = 0x{sp:08X}")
# Read 16 words from the stack print("\nStack contents:") stack = await ocd.memory.read_u32(sp, count=16) for i, val in enumerate(stack): print(f" [SP+0x{i*4:02X}] = 0x{val:08X}")
await ocd.target.resume()
asyncio.run(inspect_stack())from openocd import Session
with Session.connect_sync() as ocd: ocd.target.halt()
sp = ocd.registers.sp() pc = ocd.registers.pc() lr = ocd.registers.lr()
print(f"PC = 0x{pc:08X}") print(f"LR = 0x{lr:08X}") print(f"SP = 0x{sp:08X}")
print("\nStack contents:") stack = ocd.memory.read_u32(sp, count=16) for i, val in enumerate(stack): print(f" [SP+0x{i*4:02X}] = 0x{val:08X}")
ocd.target.resume()Method summary
Section titled “Method summary”| Method | Returns | Description |
|---|---|---|
read(name) | int | Read a single register by name |
write(name, value) | None | Write a value to a register |
read_all() | dict[str, Register] | Read all registers |
read_many(names) | dict[str, int] | Read several registers by name |
pc() | int | Read the program counter |
sp() | int | Read the stack pointer |
lr() | int | Read the link register |
xpsr() | int | Read the xPSR status register |
Errors
Section titled “Errors”| Exception | When |
|---|---|
TargetNotHaltedError | Target is running (register access requires halt) |
TargetError | Register not found or command failed |
Next steps
Section titled “Next steps”- Target Control — halt the target before register access
- Memory Operations — read memory at the address a register points to
- Error Handling — handling TargetNotHaltedError