pyGdbToolkit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyGdbToolkit/__init__.py +55 -0
- pyGdbToolkit/arch/__init__.py +79 -0
- pyGdbToolkit/arch/arm/__init__.py +17 -0
- pyGdbToolkit/arch/arm/coresight.py +427 -0
- pyGdbToolkit/arch/arm/cortex_m.py +504 -0
- pyGdbToolkit/arch/arm/fault.py +629 -0
- pyGdbToolkit/arch/arm/models.py +60 -0
- pyGdbToolkit/arch/arm/mpu.py +556 -0
- pyGdbToolkit/arch/arm/probe.py +10 -0
- pyGdbToolkit/arch/arm/providers/__init__.py +16 -0
- pyGdbToolkit/arch/arm/providers/base.py +77 -0
- pyGdbToolkit/arch/arm/providers/manufacturers/__init__.py +4 -0
- pyGdbToolkit/arch/arm/providers/manufacturers/stm32.py +680 -0
- pyGdbToolkit/arch/arm/sau.py +253 -0
- pyGdbToolkit/arch/arm/security.py +816 -0
- pyGdbToolkit/arch/arm/session_state.py +96 -0
- pyGdbToolkit/arch/arm/target.py +32 -0
- pyGdbToolkit/arch/base.py +143 -0
- pyGdbToolkit/arch/diagnostics.py +300 -0
- pyGdbToolkit/arch/registry.py +43 -0
- pyGdbToolkit/cmd_faultinfo.py +99 -0
- pyGdbToolkit/cmd_lscpu.py +116 -0
- pyGdbToolkit/cmd_profile.py +1402 -0
- pyGdbToolkit/cmd_rtos.py +298 -0
- pyGdbToolkit/cmd_secscan.py +466 -0
- pyGdbToolkit/cmd_svd.py +1208 -0
- pyGdbToolkit/diagnostic_runtime.py +52 -0
- pyGdbToolkit/rtos/__init__.py +8 -0
- pyGdbToolkit/rtos/camelot.py +513 -0
- pyGdbToolkit/session.py +233 -0
- pyGdbToolkit/svd.py +1194 -0
- pyGdbToolkit/target_memory.py +190 -0
- pygdbtoolkit-0.1.0.dist-info/METADATA +586 -0
- pygdbtoolkit-0.1.0.dist-info/RECORD +36 -0
- pygdbtoolkit-0.1.0.dist-info/WHEEL +5 -0
- pygdbtoolkit-0.1.0.dist-info/top_level.txt +1 -0
pyGdbToolkit/cmd_svd.py
ADDED
|
@@ -0,0 +1,1208 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 H2Lab Development Team
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""The ``svd`` GDB command for CMSIS-SVD inspection, manipulation, and monitoring."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
import glob
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import gdb
|
|
16
|
+
from rich import box
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
from rich.panel import Panel
|
|
19
|
+
from rich.table import Table
|
|
20
|
+
from rich.text import Text
|
|
21
|
+
|
|
22
|
+
from .arch.arm.session_state import device_report
|
|
23
|
+
from .session import SESSION as TOOLKIT_SESSION
|
|
24
|
+
from .session import SessionSlice
|
|
25
|
+
from .svd import (
|
|
26
|
+
SvdDevice,
|
|
27
|
+
SvdError,
|
|
28
|
+
SvdNotFoundError,
|
|
29
|
+
SvdPeripheral,
|
|
30
|
+
SvdRegister,
|
|
31
|
+
parse_svd_file,
|
|
32
|
+
resolve_and_fetch_svd,
|
|
33
|
+
)
|
|
34
|
+
from .target_memory import TargetMemory, TargetReadError
|
|
35
|
+
|
|
36
|
+
CONSOLE = Console(force_terminal=True)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class SvdSessionState(SessionSlice):
|
|
41
|
+
"""Encapsulate the active SVD device model and dictionary in the GDB session."""
|
|
42
|
+
|
|
43
|
+
device: SvdDevice | None = None
|
|
44
|
+
svd_dict: dict[str, Any] | None = None
|
|
45
|
+
svd_path: Path | None = None
|
|
46
|
+
watchpoints: list[SvdWatchpoint] = field(default_factory=list)
|
|
47
|
+
|
|
48
|
+
def reset(self) -> None:
|
|
49
|
+
"""Drop the loaded SVD model and delete the registered register watchpoints."""
|
|
50
|
+
for watchpoint in self.watchpoints:
|
|
51
|
+
watchpoint.delete()
|
|
52
|
+
self.watchpoints.clear()
|
|
53
|
+
self.device = None
|
|
54
|
+
self.svd_dict = None
|
|
55
|
+
self.svd_path = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
SESSION = TOOLKIT_SESSION.state(SvdSessionState)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def is_svd_loaded() -> bool:
|
|
62
|
+
"""Return whether an SVD file is loaded in the current GDB session."""
|
|
63
|
+
return SESSION.device is not None and SESSION.svd_path is not None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _get_c_type_for_size(size_bits: int) -> str:
|
|
67
|
+
"""Return the canonical C integer type name matching a register bit width.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
size_bits : int
|
|
72
|
+
Register size in bits.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
str
|
|
77
|
+
C type identifier.
|
|
78
|
+
"""
|
|
79
|
+
if size_bits <= 8:
|
|
80
|
+
return "unsigned char"
|
|
81
|
+
if size_bits <= 16:
|
|
82
|
+
return "unsigned short"
|
|
83
|
+
if size_bits <= 32:
|
|
84
|
+
return "unsigned int"
|
|
85
|
+
return "unsigned long long"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _parse_numeric_value(val_str: str) -> int:
|
|
89
|
+
"""Parse a numeric string supporting hex, binary, and decimal formats.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
val_str : str
|
|
94
|
+
Numeric representation.
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
int
|
|
99
|
+
Decoded integer value.
|
|
100
|
+
"""
|
|
101
|
+
val_str = val_str.strip()
|
|
102
|
+
if val_str.startswith("#"):
|
|
103
|
+
return int(val_str[1:], 16)
|
|
104
|
+
if val_str.startswith(("0x", "0X")):
|
|
105
|
+
return int(val_str, 16)
|
|
106
|
+
if val_str.startswith(("0b", "0B")):
|
|
107
|
+
return int(val_str, 2)
|
|
108
|
+
return int(val_str, 10)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _read_register_value(reader: TargetMemory, base_address: int, reg: SvdRegister) -> int:
|
|
112
|
+
"""Read a register value from target memory.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
reader : TargetMemory
|
|
117
|
+
Target memory reader.
|
|
118
|
+
base_address : int
|
|
119
|
+
Base physical address of the peripheral.
|
|
120
|
+
reg : SvdRegister
|
|
121
|
+
Register definition.
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
int
|
|
126
|
+
The raw integer value read from memory.
|
|
127
|
+
"""
|
|
128
|
+
reg_addr = base_address + reg.address_offset
|
|
129
|
+
size_bytes = max(1, (reg.size + 7) // 8)
|
|
130
|
+
raw_bytes = reader.read_bytes(reg_addr, size_bytes)
|
|
131
|
+
return int.from_bytes(raw_bytes, byteorder="little")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _write_register_value(base_address: int, reg: SvdRegister, value: int) -> None:
|
|
135
|
+
"""Write an integer value into a target register.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
base_address : int
|
|
140
|
+
Base physical address of the peripheral.
|
|
141
|
+
reg : SvdRegister
|
|
142
|
+
Register definition.
|
|
143
|
+
value : int
|
|
144
|
+
Value to write.
|
|
145
|
+
"""
|
|
146
|
+
reg_addr = base_address + reg.address_offset
|
|
147
|
+
size_bytes = max(1, (reg.size + 7) // 8)
|
|
148
|
+
val_bytes = value.to_bytes(size_bytes, byteorder="little")
|
|
149
|
+
inferior = gdb.selected_inferior()
|
|
150
|
+
inferior.write_memory(reg_addr, val_bytes)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class SvdWatchpoint(gdb.Breakpoint):
|
|
154
|
+
"""A hardware or software watchpoint tracking modifications to an SVD register."""
|
|
155
|
+
|
|
156
|
+
def __init__(
|
|
157
|
+
self,
|
|
158
|
+
spec: str,
|
|
159
|
+
peripheral_name: str,
|
|
160
|
+
register_name: str,
|
|
161
|
+
register: SvdRegister,
|
|
162
|
+
address: int,
|
|
163
|
+
initial_value: int,
|
|
164
|
+
) -> None:
|
|
165
|
+
super().__init__(spec, type=gdb.BP_WATCHPOINT, wp_class=gdb.WP_WRITE)
|
|
166
|
+
self.peripheral_name = peripheral_name
|
|
167
|
+
self.register_name = register_name
|
|
168
|
+
self.register = register
|
|
169
|
+
self.address = address
|
|
170
|
+
self.last_value = initial_value
|
|
171
|
+
|
|
172
|
+
def stop(self) -> bool:
|
|
173
|
+
"""Handle watchpoint triggers and notify the user with decoded bitfield changes.
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
bool
|
|
178
|
+
Always True to halt execution and return control to the GDB prompt.
|
|
179
|
+
"""
|
|
180
|
+
new_val = self.last_value
|
|
181
|
+
try:
|
|
182
|
+
reader = TOOLKIT_SESSION.memory
|
|
183
|
+
size_bytes = max(1, (self.register.size + 7) // 8)
|
|
184
|
+
raw_bytes = reader.read_bytes(self.address, size_bytes)
|
|
185
|
+
new_val = int.from_bytes(raw_bytes, byteorder="little")
|
|
186
|
+
except TargetReadError:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
render_watchpoint_hit(
|
|
190
|
+
self.peripheral_name,
|
|
191
|
+
self.register_name,
|
|
192
|
+
self.register,
|
|
193
|
+
self.address,
|
|
194
|
+
self.last_value,
|
|
195
|
+
new_val,
|
|
196
|
+
)
|
|
197
|
+
self.last_value = new_val
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def render_watchpoint_hit(
|
|
202
|
+
peripheral_name: str,
|
|
203
|
+
register_name: str,
|
|
204
|
+
register: SvdRegister,
|
|
205
|
+
address: int,
|
|
206
|
+
old_value: int,
|
|
207
|
+
new_value: int,
|
|
208
|
+
) -> None:
|
|
209
|
+
"""Render a Rich notification when an SVD register watchpoint triggers.
|
|
210
|
+
|
|
211
|
+
Parameters
|
|
212
|
+
----------
|
|
213
|
+
peripheral_name : str
|
|
214
|
+
Name of the parent peripheral.
|
|
215
|
+
register_name : str
|
|
216
|
+
Name of the register.
|
|
217
|
+
register : SvdRegister
|
|
218
|
+
Register model definition.
|
|
219
|
+
address : int
|
|
220
|
+
Physical address of the register.
|
|
221
|
+
old_value : int
|
|
222
|
+
Value before write.
|
|
223
|
+
new_value : int
|
|
224
|
+
Value after write.
|
|
225
|
+
"""
|
|
226
|
+
hex_len = max(2, (register.size + 3) // 4)
|
|
227
|
+
old_hex = f"0x{old_value:0{hex_len}X}"
|
|
228
|
+
new_hex = f"0x{new_value:0{hex_len}X}"
|
|
229
|
+
|
|
230
|
+
title_text = Text.assemble(
|
|
231
|
+
("SVD Watchpoint Hit: ", "bold yellow"),
|
|
232
|
+
(f"{peripheral_name}->{register_name}", "bold cyan"),
|
|
233
|
+
(f" @ 0x{address:08X}", "bold magenta"),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
table = Table(
|
|
237
|
+
title=title_text,
|
|
238
|
+
box=box.SIMPLE_HEAVY,
|
|
239
|
+
header_style="bold cyan",
|
|
240
|
+
show_header=True,
|
|
241
|
+
)
|
|
242
|
+
table.add_column("Property", style="bold", no_wrap=True)
|
|
243
|
+
table.add_column("Value")
|
|
244
|
+
table.add_row("Previous Value", old_hex)
|
|
245
|
+
table.add_row("New Value", Text(new_hex, style="bold green"))
|
|
246
|
+
table.add_row("Difference", f"0x{(old_value ^ new_value):0{hex_len}X}")
|
|
247
|
+
|
|
248
|
+
CONSOLE.print(table)
|
|
249
|
+
|
|
250
|
+
if register.fields:
|
|
251
|
+
field_table = Table(
|
|
252
|
+
title="Modified Bitfields",
|
|
253
|
+
box=box.SIMPLE_HEAVY,
|
|
254
|
+
header_style="bold cyan",
|
|
255
|
+
show_header=True,
|
|
256
|
+
)
|
|
257
|
+
field_table.add_column("Bits", style="cyan", no_wrap=True)
|
|
258
|
+
field_table.add_column("Field", style="bold", no_wrap=True)
|
|
259
|
+
field_table.add_column("Old Value", style="yellow")
|
|
260
|
+
field_table.add_column("New Value", style="bold green")
|
|
261
|
+
field_table.add_column("Description")
|
|
262
|
+
|
|
263
|
+
diff_count = 0
|
|
264
|
+
for field_obj in register.fields:
|
|
265
|
+
old_field_val = field_obj.extract_value(old_value)
|
|
266
|
+
new_field_val = field_obj.extract_value(new_value)
|
|
267
|
+
if old_field_val != new_field_val:
|
|
268
|
+
diff_count += 1
|
|
269
|
+
if field_obj.bit_width == 1:
|
|
270
|
+
bits_str = f"[{field_obj.bit_offset}]"
|
|
271
|
+
else:
|
|
272
|
+
msb = field_obj.bit_offset + field_obj.bit_width - 1
|
|
273
|
+
bits_str = f"[{msb}:{field_obj.bit_offset}]"
|
|
274
|
+
|
|
275
|
+
f_hex_len = max(1, (field_obj.bit_width + 3) // 4)
|
|
276
|
+
field_table.add_row(
|
|
277
|
+
bits_str,
|
|
278
|
+
field_obj.name,
|
|
279
|
+
f"0x{old_field_val:0{f_hex_len}X}",
|
|
280
|
+
f"0x{new_field_val:0{f_hex_len}X}",
|
|
281
|
+
field_obj.description or "--",
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
if diff_count > 0:
|
|
285
|
+
CONSOLE.print(field_table)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def render_load_success(
|
|
289
|
+
device: SvdDevice,
|
|
290
|
+
svd_filename: str,
|
|
291
|
+
cache_path: Path,
|
|
292
|
+
vendor: str | None,
|
|
293
|
+
product_line: str | None,
|
|
294
|
+
) -> None:
|
|
295
|
+
"""Render a summary table for a successfully detected and loaded SVD file.
|
|
296
|
+
|
|
297
|
+
Parameters
|
|
298
|
+
----------
|
|
299
|
+
device : SvdDevice
|
|
300
|
+
Parsed device object.
|
|
301
|
+
svd_filename : str
|
|
302
|
+
Matched SVD filename.
|
|
303
|
+
cache_path : Path
|
|
304
|
+
Local file path to the SVD XML.
|
|
305
|
+
vendor : str | None
|
|
306
|
+
Target vendor identifier.
|
|
307
|
+
product_line : str | None
|
|
308
|
+
Target product line name.
|
|
309
|
+
"""
|
|
310
|
+
table = Table(
|
|
311
|
+
title="SVD Target Auto-Detection & Load",
|
|
312
|
+
box=box.SIMPLE_HEAVY,
|
|
313
|
+
header_style="bold cyan",
|
|
314
|
+
show_header=True,
|
|
315
|
+
)
|
|
316
|
+
table.add_column("Property", style="bold", no_wrap=True)
|
|
317
|
+
table.add_column("Value")
|
|
318
|
+
if vendor:
|
|
319
|
+
table.add_row("Detected Vendor", vendor)
|
|
320
|
+
if product_line:
|
|
321
|
+
table.add_row("Product Line", product_line)
|
|
322
|
+
table.add_row("Matched SVD", Text(svd_filename, style="bold green"))
|
|
323
|
+
table.add_row("Device Name", device.name)
|
|
324
|
+
if device.version:
|
|
325
|
+
table.add_row("SVD Schema Version", device.version)
|
|
326
|
+
table.add_row("Peripherals Loaded", str(len(device.peripherals)))
|
|
327
|
+
table.add_row("Local Cache Path", str(cache_path))
|
|
328
|
+
CONSOLE.print(table)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def render_file_load_success(device: SvdDevice, file_path: Path) -> None:
|
|
332
|
+
"""Render a summary table for an explicitly loaded SVD file.
|
|
333
|
+
|
|
334
|
+
Parameters
|
|
335
|
+
----------
|
|
336
|
+
device : SvdDevice
|
|
337
|
+
Parsed SVD device.
|
|
338
|
+
file_path : Path
|
|
339
|
+
Path to the loaded SVD file.
|
|
340
|
+
"""
|
|
341
|
+
table = Table(
|
|
342
|
+
title="SVD File Loaded Successfully",
|
|
343
|
+
box=box.SIMPLE_HEAVY,
|
|
344
|
+
header_style="bold cyan",
|
|
345
|
+
show_header=True,
|
|
346
|
+
)
|
|
347
|
+
table.add_column("Property", style="bold", no_wrap=True)
|
|
348
|
+
table.add_column("Value")
|
|
349
|
+
table.add_row("File Path", str(file_path))
|
|
350
|
+
table.add_row("Device Name", Text(device.name, style="bold green"))
|
|
351
|
+
if device.vendor:
|
|
352
|
+
table.add_row("Vendor", device.vendor)
|
|
353
|
+
if device.version:
|
|
354
|
+
table.add_row("Version", device.version)
|
|
355
|
+
table.add_row("Peripherals Loaded", str(len(device.peripherals)))
|
|
356
|
+
CONSOLE.print(table)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def render_peripheral_state(peripheral: SvdPeripheral, reader: TargetMemory) -> None:
|
|
360
|
+
"""Render the canonical status of all registers in a peripheral.
|
|
361
|
+
|
|
362
|
+
Parameters
|
|
363
|
+
----------
|
|
364
|
+
peripheral : SvdPeripheral
|
|
365
|
+
Target peripheral model.
|
|
366
|
+
reader : TargetMemory
|
|
367
|
+
Memory reader instance.
|
|
368
|
+
"""
|
|
369
|
+
title_text = f"Peripheral: {peripheral.name} @ 0x{peripheral.base_address:08X}" + (
|
|
370
|
+
f" ({peripheral.description})" if peripheral.description else ""
|
|
371
|
+
)
|
|
372
|
+
table = Table(
|
|
373
|
+
title=title_text,
|
|
374
|
+
box=box.SIMPLE_HEAVY,
|
|
375
|
+
header_style="bold cyan",
|
|
376
|
+
show_header=True,
|
|
377
|
+
)
|
|
378
|
+
table.add_column("Offset", style="cyan", no_wrap=True)
|
|
379
|
+
table.add_column("Register", style="bold", no_wrap=True)
|
|
380
|
+
table.add_column("Address", style="magenta", no_wrap=True)
|
|
381
|
+
table.add_column("Size", no_wrap=True)
|
|
382
|
+
table.add_column("Access", no_wrap=True)
|
|
383
|
+
table.add_column("Reset Value", no_wrap=True)
|
|
384
|
+
table.add_column("Current Value", style="bold")
|
|
385
|
+
table.add_column("Description")
|
|
386
|
+
|
|
387
|
+
for reg in peripheral.registers:
|
|
388
|
+
reg_addr = peripheral.base_address + reg.address_offset
|
|
389
|
+
hex_len = max(2, (reg.size + 3) // 4)
|
|
390
|
+
reset_str = f"0x{reg.reset_value:0{hex_len}X}" if reg.reset_value is not None else "--"
|
|
391
|
+
access_str = reg.access or "--"
|
|
392
|
+
size_str = f"{reg.size}-bit"
|
|
393
|
+
|
|
394
|
+
try:
|
|
395
|
+
val = _read_register_value(reader, peripheral.base_address, reg)
|
|
396
|
+
val_str = Text(f"0x{val:0{hex_len}X}", style="bold green")
|
|
397
|
+
except TargetReadError:
|
|
398
|
+
val_str = Text("[Read Error]", style="bold yellow")
|
|
399
|
+
|
|
400
|
+
table.add_row(
|
|
401
|
+
f"+0x{reg.address_offset:04X}",
|
|
402
|
+
reg.name,
|
|
403
|
+
f"0x{reg_addr:08X}",
|
|
404
|
+
size_str,
|
|
405
|
+
access_str,
|
|
406
|
+
reset_str,
|
|
407
|
+
val_str,
|
|
408
|
+
reg.description or "--",
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
CONSOLE.print(table)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def render_register_detail(peripheral: SvdPeripheral, reg: SvdRegister, value: int) -> None:
|
|
415
|
+
"""Render the detailed bitfield view for a specific register.
|
|
416
|
+
|
|
417
|
+
Parameters
|
|
418
|
+
----------
|
|
419
|
+
peripheral : SvdPeripheral
|
|
420
|
+
Parent peripheral model.
|
|
421
|
+
reg : SvdRegister
|
|
422
|
+
Register model.
|
|
423
|
+
value : int
|
|
424
|
+
Current register value read from target memory.
|
|
425
|
+
"""
|
|
426
|
+
reg_addr = peripheral.base_address + reg.address_offset
|
|
427
|
+
hex_len = max(2, (reg.size + 3) // 4)
|
|
428
|
+
raw_hex = f"0x{value:0{hex_len}X}"
|
|
429
|
+
bin_str = f"0b{value:0{reg.size}b}"
|
|
430
|
+
|
|
431
|
+
summary_table = Table(
|
|
432
|
+
title=f"Register: {peripheral.name}->{reg.name}",
|
|
433
|
+
box=box.SIMPLE_HEAVY,
|
|
434
|
+
header_style="bold cyan",
|
|
435
|
+
show_header=True,
|
|
436
|
+
)
|
|
437
|
+
summary_table.add_column("Property", style="bold", no_wrap=True)
|
|
438
|
+
summary_table.add_column("Value")
|
|
439
|
+
summary_table.add_row("Peripheral", f"{peripheral.name} (Base 0x{peripheral.base_address:08X})")
|
|
440
|
+
summary_table.add_row("Register Address", f"0x{reg_addr:08X} (+0x{reg.address_offset:04X})")
|
|
441
|
+
summary_table.add_row(
|
|
442
|
+
"Current Value", Text(f"{raw_hex} ({value}, {bin_str})", style="bold green")
|
|
443
|
+
)
|
|
444
|
+
if reg.reset_value is not None:
|
|
445
|
+
summary_table.add_row("Reset Value", f"0x{reg.reset_value:0{hex_len}X}")
|
|
446
|
+
summary_table.add_row("Access Rights", reg.access or "unspecified")
|
|
447
|
+
if reg.description:
|
|
448
|
+
summary_table.add_row("Description", reg.description)
|
|
449
|
+
CONSOLE.print(summary_table)
|
|
450
|
+
|
|
451
|
+
if not reg.fields:
|
|
452
|
+
CONSOLE.print(
|
|
453
|
+
Panel(
|
|
454
|
+
Text(
|
|
455
|
+
"No bitfield definitions available in SVD for this register.",
|
|
456
|
+
style="dim italic",
|
|
457
|
+
),
|
|
458
|
+
box=box.ROUNDED,
|
|
459
|
+
)
|
|
460
|
+
)
|
|
461
|
+
return
|
|
462
|
+
|
|
463
|
+
fields_table = Table(
|
|
464
|
+
title=f"Bitfields for {peripheral.name}->{reg.name}",
|
|
465
|
+
box=box.SIMPLE_HEAVY,
|
|
466
|
+
header_style="bold cyan",
|
|
467
|
+
show_header=True,
|
|
468
|
+
)
|
|
469
|
+
fields_table.add_column("Bits", style="cyan", no_wrap=True)
|
|
470
|
+
fields_table.add_column("Field", style="bold", no_wrap=True)
|
|
471
|
+
fields_table.add_column("Access", no_wrap=True)
|
|
472
|
+
fields_table.add_column("Value (Hex)", style="bold green", no_wrap=True)
|
|
473
|
+
fields_table.add_column("Value (Dec/Bin)", no_wrap=True)
|
|
474
|
+
fields_table.add_column("Reset", no_wrap=True)
|
|
475
|
+
fields_table.add_column("Description")
|
|
476
|
+
|
|
477
|
+
for field_obj in sorted(reg.fields, key=lambda f: f.bit_offset, reverse=True):
|
|
478
|
+
extracted = field_obj.extract_value(value)
|
|
479
|
+
f_hex_len = max(1, (field_obj.bit_width + 3) // 4)
|
|
480
|
+
f_hex = f"0x{extracted:0{f_hex_len}X}"
|
|
481
|
+
f_bin = f"0b{extracted:0{field_obj.bit_width}b}"
|
|
482
|
+
f_dec_bin = f"{extracted} ({f_bin})" if field_obj.bit_width > 1 else str(extracted)
|
|
483
|
+
|
|
484
|
+
if field_obj.bit_width == 1:
|
|
485
|
+
bits_str = f"[{field_obj.bit_offset}]"
|
|
486
|
+
else:
|
|
487
|
+
msb = field_obj.bit_offset + field_obj.bit_width - 1
|
|
488
|
+
bits_str = f"[{msb}:{field_obj.bit_offset}]"
|
|
489
|
+
|
|
490
|
+
reset_str = (
|
|
491
|
+
f"0x{field_obj.reset_value:0{f_hex_len}X}"
|
|
492
|
+
if field_obj.reset_value is not None
|
|
493
|
+
else "--"
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
fields_table.add_row(
|
|
497
|
+
bits_str,
|
|
498
|
+
field_obj.name,
|
|
499
|
+
field_obj.access or "--",
|
|
500
|
+
f_hex,
|
|
501
|
+
f_dec_bin,
|
|
502
|
+
reset_str,
|
|
503
|
+
field_obj.description or "--",
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
CONSOLE.print(fields_table)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def render_help() -> None:
|
|
510
|
+
"""Render the command overview and help table."""
|
|
511
|
+
table = Table(
|
|
512
|
+
title="CMSIS-SVD Target Inspection & Control Commands",
|
|
513
|
+
box=box.SIMPLE_HEAVY,
|
|
514
|
+
header_style="bold cyan",
|
|
515
|
+
show_header=True,
|
|
516
|
+
)
|
|
517
|
+
table.add_column("Command Syntax", style="bold yellow", no_wrap=True)
|
|
518
|
+
table.add_column("Description")
|
|
519
|
+
table.add_row(
|
|
520
|
+
"svd load", "Auto-detect target CPUID/SoC, fetch and load matching SVD definition"
|
|
521
|
+
)
|
|
522
|
+
table.add_row("svd read <file.svd>", "Load an explicit SVD XML file from local filesystem")
|
|
523
|
+
table.add_row(
|
|
524
|
+
"svd show <device>", "Display canonical memory state and registers of a peripheral"
|
|
525
|
+
)
|
|
526
|
+
table.add_row(
|
|
527
|
+
"svd show <device> <register>", "Display detailed register state and decoded bitfields"
|
|
528
|
+
)
|
|
529
|
+
table.add_row(
|
|
530
|
+
"svd write <device> <reg> <hex_val>", "Write numeric/hex value into peripheral register"
|
|
531
|
+
)
|
|
532
|
+
table.add_row(
|
|
533
|
+
"svd monitor <device> <reg>", "Set a watchpoint on a register and report bitfield diffs"
|
|
534
|
+
)
|
|
535
|
+
table.add_row(
|
|
536
|
+
"svd dump <device> <file.json>", "Dump snapshot of device/peripheral state into a JSON file"
|
|
537
|
+
)
|
|
538
|
+
table.add_row("svd list", "List the names of all peripherals in the loaded SVD device")
|
|
539
|
+
table.add_row("svd help", "Show this command reference")
|
|
540
|
+
CONSOLE.print(table)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def render_peripheral_list(device: SvdDevice) -> None:
|
|
544
|
+
"""Render a table listing the names of all peripherals in a device.
|
|
545
|
+
|
|
546
|
+
Parameters
|
|
547
|
+
----------
|
|
548
|
+
device : SvdDevice
|
|
549
|
+
Loaded SVD device.
|
|
550
|
+
"""
|
|
551
|
+
table = Table(
|
|
552
|
+
title=f"Peripherals: {device.name}",
|
|
553
|
+
box=box.SIMPLE_HEAVY,
|
|
554
|
+
header_style="bold cyan",
|
|
555
|
+
show_header=True,
|
|
556
|
+
)
|
|
557
|
+
table.add_column("Name", style="bold", no_wrap=True)
|
|
558
|
+
table.add_column("Base Address", style="magenta", no_wrap=True)
|
|
559
|
+
table.add_column("Description")
|
|
560
|
+
for periph in sorted(device.peripherals, key=lambda p: p.base_address):
|
|
561
|
+
table.add_row(
|
|
562
|
+
periph.name,
|
|
563
|
+
f"0x{periph.base_address:08X}",
|
|
564
|
+
periph.description or "--",
|
|
565
|
+
)
|
|
566
|
+
CONSOLE.print(table)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
class SvdCmd(gdb.Command):
|
|
570
|
+
"""Inspect, manipulate, and monitor hardware registers using CMSIS-SVD definitions."""
|
|
571
|
+
|
|
572
|
+
def __init__(self) -> None:
|
|
573
|
+
super().__init__("svd", gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
|
|
574
|
+
|
|
575
|
+
def is_svd_loaded(self) -> bool:
|
|
576
|
+
"""Return whether an SVD file is loaded in the current GDB session."""
|
|
577
|
+
return is_svd_loaded()
|
|
578
|
+
|
|
579
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
580
|
+
"""Show the svd command reference.
|
|
581
|
+
|
|
582
|
+
Parameters
|
|
583
|
+
----------
|
|
584
|
+
arg : str
|
|
585
|
+
The argument string supplied by the user.
|
|
586
|
+
from_tty : bool
|
|
587
|
+
Whether GDB invoked the command interactively from its terminal.
|
|
588
|
+
"""
|
|
589
|
+
del arg
|
|
590
|
+
del from_tty
|
|
591
|
+
render_help()
|
|
592
|
+
|
|
593
|
+
def complete_args(self, subcmd: str, text: str, word: str) -> list[str]:
|
|
594
|
+
"""Provide intelligent GDB auto-completion for SVD command arguments.
|
|
595
|
+
|
|
596
|
+
Parameters
|
|
597
|
+
----------
|
|
598
|
+
subcmd : str
|
|
599
|
+
Subcommand name being completed.
|
|
600
|
+
text : str
|
|
601
|
+
The full argument text typed so far.
|
|
602
|
+
word : str
|
|
603
|
+
The specific word currently being completed.
|
|
604
|
+
|
|
605
|
+
Returns
|
|
606
|
+
-------
|
|
607
|
+
list[str]
|
|
608
|
+
Matching completion candidates.
|
|
609
|
+
"""
|
|
610
|
+
command_prefix = f"svd {subcmd}"
|
|
611
|
+
arg_text = text
|
|
612
|
+
if arg_text.startswith(command_prefix):
|
|
613
|
+
arg_text = arg_text[len(command_prefix) :].lstrip()
|
|
614
|
+
tokens = arg_text.split()
|
|
615
|
+
|
|
616
|
+
# Auto-completion for file path on 'svd read <file.svd>'
|
|
617
|
+
if subcmd == "read":
|
|
618
|
+
if not tokens or (len(tokens) == 1 and not arg_text.endswith(" ")):
|
|
619
|
+
pattern = f"{word}*"
|
|
620
|
+
expanded = os.path.expanduser(pattern)
|
|
621
|
+
matches = glob.glob(expanded)
|
|
622
|
+
results: list[str] = []
|
|
623
|
+
for m in matches:
|
|
624
|
+
if os.path.isdir(m):
|
|
625
|
+
results.append(f"{m}/")
|
|
626
|
+
elif m.lower().endswith((".svd", ".xml")):
|
|
627
|
+
results.append(m)
|
|
628
|
+
return results
|
|
629
|
+
return []
|
|
630
|
+
|
|
631
|
+
# Auto-completion for subcommands that target peripherals and registers
|
|
632
|
+
if subcmd in ("show", "write", "monitor", "dump"):
|
|
633
|
+
if SESSION.device is None:
|
|
634
|
+
return []
|
|
635
|
+
|
|
636
|
+
# Completing device / peripheral name
|
|
637
|
+
if not tokens or (len(tokens) == 1 and not arg_text.endswith(" ")):
|
|
638
|
+
periph_names = [p.name for p in SESSION.device.peripherals]
|
|
639
|
+
if subcmd == "dump":
|
|
640
|
+
periph_names.append("all")
|
|
641
|
+
periph_names.append(SESSION.device.name)
|
|
642
|
+
return [p for p in periph_names if p.upper().startswith(word.upper())]
|
|
643
|
+
|
|
644
|
+
# Completing register name
|
|
645
|
+
if subcmd in ("show", "write", "monitor"):
|
|
646
|
+
if (len(tokens) == 1 and arg_text.endswith(" ")) or (
|
|
647
|
+
len(tokens) == 2 and not arg_text.endswith(" ")
|
|
648
|
+
):
|
|
649
|
+
periph_name = tokens[0]
|
|
650
|
+
periph = SESSION.device.get_peripheral(periph_name)
|
|
651
|
+
if periph is not None:
|
|
652
|
+
reg_names = [r.name for r in periph.registers]
|
|
653
|
+
return [r for r in reg_names if r.upper().startswith(word.upper())]
|
|
654
|
+
|
|
655
|
+
return []
|
|
656
|
+
|
|
657
|
+
def _invoke_load(self, args: list[str]) -> None:
|
|
658
|
+
"""Handle 'svd load' subcommand.
|
|
659
|
+
|
|
660
|
+
Parameters
|
|
661
|
+
----------
|
|
662
|
+
args : list[str]
|
|
663
|
+
Subcommand arguments (none expected).
|
|
664
|
+
|
|
665
|
+
Raises
|
|
666
|
+
------
|
|
667
|
+
gdb.GdbError
|
|
668
|
+
If target memory cannot be read or detection fails.
|
|
669
|
+
"""
|
|
670
|
+
if args:
|
|
671
|
+
raise gdb.GdbError("'svd load' takes no arguments.")
|
|
672
|
+
|
|
673
|
+
try:
|
|
674
|
+
report = device_report()
|
|
675
|
+
except TargetReadError as err:
|
|
676
|
+
raise gdb.GdbError(f"Cannot read target memory: {err}") from err
|
|
677
|
+
|
|
678
|
+
try:
|
|
679
|
+
svd_path, candidate_name = resolve_and_fetch_svd(report)
|
|
680
|
+
device = parse_svd_file(svd_path)
|
|
681
|
+
SESSION.device = device
|
|
682
|
+
SESSION.svd_dict = device.to_dict()
|
|
683
|
+
SESSION.svd_path = svd_path
|
|
684
|
+
|
|
685
|
+
prod_line = report.product_line.value if report.product_line.is_available else None
|
|
686
|
+
render_load_success(
|
|
687
|
+
device=device,
|
|
688
|
+
svd_filename=candidate_name,
|
|
689
|
+
cache_path=svd_path,
|
|
690
|
+
vendor=report.vendor,
|
|
691
|
+
product_line=prod_line,
|
|
692
|
+
)
|
|
693
|
+
except SvdNotFoundError as err:
|
|
694
|
+
prod_info = (
|
|
695
|
+
f"{report.vendor} {report.product_line.value}"
|
|
696
|
+
if report.product_line.is_available
|
|
697
|
+
else f"vendor {report.vendor}"
|
|
698
|
+
)
|
|
699
|
+
CONSOLE.print(
|
|
700
|
+
Panel(
|
|
701
|
+
Text.assemble(
|
|
702
|
+
("SVD Not Found: ", "bold yellow"),
|
|
703
|
+
(f"No matching SVD file found for {prod_info}.\n", "default"),
|
|
704
|
+
("Details: ", "bold"),
|
|
705
|
+
(f"{err}\n", "dim"),
|
|
706
|
+
("Tip: ", "bold cyan"),
|
|
707
|
+
(
|
|
708
|
+
"You can supply an SVD file explicitly via 'svd read <file.svd>'.",
|
|
709
|
+
"default",
|
|
710
|
+
),
|
|
711
|
+
),
|
|
712
|
+
title="SVD Resolution Notice",
|
|
713
|
+
box=box.ROUNDED,
|
|
714
|
+
)
|
|
715
|
+
)
|
|
716
|
+
except SvdError as err:
|
|
717
|
+
raise gdb.GdbError(f"SVD operation failed: {err}") from err
|
|
718
|
+
|
|
719
|
+
def _invoke_read(self, args: list[str]) -> None:
|
|
720
|
+
"""Handle 'svd read <file.svd>' subcommand.
|
|
721
|
+
|
|
722
|
+
Parameters
|
|
723
|
+
----------
|
|
724
|
+
args : list[str]
|
|
725
|
+
Subcommand arguments (expecting 1 SVD file path).
|
|
726
|
+
|
|
727
|
+
Raises
|
|
728
|
+
------
|
|
729
|
+
gdb.GdbError
|
|
730
|
+
If arguments are invalid or file parsing fails.
|
|
731
|
+
"""
|
|
732
|
+
if len(args) != 1:
|
|
733
|
+
raise gdb.GdbError("Usage: svd read <file.svd>")
|
|
734
|
+
|
|
735
|
+
file_path = Path(args[0]).expanduser().resolve()
|
|
736
|
+
if not file_path.is_file():
|
|
737
|
+
raise gdb.GdbError(f"SVD file not found: {file_path}")
|
|
738
|
+
|
|
739
|
+
try:
|
|
740
|
+
device = parse_svd_file(file_path)
|
|
741
|
+
SESSION.device = device
|
|
742
|
+
SESSION.svd_dict = device.to_dict()
|
|
743
|
+
SESSION.svd_path = file_path
|
|
744
|
+
render_file_load_success(device, file_path)
|
|
745
|
+
except SvdError as err:
|
|
746
|
+
raise gdb.GdbError(f"Failed to parse SVD file: {err}") from err
|
|
747
|
+
|
|
748
|
+
def _invoke_show(self, args: list[str]) -> None:
|
|
749
|
+
"""Handle 'svd show <device_name> [<register_name>]' subcommand.
|
|
750
|
+
|
|
751
|
+
Parameters
|
|
752
|
+
----------
|
|
753
|
+
args : list[str]
|
|
754
|
+
Subcommand arguments: <device_name> [<register_name>].
|
|
755
|
+
|
|
756
|
+
Raises
|
|
757
|
+
------
|
|
758
|
+
gdb.GdbError
|
|
759
|
+
If arguments are invalid or target reading fails.
|
|
760
|
+
"""
|
|
761
|
+
if not args:
|
|
762
|
+
raise gdb.GdbError("Usage: svd show <device_name> [<register_name>]")
|
|
763
|
+
|
|
764
|
+
if SESSION.device is None:
|
|
765
|
+
raise gdb.GdbError(
|
|
766
|
+
"No SVD definition currently loaded. Run 'svd load' or 'svd read <file.svd>' first."
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
dev_name = args[0]
|
|
770
|
+
periph = SESSION.device.get_peripheral(dev_name)
|
|
771
|
+
if periph is None:
|
|
772
|
+
raise gdb.GdbError(
|
|
773
|
+
f"Peripheral '{dev_name}' not found in loaded SVD device '{SESSION.device.name}'."
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
try:
|
|
777
|
+
reader = TOOLKIT_SESSION.memory
|
|
778
|
+
except TargetReadError as err:
|
|
779
|
+
raise gdb.GdbError(f"Cannot access target memory: {err}") from err
|
|
780
|
+
|
|
781
|
+
if len(args) == 1:
|
|
782
|
+
render_peripheral_state(periph, reader)
|
|
783
|
+
elif len(args) == 2:
|
|
784
|
+
reg_name = args[1]
|
|
785
|
+
reg = periph.get_register(reg_name)
|
|
786
|
+
if reg is None:
|
|
787
|
+
raise gdb.GdbError(
|
|
788
|
+
f"Register '{reg_name}' not found in peripheral '{periph.name}'."
|
|
789
|
+
)
|
|
790
|
+
try:
|
|
791
|
+
val = _read_register_value(reader, periph.base_address, reg)
|
|
792
|
+
except TargetReadError as err:
|
|
793
|
+
raise gdb.GdbError(
|
|
794
|
+
f"Failed to read register {periph.name}->{reg.name}: {err}"
|
|
795
|
+
) from err
|
|
796
|
+
render_register_detail(periph, reg, val)
|
|
797
|
+
else:
|
|
798
|
+
raise gdb.GdbError(
|
|
799
|
+
"Too many arguments. Usage: svd show <device_name> [<register_name>]"
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
def _invoke_list(self, args: list[str]) -> None:
|
|
803
|
+
"""Handle 'svd list' subcommand.
|
|
804
|
+
|
|
805
|
+
Parameters
|
|
806
|
+
----------
|
|
807
|
+
args : list[str]
|
|
808
|
+
Subcommand arguments (none expected).
|
|
809
|
+
|
|
810
|
+
Raises
|
|
811
|
+
------
|
|
812
|
+
gdb.GdbError
|
|
813
|
+
If no SVD device is currently loaded.
|
|
814
|
+
"""
|
|
815
|
+
if args:
|
|
816
|
+
raise gdb.GdbError("Usage: svd list")
|
|
817
|
+
|
|
818
|
+
if SESSION.device is None:
|
|
819
|
+
raise gdb.GdbError(
|
|
820
|
+
"No SVD definition currently loaded. Run 'svd load' or 'svd read <file.svd>' first."
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
render_peripheral_list(SESSION.device)
|
|
824
|
+
|
|
825
|
+
def _invoke_write(self, args: list[str]) -> None:
|
|
826
|
+
"""Handle 'svd write <device_name> <register_name> <value_hex>' subcommand.
|
|
827
|
+
|
|
828
|
+
Parameters
|
|
829
|
+
----------
|
|
830
|
+
args : list[str]
|
|
831
|
+
Subcommand arguments.
|
|
832
|
+
|
|
833
|
+
Raises
|
|
834
|
+
------
|
|
835
|
+
gdb.GdbError
|
|
836
|
+
If arguments are invalid or writing fails.
|
|
837
|
+
"""
|
|
838
|
+
if len(args) != 3:
|
|
839
|
+
raise gdb.GdbError("Usage: svd write <device_name> <register_name> <value_hex>")
|
|
840
|
+
|
|
841
|
+
if SESSION.device is None:
|
|
842
|
+
raise gdb.GdbError(
|
|
843
|
+
"No SVD definition currently loaded. Run 'svd load' or 'svd read <file.svd>' first."
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
dev_name, reg_name, val_str = args[0], args[1], args[2]
|
|
847
|
+
periph = SESSION.device.get_peripheral(dev_name)
|
|
848
|
+
if periph is None:
|
|
849
|
+
raise gdb.GdbError(
|
|
850
|
+
f"Peripheral '{dev_name}' not found in loaded SVD device '{SESSION.device.name}'."
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
reg = periph.get_register(reg_name)
|
|
854
|
+
if reg is None:
|
|
855
|
+
raise gdb.GdbError(f"Register '{reg_name}' not found in peripheral '{periph.name}'.")
|
|
856
|
+
|
|
857
|
+
try:
|
|
858
|
+
value = _parse_numeric_value(val_str)
|
|
859
|
+
except ValueError as err:
|
|
860
|
+
raise gdb.GdbError(
|
|
861
|
+
f"Invalid numeric value '{val_str}': must be hex (0x...), bin (0b...), or decimal."
|
|
862
|
+
) from err
|
|
863
|
+
|
|
864
|
+
max_val = (1 << reg.size) - 1
|
|
865
|
+
if value < 0 or value > max_val:
|
|
866
|
+
raise gdb.GdbError(
|
|
867
|
+
f"Value 0x{value:X} exceeds register width ({reg.size}-bit, max: 0x{max_val:X})."
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
try:
|
|
871
|
+
reader = TOOLKIT_SESSION.memory
|
|
872
|
+
old_val = _read_register_value(reader, periph.base_address, reg)
|
|
873
|
+
except TargetReadError:
|
|
874
|
+
old_val = None
|
|
875
|
+
|
|
876
|
+
try:
|
|
877
|
+
_write_register_value(periph.base_address, reg, value)
|
|
878
|
+
except Exception as err:
|
|
879
|
+
raise gdb.GdbError(
|
|
880
|
+
f"Failed to write to register {periph.name}->{reg.name}: {err}"
|
|
881
|
+
) from err
|
|
882
|
+
|
|
883
|
+
try:
|
|
884
|
+
readback = _read_register_value(reader, periph.base_address, reg)
|
|
885
|
+
except TargetReadError:
|
|
886
|
+
readback = None
|
|
887
|
+
|
|
888
|
+
hex_len = max(2, (reg.size + 3) // 4)
|
|
889
|
+
reg_addr = periph.base_address + reg.address_offset
|
|
890
|
+
|
|
891
|
+
table = Table(
|
|
892
|
+
title=f"Write Register: {periph.name}->{reg.name}",
|
|
893
|
+
box=box.SIMPLE_HEAVY,
|
|
894
|
+
header_style="bold cyan",
|
|
895
|
+
show_header=True,
|
|
896
|
+
)
|
|
897
|
+
table.add_column("Property", style="bold", no_wrap=True)
|
|
898
|
+
table.add_column("Value")
|
|
899
|
+
table.add_row("Address", f"0x{reg_addr:08X}")
|
|
900
|
+
if old_val is not None:
|
|
901
|
+
table.add_row("Previous Value", f"0x{old_val:0{hex_len}X}")
|
|
902
|
+
table.add_row("Written Value", Text(f"0x{value:0{hex_len}X}", style="bold green"))
|
|
903
|
+
if readback is not None:
|
|
904
|
+
table.add_row("Readback Value", f"0x{readback:0{hex_len}X}")
|
|
905
|
+
CONSOLE.print(table)
|
|
906
|
+
|
|
907
|
+
def _invoke_monitor(self, args: list[str]) -> None:
|
|
908
|
+
"""Handle 'svd monitor <device_name> <regname>' subcommand.
|
|
909
|
+
|
|
910
|
+
Parameters
|
|
911
|
+
----------
|
|
912
|
+
args : list[str]
|
|
913
|
+
Subcommand arguments.
|
|
914
|
+
|
|
915
|
+
Raises
|
|
916
|
+
------
|
|
917
|
+
gdb.GdbError
|
|
918
|
+
If arguments are invalid or watchpoint creation fails.
|
|
919
|
+
"""
|
|
920
|
+
if len(args) != 2:
|
|
921
|
+
raise gdb.GdbError("Usage: svd monitor <device_name> <regname>")
|
|
922
|
+
|
|
923
|
+
if SESSION.device is None:
|
|
924
|
+
raise gdb.GdbError(
|
|
925
|
+
"No SVD definition currently loaded. Run 'svd load' or 'svd read <file.svd>' first."
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
dev_name, reg_name = args[0], args[1]
|
|
929
|
+
periph = SESSION.device.get_peripheral(dev_name)
|
|
930
|
+
if periph is None:
|
|
931
|
+
raise gdb.GdbError(
|
|
932
|
+
f"Peripheral '{dev_name}' not found in loaded SVD device '{SESSION.device.name}'."
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
reg = periph.get_register(reg_name)
|
|
936
|
+
if reg is None:
|
|
937
|
+
raise gdb.GdbError(f"Register '{reg_name}' not found in peripheral '{periph.name}'.")
|
|
938
|
+
|
|
939
|
+
reg_addr = periph.base_address + reg.address_offset
|
|
940
|
+
try:
|
|
941
|
+
reader = TOOLKIT_SESSION.memory
|
|
942
|
+
initial_val = _read_register_value(reader, periph.base_address, reg)
|
|
943
|
+
except TargetReadError:
|
|
944
|
+
initial_val = 0
|
|
945
|
+
|
|
946
|
+
c_type = _get_c_type_for_size(reg.size)
|
|
947
|
+
spec = f"*({c_type} *){reg_addr:#x}"
|
|
948
|
+
|
|
949
|
+
try:
|
|
950
|
+
wp = SvdWatchpoint(
|
|
951
|
+
spec=spec,
|
|
952
|
+
peripheral_name=periph.name,
|
|
953
|
+
register_name=reg.name,
|
|
954
|
+
register=reg,
|
|
955
|
+
address=reg_addr,
|
|
956
|
+
initial_value=initial_val,
|
|
957
|
+
)
|
|
958
|
+
SESSION.watchpoints.append(wp)
|
|
959
|
+
except gdb.error as err:
|
|
960
|
+
raise gdb.GdbError(f"Failed to create watchpoint on {spec}: {err}") from err
|
|
961
|
+
|
|
962
|
+
hex_len = max(2, (reg.size + 3) // 4)
|
|
963
|
+
table = Table(
|
|
964
|
+
title="SVD Register Monitor Activated",
|
|
965
|
+
box=box.SIMPLE_HEAVY,
|
|
966
|
+
header_style="bold cyan",
|
|
967
|
+
show_header=True,
|
|
968
|
+
)
|
|
969
|
+
table.add_column("Property", style="bold", no_wrap=True)
|
|
970
|
+
table.add_column("Value")
|
|
971
|
+
table.add_row("Peripheral", periph.name)
|
|
972
|
+
table.add_row("Register", reg.name)
|
|
973
|
+
table.add_row("Address", f"0x{reg_addr:08X}")
|
|
974
|
+
table.add_row("Initial Value", f"0x{initial_val:0{hex_len}X}")
|
|
975
|
+
table.add_row("Watch Expression", spec)
|
|
976
|
+
table.add_row("Status", Text("Active (will notify on modification)", style="bold green"))
|
|
977
|
+
CONSOLE.print(table)
|
|
978
|
+
|
|
979
|
+
def _invoke_dump(self, args: list[str]) -> None:
|
|
980
|
+
"""Handle 'svd dump <device_name> file.json' subcommand.
|
|
981
|
+
|
|
982
|
+
Parameters
|
|
983
|
+
----------
|
|
984
|
+
args : list[str]
|
|
985
|
+
Subcommand arguments.
|
|
986
|
+
|
|
987
|
+
Raises
|
|
988
|
+
------
|
|
989
|
+
gdb.GdbError
|
|
990
|
+
If arguments are invalid or dumping fails.
|
|
991
|
+
"""
|
|
992
|
+
if len(args) != 2:
|
|
993
|
+
raise gdb.GdbError("Usage: svd dump <device_name> file.json")
|
|
994
|
+
|
|
995
|
+
if SESSION.device is None:
|
|
996
|
+
raise gdb.GdbError(
|
|
997
|
+
"No SVD definition currently loaded. Run 'svd load' or 'svd read <file.svd>' first."
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
dev_name, out_file_str = args[0], args[1]
|
|
1001
|
+
out_path = Path(out_file_str).expanduser().resolve()
|
|
1002
|
+
|
|
1003
|
+
try:
|
|
1004
|
+
reader = TOOLKIT_SESSION.memory
|
|
1005
|
+
except TargetReadError as err:
|
|
1006
|
+
raise gdb.GdbError(f"Cannot access target memory: {err}") from err
|
|
1007
|
+
|
|
1008
|
+
dump_data: dict[str, Any] = {
|
|
1009
|
+
"device": SESSION.device.name,
|
|
1010
|
+
"vendor": SESSION.device.vendor,
|
|
1011
|
+
"version": SESSION.device.version,
|
|
1012
|
+
"timestamp_gdb": True,
|
|
1013
|
+
"peripherals": {},
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
# Check if user requested all peripherals or a specific peripheral
|
|
1017
|
+
if dev_name.lower() in ("all", SESSION.device.name.lower()):
|
|
1018
|
+
target_peripherals = list(SESSION.device.peripherals)
|
|
1019
|
+
else:
|
|
1020
|
+
periph = SESSION.device.get_peripheral(dev_name)
|
|
1021
|
+
if periph is None:
|
|
1022
|
+
raise gdb.GdbError(
|
|
1023
|
+
f"Peripheral '{dev_name}' not found in loaded SVD device '{SESSION.device.name}'."
|
|
1024
|
+
)
|
|
1025
|
+
target_peripherals = [periph]
|
|
1026
|
+
|
|
1027
|
+
total_regs = 0
|
|
1028
|
+
for p in target_peripherals:
|
|
1029
|
+
p_dict: dict[str, Any] = {
|
|
1030
|
+
"name": p.name,
|
|
1031
|
+
"description": p.description,
|
|
1032
|
+
"base_address": f"0x{p.base_address:08X}",
|
|
1033
|
+
"base_address_int": p.base_address,
|
|
1034
|
+
"group_name": p.group_name,
|
|
1035
|
+
"registers": {},
|
|
1036
|
+
}
|
|
1037
|
+
for r in p.registers:
|
|
1038
|
+
total_regs += 1
|
|
1039
|
+
reg_addr = p.base_address + r.address_offset
|
|
1040
|
+
hex_len = max(2, (r.size + 3) // 4)
|
|
1041
|
+
try:
|
|
1042
|
+
val = _read_register_value(reader, p.base_address, r)
|
|
1043
|
+
val_hex = f"0x{val:0{hex_len}X}"
|
|
1044
|
+
decoded_fields = r.decode(val)
|
|
1045
|
+
read_error = None
|
|
1046
|
+
except TargetReadError as read_err:
|
|
1047
|
+
val = None
|
|
1048
|
+
val_hex = None
|
|
1049
|
+
decoded_fields = []
|
|
1050
|
+
read_error = str(read_err)
|
|
1051
|
+
|
|
1052
|
+
r_dict: dict[str, Any] = {
|
|
1053
|
+
"name": r.name,
|
|
1054
|
+
"display_name": r.display_name,
|
|
1055
|
+
"description": r.description,
|
|
1056
|
+
"address_offset": f"0x{r.address_offset:04X}",
|
|
1057
|
+
"address": f"0x{reg_addr:08X}",
|
|
1058
|
+
"size": r.size,
|
|
1059
|
+
"access": r.access,
|
|
1060
|
+
"reset_value": (
|
|
1061
|
+
f"0x{r.reset_value:0{hex_len}X}" if r.reset_value is not None else None
|
|
1062
|
+
),
|
|
1063
|
+
"value_raw": val,
|
|
1064
|
+
"value_hex": val_hex,
|
|
1065
|
+
"fields": decoded_fields,
|
|
1066
|
+
}
|
|
1067
|
+
if read_error:
|
|
1068
|
+
r_dict["error"] = read_error
|
|
1069
|
+
p_dict["registers"][r.name] = r_dict
|
|
1070
|
+
|
|
1071
|
+
dump_data["peripherals"][p.name] = p_dict
|
|
1072
|
+
|
|
1073
|
+
try:
|
|
1074
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1075
|
+
with out_path.open("w", encoding="utf-8") as json_file:
|
|
1076
|
+
json.dump(dump_data, json_file, indent=2)
|
|
1077
|
+
except OSError as err:
|
|
1078
|
+
raise gdb.GdbError(f"Failed to write dump to '{out_path}': {err}") from err
|
|
1079
|
+
|
|
1080
|
+
file_size = out_path.stat().st_size
|
|
1081
|
+
table = Table(
|
|
1082
|
+
title="SVD Memory Dump Saved",
|
|
1083
|
+
box=box.SIMPLE_HEAVY,
|
|
1084
|
+
header_style="bold cyan",
|
|
1085
|
+
show_header=True,
|
|
1086
|
+
)
|
|
1087
|
+
table.add_column("Property", style="bold", no_wrap=True)
|
|
1088
|
+
table.add_column("Value")
|
|
1089
|
+
table.add_row("Target", dev_name)
|
|
1090
|
+
table.add_row("Output File", Text(str(out_path), style="bold green"))
|
|
1091
|
+
table.add_row("Peripherals Dumped", str(len(target_peripherals)))
|
|
1092
|
+
table.add_row("Registers Dumped", str(total_regs))
|
|
1093
|
+
table.add_row("File Size", f"{file_size:,} bytes")
|
|
1094
|
+
CONSOLE.print(table)
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
class _SvdSubcommand(gdb.Command):
|
|
1098
|
+
"""Base class for concrete ``svd`` subcommands registered under the prefix command."""
|
|
1099
|
+
|
|
1100
|
+
def __init__(self, parent: SvdCmd, name: str) -> None:
|
|
1101
|
+
self.parent = parent
|
|
1102
|
+
self.name = name
|
|
1103
|
+
super().__init__(f"svd {name}", gdb.COMMAND_USER)
|
|
1104
|
+
|
|
1105
|
+
def _argv(self, arg: str) -> list[str]:
|
|
1106
|
+
"""Split command arguments using GDB's own CLI lexer."""
|
|
1107
|
+
return list(gdb.string_to_argv(arg)) if arg.strip() else []
|
|
1108
|
+
|
|
1109
|
+
def complete(self, text: str, word: str) -> list[str]:
|
|
1110
|
+
"""Complete arguments for the concrete subcommand."""
|
|
1111
|
+
return self.parent.complete_args(self.name, text, word)
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
class SvdLoadCmd(_SvdSubcommand):
|
|
1115
|
+
"""Auto-detect target CPUID/SoC, fetch and load matching SVD definition."""
|
|
1116
|
+
|
|
1117
|
+
def __init__(self, parent: SvdCmd) -> None:
|
|
1118
|
+
super().__init__(parent, "load")
|
|
1119
|
+
|
|
1120
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
1121
|
+
"""Execute ``svd load``."""
|
|
1122
|
+
del from_tty
|
|
1123
|
+
self.parent._invoke_load(self._argv(arg))
|
|
1124
|
+
|
|
1125
|
+
|
|
1126
|
+
class SvdReadCmd(_SvdSubcommand):
|
|
1127
|
+
"""Load an explicit SVD XML file from local filesystem."""
|
|
1128
|
+
|
|
1129
|
+
def __init__(self, parent: SvdCmd) -> None:
|
|
1130
|
+
super().__init__(parent, "read")
|
|
1131
|
+
|
|
1132
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
1133
|
+
"""Execute ``svd read``."""
|
|
1134
|
+
del from_tty
|
|
1135
|
+
self.parent._invoke_read(self._argv(arg))
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
class SvdShowCmd(_SvdSubcommand):
|
|
1139
|
+
"""Display peripheral or register state from the loaded SVD device."""
|
|
1140
|
+
|
|
1141
|
+
def __init__(self, parent: SvdCmd) -> None:
|
|
1142
|
+
super().__init__(parent, "show")
|
|
1143
|
+
|
|
1144
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
1145
|
+
"""Execute ``svd show``."""
|
|
1146
|
+
del from_tty
|
|
1147
|
+
self.parent._invoke_show(self._argv(arg))
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
class SvdWriteCmd(_SvdSubcommand):
|
|
1151
|
+
"""Write a numeric value into a peripheral register."""
|
|
1152
|
+
|
|
1153
|
+
def __init__(self, parent: SvdCmd) -> None:
|
|
1154
|
+
super().__init__(parent, "write")
|
|
1155
|
+
|
|
1156
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
1157
|
+
"""Execute ``svd write``."""
|
|
1158
|
+
del from_tty
|
|
1159
|
+
self.parent._invoke_write(self._argv(arg))
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
class SvdMonitorCmd(_SvdSubcommand):
|
|
1163
|
+
"""Set a watchpoint on a register and report bitfield differences."""
|
|
1164
|
+
|
|
1165
|
+
def __init__(self, parent: SvdCmd) -> None:
|
|
1166
|
+
super().__init__(parent, "monitor")
|
|
1167
|
+
|
|
1168
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
1169
|
+
"""Execute ``svd monitor``."""
|
|
1170
|
+
del from_tty
|
|
1171
|
+
self.parent._invoke_monitor(self._argv(arg))
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
class SvdDumpCmd(_SvdSubcommand):
|
|
1175
|
+
"""Dump a snapshot of device or peripheral state into a JSON file."""
|
|
1176
|
+
|
|
1177
|
+
def __init__(self, parent: SvdCmd) -> None:
|
|
1178
|
+
super().__init__(parent, "dump")
|
|
1179
|
+
|
|
1180
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
1181
|
+
"""Execute ``svd dump``."""
|
|
1182
|
+
del from_tty
|
|
1183
|
+
self.parent._invoke_dump(self._argv(arg))
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
class SvdListCmd(_SvdSubcommand):
|
|
1187
|
+
"""List the names of all peripherals in the loaded SVD device."""
|
|
1188
|
+
|
|
1189
|
+
def __init__(self, parent: SvdCmd) -> None:
|
|
1190
|
+
super().__init__(parent, "list")
|
|
1191
|
+
|
|
1192
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
1193
|
+
"""Execute ``svd list``."""
|
|
1194
|
+
del from_tty
|
|
1195
|
+
self.parent._invoke_list(self._argv(arg))
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
class SvdHelpCmd(_SvdSubcommand):
|
|
1199
|
+
"""Show the SVD command reference."""
|
|
1200
|
+
|
|
1201
|
+
def __init__(self, parent: SvdCmd) -> None:
|
|
1202
|
+
super().__init__(parent, "help")
|
|
1203
|
+
|
|
1204
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
1205
|
+
"""Execute ``svd help``."""
|
|
1206
|
+
del arg
|
|
1207
|
+
del from_tty
|
|
1208
|
+
render_help()
|