为了生成堆栈跟踪,我使用boost::stacktrace
。我在Debian上有一个构建代理,它正在编译unix和windows版本的程序。Unix版本使用安装的本机g++
进行编译,windows交叉编译使用mingw-w64
创建。我正在使用libbacktrace
后端进行两次编译。boost和libbacktrace本身都是在Debian机器上使用相同的mingw-w64编译器编译的。
在我的CMakeLists.txt
中,我指定:add_definitions(-DBOOST_STACKTRACE_USE_BACKTRACE)
堆栈跟踪生成如下:
namespace foo {
class Bar {
public:
void fooBar() {
std::cout << boost::stacktrace::stacktrace() << std::endl;
}
};
}
int main(int argc, char *argv[]) {
foo::Bar bar;
bar.fooBar();
}
当在我的计算机(基本操作系统)上编译和从我的unix机器上的构建服务器下载时,这会产生以下输出(带有-g,没有优化)。
0# foo::Bar::fooBar() in /home/cromon/CLionProjects/test-proj/cmake-build-debug/test-proj
1# main at /home/cromon/CLionProjects/test-proj/main.cpp:31
2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
3# _start in /home/cromon/CLionProjects/test-proj/cmake-build-debug/test-proj
这是在我的unix机器上的构建服务器上创建的二进制文件的输出:
0# foo::Bar::fooBar() in ./test-proj
1# main at /opt/teamcity/2018.2/TeamCity/buildAgent/work/d79789e141c5605f/test-proj/main.cpp:31
2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
3# _start in ./test-proj
现在,如果我在Windows机器上使用在Unix上使用mingw编译的二进制文件,则可以观察到以下输出
0# ZN5boost10stacktrace16basic_stacktraceISaINS0_5frameEEE4initEyy at /opt/teamcity/boost/1_69/windows/include/boost-1_69/boost/stacktrace/stacktrace.hpp:75
1# ZN3foo3Bar6fooBarEv at /opt/teamcity/2018.2/TeamCity/buildAgent/work/eb975d0a928ba129/test_proj/main.cpp:22
2# main at /opt/teamcity/2018.2/TeamCity/buildAgent/work/eb975d0a928ba129/test_proj/main.cpp:31
3# _tmainCRTStartup at ./mingw-w64-crt/crt/crtexe.c:336
4# mainCRTStartup at ./mingw-w64-crt/crt/crtexe.c:214
5# register_frame_ctor in C:WINDOWSSystem32KERNEL32.DLL
6# register_frame_ctor in C:WINDOWSSYSTEM32ntdll.dll
我也尝试过循环浏览这些帧,并在它们上运行boost::core::demangle
,但没有成功
到目前为止,我只能看到unix上的编译环境和windows机器上的运行时环境之间的一个区别。在Windows上,我有8.2.0
版本的g++
,在unix上是6.3.0
。这会造成什么问题吗?当交叉编译时,还有什么可能导致仅在windows上进行解映射失败?
TL/DR:libbacktrace由于某些原因剥离了前导下划线和boost,并不正确地调用backtrace_pcinfo
->将创建libbacktrack和boost的问题(在我的本地汇编中修复)
更详细的回应:
通过创建libbacktrace的调试构建并逐步执行代码,我能够弄清楚这种奇怪的行为。在我看来,boost和libbacktrace都有一个bug。
非常高级的解释boost::stacktrace在选择libbacktrace后端时如何工作:
- 调用
backtrace_pcinfo
,这将读取(在第一次调用时)并搜索DWARF调试信息(至少是mingw实现) - 如果上一次调用成功,它将返回此处,因为它将获得足够多的信息(函数、文件、行)
- 如果它返回零,它将尝试调用
backtrace_syminfo
。这将(针对mingw)搜索coff符号表 - 如果它返回零,您将打印一个原始地址,否则至少打印函数名(没有文件/行)
我能够追溯到libbacktrace的第一个错误/意外行为(双关语)。由于某种原因,gcc/pecoff.c#440中的实现去掉了前导下划线(如果存在)。这就是为什么函数名都缺少前导下划线,并且无法进行解映射的原因。我认为这可以被认为是一个错误,或者至少我看不出除了尝试漂亮的打印符号失败之外的任何其他原因。
现在,细心的读者可能还记得我的问题,我正在使用调试信息,所以它不需要进入coff符号表,而是应该使用DWARF调试信息。这就是boost的bug所在的地方。
必须使用两个回调来调用backtrace_pcinfo
。第一个将接收解析的符号信息(如果有的话),第二个是错误回调。当找到一个符号时,调用第一个回调,并从backtrace_pcinfo
或代码中返回其返回值:
返回backtrace_pcinfo
:return state->fileline_fn (state, pc, callback, error_callback, data);
mingw的fileline_fn
是侏儒实现dwarf_fileline
,返回如下:
ret = dwarf_lookup_pc (state, ddata, pc, callback, error_callback,
data, &found);
if (ret != 0 || found)
return ret;
dwarf_lookup_pc
现在返回找到符号时回调返回的内容:return callback (data, pc, filename, lineno, function->name);
现在,让我们看看回调看起来如何提升:
inline int libbacktrace_full_callback(void *data, uintptr_t /*pc*/, const char *filename, int lineno, const char *function) {
pc_data& d = *static_cast<pc_data*>(data);
if (d.filename && filename) {
*d.filename = filename;
}
if (d.function && function) {
*d.function = function;
}
d.line = lineno;
return 0;
}
由于返回值直接传播到backtrace_pcinfo
,这意味着无论是否发现任何内容,此检查都将始终进入or
情况:
::backtrace_pcinfo(
state,
reinterpret_cast<uintptr_t>(addr),
boost::stacktrace::detail::libbacktrace_full_callback,
boost::stacktrace::detail::libbacktrace_error_callback,
&data
)
||
::backtrace_syminfo(
state,
reinterpret_cast<uintptr_t>(addr),
boost::stacktrace::detail::libbacktrace_syminfo_callback,
boost::stacktrace::detail::libbacktrace_error_callback,
&data
);
这意味着它将始终使用以某种方式剥离领先空间的实现。我把回调改为返回1
,现在一切都好了。