解决backward-cpp交叉编译时无法打印文件名、行号的问题
2026-01-16 23:59:40

程序有时候会崩溃退出,完全不知道是哪里崩的,一点信息都没有。于是找到了 backward-cpp 这个项目,它使用非常简单,跨平台支持的不错。

但是我在Linux下使用时无法打印源文件名,折腾了好几个小时才找到原因:因为我是在Windows下交叉编译的,而backward-cpp的cmake配置中会自动检测dwdwarfbfd,显然在Windows下是没有这些库的,于是崩溃时只能打印内存地址。

1
2
3
4
5
6
7
Stack trace (most recent call last):
#4 Object "[0xffffffffffffffff]", at 0xffffffffffffffff, in
#3 Object "./DemoServer", at 0x415838, in
#2 Object "/lib/x86_64-linux-gnu/libc.so.6", at 0x7f4eefe5483f, in __libc_start_main
#1 Object "./DemoServer", at 0x41576f, in
#0 Object "./DemoServer", at 0x415f13, in
Segmentation fault (Address not mapped to object [(nil)])

#方案一:在Windows下连接libdwarf, libelf, libdl等库

在Linux下安装相关的库,然后将头文件和库文件拷贝到Windows上,再到CMake中链接也是可以的,但这会陷入依赖地狱,非常折腾。

#方案二:用addr2line

仅依赖addr2line命令,这样在Windows上编译时就是零依赖,只需要修改下backward的源码即可。

首先,不再用库自带的CMake配置了,直接下载backward.hpp引入到项目中。
然后写个简单的target:

1
2
3
4
5
6
7
8
9
10
11
set(TARGET_NAME backward)

add_library(${TARGET_NAME} INTERFACE)

target_include_directories(${TARGET_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

target_compile_definitions(${TARGET_NAME} INTERFACE
BACKWARD_HAS_DW=0
BACKWARD_HAS_BFD=0
BACKWARD_HAS_DWARF=0
)

修改TraceResolverLinuxImpl<trace_resolver_tag::backtrace_symbol>::resolve方法。
之前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ResolvedTrace resolve(ResolvedTrace trace) override {
char *filename = _symbols[trace.idx];
char *funcname = filename;
while (*funcname && *funcname != '(') {
funcname += 1;
}
trace.object_filename.assign(filename,
funcname); // ok even if funcname is the ending
// \0 (then we assign entire string)

if (*funcname) { // if it's not end of string (e.g. from last frame ip==0)
funcname += 1;
char *funcname_end = funcname;
while (*funcname_end && *funcname_end != ')' && *funcname_end != '+') {
funcname_end += 1;
}
*funcname_end = '\0';
trace.object_function = this->demangle(funcname);
trace.source.function = trace.object_function; // we cannot do better.
}
return trace;
}

修改后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
ResolvedTrace resolve(ResolvedTrace trace) override {
char cmd[1024];
char self_path[PATH_MAX] = {0};

ssize_t len = ::readlink("/proc/self/exe", self_path, sizeof(self_path) - 1);

if (len > 0) {
self_path[len] = '\0';
} else {
FILE* f = fopen("/proc/self/cmdline", "rb");
if (f) {
size_t n = fread(self_path, 1, sizeof(self_path) - 1, f);
if (n > 0) {
self_path[n] = '\0';
}
fclose(f);
}
}

if (self_path[0] == 0) {
if (_symbols) {
char *filename = _symbols[trace.idx];
trace.object_filename = filename ? filename : "unknown";
}
return trace;
}

snprintf(cmd, sizeof(cmd), "/usr/bin/addr2line -e \"%s\" -f -C 0x%lx", self_path, (unsigned long)trace.addr);

FILE* fp = popen(cmd, "r");
if (fp) {
char func_buf[1024] = {0};
char file_buf[1024] = {0};

char* line1 = fgets(func_buf, sizeof(func_buf), fp);
char* line2 = fgets(file_buf, sizeof(file_buf), fp);

if (line1 && line2) {
size_t l = strlen(func_buf);
if (l > 0 && func_buf[l-1] == '\n') func_buf[l-1] = 0;

l = strlen(file_buf);
if (l > 0 && file_buf[l-1] == '\n') file_buf[l-1] = 0;

trace.object_function = func_buf;
trace.source.function = func_buf;

std::string file_line = file_buf;
size_t colon_pos = file_line.find_last_of(':');
if (colon_pos != std::string::npos) {
trace.source.filename = file_line.substr(0, colon_pos);
try {
trace.source.line = std::stoi(file_line.substr(colon_pos + 1));
} catch (...) { trace.source.line = 0; }
} else {
trace.source.filename = file_line;
}
}
pclose(fp);
return trace;
}

if (_symbols) {
char *filename = _symbols[trace.idx];
trace.object_filename = filename ? filename : "unknown";
}
return trace;
}

再次测试:

1
2
3
4
5
6
7
8
Stack trace (most recent call last):
#4 Source "??", line 0, in ?? [0xffffffffffffffff]
#3 Source "/opt/glibc-2.23/csu/../sysdeps/x86_64/start.S", line 118, in _start [0x415978]
#2 Source "??", line 0, in ?? [0x7f3c4eddc082]
#1 Source "C:\work\code\MyApp\cmake-build-relwithdebinfo-linux-x86_64/C:/work/code/MyApp/src/Demo/Server/src/main.cpp", line 117, in main [0x4158af]
#0 Source "C:\work\code\MyApp\cmake-build-relwithdebinfo-linux-x86_64/C:/work/code/MyApp/src/Demo/Server/src/main.cpp", line 101, in cause_crash() [0x416053]
Segmentation fault (Address not mapped to object [(nil)])
Segmentation fault (core dumped)

可以看到源文件名和行号都能打印了,只是这个路径还是Windows下的,这个可以用-fdebug-prefix-map编译选项来优化一下

1
add_compile_options("-fdebug-prefix-map=${CMAKE_SOURCE_DIR}=")

这样就变成相对路径了,完美了

1
2
3
4
5
6
7
8
Stack trace (most recent call last):
#4 Source "??", line 0, in ?? [0xffffffffffffffff]
#3 Source "/opt/glibc-2.23/csu/../sysdeps/x86_64/start.S", line 118, in _start [0x415978]
#2 Source "??", line 0, in ?? [0x7fb465c5d082]
#1 Source "/src/Demo/Server/src/main.cpp", line 117, in main [0x4158af]
#0 Source "/src/Demo/Server/src/main.cpp", line 101, in cause_crash() [0x416053]
Segmentation fault (Address not mapped to object [(nil)])
Segmentation fault (core dumped)