aj3423
aj3423

Reputation: 2571

using std::thread in shared library causes SIGSEGV

I just came to Linux c++ programming from Windows. Trying to make a shared library libso.so, which uses std::thread. The shared library will be loaded by other people and call the export function. The test code:

// 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)

It crashes in the run() function, the output is:

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

The std::thread seems work fine in executable, but not in shared library. What do I miss?

The environment: g++ 9.3.0, cmake 3.16.3

Edited:

Try ldd.

ldd ./test shows no pthread, but ldd ./libso.so has libpthread.so.0. The difference of linking param with 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

The only difference is -fPIC, I googled and add set_property(TARGET test PROPERTY POSITION_INDEPENDENT_CODE ON) to the executable, but nothing changed.

Workaround 1

Since the .so has libpthread.so.0, I tried to add the code in .so to the executable:

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

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

And it works, now the ldd ./test shows libpthread.so.0 and no crash. Which means: if a shared library uses std::thread and an executable wants to load it, the executable itself must also use std::thread.

Workaround 2:

The std::thread works fine in executable, but crashes in shared library. Found some related discuss, the walkaround is using boost::thread instead of std::thread and linking to boost_thread library, no crash .

Upvotes: 3

Views: 728

Answers (1)

prog-fh
prog-fh

Reputation: 16925

I guess the problem is more related to dynamic linking than threads.

The call dlopen("libso.so", RTLD_LAZY) will try to find the library in a standard location.
Except if you set the LD_LIBRARY_PATH environment variable to something that includes . (the current directory) this library won't be found.

For a simple test you can either:

  • use export LD_LIBRARY_PATH=. in the terminal before launching your program,
  • use dlopen("./libso.so", RTLD_LAZY) in your source code.

After using dlopen() or dlsym() if you obtain a null pointer, then dlerror() can help displaying the reason of the failure.

Note that on Windows the current directory and the executable path are standard search paths for dynamic libraries; on UNIX this is not the case, which could be surprising when changing the target platform.


edit

cmake uses the -Wl,-rpath option to hardcode a library search path in the executable, so all of what I explained above becomes useless for this problem.

Assuming the dynamic library is found, the only way I can reproduce the crash is to forget pthread in target_link_libraries for test.


second edit

I finally managed to reproduce the crash with Ubuntu (in WSL).
Apparently your linker decides to ignore the libraries that are not directly used by the executable.
This behavior suggests that the linker option --as-needed is switched on by default.
To contradict this default behaviour, you need to pass the linker option --no-as-needed before -lpthread.
This way, you don't have to insert a dummy thread in your executable.
Using set(CMAKE_CXX_FLAGS -Wl,--no-as-needed) in the CMakeLists.txt file you provide did the trick for me.

Upvotes: 3

Related Questions