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,一步到位。
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 四个命令的对比与选择
| 命令 | 适用场景 | 性能影响 | 典型用法 |
|---|---|---|---|
| 一次性查看变量值 | 无 | 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 知识体系图
嗯,这四个命令其实各有侧重。print适合快速查看,display适合持续观察,watch适合被动等待变化,x适合深入内存细节。我个人建议,调试时先用print摸清基本情况,再用display锁定关键变量,遇到诡异问题就上watch,最后用x确认内存状态。
记住一点:工具是死的,人是活的。别死记命令格式,多想想你真正需要看什么数据,然后选最合适的命令。
可可精品资料库