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.
@@ -0,0 +1,55 @@
1
+ # SPDX-FileCopyrightText: 2026 H2Lab Development Team
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """GDB commands provided by pyGdbToolkit."""
5
+
6
+ from .cmd_faultinfo import FaultInfoCmd
7
+ from .cmd_lscpu import LscpuCmd
8
+ from . import cmd_rtos
9
+ from .cmd_rtos import RtosCmd
10
+ from . import cmd_secscan
11
+ from .cmd_secscan import SecscanCmd
12
+ from . import cmd_svd
13
+ from .cmd_svd import SvdCmd
14
+ from .session import SESSION, SessionSlice, ToolkitSession, install_event_hooks
15
+
16
+ install_event_hooks()
17
+
18
+ LscpuCmd()
19
+ FaultInfoCmd()
20
+ _RTOS_COMMAND = RtosCmd()
21
+ cmd_rtos.RtosSelectCmd(_RTOS_COMMAND)
22
+ cmd_rtos.RtosListCmd(_RTOS_COMMAND)
23
+ cmd_rtos.RtosLoadProjectCmd(_RTOS_COMMAND)
24
+ cmd_rtos.RtosShowCmd(_RTOS_COMMAND)
25
+ cmd_rtos.RtosShowTaskCmd(_RTOS_COMMAND)
26
+ cmd_rtos.RtosShowschedCmd(_RTOS_COMMAND)
27
+ _SVD_COMMAND = SvdCmd()
28
+ cmd_svd.SvdLoadCmd(_SVD_COMMAND)
29
+ cmd_svd.SvdReadCmd(_SVD_COMMAND)
30
+ cmd_svd.SvdShowCmd(_SVD_COMMAND)
31
+ cmd_svd.SvdWriteCmd(_SVD_COMMAND)
32
+ cmd_svd.SvdMonitorCmd(_SVD_COMMAND)
33
+ cmd_svd.SvdDumpCmd(_SVD_COMMAND)
34
+ cmd_svd.SvdListCmd(_SVD_COMMAND)
35
+ cmd_svd.SvdHelpCmd(_SVD_COMMAND)
36
+
37
+ _SECSCAN_COMMAND = SecscanCmd()
38
+ cmd_secscan.SecscanAuditCmd(_SECSCAN_COMMAND)
39
+ cmd_secscan.SecscanReportCmd(_SECSCAN_COMMAND)
40
+ cmd_secscan.SecscanHelpCmd(_SECSCAN_COMMAND)
41
+
42
+ __all__ = [
43
+ "SESSION",
44
+ "FaultInfoCmd",
45
+ "LscpuCmd",
46
+ "RtosCmd",
47
+ "SecscanCmd",
48
+ "SessionSlice",
49
+ "SvdCmd",
50
+ "ToolkitSession",
51
+ "cmd_rtos",
52
+ "cmd_secscan",
53
+ "cmd_svd",
54
+ "install_event_hooks",
55
+ ]
@@ -0,0 +1,79 @@
1
+ # SPDX-FileCopyrightText: 2026 H2Lab Development Team
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Portable architecture detection, inspection, and diagnostic APIs."""
5
+
6
+ from .arm.probe import DEFAULT_ARM_PROBES
7
+ from .arm.fault import CortexMFaultCollector
8
+ from .arm.security import CortexMSecurityAuditor
9
+ from .base import (
10
+ Architecture,
11
+ ArchitectureProbe,
12
+ ProbeResult,
13
+ RegisterValue,
14
+ SystemRegisterSet,
15
+ TargetDescription,
16
+ )
17
+ from .diagnostics import (
18
+ ArchitectureDiagnosticRuntime,
19
+ DiagnosticField,
20
+ DiagnosticFinding,
21
+ DiagnosticContent,
22
+ DiagnosticPanel,
23
+ DiagnosticReport,
24
+ DiagnosticRegisterReader,
25
+ DiagnosticResult,
26
+ DiagnosticRuntimeAccess,
27
+ DiagnosticRuntime,
28
+ DiagnosticSection,
29
+ DiagnosticService,
30
+ DiagnosticServiceName,
31
+ DiagnosticServiceRegistry,
32
+ DiagnosticSeverity,
33
+ DiagnosticSymbolResolver,
34
+ DiagnosticTable,
35
+ DiagnosticTableRow,
36
+ DiagnosticValue,
37
+ )
38
+ from .registry import ArchitectureRegistry
39
+
40
+ DEFAULT_ARCHITECTURE_REGISTRY = ArchitectureRegistry(DEFAULT_ARM_PROBES)
41
+ DEFAULT_DIAGNOSTIC_SERVICE_REGISTRY = DiagnosticServiceRegistry(
42
+ (CortexMSecurityAuditor(), CortexMFaultCollector())
43
+ )
44
+ DEFAULT_DIAGNOSTIC_RUNTIME: DiagnosticRuntime = ArchitectureDiagnosticRuntime(
45
+ DEFAULT_ARCHITECTURE_REGISTRY,
46
+ DEFAULT_DIAGNOSTIC_SERVICE_REGISTRY,
47
+ )
48
+
49
+ __all__ = [
50
+ "Architecture",
51
+ "ArchitectureDiagnosticRuntime",
52
+ "ArchitectureProbe",
53
+ "ArchitectureRegistry",
54
+ "DEFAULT_ARCHITECTURE_REGISTRY",
55
+ "DEFAULT_DIAGNOSTIC_RUNTIME",
56
+ "DEFAULT_DIAGNOSTIC_SERVICE_REGISTRY",
57
+ "DiagnosticField",
58
+ "DiagnosticFinding",
59
+ "DiagnosticContent",
60
+ "DiagnosticPanel",
61
+ "DiagnosticReport",
62
+ "DiagnosticRegisterReader",
63
+ "DiagnosticResult",
64
+ "DiagnosticRuntimeAccess",
65
+ "DiagnosticRuntime",
66
+ "DiagnosticSection",
67
+ "DiagnosticService",
68
+ "DiagnosticServiceName",
69
+ "DiagnosticServiceRegistry",
70
+ "DiagnosticSeverity",
71
+ "DiagnosticSymbolResolver",
72
+ "DiagnosticTable",
73
+ "DiagnosticTableRow",
74
+ "DiagnosticValue",
75
+ "ProbeResult",
76
+ "RegisterValue",
77
+ "SystemRegisterSet",
78
+ "TargetDescription",
79
+ ]
@@ -0,0 +1,17 @@
1
+ # SPDX-FileCopyrightText: 2026 H2Lab Development Team
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Curated Arm target-description and probe integration surface.
5
+
6
+ Import Arm implementation APIs from their defining submodules, such as
7
+ ``arch.arm.cortex_m`` or ``arch.arm.mpu``.
8
+ """
9
+
10
+ from .target import ArmProfile, ArmTargetDescription
11
+ from .cortex_m import CortexMProbe
12
+
13
+ __all__ = [
14
+ "ArmProfile",
15
+ "ArmTargetDescription",
16
+ "CortexMProbe",
17
+ ]
@@ -0,0 +1,427 @@
1
+ # SPDX-FileCopyrightText: 2026 H2Lab Development Team
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """CoreSight component identification and ROM-table discovery utilities."""
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass
9
+ from typing import Sequence
10
+
11
+ from ...target_memory import TargetMemory, TargetReadError
12
+
13
+ MCU_ROM_TABLE_ADDRESS = 0xE00FE000
14
+ PROCESSOR_ROM_TABLE_ADDRESS = 0xE00FF000
15
+
16
+ ROM_TABLE_COMPONENT_CLASS = 0x1
17
+
18
+ _CIDR_PREAMBLE_MASK = 0xFFFF0FFF
19
+ _CIDR_PREAMBLE_VALUE = 0xB105000D
20
+ _CIDR_CLASS_MASK = 0x0000F000
21
+ _CIDR_CLASS_SHIFT = 12
22
+ _PIDR_OFFSETS = (0xFE0, 0xFE4, 0xFE8, 0xFEC, 0xFD0)
23
+ _CIDR_OFFSETS = (0xFF0, 0xFF4, 0xFF8, 0xFFC)
24
+
25
+
26
+ class CoreSightError(RuntimeError):
27
+ """Base error for malformed or inaccessible CoreSight metadata."""
28
+
29
+
30
+ class ComponentIdentityError(CoreSightError):
31
+ """A component's CIDR or PIDR registers are invalid or inaccessible."""
32
+
33
+
34
+ class RomTableError(CoreSightError):
35
+ """A ROM table is malformed, inaccessible, or exceeds safety limits."""
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class Jep106Identity:
40
+ """A complete JEP106 manufacturer identity."""
41
+
42
+ bank: int
43
+ code: int
44
+
45
+ def __post_init__(self) -> None:
46
+ """Validate architecturally sized JEP106 fields."""
47
+ if not 0 <= self.bank <= 0xF:
48
+ raise ValueError("JEP106 continuation bank must fit in four bits")
49
+ if not 0 <= self.code <= 0x7F:
50
+ raise ValueError("JEP106 identity code must fit in seven bits")
51
+
52
+ def display(self) -> str:
53
+ """Return a report-ready representation of the complete identity."""
54
+ return f"bank {self.bank}, code 0x{self.code:02X}"
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class ComponentID:
59
+ """Validated CoreSight Component ID register fields."""
60
+
61
+ raw: tuple[int, int, int, int]
62
+ component_class: int
63
+
64
+
65
+ @dataclass(frozen=True)
66
+ class PeripheralID:
67
+ """Decoded CoreSight Peripheral ID register fields."""
68
+
69
+ raw: tuple[int, int, int, int, int]
70
+ part_number: int
71
+ jedec_present: bool
72
+ jep106: Jep106Identity | None
73
+
74
+
75
+ @dataclass(frozen=True)
76
+ class ComponentIdentity:
77
+ """Validated component and peripheral identity at one 4-KiB base."""
78
+
79
+ base: int
80
+ component_id: ComponentID
81
+ peripheral_id: PeripheralID
82
+
83
+
84
+ @dataclass(frozen=True)
85
+ class RomTableEntry:
86
+ """One resolved format-1 ROM-table entry."""
87
+
88
+ table_base: int
89
+ index: int
90
+ raw: int
91
+ component_base: int
92
+
93
+
94
+ @dataclass(frozen=True)
95
+ class RomTable:
96
+ """A validated ROM table and the components reached from it."""
97
+
98
+ base: int
99
+ identity: ComponentIdentity
100
+ entries: tuple[RomTableEntry, ...]
101
+ components: tuple[ComponentIdentity, ...]
102
+
103
+
104
+ @dataclass(frozen=True)
105
+ class RomTableDiscovery:
106
+ """A best-effort ROM-table scan with an explicit available state."""
107
+
108
+ base: int
109
+ table: RomTable | None = None
110
+ unavailable_reason: str | None = None
111
+
112
+ def __post_init__(self) -> None:
113
+ """Ensure a discovery result is either available or unavailable."""
114
+ if (self.table is None) == (self.unavailable_reason is None):
115
+ raise ValueError("a ROM-table discovery must have exactly one state")
116
+
117
+ @property
118
+ def is_available(self) -> bool:
119
+ """Whether a valid ROM table was discovered."""
120
+ return self.table is not None
121
+
122
+
123
+ @dataclass(frozen=True)
124
+ class CoreSightDiscovery:
125
+ """Best-effort discovery result for the target's primary ROM table."""
126
+
127
+ mcu_rom: RomTableDiscovery
128
+
129
+
130
+ def decode_component_id(raw: Sequence[int]) -> ComponentID:
131
+ """Validate and decode standard Component ID register bytes.
132
+
133
+ Parameters
134
+ ----------
135
+ raw
136
+ CIDR0 through CIDR3, each represented by its low byte.
137
+
138
+ Returns
139
+ -------
140
+ ComponentID
141
+ The validated component class and original register bytes.
142
+
143
+ Raises
144
+ ------
145
+ ComponentIdentityError
146
+ If the bytes do not carry the standard preamble.
147
+ """
148
+ values = _register_bytes(raw, 4, "CIDR")
149
+ packed = sum(value << (index * 8) for index, value in enumerate(values))
150
+ if (packed & _CIDR_PREAMBLE_MASK) != _CIDR_PREAMBLE_VALUE:
151
+ raise ComponentIdentityError(
152
+ "CIDR does not contain the standard CoreSight component preamble"
153
+ )
154
+ return ComponentID(
155
+ raw=(values[0], values[1], values[2], values[3]),
156
+ component_class=(packed & _CIDR_CLASS_MASK) >> _CIDR_CLASS_SHIFT,
157
+ )
158
+
159
+
160
+ def decode_peripheral_id(raw: Sequence[int]) -> PeripheralID:
161
+ """Decode standard Peripheral ID register bytes.
162
+
163
+ JEP106 fields are exposed only when PIDR2's JEDEC-present bit is set. The
164
+ seven-bit identity code combines PIDR1's high nibble with PIDR2's low
165
+ three bits; PIDR4 supplies the continuation bank.
166
+
167
+ Parameters
168
+ ----------
169
+ raw
170
+ PIDR0 through PIDR4, each represented by its low byte.
171
+
172
+ Returns
173
+ -------
174
+ PeripheralID
175
+ The component part number and, when advertised, JEP106 identity.
176
+ """
177
+ values = _register_bytes(raw, 5, "PIDR")
178
+ part_number = values[0] | ((values[1] & 0x0F) << 8)
179
+ jedec_present = bool(values[2] & 0x08)
180
+ jep106 = None
181
+ if jedec_present:
182
+ jep106 = Jep106Identity(
183
+ bank=values[4] & 0x0F,
184
+ code=((values[2] & 0x07) << 4) | ((values[1] >> 4) & 0x0F),
185
+ )
186
+ return PeripheralID(
187
+ raw=(values[0], values[1], values[2], values[3], values[4]),
188
+ part_number=part_number,
189
+ jedec_present=jedec_present,
190
+ jep106=jep106,
191
+ )
192
+
193
+
194
+ def read_component_identity(
195
+ reader: TargetMemory,
196
+ base: int,
197
+ *,
198
+ expected_class: int | None = None,
199
+ ) -> ComponentIdentity:
200
+ """Read and validate one CoreSight component identity.
201
+
202
+ Parameters
203
+ ----------
204
+ reader
205
+ Target-memory reader used for register reads.
206
+ base
207
+ The 4-KiB-aligned component base.
208
+ expected_class
209
+ An optional required CoreSight component class.
210
+
211
+ Returns
212
+ -------
213
+ ComponentIdentity
214
+ The validated CIDR and decoded PIDR values.
215
+
216
+ Raises
217
+ ------
218
+ ComponentIdentityError
219
+ If the base, CIDR, PIDR, or expected component class is invalid.
220
+ """
221
+ _validate_component_base(base)
222
+ try:
223
+ component_id = decode_component_id(
224
+ tuple(reader.read_uint32(base + offset) & 0xFF for offset in _CIDR_OFFSETS)
225
+ )
226
+ peripheral_id = decode_peripheral_id(
227
+ tuple(reader.read_uint32(base + offset) & 0xFF for offset in _PIDR_OFFSETS)
228
+ )
229
+ except TargetReadError as error:
230
+ raise ComponentIdentityError(
231
+ f"could not read component identity at 0x{base:08X}: {error}"
232
+ ) from error
233
+
234
+ if expected_class is not None and component_id.component_class != expected_class:
235
+ raise ComponentIdentityError(
236
+ f"component at 0x{base:08X} has class 0x{component_id.component_class:X}; "
237
+ f"expected 0x{expected_class:X}"
238
+ )
239
+ return ComponentIdentity(
240
+ base=base,
241
+ component_id=component_id,
242
+ peripheral_id=peripheral_id,
243
+ )
244
+
245
+
246
+ def walk_rom_table(
247
+ reader: TargetMemory,
248
+ table_base: int,
249
+ *,
250
+ max_entries: int = 256,
251
+ max_nesting: int = 8,
252
+ ) -> RomTable:
253
+ """Traverse a bounded format-1 CoreSight ROM table.
254
+
255
+ Parameters
256
+ ----------
257
+ reader
258
+ Target-memory reader used for all table and identity reads.
259
+ table_base
260
+ The 4-KiB-aligned base address of the root ROM table.
261
+ max_entries
262
+ Maximum number of nonzero entries across the complete traversal.
263
+ max_nesting
264
+ Maximum number of nested ROM-table levels below the root.
265
+
266
+ Returns
267
+ -------
268
+ RomTable
269
+ The root table, resolved entries, and validated reachable components.
270
+
271
+ Raises
272
+ ------
273
+ RomTableError
274
+ If the table is malformed, inaccessible, cyclic, or exceeds a limit.
275
+ """
276
+ _validate_component_base(table_base)
277
+ if max_entries <= 0:
278
+ raise ValueError("max_entries must be positive")
279
+ if max_nesting < 0:
280
+ raise ValueError("max_nesting must not be negative")
281
+
282
+ try:
283
+ root_identity = read_component_identity(
284
+ reader, table_base, expected_class=ROM_TABLE_COMPONENT_CLASS
285
+ )
286
+ except ComponentIdentityError as error:
287
+ raise RomTableError(f"invalid ROM table at 0x{table_base:08X}: {error}") from error
288
+
289
+ entries: list[RomTableEntry] = []
290
+ components: list[ComponentIdentity] = []
291
+ visited_tables: set[int] = set()
292
+ visited_components: set[int] = {table_base}
293
+
294
+ def scan_table(base: int, identity: ComponentIdentity, depth: int) -> None:
295
+ """Read one table after its identity has already been validated."""
296
+ if base in visited_tables:
297
+ raise RomTableError(f"ROM-table cycle at 0x{base:08X}")
298
+ visited_tables.add(base)
299
+
300
+ for index in range(max_entries):
301
+ try:
302
+ raw = reader.read_uint32(base + index * 4)
303
+ except TargetReadError as error:
304
+ raise RomTableError(
305
+ f"could not read ROM-table entry {index} at 0x{base:08X}: {error}"
306
+ ) from error
307
+ if raw == 0:
308
+ return
309
+ if len(entries) >= max_entries:
310
+ raise RomTableError(f"ROM table at 0x{base:08X} exceeded {max_entries} entries")
311
+ if not raw & 0x01:
312
+ continue
313
+
314
+ component_base = _resolve_rom_entry(base, index, raw)
315
+ if component_base in visited_components:
316
+ raise RomTableError(
317
+ f"ROM table at 0x{base:08X} repeats component 0x{component_base:08X}"
318
+ )
319
+
320
+ try:
321
+ component = read_component_identity(reader, component_base)
322
+ except ComponentIdentityError as error:
323
+ raise RomTableError(
324
+ f"invalid component in ROM table at 0x{base:08X}, entry {index}: {error}"
325
+ ) from error
326
+
327
+ visited_components.add(component_base)
328
+ entries.append(
329
+ RomTableEntry(
330
+ table_base=base,
331
+ index=index,
332
+ raw=raw,
333
+ component_base=component_base,
334
+ )
335
+ )
336
+ components.append(component)
337
+ if component.component_id.component_class == ROM_TABLE_COMPONENT_CLASS:
338
+ if depth >= max_nesting:
339
+ raise RomTableError(
340
+ f"ROM table at 0x{base:08X} exceeded nesting limit {max_nesting}"
341
+ )
342
+ scan_table(component_base, component, depth + 1)
343
+ raise RomTableError(f"ROM table at 0x{base:08X} exceeded {max_entries} entries")
344
+
345
+ scan_table(table_base, root_identity, 0)
346
+ return RomTable(
347
+ base=table_base,
348
+ identity=root_identity,
349
+ entries=tuple(entries),
350
+ components=tuple(components),
351
+ )
352
+
353
+
354
+ def discover_rom_tables(reader: TargetMemory) -> CoreSightDiscovery:
355
+ """Discover the target ROM table from its ordered CoreSight root addresses.
356
+
357
+ The MCU ROM-table root is attempted first at ``0xE00FE000``. When it is
358
+ inaccessible or invalid, the standard processor-ROM root at ``0xE00FF000``
359
+ is attempted. A valid first root is traversed normally, including all
360
+ nested ROM tables it references.
361
+
362
+ Parameters
363
+ ----------
364
+ reader
365
+ Target-memory reader used for table discovery.
366
+
367
+ Returns
368
+ -------
369
+ CoreSightDiscovery
370
+ The first valid table discovery result or the final failure reason.
371
+ """
372
+ rom_table: RomTableDiscovery
373
+ for base_address in (MCU_ROM_TABLE_ADDRESS, PROCESSOR_ROM_TABLE_ADDRESS):
374
+ rom_table = _discover_rom_table(reader, base_address, require_jep106=True)
375
+ if rom_table.is_available:
376
+ break
377
+ return CoreSightDiscovery(mcu_rom=rom_table)
378
+
379
+
380
+ def _discover_rom_table(
381
+ reader: TargetMemory,
382
+ base: int,
383
+ *,
384
+ require_jep106: bool = False,
385
+ ) -> RomTableDiscovery:
386
+ """Scan one fixed ROM-table root without making discovery command-fatal."""
387
+ try:
388
+ table = walk_rom_table(reader, base)
389
+ if require_jep106 and table.identity.peripheral_id.jep106 is None:
390
+ raise RomTableError(
391
+ f"ROM table at 0x{base:08X} does not advertise a JEDEC manufacturer identity"
392
+ )
393
+ except RomTableError as error:
394
+ return RomTableDiscovery(base=base, unavailable_reason=str(error))
395
+ return RomTableDiscovery(base=base, table=table)
396
+
397
+
398
+ def _register_bytes(raw: Sequence[int], expected_count: int, name: str) -> tuple[int, ...]:
399
+ """Validate a fixed-length sequence of low-byte register values."""
400
+ if len(raw) != expected_count:
401
+ raise ComponentIdentityError(f"{name} requires {expected_count} register bytes")
402
+ if any(not isinstance(value, int) or not 0 <= value <= 0xFF for value in raw):
403
+ raise ComponentIdentityError(f"{name} register values must be unsigned bytes")
404
+ return tuple(raw)
405
+
406
+
407
+ def _validate_component_base(base: int) -> None:
408
+ """Validate the address range and alignment shared by component accesses."""
409
+ if not 0 <= base <= 0xFFFFF000:
410
+ raise ComponentIdentityError("component base must be a 32-bit address")
411
+ if base & 0xFFF:
412
+ raise ComponentIdentityError("component base must be 4-KiB aligned")
413
+
414
+
415
+ def _resolve_rom_entry(table_base: int, index: int, raw: int) -> int:
416
+ """Validate and resolve one nonzero format-1 ROM-table entry."""
417
+ if not 0 <= raw <= 0xFFFFFFFF:
418
+ raise RomTableError(f"ROM-table entry {index} is not an unsigned 32-bit value")
419
+ if not raw & 0x02:
420
+ raise RomTableError(f"ROM-table entry {index} does not use format 1")
421
+
422
+ offset = int.from_bytes((raw & 0xFFFFF000).to_bytes(length=4, signed=False), signed=True)
423
+
424
+ component_base = table_base + offset
425
+ if not 0 <= component_base <= 0xFFFFF000:
426
+ raise RomTableError(f"ROM-table entry {index} resolves outside 32-bit address space")
427
+ return component_base