Debugging Tips
Before GDB
Before stepping into gdb, I would recommand print statement first. Always think about whether a single print statement can solve your problem before you decide to use gdb.
Using GDB with QEMU
First, use command make qemu-gdb
to compile and start qemu.
$ make qemu-gdb
riscv64-linux-gnu-gcc -Wall -Werror -O -fno-omit-frame-pointer -ggdb -gdwarf-2 -MD -mcmodel=medany -fno-common -nostdlib
...
balloc: first 800 blocks have been allocated
balloc: write bitmap block at sector 45
*** Now run 'gdb' in another window.
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -global virtio-mmio.force-legacy=false -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -S -gdb tcp::25000
If you look at the Makefile
, you can find the target definition at the end of file.
K=kernel
U=user
...
qemu: $K/kernel fs.img
$(QEMU) $(QEMUOPTS)
.gdbinit: .gdbinit.tmpl-riscv
sed "s/:1234/:$(GDBPORT)/" < $^ > $@
qemu-gdb: $K/kernel .gdbinit fs.img
@echo "*** Now run 'gdb' in another window." 1>&2
$(QEMU) $(QEMUOPTS) -S $(QEMUGDB)
The difference between qemu
and qemu-gdb
is that qemu-gdb
will generate a gdb configuration file .gdbinit
, which will be further read by gdb for initialization. Therefore, please run following gdb-multiarch
(or riscv64-linux-gnu-gdb
or riscv64-unknown-elf-gdb
) under the same dicrectory so that they can find the initialization file.
Run gdb-multiarch
(or riscv64-linux-gnu-gdb
or riscv64-unknown-elf-gdb
) in another window UNDER THE DIRECTORY OF XV6.
If you start gdb and see a warning of the form 'warning: File "/home/xv6-riscv-UCR-CS202-Fall24/. gdbinit" auto-loading has been declined by ...', edit
~/.gdbinit
to add "add-auto-load-safe-path..." as suggected by the warning.
$ gdb-multiarch
GNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git
...
The target architecture is set to "riscv:rv64".
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
0x0000000000001000 in ?? ()
(gdb)
Afterwards, you can just use gdb as how you do for normal program.
For example, if we want to set breakpoint at line 137 of file syscall.c
:
(gdb) b syscall.c:137
Breakpoint 1 at 0x8000283c: file kernel/syscall.c, line 137.
Then you can input c
to continue the program and it will stop at the breakpoint set.
(gdb) c
Continuing.
[Switching to Thread 1.2]
Thread 2 hit Breakpoint 1, syscall () at kernel/syscall.c:137
137 struct proc *p = myproc();
Refer to MIT's sildes about common-used gdb commands.
Some are listed below:
continue
orc
, continue executing the program untill the next breakpoint or the end of program.step
ors
, execute the program by one step, if this is a function call, step into it.next
orn
, execute the program by one step without stepping into a function call.finish
, execute to the end of function.print <expression>
orp <expression>
: print the value of expression.display <expression>
: automatically display the value of expression whenever stop.set <variable> = <value>
: modify the value of variable.backtrace
orbt
: show the call stack.info frame
: show information about current frame.info locals
: how all local variables for current frame.list
orl
: show the source code.list <location>
: show the source code at specific location.