交叉编译时,Boost Stacktrace不会取消名称分解

时间:2019-01-23 18:35:29

标签: c++ boost mingw

为了生成堆栈跟踪,我正在使用boost::stacktrace。我在Debian上有一个构建代理,该代理正在编译该程序的unix和Windows版本。 Unix版本使用已安装的本地g++进行编译,而Windows交叉编译使用mingw-w64创建。我在两个编译中都使用libbacktrace后端。 boost和libbacktrace本身都使用相同的mingw-w64编译器在Debian机器上进行编译。

在我的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();
}

在我的计算机(基本OS)上进行编译以及从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上进行交叉编译时,分解操作才能失败?

1 个答案:

答案 0 :(得分:0)

TL / DR:出于某些原因,libbacktrace会删除下划线并引起不正确的调用backtrace_pcinfo->将创建libbacktrace和boost的问题(已在我的本地版本中修复)

更详细的答复:

通过创建libbacktrace的调试版本并逐步执行代码,我能够弄清怪异的行为。在我看来,boost和libbacktrace都有一个错误。

非常高级的解释,说明了在选择libbacktrace后端时boost :: stacktrace是如何工作的:

  • 调用backtrace_pcinfo,它将读取(在第一次调用时)并搜索DWARF调试信息(至少是mingw实现)
  • 如果上一次呼叫成功,它将返回此处,因为它将获得足够多的信息(函数,文件,行)
  • 如果返回零,它将尝试调用backtrace_syminfo。这将(对于mingw)搜索coff符号表
  • 如果返回零,则将打印原始地址,否则至少显示函数名称(无文件/行)

我能够追溯到libbacktrace(双关语意)的第一个错误/意外行为。由于某些原因,gcc/pecoff.c#440中的实现会删除前划线(如果有的话)。这就是函数名称全都缺少前导下划线且无法取消修饰的原因。我认为这可以被认为是一个错误,或者至少我没有看到除了尝试打印漂亮的符号失败以外的其他原因。

现在,细心的读者可能会从我的问题中记住我正在使用调试信息,因此它不必进入coff符号表,而应使用DWARF调试信息。这就是带有boost的bug所在的地方。

backtrace_pcinfo必须通过两个回调来调用。第一个将接收解析的符号信息(如果有),第二个将返回错误回调。找到符号后,将调用第一个回调,并从backtrace_pcinfo或代码中返回其返回值:

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