飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 基础入门

    • Linux 命令概述与环境准备
    • 文件与目录操作(上):ls、cd、pwd
    • 文件与目录操作(下):cat、touch、mkdir、rm、cp、mv
    • 获取帮助与基础工具:man、help、clear、exit、sudo
  • 文件权限

    • 文件权限管理:chmod、chown
  • 系统与监控

    • 进程管理与系统监控:ps、top、kill、df、du、free
    • 用户与环境:whoami、id、env、echo、history
  • 网络命令

    • 网络诊断(上):ping、curl、wget
    • 网络诊断(下):netstat、ss、ip
    • 网络抓包与高级工具:traceroute、tcpdump、nc
    • DNS 与防火墙:nslookup、dig、iptables
  • 文本处理

    • 文本搜索:grep
    • 文本处理(上):awk
    • 文本处理(下):sed
    • 文本排序与统计:sort、uniq、wc
    • 文件查看与查找:head、tail、cut、find
    • 归档压缩与管道组合:tar、gzip、xargs
  • 开发工具

    • 开发工具链(上):gcc、make
    • 开发工具链(下):gdb、time
    • 版本控制:git
    • 远程操作:ssh、scp
    • 编辑器:vim、nano
  • Shell 编程

    • Shell 编程基础(上):变量、管道、重定向
    • Shell 编程基础(下):条件、循环、函数
  • 综合实战

    • 综合实战:日志分析

开发工具链(下):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 命令

命令简写作用
runr运行程序
breakb设置断点
continuec继续运行
nextn单步执行(不进入函数)
steps单步执行(进入函数)
finish运行到当前函数返回
printp打印变量值
display每次停止时自动显示变量
undisplay取消自动显示
backtracebt查看调用栈
framef切换到指定栈帧
listl显示源代码
info查看信息
deleted删除断点
disable禁用断点
enable启用断点
watch设置观察点(变量变化时停止)
set修改变量值
quitq退出 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 密集型,程序充分利用 CPU
  • real >> 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:详细资源使用统计,包括内存、页错误、上下文切换

动手实践

  1. 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
    
  2. 调试段错误:

    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) q
    
  3. time 性能测试:

    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
    # 对比优化前后的时间差异
    
  4. 详细资源统计:

    /usr/bin/time -v ./loop
    # 观察 Maximum resident set size(内存使用)
    
  5. 观察点练习:

    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
    
  6. 思考:time 输出的 user 和 sys 时间之和为什么有时会大于 real 时间?什么情况下会出现这种情况?

上一页
开发工具链(上):gcc、make
下一页
版本控制:git