3

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 .

aj3423
  • 2,003
  • 3
  • 32
  • 70
  • May be you should test whether `dll` or `run` is not null before using it? – prog-fh Apr 17 '20 at 18:24
  • They are not null, I removed the error check to simplify the code, the output `run() begin` comfirms function `run` called correctly. – aj3423 Apr 17 '20 at 19:57
  • Off: I think it would work if you used `C` and `pthreads`. (Of course you would have to build both the main program and the plugin with `-pthread` option.) – Lorinczy Zsigmond Apr 17 '20 at 20:36
  • @aj3423 I think I have found the cause of this weird behaviour, and a fix in the build process to prevent it from happening. (see the second edit in my answer). – prog-fh Apr 18 '20 at 19:10

1 Answers1

3

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.

prog-fh
  • 13,492
  • 1
  • 15
  • 30
  • I tried both `export LD_LIBRARY_PATH=. ` and `dlopen("./libso.so", RTLD_LAZY)`, still crashes. – aj3423 Apr 17 '20 at 20:04
  • @aj3423 my bad, see the edit, sorry to have been so misleading :-( . But your example works perfectly for me, I cannot reproduce the crash without modifying `CMakeLists.txt`. (g++ 9.3.0, cmake 3.17.1) – prog-fh Apr 17 '20 at 20:47
  • The `boost::thread` works, maybe the implement of `std::thread` and `boost::thread` is different. Post updated. – aj3423 Apr 18 '20 at 06:55
  • 1
    @aj3423 and did you try to link your executable against `rt` *before* `pthread` as mentioned in the link of your update? Even without cmake (a simple makefile to enable a better control of the build options) the only way to reproduce the crash (I tried on different linux systems) is to *forget* to link the executable against `pthread`. When you run `ldd ./test` can you see that `libpthread.so` is actually found? – prog-fh Apr 18 '20 at 09:18
  • I tried the `rt` before `pthread`, and after, and before+after, still crash. I added the detailed linking param and `ldd` output to the post. – aj3423 Apr 18 '20 at 16:46
  • cmake uses `/usr/bin/c++`, which is just a symbol link to `/usr/bin/g++`, `g++ --version` is *g++ (Debian 9.3.0-10) 9.3.0*. I'm running on Kali linux with everything up to date. And the `std::this_thread::get_id();` also crashes, there must be an instance of `std::thread`, otherwise it will crash. – aj3423 Apr 18 '20 at 17:47
  • That's it, the default `--as-need` causes the problem. With `target_link_libraries(test dl "-Wl,--no-as-needed" pthread "-Wl,--as-needed")` in cmake, now works fine. Thank you. – aj3423 Apr 19 '20 at 06:12