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
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 H2Lab Development Team
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Cortex-M Secure Attribution Unit inspection."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import IntEnum, StrEnum, unique
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from ...target_memory import TargetMemory, TargetReadError, TargetWriteError, WritableTargetMemory
|
|
13
|
+
from ..base import RegisterValue
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from .cortex_m import CortexMTargetDescription
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
SAU_BASE_ADDRESS = 0xE000EDD0
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@unique
|
|
23
|
+
class SauRegister(IntEnum):
|
|
24
|
+
"""SAU register offsets from ``SAU_BASE_ADDRESS``."""
|
|
25
|
+
|
|
26
|
+
CTRL = 0x000
|
|
27
|
+
TYPE = 0x004
|
|
28
|
+
RNR = 0x008
|
|
29
|
+
RBAR = 0x00C
|
|
30
|
+
RLAR = 0x010
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@unique
|
|
34
|
+
class SauDefaultSecurity(StrEnum):
|
|
35
|
+
"""Security attribution outside enabled SAU regions."""
|
|
36
|
+
|
|
37
|
+
SECURE = "secure"
|
|
38
|
+
NON_SECURE = "non-secure"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@unique
|
|
42
|
+
class SauAttribution(StrEnum):
|
|
43
|
+
"""Security attribution selected by one enabled SAU region."""
|
|
44
|
+
|
|
45
|
+
NON_SECURE = "non-secure"
|
|
46
|
+
NON_SECURE_CALLABLE = "non-secure-callable"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class SauDescription:
|
|
51
|
+
"""Static SAU interface description for one Armv8-M core."""
|
|
52
|
+
|
|
53
|
+
registers: tuple[SauRegister, ...]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class SauStatus:
|
|
58
|
+
"""SAU implementation and enable-state information."""
|
|
59
|
+
|
|
60
|
+
description: SauDescription | None
|
|
61
|
+
registers: tuple[RegisterValue, ...]
|
|
62
|
+
region_count: int | None
|
|
63
|
+
enabled: bool | None
|
|
64
|
+
all_non_secure: bool | None
|
|
65
|
+
default_security: SauDefaultSecurity | None
|
|
66
|
+
unavailable_reason: str | None = None
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def is_available(self) -> bool:
|
|
70
|
+
"""Whether the SAU status was read successfully."""
|
|
71
|
+
return self.unavailable_reason is None
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def unavailable(
|
|
75
|
+
cls,
|
|
76
|
+
description: SauDescription | None,
|
|
77
|
+
reason: str,
|
|
78
|
+
registers: tuple[RegisterValue, ...] = (),
|
|
79
|
+
) -> SauStatus:
|
|
80
|
+
"""Create an explicit unavailable SAU status."""
|
|
81
|
+
return cls(description, registers, None, None, None, None, reason)
|
|
82
|
+
|
|
83
|
+
def get_register(self, name: str) -> RegisterValue | None:
|
|
84
|
+
"""Find a captured SAU register by case-insensitive name."""
|
|
85
|
+
normalized_name = name.upper()
|
|
86
|
+
return next(
|
|
87
|
+
(register for register in self.registers if register.name.upper() == normalized_name),
|
|
88
|
+
None,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class SauRegion:
|
|
94
|
+
"""One decoded SAU region configuration."""
|
|
95
|
+
|
|
96
|
+
index: int
|
|
97
|
+
enabled: bool
|
|
98
|
+
start_address: int
|
|
99
|
+
end_address: int
|
|
100
|
+
size_bytes: int | None
|
|
101
|
+
attribution: SauAttribution
|
|
102
|
+
raw_rbar: int
|
|
103
|
+
raw_rlar: int
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class SauRegionResult:
|
|
108
|
+
"""A decoded SAU region or its explicit selection/read failure."""
|
|
109
|
+
|
|
110
|
+
index: int
|
|
111
|
+
region: SauRegion | None = None
|
|
112
|
+
unavailable_reason: str | None = None
|
|
113
|
+
|
|
114
|
+
def __post_init__(self) -> None:
|
|
115
|
+
"""Require exactly one region result state."""
|
|
116
|
+
if (self.region is None) == (self.unavailable_reason is None):
|
|
117
|
+
raise ValueError("an SAU region result must be available or unavailable")
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def unavailable(cls, index: int, reason: str) -> SauRegionResult:
|
|
121
|
+
"""Create an unavailable SAU region result."""
|
|
122
|
+
return cls(index=index, unavailable_reason=reason)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def is_available(self) -> bool:
|
|
126
|
+
"""Whether the selected region was read and decoded."""
|
|
127
|
+
return self.region is not None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(frozen=True)
|
|
131
|
+
class SauDump:
|
|
132
|
+
"""SAU status and region results for one Cortex-M target."""
|
|
133
|
+
|
|
134
|
+
status: SauStatus
|
|
135
|
+
regions: tuple[SauRegionResult, ...]
|
|
136
|
+
restore_error: str | None = None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
ARMV8_M_SAU = SauDescription(tuple(SauRegister))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def read_sau_status(reader: TargetMemory, target: CortexMTargetDescription) -> SauStatus:
|
|
143
|
+
"""Read SAU implementation and control status for a known Cortex-M core."""
|
|
144
|
+
description = target.core.sau if target.core is not None else None
|
|
145
|
+
if description is None:
|
|
146
|
+
return SauStatus.unavailable(None, "this Cortex-M core has no SAU description")
|
|
147
|
+
|
|
148
|
+
registers: list[RegisterValue] = []
|
|
149
|
+
sau_type = _read_register(reader, SauRegister.TYPE)
|
|
150
|
+
registers.append(sau_type)
|
|
151
|
+
if not sau_type.is_available:
|
|
152
|
+
assert sau_type.unavailable_reason is not None
|
|
153
|
+
return SauStatus.unavailable(description, sau_type.unavailable_reason, tuple(registers))
|
|
154
|
+
|
|
155
|
+
sau_ctrl = _read_register(reader, SauRegister.CTRL)
|
|
156
|
+
registers.append(sau_ctrl)
|
|
157
|
+
if not sau_ctrl.is_available:
|
|
158
|
+
assert sau_ctrl.unavailable_reason is not None
|
|
159
|
+
return SauStatus.unavailable(description, sau_ctrl.unavailable_reason, tuple(registers))
|
|
160
|
+
|
|
161
|
+
assert sau_type.value is not None
|
|
162
|
+
assert sau_ctrl.value is not None
|
|
163
|
+
all_non_secure = bool(sau_ctrl.value & (1 << 1))
|
|
164
|
+
return SauStatus(
|
|
165
|
+
description,
|
|
166
|
+
tuple(registers),
|
|
167
|
+
sau_type.value & 0xFF,
|
|
168
|
+
bool(sau_ctrl.value & 0x1),
|
|
169
|
+
all_non_secure,
|
|
170
|
+
SauDefaultSecurity.NON_SECURE if all_non_secure else SauDefaultSecurity.SECURE,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def dump_sau_regions(
|
|
175
|
+
reader: WritableTargetMemory,
|
|
176
|
+
target: CortexMTargetDescription,
|
|
177
|
+
status: SauStatus | None = None,
|
|
178
|
+
) -> SauDump:
|
|
179
|
+
"""Read and decode SAU regions, restoring the target's original selector.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
reader
|
|
184
|
+
Target-memory reader and writer used for SAU registers.
|
|
185
|
+
target
|
|
186
|
+
Known Cortex-M target description returned by :func:`decode_cpuid`.
|
|
187
|
+
status
|
|
188
|
+
Optional previously captured status. Reusing it avoids rereading the
|
|
189
|
+
mutable SAU control registers before selecting regions.
|
|
190
|
+
"""
|
|
191
|
+
if status is None:
|
|
192
|
+
status = read_sau_status(reader, target)
|
|
193
|
+
if not status.is_available or status.region_count == 0:
|
|
194
|
+
return SauDump(status, ())
|
|
195
|
+
|
|
196
|
+
original_rnr = _read_register(reader, SauRegister.RNR)
|
|
197
|
+
if not original_rnr.is_available:
|
|
198
|
+
assert original_rnr.unavailable_reason is not None
|
|
199
|
+
return SauDump(
|
|
200
|
+
SauStatus.unavailable(
|
|
201
|
+
status.description,
|
|
202
|
+
original_rnr.unavailable_reason,
|
|
203
|
+
status.registers + (original_rnr,),
|
|
204
|
+
),
|
|
205
|
+
(),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
assert original_rnr.value is not None
|
|
209
|
+
regions: list[SauRegionResult] = []
|
|
210
|
+
restore_error: str | None = None
|
|
211
|
+
try:
|
|
212
|
+
assert status.region_count is not None
|
|
213
|
+
for index in range(status.region_count):
|
|
214
|
+
try:
|
|
215
|
+
reader.write_uint32(SAU_BASE_ADDRESS + SauRegister.RNR, index)
|
|
216
|
+
rbar = reader.read_uint32(SAU_BASE_ADDRESS + SauRegister.RBAR)
|
|
217
|
+
rlar = reader.read_uint32(SAU_BASE_ADDRESS + SauRegister.RLAR)
|
|
218
|
+
except (TargetReadError, TargetWriteError) as error:
|
|
219
|
+
regions.append(SauRegionResult.unavailable(index, str(error)))
|
|
220
|
+
continue
|
|
221
|
+
regions.append(SauRegionResult(index, _decode_region(index, rbar, rlar)))
|
|
222
|
+
finally:
|
|
223
|
+
try:
|
|
224
|
+
reader.write_uint32(SAU_BASE_ADDRESS + SauRegister.RNR, original_rnr.value)
|
|
225
|
+
except TargetWriteError as error:
|
|
226
|
+
restore_error = str(error)
|
|
227
|
+
return SauDump(status, tuple(regions), restore_error)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _read_register(reader: TargetMemory, register: SauRegister) -> RegisterValue:
|
|
231
|
+
"""Read one SAU register while preserving a target access failure."""
|
|
232
|
+
address = SAU_BASE_ADDRESS + register.value
|
|
233
|
+
try:
|
|
234
|
+
value = reader.read_uint32(address)
|
|
235
|
+
except TargetReadError as error:
|
|
236
|
+
return RegisterValue.unavailable(register.name, address, 32, str(error))
|
|
237
|
+
return RegisterValue.known(register.name, address, 32, value)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _decode_region(index: int, rbar: int, rlar: int) -> SauRegion:
|
|
241
|
+
"""Decode one SAU ``RBAR``/``RLAR`` region pair."""
|
|
242
|
+
start_address = rbar & 0xFFFFFFE0
|
|
243
|
+
end_address = (rlar & 0xFFFFFFE0) | 0x1F
|
|
244
|
+
return SauRegion(
|
|
245
|
+
index,
|
|
246
|
+
bool(rlar & 0x1),
|
|
247
|
+
start_address,
|
|
248
|
+
end_address,
|
|
249
|
+
end_address - start_address + 1 if end_address >= start_address else None,
|
|
250
|
+
SauAttribution.NON_SECURE_CALLABLE if rlar & (1 << 1) else SauAttribution.NON_SECURE,
|
|
251
|
+
rbar,
|
|
252
|
+
rlar,
|
|
253
|
+
)
|