FAQ-14:段错误(segmentation fault)

预计阅读时间:18 分钟

📖 目录

问题速查表

问题解决章节
程序崩溃,需要确认段错误记录第一步:确认段错误信息
需要定位崩溃的具体代码位置第二步:生成并分析 core dump
怀疑内存硬件故障导致随机崩溃第三步:检查内存硬件
动态链接库缺失或版本不兼容第四步:检查动态链接库
栈空间不足(递归过深或大数组)第五步:检查栈空间
开发阶段排查内存错误第六步:使用 Address Sanitizer(开发场景)

Segmentation fault(通常简写为 segfault)是 Linux 系统中最常见的程序崩溃信号。当程序试图访问不允许访问的内存区域(如访问未分配的内存、写入只读内存、或超出进程地址空间范围)时,内核会向该进程发送 SIGSEGV 信号,默认行为是终止进程并生成 core dump 文件。

段错误的本质是操作系统的内存保护机制在起作用。每个进程都运行在自己的虚拟地址空间中,内核通过页表和 MMU(内存管理单元)确保进程只能访问属于自己的内存区域。当程序违反这个规则时,硬件会触发一个异常,内核捕获后向违规进程发送 SIGSEGV 信号。这种保护防止了一个程序的错误操作影响其他进程或整个系统。

段错误可能出现在任何使用 C/C++、Rust 等需要手动管理内存的编程语言编写的程序中,也可能出现在 Python、Java 等语言的 C 扩展模块中。对于系统管理员来说,理解如何诊断段错误是排查服务崩溃的关键技能。

原因分析

空指针解引用是最常见的原因之一。当程序尝试通过一个值为 NULL(0x0)的指针访问内存时,由于该地址通常不在进程的地址空间内,会触发段错误。这通常是因为函数返回了错误结果但没有检查,或者变量未正确初始化。

缓冲区溢出是另一个主要来源。当程序向一个固定大小的缓冲区写入超过其容量的数据时,可能会覆盖相邻的内存区域,包括返回地址、函数指针或其他关键数据,最终导致段错误。

使用已释放的内存(use-after-free)在复杂的程序中很常见。当程序释放了一块内存后,又通过之前的指针访问它,如果该内存已被重新分配给其他用途,就会产生不可预测的行为,包括段错误。

栈溢出通常由无限递归或过大的局部数组引起。每个线程的栈空间有限(通常为 8MB),当递归调用层数过深或在栈上分配了过大的数据结构时,会超出栈边界触发段错误。

内存硬件故障虽然相对少见,但在服务器环境中确实存在。损坏的内存条可能导致程序在正常运行时随机崩溃,且症状与软件 bug 类似,增加了排查难度。

动态链接库版本不兼容也会导致段错误。当程序链接的共享库版本与编译时不一致,或者库文件损坏,可能导致函数调用约定不匹配,进而引发内存访问异常。

排查决策树

段错误的根因分布很广,按下面的决策树从"有没有证据"到"证据指向哪里"逐层推进,核心原则是:先拿到 core dump 再用 gdb 说话,不要瞎猜:

程序崩溃:Segmentation fault (core dumped)
│
├─ ① 先确认崩溃记录与是否有 core 文件
│    │    dmesg | grep segfault / coredumpctl list
│    │    ├─ 有 core → 进入 ② 用 gdb 分析
│    │    └─ 无 core(ulimit -c 0 或 core_pattern 未配置)
│    │         → 先启用 core dump,再复现一次
│
├─ ② gdb 栈回溯指向哪里
│    │    gdb 中 bt / bt full
│    │    ├─ 指向应用自身函数 → 代码 bug
│    │    │    ├─ 空指针 / 越界 / use-after-free
│    │    │    │    └─ 开发环境用 ASan/Valgrind 复现定位
│    │    │    └─ 递归过深 / 大数组 → 栈溢出 → ulimit -s / 改堆分配
│    │    └─ 指向共享库 → 进入 ③
│
├─ ③ 动态库检查
│    │    ldd 程序 | grep "not found"
│    │    ├─ 缺失 → 安装 / 重装对应库
│    │    ├─ LD_LIBRARY_PATH 指向错误版本 → 修正环境变量
│    │    └─ 库都正常 → 进入 ④
│
└─ ④ 环境性因素
     ├─ 崩溃随机、复现率低 → 怀疑内存硬件
     │    └─ memtester / memtest86+ 检测,journalctl -k 看 MCE 记录
     └─ 崩溃与其他变更时间点重合 → 回查升级记录、配置变更

排查步骤

第一步:确认段错误信息

# 查看内核日志中的段错误记录
dmesg | tail -20 | grep segfault
# 输出示例:
# [12345.678] myapp[1234]: segfault at 0xdeadbeef ip 0x7f1234567890 sp 0x7ffabc123456 error 4

# 使用 systemd-coredump 查看 core dumpcoredumpctl list
coredumpctl info 程序名
coredumpctl gdb 程序名  # 直接用 gdb 分析

第二步:生成并分析 core dump

# 启用 core dump
ulimit -c unlimited

# 设置 core dump 文件名模式
echo "core.%e.%p" > /proc/sys/kernel/core_pattern

# 运行程序并等待崩溃
./myapp

# 查看生成的 core 文件
ls -lh core*

# 用 gdb 分析 core dump
sudo apt install gdb
gdb ./myapp core.1234

# 在 gdb 中常用命令
(gdb) bt          # 查看完整调用栈
(gdb) bt full     # 查看调用栈及局部变量
(gdb) info registers  # 查看寄存器状态
(gdb) print 变量名    # 查看变量值

第三步:检查内存硬件

# 使用 memtester 进行内存测试
sudo apt install memtester
sudo memtester 1G 1  # 测试 1GB 内存,运行 1 轮

# 或重启后进入 memtest86+(在 GRUB 启动菜单中选择)
# 查看硬件错误日志
sudo journalctl -k | grep -i "memory error\|mce\|EDAC\|hardware error"

# 查看 MCE(Machine Check Exception)
sudo mcelog --client

第四步:检查动态链接库

# 检查程序依赖的共享库
ldd /path/to/program

# 确认所有库文件都存在且版本匹配
# 输出中如果有 "not found" 表示缺失库文件

# 检查库文件完整性
ldconfig -p | grep 库名

# 重新安装缺失的库
sudo apt install --reinstall lib库名

第五步:检查栈空间

# 查看当前栈大小限制
ulimit -s
# 输出单位为 KB,默认通常是 8192(8MB)

# 临时增大栈大小
ulimit -s 65536  # 改为 64MB

# 永久修改(/etc/security/limits.conf)
# * soft stack 65536

第六步:使用 Address Sanitizer(开发场景)

# 编译时启用 ASan(GCC/Clang)
gcc -fsanitize=address -g -o myapp myapp.c
# 或 C++
g++ -fsanitize=address -g -o myapp myapp.cpp

# 运行程序,ASan 会检测内存错误并输出详细报告
./myapp
# 输出示例:
# ==1234==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc...
# SUMMARY: AddressSanitizer: stack-buffer-overflow myapp.c:42 in main

解决方案

针对空指针解引用

// 在代码中添加 NULL 检查
void process(char *data) {
    if (data == NULL) {
        fprintf(stderr, "Error: data is NULL\n");
        return;
    }
    // 安全使用 data
}

// 使用编译器警告
gcc -Wall -Wextra -o myapp myapp.c

针对缓冲区溢出

// 使用安全的字符串操作函数
// 避免:strcpy(buf, src);
// 推荐:
strncpy(buf, src, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';

// 或使用 snprintf
snprintf(buf, sizeof(buf), "%s", src);

// 编译时启用栈保护
gcc -fstack-protector-strong -o myapp myapp.c

针对 use-after-free

// 释放后将指针置为 NULL
free(ptr);
ptr = NULL;

// 使用 Valgrind 检测内存问题
valgrind --tool=memcheck --leak-check=full ./myapp

// 或使用 Address Sanitizer(性能更好)
gcc -fsanitize=address -g -o myapp myapp.c

针对栈溢出

// 检查递归终止条件
int factorial(int n) {
    if (n <= 1) return 1;  // 确保有终止条件
    return n * factorial(n - 1);
}

// 将大数组改为动态分配(堆上)
// 避免:
void func() {
    int arr[1000000];  // 栈上分配可能溢出
}
// 推荐:
void func() {
    int *arr = malloc(1000000 * sizeof(int));
    // 使用完毕后 free(arr);
}

真实案例

案例 A:ulimit -c 0 导致无 core 可分析,启用后 gdb 栈回溯秒定位

现象:自研 C++ 数据处理服务上线后频繁崩溃,但只拿到一段 dmesg 记录,没有 core 文件:

$ dmesg | tail -5
[432109.87] process_data[2456]: segfault at 0x7f8a2c000000 ip 0x55a3f1d2a1b3 sp 0x7f8a0ffffc30 error 4

$ ls core* 2>/dev/null
ls: cannot access 'core*': No such file or directory

$ ulimit -c
0

根因ulimit -c 0 禁用了 core dump。dmesg 虽然给出了崩溃地址(ip 0x55a3...),但没有 core 文件就无法用 gdb 还原调用栈,等于只有结论没有证据。

修复:启用 core dump 并固定输出路径:

$ ulimit -c unlimited
$ echo 'kernel.core_pattern=/var/crash/core.%e.%p' | sudo tee /etc/sysctl.d/99-core.conf
$ sudo sysctl -p /etc/sysctl.d/99-core.conf
$ ./process_data                     # 复现崩溃
$ ls -lh /var/crash/
-rw------- 1 root root 128M /var/crash/core.process_data.2456

验证(gdb 栈回溯)

$ gdb ./process_data /var/crash/core.process_data.2456
(gdb) bt
#0  memcpy (dest=0x7f8a2c000000, src=0x55a3f2000ab0, n=4096) at memcpy.S
#1  0x000055a3f1d2a1b3 in parse_chunk (data=0x0, len=4096) at process.c:87
#2  0x000055a3f1d2a402 in main (argc=2, argv=0x7ffd...) at process.c:152
(gdb) frame 1
(gdb) print data
$1 = (char *) 0x0

栈回溯显示 parse_chunk 在 process.c:87 把 NULL 指针直接传给了 memcpy,空指针解引用实锤。修复:

// process.c:85
if (data == NULL) {
    log_error("parse_chunk: NULL data, len=%zu", len);
    return -1;
}
memcpy(dst, data, len);

重新编译部署后观察一周无崩溃。后续把 core 配置写进部署脚本,避免新环境再次踩坑。

案例 B:LD_LIBRARY_PATH 指向旧版动态库,ldd 揪出元凶

现象:升级系统库后,第三方商业软件(/opt/vendor/bin/analysis)启动即崩溃:

$ /opt/vendor/bin/analysis
Segmentation fault (core dumped)

排查过程:先看程序依赖的共享库,注意加载路径:

$ ldd /opt/vendor/bin/analysis | grep -E "not found|ssl|crypto"
    libssl.so.3 => /usr/local/lib/libssl.so.3 (0x00007f...)
    libcrypto.so.3 => /usr/local/lib/libcrypto.so.3 (0x00007f...)
$ echo $LD_LIBRARY_PATH
/usr/local/lib

根因:环境变量 LD_LIBRARY_PATH=/usr/local/lib 让程序加载了用户源码编译的旧版 OpenSSL(符号表与软件预期不一致),函数调用约定错位导致崩溃。系统自带的 /usr/lib/x86_64-linux-gnu/libssl.so.3 才是兼容版本。

修复:清空该环境变量并从配置文件中移除:

$ unset LD_LIBRARY_PATH
$ sed -i '/LD_LIBRARY_PATH/d' ~/.bashrc
$ ldd /opt/vendor/bin/analysis | grep ssl
    libssl.so.3 => /usr/lib/x86_64-linux-gnu/libssl.so.3 (0x00007f...)

验证

$ /opt/vendor/bin/analysis && echo OK
OK
$ ldd -r /opt/vendor/bin/analysis   # -r 检查所有符号可解析,无报错即通过

经验:LD_LIBRARY_PATH 是全局影响的环境变量,只应在临时调试时使用;生产环境优先用 rpath 或把库安装到标准路径,升级库后对关键软件做一次 ldd -r 回归检查。

案例 C:硬件内存故障导致随机 segfault

现象:服务器上的 Java 应用不定时崩溃,segfault 地址每次不同,没有固定规律。

# dmesg 中多次出现不同地址的 segfault
$ dmesg | grep segfault
[432109.87] java[2456]: segfault at 0x7f8a2c000000 ip 0x55a3f1d2a1b3
[433215.42] java[3012]: segfault at 0x7f3b1a000000 ip 0x55a3f1d2a1b3
[434567.89] java[2789]: segfault at 0x7fc5e8000000 ip 0x55a3f1d2a1b3

# 每次崩溃地址不同 → 不是代码 bug,疑似硬件问题
# 运行 memtest86+ 检测内存
$ sudo memtester 4G 3    # 测试 4GB 内存,跑 3 轮
FAIL: 0x5a5a5a5a != 0x00000000 at address 0x1a2b3c4d
FAIL: Memory write/read mismatch

根因:物理内存条的某个区域损坏,导致该区域写入的数据读取时位翻转。Java 堆分配到损坏地址时触发 segfault,地址随机是因为每次分配位置不同。

修复:更换故障内存条。运行 memtestermemtest86+ 可以精确定位故障地址范围。

案例 D:Valgrind 检测出隐藏的内存泄漏导致程序逐渐崩溃

现象:一个长期运行的 C 程序在运行数小时后崩溃,但崩溃前没有任何明显错误:

# 程序运行一段时间后崩溃
$ ./long-running-app
... 正常运行 3 小时 ...
Segmentation fault (core dumped)

# 查看内存使用趋势
$ while true; do ps -p $(pgrep long-running-app) -o rss=; sleep 60; done
524288
655360
786432
917504
1048576  # 内存持续增长,最终耗尽

排查过程:使用 Valgrind 检测内存问题:

# 使用 Valgrind 检测内存泄漏
$ valgrind --tool=memcheck --leak-check=full --track-origins=yes ./long-running-app 2>&1 | head -100
==12345== Memcheck, a memory error detector
==12345== HEAP SUMMARY:
==12345==   in use at exit: 1,048,576 bytes in 4,096 blocks
==12345==   total heap usage: 524,288 allocs, 520,192 frees, 125,829,120 bytes allocated
==12345==
==12345== 1,048,576 bytes in 4,096 blocks are definitely lost in loss record 1 of 1
==12345==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345==    by 0x1091B3: process_data (app.c:142)
==12345==    by 0x10945B: main (app.c:200)

根因:程序在 process_data 函数中每次调用都分配内存,但某些错误路径下没有释放,导致内存泄漏。运行数小时后耗尽地址空间,触发 segfault。

修复

// app.c:142 附近的代码
void *process_data(const char *input) {
    char *buffer = malloc(BUF_SIZE);
    if (buffer == NULL) return NULL;
    
    int ret = parse_input(buffer, input);
    if (ret < 0) {
        // 错误:这里没有释放 buffer 就返回了
        return NULL;  // ← 修复:改为 goto cleanup
    }
    
    // ... 处理数据 ...
    
    free(buffer);
    return result;

cleanup:
    free(buffer);
    return NULL;
}

预防:使用 -fsanitize=address 编译并在 CI 中运行测试。对长期运行的程序定期用 Valgrind 做内存检测。

案例 E:多线程竞态条件导致随机段错误

现象:多线程程序在高并发时随机崩溃,崩溃位置不固定:

# 崩溃时的堆栈
$ gdb ./threaded-app core.1234
(gdb) bt
#0  0x00007f... in __memmove_avx2 () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x000055... in update_counter (ctx=0x55...) at stats.c:87
#2  0x000055... in worker_thread (arg=0x55...) at worker.c:45

排查过程:使用 Thread Sanitizer 检测竞态条件:

# 编译时启用 Thread Sanitizer
$ gcc -fsanitize=thread -g -o threaded-app threaded-app.c -lpthread

# 运行程序
$ ./threaded-app
WARNING: ThreadSanitizer: data race (pid=1234)
  Write of size 4 at 0x7f... by thread T1:
    #0 update_counter stats.c:87
    #1 worker_thread worker.c:45
  
  Previous read of size 4 at 0x7f... by thread T2:
    #0 read_counter stats.c:92
    #2 worker_thread worker.c:50

根因:多个线程同时读写共享的计数器变量,没有使用互斥锁保护,导致数据竞争和随机崩溃。

修复

// stats.c 中添加互斥锁
static pthread_mutex_t counter_lock = PTHREAD_MUTEX_INITIALIZER;
static int shared_counter = 0;

void update_counter(int value) {
    pthread_mutex_lock(&counter_lock);
    shared_counter += value;
    pthread_mutex_unlock(&counter_lock);
}

int read_counter(void) {
    int val;
    pthread_mutex_lock(&counter_lock);
    val = shared_counter;
    pthread_mutex_unlock(&counter_lock);
    return val;
}

预防:使用 -fsanitize=thread 编译并在 CI 中运行并发测试。对共享资源的访问始终使用适当的同步机制。

案例 F:内核模块导致整个系统段错误

现象:加载自定义内核模块后,系统随机崩溃,dmesg 中出现内核级别的 segfault:

# 内核日志
$ dmesg | grep -i "segfault\|oops\|bug"
[  123.456] BUG: unable to handle page fault at ffffffff82000000
[  123.456] PGD 0 P4D 0
[  123.456] Oops: 0000 [#1] SMP NOPTI
[  123.456] CPU: 2 PID: 1234 Comm: mymodule Tainted: G        W  O  5.15.0-generic
[  123.456] RIP: 0010:my_func+0x1a/0x50 [mymodule]

排查过程

# 查看加载的模块
$ lsmod | grep mymodule
mymodule 16384 0

# 检查模块信息
$ modinfo mymodule
filename:       /lib/modules/5.15.0/kernel/drivers/mymodule.ko
description:    My custom module
license:        GPL

# 查看模块日志
$ journalctl -k | grep mymodule

根因:内核模块代码中存在内存访问错误(如空指针解引用、越界访问)。内核级别的错误会导致整个系统崩溃,比用户空间程序更危险。

修复:卸载有问题的模块,修复代码后重新编译:

# 卸载模块
$ sudo rmmod mymodule

# 修复代码后重新编译
$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

# 加载修复后的模块
$ sudo insmod mymodule.ko

预防:内核模块开发必须极其谨慎。使用 KASAN(Kernel Address Sanitizer)检测内存错误。在测试环境中充分验证后再部署到生产环境。

预防措施

  • 编译时始终启用警告:-Wall -Wextra -Werror,将警告视为错误
  • 定期使用 Valgrind 或 Address Sanitizer 进行内存检测
  • 使用静态分析工具(如 cppcheck、clang-tidy)在编码阶段发现潜在问题
  • 对于生产环境的关键服务,启用 core dump 并配置 coredumpctl 便于事后分析
  • 硬件层面定期运行 memtest86+ 检测内存故障
  • CI 流水线中接入 gdb + core dump 自动分析步骤,代码合并前崩溃即可定位到行
  • 禁止全局设置 LD_LIBRARY_PATH,库路径统一通过 ldconfig 配置,升级库后对关键软件做 ldd -r 回归
  • 对承载关键服务的物理机每季度做一次内存巡检(memtest86+ / memtester),硬件更换后必测

进阶排查技巧

使用 gdb 进行事后调试

core dump 是分析段错误的金矿,gdb 可以还原崩溃现场:

# 启用 core dump
ulimit -c unlimited
echo '/var/crash/core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern

# 运行程序并复现崩溃
./myapp

# 使用 gdb 分析 core dump
gdb ./myapp /var/crash/core.myapp.1234

# gdb 常用命令
(gdb) bt              # 查看调用栈
(gdb) bt full         # 查看调用栈及局部变量
(gdb) frame 1         # 切换到栈帧 1
(gdb) print variable  # 查看变量值
(gdb) info registers  # 查看寄存器状态
(gdb) disassemble     # 反汇编当前函数

使用 coredumpctl 管理 core dump

# 列出所有 core dump
coredumpctl list

# 查看特定程序的 core dump
coredumpctl info myapp

# 用 gdb 分析
coredumpctl gdb myapp

# 导出 core dump 文件
coredumpctl dump -o /tmp/core.myapp

使用 dmesg 追踪段错误模式

# 查看所有段错误记录
dmesg | grep segfault

# 分析崩溃地址模式
dmesg | grep segfault | awk '{print $6}' | sort | uniq -c | sort -rn

# 查看内核日志中的内存错误
dmesg | grep -i "memory\|mce\|edac\|hardware error"

使用 Address Sanitizer 运行时检测

# 编译时启用 ASan
gcc -fsanitize=address -g -o myapp myapp.c

# 运行程序(ASan 会自动检测内存错误)
./myapp

# ASan 输出示例:
# ==1234==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
# ==1234==READ of size 4 at 0x... thread T0
# ==1234==    #0 0x... in func myapp.c:42

常见误区与陷阱

  • 不要忽略编译器警告-Wall -Wextra 可以在编译期发现很多潜在的段错误
  • 不要在生产环境使用 Valgrind:性能开销太大,只在开发/测试环境使用
  • 不要盲目禁用 core dump:core dump 是事后分析的关键证据
  • 不要忽略随机崩溃:随机的 segfault 很可能是硬件问题或内存泄漏
  • 不要在没有调试信息的情况下分析 core dump:编译时加 -g 保留调试符号

故障复现与验证清单

修复段错误问题后,使用以下清单验证:

# 1. 编译时启用所有警告
gcc -Wall -Wextra -Werror -o myapp myapp.c

# 2. 使用 Valgrind 检测内存问题
valgrind --tool=memcheck --leak-check=full ./myapp

# 3. 使用 Address Sanitizer 测试
gcc -fsanitize=address -g -o myapp-asan myapp.c
./myapp-asan

# 4. 长时间运行测试
./myapp &  # 运行 24 小时以上
# 监控内存使用:watch -n 60 'ps -p $(pgrep myapp) -o rss='

# 5. 检查 core dump 配置
ulimit -c  # 应为 unlimited
cat /proc/sys/kernel/core_pattern

最佳实践

  • 编译选项:开发阶段使用 -fsanitize=address -g -Wall -Wextra,发布时使用 -O2 -fstack-protector-strong
  • core dump 管理:确保 ulimit -c unlimited 已设置,并配置 /proc/sys/kernel/core_pattern 以便自动收集 core 文件
  • 版本控制:使用包管理器安装的程序,确保版本与系统其他组件兼容;手动编译的程序记录编译选项
  • 日志关联:将 segfault 事件与应用日志、系统日志关联分析,找出崩溃前的操作序列
  • 防御性编程:在代码中加入必要的空指针检查、边界检查、返回值检查,宁可冗余也不冒险

延伸阅读

附录:内存调试工具速查表

工具用途安装命令
gdbcore dump 分析、实时调试sudo apt install gdb
Valgrind内存泄漏检测、非法访问检测sudo apt install valgrind
Address Sanitizer运行时内存错误检测编译选项 -fsanitize=address
Thread Sanitizer竞态条件检测编译选项 -fsanitize=thread
memtester内存硬件检测sudo apt install memtester
memtest86+全面内存硬件测试GRUB 启动菜单选择
coredumpctlcore dump 管理systemd 自带
strace系统调用跟踪sudo apt install strace

附录:gdb 常用命令速查

# 启动 gdb
gdb ./program
gdb ./program core.1234

# 常用命令
(gdb) run                  # 运行程序
(gdb) bt                   # 查看调用栈
(gdb) bt full              # 查看调用栈及局部变量
(gdb) frame N              # 切换到栈帧 N
(gdb) print variable       # 查看变量值
(gdb) print *pointer       # 查看指针指向的内容
(gdb) info registers       # 查看寄存器状态
(gdb) info locals          # 查看所有局部变量
(gdb) disassemble          # 反汇编当前函数
(gdb) list                 # 显示源代码
(gdb) break function       # 设置断点
(gdb) continue             # 继续执行
(gdb) step                 # 单步执行(进入函数)
(gdb) next                 # 单步执行(跳过函数)
(gdb) quit                 # 退出 gdb

附录:Valgrind 使用示例

# 基本内存检测
valgrind --tool=memcheck ./program

# 检测内存泄漏
valgrind --tool=memcheck --leak-check=full ./program

# 检测非法内存访问
valgrind --tool=memcheck --track-origins=yes ./program

# 输出到文件
valgrind --log-file=/tmp/valgrind.log ./program

# 常见错误类型:
# Invalid read/write of size N    # 非法读写
# Use of uninitialised value       # 使用未初始化值
# Conditional jump depends on...   # 依赖未初始化值
# LEAK SUMMARY:                    # 内存泄漏摘要

附录:Address Sanitizer 输出解读

# 编译启用 ASan
gcc -fsanitize=address -g -o program program.c

# 运行程序
./program

# 输出示例:
# ==1234==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
# ==1234==READ of size 4 at 0x... thread T0
#     #0 0x... in func program.c:42
#     #1 0x... in main program.c:100
# ==1234==ABORTING

# 错误类型:
# heap-buffer-overflow      # 堆缓冲区溢出
# stack-buffer-overflow     # 栈缓冲区溢出
# heap-use-after-free       # 堆释放后使用
# stack-use-after-return    # 栈返回后使用
# double-free               # 双重释放
# memory-leak               # 内存泄漏

案例 G:共享库符号版本不匹配导致运行时崩溃

现象:升级 OpenSSL 后,依赖旧版 OpenSSL 的程序启动时崩溃:

$ /opt/legacy-app/bin/app
Segmentation fault (core dumped)

# 检查依赖的库
$ ldd /opt/legacy-app/bin/app
    libssl.so.1.1 => /usr/lib/x86_64-linux-gnu/libssl.so.1.1
    libcrypto.so.1.1 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1

排查过程

# 检查库文件是否存在
$ ls -la /usr/lib/x86_64-linux-gnu/libssl.so.1.1
ls: cannot access '/usr/lib/x86_64-linux-gnu/libssl.so.1.1': No such file or directory

# 系统上只有 OpenSSL 3.x
$ ls /usr/lib/x86_64-linux-gnu/libssl.so.*
/usr/lib/x86_64-linux-gnu/libssl.so.3

# 用 gdb 分析 core dump
$ gdb /opt/legacy-app/bin/app core.1234
(gdb) bt
#0  0x00007f... in SSL_CTX_new () from /usr/lib/x86_64-linux-gnu/libssl.so.3
#1  0x000055... in init_ssl (ctx=0x0) at app.c:45

根因:程序编译时链接的是 OpenSSL 1.1,但系统升级到 OpenSSL 3.x 后,旧版库文件被删除。虽然 ldd 通过符号链接找到了 libssl.so.3,但函数签名和数据结构已改变,导致运行时崩溃。

修复

# 方案一:安装兼容版本的旧库
sudo apt install libssl1.1

# 方案二:重新编译程序,链接新版 OpenSSL
cd /opt/legacy-app && make clean && make

# 方案三:使用容器运行旧版程序
docker run --rm -v /opt/legacy-app:/app openssl:1.1 /app/bin/app

预防:系统升级后对关键软件做 ldd -r 回归检查。使用容器化部署可以隔离系统库依赖。

↑ 回到顶部