summary refs log tree commit diff
diff options
context:
space:
mode:
authorArun Isaac2022-10-19 15:58:52 +0530
committerArun Isaac2022-10-19 15:59:25 +0530
commit21f7f9650a33f81af3f149188f8ec15089694f6d (patch)
tree5fffe4e0a962dca74d9f545257ca1e9e9c65b3b7
parent21f33beec687e71bef2b33238a14b4e907aa8ec3 (diff)
downloadgn-gemtext-21f7f9650a33f81af3f149188f8ec15089694f6d.tar.gz
Add RISC-V debugging using GDB and QEMU.
-rw-r--r--topics/riscv/debug-riscv-assembly-with-qemu-and-gdb.gmi123
1 files changed, 123 insertions, 0 deletions
diff --git a/topics/riscv/debug-riscv-assembly-with-qemu-and-gdb.gmi b/topics/riscv/debug-riscv-assembly-with-qemu-and-gdb.gmi
new file mode 100644
index 0000000..bcb51ba
--- /dev/null
+++ b/topics/riscv/debug-riscv-assembly-with-qemu-and-gdb.gmi
@@ -0,0 +1,123 @@
+# Debug RISC-V assembly with QEMU and GDB
+
+In this tutorial, we will write RISC-V assembly code, run it in the QEMU emulator and debug it using GDB, the GNU debugger.
+
+First, we write a short manifest file so that we can pull in all the tools we need using the ever-convenient Guix.
+
+## Install the required tools
+
+```
+(use-modules (gnu packages cross-base)
+             (gnu packages gdb)
+             (gnu packages virtualization))
+
+(define cross-base
+  (@@ (gnu packages cross-base) cross))
+
+(packages->manifest
+ (list (cross-base gdb "riscv64")
+       (cross-binutils "riscv64")
+       qemu))
+```
+Put the above in a file manifest.scm, and run
+```
+$ guix shell -m manifest.scm
+```
+This drops us into a shell with a gdb, as, ld and qemu for riscv64.
+```
+[env]$ which riscv64-gdb riscv64-as riscv64-ld qemu-riscv64
+/gnu/store/…-profile/bin/riscv64-gdb
+/gnu/store/…-profile/bin/riscv64-as
+/gnu/store/…-profile/bin/riscv64-ld
+/gnu/store/…-profile/bin/qemu-riscv64
+```
+
+## Assemble, link and run a Hello World RISC-V assembly program
+
+Here we have a simple Hello World program that prints the string "Hello World" using the write syscall (64) and exits with status 0 using the exit syscall (93).
+```
+        .global _start
+        
+        .data
+str:    .string "Hello world!\n"
+
+        .text
+_start: li a0, 1                # stdout file descriptor
+        la a1, str              # load address of str
+        li a2, 13               # length of str
+        li a7, 64               # write syscall
+        ecall
+
+exit:   li a0, 0                # set exit code
+        li a7, 93               # exit syscall
+        ecall
+```
+We put this in a file hello.s, assemble and link it. When assembling, we pass the -gstabs flag so that GDB debugging information is generated.
+```
+[env]$ riscv64-as hello.s -gstabs -o hello.o
+[env]$ riscv64-ld hello.o -o hello
+```
+Now that we have our executable, we can run it in qemu.
+```
+[env]$ qemu-riscv64 hello
+Hello World!
+```
+
+## Debug using GDB
+
+We run the hello executable in qemu but tell it to wait for a gdb connection on port 1234.
+```
+[env]$ qemu-riscv64 -g 1234 hello
+```
+While qemu is waiting in a shell, in another shell we run gdb and connect to port 1234.
+```
+[env]$ riscv64-gdb hello
+(gdb) target remote :1234
+Remote debugging using :1234
+_start () at hello.s:7
+7	_start: li a0, 1                # stdout file descriptor
+```
+We have dropped into debugging the program at the _start label in the program. We may step through the instructions one by one using the next command.
+```
+(gdb) next
+8	        la a1, str              # load address of str
+(gdb) next
+9	        li a2, 13               # length of str
+(gdb) next
+10	        li a7, 64               # write syscall
+```
+We may continue normal execution using the continue command.
+```
+(gdb) continue
+Continuing.
+[Inferior 1 (process 1) exited normally]
+```
+Finally, quit.
+```
+(gdb) quit
+```
+
+## Other useful gdb commands
+
+We may also set breakpoints at different labels using the break command. For example, to break execution at the exit label:
+```
+(gdb) break exit
+```
+We may print memory or the value of CPU registers. To print the value of CPU register a0 using
+```
+(gdb) print $a0
+```
+If register a0 contains a memory address, we may print, say 10 bytes, at that address using
+```
+(gdb) x/10xb $a0
+0x12000:	0x48	0x65	0x6c	0x6c	0x6f	0x20	0x77	0x6f
+0x12008:	0x72	0x6c
+```
+Or, we may print 10 characters at that address.
+```
+(gdb) x/10cb $a0
+0x12000:	72 'H'	101 'e'	108 'l'	108 'l'	111 'o'	32 ' '	119 'w'	111 'o'
+0x12008:	114 'r'	108 'l'
+```
+There are many more commands. Here is a good GDB cheatsheet listing the commonly used ones.
+=> https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf