在装有dlopen的库中使用std :: thread会导致sigsev [英] using std::thread in a library loaded with dlopen leads to a sigsev

查看:244
本文介绍了在装有dlopen的库中使用std :: thread会导致sigsev的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近发现使用std::threaddlopen的奇怪行为.

I recently discovered a strange behaviour using std::thread and dlopen.

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

Basically, when I execute a std::thread in a library which is loaded using dlopen I receive a sigsev. The library itself is linked against pthread, the executable that calls dlopen is not.

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

Once I link the executable against pthread or the library itself everything works fine. However, we are using a plugin based infrastructure, where we do not know if the application itself is linked against pthread or not. Therefore, it is not an option to link the executable always against pthread.

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

Please find attached some code to reproduce the Issue. Currently I am not sure what causes the Issue. Is it a problem of gcc, glibc, libstdc++, or the ld.so? Is there a convenient way to work around this? I looks like this glibc bug is related, but I am using glibc2.27 (debian testing).

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

Calling pthread_create itself from the library seems to work.

#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的查找成功了:

EDIT

I forgot to mention: If I am running the faulty example (i.e. example 1) using LD_DEBUG=all the program crashes during the lookup of pthread_create. Even more interesting is that a former lookup of pthread_create succeeds:

  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      =====

推荐答案

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

I can offer some background as to why there is a segfault, but unfortunately no solution.

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

It seems that this is an issue with libstdc++: Technically this huge monolithic library depends on libpthread, but for good reasons, they do not link against libpthread. Now in order to be able to load libstdc++ from programs that don't use threads at all, the missing symbols (e.g. pthread_create) must come somewhere. So libstdc++ defines them as weak symbols.

这些弱符号还用于在运行时检测是否实际加载了libpthread.对于旧的ABI甚至还有一个

These weak symbols are also used to detect at runtime whether libpthread is actually loaded. For an old ABI there even was a check in _M_start_thread which caused a meaningful exception if pthread was not loaded instead of calling a weakly defined nullptr - something I would not wish upon my worst enemey.

不幸的是,新ABI的运行时检查丢失了.而是有一个_M_start_thread的代码时创建依赖项,并将指向pthread_create的指针传递给此函数,从而为pthread_create进行链接时检查.不幸的是,该指针被丢弃,而仍然使用弱的nullptr指针.

Unfortunately that run-time check got lost for the new ABI. Instead, there is a link-time check for pthread_create by creating a dependency when compiling the code which calls _M_start_thread, and passing a pointer to pthread_create into this function. Unfortunately that pointer is discarded and the still weakly nullptr pointer is used.

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

Now something during the linking/loading causes the weakly defined pthread_create to not be overridden in your problematic case. I am unsure about the exact resolution rules that apply there - I assume it has to do with libstdc++ being already fully loaded when libpthread is being loaded. I would be glad if any additional answer would clarify that. Unfortunately there also seems to be no generally viable option to fix that other than linking the main application with -lpthread or LD_PRELOAD=libpthread.so (which I wouldn't really recommend).

这篇关于在装有dlopen的库中使用std :: thread会导致sigsev的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆