326 lines
9.2 KiB
Plaintext
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
|