Skip to content

Commit 82ae62d

Browse files
authored
docs: add GDB stub and source-level debugging to reproducing-runs tutorial (#315)
1 parent 58b6513 commit 82ae62d

File tree

4 files changed

+220
-14
lines changed

4 files changed

+220
-14
lines changed

docs/src/config/common-options.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,3 +359,6 @@ and let an external debugger (for example a GDB stub) control resume, set:
359359
```python
360360
@tsffs.repro_auto_continue = False
361361
```
362+
363+
See [Debugging Live with a GDB Stub](../tutorials/edk2-uefi/reproducing-runs.md#debugging-live-with-a-gdb-stub)
364+
for a full walkthrough.

docs/src/tutorials/edk2-uefi/reproducing-runs.md

Lines changed: 209 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
# Reproducing Runs
22

33
It is unlikely you'll find any bugs with this harness (if you do, report them to edk2!),
4-
but we can still test the "repro" functionality which allows you to replay an execution
5-
of a testcase from an input file. After pressing Ctrl+C during execution, list the
6-
corpus files (tip: `!` in front of a line in the SIMICS console lets you run shell
7-
commands):
4+
but we can still test the
5+
["repro" functionality](../../fuzzing/analyzing-results.md)
6+
which allows you to replay an execution of a testcase from an input file.
7+
8+
## Listing and Examining Testcases
9+
10+
After pressing Ctrl+C during execution, list the corpus files (tip: `!` in front of a
11+
line in the SIMICS console lets you run shell commands):
812

913
```txt
1014
simics> !ls corpus
@@ -30,8 +34,14 @@ We can tell the fuzzer that we want to run with this specific input by using:
3034
simics> @tsffs.iface.fuzz.repro("%simics%/corpus/4385dc33f608888d")
3135
```
3236

33-
The simulation will run once with this input, then output a message that you can replay
34-
the simulation by running:
37+
> **Note:** You can change the testcase you are examining by choosing a different one
38+
> with `tsffs.iface.fuzz.repro`, but you cannot resume fuzzing after entering repro
39+
> mode due to inconsistencies with the simulated system clock.
40+
41+
## Inspecting State with SIMICS Reverse Debugging
42+
43+
By default, the simulation runs the testcase through to completion and saves a bookmark
44+
at the point the harness was triggered. You can then replay the execution by running:
3545

3646
```txt
3747
simics> reverse-to start
@@ -41,12 +51,197 @@ From here, you can examine memory and registers (with `x`), single step executio
4151
and more! Check out the SIMICS documentation and explore all the deep debugging
4252
capabilities that SIMICS offers. When you're done exploring, run `c` to continue.
4353

44-
You can change the testcase you are examining by choosing a different one with
45-
`tsffs.iface.fuzz.repro`, but you cannot resume fuzzing after entering repro mode due
46-
to inconsistencies with the simulated system clock.
54+
## Debugging Live with a GDB Stub
55+
56+
Reverse debugging is great for inspecting state after a testcase has run. If you instead
57+
want to step through execution as it happens (setting breakpoints, watching registers
58+
change, using a familiar GDB workflow), you can attach a GDB stub before the testcase
59+
executes.
60+
61+
**Step 1: Enable repro mode without auto continue.**
62+
63+
Add the following to your SIMICS script:
64+
65+
```python
66+
@tsffs.iface.fuzz.repro("%simics%/corpus/4385dc33f608888d")
67+
@tsffs.repro_auto_continue = False
68+
```
69+
70+
With this set, when the harness start (`HARNESS_START`) is hit, TSFFS will write the
71+
testcase into the target's buffer and pause the simulation there, waiting for you to
72+
resume manually.
73+
74+
```c
75+
if (!Input) {
76+
return EFI_OUT_OF_RESOURCES;
77+
}
78+
79+
HARNESS_START(Input, &InputSize); // <-- Simulation is paused here
80+
81+
Print(L"Input: %p Size: %d\n", Input, InputSize);
82+
```
83+
84+
**Step 2: Start the GDB stub.**
85+
86+
SIMICS exposes a GDB remote stub via the `new-gdb-remote` command. Start it by
87+
appending it to your invocation on the command line so it runs after your script
88+
finishes loading:
89+
90+
```sh
91+
./simics run.simics -e new-gdb-remote
92+
```
93+
94+
Or add it at the end of your script directly, after the `run` line:
95+
96+
```txt
97+
new-gdb-remote
98+
```
99+
100+
SIMICS output:
101+
102+
```txt
103+
[tsffs info] Stopped for repro. Restore to start bookmark with 'reverse-to start'
104+
No CPU is specified; using current processor.
105+
[gdb0 info] Attached to CPU: qsp.mb.cpu0.core[0][0]
106+
Warning: This can expose the target system on the host local network.
107+
[gdb0 info] Awaiting GDB connections on port 9123.
108+
[gdb0 info] Connect from GDB using: "target remote localhost:9123"
109+
```
110+
111+
**Step 3: Connect from your GDB client.**
112+
113+
From a separate terminal, connect to the stub. You now have full GDB control over the
114+
simulated target. Set breakpoints, step through instructions, inspect memory and
115+
registers. The simulation will only advance when you tell it to.
116+
117+
```sh
118+
(gdb) target remote :9123
119+
Remote debugging using :9123
120+
warning: No executable has been specified and target does not support
121+
determining executable automatically. Try using the "file" command.
122+
0x00000000dd5b7c8c in ?? ()
123+
(gdb) bt
124+
#0 0x00000000dd5b7c8c in ?? ()
125+
#1 0x0000000000000000 in ?? ()
126+
(gdb) x/10i $rip
127+
=> 0xdd5b7c8c: cpuid
128+
0xdd5b7c8e: incq -0x128(%rbp)
129+
0xdd5b7c95: jne 0xdd5b7743
130+
0xdd5b7c9b: lea 0x2a6a4(%rip),%rcx # 0xdd5e2346
131+
0xdd5b7ca2: call 0xdd5b5af9
132+
0xdd5b7ca7: mov 0x4f6a2(%rip),%rax # 0xdd607350
133+
0xdd5b7cae: mov $0x1,%edx
134+
0xdd5b7cb3: mov -0x118(%rbp),%rcx
135+
0xdd5b7cba: call *0x30(%rax)
136+
0xdd5b7cbd: test %rax,%rax
137+
(gdb)
138+
```
139+
140+
## Source-Level Debugging
141+
142+
Once connected with a GDB stub, you can go further and load debug symbols so GDB shows
143+
C source lines, function names, and local variables instead of raw addresses. This
144+
requires two additional pieces of setup: OS awareness in the SIMICS script, and loading
145+
the symbols at the correct address in GDB.
146+
147+
**Step 1: Add OS awareness to the script.**
148+
149+
Add the following block to your `run.simics` right after the `load-target` line:
47150

48-
> **Tip:** If you want to attach a debugger (such as a GDB stub) before the
49-
> testcase executes, set `@tsffs.repro_auto_continue = False`. When the harness
50-
> is triggered, TSFFS will prepare the testcase but wait for you to resume
51-
> simulation manually. See [Disable Auto-Continue in Repro Mode](../../config/common-options.md#disable-auto-continue-in-repro-mode)
52-
> for details.
151+
```txt
152+
new-os-awareness name = qsp.software
153+
qsp.software.insert-tracker tracker = uefi_fw_tracker_comp
154+
qsp.software.tracker.detect-parameters -load
155+
qsp.software.enable-tracker
156+
```
157+
158+
This instructs SIMICS to track which EFI modules are loaded and at what addresses.
159+
160+
**Step 2: Find the load address of `Tutorial.efi`.**
161+
162+
After the simulation has started and the target has booted, run in the SIMICS console:
163+
164+
```txt
165+
simics> qsp.software.tracker.list-modules max = 100
166+
```
167+
168+
Look for `Tutorial.efi` in the output:
169+
170+
```txt
171+
│ 85│Tutorial.efi │ 0xdd548000│ 0xc3b80│
172+
```
173+
174+
The `Loaded Address` column gives the base address of the module (`0xdd548000` in this
175+
example).
176+
177+
**Step 3: Find the `.text` section RVA.**
178+
179+
On your host, inspect the debug file produced by the build:
180+
181+
```sh
182+
$ objdump -h project/Tutorial.debug
183+
project/Tutorial.debug: file format elf64-x86-64
184+
185+
Sections:
186+
Idx Name Size VMA LMA File off Algn
187+
0 .text 0009a9d4 0000000000000240 0000000000000240 00000100 2**6
188+
1 .data ...
189+
```
190+
191+
The VMA of the `.text` section is `0x240`. Since EFI binaries are position-independent,
192+
this equals the RVA to add to the load address.
193+
194+
**Step 4: Load symbols in GDB.**
195+
196+
In your GDB session, load the debug file using the load address from step 2 and the
197+
`.text` VMA from step 3:
198+
199+
```sh
200+
(gdb) add-symbol-file project/Tutorial.debug 0xdd548000+0x240
201+
```
202+
203+
GDB will now resolve addresses to source lines and function names. You can set
204+
breakpoints by function or file location, step through C source, and inspect named
205+
variables.
206+
207+
**Step 5: Map source paths.**
208+
209+
The debug symbols reference the paths as they existed inside the Docker build container
210+
(rooted at `/edk2`). The build script can copy those sources to your host under
211+
`edk2-uefi/edk2/` when run with `COPY_SOURCES=1`:
212+
213+
```sh
214+
COPY_SOURCES=1 ./build.sh
215+
```
216+
217+
Then tell GDB how to map the container paths to your local copy:
218+
219+
```sh
220+
(gdb) set substitute-path /edk2 /absolute/path/to/examples/tutorials/edk2-uefi/edk2
221+
```
222+
223+
GDB will now find source files automatically when stepping through code.
224+
225+
```sh
226+
(gdb) bt
227+
#0 0x00000000dd5b89c6 in UefiMain (SystemTable=<optimized out>, ImageHandle=<optimized out>) at /edk2/Tutorial/Tutorial.c:58
228+
#1 ProcessModuleEntryPointList (SystemTable=<optimized out>, ImageHandle=<optimized out>) at /edk2/Tutorial/Build/CryptoPkg/All/DEBUG_GCC/X64/Tutorial/Tutorial/DEBUG/AutoGen.c:319
229+
#2 _ModuleEntryPoint (ImageHandle=<optimized out>, SystemTable=<optimized out>) at /edk2/MdePkg/Library/UefiApplicationEntryPoint/ApplicationEntryPoint.c:58
230+
#3 0x00000000df32e14c in ?? ()
231+
#4 0x00000001df322a88 in ?? ()
232+
#5 0x00000000df322a98 in ?? ()
233+
#6 0x00000000df322a20 in ?? ()
234+
#7 0x0000000000000000 in ?? ()
235+
(gdb) list .
236+
53 BOOLEAN Status = X509VerifyCert(Cert, CertSize, CACert, CACertSize);
237+
54
238+
55 if (Status) {
239+
56 HARNESS_ASSERT();
240+
57 } else {
241+
58 HARNESS_STOP();
242+
59 }
243+
60
244+
61 if (Input) {
245+
62 FreePages(Input, EFI_SIZE_TO_PAGES(MaxInputSize));
246+
(gdb)
247+
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
project/*
22
src/tsffs.h
33
!project/run.simics
4+
edk2/

examples/tutorials/edk2-uefi/build.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ for file_ext in efi map debug; do
2525
"${SCRIPT_DIR}/project/Tutorial.${file_ext}"
2626
done
2727

28+
# copy edk2 sources for source-level GDB debugging (opt-in: set COPY_SOURCES=1)
29+
if [ "${COPY_SOURCES:-0}" = "1" ]; then
30+
# rm -rf first to work around a docker cp bug where existing directories are not updated
31+
rm -rf "${SCRIPT_DIR}/edk2"
32+
docker cp "${CONTAINER_NAME}:/edk2" "${SCRIPT_DIR}/edk2"
33+
fi
34+
2835
docker rm -f "${CONTAINER_NAME}"
2936

3037
# ensure corpus

0 commit comments

Comments
 (0)