C 语言使用 gdb 调试
示例程序
#include <stdio.h>
int main(void) {
char s[20] = {0};
s[0] = 's';
s[2] = 's';
printf("%s\n", s);
return 0;
}
编译
$ gcc fun_printf.c -o fun_printf
运行
$ ./fun_printf
s
查看示例程序,期望输出 s s,但实际只输出了一个s
查看 printf 函数的说明
$ man 3 printf
man 命令
| 章节 | 内容描述 | 示例 |
|---|---|---|
| 1 | shell | man ls, man printf |
| 2 | 系统调用 | man 2 unlink, man 2 open |
| 3 | C库函数 | man 3 printf |
| 4 | 特殊设备文件 | man 4 null |
| 5 | 文件格式与配置文件(如 /etc/passwd) | man 5 passwd |
| 6 | 游戏与屏保 | man 6 intro |
| 7 | 杂项 | man 7 utf8 |
| 8 | 系统管理命令 | man 8 fdisk |
使用gdb 调试程序
重新编译程序 支持调试
$ gcc -g fun_printf.c -o fun_printf
启动 gdb 调试
$ gdb ./fun_printf
GNU gdb (Ubuntu 15.1-1ubuntu1~24.04.1) 15.1
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./fun_printf...
(gdb)
查看源码,设置断点
(gdb) l
1 #include <stdio.h>
2
3 int main(void) {
4 char s[20] = {0};
5 s[0] = 's';
6 s[2] = 's';
7 printf("%s\n", s);
8 return 0;
9 }
(gdb) b 7
Breakpoint 1 at 0x11a3: file fun_printf.c, line 7.
开始运行程序
(gdb) run
Starting program: /home/scd/book/linux-c/ch02/fun_printf
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, main () at fun_printf.c:7
7 printf("%s\n", s);
进入函数
(gdb) s
0x00007ffff7c87bf8 in __GI__IO_puts (str=0x7fffffffd8f0 "s") at ./libio/ioputs.c:35
35 size_t len = strlen (str);
发现 printf 函数变成了 puts
查看 puts 函数的源码
(gdb) l
30
31 int
32 _IO_puts (const char *str)
33 {
34 int result = EOF;
35 size_t len = strlen (str);
36 _IO_acquire_lock (stdout);
37
38 if ((_IO_vtable_offset (stdout) != 0
39 || _IO_fwide (stdout, -1) == -1)
(gdb)
40 && _IO_sputn (stdout, str, len) == len
41 && _IO_putc_unlocked ('\n', stdout) != EOF)
42 result = MIN (INT_MAX, len + 1);
43
44 _IO_release_lock (stdout);
45 return result;
46 }
47
48 weak_alias (_IO_puts, puts)
49 libc_hidden_def (_IO_puts)
(gdb)
End of the file was already reached, use "list ." to list the current location again
执行获取长度方法 strlen (str)
(gdb) n
36 _IO_acquire_lock (stdout);
(gdb) print len
$1 = 1
获取的长度只有1
跳出当前函数
(gdb) finish
Run till exit from #0 __GI__IO_puts (str=0x7fffffffd8f0 "s") at ./libio/ioputs.c:36
s
main () at fun_printf.c:8
8 return 0;
Value returned is $2 = 2
只打印出了一个s
但是用 gdb 的 print 的打印可以看到 2个 s
(gdb) print s
$3 = "s\000s", '\000' <repeats 16 times>
查看s的内存
(gdb) x/20bc s
0x7fffffffd8f0: 115 's' 0 '\000' 115 's' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000'
0x7fffffffd8f8: 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000'
0x7fffffffd900: 0 '\000' 0 '\000' 0 '\000' 0 '\000'
x/20bc s 是 GDB 中查看内存的命令,各部分含义如下:
- x → examine 的缩写,用于查看内存内容。
- 20 → 表示要显示 20 个 单位(后面会说明单位大小)。
- b → 单位是 字节 (byte),每次显示 1 个字节。
- c → 将每个字节以 字符 (character) 格式显示。
从变量 s 的地址开始,以字符形式显示连续 20 个字节的内容
发现第2个字符是 \0, puts 函数在输出的时候遇到\0 就结束了,所以只输出了一个s。具体的还要看看 strlen (str) 的汇编代码
gdb 相关知识
- 编译准备
在使用 GDB 之前,必须确保编译时加入了 -g 选项,以便在可执行文件中嵌入调试信息(如变量名、行号等)。建议同时关闭优化(-O0 a),以避免代码重排导致调试困难。
gcc -g -O0 -o program program.c
- 启动 GDB
可以通过以下几种方式启动调试会话:
- 加载可执行文件:gdb ./program
- 加载 Core Dump 文件(用于分析崩溃):gdb ./program core
- 附加到运行中的进程:gdb -p
- 启动时附带参数 gdb --args ./test hello
- 核心调试命令
- 设置断点 (Breakpoints)
断点让程序在指定位置暂停执行。
break main 或 b main:在 main 函数入口设置断点。
break 10 或 b 10:在第 10 行设置断点。
break file.c:20:在指定文件的第 20 行设置断点。
break 20 if x > 10:设置条件断点,仅当 x > 10 时暂停。
info breakpoints 或 i b:查看当前所有断点信息。
delete 1:删除编号为 1 的断点。
-
运行与控制执行
run 或 r:启动程序,直到遇到第一个断点或程序结束。
continue 或 c:从当前暂停处继续执行,直到下一个断点。
next 或 n:单步执行下一行代码,不进入函数内部(Step Over)。
step 或 s:单步执行下一行代码,进入函数内部(Step Into)。
finish:执行完当前函数并返回到调用者处暂停。
until 或 u:快速执行直到退出当前循环或到达指定行。 -
查看数据与状态
print variable 或 p variable:打印变量的值。
p/x var:以十六进制显示。
p *ptr:查看指针指向的内容。
p arr@5:查看数组前 5 个元素。
display variable:每次程序暂停时自动打印该变量的值。
undisplay 1:取消编号为 1 的自动显示。
backtrace 或 bt:查看函数调用栈,帮助定位崩溃来源。
frame n 或 f n:切换到第 n 层栈帧,以便查看该层级函数的局部变量。
info locals:查看当前栈帧的所有局部变量。
info args:查看当前函数的参数。
whatis var:查看变量的类型。 -
监控变量变化 (Watchpoints)
watch variable:当变量的值被修改时,程序自动暂停。
rwatch variable:当变量被读取时暂停。
awatch variable:当变量被读取或写入时暂停。 -
其他实用命令
list 或 l:显示源代码。l 10,20 显示第 10 到 20 行。
set variable x = 10:在运行时修改变量 x 的值,用于测试不同逻辑分支。
quit 或 q:退出 GDB。
gdb 相关函数详解
print 函数
print(可简写为 p)是 GDB 中最常用的查看数据命令,用于在程序暂停时输出变量、表达式或寄存器的值。
基本用法
(gdb) print len
$1 = 1
(gdb) p s
$2 = "s\000s", '\000' <repeats 16 times>
每次执行 print 都会产生一个历史变量($1、$2……),后续可以用 $1、$2 再次引用这些结果:
(gdb) p $1 + $2
打印数组
直接打印数组时会显示整个数组;也可以只查看前 N 个元素:
(gdb) p s
(gdb) p s@5
p s@5 表示从 s 开始打印 5 个元素,适合处理指针或数组片段。
格式化输出
在变量名后加 /格式 可以指定输出格式,常见的格式有:
| 格式 | 含义 | 示例 |
|---|---|---|
x | 十六进制 | p/x num |
d | 有符号十进制 | p/d num |
u | 无符号十进制 | p/u num |
o | 八进制 | p/o num |
c | 字符 | p/c ch |
f | 浮点数 | p/f pi |
s | 字符串 | p/s str |
(gdb) p/x 255
$1 = 0xff
(gdb) p/c 65
$2 = 65 'A'
(gdb) p/s s
$3 = "s"
打印指针与内存
print 可以直接解引用指针、查看指针指向的内容:
(gdb) p *ptr
(gdb) p ptr
(gdb) p &var
打印表达式与函数调用
print 不仅能打印变量,还可以计算表达式,甚至在调试环境中调用当前程序可见的函数:
(gdb) p x + y * 2
(gdb) p strlen(s)
不过要注意,调用函数是在被调试进程内执行的,可能产生副作用;对于优化过的代码或没有调试符号的库,结果可能不完整。
打印结构体与对象
结构体、联合体、类对象也可以直接打印,GDB 会递归展开显示成员:
(gdb) p st
$1 = {name = "hello", age = 18, score = 95.5}
查看变量类型
配合 whatis 或 ptype 可以查看变量类型:
(gdb) whatis s
type = char [20]
(gdb) ptype s
type = char [20]
修改变量的值
print 本身只读;如需运行时修改,通常使用 set variable 或 set var:
(gdb) set variable x = 10
(gdb) set var s[1] = 's'
修改后继续执行,即可测试不同的逻辑分支。
小结
print 的核心作用是:在程序暂停时读取并输出数据。结合 /格式、数组切片、指针解引用和表达式计算,可以快速判断当前状态是否符合预期。本文前面的调试正是通过 print len、print s 发现 len 为 1、第 2 个字符是 \0,从而定位到输出异常的根因。
ubuntu 下载 C 库函数源码包
- /etc/apt/sources.list.d/ubuntu.sources 增加源码包
# 二进制包源(原有)
Types: deb
URIs: https://mirrors.ustc.edu.cn/ubuntu/
Suites: noble noble-updates noble-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
# 源码包源(新增)
Types: deb-src
URIs: https://mirrors.ustc.edu.cn/ubuntu/
Suites: noble noble-updates noble-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
# 二进制安全更新源(原有)
Types: deb
URIs: https://mirrors.ustc.edu.cn/ubuntu/
Suites: noble-security
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
# 源码安全更新源(新增)
Types: deb-src
URIs: https://mirrors.ustc.edu.cn/ubuntu/
Suites: noble-security
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
- 新建文件夹,下载C 库函数源码包
$ mkdir -p ${HOME}/.c
$ cd ${HOME}/.c
$ sudo apt update
$ sudo apt-get source libc6
- 检查已下载的 C 库函数源码包
$ ls -lt
total 18568
drwxr-xr-x 65 root root 4096 May 9 21:51 glibc-2.39
-rw-r--r-- 1 root root 469880 Feb 3 12:42 glibc_2.39-0ubuntu8.7.debian.tar.xz
-rw-r--r-- 1 root root 9257 Feb 3 12:42 glibc_2.39-0ubuntu8.7.dsc
-rw-r--r-- 1 root root 18520988 Feb 3 2024 glibc_2.39.orig.tar.xz
-rw-r--r-- 1 root root 833 Feb 3 2024 glibc_2.39.orig.tar.xz.asc
gdb 初始化配置
配置库函数源码
$ echo "directory /home/scd/.c/glibc-2.39" >> ~/.gdbinit
1万+



被折叠的 条评论
为什么被折叠?



