1 前言

当前生产环境下C++程序存在排错的问题,如果仅是业务异常导致出错,往往有日志可以提供详细信息,但如果是代码错误导致程序段错误崩溃,就没有太好的解决方法。

目前比较常见的方式有以下几种:

  1. 程序内部捕获段错误信号,当发生段错误时,打印backtrace信息
  2. 程序不捕获段错误信号,设置Coredump文件输出路径,将core文件导入开发环境排错
  3. 在2的基础上,生产环境同时部署-g的执行文件,当有core文件时使用debug版现场定位问题。

其中,第一种方法虽然能大致定位代码逻辑错误位置,但由于没有足够的堆栈数据,很难正式确认问题;第二种方式操作较为复杂,同时存在生产数据泄露到开发环境的风险;第三种方式目前相对而言最优,但会导致编译构建时间翻倍,同时发布包体积翻倍的问题。

近期学到一种新方法,主要在方案3的基础上改进——使用objcopy将符号导出。

2 objcopy将DEBUG符号摘出

objcopyGNU工具集中的一个,作用是从一个二进制文件中拷贝特定信息到另一个文件中。

利用它我们可以将debug版的程序中的符号表导出到程序外部,当需要调试的时候再将符号表导入二进制程序,进行调试。

在编译构建二进制目标时,需要编译选项增加-g符号,用于将符号表导入,此时gdb可以正常使用。

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")

然后使用下面的命令对符号表进行导出、移除和放入:

# 将cpp_test中的调试符号表导出到另一个文件cpp_test.dbg
objcopy --only-keep-debug ./cpp_test cpp_test.dbg
# 将cpp_test中的符号表删除
objcopy --strip-debug cpp_test
# 将cpp_test.dbg符号表导入到cpp_test中
objcopy --add-gnu-debuglink=cpp_test.dbg cpp_test

2.1 示例

我们先准备一个稳定触发段错误的代码:

#include <array>
#include <iostream>
using namespace std;
 
int main(int argc, char const *argv[])
{
    int *a = nullptr;
    cout << *a << endl;
    return 0;
}

对应编译选项

cmake_minimum_required(VERSION 3.5)
 
project(cpp_test)
 
add_executable(cpp_test main.cpp)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -g")

这个时候我们可以编译运行,得到:

wzq@WZQ-Laptop:~/cpp_test/build$ ./cpp_test 
Segmentation fault

我们可以通过gdb来运行

wzq@WZQ-Laptop:~/cpp_test/build$ gdb ./cpp_test
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.
 
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./cpp_test...
(gdb) r
Starting program: /home/wzq/cpp_test/build/cpp_test 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
 
Program received signal SIGSEGV, Segmentation fault.
main (argc=1, argv=0x7fffffffdd98) at /home/wzq/cpp_test/main.cpp:8
8           cout << *a << endl;
(gdb) 

接下来我们移除符号到cpp_test.dbg,再gdb运行:

wzq@WZQ-Laptop:~/cpp_test/build$ objcopy --only-keep-debug ./cpp_test cpp_test.dbg
wzq@WZQ-Laptop:~/cpp_test/build$ objcopy --strip-debug cpp_test
wzq@WZQ-Laptop:~/cpp_test/build$ gdb ./cpp_test
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.
 
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./cpp_test...
(No debugging symbols found in ./cpp_test)
(gdb) r
Starting program: /home/wzq/cpp_test/build/cpp_test 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
 
Program received signal SIGSEGV, Segmentation fault.
0x0000555555555084 in main ()
(gdb) bt
#0  0x0000555555555084 in main ()
(gdb) 

发现找不到符号了。

但当我们再将符号表导入,则又正常了。

wzq@WZQ-Laptop:~/cpp_test/build$ objcopy --add-gnu-debuglink=cpp_test.dbg cpp_test
wzq@WZQ-Laptop:~/cpp_test/build$ gdb ./cpp_test
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.
 
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./cpp_test...
Reading symbols from /home/wzq/cpp_test/build/cpp_test.dbg...
(gdb) r
Starting program: /home/wzq/cpp_test/build/cpp_test 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
 
Program received signal SIGSEGV, Segmentation fault.
main (argc=1, argv=0x7fffffffdd98) at /home/wzq/cpp_test/main.cpp:8
8           cout << *a << endl;
(gdb) 

后续在生产上只需要设置core文件的文件名和路径即可:core-设置core文件名和路径并手动触发coredump