Debugging Guest Applications with QEMU and GDB
This section will cover how to debug a guest application with QEMU and GDB, and will cover different methods of debugging such as:
Intrusive debugging (debugging so that when a breakpoint is hit, the kernel is paused as well)
Non-intrusive debugging (debugging so that when a breakpoint is hit, the kernel is not paused)
Since QEMU emulates the CPU, the breakpoints set by GDB are hardware breakpoints, not software breakpoints.
Differences Between Zynq UltraScale+ MPSoC and Versal Adaptive SoC
The examples on this page are done on the Zynq UltraScale+ MPSoC platform.
On Versal Adaptive SoC, the differences are:
2 ARM-A CPUs instead of 4
Acquiring the Tools
The tools mentioned in this guide are:
aarch64-none-elf-gdbaarch64-linux-gnu-gdbarm-none-eabi-gdbgdb-multiarchmb-gdb
aarch64-none-elf-gdb, aarch64-linux-gnu-gdb, arm-none-eabi-gdb, and mb-gdb are bundled with PetaLinux, Yocto, and Vitis tools.gdb-multiarch can be downloaded through your package manager (e.g. apt-get).
Kernel-Intrusive Debugging
Kernel-intrusive debugging allows you to debug the kernel or bare-metal image on your guest, and has the added benefit of being easier to set up.
The main disadvantage of intrusive debugging with QEMU is only being able to capture SIGINT and SIGTRAP signals.
This means if your program has a segmentation fault and a SIGSEGV is emitted, it will not be caught and your program will exit.
Intrusive debugging requires QEMU parameters to connect to its GDB server, and additional GDB commands in order to start a debugging session.
Enabling a GDB connection to QEMU
QEMU contains a GDB server that you can connect to, allowing you to debug your QEMU application.
To enable connection to the GDB server, you need to pass in a parameter to QEMU that specify the hostname and port it should listen on.
QEMU parameter | Info |
|---|---|
| Makes QEMU's GDB server listen on host hostname on port port. Generally the hostname is "localhost" and the port can be anything, as long as you can connect to it. |
| Makes QEMU's GDB server listen on host hostname on port port and makes emulation start in a paused state. To un-pause emulation, connect to QEMU using GDB and use the continue command. |
| Shorthand for |
-gdb tcp:localhost:9000If booting QEMU using PetaLinux, the primary machine will typically listen on localhost:9000.
For example, if booting a ZCU102 machine using PetaLinux, the ARM machine will listen on localhost:9000, while the Microblaze machine will not have remote debugging enabled.
To simultaneously debug both MicroBlaze and ARM machines in a multi-arch environment with PetaLinux, use the --pmu-qemu-args='-gdb tcp:<hostname>:<port>' argument to enable debugging on the MicroBlaze QEMU machine.
Connecting GDB to QEMU
To connect GDB to QEMU, you need to use the GDB that corresponds to your target's architecture.
For example, some of the architectures in the AMD QEMU environments are:
GDB | Architecture |
|---|---|
| ARM A53, ARM A72 |
| ARM R5
This is because QEMU will give register information for the first CPU cluster, which are the ARM A72 or ARM A53 CPUs. If this happens, you can debug the ARM R5s with |
| Multiple ARM architectures, and others (excluding Microblaze) |
| Microblaze |
In this guide, we will use gdb-multiarch to debug the ARM CPUs.
To connect to QEMU's GDB server using your host GDB, you need to create a remote connection.
Once you are connected, you can debug your emulated environment like you would debug any other program.
GDB command | Info |
|---|---|
| Attempts to connect to host hostname on port port. |
| Attempts to connect to host hostname on port port, and will remain connected after the debugged program exits or GDB detaches from it. |
$ aarch64-none-elf-gdb hello_a53.elf
GNU gdb (Linaro GDB 2018.04) 8.0.50.20171128-git
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-unknown-linux-gnu --target=aarch64-none-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /proj/xhdsswstaff/saipava/epdsw1/work/hello_a53.elf...done.
(gdb)
(gdb) target remote:9000
Remote debugging using :9000
_vector_table () at asm_vectors.S:208
208 b _boot
(gdb) b main
Breakpoint 1 at 0x1740: file ../src/main.c, line 75.
(gdb) c
Continuing.
Thread 1 "" hit Breakpoint 1, main () at ../src/main.c:75
75 BootStatus = XPm_GetBootStatus();
(gdb)Non-Kernel-Intrusive Debugging
Rather than running GDB on the host machine and using the GDB server provided by QEMU, it's possible to run GDB on the guest machine as long as the guest supports it.
This can provide a series of advantages, such as:
Only debugging your program (assuming you're not debugging your kernel)
Being able to catch more signals, such as
SIGSEGVNot losing control of GDB to other signals, such as
SIGTRAP
The disadvantages are that it is more work to install GDB on the guest machine, assuming it can be installed at all, and it uses storage space on the guest.
Installing GDB on the Guest
For this example, we will run GDB on a Linux guest, on the 64-bit ARM-A CPUs on a Zynq UltraScale+ MPSoC QEMU machine.
The machine's image will be a default PetaLinux ZCU102 image, and we will install GDB by copying the files from the host using SCP.
Download the ARM64 GDB package from here and save it somewhere.
Unpack the package. This should produce the folders
DEBIAN,etc, andusr.$ sudo dpkg-deb -R gdb_7.12-6_arm64.deb .Compress the
etcandusrfolders. Optionally delete theDEBIAN,etc, andusrfolders when you are done.
On a default PetaLinux image,zipandtarfiles are supported.$ zip -r gdb.zip etc usrOn the guest, copy the compressed file via SCP and extract it.
root@xilinx-zcu102-2020_2:~# scp <host user>@<host IP>:/path/to/gdb.zip . root@xilinx-zcu102-2020_2:~# unzip gdb.zipOn the guest, copy the extracted contents to root.
root@xilinx-zcu102-2020_2:~# cp -r etc usr /
Running GDB on the Guest
GDB can be run from the guest as if you would normally run it from the host.
If your binary and source files are not on the guest machine, copy them from the host before running GDB.
root@xilinx-zcu102-2020_2:~# scp <host user>@<host IP>:/scratch/proj/gdb-test/segfault.c .
<host user>@<host IP>'s password:
segfault.c 100% 171 0.2KB/s 00:00
root@xilinx-zcu102-2020_2:~# scp <host user>@<host IP>:/scratch/proj/gdb-test/segfault.elf .
<host user>@<host IP>'s password:
segfault.elf 100% 11KB 10.6KB/s 00:00
root@xilinx-zcu102-2020_2:~# gdb
gdb: /lib/libncurses.so.5: no version information available (required by gdb)
gdb: /lib/libncurses.so.5: no version information available (required by gdb)
gdb: /lib/libncurses.so.5: no version information available (required by gdb)
gdb: /lib/libtinfo.so.5: no version information available (required by gdb)
GNU gdb (Debian 7.12-6) 7.12.0.20161007-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "aarch64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) file segfault.elf
Reading symbols from segfault.elf...done.
(gdb) r
Starting program: /home/root/segfault.elf
Program received signal SIGSEGV, Segmentation fault.
0x00000000004005d8 in main () at segfault.c:8
8 *p_null = 0xAA55AA55;
(gdb) GDB Commands
This section will cover GDB commands used regardless if GDB is used remotely or locally.
This section covers a small subset of what is available. A full list can be found here.
Loading Symbols
GDB requires symbols from the program being executed, otherwise it won't know anything about the program and won't be able to debug.
You can still debug your application without symbols loaded, however it will be much more difficult.
Command | Info |
|---|---|
| Loads the symbols in file.elf into GDB This option is used when starting GDB, for example: |
| Loads the symbols in file.elf into GDB. symbol-file can only store symbols from one file at a time. |
| Loads the symbols in file.elf into GDB. addr is the start address of the .text section in file.elf. To find the address of .text you can do:
|
(gdb) symbol-file test.elf
Reading symbols from test.elf...done.Controlling Execution
Command | Info |
|---|---|
| Starts execution |
| Pauses execution. |
| Continues execution. |
| Kills the current process. |
| Quits GDB. |
komlodi@machine:/scratch/gdb-test$ gdb test.elf
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from test.elf...done.
(gdb) r
Starting program: /scratch/gdb-test/test.elf
^C
Program received signal SIGINT, Interrupt.
main () at test.c:23
23 while(1);
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
main () at test.c:23
23 while(1);
(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) q
komlodi@machine:/scratch/gdb-test$Breakpoints and Watchpoints
Breakpoints allow you to pause execution at a specific point in your code. This allows you to observe program state at a specific point in time.
Watchpoints allow you to pause execution when the value of an expression changes. This can include a variable or address being modified, or a more complicated expression such as "var < 10".
Command | Info |
|---|---|
| Gives you information on all breakpoints in your GDB session. |
| Gives you information on all watchpoints in your GDB session. |
| Adds a breakpoint at line line-number in file or at the start of function function-name. The breakpoint number generated here is used when disabling or deleting a breakpoint. |
| Adds a watchpoint that pauses program when variable var or address addr are modified. Watchpoints will trigger after the instruction is executed, meaning GDB will pause on the line after the watchpoint triggers. When using a watchpoint with an expression, GDB will evaluate the expression and pause when it is non-zero. |
| Disables breakpoint breakpoint-number |
| Enables breakpoint breakpoint-number |
| Deletes breakpoint breakpoint-number |
Reading symbols from test.elf...done.
(gdb) b main
Breakpoint 1 at 0x4005de: file test.c, line 20.
(gdb) watch global_var & 0x00000003
Hardware watchpoint 2: global_var & 0x00000003
(gdb) r
Starting program: /scratch/gdb-test/test.elf
Breakpoint 1, main () at test.c:20
20 {
(gdb) c
Continuing.
Hardware watchpoint 2: global_var & 0x00000003
Old value = 0
New value = 3
foo (s=0x7fffffffe1c0) at test.c:38
38 if (s->str == NULL) {
(gdb) p/x global_var
$1 = 0xcc33cc33
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000004005de in main at test.c:20
breakpoint already hit 1 time
2 hw watchpoint keep y global_var & 0x00000003
breakpoint already hit 1 time
(gdb) info watch
Num Type Disp Enb Address What
2 hw watchpoint keep y global_var & 0x00000003
breakpoint already hit 1 time
(gdb) d 1
(gdb) d 2
(gdb) info break
No breakpoints or watchpoints.
(gdb)
Stepping through your program
When paused, GDB allows you to control the program flow in a variety of ways.
Command | Info |
|---|---|
| Steps through one line of your program. |
| Steps through one line of your program. |
| Steps through one instruction of your program. |
| Steps through one instruction of your program. |
| Executes until the end of the current function. |
Reading symbols from test.elf...done.
(gdb) b main
Breakpoint 1 at 0x4005de: file test.c, line 20.
(gdb) r
Starting program: /scratch/gdb-test/test.elf
Breakpoint 1, main () at test.c:20
20 {
(gdb) n
23 foo(&s);
(gdb) s
foo (s=0x7fffffffe1c0) at test.c:30
30 s->str = "This is a string";
(gdb) n
31 memset(s->arr, 0xAA, sizeof(s->arr));
(gdb) n
32 s->var = 1234;
(gdb) c
Continuing.
Stack and frame information
Command | Info |
|---|---|
| Prints the call stack, in the order that functions were called, as a list of frames |
| Changes the current frame being observed to frame-number. |
| Gives you information on the current frame. |
| Gives you information of all static/global variables and symbols in your program. On some systems, this may print a lot of information. |
| Gives you information on local variables in the current frame. |
| Gives you information on arguments passed into the current frame. |
Breakpoint 1, foo (s=0x7fffffffe1c0) at test.c:30
30 s->str = "This is a string";
(gdb) backtrace
#0 foo (s=0x7fffffffe1c0) at test.c:30
#1 0x00000000004005f9 in main () at test.c:23
(gdb) frame 0
#0 foo (s=0x7fffffffe1c0) at test.c:30
30 s->str = "This is a string";
(gdb) info frame
Stack level 0, frame at 0x7fffffffe1c0:
rip = 0x400620 in foo (test.c:30); saved rip = 0x4005f9
called by frame at 0x7fffffffe200
source language c.
Arglist at 0x7fffffffe1b0, args: s=0x7fffffffe1c0
Locals at 0x7fffffffe1b0, Previous frame's sp is 0x7fffffffe1c0
Saved registers:
rbp at 0x7fffffffe1b0, rip at 0x7fffffffe1b8
(gdb) info locals
No locals.
(gdb) info args
s = 0x7fffffffe1c0
(gdb) frame 1
#1 0x00000000004005f9 in main () at test.c:23
23 foo(&s);
(gdb) info locals
s = {var = 0, arr = "\000\000\000\000\000\000\000",
str = 0x4006b0 <__libc_csu_init> "AWAVA\211\377AUATL\215%N\a ", c = -32 '\340', num = 0}
(gdb) info args
No arguments.
(gdb)
Printing Variables
gdb allows you to print out variable information using expressions. The expressions use C-like syntax.
Command | Info |
|---|---|
| Prints var. |
| Prints the first len values of arr. |
| Prints var as a hexadecimal number |
| Prints var as a signed integer. |
| Prints var as a binary number. |
| Prints var as a character. |
| Prints type definition of var, or the type definition of type. |
Reading symbols from test.elf...done.
(gdb) b foo
Breakpoint 1 at 0x400620: file test.c, line 30.
(gdb) r
Starting program: /scratch/gdb-test/test.elf
Breakpoint 1, foo (s=0x7fffffffe1c0) at test.c:30
(gdb) ptype s
type = volatile struct {
uint32_t var;
uint8_t arr[8];
char *str;
char c;
} *
30 s->str = "This is a string";
(gdb) n
31 memset(s->arr, 0xAA, sizeof(s->arr));
(gdb) n
32 s->c = 'z';
(gdb) n
33 s->var = 1;
(gdb) n
34 global_var = 0xCC33CC33;
(gdb) p s->str
$1 = 0x400734 "This is a string"
(gdb) p *s->str@4
$2 = "This"
(gdb) p/c s->str[0]
$3 = 84 'T'
(gdb) p/x s->arr
$4 = {0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa}
(gdb) p/t s->arr
$5 = {10101010, 10101010, 10101010, 10101010, 10101010, 10101010, 10101010, 10101010}
(gdb) p/d s->var
$7 = 1Modifying Variables
Command | Info |
|---|---|
| Sets the variable var to the value val. |
foo (s=0x7fffffffe1d0) at test.c:38
38 if (s->str == NULL) {
(gdb) p s->str
$1 = 0x400734 "This is a string"
(gdb) set s->str=0x00
(gdb) n
39 puts("s contains a NULL string");Macros
GDB provides commands to expand, list, and define macros.
In this case, 'macro' refers to any preprocessor definition, not just one that takes arguments. For example, each of the following lines would be considered a macro:
#define REG_FIELD_0_MASK 0x01
#define REG_FIELD_1_MASK (1 << 1)
#define REG_FIELD_X_MASK(x) (1 << x)Most compilers don't create macro information by default. For example, on gcc, you would create macro information by passing in the -g3 flag at compile time.
Command | Info |
|---|---|
| Expands, but does not parse, all preprocessor macros in expr. |
| Shows the current definition of macro macro, and where it was declared. |
| Shows all macros that are currently defined in function. This will also print all macro definitions defined in function, including ones from standard libraries. |
Breakpoint 1, main () at gdb-macro.c:13
13 for (i=0; i<sizeof(reg) * 8; ++i) {
(gdb) n
14 printf("bit %.2d: %d\n", i, REG_BIT_EXTRACT(reg, i));
(gdb) info macro REG_BIT_EXTRACT
Defined at /scratch/test-code/gdb-macro.c:5
#define REG_BIT_EXTRACT(reg, bit) (!!GET_BIT(reg, bit))
(gdb) info macro GET_BIT
Defined at /scratch/test-code/gdb-macro.c:4
#define GET_BIT(val, bit) (val & (1 << bit))
(gdb) macro exp REG_BIT_EXTRACT(reg, i)
expands to: (!!(reg & (1 << i)))
(gdb) macro exp REG_BIT_EXTRACT(0xAA55AA55, 0)
expands to: (!!(0xAA55AA55 & (1 << 0)))
(gdb) p REG_BIT_EXTRACT(0xAA55AA55, 0)
$1 = 1Signals
When debugging your code it may be useful to manage signals, particularly if they keep interrupting your GDB session.
QEMU GDB server is only capable of handling SIGINT and SIGTRAP signals. Meaning if your program receives a SIGSEGV signal, it will not be passed to GDB.
Command | Info |
|---|---|
| Prints information on all symbols, or only signal sig if specified |
| Sets a catchpoint at signals sig1 and sig2, or all signals if all is specified. |
| Handles signal sig depending on the following keywords:
|
(gdb) symbol-file myapp
Reading symbols from myapp...done.
(gdb) b main
Breakpoint 6 at 0x4008a4: file myapp.c, line 85.
(gdb) c
Continuing.
[Switching to Thread 1.1]
Thread 1 hit Breakpoint 6, main () at myapp.c:85
85 puts("Program start");
(gdb) n
Thread 1 received signal SIGTRAP, Trace/breakpoint trap.
0xffffff800810f814 in ?? ()
(gdb)
Cannot find bounds of current function
(gdb)
Cannot find bounds of current function
(gdb) c
Continuing.
^C
Thread 1 received signal SIGINT, Interrupt.
0xffffff800809c088 in ?? ()
(gdb) info signals SIGTRAP
Signal Stop Print Pass to program Description
SIGTRAP Yes Yes Yes Trace/breakpoint trap
(gdb) handle SIGTRAP nostop print
SIGTRAP is used by the debugger.
Are you sure you want to change it? (y or n) y
Signal Stop Print Pass to program Description
SIGTRAP No Yes Yes Trace/breakpoint trap
(gdb) c
Continuing.
[Switching to Thread 1.2]
Thread 2 hit Breakpoint 6, main () at myapp.c:85
85 puts("Program start");
(gdb) n
Thread 2 received signal SIGTRAP, Trace/breakpoint trap.
# Execution resumesThreads
Command | Info |
|---|---|
|
© 2025 Advanced Micro Devices, Inc. Privacy Policy