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/__init__.py
ADDED
|
@@ -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
|