开发工具链(下):gdb、time
gdb:GNU 调试器
gdb(GNU Debugger)是 Linux 下最强大的 C/C++ 调试工具,支持断点、单步执行、查看变量、回溯调用栈等功能。
准备调试程序
编译时必须加 -g 选项生成调试信息:
gcc -g hello.c -o hello
启动 gdb
# 直接启动并加载程序
gdb ./hello
# 启动 gdb 后加载程序
(gdb) file ./hello
# 附加到运行中的进程(PID)
gdb -p 1234
# 加载 core dump 文件
gdb ./hello core.1234
常用 gdb 命令
| 命令 | 简写 | 作用 |
|---|---|---|
run | r | 运行程序 |
break | b | 设置断点 |
continue | c | 继续运行 |
next | n | 单步执行(不进入函数) |
step | s | 单步执行(进入函数) |
finish | 运行到当前函数返回 | |
print | p | 打印变量值 |
display | 每次停止时自动显示变量 | |
undisplay | 取消自动显示 | |
backtrace | bt | 查看调用栈 |
frame | f | 切换到指定栈帧 |
list | l | 显示源代码 |
info | 查看信息 | |
delete | d | 删除断点 |
disable | 禁用断点 | |
enable | 启用断点 | |
watch | 设置观察点(变量变化时停止) | |
set | 修改变量值 | |
quit | q | 退出 gdb |
调试示例
$ gdb ./hello
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
(gdb) b main # 在 main 函数设置断点
Breakpoint 1 at 0x1149: file hello.c, line 4.
(gdb) r # 运行程序
Starting program: /home/user/hello
Breakpoint 1, main () at hello.c:4
4 int x = 10;
(gdb) n # 单步执行(不进入函数)
5 int y = x + 5;
(gdb) p x # 打印变量 x
$1 = 10
(gdb) p y # 打印变量 y
$2 = 15
(gdb) n # 继续单步
6 printf("x = %d, y = %d\n", x, y);
(gdb) n # 执行 printf
x = 10, y = 15
7 return 0;
(gdb) bt # 查看调用栈
#0 main () at hello.c:7
(gdb) c # 继续运行
Continuing.
[Inferior 1 (process 1234) exited normally]
(gdb) q # 退出
高级调试技巧
# 条件断点(x == 5 时停止)
(gdb) b 10 if x == 5
# 临时断点(只生效一次)
(gdb) tb main
# 观察变量(变量变化时停止)
(gdb) watch x
# 查看数组
(gdb) p arr
(gdb) p arr[0]@10 # 打印前 10 个元素
# 查看结构体
(gdb) p student
(gdb) p student.name
# 查看内存
(gdb) x/10x &x # 以十六进制显示 x 地址开始的 10 个字
(gdb) x/10d &x # 以十进制显示
# 反汇编
(gdb) disassemble main
# 设置参数
(gdb) set args arg1 arg2
(gdb) r
# 重定向输入输出
(gdb) run < input.txt > output.txt
time:测量程序运行时间
time 测量命令的执行时间,包括实际时间、用户态时间和内核态时间。
# 基本用法
time ./hello
# 详细输出(-v verbose,GNU time)
/usr/bin/time -v ./hello
# 格式化输出
time -f "real %e, user %U, sys %S" ./hello
time 输出解读
$ time ./hello
Hello, World!
real 0m0.123s # 实际经过的时间(墙上时钟时间)
user 0m0.050s # 用户态 CPU 时间(程序在用户空间执行的时间)
sys 0m0.010s # 内核态 CPU 时间(程序在内核空间执行的时间)
| 时间 | 含义 | 分析 |
|---|---|---|
| real | 实际时间 | 从启动到结束的总时间,包含等待 IO、睡眠 |
| user | 用户态时间 | CPU 执行用户代码的时间 |
| sys | 内核态时间 | CPU 执行系统调用的时间 |
性能分析:
real ≈ user + sys:CPU 密集型,程序充分利用 CPUreal >> user + sys:IO 密集型,程序大量等待 IO(磁盘、网络)sys很高:大量系统调用,可能需要优化
GNU time 详细输出
$ /usr/bin/time -v ./hello
Command being timed: "./hello"
User time (seconds): 0.05
System time (seconds): 0.01
Percent of CPU this job got: 98%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.06
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 1024 # 最大内存使用
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 100
Voluntary context switches: 1
Involuntary context switches: 2
Swaps: 0
File system inputs: 0
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
| 指标 | 含义 |
|---|---|
| Maximum resident set size | 程序使用的最大物理内存(KB) |
| Major page faults | 需要从磁盘加载的页错误次数 |
| Minor page faults | 不需要 IO 的页错误次数 |
| Voluntary context switches | 主动让出 CPU 的次数 |
| Involuntary context switches | 被强制切换的次数 |
综合实战:调试段错误
场景:程序崩溃(Segmentation Fault)
# 编译带调试信息的程序
gcc -g -O0 buggy.c -o buggy
# 运行,发现崩溃
./buggy
# Segmentation fault (core dumped)
# 用 gdb 分析 core dump
gdb ./ buggy core
# 或在 gdb 中直接运行
(gdb) r
Program received signal SIGSEGV, Segmentation fault.
0x0000555555555149 in main () at buggy.c:8
8 *ptr = 10; // ptr 是空指针!
(gdb) p ptr
$1 = (int *) 0x0
(gdb) bt
#0 main () at buggy.c:8
本篇小结
gdb:GNU 调试器,编译时加-g生成调试信息b设置断点,r运行,n单步(不进入函数),s单步(进入函数)p打印变量,bt查看调用栈,c继续运行,q退出watch观察变量变化,x查看内存,disassemble反汇编time:测量程序执行时间,real实际时间,user用户态,sys内核态real ≈ user + sys→ CPU 密集型;real >> user + sys→ IO 密集型/usr/bin/time -v:详细资源使用统计,包括内存、页错误、上下文切换
动手实践
gdb 基础调试:
cat > debug.c #include <stdio.h> int main() { int x = 10; int y = 20; int sum = x + y; printf("sum = %d\n", sum); return 0; } # Ctrl+D gcc -g debug.c -o debug gdb ./debug (gdb) b main (gdb) r (gdb) n (gdb) p x (gdb) n (gdb) p y (gdb) n (gdb) p sum (gdb) c (gdb) q调试段错误:
cat > segfault.c #include <stdio.h> int main() { int *ptr = NULL; *ptr = 10; // 空指针解引用 return 0; } # Ctrl+D gcc -g segfault.c -o segfault ./segfault # 崩溃 gdb ./segfault (gdb) r # 运行到崩溃 (gdb) bt # 查看调用栈 (gdb) p ptr # 查看 ptr 值 (gdb) qtime 性能测试:
cat > loop.c #include <stdio.h> int main() { long sum = 0; for (long i = 0; i < 100000000; i++) { sum += i; } printf("sum = %ld\n", sum); return 0; } # Ctrl+D gcc loop.c -o loop time ./loop gcc -O2 loop.c -o loop_opt time ./loop_opt # 对比优化前后的时间差异详细资源统计:
/usr/bin/time -v ./loop # 观察 Maximum resident set size(内存使用)观察点练习:
cat > watch.c #include <stdio.h> int main() { int x = 0; for (int i = 0; i < 5; i++) { x = i * i; } printf("x = %d\n", x); return 0; } # Ctrl+D gcc -g watch.c -o watch gdb ./watch (gdb) b main (gdb) r (gdb) watch x # x 变化时停止 (gdb) c # 继续,会在 x 变化时停止 (gdb) p x # 查看当前值 (gdb) c # 继续 (gdb) q思考:
time输出的user和sys时间之和为什么有时会大于real时间?什么情况下会出现这种情况?