段错误?死锁?Core Dump不会用?3个GDB技巧让你调试效率翻倍

段错误?死锁?Core Dump不会用?3个GDB技巧让你调试效率翻倍

嵌入式开发里最浪费时间的事情是什么?不是写代码,是找bug。尤其是那些"我代码逻辑没问题啊,怎么就崩了"的bug。

今天聊3个GDB实战技巧,每一个都是我在调试Linux驱动和嵌入式应用时反复用到的。不扯理论,直接上命令。


技巧一:搞定段错误——从Core Dump回溯现场

段错误是嵌入式开发者的老朋友了。指针飞了、数组越界、栈溢出,结果都是SIGSEGV。

第一步:打开Core Dump

默认系统不开core dump,先打开:

ulimit-cunlimitedecho"/tmp/core.%p.%e">/proc/sys/kernel/core_pattern

这样程序崩溃后,/tmp/core.1234.myapp就有了。

第二步:回溯现场

gdb ./myapp /tmp/core.1234.myapp(gdb)bt(gdb)bt full(gdb)info registers

bt看调用栈,bt full看每层函数的局部变量值。80%的段错误,一看到崩溃在哪个函数、传了什么参数,马上就能定位。

第三步:查看内存

有时候崩溃在memcpy里,bt看到的是libc的栈帧,没用。这时候看参数:

(gdb) frame 1 # 切到上一帧(调用memcpy的代码) (gdb) info args (gdb) p src (gdb) p dst (gdb) p/x *src@20 # 打印src指向的前20个字节

真实案例:一次调试WiFi驱动,skb->data指向了已释放的内存。运行几小时后随机崩溃。bt一看在memcpy,打印src发现地址在kmalloc-128 slab之外,一查是上层模块提前kfree_skb了。半小时定位,之前纯靠加printk搞了三天没找到。


技巧二:条件断点+自动化——调试时序问题

嵌入式里最恶心的一类bug:跑100次偶尔崩1次。条件断点就是干这个的。

条件断点

(gdb) break gpio_irq_handler if irq_num == 42 (gdb) break kmalloc if size > 4096 (gdb) break my_func if strcmp(name, "sensor0") == 0

自动打印+继续

调试中断处理或者驱动probe时,不想每次都手动敲命令:

(gdb) break device_probe (gdb) commands > printf "Probing %s\n", pdev->name > print devm_kzalloc(pdev, 0x1000) > continue > end

观察点(Watchpoint)

调试"哪个不要脸的改了我的变量":

(gdb) watch my_global_var # 写触发 (gdb) rwatch my_global_var # 读触发 (gdb) awatch my_global_var # 读写都触发

真实案例:调试一个I2C触摸屏驱动,g_touch_state总是不对。怀疑被别的模块改了但找不到。watch g_touch_state一上,断下来发现是DMA完成回调里直接写了共享内存——但锁机制没覆盖这个路径。5分钟找到,之前printk打了一百行没看出来。


技巧三:远程调试(gdbserver)——嵌入式板子的标配

嵌入式开发,程序跑在ARM/MIPS板子上,调试器在主机的x86上。用gdbserver:

板子端

# 编译带调试符号的目标程序arm-linux-gnueabihf-gcc-g-O0-omyapp myapp.c# 板子上启动gdbservergdbserver :2345 ./myapp

主机端

arm-linux-gnueabihf-gdb ./myapp(gdb)target remote192.168.1.100:2345(gdb)continue

剩下的和在本地一样,break、next、print全部操作远程板子的内存和寄存器。

小技巧:加载内核模块调试符号

调试驱动时:

# 板子上insmod mydriver.kocat/sys/module/mydriver/sections/.textcat/sys/module/mydriver/sections/.datacat/sys/module/mydriver/sections/.bss# GDB里(gdb)add-symbol-file mydriver.o 0x<.text地址>-s.data 0x<.data地址>-s.bss 0x<.bss地址>

然后就可以在驱动函数里打断点、单步调试了。比printk刷日志爽一百倍。

target remote

add-symbol-file

断点/单步

BKPT指令

主机 x86

gdbserver on ARM板

被调试进程

GDB 符号解析

内核模块符号

硬件调试寄存器


课后练习

  1. 写一个故意段错误的程序(比如解引用空指针),编译后用gdb的bt定位到具体行
  2. 用watchpoint找出"谁改了全局变量"
  3. 配置一个gdbserver+QEMU的远程调试环境,验证远程断点可用

一句话总结

三个技巧吃透了,大部分嵌入式调试场景不用再加printk重编。尤其Core Dump和远程调试,省的是实打实的编译部署时间,一次省半小时,一个月下来够你摸好几天的鱼。