在装有dlopen的库中使用std :: thread会导致sigsev

时间:2018-07-06 11:19:18

标签: c++ glibc dlopen stdthread

我最近发现使用std::threaddlopen出现了奇怪的行为。

基本上,当我在使用std::thread加载的库中执行dlopen时,会收到一个sigsev。库本身与pthread链接,而调用dlopen的可执行文件则不是。

一旦我将可执行文件链接到pthread或库本身,一切正常。但是,我们使用的是基于插件的基础结构,我们不知道应用程序本身是否与pthread链接。因此,永远不能将可执行文件与pthread链接在一起。

请找到随附的一些代码来重现此问题。目前,我不确定导致此问题的原因。是gcc,glibc,libstdc ++或ld.so的问题吗?是否有解决此问题的便捷方法?我看起来与此glibc bug相关,但是我正在使用glibc2.27(debian测试)。

从库中调用pthread_create本身似乎可行。

hello.cpp

#include <thread>
#include <iostream>

void thread()
{
    std::thread t ([](){std::cout << "hello world" << std::endl;});
    t.join();
}

extern "C" {
    void hello()
    {
        thread();
    }
}

example.cpp

#include <iostream>
#include <dlfcn.h>

/** code from https://www.tldp.org/HOWTO/html_single/C++-dlopen/
*/
int main() {

    std::cout << "C++ dlopen demo\n\n";

    // open the library
    std::cout << "Opening hello.so...\n";
    void* handle = dlopen("./libhello.so", RTLD_LAZY);

    if (!handle) {
        std::cerr << "Cannot open library: " << dlerror() << '\n';
        return 1;
    }

    // load the symbol
    std::cout << "Loading symbol hello...\n";
    typedef void (*hello_t)();

    // reset errors
    dlerror();
    hello_t hello = (hello_t) dlsym(handle, "hello");
    const char *dlsym_error = dlerror();
    if (dlsym_error) {
        std::cerr << "Cannot load symbol 'hello': " << dlsym_error <<
            '\n';
        dlclose(handle);
        return 1;
    }

    // use it to do the calculation
    std::cout << "Calling hello...\n";
    hello();

    // close the library
    std::cout << "Closing library...\n";
    dlclose(handle);
}

build.sh(构建并执行上面的示例。示例1崩溃)

#!/bin/bash

echo "g++ -shared -fPIC -std=c++14 hello.cpp -o libhello.so -pthread"
g++ -shared -fPIC -std=c++14 hello.cpp -o libhello.so -pthread

echo "g++ example.cpp -o example1 -ldl"
g++ example.cpp -o example1 -ldl

echo "g++ example.cpp -o example2 -ldl -pthread"
g++ example.cpp -o example2 -ldl -pthread

echo "g++ example.cpp -o example3 -ldl -lhello -L ./"
g++ example.cpp -o example3 -ldl -lhello -L ./

export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(pwd)

echo "===== example1 ====="
./example1
echo "===== end      ====="

echo "===== example2 ====="
./example2
echo "===== end      ====="

echo "===== example3 ====="
./example3
echo "===== end      ====="

编辑

我忘了提:如果我使用LD_DEBUG=all运行错误的示例(即示例1),则在查找pthread_create时程序崩溃。更有趣的是,先前对pthread_create的查找成功了:

  8111:     symbol=_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_;  lookup in file=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0]
  8111:     binding file ./libhello.so [0] to /usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0]: normal symbol `_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_' [GLIBCXX_3.4]
  8111:     symbol=pthread_create;  lookup in file=./example1 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib/x86_64-linux-gnu/libdl.so.2 [0]
  8111:     symbol=pthread_create;  lookup in file=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib/x86_64-linux-gnu/libm.so.6 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib/x86_64-linux-gnu/libgcc_s.so.1 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib64/ld-linux-x86-64.so.2 [0]
  8111:     symbol=pthread_create;  lookup in file=./libhello.so [0]
  8111:     symbol=pthread_create;  lookup in file=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib/x86_64-linux-gnu/libm.so.6 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib/x86_64-linux-gnu/libgcc_s.so.1 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib/x86_64-linux-gnu/libpthread.so.0 [0]
  8111:     binding file ./libhello.so [0] to /lib/x86_64-linux-gnu/libpthread.so.0 [0]: normal symbol `pthread_create' [GLIBC_2.2.5]
  8111:     symbol=_ZTVNSt6thread6_StateE;  lookup in file=./example1 [0]
  8111:     symbol=_ZTVNSt6thread6_StateE;  lookup in file=/lib/x86_64-linux-gnu/libdl.so.2 [0]
  8111:     symbol=_ZTVNSt6thread6_StateE;  lookup in file=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0]
  8111:     binding file ./libhello.so [0] to /usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0]: normal symbol `_ZTVNSt6thread6_StateE' [GLIBCXX_3.4.22]
  ...
  8111:     binding file ./libhello.so [0] to ./libhello.so [0]: normal symbol `_ZNSt10_Head_baseILm0EPNSt6thread6_StateELb0EE7_M_headERS3_'
  8111:     symbol=_ZNSt6thread15_M_start_threadESt10unique_ptrINS_6_StateESt14default_deleteIS1_EEPFvvE;  lookup in file=./example1 [0]
  8111:     symbol=_ZNSt6thread15_M_start_threadESt10unique_ptrINS_6_StateESt14default_deleteIS1_EEPFvvE;  lookup in file=/lib/x86_64-linux-gnu/libdl.so.2 [0]
  8111:     symbol=_ZNSt6thread15_M_start_threadESt10unique_ptrINS_6_StateESt14default_deleteIS1_EEPFvvE;  lookup in file=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0]
  8111:     binding file ./libhello.so [0] to /usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0]: normal symbol `_ZNSt6thread15_M_start_threadESt10unique_ptrINS_6_StateESt14default_deleteIS1_EEPFvvE' [GLIBCXX_3.4.22]
  8111:     symbol=pthread_create;  lookup in file=./example1 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib/x86_64-linux-gnu/libdl.so.2 [0]
  8111:     symbol=pthread_create;  lookup in file=/usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib/x86_64-linux-gnu/libm.so.6 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib/x86_64-linux-gnu/libgcc_s.so.1 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
  8111:     symbol=pthread_create;  lookup in file=/lib64/ld-linux-x86-64.so.2 [0]
  ./build.sh: line 18:  8111 Segmentation fault      (core dumped) LD_DEBUG=all ./example1
  ===== end      =====

3 个答案:

答案 0 :(得分:3)

  

一旦我将可执行文件链接到pthread或库本身,一切都会正常运行。但是,我们使用的是基于插件的基础结构,我们不知道应用程序本身是否与pthread链接。因此,永远不能将可执行文件与pthread链接在一起。

相反:很少有系统支持某个应用程序突然变成“多线程”(您的系统显然不支持)。

如果您需要潜在地支持 多线程插件,则必须开始支持多线程,这是通过与libpthread链接或通过添加-pthread来实现的。标志以编译和链接主要可执行文件的行。

  

是gcc,glibc,libstdc ++或ld.so的问题

libstdc++存在问题-GLIBC确实支持“突然多线程”执行,GCC根本不是运行时环境的一部分,而ld.so是GLIBC的一部分。

答案 1 :(得分:3)

我可以提供一些背景信息,说明为什么存在段错误,但不幸的是没有解决办法。

这似乎是libstdc++的问题:从技术上讲,这个庞大的整体库依赖于libpthread,但是出于充分的原因,它们并不与libpthread链接。现在,为了能够从根本不使用线程的程序中加载libstdc++,缺少的符号(例如pthread_create)必须放在某个地方。因此libstdc++将它们定义为弱符号。

这些弱符号还用于在运行时检测libpthread是否实际加载。对于较旧的ABI,甚至有一个check in _M_start_thread,如果未加载pthread而不是调用弱定义的nullptr,则会引起有意义的异常-对于我最讨厌的敌人,我不希望这样做。

不幸的是,新ABI的运行时检查丢失了。相反,对于pthread_create,有一个link-time check是通过在编译调用_M_start_thread的代码时创建依赖项并将指向pthread_create的指针传递到此函数中来实现的。不幸的是,该指针被丢弃,而仍然使用弱的nullptr指针。

现在,在链接/加载过程中发生的某些情况导致在您遇到问题的情况下不会覆盖弱定义的pthread_create。我不确定适用的确切解析规则-我认为这与libstdc++加载时已经完全加载libpthread有关。如果有任何其他答案可以澄清这一点,我将感到高兴。不幸的是,除了将主应用程序与-lpthreadLD_PRELOAD=libpthread.so(我不建议这样做)链接之外,似乎没有其他可行的解决方案。

答案 2 :(得分:1)

问题出在libstdc ++。

  • 使用C程序不会发生这种情况。
  • 使用libc ++构建的C ++程序也不会发生这种情况。
  • 对于使用libstdc ++静态生成的C ++程序,静态也不会发生。
  • 对于使用libc ++构建的库,即使调用程序是使用libstdc ++动态构建的,也不会发生这种情况。
  • 当程序dl使用RTLD_GLOBAL打开库时,也不会发生这种情况。

因此一种解决方案是切换到libc ++。显然,只有在从不导出任何依赖于任何std::类型的接口的情况下,这才起作用。特别是,仅导出与C兼容的接口的库应该可以。

另一种解决方案是让您的库中加载RTLD_GLOBAL(您可能需要将其分成两个部分,一个是主库,另一个是一个仅存有RTLD_GLOBAL的小存根)。

同时,应该针对libstdc ++提交错误,然后等待修复。没有理由应该这样破坏它。

如果以上都不是可行的选择,那么唯一的解决方案似乎是在调用方和多线程模块之间实现完全隔离。使多线程模块成为一个独立的可执行文件,从您的插件派生执行该文件,并通过管道将参数/结果编组给它/从中提取结果。

最后,在调用程序中总是存在预加载libpthread的丑陋解决方法。