【问题标题】:Boost stacktrace not demangling names when cross compiled交叉编译时提升堆栈跟踪不消除名称
【发布时间】:2019-06-17 10:03:29
【问题描述】:

为了生成堆栈跟踪,我使用了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,但它失败了。

到目前为止,我只能看到 unix 上的编译环境和我的 windows 机器上的运行时环境之间的一个区别。在 Windows 上,我有 g++ 版本 8.2.0,在 unix 上是 6.3.0。这会导致任何问题吗?交叉编译时,还有什么可能导致仅在 windows 上的去重失败?

【问题讨论】:

  • 我注意到,它似乎只删除了第一个_,例如mainCRTStartup 实际上被命名为_mainCRTStartup_tmainCRTStartup 将是__tmainCRTStartup。还 _ZN3foo3Bar6fooBarEv 应该被 GCC 拆解器在线正确拆解...

标签: c++ boost mingw


【解决方案1】:

TL/DR:libbacktrace 出于某种原因去除了前导下划线并错误地提升了调用 backtrace_pcinfo -> 将创建 libbacktrace 和 boost 的问题(在我的本地编译中修复)

更详细的回复:

通过创建 libbacktrace 的调试版本并单步执行代码,我能够找出奇怪的行为。在我看来,boost 和 libbacktrace 都存在错误。

非常高的级别解释如何提升:: sacktrace在选择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-&gt;fileline_fn (state, pc, callback, error_callback, data);

fileline_fn for mingw 是 dwarf 实现 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-&gt;name);

现在让我们看看 boost 提供的回调是怎样的:

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,现在一切正常。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-01
    • 2011-01-03
    • 1970-01-01
    • 2020-01-05
    • 1970-01-01
    • 2012-07-18
    • 2021-09-07
    相关资源
    最近更新 更多