89

Consider the following scenario:

  • Shared Library libA.so ,with no dependencies.
  • Shared Library libB.so, with libA.so as its dependency.

I want to compile a binary file that links with the libB. Should I link the binary with libB only or with libA either?

Is there any way to link only with the direct dependencies, letting the resolution of unresolved symbols from the dependencies for runtime?

I'm worried about the fact that the library libB implementation may change in the future, introducing other dependencies (libC, libD, libE for instance). Am I going to have problems with that?

In other words:

  • libA files: a.cpp a.h
  • libB files: b.cpp b.h
  • main program files: main.cpp

Of course, b.cpp includes a.h and main.cpp includes b.h.

Compilation commands:

g++ -fPIC a.cpp -c
g++ -shared -o libA.so a.o

g++ -fPIC b.cpp -c -I.
g++ -shared -o libB.so b.o -L. -lA

Which of the bellow options should I use?

g++ main.cpp -o main -I. -L. -lB

or

g++ main.cpp -o main -I. -L. -lB -lA

I couldn't use the first option. The linker complains about the unresolved symbols from the library libA. But it sound a little strange to me.

Thanks very much.

-- Updated comments:

When I link the binary, the linker will try to resolve all symbols from the main and the libB. However, libB has undefined symbols from the libA. That's why the linker complains about that.

That's why I need to link with the libA too. However I found a way to ignore unresolved symbols from shared libraries. Looks like I should use the following command line to do that:

g++ main.cpp -o main -I. -L. -lB -Wl,-unresolved-symbols=ignore-in-shared-libs

Looks like it is still possible to use the -rpath option. However I need to understand it a little better.

Does anyone knows any possible pitfalls when using the -Wl,-unresolved-symbols=ignore-in-shared-libs option?

-- Updated comments 2:

-rpath should not be used for this purpose. It is useful to force a library to be found in a given directory. The -unresolved-symbol approach looks much better.

Thanks again.

David Berry
  • 40,941
  • 12
  • 84
  • 95
Marcus
  • 1,675
  • 3
  • 18
  • 29
  • Here is a runnable minimal example for those looking to test this kind of stuff out: https://github.com/cirosantilli/cpp-cheat/tree/b9d026ae960d92e7a09bd34b6406d39429930d63/shared-library/lib-lib-dependency – Ciro Santilli OurBigBook.com Jan 12 '19 at 16:23

4 Answers4

49

It looks like you are most of the way there already. Well done with your investigation. Let's see if I can help clear up the 'why' behind it.

Here's what the linker is doing. When you link your executable ('main' above) it has some symbols (functions and other things) that are unresolved. It will look down the list of libraries that follow, trying to resolve unresolved symbols. Along the way, it finds that some of the symbols are provided by libB.so, so it notes that they are now resolved by this library.

However, it also discovers that some of those symbols use other symbols that are not yet resolved in your executable, so it now needs to resolve those as well. Without linking against libA.so, your application would be incomplete. Once it links against libA.so, all symbols are resolved and linking is complete.

As you saw, the use of -unresolved-symbols-in-shared-libs, doesn't fix the problem. It just defers it so that those symbols are resolved at run time. That's what -rpath is for: to specify the libraries to be searched at run time. If those symbols can't be resolved then, your app will fail to start.

It's not an easy thing to figure out library dependencies because a symbol could be provided by more than one library and be satisfied by linking against any one of them.

There is another description of this process here: Why does the order in which libraries are linked sometimes cause errors in GCC?

Community
  • 1
  • 1
kithril
  • 1,183
  • 2
  • 12
  • 22
  • 2
    I know it was a long time ago, I forgot to mark it as solved. Thanks very much for your answer.] – Marcus Dec 04 '13 at 18:49
  • Thank you guys for the amazing question, and a befitting answer! I have almost exactly the same scenario. I'm trying to wrap openCV shared objects in a custom shared object. lets say C.so is my custom so, and CV.so is some openCV so. I have successfully linked C.so against CV.so, and I want to link a main.cpp (you get it!) against C.so, but the thing is apart from using objects from C.so, main.cpp uses an object 'cv::Mat' which is defined in CV.so.. In this case for main.cpp to run do I need to link against both C.so and CV.so?? I'd love it if someone sheds light on this. Thank you! :) – Salar Khan Nov 16 '16 at 22:31
  • 2
    Why wouldn't the linker resolves all required libA.so symbols when producing libB.so? The main application shouldn't have to link to libA as it should be transparent, e.g. indirectly shielded by libB? – Wilson Nov 02 '17 at 06:55
  • This is incorrect. Linker makes no use of the libA.so other than to notify a user of incomplete dependency chain. Without providing the libA.so but instead passing the -unresolved-symbols=ignore-in-shared-libs you will get the same exact executable. – listerreg Apr 30 '19 at 20:15
24

For dynamic linking only with direct dependencies you can use -Wl,--as-needed with adding the libs after -Wl,--as-needed:

gcc main.c -o main -I. -L. -Wl,--as-needed -lB -lA

For checking the direct dependencies you should use readelf instead of ldd because ldd also shows the indirect dependencies.

$ readelf -d main | grep library
0x0000000000000001 (NEEDED)             Shared library: [libB.so]
0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

ldd also shows the indirect dependencies:

$ LD_LIBRARY_PATH=. ldd ./main
linux-vdso.so.1 (0x00007fff13717000)
libB.so => ./libB.so (0x00007fb6738ed000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb6734ea000)
libA.so => ./libA.so (0x00007fb6732e8000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb673af0000)

If you use cmake, you can add the following lines to include only direct dependencies:

set(CMAKE_EXE_LINKER_FLAGS    "-Wl,--as-needed ${CMAKE_EXE_LINKER_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--as-needed ${CMAKE_SHARED_LINKER_FLAGS}")
Chris Krycho
  • 3,125
  • 1
  • 23
  • 35
user1047271
  • 311
  • 2
  • 9
  • Sorry, I have made your example as c programm instead of c++. Therefore I used the gcc compiler. But it also works with g++. – user1047271 Mar 23 '15 at 11:24
  • 1
    That's really useful information. I did not know about readelf use instead of ldd. Thanks very much. – Marcus Mar 24 '15 at 12:49
1

Another option is to use libtool

If you change the g++ call to libtool --mode=compile g++ to compile the source code and then libtool --mode=link g++ to create the application off of libB, then libA will be linked automatically.

mattmilten
  • 6,242
  • 3
  • 35
  • 65
0

This is an interesting post - I was banging my head with this as well, but I think you miss a point here..

The idea is as follows, right ?

main.cpp =(depends)=> libB.so =(depends)=> libA.so

Let's further consider that ..

  • In a.cpp (and only there) you define a class / variable, let's call it "symA"
  • In b.cpp (and only there) you define a class / variable, let's call it "symB".
  • symB uses symA
  • main.cpp uses symB

Now, libB.so and libA.so have been compiled as you described above. After that, your first option should work, i.e.:

g++ main.cpp -o main -I. -L. -lB

I guess that your problem originates from the fact that

in main.cpp you also refer to symA

Am I correct?

If you use a symbol in your code, then that symbol must be found in an .so file

The whole idea of inter-referencing shared libraries (i.e. creating APIs), is that the symbols in the deeper layers are hidden (think of peeling onions) and not used. .. i.e. don't refer to symA in your main.cpp, but only to symB instead (and let symB to refer symA only).

El Sampsa
  • 1,673
  • 3
  • 17
  • 33
  • 1
    You misunderstood it. Evidently you need to resolve the immediate required symbols. The problem is related to symbols that your dependences may require. It is common when developing libraries to be used by 3rd party with the library user having to provide implementation to functionalities your library uses. You develop using a stub, with no dependencies at all and the integrator will develop a lib to replace the stub, then link everything together. The loader will have to resolve all dependencies. However your compilation scripts (and your users') must account for it. – Marcus Aug 17 '17 at 16:24