4. GDB数据查看

调试C/C++程序,说白了就是跟数据和代码打交道。你想想看,程序跑飞了、变量值不对、内存被踩了——这些场景,哪个不需要我们深入查看数据?今天我就把这四个最常用的数据查看命令掰开揉碎了讲清楚。

4.1 print命令:最基础的变量查看

print(简写p)是我用得最多的命令,没有之一。它的作用很简单:打印变量或表达式的值。

(gdb) p my_var
$1 = 42
(gdb) p my_var + 10
$2 = 52
(gdb) p &my_var
$3 = (int *) 0x7fffffffe4a0

这里有个细节:GDB会把每次print的结果存到历史变量里,$1、$2这些就是。你可以直接用$1来引用之前的结果。

小技巧: 我个人习惯用p/x查看十六进制,p/d看十进制,p/c看字符。比如检查指针是否为空,用p/x ptr一眼就能看出0x0。

4.2 display命令:自动刷新观察

display跟print很像,但它会自动执行。每次程序停下来,display的变量都会重新打印。这在单步调试时特别有用。

(gdb) display my_var
1: my_var = 42
(gdb) display /x my_ptr
2: /x my_ptr = 0x7fffffffe4a0
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:   y  my_var
2:   y  /x my_ptr

为什么要用display?我举个例子。有一次我在调试一个网络协议栈的粘包问题,需要同时盯着buffer_len、offset和packet_type三个变量。如果用print,每步都要敲三遍命令,手指头都酸了。用display,一步到位。

注意: display多了会影响调试速度。我曾经在一个嵌入式板子上开了十几个display,结果单步执行慢得像蜗牛。用undisplay 编号可以删除不需要的。

4.3 watch命令:数据变化的哨兵

watch命令设置观察点,当被监视的变量发生变化时,程序会自动暂停。这比人眼盯着屏幕靠谱多了。

(gdb) watch my_var
Hardware watchpoint 1: my_var
(gdb) watch *(int*)0x7fffffffe4a0
Hardware watchpoint 2: *(int*)0x7fffffffe4a0
(gdb) info watchpoints
Num     Type           Disp Enb Address    What
1       hw watchpoint  keep y              my_var
2       hw watchpoint  keep y              *0x7fffffffe4a0

这里要区分硬件观察点和软件观察点。硬件观察点不消耗CPU,但数量有限(通常4个左右)。软件观察点数量不限,但会让程序跑得极慢。

避坑指南: 我曾经在调试一个多线程程序时,用watch监视一个全局变量。结果发现watch只在当前线程生效,其他线程修改了变量根本不会触发。后来改用awatch(读写都监视)才解决问题。

4.4 x命令:直接查看内存

x(examine的缩写)命令直接查看内存内容。格式是x/[数量][格式][大小] 地址。

(gdb) x/10xb 0x7fffffffe4a0
0x7fffffffe4a0: 0x2a 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe4a8: 0x00 0x00
(gdb) x/5wd 0x7fffffffe4a0
0x7fffffffe4a0: 42      0       0       0       0
(gdb) x/3i 0x400520
   0x400520 <main>:      push   %rbp
   0x400521 <main+1>:    mov    %rsp,%rbp
   0x400524 <main+4>:    sub    $0x10,%rsp

格式选项:x十六进制、d十进制、c字符、s字符串、i指令。大小选项:b字节、h半字、w字、g双字。

实用技巧: 检查结构体对齐时,用x/20xb &struct_var看内存布局。我遇到过结构体成员之间被编译器插入了填充字节,导致memcpy拷贝数据错位,就是用x命令发现的。

4.5 四个命令的对比与选择

命令 适用场景 性能影响 典型用法
print 一次性查看变量值 无 p my_var
display 反复查看同一组变量 低 display my_var
watch 监视变量何时被修改 中到高 watch my_var
x 查看原始内存数据 无 x/10xb addr

4.6 实战:粘包问题中的内存查看

说到粘包,我印象最深的一次调试经历是这样的:一个TCP服务器接收数据时,两个数据包粘在了一起。我用watch监视了接收缓冲区的长度字段,发现长度值被错误地覆盖了。

(gdb) watch recv_buf->len
Hardware watchpoint 3: recv_buf->len
(gdb) c
Continuing.
Hardware watchpoint 3: recv_buf->len
Old value = 128
New value = 256
main () at server.c:85
85          memcpy(recv_buf->data, packet, packet_len);

看到没?recv_buf->len从128变成了256,但实际只收到了128字节的数据。这说明memcpy之前,len字段已经被错误地修改了。再配合x/20xb recv_buf查看内存布局,发现是结构体成员顺序导致的对齐问题。

核心要点: print看变量值,display自动刷新,watch监视变化,x看内存原始数据。这四个命令组合使用,能覆盖90%以上的数据调试场景。

4.7 知识体系图

GDB数据查看命令知识体系 数据查看命令 print (p) display watch x (examine) 一次性查看 支持格式控制 历史变量$1..$n 自动刷新 单步调试利器 info display管理 硬件/软件观察点 值变化时暂停 awatch/rwatch 原始内存查看 格式/大小控制 反汇编指令 组合使用建议 print + display 日常调试 | watch 定位数据异常 | x 分析内存布局 实战场景:粘包问题 → watch监视长度字段 → x查看内存布局 → print验证修复

嗯,这四个命令其实各有侧重。print适合快速查看,display适合持续观察,watch适合被动等待变化,x适合深入内存细节。我个人建议,调试时先用print摸清基本情况,再用display锁定关键变量,遇到诡异问题就上watch,最后用x确认内存状态。

记住一点:工具是死的,人是活的。别死记命令格式,多想想你真正需要看什么数据,然后选最合适的命令。

最后提醒: 在嵌入式平台上,硬件观察点数量有限。我曾经在调试一个ARM Cortex-M3的程序时,同时用了4个watch,结果第5个自动降级为软件观察点,程序直接跑飞了。所以,watch够用就好,别贪多。

可可精品资料库