blob: 47676ab6ef8ecfb81f9719d67855b2c44c9cb2d7 [file] [log] [blame] [view]
# How to Use GDB with Renode to Debug seL4 Threads with Symbols
## High-Level Usage
The method for debugging seL4 threads with Renode is relatively simple, and
involves the usual suspects of `m debug-simulation` (or `m simulate-debug`) and `kgdb.sh`, but the
interface through GDB has changed slightly. In general, a debugging session now
looks like the following:
On one terminal, start an instance of Renode:
```bash
source build/setup.sh
m debug-simulation
```
[Note `m debug-simulation` gives GDB
control before anything runs while `m simulate-debug` lets Renode run
until `kgdb.sh` is invoked and contacts GDB. The former is necessary to
debug issues in the boot process (bootstrap, kernel, rootserver).]
Once Renode has started and starts outputting green debug logs, in another
terminal start up GDB with:
```bash
kgdb.sh
```
At this point, GDB should connect to the running Renode instance started by
`debug-simulation`, and you'll see something equivalent to the following (output
is not exact -- symbol reading may differ):
```
ghost:~/$ kgdb.sh
Source directories searched: /usr/local/google/home/sleffler/shodan/sw/tock:$cdir:$cwd
Reading symbols from out/shodan_boot_rom/multihart_boot_rom/multihart_boot_rom.elf...
add symbol table from file "out/shodan_boot_rom/multihart_boot_rom/multihart_boot_rom.elf"
Reading symbols from out/shodan_boot_rom/multihart_boot_rom/multihart_boot_rom.elf...
add symbol table from file "out/matcha/riscv32imc-unknown-none-elf/debug/matcha_platform"
Reading symbols from out/matcha/riscv32imc-unknown-none-elf/debug/matcha_platform...
add symbol table from file "out/matcha/riscv32imc-unknown-none-elf/debug/matcha_app"
Reading symbols from out/matcha/riscv32imc-unknown-none-elf/debug/matcha_app...
Remote debugging using localhost:3333
warning: multi-threaded target stopped without sending a thread-id, using first non-exited thread
0x00008090 in _reset_start ()
Symbol autoswitching is now disabled
(gdb)
```
At this point, GDB has stopped the simulator, giving you a chance to set
breakpoints and examine the system.
`kgdb.sh` will load symbols on the seL4 side, based on their
thread / CAmkES component, so there is no need to manually find the associated symbol file
and load it with the correct offsets. It may be necessary to switch symbol
tables, which can easily be done using `sel4 switch-symbols <component>`, which
will automatically load the symbols for CAmkES `<component>` with the appropriate offsets
(a `<component>` is the lower-case version of the CAmkES name, e.g. the CAmkES component
`DebugConsole` translates to `debug_console`.)
In this environment, there are two sets of breakpoints that can be used:
standard GDB `break`-style breakpoints, and renode-side `sel4 break`
breakpoints. The former work within the currently executing thread and can be
used for "local" breaks inside of a thread, whereas the latter can be set in any
thread that was previously discovered by the Renode extension.
Once a breakpoint is setup using `sel4 break` one can just
`continue`. For example, consider this debugging session:
```
<assuming cpu1 is halted and GDB is waiting at a prompt>
(gdb) sel4 wait-for-thread rootserver
Thread 2 "matcha.cpu1" received signal SIGTRAP, Trace/breakpoint trap.
[Switching to Thread 2]
0xff814802 in ?? ()
(gdb) # Switch to the 'rootserver' thread's symbol table and set a breakpoint on main
(gdb) sel4 switch-symbols rootserver
Reading symbols from out/cantrip/riscv32-unknown-elf/debug/capdl-loader...
(gdb) sel4 break rootserver main
(gdb) c
Continuing.
Thread 2 "matcha.cpu1" received signal SIGTRAP, Trace/breakpoint trap.
cantrip_os_rootserver::main () at src/main.rs:164
164 pub fn main() {
(gdb)
(gdb) # As we are in capdl-loader thread and proper symbols are loaded, all commands
(gdb) # like break work as expected.
(gdb) b init_system
Breakpoint 1 at 0x4c632: file /usr/local/google/home/sleffler/shodan/cantrip/projects/cantrip/apps/system/components/cantrip-os-common/src/model/mod.rs, line 200.
(gdb) c
Continuing.
Thread 2 "matcha.cpu1" hit Breakpoint 1, model::CantripOsModel::init_system (self=0x8e3cc8)
at /usr/local/google/home/sleffler/shodan/cantrip/projects/cantrip/apps/system/components/cantrip-os-common/src/model/mod.rs:200
200 self.init_copy_region();
```
Note that symbol tables are per-component but breakpoints are
per-thread. The seL4 GDB extentions assume all threads in a component
share the same VSpace and hence the same symbol table. Beware also
that when symbol names are ambiguous you can specify fully-qualified
names; e.g. above `main` was expanded to `cantrip_os_rootserver::main` and
`init_system` to `model::CantripOsModel::init_system`. Several well-known
special symbols are supported: `kernel` for the seL4 Kernel, `user`
for any thread running in user space, and `rootserver` for the first
seL4 thread that runs after boot.
## Temporary Breakpoints
In addition to regular GDB breakpoints the sel4 extentions support temporary
breakpoints. The syntax is `sel4 tbreak <threadname> [<symbol>]`. If `<symbol>`
is not specified GDB will stop on the next entry to `<threadname>`. This is
especially useful for stopping the next time you enter user space:
```
(gdb) sel4 thread
kernel
(gdb) sel4 tbreak user
(gdb) c
Continuing.
Thread 2 "matcha.cpu1" received signal SIGTRAP, Trace/breakpoint trap.
[Switching to Thread 2]
0xff80311c in ?? ()
```
or the kernel:
```
(gdb) sel4 tbreak kernel
(gdb) sel4 list-breakpoints
--------------------------
|Thread|Address|Temporary|
--------------------------
|kernel|any |True |
--------------------------
(gdb) c
Continuing.
Thread 2 "matcha.cpu1" received signal SIGTRAP, Trace/breakpoint trap.
0xff803000 in ?? ()
(gdb)
```
## Theory of Operation
The kgdb.sh / Renode symbol lookup mechanism is
composed of two parts: a custom Renode extension in
`sim/config/shodan_infrastructure/seL4Extensions.cs` and a custom GDB
extension in `sim/renode/tools/sel4_extensions/gdbscript.py`. `kgdb.sh`
is a simple bash script that starts GDB and connects to the appropriate
remote with the extension loaded.
The Renode extension tracks the VSpace setup for each thread and
translates virtual addresses to physical addresses. Without this work
symbols will be (almost always) incorrectly resolved because every VSpace
memory range starts at 0.
In addition, the extension provides commands to Renode to add debugging hooks to
an execution, all prefixed with the subcommand `seL4` (case matters at the
Renode prompt!). In our simulation config, Renode is configured to start the
seL4 extension on cpu1 using the `cpu1 CreateseL4` command.
The GDB extension provides a matching set of wrappers via the `sel4` command
prefix to examine the simulator state and take a look at threads in the system.
Both extensions provide built in help at their respective command lines, but
suffice it to say, the GDB extension is essentially just an automation mechanism
to load in symbols for the current thread context from disk when the Renode
extension detects a context switch.
During debugging, most symbols will not be loaded in GDB at the start, so it can
be helpful to use `sel4 wait-for-thread <threadname>` to tell Renode to break
execution upon entry of the given thread. When a break occurs the appropriate
symbols may be loaded with the `sel4 switch-symbols <threadname>` command or,
if `sel4 symbol-autoswitching true` is set then Renode will automatically switch
symbol tables based on the current thread. Symbol-autoswitching is disabled by
default because GDB does not have a reliable way to synchronize the
cmdline with the runtime so repeatedly switching between threads can leave
GDB in a bad state where it will crash on an assert.
To set a breakpoint for a thread that is not the current context, the thread
needs to be entered at least once. After that symbols in that thread can be
resolved so the `sel4 break <threadname> <symbol>` command works. Beware that
if `<symbol>` is wrong (e.g. mis-typed) the error diagnostic will go to the
Renode console, not to the GDB cmdline. Note also that using regular GDB
breakpoints will generate complaints about not being able to set the breakpoints
when switching to a different thread. Best to use `sel4 break` in seL4 user
threads and limit GDB `break` to contexts like the kernel or multihart_boot_rom.
## Debugging CantripOS applications
CantripOS supports dynamically loading application programs.
An application is much like a system component and GDB can be used similarl
to the description above except for loading symbols. Unlike a system component
there is no well-known mapping from the thread name to the symbol file
so you must manually load the symbols with the GDB add-symbol-file command.
For example, first you need to get Renode to the point where it knows about
the application's thread. This is done by first installing the application:
```
CANTRIP> install keyval.app
Collected 445892 bytes of data, crc32 a1275adf
Application "keyval" installed
```
Next wait for the ProcessManager to resume the application thread:
```
(gdb) sel4 switch-symbols process_manager
Reading symbols from out/cantrip/riscv32-unknown-elf/debug/process_manager.instance.bin...
(gdb) sel4 break process_manager_process_manager_proc_ctrl_0000_tcb cantrip_proc_manager::sel4bundle::{impl#1}::resume
(gdb) sel4 list-breakpoints
----------------------------------------------------------------------
|Thread |Address|Temporary|
----------------------------------------------------------------------
|process_manager_process_manager_proc_ctrl_0000_tcb|0x3FDEE|False |
----------------------------------------------------------------------
(gdb) c
Continuing.
```
Then kickoff the application from the console shell:
```
CANTRIP> start keyval
Bundle "keyval" started
```
When GDB stops at the breakpoint the thread will be known to seL4 and
you can use the usual seL4 gdb commands:
```
Thread 2 "matcha.cpu1" received signal SIGTRAP, Trace/breakpoint trap.
[Switching to Thread 2]
cantrip_proc_manager::sel4bundle::{impl#1}::resume (self=0x0) at cantrip-proc-manager/src/sel4bundle/mod.rs:725
725 fn resume(&self) -> Result<(), ProcessManagerError> {
(gdb) sel4 threads
...
keyval
(gdb) sel4 tbreak keyval
(gdb) sel4 list-breakpoints
----------------------------------------------------------------------
|Thread |Address|Temporary|
----------------------------------------------------------------------
|process_manager_process_manager_proc_ctrl_0000_tcb|0x3FDEE|False |
|keyval |any |True |
----------------------------------------------------------------------
(gdb) c
Continuing.
At this point you are stopped in the application at the first instruction and you will
want to manually load the application's symbols:
```
Thread 2 "matcha.cpu1" received signal SIGTRAP, Trace/breakpoint trap.
0x00010770 in exit ()
(gdb) add-symbol-file /usr/local/google/home/sleffler/shodan/out/cantrip/riscv32-unknown-elf/debug/apps/rust/keyval/keyval.elf
add symbol table from file "/usr/local/google/home/sleffler/shodan/out/cantrip/riscv32-unknown-elf/debug/apps/rust/keyval/keyval.elf"
Reading symbols from /usr/local/google/home/sleffler/shodan/out/cantrip/riscv32-unknown-elf/debug/apps/rust/keyval/keyval.elf...
```
From here on you should be able to work as normal. If there are
previously loaded symbols that conflict just remove them with gdb's
remove-symbol-file command (check the output of info file to see what
symbols gdb has loaded). Note that several of the above steps requires
Renode to single-step execution which is slow.
Beware that the sel4 extension code uses substring matching of component
names for operations like `sel4 break`. If the application name is a
substring (e.g. "timer") it is not possible to qualify the name. For the
moment you will want to use a different / unique name.
[*eventually the above process should be automated to eliminate the complexity*]
## Esoterica
The Renode extensions identify seL4 threads by intercepting
`seL4_DebugNameThread` system calls that assign a thread an ASCII
string. Typically this happens when the thread first starts up but
it can also happen otherwise; e.g. when the rootserver constructs
a thread it may assign a name. This means that a command like `sel4
wait-for-thread <threadname>` may stop GDB in an unexpected context (e.g.
the rootserver).
In order for the Renode extension to intercept `seL4_DebugNameThread`
system calls it must know the system call ID. Because seL4 does not
have a fixed ABI this number may change depending on how the system is
built and/or if the system source is modified. To deal with this the
system call ID is specified as an argument to the `CreateSeL4` Renode
directive; e.g. `monitor cpu1 CreateSeL4 0xffffffee`. If you notice seL4
threads are not identified and simulations slow to a crawl then check
the setting--when there is a msimatch Renode will be single-stepping
execution waiting for threads to be entered for the first time (the
`sel4 ready` command may also be helpful in diagnosing this behaviour).