Segfault发生时程序已崩溃,常规日志无效;必须用sigaction注册带SA_SIGINFO标志的信号处理器,通过write()安全记录si_addr和si_code等信息。

Segfault发生时,程序已经崩溃,常规日志无效
Linux下segfault(SIGSEGV)会直接终止进程,默认不输出任何可定位的上下文。你不能靠std::cout或spdlog::info()捕获它——信号到来时,栈可能已损坏,C++对象析构器甚至不会执行。必须用信号处理机制在内核交付信号的瞬间介入。
用sigaction注册SIGSEGV处理器,避免signal()的不可靠行为
signal()在不同glibc版本中语义不一致(比如是否自动重置、是否阻塞其他信号),且不支持传递额外参数。sigaction()才是POSIX标准做法,能精确控制掩码、标志和上下文获取。
关键点:
- 必须设置
SA_SIGINFO标志,否则拿不到siginfo_t*里的si_addr(出错地址)和si_code(触发原因) - 不要在信号处理函数里调用
printf、malloc、std::string等非异步信号安全函数——它们可能死锁或崩溃 - 用
write()系统调用直接写文件描述符,它是异步信号安全的
示例片段(精简核心逻辑):
void segv_handler(int sig, siginfo_t* info, void* ucontext) {
int fd = open("/tmp/segfault.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd char buf[256];
int len = snprintf(buf, sizeof(buf),
"SIGSEGV at %p, code=%d (addr=%p)\n",
info->si_addr, info->si_code, info->si_addr);
write(fd, buf, len);
close(fd);
// 恢复默认行为,让系统生成core dump(可选)
signal(SIGSEGV, SIG_DFL);
raise(SIGSEGV);
}
配合ulimit -c和/proc/sys/kernel/core_pattern获取完整core dump
仅记录地址远远不够。要定位到具体哪行代码、哪个变量解引用出问题,必须靠core dump配合gdb回溯。
操作步骤:
- 运行前执行
ulimit -c unlimited,否则内核会忽略core生成 - 检查
/proc/sys/kernel/core_pattern:若值为core,dump会生成在当前目录;若为|/path/to/script %p,则被管道接管——确保它不依赖复杂环境(如bash、python) - 编译时加
-g,链接时不strip符号,否则gdb ./a.out core看不到源码行号
常见陷阱:systemd默认关闭core dump,需手动启用:sudo sysctl -w kernel.core_pattern=/tmp/core.%e.%p,并确认kernel.core_uses_pid=1避免覆盖。
用backtrace()和dladdr()在信号处理中做简易栈回溯
如果无法保留core文件(如嵌入式环境或权限受限),可在信号处理器里尝试打印调用栈。但注意:backtrace()不是完全异步信号安全,但在多数glibc实现中,只要不触发内存分配,实际可用。
实操要点:
- 预先分配足够大的缓冲区(如
void* buffer[128]),避免栈上动态分配 - 用
backtrace(buffer, 128)获取返回地址,再用backtrace_symbols_fd(buffer, n, fd)写入文件 -
dladdr()可把地址转成函数名+偏移,但需确保二进制未strip,且.symtab段存在
注意:此方法无法还原局部变量或寄存器状态,也不能替代core dump——只是应急定位入口函数。
真正难的是多线程场景下sigaction只对发送信号的线程生效,而segfault可能发生在任意线程。此时需用pthread_sigmask()统一屏蔽信号,再由主线程统一处理,否则日志可能混杂或丢失上下文。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











