在C/C++程序开发过程中,是不是经常会遇到这种场景:时间紧迫匆忙上线,程序突然崩溃。开发同事拿到日志,一看无法定位。临近节点快要交付,各方领导在催。加班加点痛苦排查,找出问题羞愧。 程序崩溃不可怕,无从排查才尴尬。特别是后台程序,“噶”的悄无声息,似乎它未曾存在过。
问题现场
先举例一种crash
的代码:
void causeSegmentationFault() {
int* ptr = nullptr;
*ptr = 1; // 这会引发段错误
}
int main() {
// 触发段错误
causeSegmentationFault();
return 0;
}
输出
执行程序能看到crash
提示。
./exe
Segmentation fault
从实现上,能够快速的看出访问非法内存,导致crash
。
而在实际项目中,这类骚操作往往藏的很深,且没有关键日志,第一时间很难发现程序崩溃了,以及崩溃的大致范围。
既然问题存在,那如何解决呢?徘徊不定,先问魔镜:
Me: 魔镜啊魔镜,上述代码有什么问题?
魔镜:哈哈哈哈哈哈哈,你的程序崩溃啦。这么愚蠢的写法,真让人笑掉大牙。谁让你用空指针啊,赶紧回炉重造吧~
Me: ??? 给你接的网线,网速过快了吧。
别秀了,说正事。如果是大型项目,运行时出现crash,如何快速且精准地定位问题根源。
魔镜:神曲太魔性了,给我洗脑了都。
运行时出现程序崩溃,通常有三种解决方法:
1. 增加日志记录:
在代码关键位置添加日志,通过检查日志来推测崩溃原因。
2. 生成coredump文件:
配置系统生成coredump文件,并使用gdb分析以定位段错误。
3. 获取异常信号,打印堆栈:
通过注册信号处理函数(如 signal 或 sigaction),在程序崩溃(如段错误、断言失败等)时自动捕获当前的函数调用堆栈,并将其输出到日志或标准输出。
通过与AI
的友好沟通,了解大致有三种常用方案:
coredump
文件确实能够通过GDB
快速定位问题。然而,极端情况下,可能会遇到coredump
文件过大或生成不完整,导致无法解析和定位问题。通常情况下,比较完备的大型项目以上三种方式应该都会存在。这里简单记录一下如何在程序崩溃时,日志记录当前堆栈信息。
sigaction()
或 signal()
注册信号处理回调,捕获如SIGSEGV
、SIGABRT
等异常信号。backtrace()
获取调用栈帧。backtrace_symbols()
转换为可读字符串。dladdr()
解析符号地址获取更精确的函数名和源文件信息。abi::__cxa_demangle()
对经过 Name Mangling
的函数名进行 demangle
还原,获取可读的函数签名。实现 实现上比较简单,按照上述描述,代码大致如下:
std::string DumpBacktrace(const int32_t frames)
{
std::ostringstream oss;
void* callStack[frames];
int32_t numFrames = ::backtrace(callStack, frames);
char** symbols = ::backtrace_symbols(callStack, numFrames);
for (int32_t i = 0; i < numFrames; ++i) {
Dl_info info;
std::string funcName;
if (dladdr(callStack[i], &info)) {
int32_t status = -1;
char* demangled = abi::__cxa_demangle(info.dli_sname, nullptr, 0, &status);
if (demangled != nullptr) {
funcName = demangled;
free(demangled);
} elseif (info.dli_sname != nullptr) {
funcName = info.dli_sname;
} else {
funcName = "??";
}
} else {
funcName = (symbols != nullptr ? symbols[i] : "??");
}
oss << "#" << std::setw(2) << i << " " << funcName << " at " << callStack[i] << std::endl;
}
if (symbols != nullptr) {
free(symbols);
}
if (numFrames >= frames) {
oss << "# [truncated]" << std::endl;
}
return oss.str();
}
void signalHandler(int signum) {
std::cout << "Caught signal " << signum << ", printing backtrace:" << std::endl;
std::string backtrace = DumpBacktrace(20);
std::cout << backtrace << std::endl;
// 恢复默认信号处理
signal(signum, SIG_DFL);
// 重新发送信号以触发默认行为
raise(signum);
}
void causeSegmentationFault() {
int* ptr = nullptr;
*ptr = 1; // 这会引发段错误
}
int main() {
// 注册信号处理函数
signal(SIGSEGV, signalHandler);
// 触发段错误
causeSegmentationFault();
return0;
}
输出 程序运行时触发段错误(Signal 11),捕获异常并打印堆栈信息如下:
$ ./exe
Caught signal 11, printing backtrace:
# 0 DumpBacktrace[abi:cxx11](int) at 0x5615568f8539
# 1 signalHandler(int) at 0x5615568f88dd
# 2 ?? at 0x7fbfa33af520
# 3 causeSegmentationFault() at 0x5615568f8978
# 4 main at 0x5615568f89a2
# 5 ?? at 0x7fbfa3396d90
# 6 __libc_start_main at 0x7fbfa3396e40
# 7 _start at 0x5615568f8345
Segmentation fault
通过分析堆栈打印的函数,大致能够定位问题接口causeSegmentationFault
,再结合源码很快能够排查出问题所在。
SIGSEGV
、SIGABRT
等),然后在信号处理函数中打印堆栈信息即可。abi::__cxa_demangle
还原函数名。-O0 -g -rdynamic
选项以保留符号信息,确保堆栈中的函数名可被解析。用心感悟,认真记录,写好每一篇文章,分享每一框干货。
更多文章内容包括但不限于C/C++、Linux、开发常用神器等,可进入“开源519公众号”聊天界面输入“文章目录” 或者 菜单栏选择“文章目录”查看。公众号后台聊天框输入本文标题,在线查看源码。 在聊天框输入“开源519资料” 获取Linux C/C++ 学习资料书籍。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有