GNU Debugger

GNU Debugger,简称 GDB,是 GNU 软件系统中的除错器,由於其具有可移植的优点,在现今的主流处理器架构与作业系统平台上都可以看见 GDB 的身影。

使用 GDB 进行除错

如果要使用 GDB 对 C 程序进行除错,需要在编译时添加 -g 参数:

$ gcc main.c -g -o main

等到 gcc 编译完成後,我们便可以使用 gdb 打开可执行档案进行除错:

gdb ./main

使用 -g 产生除错讯息会大大的增加应用程序的档案大小,一般在发布应用程序时是不会以 -g 参数编译的。
除错完成後,我们可以使用 strip 指令清掉应用程序中的除错资讯:

strip main

常见指令

gdb 的常见指令如下:

  • help h: 显示指令简短说明,如: help breakpoint

  • file: 开启档案,等同於 gdb filename

  • run r: 继续或是重新执行程序。

  • kill: 中止执行中的程序。

  • backtrace bt: 追踪 Stack,会显示出上层所有 frame 的简略资讯。

  • print p: 印出变数内容。

  • list l: 印出程序码。

  • whatis:印出变数的型态。例: whatis i,印出变数 i 的型态。

  • breakpoint b, bre, break: 设定中断点

    • 使用 info breakpoint 或是 info b 查看已设定了哪些中断点。
    • 在程序被中断後,使用 info line 来查看正停在哪一行。
  • continue c, cont: 由目前中断的地方开始继续执行。

  • frame: 显示正在执行的行数、副程序名称、及其所传送的参数等等 frame 资讯。
     frame 2: 看到 #2,也就是上上一层的 frame 的资讯。

  • next n: 单步执行,但遇到 frame 时不会进入 frame 中单步执行。

  • step s: 单步执行。但遇到 frame 时则会进入 frame 中单步执行。

  • until: 直接跑完一个 while 回圈。

  • return: 中止执行该 frame(视同该 frame 已执行完毕),
     并返回上个 frame 的呼叫点。功用类似 C 里的 return 指令。

  • finish: 执行完这个 frame。当进入一个过深的 frame 时,如:C 函式库,
     可能必须下达多个 finish 才能回到原来的进入点。

  • up: 直接回到上一层的 frame,并显示其 stack 资讯,如进入点及传入的参数等。

  • up 2: 直接回到上三层的 frame,并显示其 stack 资讯。

  • down: 直接跳到下一层的 frame,并显示其 stack 资讯。

    必须使用 up 回到上层的 frame 後,才能用 down 回到该层来。

  • display: 在遇到中断点时,自动显示特定变数的内容。

  • undisplay: 取消 display

  • commands: 在遇到中断点时要自动执行的指令。

  • info: 显示一些特定的资讯,如:

    • info break 显示中断点。
    • info share 显示共享函式库资讯。
  • disable: 暂时关闭某个 breakpoint 或 display。

  • enable: 将被暂时关闭的功能启用。

  • clear/delete: 删除某个 breakpoint。

  • set: 设定特定参数,如: set env 设定/修改环境变数。

  • unset: 取消特定参数,如: unset env 删除环境变数。

  • show: 显示特定参数。如: show environment 显示环境变数。

  • attach PID: 载入已执行中的程序以进行除错。其中的 PID 可由 ps 指令取得。

  • detach PID: 释放 attached program。

  • shell: 执行 Shell 指令,例如: shell ls 会呼叫 shell 并执行 ls 指令。

  • quit: 离开 gdb。

  • <Enter>: 直接执行上个指令。

在程序中产生中断点

除了可以使用 b 设定中断点外,透过下面程序码中的方法,我们同样可以在程序码中设置中断点:

int main() {
    int val = 1;
    val = 42;
    asm("int $3"); // set a breakpoint here
    val = 7;
}

不只如此,我们还可以利用前置处理器让除错变得更容易:

int main() {
    int val = 1;
    val = 42;
#ifdef DEBUG
    asm("int $3"); // set a breakpoint here
#endif
    val = 7;
}
$ gcc main.c -g -DDEBUG -o main 
gdb ./main
(gdb) r
[...]
Program received signal SIGTRAP, Trace/breakpoint trap.
main () at main.c:6
6	    val = 7;
(gdb) p val
$1 = 42

上面的范例告诉我们,当 Process 执行到第六行时收到了讯号 SIGTRAP,接着我们可以使用 p 印出变数当前储存的内容。

TUI Mode

如果觉得 GDB 的命列列模式不够友善,我们也可以使用 GDB 提供的 TUI Mode。该模式会显示除错中的程序码,并且将当前执行的程序反白:

要使用 TUI Mode,有几种方法:

  1. 使用命令:
gdb -tui

或是:

gdbtui
  1. 进入 GDB 後使用组合键 ctrl + x + a,如果要退出 TUI Mode 则再次使用组合键即可。

在嵌入式系统开发中使用 GDB

因为在修改 Timer_handler 时出现了一些异常,所以笔者尝试为 mini-riscv-os 新增 debug 脚本,效果如下:

make debug
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -T os.ld -o os.elf start.s sys.s lib.c timer.c task.c os.c user.c trap.c lock.c
Press Ctrl-C and then input 'quit' to exit GDB and QEMU
-------------------------------------------------------
Reading symbols from os.elf...
Breakpoint 1 at 0x80000000: file start.s, line 7.
0x00001000 in ?? ()
=> 0x00001000:  97 02 00 00     auipc   t0,0x0

Thread 1 hit Breakpoint 1, _start () at start.s:7
7           csrr t0, mhartid                # read current hart id
=> 0x80000000 <_start+0>:       f3 22 40 f1     csrr    t0,mhartid
(gdb)

修改 Makefile 与添加 gdbinit

因为 mini-riscv-os 使用了 Make 建构工具,为了保持一致性,我们需要在 Makefile 动一点手脚。
至於 gdbinit 则是设定 gdb 的一些参数,内容如下:

set disassemble-next-line on
b _start
target remote : 1234
c

接着,在 Makefile 加入以下内容:

.PHONY : debug
debug: all
	@echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU"
	@echo "-------------------------------------------------------"
	@${QEMU} ${QFLAGS} -kernel os.elf -s -S &
	@${GDB} os.elf -q -x ./gdbinit

这样一来,就能够在开发作业系统时使用 GDB 进行除错了!

设置中断点

实际上,我们可以将中断点设置在指定档案的指定行数上:

(gdb) b trap.c:27
Breakpoint 2 at 0x80008f78: file trap.c, line 27.
(gdb)

根据上面的范例,当虚拟机执行到 trap.c 的第 27 行时,整个工作都会被暂停下来直到我们按下 c (continue) 或是 s (step)。
这麽做可以让作业系统每一次发生中断时都暂停执行,这时就可以利用 gdb 检查 stack、特定变数或是暂存器的状态是否符合我们的预期。

Reference


<<:  [机派X] Day 12 - 那些年还没介绍的无人机部件

>>:  DAY 12 Big Data 5Vs – Variety(速度) Lambda

Flutter基础介绍与实作-Day30 最後总结

今天是最後一天了好开心喔!!!原本以为我自己没办法做完这30天,没想到我竟然在不知不觉中写完了,突然...

Day 9 | Unity AR手游「山海异闻录」开发套件一览表

今天的文章,要来为各位整理我们在正式开发前,查询的AR套件相关资料。 目录 AR开发套件 其他套件 ...

[Day28] 实战 - 波段创新高

影片在这里 分类:选股 波段 重点整理 目的: 大盘或景气表现不好时,价格还能创新高。表示背後有特别...

第六天:首次启动设定

若是您选择以软件包或 Docker 这种 On Premises 的安装方式安装在本机电脑的话,那首...

Youtube Data API 教学 - 抓取你的金钥 API key

「鲑鱼均,因为一场鲑鱼之乱被主管称为鲑鱼世代,广义来说以年龄和脸蛋分类的话这应该算是一种 KNN 的...