在共享库中使用 std::thread 会导致 SIGSEGV

using std::thread in shared library causes SIGSEGV

我刚从 Windows 转入 Linux c++ 编程。尝试制作共享库 libso.so,它使用 std::thread。共享库会被其他人加载并调用导出函数。测试代码:

// so.cpp, the .so library

#include <iostream>
#include <thread>
using namespace std;

extern "C" 
void run() {
    cout << "run() begin" << endl;

    std::thread t([] {
    });
    t.join();

    cout << "run() end" << endl;
}

// test.cpp, the test loader

#include <dlfcn.h>

int main() {
    typedef void (*run_t)();

    auto dll = dlopen("libso.so", RTLD_LAZY);

    run_t run = (run_t) dlsym(dll, "run");

    run();
}

// The CMakeLists.txt file 

cmake_minimum_required(VERSION 3.0)
PROJECT (test)

Include_Directories(${PROJECT_SOURCE_DIR})

Link_Directories(${PROJECT_BINARY_DIR})

add_library(so SHARED so.cpp )
target_link_libraries(so pthread)

add_executable( test test.cpp )
target_link_libraries(test pthread dl)

run()函数中崩溃,输出为:

run() begin
“./test” terminated by signal SIGSEGV (Address boundary error)

std::thread 在可执行文件中似乎工作正常,但在共享库中却不行。我想念什么?

环境:g++9.3.0,cmake 3.16.3

已编辑:

试试 ldd.

ldd ./test 显示没有 pthread,但 ldd ./libso.solibpthread.so.0。 链接参数与SET(CMAKE_VERBOSE_MAKEFILE TRUE)

的区别
// linking executable 'test'
/usr/bin/c++    -rdynamic CMakeFiles/test.dir/test.cpp.o  -o test   -L/e/c/1/kali  -Wl,-rpath,/e/c/1/kali -ldl -lpthread

// linking library 'libso.so'
/usr/bin/c++ -fPIC   -shared -Wl,-soname,libso.so -o libso.so CMakeFiles/so.dir/so.cpp.o   -L/e/c/2/kali  -Wl,-rpath,/e/c/1/kali -lpthread

唯一的区别是 -fPIC,我搜索并添加 set_property(TARGET test PROPERTY POSITION_INDEPENDENT_CODE ON) 到可执行文件,但没有任何改变。

解决方法 1

由于.so有libpthread.so.0,我尝试将.so中的代码添加到可执行文件中:

int main() {
    std::thread t([]{}); // to make executable linking to `pthread`
    t.join();

    // ... load dll and call run function
}

它起作用了,现在 ldd ./test 显示 libpthread.so.0 并且没有崩溃。这意味着:如果共享库使用 std::thread 并且可执行文件想要加载它,则可执行文件本身也必须使用 std::thread.

解决方法 2

std::thread 在可执行文件中运行良好,但在共享库中崩溃。发现一些 walkaround 使用 boost::thread 而不是 std::thread 并链接到 boost_thread 库,没有崩溃。

我猜这个问题与动态链接更相关 比线程。

呼叫dlopen("libso.so", RTLD_LAZY)将尝试 在标准位置找到图书馆。
除非你设置 LD_LIBRARY_PATH 环境 变量为包含 . 的内容(当前 目录)将找不到该库。

对于简单的测试,您可以:

  • 之前在终端使用export LD_LIBRARY_PATH=. 启动您的程序,
  • 在您的源代码中使用 dlopen("./libso.so", RTLD_LAZY)

在使用 dlopen()dlsym() 之后,如果您获得一个 null 指针,那么 dlerror() 可以帮助显示原因 的失败。

注意 Windows 当前目录和可执行文件 path 是动态库的标准搜索路径;在 UNIX 上 事实并非如此,这在改变时可能会令人惊讶 目标平台。


编辑

cmake 使用 -Wl,-rpath 选项硬编码库搜索 可执行文件中的路径,所以我上面解释的所有内容都变成了 对这个问题没用。

假设找到了动态库,只有这样我才能重现 崩溃是 忘记 pthread in target_link_libraries for test.


第二次编辑

我终于设法用 Ubuntu 重现了崩溃(在 WSL 中)。
显然你的链接器决定忽略那些是 不直接被可执行文件使用。
此行为表明链接器选项 --as-needed 是 默认开启。
为了与这种默认行为相矛盾,您需要传递链接器 选项 --no-as-needed 之前 -lpthread.
这样,您就不必在您的 可执行文件。
CMakeLists.txt 中使用 set(CMAKE_CXX_FLAGS -Wl,--no-as-needed) 你提供的文件对我有用。