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