1

I step into this problem accidentally.

// foo.cpp
#include <iostream>

int i;

extern "C" {
    void pn() {
        std::cout << "Hello!" << std::endl;
    }
}

I compiled foo.cpp into foo.so using gcc:

gcc -fPIC --shared foo.cpp -o libfoo.so

Then there is bar.cpp.

// bar.cpp
#include <dlfcn.h>
#include <stdio.h>
#include <iostream>

using namespace std;

extern "C" {
    int decode();
}

int decode() {
    void *handle = dlopen("libfoo.so", RTLD_LAZY);
    if (!handle) {
        printf("%s \n", dlerror());
        return 1;
    }
    void (*pn)() = (void (*)()) dlsym(handle, "pn");
    (*pn)();
    return 0;
}

bar.cpp was compiled into libbar.so, using g++.

g++ -fPIC --shared -L. -Wl,-R. bar.cpp -lfoo -ldl -o libbar.so

decode is called in main.cpp.

// main.cpp
#include <dlfcn.h>
#include <stdio.h>

// Magic sauce
//#include <iostream>
//using namespace std;

int main() {
    void *handle = dlopen("libbar.so", RTLD_LAZY);
    if (!handle) {
        printf("%s \n", dlerror());
        return 1;
    }
    int (*pn)() = (int (*)()) dlsym(handle, "decode");
    (*pn)();
    return 0;
}

main.cpp is compiled into executable main.

 g++ -L. -Wl,-R. main.cpp -lbar -ldl -lstdc++ -o main

I used ldd to check libfoo's dependencies.

linux-vdso.so.1 =>  (0x00007ffd5b75e000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2d9677a000)
/lib64/ld-linux-x86-64.so.2 (0x0000563d0c838000)

If I use iostream in main.cpp, which cause main to depend on libstdc++.so. Then this main runs without any problem. But if main does not contain that c++ usage, the executable breaks.

./libfoo.so: undefined symbol: _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_

I have been struggled for three days. Wound appreciate any help.

I know that compile foo.cpp using g++ is the righteous choice. I'm just trying to understand what's happening here.

Jeff Wen
  • 43
  • 1
  • 7

3 Answers3

2

It's failing when you do not use iostream because the main file your compiler sees does not depend on libstdc++, but foo.cpp/foo.so does depend on libstdc++.

You may wonder why a mere #include <iostream> fixes the problem. That's because iostream actually does some work simply by being included. For example it sets up std::cout. So while you might think the program code hasn't changed, it actually has--there is static initialization added by the #include which depends on libstdc++.

A general solution for this type of problem (indirect dependencies the compiler can't see) is here: Force to link against unused shared library

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • So, the `libfoo.so` share dependencies with main as a whole process image. Am I right? – Jeff Wen Jan 18 '18 at 12:43
  • @JeffWen: Yes. The main program (the one you link) loads whatever its known dependencies are. Then those dependencies are available to the shared object code which is loaded via `dlsym()`. Since `foo.so` doesn't statically depend on `libstdc++.so`, you need to make sure you load the latter before the former. – John Zwinck Jan 18 '18 at 13:11
  • Can u check my update. I think they're difference on dependency sharing between dynamic loading and dynamic linking. – Jeff Wen Jan 18 '18 at 13:35
  • 1
    @JeffWen: You've suddenly forked your question into two questions. That's not fair. Post a new question with the code stripped down to not use `dlsym()` if you have a problem with non-dlsym code. – John Zwinck Jan 18 '18 at 13:42
  • Ok then. But even when `main` is not using any c++ symbol, there's still `libbar.so` which depends on `libstdc++`. When the process starts, `libbar.so` and *it's dependencies* are supported to be loaded. So why is it not working if all the participants share dependencies as a whole process image? – Jeff Wen Jan 18 '18 at 15:24
2

You should use g++ instead of gcc to compile libfoo.so. Then libstdc++ will be added to libfoo.so's dependency.

llllllllll
  • 16,169
  • 4
  • 31
  • 54
2

You should really stick with one language, and don't mix C and C++ like you did (as long as you don't know exactly what you are doing (no offense, experience))! Why is the only part of your code that is actually C++ compiled using a C compiler?

Compile libfoo using g++ not gcc:

g++ -fPIC --shared foo.cpp -o libfoo.so

update

[...]I'm just trying to understand what's happening here.[...]

I think what is happening is a mix of various things.

  1. First of all, gcc and g++ are not simply compiler programs but collections of programs, including compiler, linker etc. Both will automatically decide to use a C compiler or a C++ compiler/ linker(options). So your foo.cpp is compiled perfectly...
  2. Second std::cout is defined extern (code from my cygwin installation extern ostream cout; /// Linked to standard output). This means, while linking libfoo.so the linker does not have to link to libstdc++.so.X as it can be a dependency from another linker unit (see 3.). PLUS using gcc the linker won't even try to.
  3. As you added iostream to your main application, gcc recognizes this and uses other linker options to generate your binary. You can use -v option to see the differences between gcc and g++ and you should see that LIBRARY_PATH will differ. Also using g++ in COLLECT_GCC_OPTIONS there will be the option -lstdc++. That's the reason for the dependency libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 if you compile libfoo using g++.
  4. As you are using dl as loading option, either your shared object itself or (OS dependent) your executable should contain the right libraries, regardless some middlemans (libbar in your case). If not, you will get the error you see. NOTE: I would not suggest you to depend on the executable, as it is OS dependent and propably won't work on other *nix.
  5. Here is an example you can play with. try to change gcc -D MAINDEF -Wall --pedantic --pedantic-errors -ldl -lstdc++ main.cpp -o main to g++ or remove -lstdc++...

In short: As already said, use the correct compiler is the way to go! The error is not inside libbar nor main as this units should not be responsible for the correct library dependencies of libfoo.

[...]I don't have access to their source code.[...]

Tell the guy's to fix their dependecies!

user1810087
  • 5,146
  • 1
  • 41
  • 76
  • It's a upstream `.so` file and I don't have access to their source code. And I am trying to figure out what's happening here. – Jeff Wen Jan 18 '18 at 12:46
  • @JeffWen again, what is upstream? also if you don't have access to the source, how do you know it uses std::cout and is compiled with gcc? – user1810087 Jan 19 '18 at 12:59
  • It's really helpful. But still, I don't understand why won't gcc compile `foo.cpp` with `stdc++`. Why wound `extern ostream cout` make any difference? The compiled object from `foo.cpp` (call it `foo.o`) has those non-local symbols by using `std::cout`, right? Why won't the linker complain and abort since it has nowhere to find those symbols and have no `.so` to relocate those in runtime? – Jeff Wen Jan 20 '18 at 03:58