4. GDB数据查看

调试的核心是什么?说白了,就是看数据。

你设了断点,程序停下来了。然后呢?你得知道变量值对不对,内存里到底写了什么,结构体有没有被踩坏。GDB里最常用的就是print命令,但很多人只用了个皮毛。今天我把这块掰开揉碎了讲清楚。

4.1 print命令的基本用法

print,缩写是p。用法很简单:p 变量名。但这里有几个细节,我当年踩过坑。

(gdb) p i
$1 = 42

看到那个$1了吗?这是GDB自动给结果编的号。每次print,结果都会被存到GDB的“历史记录”里。你可以用$1、$2来引用之前的结果。

小技巧: 如果你只想看结果,不想存历史,用 print /f 或者直接用 call 命令。

print还能直接算表达式:

(gdb) p i + j
$2 = 100
(gdb) p &i
$3 = (int *) 0x7fffffffe4a0

嗯,这里要注意:取地址操作符 & 在GDB里完全可用。我经常用它来检查指针是否指向了非法区域。

4.2 显示格式控制

默认情况下,print按变量的类型来显示。但有时候你需要看别的格式。比如一个整数,你想看它的十六进制、二进制,或者字符表示。

格式控制符用斜杠加字母:p /格式 变量

格式符 含义 示例
/x 十六进制 p /x i
/d 有符号十进制 p /d i
/u 无符号十进制 p /u i
/o 八进制 p /o i
/t 二进制 p /t i
/c 字符 p /c i
/f 浮点数 p /f f
/s 字符串(对指针) p /s ptr

举个例子,你有个寄存器值,想看看它的二进制位:

(gdb) p /t 0xABCD
$4 = 1010101111001101

我在调试嵌入式驱动时,经常用/t来看状态寄存器的各个位。一眼就能看出哪个标志位被置了。

核心要点: 格式控制符不改变变量本身,只改变显示方式。你print一个int,它还是int,只是GDB帮你转换了显示格式。

4.3 查看内存:x命令

print看的是变量,x命令看的是内存。x是examine的缩写。

语法:x /[数量][格式][大小] 地址

比如:

(gdb) x /10xb 0x7fffffffe4a0
0x7fffffffe4a0: 0x2a 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe4a8: 0x00 0x00

这里:

  • 10:显示10个单位
  • x:十六进制格式
  • b:每个单位1字节(byte)

大小选项:

  • b:1字节
  • h:2字节(halfword)
  • w:4字节(word)
  • g:8字节(giant)

格式选项和print一样:x、d、u、o、t、c、f、s、i(指令)

注意: 用x命令时,地址一定要合法。我曾经在调试一个野指针问题时,直接用x看了非法地址,结果GDB报错“Cannot access memory at address 0x...”。这其实是个好信号——说明指针确实有问题。

看字符串数组特别方便:

(gdb) x /s 0x400600
0x400600: "Hello, GDB!"

看指令(反汇编):

(gdb) x /5i $pc
=> 0x400526 <main+4>: mov    $0x0,%eax
   0x40052b <main+9>: mov    $0x400600,%edi
   0x400530 <main+14>: callq  0x400400 <puts@plt>

这个/i格式在调试汇编代码时简直是神器。我经常用它来确认当前执行到了哪条指令。

4.4 自动显示:display命令

每次单步执行后,你想自动看到某些变量的值?用display。

(gdb) display i
1: i = 42
(gdb) display /x j
2: j = 0x64

每次执行next、step、continue后,GDB会自动打印这些表达式。

常用display命令:

  • display 表达式:添加自动显示
  • info display:列出所有自动显示项
  • delete display 编号:删除指定项
  • disable display 编号:临时禁用
  • enable display 编号:重新启用
  • undisplay 编号:同delete display

举个例子,你调试一个循环:

(gdb) display i
(gdb) display sum
(gdb) break 20
(gdb) continue
...
1: i = 0
2: sum = 0
(gdb) next
1: i = 1
2: sum = 1
(gdb) next
1: i = 2
2: sum = 3

每次执行next,GDB自动打印i和sum的值。省得你每次手动print。

我的习惯: 在调试复杂数据结构时,我会用display来跟踪关键字段。比如链表的头指针、循环计数器、状态机的当前状态。这样单步执行时,数据变化一目了然。

4.5 实战:调试一个缓冲区溢出

我记得有一次,一个同事说他的程序在某个函数返回时崩溃。我帮他调试,用到了今天讲的这些命令。

场景:一个简单的字符串拷贝,但目标缓冲区太小。

#include <stdio.h>
#include <string.h>

void vulnerable() {
    char buffer[8];
    char *input = "Hello, GDB! This is a test.";
    strcpy(buffer, input);
    printf("buffer: %s\n", buffer);
}

int main() {
    vulnerable();
    return 0;
}

编译时加-g,然后调试:

(gdb) break vulnerable
(gdb) run
(gdb) p buffer
$1 = "Hello, G"
(gdb) x /20xb buffer
0x7fffffffe4a0: 0x48 0x65 0x6c 0x6c 0x6f 0x2c 0x20 0x47
0x7fffffffe4a8: 0x44 0x42 0x21 0x20 0x54 0x68 0x69 0x73
0x7fffffffe4b0: 0x20 0x69 0x73 0x20

看到了吗?buffer只有8字节,但数据已经写到了0x7fffffffe4a8之后。这就是溢出。用x命令看内存,能直观地看到数据覆盖了相邻的栈空间。

总结: print看变量,x看内存,display自动跟踪。这三板斧用好了,大部分数据问题都能快速定位。
GDB数据查看知识体系 GDB数据查看 print 命令 p 变量名 p 表达式 p &变量 格式控制 /f /x 十六进制 /t 二进制 /s 字符串 x 命令(内存) x /数量格式大小 地址 /i 反汇编 /s 字符串 display 自动显示 display 表达式 → 每次单步自动打印

好了,这一章的内容就这些。print、格式控制、x命令、display,每个都是调试时的利器。你想想看,如果只会用print,遇到内存问题是不是就抓瞎了?所以这些命令都得练熟。

可可精品资料库