c++ - 交叉编译时提升堆栈跟踪不消除名称
问题描述
为了生成我正在使用的堆栈跟踪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:\WINDOWS\System32\KERNEL32.DLL
6# register_frame_ctor in C:\WINDOWS\SYSTEM32\ntdll.dll
我也尝试遍历框架并boost::core::demangle
在它们上运行 a ,但它没有这样做。
到目前为止,我在 unix 上的编译环境和我的 windows 机器上的运行时环境之间只能看到一个区别。在 Windows 上我有g++
版本8.2.0
,在 unix 上是6.3.0
. 这会导致任何问题吗?交叉编译时,还有什么可能导致仅在 Windows 上进行拆解失败?
解决方案
TL/DR:libbacktrace 出于某种原因去除了前导下划线并错误地提升了调用backtrace_pcinfo
-> 将创建 libbacktrace 和 boost 的问题(在我的本地编译中修复)
更详细的回复:
通过创建 libbacktrace 的调试版本并单步执行代码,我能够找出奇怪的行为。在我看来,boost 和 libbacktrace 都有一个错误。
选择 libbacktrace 后端时 boost::stacktrace 如何工作的非常高级的解释:
- Call
backtrace_pcinfo
,这将读取(在第一次调用时)并搜索 DWARF 调试信息(至少是 mingw 实现) - 如果之前的调用成功,它将返回这里,因为它已经获得了足够多的信息(函数、文件、行)
- 如果它返回零,它将尝试调用
backtrace_syminfo
. 这将(对于 mingw)搜索 coff 符号表 - 如果它返回零,您将打印一个原始地址,否则至少是函数名(无文件/行)
我能够追溯到 libbacktrace(双关语)的第一个错误/意外行为。出于某种原因, gcc/pecoff.c#440中的实现会去除前导下划线(如果存在)。这就是函数名称都缺少前导下划线并且不能被分解的原因。我认为这可以被认为是一个错误,或者至少我没有看到任何其他原因,除了尝试漂亮的打印符号失败。
现在细心的读者可能从我的问题中记得我正在使用调试信息,因此它不需要进入 coff 符号表,而是应该使用 DWARF 调试信息。这就是 boost 的错误所在。
backtrace_pcinfo
必须用两个回调调用。第一个将接收解析的符号信息(如果有),第二个是错误回调。当一个符号被找到时,第一个回调被调用并且它的返回值从代码中backtrace_pcinfo
或者在代码中返回:
返回backtrace_pcinfo
:return state->fileline_fn (state, pc, callback, error_callback, data);
fileline_fn
对于 mingw 是矮人实现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
,现在一切都很好。
推荐阅读
- excel - Excel VBA For Loop 多次运行
- php - Laravel ViewComposer 使用的值应该存储在哪里
- python - 使用 Python 和 Beautifulsoup 抓取下一页
- junit - 如何对 mockito 的 verify 方法施加不受限制的调用次数?
- php - 不支持的文本语言:自然语言理解 API
- javascript - 如何为 jQuery/JS 函数编写 mocha/chai 测试用例
- python - 在循环期间将数字附加到某些列表
- r - R中函数内的并行化——避免将对象自动导出到集群
- javascript - Javascript,cookie点击器计数器未更新
- sql - 尝试使用两个内连接向 sql 添加新值