包含: .gdbinit文件、vscode调试配置、openocd多核调试配置等
仓库里缺失的.gdbinit文件内容是:
target extended-remote localhost:3333
symbol-file obj/riscv-pke
# m_start中会timerinit()
break s_start
continue
set $mie = $mie & ~0x80官方docker镜像apt update && apt install lsof libncurses5 libjim-dev libjim0.79 libpython2.7装完依赖后就可以使用这几个命令连接至openocd调试. 但是给的gdb版本有点老, 建议更新一下
gdb调试pke相当于调试一个嵌入式设备, 这方面资料很多, 总体流程是这样的:
- 安装工具链:
spike,openocd用pke-doc那个就行,riscv64-unknown-elf-gdb建议用官方docker镜像的那个, 不然有些实验可能有些小问题(未解决). - 启动spike:
spike --rbb-port=9824 --halted obj/riscv-pke obj/app_errorline(旧版本--halted是-H) - 启动openocd:
openocd -f ./.spike.cfg - 启动gdb:
riscv64-unknown-elf-gdb启动- 之后在gdb命令行中
target extended-remote localhost:3333连接到openocd 使用symbol-file obj/riscv-pke载入调试符号信息 - 正常打断点调试
b m_start c
-
spike会先从
0x1000开始执行几条代码(并非elf入口点), 用来提供dtb相关信息, 这几条指令并不在elf文件中, GDB也无法感知, 因此gdbload命令会从_mentry开始执行, 此时a0 a1未初始化, 调试到init_dtb()就会炸; -
在启用timer后每次gdb step都会先进入中断处理, 可通过set $mie = $mie & ~0x80关闭MIE_MTIE
-
工具链最好不要用发行版的, 虽然写了有riscv支持, 但是有点容易出兼容性问题, 最好去riscv官方仓库那里;
-
pke-doc那个镜像的gdb工具链有点小小兼容性问题, 有些调试信息识别不了;
-
从21年到现在spike的命令行参数(
-H->--halted)和openocd的配置文件格式有些小改, 需要稍微注意.
-
起初按照GDB-and-OpenOCD文档的20.2节进行调试:
riscv64-unknown-elf-gdb连接上openocd后:
并未察觉到0x1000地址的含义, 以为只是由于gdb"尚未感知"
obj/riscv-pke导致的问题, 因此参考文档执行load obj/riscv-pke, 随后$pc变成了elf入口点(_mentry), 似乎一切正常.
然后发现执行到
init_dtb()内的fdt_scan()时$pc突然变成0x0:
-
想不明白, 开始排查:
先把GDB去了, 用openocd调试看看,
$pc依然是0x1000:
打完断点, 单步之后依然复现:
-
终于注意到了0x1000, 查看spike的指令log:
查看spike源代码发现其加电后会在0x1000进行一些dtb之类的初始化, 然后跳转至入口点_mentry:
即
void m_start(uintptr_t hartid, uintptr_t dtb)的两个参数a0 a1
-
原因分析:
通过
set debug remote 1查看传输日志得知, gdb中load操作会将本地的obj/riscv-pke文件通过openocd传输至spike内存, 其实是重复操作, 而GDBload之后会根据elf文件将$pc设置为其中的入口点, 从而跳过了0x1000那段代码的执行,a0 a1未能初始化, 导致init_dtb()炸掉.(gdb) help load Dynamically load FILE into the running program. FILE symbols are recorded for access from GDB. Usage: load [FILE] [OFFSET] -
解决办法:
通过上面的过程可知, openocd具备完备的调试功能(断点/单步/内存查等), gdb不过是作为一个友好的接口(连接源代码/反汇编), 因此gdb只需要处理调试符号相关内容:
target extended-remote localhost:3333 symbol-file obj/riscv-pke b m_start c
-
开启timer后时钟中断影响调试:
开启时钟中断前next能够正常执行, 开启后会进入中断处理, 且无法继续:
set $mie = $mie & ~0x80关闭MIE_MTIE中断使能. -
多核调试:
开启多核后GDB和OpenOCD识别不到两个hart, 因为只创建了riscv.cpu:
参考smp-threads-not-showing-in-gdb添加调试target后:
set _TARGETNAME $_CHIPNAME.cpu target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME -coreid 0 -rtos hwthread target create $_TARGETNAME.1 riscv -chain-position $_TARGETNAME -coreid 1 -rtos hwthread
OpenOCD针对每个hwthread开了一个GDB端口(因为hwthread不一定是对称的).
参考文档Define CPU targets working in SMP, 应该手动开启SMP:
target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME -coreid 0 -rtos hwthread target create $_TARGETNAME.1 riscv -chain-position $_TARGETNAME -coreid 1 -rtos hwthread target smp riscv.cpu.0 riscv.cpu.1
可以在GDB中进行多线程调试:
- 浙江大学24年秋冬操作系统实验(关于 spike 工具链的使用)
- gdb target和load
- GDB-and-OpenOCD
- VSCode调试时preLaunchTask为持续后台任务
- smp-threads-not-showing-in-gdb
- Config-File-Guidelines # 6.3.4 Define CPU targets working in SMP
-
系统: Ubuntu 22.04.1 LTS -
GDB: riscv64-unknown-elf-gdb (官方release) -
Spike: commit 206268c -
OpenOCD: commit fac1412 -
比较新的GCC(2025年)编译发现有些实验会遇到, 其实是代码有BUG.Misaligned Load!, 2021年的可以正常通过, 未仔细分析 -
Spike和OpenOCD镜像中的2021版本和最新的版本都可以, 只是要小改一下参数格式.
配置文件: 详细配置文件
效果:
-
.gdbinit:
# riscv64-unknown-elf-gdb -ex "target ext :3333" monitor reset halt symbol-file obj/riscv-pke # m_start中会timerinit() b s_start c set $mie = $mie & ~0x80
-
.vscode/launch.json:
{ "version": "0.2.0", "configurations": [ { "name": "(gdb) 调试pke", "type": "cppdbg", "request": "launch", "args": [], "stopAtEntry": true, // "stopAtConnect": true, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "program": "${workspaceFolder}/obj/riscv-pke", "miDebuggerPath": "riscv64-unknown-elf-gdb", // "miDebuggerServerAddress": "localhost:3333", // "miDebuggerArgs": "obj/riscv-pke", // "useExtendedRemote": true, "setupCommands": [ { "description": "为 gdb 启用整齐打印", "text": "-enable-pretty-printing", "ignoreFailures": false }, { "text": "cd ${workspaceFolder}", "ignoreFailures": false }, { "description": "target ext :3333, 连接至openocd", "text": "target extended-remote localhost:3333", "ignoreFailures": false }, { "text": "source .gdbinit", "ignoreFailures": true }, ], "preLaunchTask": "start_kernel.sh" } ] }
-
.vscode/tasks.json:
{ "version": "2.0.0", "options": { "cwd": "${workspaceFolder}" }, "tasks": [ { "type": "shell", "label": "start_kernel.sh", "command": "cd ${workspaceFolder} && ./start_kernel.sh", "isBackground": true, "args": [], "group": { "kind": "build", "isDefault": false }, "problemMatcher": [ { "pattern": { "regexp": "^(START_KERNEL)", //问题模式或者所提供或预定义问题模式的名称。如果已指定基准,则可以省略。 "line": 1 // "file": 1, // "location": 2, // "message": 3 }, "background": { //用于跟踪在后台任务上激活的匹配程序的开始和结束的模式。 "activeOnStart": true, //如KERNEL_STARTED果设置为 true,则任务启动时后台监视器处于活动模式。这相当于发出与 beginsPattern 匹配的行 "beginsPattern": "^(START_KERNEL)", //如果在输出内匹配,则会发出后台任务开始的信号 "endsPattern": "^(START_KERNEL)" //如果在输出内匹配,则会发出后台任务结束的信号 } } ], "detail": "start_kernel.sh" } ] }
-
start_kernel.sh
#!/bin/bash echo "START_KERNEL" echo "start at $(pwd)" # make gdb-clean kill -9 $(lsof -i:9824 -t) kill -9 $(lsof -i:3333 -t) # spike -l --log=spike.log obj/riscv-pke obj/app* # 效果是spike先后台运行, 但是依然显示spike的输出 spike --rbb-port=9824 --halted obj/riscv-pke obj/app* 2>&1 | tee spike.log & # spike --rbb-port=9824 --halted -p2 obj/riscv-pke obj/app* 2>&1 | tee spike.log & sleep 0.1s # openocd -f ./.spike.cfg -c "reset halt" 2>&1 | tee openocd.log openocd -f ./.spike.cfg > openocd.log 2>&1 # telnet localhost 4444 # reset # halt # bp 0x0000000080001d32 4 # rbp 0x0000000080001d32 # step # resume # riscv64-unknown-elf-gdb obj/riscv-pke -ex "target ext :3333" -ex "monitor reset halt" -ex "b m_start" -ex "c" -ex "layout src" -ex "focus cmd" echo STARTED
