在装有dlopen的库中使用std :: thread会导致sigsev [英] using std::thread in a library loaded with dlopen leads to a sigsev
问题描述
我最近发现使用std::thread
和dlopen
的奇怪行为.
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++
有关.如果有任何其他答案可以澄清这一点,我将感到高兴.不幸的是,除了将主应用程序与-lpthread
或LD_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屋!