示例程序
c
#include <stdio.h>
int main(void) {
char s[20] = {0};
s[0] = 's';
s[2] = 's';
printf("%s\n", s);
return 0;
}
编译
bash
$ gcc fun_printf.c -o fun_printf
运行
bash
$ ./fun_printf
s
查看示例程序,期望输出 s s,但实际只输出了一个s
查看 printf 函数的说明
bash
$ 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 调试程序
重新编译程序 支持调试
bash
$ gcc -g fun_printf.c -o fun_printf
启动 gdb 调试
bash
$ 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)
查看源码,设置断点
bash
(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.
开始运行程序
bash
(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);
进入函数
bash
(gdb) s
0x00007ffff7c87bf8 in __GI__IO_puts (str=0x7fffffffd8f0 "s") at ./libio/ioputs.c:35
35 size_t len = strlen (str);
发现 printf 函数变成了 puts
查看 puts 函数的源码
bash
(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
跳出当前函数
bash
(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
bash
(gdb) print s
$3 = "s\000s", '\000' <repeats 16 times>
查看s的内存
bash
(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),以避免代码重排导致调试困难。
bash
gcc -g -O0 -o program program.c
- 启动 GDB
可以通过以下几种方式启动调试会话:
- 加载可执行文件:gdb ./program
- 加载 Core Dump 文件(用于分析崩溃):gdb ./program core
- 附加到运行中的进程:gdb -p
- 核心调试命令
- 设置断点 (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 初始化配置
配置库函数源码
bash
$ echo "directory /home/scd/.c/glibc-2.39" >> ~/.gdbinit