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_rtos.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 H2Lab Development Team
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""GDB commands to select an RTOS and load its project symbols."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import tomllib
|
|
12
|
+
from types import ModuleType
|
|
13
|
+
import xml.etree.ElementTree as ET
|
|
14
|
+
|
|
15
|
+
import gdb
|
|
16
|
+
from rich import box
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
from rich.table import Table
|
|
19
|
+
from rich.text import Text
|
|
20
|
+
|
|
21
|
+
from .rtos import SUPPORTED_RTOS
|
|
22
|
+
from .session import SESSION as TOOLKIT_SESSION
|
|
23
|
+
from .session import SessionSlice
|
|
24
|
+
|
|
25
|
+
CONSOLE = Console(force_terminal=True)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class RtosSessionState(SessionSlice):
|
|
30
|
+
"""Encapsulate the RTOS selection, its loaded project, and the active trace."""
|
|
31
|
+
|
|
32
|
+
selected_rtos: ModuleType | None = None
|
|
33
|
+
project_path: Path | None = None
|
|
34
|
+
task_list: object | None = None
|
|
35
|
+
scheduler_trace: SchedulerTrace | None = None
|
|
36
|
+
|
|
37
|
+
def clear_project(self) -> None:
|
|
38
|
+
"""Delete the active scheduler trace and drop the loaded project metadata."""
|
|
39
|
+
if self.scheduler_trace is not None:
|
|
40
|
+
self.scheduler_trace.delete()
|
|
41
|
+
self.project_path = None
|
|
42
|
+
self.task_list = None
|
|
43
|
+
|
|
44
|
+
def reset(self) -> None:
|
|
45
|
+
"""Drop the loaded project metadata and the RTOS selection."""
|
|
46
|
+
self.clear_project()
|
|
47
|
+
self.selected_rtos = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class RtosCmd(gdb.Command):
|
|
51
|
+
"""Manage the selected RTOS."""
|
|
52
|
+
|
|
53
|
+
def __init__(self) -> None:
|
|
54
|
+
"""Register the prefix command and bind the session-owned RTOS state."""
|
|
55
|
+
self.state = TOOLKIT_SESSION.state(RtosSessionState)
|
|
56
|
+
super().__init__("rtos", gdb.COMMAND_USER, gdb.COMPLETE_NONE, True)
|
|
57
|
+
|
|
58
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
59
|
+
"""Show the RTOS command reference."""
|
|
60
|
+
del arg, from_tty
|
|
61
|
+
table = Table(title="RTOS Commands", box=box.SIMPLE_HEAVY, header_style="bold cyan")
|
|
62
|
+
table.add_column("Command", style="bold yellow")
|
|
63
|
+
table.add_column("Description")
|
|
64
|
+
table.add_row("rtos select <name>", "Select a supported RTOS")
|
|
65
|
+
table.add_row("rtos list", "List supported RTOS implementations")
|
|
66
|
+
table.add_row("rtos load-project --from <path>", "Load project symbols and task metadata")
|
|
67
|
+
table.add_row("rtos show", "Show the kernel and task memory layout and metadata")
|
|
68
|
+
table.add_row("rtos show task <taskname>", "Inspect a task on the stopped target")
|
|
69
|
+
table.add_row("rtos showsched <num>", "Trace the next num scheduler elections")
|
|
70
|
+
CONSOLE.print(table)
|
|
71
|
+
|
|
72
|
+
def _invoke_select(self, args: list[str]) -> None:
|
|
73
|
+
if len(args) != 1:
|
|
74
|
+
raise gdb.GdbError("Usage: rtos select <name>")
|
|
75
|
+
name = args[0]
|
|
76
|
+
if name not in SUPPORTED_RTOS:
|
|
77
|
+
raise gdb.GdbError(f"Unsupported RTOS: {name} (supported: {', '.join(SUPPORTED_RTOS)})")
|
|
78
|
+
self.state.selected_rtos = SUPPORTED_RTOS[name]
|
|
79
|
+
self.state.clear_project()
|
|
80
|
+
CONSOLE.print(Text.assemble("Selected RTOS: ", (name, "bold green")))
|
|
81
|
+
|
|
82
|
+
def _invoke_list(self, args: list[str]) -> None:
|
|
83
|
+
if args:
|
|
84
|
+
raise gdb.GdbError("Usage: rtos list")
|
|
85
|
+
table = Table(title="Supported RTOS", box=box.SIMPLE_HEAVY, header_style="bold cyan")
|
|
86
|
+
table.add_column("Name")
|
|
87
|
+
table.add_column("Status")
|
|
88
|
+
for name, implementation in SUPPORTED_RTOS.items():
|
|
89
|
+
status = "Selected" if implementation is self.state.selected_rtos else ""
|
|
90
|
+
table.add_row(name, status)
|
|
91
|
+
CONSOLE.print(table)
|
|
92
|
+
if self.state.selected_rtos is None:
|
|
93
|
+
CONSOLE.print("No RTOS selected.")
|
|
94
|
+
|
|
95
|
+
def _invoke_load_project(self, args: list[str]) -> None:
|
|
96
|
+
if len(args) != 2 or args[0] != "--from":
|
|
97
|
+
raise gdb.GdbError("Usage: rtos load-project --from <path>")
|
|
98
|
+
if self.state.selected_rtos is None:
|
|
99
|
+
raise gdb.GdbError("Select an RTOS first with rtos select <name>")
|
|
100
|
+
self.state.clear_project()
|
|
101
|
+
project_path = Path(args[1]).expanduser().resolve()
|
|
102
|
+
try:
|
|
103
|
+
elfs = self.state.selected_rtos.project_elfs(project_path)
|
|
104
|
+
except (OSError, ValueError, tomllib.TOMLDecodeError) as error:
|
|
105
|
+
raise gdb.GdbError(f"Cannot load RTOS project: {error}") from error
|
|
106
|
+
if not elfs or elfs[0].name != "sentry-kernel.elf":
|
|
107
|
+
raise gdb.GdbError("Cannot load RTOS task metadata: kernel ELF is required")
|
|
108
|
+
for elf in elfs:
|
|
109
|
+
escaped = str(elf).replace("\\", "\\\\").replace('"', '\\"')
|
|
110
|
+
gdb.execute(f'add-symbol-file "{escaped}" -o 0', from_tty=False, to_string=True)
|
|
111
|
+
CONSOLE.print(
|
|
112
|
+
Text.assemble("Loaded symbols: ", (str(elf.relative_to(project_path)), "green"))
|
|
113
|
+
)
|
|
114
|
+
try:
|
|
115
|
+
task_list = self.state.selected_rtos.load_task_list(project_path, elfs[0])
|
|
116
|
+
except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError, gdb.error) as error:
|
|
117
|
+
raise gdb.GdbError(f"Cannot load RTOS task metadata: {error}") from error
|
|
118
|
+
self.state.task_list = task_list
|
|
119
|
+
self.state.project_path = project_path
|
|
120
|
+
|
|
121
|
+
def _invoke_show(self, args: list[str]) -> None:
|
|
122
|
+
if args:
|
|
123
|
+
raise gdb.GdbError("Usage: rtos show")
|
|
124
|
+
if self.state.selected_rtos is None:
|
|
125
|
+
raise gdb.GdbError("Select an RTOS first with rtos select <name>")
|
|
126
|
+
if self.state.project_path is None:
|
|
127
|
+
raise gdb.GdbError("Load a project first with rtos load-project --from <path>")
|
|
128
|
+
try:
|
|
129
|
+
CONSOLE.print(
|
|
130
|
+
self.state.selected_rtos.show_project(self.state.project_path, self.state.task_list)
|
|
131
|
+
)
|
|
132
|
+
except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError) as error:
|
|
133
|
+
raise gdb.GdbError(f"Cannot show RTOS project: {error}") from error
|
|
134
|
+
|
|
135
|
+
def _invoke_show_task(self, args: list[str]) -> None:
|
|
136
|
+
if len(args) != 1:
|
|
137
|
+
raise gdb.GdbError("Usage: rtos show task <taskname>")
|
|
138
|
+
state = self.state
|
|
139
|
+
if state.selected_rtos is None or state.project_path is None or state.task_list is None:
|
|
140
|
+
raise gdb.GdbError("Select an RTOS and load a project before rtos show task")
|
|
141
|
+
try:
|
|
142
|
+
CONSOLE.print(
|
|
143
|
+
state.selected_rtos.show_task(state.project_path, state.task_list, args[0])
|
|
144
|
+
)
|
|
145
|
+
except (OSError, ValueError, KeyError, TypeError, ET.ParseError, gdb.error) as error:
|
|
146
|
+
raise gdb.GdbError(f"Cannot show task {args[0]}: {error}") from error
|
|
147
|
+
|
|
148
|
+
def _invoke_showsched(self, args: list[str]) -> None:
|
|
149
|
+
if len(args) != 1 or not args[0].isdecimal() or int(args[0]) < 1:
|
|
150
|
+
raise gdb.GdbError("Usage: rtos showsched <num> (num must be positive)")
|
|
151
|
+
if self.state.selected_rtos is None or self.state.task_list is None:
|
|
152
|
+
raise gdb.GdbError("Select an RTOS and load a project before rtos showsched")
|
|
153
|
+
if self.state.scheduler_trace is not None:
|
|
154
|
+
self.state.scheduler_trace.delete()
|
|
155
|
+
self.state.scheduler_trace = SchedulerTrace(self, int(args[0]))
|
|
156
|
+
CONSOLE.print(
|
|
157
|
+
f"Tracing {args[0]} scheduler elections. Continue the target to collect them."
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class SchedulerTrace(gdb.Breakpoint):
|
|
162
|
+
"""Trace elections at sched_elect entry, sampling the elected handle on return."""
|
|
163
|
+
|
|
164
|
+
def __init__(self, parent: RtosCmd, limit: int) -> None:
|
|
165
|
+
self.parent = parent
|
|
166
|
+
self.limit = limit
|
|
167
|
+
self.elections: list[str] = []
|
|
168
|
+
super().__init__("sched_elect", internal=True)
|
|
169
|
+
|
|
170
|
+
def delete(self) -> None:
|
|
171
|
+
"""Remove the breakpoint and clear the active scheduler trace."""
|
|
172
|
+
if self.is_valid():
|
|
173
|
+
super().delete()
|
|
174
|
+
if self.parent.state.scheduler_trace is self:
|
|
175
|
+
self.parent.state.scheduler_trace = None
|
|
176
|
+
|
|
177
|
+
def stop(self) -> bool:
|
|
178
|
+
"""Place a finish breakpoint to observe the elected task."""
|
|
179
|
+
try:
|
|
180
|
+
_ElectionReturn(self)
|
|
181
|
+
except gdb.error as error:
|
|
182
|
+
gdb.post_event(self.delete)
|
|
183
|
+
CONSOLE.print(f"[red]Cannot trace scheduler return: {error}[/red]")
|
|
184
|
+
return True
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class _ElectionReturn(gdb.FinishBreakpoint):
|
|
189
|
+
"""Capture the elected task after sched_elect updates scheduler state."""
|
|
190
|
+
|
|
191
|
+
def __init__(self, trace: SchedulerTrace) -> None:
|
|
192
|
+
self.trace = trace
|
|
193
|
+
super().__init__(gdb.newest_frame(), internal=True)
|
|
194
|
+
|
|
195
|
+
def stop(self) -> bool:
|
|
196
|
+
"""Record the returned task and stop when the requested count is reached."""
|
|
197
|
+
trace = self.trace
|
|
198
|
+
try:
|
|
199
|
+
selected_rtos = trace.parent.state.selected_rtos
|
|
200
|
+
if selected_rtos is None:
|
|
201
|
+
raise ValueError("No RTOS selected during scheduler trace")
|
|
202
|
+
value = self.return_value
|
|
203
|
+
handle = int(value if value is not None else gdb.parse_and_eval("$r0"))
|
|
204
|
+
task = selected_rtos.elected_task(trace.parent.state.task_list, handle)
|
|
205
|
+
except (gdb.error, ValueError) as error:
|
|
206
|
+
gdb.post_event(trace.delete)
|
|
207
|
+
CONSOLE.print(f"[red]Cannot read elected task: {error}[/red]")
|
|
208
|
+
return True
|
|
209
|
+
trace.elections.append(task)
|
|
210
|
+
if len(trace.elections) < trace.limit:
|
|
211
|
+
return False
|
|
212
|
+
gdb.post_event(trace.delete)
|
|
213
|
+
CONSOLE.print(selected_rtos.scheduling_chart(trace.elections))
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class _RtosSubcommand(gdb.Command):
|
|
218
|
+
"""Base class for concrete ``rtos`` subcommands registered under the prefix command."""
|
|
219
|
+
|
|
220
|
+
def __init__(self, parent: RtosCmd, name: str, is_prefix: bool = False) -> None:
|
|
221
|
+
self.parent = parent
|
|
222
|
+
self.name = name
|
|
223
|
+
super().__init__(f"rtos {name}", gdb.COMMAND_USER, gdb.COMPLETE_NONE, is_prefix)
|
|
224
|
+
|
|
225
|
+
def _argv(self, arg: str) -> list[str]:
|
|
226
|
+
return list(gdb.string_to_argv(arg)) if arg.strip() else []
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class RtosSelectCmd(_RtosSubcommand):
|
|
230
|
+
"""Select a supported RTOS for the current GDB session."""
|
|
231
|
+
|
|
232
|
+
def __init__(self, parent: RtosCmd) -> None:
|
|
233
|
+
super().__init__(parent, "select")
|
|
234
|
+
|
|
235
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
236
|
+
"""Select a supported RTOS."""
|
|
237
|
+
del from_tty
|
|
238
|
+
self.parent._invoke_select(self._argv(arg))
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class RtosListCmd(_RtosSubcommand):
|
|
242
|
+
"""List supported RTOS implementations and identify the selected one."""
|
|
243
|
+
|
|
244
|
+
def __init__(self, parent: RtosCmd) -> None:
|
|
245
|
+
super().__init__(parent, "list")
|
|
246
|
+
|
|
247
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
248
|
+
"""Display supported RTOS implementations and the current selection."""
|
|
249
|
+
del from_tty
|
|
250
|
+
self.parent._invoke_list(self._argv(arg))
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class RtosLoadProjectCmd(_RtosSubcommand):
|
|
254
|
+
"""Load symbols from a project of the selected RTOS."""
|
|
255
|
+
|
|
256
|
+
def __init__(self, parent: RtosCmd) -> None:
|
|
257
|
+
super().__init__(parent, "load-project")
|
|
258
|
+
|
|
259
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
260
|
+
"""Load project symbols and task metadata for the selected RTOS."""
|
|
261
|
+
del from_tty
|
|
262
|
+
self.parent._invoke_load_project(self._argv(arg))
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class RtosShowCmd(_RtosSubcommand):
|
|
266
|
+
"""Show the kernel and task memory layout of the loaded RTOS project."""
|
|
267
|
+
|
|
268
|
+
def __init__(self, parent: RtosCmd) -> None:
|
|
269
|
+
super().__init__(parent, "show", True)
|
|
270
|
+
|
|
271
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
272
|
+
"""Display the loaded project's task memory layout."""
|
|
273
|
+
del from_tty
|
|
274
|
+
self.parent._invoke_show(self._argv(arg))
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class RtosShowTaskCmd(_RtosSubcommand):
|
|
278
|
+
"""Inspect a task's live kernel context on the stopped target."""
|
|
279
|
+
|
|
280
|
+
def __init__(self, parent: RtosCmd) -> None:
|
|
281
|
+
super().__init__(parent, "show task")
|
|
282
|
+
|
|
283
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
284
|
+
"""Display a task's live context and saved stack."""
|
|
285
|
+
del from_tty
|
|
286
|
+
self.parent._invoke_show_task(self._argv(arg))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class RtosShowschedCmd(_RtosSubcommand):
|
|
290
|
+
"""Trace and chart the next scheduler elections."""
|
|
291
|
+
|
|
292
|
+
def __init__(self, parent: RtosCmd) -> None:
|
|
293
|
+
super().__init__(parent, "showsched")
|
|
294
|
+
|
|
295
|
+
def invoke(self, arg: str, from_tty: bool) -> None:
|
|
296
|
+
"""Trace the requested number of scheduler elections."""
|
|
297
|
+
del from_tty
|
|
298
|
+
self.parent._invoke_showsched(self._argv(arg))
|