326 lines
9.2 KiB
Plaintext

---
title: Error Handling
description: Exception hierarchy and error recovery patterns
---
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
openocd-python uses a structured exception hierarchy rooted at `OpenOCDError`. Every exception the library raises is a subclass of this base, so you can catch broadly or narrowly depending on your needs.
## Exception hierarchy
```
OpenOCDError
├── ConnectionError # TCP connection failures
├── TimeoutError # Deadline exceeded
├── TargetError # Target not responding or returned an error
│ └── TargetNotHaltedError # Operation requires halted target
├── FlashError # Flash operation failed
├── JTAGError # JTAG communication error
├── SVDError # SVD file or parsing error
├── ProcessError # OpenOCD subprocess failed
└── BreakpointError # Breakpoint/watchpoint operation failed
```
All exceptions are importable from the top-level `openocd` package:
```python
from openocd import (
OpenOCDError,
ConnectionError,
TimeoutError,
TargetError,
TargetNotHaltedError,
FlashError,
JTAGError,
SVDError,
ProcessError,
)
```
<Aside type="note">
`BreakpointError` is defined in `openocd.breakpoints` rather than `openocd.errors`, but it still inherits from `OpenOCDError`. Import it as `from openocd.breakpoints import BreakpointError`.
</Aside>
## Exception details
### OpenOCDError
The base class for all library exceptions. Catching this handles any error that openocd-python can raise:
```python
from openocd import OpenOCDError, Session
with Session.connect_sync() as ocd:
try:
state = ocd.target.state()
except OpenOCDError as e:
print(f"Something went wrong: {e}")
```
### ConnectionError
Raised when a TCP connection to OpenOCD cannot be established. Common causes:
- OpenOCD is not running
- Wrong host or port
- Firewall blocking the connection
- Network unreachable
```python
from openocd import ConnectionError, Session
try:
with Session.connect_sync(host="192.168.1.99", port=6666) as ocd:
ocd.target.state()
except ConnectionError as e:
print(f"Cannot reach OpenOCD: {e}")
```
Also raised if a command is sent after the connection has been closed, or if OpenOCD closes the connection unexpectedly during a read.
### TimeoutError
Raised when an operation exceeds its deadline. This can happen during:
- Initial connection (`Session.connect()` with `timeout` parameter)
- Waiting for OpenOCD to start (`Session.start()` with `timeout` parameter)
- Individual command responses (the `TclRpcConnection` timeout)
- `target.wait_halt()` when the target does not halt in time
```python
from openocd import TimeoutError, Session
with Session.connect_sync() as ocd:
try:
# Wait up to 2 seconds for the target to halt
ocd.target.wait_halt(timeout_ms=2000)
except TimeoutError:
print("Target did not halt within 2 seconds")
```
### TargetError
Raised when a target command fails. The error message contains the raw OpenOCD response for diagnosis. This covers halt, resume, step, reset, memory read/write, and register access failures.
```python
from openocd import TargetError, Session
with Session.connect_sync() as ocd:
try:
ocd.target.halt()
except TargetError as e:
print(f"Target command failed: {e}")
```
### TargetNotHaltedError
A subclass of `TargetError`, raised specifically when an operation requires a halted target but the target is running. This is most commonly encountered when reading or writing registers.
```python
from openocd import TargetNotHaltedError, Session
with Session.connect_sync() as ocd:
try:
pc = ocd.registers.pc()
except TargetNotHaltedError:
print("Halt the target first before reading registers")
ocd.target.halt()
pc = ocd.registers.pc()
```
Because `TargetNotHaltedError` is a subclass of `TargetError`, catching `TargetError` will also catch it:
```python
try:
pc = ocd.registers.pc()
except TargetNotHaltedError:
# Handle the specific case
ocd.target.halt()
pc = ocd.registers.pc()
except TargetError:
# Handle other target errors
print("Unexpected target error")
```
<Aside type="caution">
Order matters when catching exceptions. Always put the more specific exception (`TargetNotHaltedError`) before the more general one (`TargetError`), or the specific handler will never run.
</Aside>
### FlashError
Raised when a flash operation fails -- programming, erasing, verifying, or reading flash memory. The error message includes the raw OpenOCD response.
```python
from openocd import FlashError, Session
from pathlib import Path
with Session.connect_sync() as ocd:
try:
ocd.flash.write_image(Path("firmware.bin"))
except FlashError as e:
print(f"Flash programming failed: {e}")
```
Also raised for:
- Invalid sector ranges (e.g., `first > last` in `erase_sector`)
- Verification mismatches after `write_image` with `verify=True`
- Unparseable flash info output
### JTAGError
Raised when JTAG communication fails. This covers chain scan errors, TAP state transitions, and raw scan operations.
```python
from openocd import JTAGError, Session
with Session.connect_sync() as ocd:
try:
taps = ocd.jtag.scan_chain()
except JTAGError as e:
print(f"JTAG error: {e}")
```
### SVDError
Raised when SVD-related operations fail:
- SVD file not found or cannot be parsed
- Peripheral name not found in the loaded SVD
- Register name not found within a peripheral
- No SVD file loaded when trying to list or decode
```python
from openocd import SVDError, Session
from pathlib import Path
with Session.connect_sync() as ocd:
try:
ocd.svd.load(Path("nonexistent.svd"))
except SVDError as e:
print(f"SVD error: {e}")
```
### ProcessError
Raised when the OpenOCD subprocess fails to start or exits unexpectedly. This only applies when using `Session.start()`.
```python
from openocd import ProcessError, Session
try:
with Session.start_sync("nonexistent_config.cfg") as ocd:
pass
except ProcessError as e:
print(f"OpenOCD failed to start: {e}")
```
Common causes:
- OpenOCD binary not found on `PATH`
- Invalid configuration file
- OpenOCD exits before the TCL RPC port is ready
- Permission errors
### BreakpointError
Raised when a breakpoint or watchpoint operation fails. Defined in `openocd.breakpoints` but inherits from `OpenOCDError`.
```python
from openocd.breakpoints import BreakpointError
from openocd import Session
with Session.connect_sync() as ocd:
try:
ocd.breakpoints.add(0x08001234, length=2, hw=True)
except BreakpointError as e:
print(f"Breakpoint error: {e}")
```
## Catching patterns
### Broad catch -- handle any library error
```python
from openocd import OpenOCDError, Session
with Session.connect_sync() as ocd:
try:
ocd.target.halt()
pc = ocd.registers.pc()
data = ocd.memory.read_u32(pc, count=4)
except OpenOCDError as e:
print(f"Operation failed: {e}")
```
### Narrow catch -- handle specific failure modes
```python
from openocd import (
Session,
ConnectionError,
TargetError,
TargetNotHaltedError,
TimeoutError,
)
try:
with Session.connect_sync() as ocd:
ocd.target.halt()
pc = ocd.registers.pc()
ocd.target.resume()
except ConnectionError:
print("Could not connect to OpenOCD")
except TargetNotHaltedError:
print("Target is not halted")
except TimeoutError:
print("Operation timed out")
except TargetError as e:
print(f"Target error: {e}")
```
### Retry pattern
```python
from openocd import Session, TimeoutError
with Session.connect_sync() as ocd:
for attempt in range(3):
try:
ocd.target.halt()
ocd.target.wait_halt(timeout_ms=1000)
break
except TimeoutError:
if attempt == 2:
raise
print(f"Attempt {attempt + 1} timed out, retrying...")
ocd.target.reset(mode="halt")
```
### Recovery from TargetNotHaltedError
A common pattern is to attempt a register read and automatically halt if needed:
```python
from openocd import Session, TargetNotHaltedError
def safe_read_pc(ocd) -> int:
try:
return ocd.registers.pc()
except TargetNotHaltedError:
ocd.target.halt()
return ocd.registers.pc()
with Session.connect_sync() as ocd:
pc = safe_read_pc(ocd)
print(f"PC = 0x{pc:08X}")
```
## Error responses from OpenOCD
Internally, most subsystems detect errors by checking for the word "error" in the OpenOCD response string. This is because OpenOCD's TCL RPC protocol does not use structured error codes -- all errors are communicated as plain text in the response body.
The library wraps these text responses in the appropriate exception type so you do not need to parse them yourself. The original OpenOCD message is preserved in the exception's string representation.
## Next steps
- [Target Control](/guides/target-control/) -- which methods raise which exceptions
- [Memory Operations](/guides/memory-operations/) -- error handling for memory reads and writes
- [Session Lifecycle](/guides/session-lifecycle/) -- connection and process errors