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,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
+ )