在Ubuntu上使用QEMU学习RISC-V程序(2)gdb调试
文章目录
- 一、准备工作
- 二、基本调试流程
- 1. 设置断点
- 2. 执行程序
- 3. 查看源代码/汇编
- 三、查看寄存器
- 1. 查看通用寄存器
- 2. 查看特殊寄存器
- 四、查看内存
- 1. 内存查看命令
- 2. 内存修改命令
- 五、调试实战示例
- 六、高级调试技巧
- 1. 条件断点
- 2. 自动显示
- 3. 内存断点(观察点)
- 4. 回溯调用栈
- 七、退出调试
- 八、退出QEMU
- 总结
在嵌入式开发中,GDB是调试RISC-V程序的核心工具。以下是使用
gdb-multiarch
调试
add.elf
程序的详细步骤,包括断点设置、寄存器查看、内存分析等关键操作:
一、准备工作
- 给elf文件增加调试信息
# 编译汇编代码(添加-g选项)
riscv64-unknown-elf-as -g -march=rv64g -mabi=lp64 add.s -o add.o# 链接(保持-g选项)
riscv64-unknown-elf-ld -g -T link.ld add.o -o add.elf# 检查文件是否包含调试信息
riscv64-unknown-elf-readelf -S add.elf | grep debug# 输出示例(应包含多个debug_*节)
.debug_info PROGBITS 0000000000000000 000002e0
.debug_abbrev PROGBITS 0000000000000000 000006e8
.debug_line PROGBITS 0000000000000000 000007e0
- 启动QEMU并暂停执行
qemu-system-riscv64 \-machine virt \-cpu rv64 \-m 128M \-nographic \-bios none \-kernel add.elf \-s -S # -s开启GDB服务器,-S暂停执行直到GDB连接
- 启动GDB并连接到QEMU
如果没有安装gdb-multiarch,先用sudo apt install gdb-multiarch进行安装。
gdb-multiarch add.elf # 加载带有调试信息的ELF文件# 在GDB交互式界面中执行以下命令
(gdb) set architecture riscv:rv64 # 指定目标架构
(gdb) target remote :1234 # 连接到QEMU的GDB服务器
二、基本调试流程
1. 设置断点
(gdb) break _start # 在_start标签处设置断点
(gdb) break add.s:20 # 在add.s文件第20行设置断点
(gdb) break print_string # 在print_string函数入口设置断点
(gdb) info breakpoints # 查看已设置的断点
2. 执行程序
(gdb) continue # 继续执行到下一个断点
(gdb) next # 单步执行(不进入函数)
(gdb) step # 单步执行(进入函数)
(gdb) finish # 执行完当前函数并返回
(gdb) until 25 # 执行到当前文件第25行
3. 查看源代码/汇编
(gdb) list # 显示当前位置附近的源代码
(gdb) list 10,20 # 显示第10-20行代码
(gdb) disassemble # 反汇编当前函数
(gdb) x/i $pc # 显示当前执行的指令
(gdb) layout asm # 切换到汇编视图
(gdb) layout regs # 显示寄存器窗口
三、查看寄存器
1. 查看通用寄存器
(gdb) info registers # 显示所有寄存器
(gdb) print $a0 # 查看a0寄存器的值
(gdb) p/x $sp # 以十六进制形式查看sp寄存器
(gdb) p/a $ra # 以地址形式查看ra寄存器
(gdb) display $t0 # 每次停止时自动显示t0寄存器
2. 查看特殊寄存器
(gdb) p $mstatus # 查看机器状态寄存器
(gdb) p $mcause # 查看异常原因寄存器
(gdb) p $mtvec # 查看异常向量表基址
(gdb) set $pc = 0x80000010 # 修改程序计数器
四、查看内存
1. 内存查看命令
(gdb) x/10xw 0x80000000 # 从0x80000000开始,以16进制显示10个32位字
(gdb) x/20b $sp # 从sp开始,以字节形式显示20个值
(gdb) x/10i 0x80000000 # 从0x80000000开始,显示10条指令
(gdb) examine/i $pc # 查看当前执行的指令
2. 内存修改命令
(gdb) set {int}0x80000010 = 42 # 将0x80000010处的32位整数设置为42
(gdb) set *0x80000014 = 0x1234 # 将0x80000014处的16位值设置为0x1234
五、调试实战示例
假设我们要调试add.s
程序中的加法操作:
- 设置断点并开始执行
(gdb) break _start
(gdb) continue
- 单步执行到加法操作
(gdb) step # 执行初始化栈指针
(gdb) step # 加载a0 = 10
(gdb) step # 加载a1 = 20
(gdb) step # 执行add a2, a0, a1
- 验证加法结果
(gdb) p/x $a0 # 应该显示0xa (10)
(gdb) p/x $a1 # 应该显示0x14 (20)
(gdb) p/x $a2 # 应该显示0x1e (30)
- 查看内存中的变量
(gdb) x/i $pc # 显示当前指令
(gdb) x/10xw 0x80000000 # 查看.text段内容
(gdb) x/10xw $sp # 查看栈内容
- 调试UART输出
(gdb) break print_string
(gdb) continue
(gdb) step # 进入print_string函数
(gdb) p $a0 # 查看要打印的字符串地址
(gdb) x/s $a0 # 以字符串形式显示内存内容
六、高级调试技巧
1. 条件断点
(gdb) break add.s:25 if $a0 > 10 # 当a0>10时触发断点
2. 自动显示
(gdb) display/i $pc # 每次停止时显示当前指令
(gdb) display/x $a0 # 每次停止时显示a0寄存器
3. 内存断点(观察点)
(gdb) watch *0x80000020 # 当地址0x80000020被修改时触发
4. 回溯调用栈
(gdb) backtrace # 显示调用栈
(gdb) frame 1 # 切换到第1层栈帧
七、退出调试
(gdb) disconnect # 断开与QEMU的连接
(gdb) quit # 退出GDB
八、退出QEMU
退出qemu-system-riscv64
通常可以使用快捷键或通过监视器界面来操作,具体方法如下:
- 使用快捷键:按下
Ctrl + a
,然后松开这两个键,再按下x
,即可直接终止QEMU进程,回到shell界面。 - 通过监视器界面:首先按下
Ctrl + a
,然后松开,再按下c
,这将退出当前操作系统的shell界面,进入QEMU的监视器界面。接着在监视器界面中,输入q
并按回车键,即可完全退出QEMU。
总结
通过上述命令,你可以全面掌控程序的执行流程,监控寄存器和内存的变化,从而快速定位问题。GDB的强大功能不仅适用于调试简单程序,也是开发复杂嵌入式系统的必备工具。建议结合具体程序多练习,熟练掌握这些命令将显著提高开发效率。