C++获取Crash调用堆栈及行号
发布时间:2023-06-02 16:44:36.494 文章来源:AiSoftCloud 浏览次数:760 下载次数:1 

背景

程序在运行过程中如果出现coredump等异常,为了尽快确定异常代码位置,可以使用gdb调试工具,通过bt命令可以查看异常位置的backtrace/callstack,通过此信息有助于快速排查问题。

然而,通常情况下,程序在运行过程中不能一直开着gdb,或者程序已经挂掉了,此时也无法再使用gdb工具,这种情况下如何快速定位异常呢。

答案是有的,操作系统提供一种信号处理机制,可以在程序挂掉之前做一些用户自定义的操作,借助此功能,如果在程序挂掉前获取到堆栈信息,就可以找到异常代码位置。

首先写一个示例程序:

  1. void InvokeDefaultSignalHandler(int signal_number) {
  2. struct sigaction sig_action;
  3. memset(&sig_action, 0, sizeof(sig_action));
  4. sigemptyset(&sig_action.sa_mask);
  5. sig_action.sa_handler = SIG_DFL;
  6. sigaction(signal_number, &sig_action, nullptr);
  7. kill(getpid(), signal_number);
  8. }
  9. void FailureSignalHandler(int signal_number, siginfo_t *signal_info, void *ucontext) {
  10. std::stringstream ss;
  11. void *buffer[32];
  12. int32_t nptrs = backtrace(buffer, 32);
  13. char** strings = backtrace_symbols(buffer, nptrs);
  14. for (int32_t i = 0; i < nptrs; ++i) {
  15. std::cout << std::hex << buffer[i] << " " << strings[i] << std::endl;
  16. }
  17. InvokeDefaultSignalHandler(signal_number);
  18. }
  19. void InstallFailureSignalHandler() {
  20. struct sigaction sig_action;
  21. memset(&sig_action, 0, sizeof(sig_action));
  22. sigemptyset(&sig_action.sa_mask);
  23. sig_action.sa_flags |= SA_SIGINFO;
  24. sig_action.sa_sigaction = &FailureSignalHandler;
  25. sigaction(SIGSEGV, &sig_action, nullptr);
  26. sigaction(SIGILL, &sig_action, nullptr);
  27. sigaction(SIGFPE, &sig_action, nullptr);
  28. sigaction(SIGABRT, &sig_action, nullptr);
  29. sigaction(SIGBUS, &sig_action, nullptr);
  30. sigaction(SIGTERM, &sig_action, nullptr);
  31. }
  32. int main(int argc, char** argv) {
  33. InstallFailureSignalHandler();
  34. Test()
  35. }

编译完成后,运行结果如下:

  1. 0x55e8d271b309 ./backtrace_test(_Z20FailureSignalHandleriP9siginfo_tPv+0x65) [0x55e8d271b309]
  2. 0x7fe4c7455f10 /lib/x86_64-linux-gnu/libc.so.6(+0x3ef10) [0x7fe4c7455f10]
  3. 0x55e8d271992e ./backtrace_test(_Z4Testv+0x34) [0x55e8d271992e]
  4. 0x55e8d271b552 ./backtrace_test(main+0x19) [0x55e8d271b552]
  5. 0x7fe4c7438c87 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7fe4c7438c87]
  6. 0x55e8d271981a ./backtrace_test(_start+0x2a) [0x55e8d271981a]
  7. Segmentation fault (core dumped)

在上面堆栈中,可以看到堆栈对应的函数,但是看不到堆栈对应的行号,通过addr2line命令输出为??:0或??:?

  1. addr2line -e ./backtrace_test 0x55e8d271b309 -Cf
  2. ??
  3. ??:0
  4. addr2line -e /lib/x86_64-linux-gnu/libc.so.6 0x7fe4c7455f10 -Cf
  5. ??
  6. ??:0

这是因为:addr2line只能通过地址偏移量来查找,而打印出的地址是绝对地址。由于共享库加载到内存的位置是不确定的,为了计算地址偏移量,需要获取到进程的内存映射文件(/proc/pid/maps):

在上述程序基础上增加以下代码:

  1. struct LibraryBacktrace {
  2. void* offset_start;
  3. void* offset_end;
  4. char property[256];
  5. char not_care1[128];
  6. char not_care2[128];
  7. char not_care3[128];
  8. char library_path[256];
  9. };
  10. bool GetBacktraceLibrary(std::vector<LibraryBacktrace>* res) {
  11. FILE* fd_maps=NULL;
  12. fd_maps=fopen("/proc/self/maps","r");
  13. unsigned long exe_symbol_offset=0;
  14. char* unknow_position="??:0\n";
  15. if(fd_maps==NULL) {
  16. return -1;
  17. }
  18. char maps_line[1024];
  19. while(NULL!=fgets(maps_line,sizeof(maps_line),fd_maps)) {
  20. LibraryBacktrace d;
  21. sscanf(maps_line,"%p-%p\t%s\t%s\t%s\t%s\t%s"
  22. ,&d.offset_start
  23. ,&d.offset_end
  24. ,d.property
  25. ,d.not_care1
  26. ,d.not_care2
  27. ,d.not_care3
  28. ,d.library_path);
  29. std::cout << d.offset_start << ", " << d.offset_end << ", "
  30. << d.property << ", " << d.not_care1 << ", " << d.not_care2
  31. << ", " << d.not_care3 << ", " << d.library_path << std::endl;
  32. res->push_back(d);
  33. }
  34. fclose(fd_maps);
  35. }

通过上述接口获取到本进程的内存映射信息,然后遍历backtrace接口得到的调用堆栈地址(绝对地址),找到该地址在内存映射信息中的位置,从而可以的到该地址所在的动态库是哪个,以及该地址相对于该动态库起始地址的便宜量:

内存映射信息如下:

  1. 0x5644860d9000, 0x5644860eb000, r-xp, 00000000, 08:11, 108659273, /mnt/test/backtrace/build/backtrace_test
  2. 0x5644862eb000, 0x5644862ec000, r--p, 00012000, 08:11, 108659273, /mnt/test/backtrace/build/backtrace_test
  3. 0x5644862ec000, 0x5644862ed000, rw-p, 00013000, 08:11, 108659273, /mnt/test/backtrace/build/backtrace_test
  4. 0x564486ab6000, 0x564486ad7000, rw-p, 00000000, 00:00, 0, [heap]
  5. 0x7f94c4bce000, 0x7f94c4d6b000, r-xp, 00000000, 08:02, 14024800, /lib/x86_64-linux-gnu/libm-2.27.so
  6. 0x7f94c4d6b000, 0x7f94c4f6a000, ---p, 0019d000, 08:02, 14024800, /lib/x86_64-linux-gnu/libm-2.27.so
  7. 0x7f94c4f6a000, 0x7f94c4f6b000, r--p, 0019c000, 08:02, 14024800, /lib/x86_64-linux-gnu/libm-2.27.so
  8. 0x7f94c4f6b000, 0x7f94c4f6c000, rw-p, 0019d000, 08:02, 14024800, /lib/x86_64-linux-gnu/libm-2.27.so
  9. 0x7f94c4f6c000, 0x7f94c5153000, r-xp, 00000000, 08:02, 14024791, /lib/x86_64-linux-gnu/libc-2.27.so
  10. 0x7f94c5153000, 0x7f94c5353000, ---p, 001e7000, 08:02, 14024791, /lib/x86_64-linux-gnu/libc-2.27.so
  11. 0x7f94c5353000, 0x7f94c5357000, r--p, 001e7000, 08:02, 14024791, /lib/x86_64-linux-gnu/libc-2.27.so
  12. 0x7f94c5357000, 0x7f94c5359000, rw-p, 001eb000, 08:02, 14024791, /lib/x86_64-linux-gnu/libc-2.27.so
  13. 0x7f94c5359000, 0x7f94c535d000, rw-p, 00000000, 00:00, 0, /lib/x86_64-linux-gnu/libc-2.27.so
  14. 0x7f94c535d000, 0x7f94c54d6000, r-xp, 00000000, 08:02, 5776090, /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
  15. 0x7f94c54d6000, 0x7f94c56d6000, ---p, 00179000, 08:02, 5776090, /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
  16. 0x7f94c56d6000, 0x7f94c56e0000, r--p, 00179000, 08:02, 5776090, /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
  17. 0x7f94c56e0000, 0x7f94c56e2000, rw-p, 00183000, 08:02, 5776090, /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
  18. 0x7f94c56e2000, 0x7f94c56e6000, rw-p, 00000000, 00:00, 0, /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
  19. 0x7f94c56e6000, 0x7f94c56fd000, r-xp, 00000000, 08:02, 14029994, /lib/x86_64-linux-gnu/libgcc_s.so.1
  20. 0x7f94c56fd000, 0x7f94c58fc000, ---p, 00017000, 08:02, 14029994, /lib/x86_64-linux-gnu/libgcc_s.so.1
  21. 0x7f94c58fc000, 0x7f94c58fd000, r--p, 00016000, 08:02, 14029994, /lib/x86_64-linux-gnu/libgcc_s.so.1
  22. 0x7f94c58fd000, 0x7f94c58fe000, rw-p, 00017000, 08:02, 14029994, /lib/x86_64-linux-gnu/libgcc_s.so.1
  23. 0x7f94c58fe000, 0x7f94c5927000, r-xp, 00000000, 08:02, 14024720, /lib/x86_64-linux-gnu/ld-2.27.so
  24. 0x7f94c5af5000, 0x7f94c5af9000, rw-p, 00000000, 00:00, 0, /lib/x86_64-linux-gnu/ld-2.27.so
  25. 0x7f94c5b25000, 0x7f94c5b27000, rw-p, 00000000, 00:00, 0, /lib/x86_64-linux-gnu/ld-2.27.so
  26. 0x7f94c5b27000, 0x7f94c5b28000, r--p, 00029000, 08:02, 14024720, /lib/x86_64-linux-gnu/ld-2.27.so
  27. 0x7f94c5b28000, 0x7f94c5b29000, rw-p, 0002a000, 08:02, 14024720, /lib/x86_64-linux-gnu/ld-2.27.so
  28. 0x7f94c5b29000, 0x7f94c5b2a000, rw-p, 00000000, 00:00, 0, /lib/x86_64-linux-gnu/ld-2.27.so
  29. 0x7ffd64b68000, 0x7ffd64b89000, rw-p, 00000000, 00:00, 0, [stack]
  30. 0x7ffd64bb3000, 0x7ffd64bb6000, r--p, 00000000, 00:00, 0, [vvar]
  31. 0x7ffd64bb6000, 0x7ffd64bb8000, r-xp, 00000000, 00:00, 0, [vdso]
  32. 0xffffffffff600000, 0xffffffffff601000, --xp, 00000000, 00:00, 0, [vsyscall]

堆栈信息如下:

  1. 0x5644860e48f9 ./backtrace_test(_Z20FailureSignalHandleriP9siginfo_tPv+0x65) [0x5644860e48f9]
  2. 0x7f94c4faaf10 /lib/x86_64-linux-gnu/libc.so.6(+0x3ef10) [0x7f94c4faaf10]
  3. 0x5644860e2f1e ./backtrace_test(_Z4Testv+0x34) [0x5644860e2f1e]
  4. 0x5644860e4c68 ./backtrace_test(main+0x19) [0x5644860e4c68]
  5. 0x7f94c4f8dc87 /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f94c4f8dc87]
  6. 0x5644860e2e0a ./backtrace_test(_start+0x2a) [0x5644860e2e0a]

比如0x7f94c4faaf10 /lib/x86_64-linux-gnu/libc.so.6(+0x3ef10) [0x7f94c4faaf10]

在上述内存映射信息中找0x5644860e2f1e所在的位置为:

0x5644860d9000, 0x5644860eb000, r-xp, 00000000, 08:11, 108659273, /mnt/baidu/test/backtrace/build/backtrace_test

对应的动态库是:/mnt/test/backtrace/build/backtrace_test

该动态库的起始地址是:0x5644860d9000

得到偏移地址:0x5644860e2f1e - 0x5644860d9000 = 0x9F1E

再次使用addr2line命令addr2line -e /mnt/test/backtrace/build/backtrace_test 0x9F1E

结果为:/mnt/test/backtrace/backtrace_test_main.cc:15

  1. std::string DumpSymbol(std::vector<SymbolData>* vec) {
  2. std::vector<LibraryBacktrace> library_backtrace;
  3. GetBacktraceLibrary(&library_backtrace);
  4. std::map<std::string, void*> m;
  5. for (const auto& it : library_backtrace) {
  6. std::string library = std::string(it.library_path);
  7. auto d = m.find(library);
  8. if (d == m.end()) {
  9. m.insert(std::make_pair(library, it.offset_start));
  10. } else {
  11. if (d->second >= it.offset_start) {
  12. d->second = it.offset_start;
  13. }
  14. }
  15. }
  16. for (auto& it : *vec) {
  17. if (!it.func1.empty() || it.addr1.empty() || it.file.empty()) {
  18. it.func2 = "(" + it.func1 + "+" + it.addr1 + ")";
  19. it.line = "??:?";
  20. // continue;
  21. }
  22. void* bt = 0;
  23. sscanf(it.addr2.c_str(), "%p", &bt);
  24. std::string library = "";
  25. for (const auto& it : library_backtrace) {
  26. if (it.offset_start <= bt && it.offset_end >= bt) {
  27. library = it.library_path;
  28. break;
  29. }
  30. }
  31. uint64_t offset = 0;
  32. auto it3 = m.find(library);
  33. if (it3 == m.end()) {
  34. std::cout << "library not found error" << std::endl;
  35. } else {
  36. offset = (char*)bt - (char*)it3->second;
  37. }
  38. char addrstr[128] = {0};
  39. snprintf(addrstr, sizeof(addrstr), "0x%lx", offset);
  40. std::string cmd = "addr2line " + std::string(addrstr) + " -e " + library + " -f -C";
  41. std::cout << "++++ cmd: " << std::hex << it.addr2 << ": " << cmd << std::endl;
  42. FILE* p = popen(cmd.c_str(), "r");
  43. if (!p) {
  44. continue;
  45. }
  46. char buff[1024] = { 0 };
  47. int32_t res_line_count = 0;
  48. while (fgets(buff, sizeof(buff), p) != nullptr) {
  49. std::string str(buff);
  50. if (!str.empty() && str.back() == '\n') {
  51. str = str.substr(0, str.length() - 1);
  52. }
  53. if (res_line_count == 0) {
  54. it.func2 = str;
  55. } else if (res_line_count == 1) {
  56. it.line = str;
  57. }
  58. ++res_line_count;
  59. }
  60. pclose(p);
  61. }
  62. int32_t index = 0;
  63. std::stringstream ss;
  64. for (const auto& it : *vec) {
  65. ss << " #" << index << " " << it.addr2
  66. << " " << it.func2 << " " << it.line << std::endl;
  67. ++index;
  68. }
  69. return ss.str();
  70. }

参考文章

backtrace 实现原理

更多文章可关注公众号
aisoftcloud