Exclude dynamic dependencies from build command? - linux

Let's suppose we have a library libutils.so:
ldd libutils.so
...
libdependency.so
...
Let's further suppose that we need to build an application:
g++ appliation.cpp -lutils -o application
May we omit -ldependency in the above command or must we write:
g++ appliation.cpp -lutils -ldependency -o application

May we omit -ldependency in the above command
If you control the linkage of libutils.so itself, yes you can. An illustration:
main.c
extern void foo(void);
int main(void)
{
foo();
return 0;
}
foo.c
extern void bar(void);
void foo(void)
{
bar();
}
bar.c
#include <stdio.h>
void bar(void)
{
puts(__func__);
}
We'll make a program that depends on libfoo.so, which depends on libbar.so.
Make the object files:
$ gcc -Wall -c -fPIC foo.c bar.c
gcc -Wall -c main.c
Now link libbar.so the No Frills way:
$ gcc -shared -o libbar.so bar.o
Next link libfoo.so like this:
$ gcc -shared -o libfoo.so foo.o -L. -lbar -Wl,-rpath=$(pwd)
The effect of the -rpath linker option is:
-rpath=dir
Add a directory to the runtime library search path. This is used when linking an ELF executable with shared objects.
All -rpath arguments are concatenated and passed to the runtime linker, which uses them to locate shared objects at runtime.
The -rpath option is also used when locating shared objects which are needed by shared objects explicitly included in the link;
see the description of the -rpath-link option. If -rpath is not used when linking an ELF executable,
the contents of the environment variable LD_RUN_PATH will be used if it is defined.
The result is that:
$ objdump -x -j .dynamic libfoo.so | egrep '(RUNPATH|NEEDED)'
NEEDED libbar.so
RUNPATH /home/imk/develop/so/scrap
libfoo.so has a NEEDED entry inscribed in its .dynamic section saying that
the library has a runtime dependency on libbar.so. Likewise it has
a RUNPATH entry there saying that runtime dependencies may be searched for in /home/imk/develop/so/scrap
That's just the pwd where I did the linkage: it doesn't have to be that, as long as is indeed
a directory where libbar.so can be found when the linker or loader comes looking for it.
This information can be read by the linker, when libbar.so is linked with something else,
and by the loader at runtime. So finally I can link prog like this:
$ gcc -o prog main.o -L. -lfoo -Wl,-rpath=$(pwd)
I don't need to mention -lbar, because libfoo.so itself provides the linker with
the information that libfoo.so depends on libbar.so, and where to look for it.
Since I also passed -rpath=$(pwd) in the linkage of prog, we see that prog
will provide this information
$ objdump -x -j .dynamic prog | egrep '(RUNPATH|NEEDED)'
NEEDED libfoo.so
NEEDED libc.so.6
RUNPATH /home/imk/develop/so/scrap
to the runtime loader: prog needs libfoo.so, and it can be looked for
in /home/imk/develop/so/scrap. When the loader finds libfoo.so and loads it, it will
discover from it that:
NEEDED libbar.so
RUNPATH /home/imk/develop/so/scrap
and will in turn find and load libbar.so, which will enable it to resolve all
symbols referred to in the process under construction. Consequently, prog can be run immediately:
$ ./prog
bar
I didn't have to pass -rpath=$(pwd) in the linkage of prog. But if I hadn't:
$ gcc -o prog main.o -L. -lfoo
$ ./prog
./prog: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
the loader wouldn't know where to find libfoo.so. See:
$ ldd prog
linux-vdso.so.1 (0x00007ffffcc35000)
libfoo.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4d1aff9000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4d1b5ec000)
And then I'd have to resort to:
$ export LD_LIBRARY_PATH=.
$ ldd prog
linux-vdso.so.1 (0x00007fff964dc000)
libfoo.so => ./libfoo.so (0x00007fc2a7f35000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc2a7b44000)
libbar.so => ./libbar.so (0x00007fc2a7942000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc2a8339000)
$ ./prog
bar
Later
it is still a little bit unclear whether the presence of libdependency.so among the output of ldd libutils.so is enough to omit -ldependencny during linkage
You would need to ask at least one and at most two questions about the output of ldd utils.so:-
Does the ldd libutils.so output report the so-name libdependency.so at all?
If Yes to 1, does it also resolve that so-name to an actual file?
If No to 1 then libdutils.so contains no informatation about its dependency of libdependency.so
and you must specify -lutils -ldependency in any further linkage.
If Yes to 1 but No to 2 (i.e.ldd libutils.so reports libdependency.so => not found) then libutils.so has
a NEEDED entry for the so-name libdependency.so but not a RUNPATH entry by which the linker or
loader can resolve that so-name to any actual file. In that case again, you must link -lutils -ldependency if you link -lutils, so that the linker will then search for a file that resolves -ldependency. At least, you must do so as long as ldd libutils.so still reports libdependency.so => not found when you do the linkage. Read on...
If Yes to 1 and Yes to 2 then you can drop -ldependency in a further linkage provided it is
run in the same environment in which you ran ldd libutils.so
That caveat is needed because if ldd libutils.so resolves libdependency.so, all you know
is that ldd was able to resolve libdependency.so using the loader's search algorithm:-
The LD_LIBRARY_PATH environment variable (in the active shell), lists a directory
in which libdependency.so is found, or
libutils.so provides a RUNPATH in which libdependency.so is found, or
libdependency.so is found in one of the directories listed in /etc/ld.so.conf (or the recursive include-expansion thereof), or
libdependency.so is found in one of the loader's trusted search directories, /lib and /usr/lib
If ldd can resolve libdependency.so in one of those four ways, then the linker will be able
to do it the same way, as long as that way still succeeds when you do the linkage.
So going back to my example, and my linkage:
$ gcc -shared -o libfoo.so foo.o -L. -lbar -Wl,-rpath=$(pwd)
After that, thanks to -rpath=$(pwd). I can link prog like:
$ gcc -o prog main.o -L. -lfoo
without mentioning -lbar, and it succeeds. Now I link libfoo.so instead without
an -rpath:
$ gcc -shared -o libfoo.so foo.o -L. -lbar
after which:
$ objdump -x -j .dynamic libfoo.so | egrep '(RUNPATH|NEEDED)'
NEEDED libbar.so
there's no RUNPATH anymore, and consequently:
$ ldd libfoo.so
linux-vdso.so.1 (0x00007ffda05e6000)
libbar.so => not found
because the loader can't resolve libbar.so in any other way either.
Now I can no longer link prog without -lbar:
$ gcc -o prog main.o -L. -lfoo
/usr/bin/ld: warning: libbar.so, needed by ./libfoo.so, not found (try using -rpath or -rpath-link)
./libfoo.so: undefined reference to `bar'
But if I do:
$ export LD_LIBRARY_PATH=$(pwd)
then:
$ ldd libfoo.so
linux-vdso.so.1 (0x00007ffe56d1e000)
libbar.so => /home/imk/develop/so/scrap/libbar.so (0x00007fd2456e8000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd2452f7000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd245aec000)
libfoo.so's dependency libbar.so is resolved by the loader, using the LD_LIBRARY_PATH, and in the
same way by the linker:
$ gcc -o prog main.o -L. -lfoo; echo Done
Done
And if I clear LD_LIBRARY_PATH again:
$ unset LD_LIBRARY_PATH
$ gcc -o prog main.o -L. -lfoo; echo Done
/usr/bin/ld: warning: libbar.so, needed by ./libfoo.so, not found (try using -rpath or -rpath-link)
./libfoo.so: undefined reference to `bar'
collect2: error: ld returned 1 exit status
Done
back to failure.

Related

Link a C .o file before a shared .so in Rust

My Rust main function depends on a C object file foo.o, which depends on a C shared library libbar.so.
I need to link foo.o before libbar.so in Rust,
I tried rust-link-lib=foo. It only works for libraries that begin with lib and end with .so or .a.
I tried rust-link-arg=foo.o and rust-link-lib=bar. It puts foo.o after -lbar, which results in an undefined reference error.
Compile the C .o and .so files,
gcc -c foo.c
gcc -fPIC -shared -o libbar.so bar.c
Linking in gcc succeeds,
gcc main.c -o main foo.o -L./ -lbar
LD_LIBRARY_PATH=./ ./main
Linking in rust
cargo run
failed with
= note: /usr/bin/ld: foo.o: in function `foo':
foo.c:(.text+0x10): undefined reference to `bar'
collect2: error: ld returned 1 exit status
Sample code here.
Thank you for your help.

versioned symbols with Linux ld-linux.so

I am trying to understand how ld-linux.so resolves references to versioned symbols on Linux. I have the following files:
test.c:
void f();
int main()
{
f();
}
a.c and b.c:
void f() {}
symbols.txt:
ABC {
global:
*;
};
Makefile:
all: liba.so libb.so test
liba.so: a.c
gcc -g -shared $^ -o $# -Wl,--version-script=symbols.txt
libb.so: b.c
gcc -g -shared $^ -o $#
test: test.c liba.so
gcc -g test.c -la -L. -o $#
clean:
rm -f liba.so libb.so test
I then ran the following command
LD_PRELOAD=./libb.so LD_LIBRARY_PATH=. ./test
I find that f() from b.c is invoked even though the symbol f in libb.so does not have the version required by test (f#ABC). Why does this happen ?
version-script is used to confine the symbols to be exported in shared libraries. this means faster link speed and few change for symbols conflict.
By default, most function name will be exported, so you can link with libb.so without any problem.
If you want to determine which version of function should be used, You need to specify it in your program. This need some assembly code to specify .symver.
For more details, please read Ulrich Drepper’s paper https://www.akkadia.org/drepper/dsohowto.pdf

What is the equivalent of #loader_path for rpath specification on linux?

On osx loader, #loader_path resolves to the position of the generic binary object, and #executable_path to the position of the executable. On Linux, apparently there's only $ORIGIN, which resolves to the executable path. Is there a hidden feature in the linux loader to specify a dynamic search path for a generic ELF object? Or maybe $ORIGIN behaves differently for so objects?
Linux also has $LIB and $PLATFORM, but they don't provide what I need.
$ORIGIN is the location of the object being loaded, so it is different in the executable and shared libraries loaded by the executable.
Edit: Here's a small test I performed to check:
~$ mkdir /tmp/tests
~$ cd /tmp/tests
tests$ mkdir good bad
tests$ gcc -fPIC -shared -o good/libtest.so -Wl,-rpath,\$ORIGIN -x c - <<< 'int puts(const char*); void foo() { puts("good"); }'
tests$ gcc -fPIC -shared -o bad/libtest.so -Wl,-rpath,\$ORIGIN -x c - <<< 'int puts(const char*); void foo() { puts("bad"); }'
tests$ gcc -fPIC -shared -o good/libtest2.so -Wl,-rpath,\$ORIGIN -x c - -ltest -Lgood <<< 'void foo(); void bar() { foo(); }'
tests$ gcc -o bad/a.out good/libtest2.so -x c - -Wl,-rpath,\$ORIGIN -Wl,-rpath-link,good <<< 'void bar(); int main() { bar(); }'
tests$
tests$ readelf -d bad/* good/* | grep RPATH
0x000000000000000f (RPATH) Library rpath: [$ORIGIN]
0x000000000000000f (RPATH) Library rpath: [$ORIGIN]
0x000000000000000f (RPATH) Library rpath: [$ORIGIN]
0x000000000000000f (RPATH) Library rpath: [$ORIGIN]
tests$
tests$ ldd bad/a.out
linux-vdso.so.1 => (0x00007faf2f295000)
good/libtest2.so (0x00007faf2f092000)
libc.so.6 => /lib64/libc.so.6 (0x0000003949800000)
libtest.so => /tmp/tests/good/libtest.so (0x00007faf2ee66000)
/lib64/ld-linux-x86-64.so.2 (0x0000003949400000)
tests$ bad/a.out
good
I think that demonstrates it works, everything has RPATH=$ORIGIN, the executable is explicitly linked to libtest2.so, which picks up libtest.so in its own directory not the executable's.
Using LD_DEBUG=libs bad/a.out shows:
[...]
17779: find library=libtest.so [0]; searching
17779: search path=/tmp/tests/good/tls/x86_64:/tmp/tests/good/tls:/tmp/tests/good/x86_64:/tmp/tests/good (RPATH from file good/libtest2.so)
[...]
i.e. when looking for the libtest.so dependency of good/libtest2.so the search path uses the RPATH from good/libtest2.so, which expands to /tmp/tests/good which is the $ORIGIN from good/libtest2.so not the $ORIGIN of the executable.
The linux loader searches in following order:
DT_RPATH (specified on gcc command line, ignored si DT_RUNPATH exist)
LD_LIBRARY_PATH (specified in the environment)
DT_RUNPATH (specified on gcc command line)
ld.so.conf (for directories specified on lines and compiled with ldconfig)
/lib and /usr/lib

Convincing gcc to ignore system libraries in favour of locally installed libraries

I am trying to build a simple executable that uses boost_serialization and boost_iostreams.
#include <fstream>
#include <iostream>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/device/file.hpp>
int main()
{
using namespace boost::iostreams;
filtering_ostream os;
os.push(boost::iostreams::gzip_compressor());
os.push(boost::iostreams::file_sink("emptyGzipBug.txt.gz"));
}
Unfortunately the system I am working with has a very outdated version of boost_serialization in /usr/lib/, and I have no way to change that.
I am fairly certain when I build the example using
g++ -o main main.cpp -lboost_serialization -lboost_iostreams
that the linker errors result because gcc uses the system version of boost_serialization rather than my locally installed version. Setting LIBRARY_PATH and LD_LIBRARY_PATH to /home/andrew/install/lib doesnt work. When i build using
g++ -o main main.cpp -L/home/andrew/install/lib -lboost_serialization -lboost_iostreams
then everything works.
My questions are:
How can I get gcc to tell me the filenames of the libraries its using?
Is it possible to setup the environment so that I dont have to specify the absolute path to my local boost on the command line of gcc.
PS After typing the below info, I thought I'd be kind and add what you need for your specific case:
g++ -Wl,-rpath,/home/andrew/install/lib -o main main.cpp -I/home/andrew/install/include -L/home/andrew/install/lib -lboost_serialization -lboost_iostreams
gcc itself doesn't care about the libraries. The linker does ;).
Even though the linker needs to find the shared libraries so it can resolve
symbols, it doesn't store the path of those libraries in the executable normally.
So, for a start, lets find out what is actually in the binary after you linked it:
$ readelf -d main | grep 'libboost'
0x0000000000000001 (NEEDED) Shared library: [libboost_serialization.so.1.54.0]
0x0000000000000001 (NEEDED) Shared library: [libboost_iostreams.so.1.54.0]
Just the names thus.
The libraries that are actually used are detemined by /lib/ld-linux.so.*
at run time:
$ ldd main | grep libboost
libboost_serialization.so.1.54.0 => /usr/lib/x86_64-linux-gnu/libboost_serialization.so.1.54.0 (0x00007fd8fa920000)
libboost_iostreams.so.1.54.0 => /usr/lib/x86_64-linux-gnu/libboost_iostreams.so.1.54.0 (0x00007fd8fa700000)
The path is found by looking in /etc/ld.so.cache (which is normally
compiled by running ldconfig). You can print its contents with:
ldconfig -p | grep libboost_iostreams
libboost_iostreams.so.1.54.0 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libboost_iostreams.so.1.54.0
libboost_iostreams.so.1.49.0 (libc6,x86-64) => /usr/lib/libboost_iostreams.so.1.49.0
libboost_iostreams.so (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libboost_iostreams.so
but since that is only the cached result of a previous look up,
you are more interested in the output of:
$ ldconfig -v 2>/dev/null | egrep '^[^[:space:]]|libboost_iostreams'
/lib/i386-linux-gnu:
/usr/lib/i386-linux-gnu:
/usr/local/lib:
/lib/x86_64-linux-gnu:
/usr/lib/x86_64-linux-gnu:
libboost_iostreams.so.1.54.0 -> libboost_iostreams.so.1.54.0
/lib32:
/usr/lib32:
/lib:
/usr/lib:
libboost_iostreams.so.1.49.0 -> libboost_iostreams.so.1.49.0
which shows the paths that it looked in before finding a result.
Note if you are linking a 64bit program and it would find a 32bit
library first (or visa versa) then that would be skipped as being
incompatible. Otherwise, the first one found is used.
The paths used to search are specified in /etc/ld.so.conf which is
read (usually at boot time, or after installing something new)
when running ldconfig as root.
However, precedence take paths specified as a colon separated list
of paths in the environment variable LD_LIBRARY_PATH.
For example, if I'd do:
$ export LD_LIBRARY_PATH=/tmp
$ cp /usr/lib/libboost_iostreams.so.1.49.0 /tmp/libboost_iostreams.so.1.54.0
$ ldd main | grep libboost_iostreams
libboost_iostreams.so.1.54.0 => /tmp/libboost_iostreams.so.1.54.0 (0x00007f621add8000)
then it finds 'libboost_iostreams.so.1.54.0' in /tmp (even though it was a libboost_iostreams.so.1.49.0).
Note that you CAN hardcode a path in your executable by passing -rpath to
the linker:
$ unset LD_LIBRARY_PATH
$ g++ -Wl,-rpath,/tmp -o main main.cpp -lboost_serialization -lboost_iostreams
$ ldd main | grep libboost_iostreams
libboost_iostreams.so.1.54.0 => /tmp/libboost_iostreams.so.1.54.0 (0x00007fbd8bcd8000)
which can be made visible with
$ readelf -d main | grep RPATH
0x000000000000000f (RPATH) Library rpath: [/tmp]
Note that LD_LIBRARY_PATH even takes precedence over -rpath, unless
you also passed -Wl,--disable-new-dtags, along with the -rpath and provided that you are linking an executable and your linker supports
this flag.
You can show the search paths that gcc uses during compile(link) time with the -print-search-dirs command line option:
$ g++ -print-search-dirs | grep libraries
libraries: =/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../x86_64-linux-gnu/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../lib/:/lib/x86_64-linux-gnu/4.7/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/4.7/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../x86_64-linux-gnu/lib/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../:/lib/:/usr/lib/
This can be influenced by adding -L command line options. If a library can't be found in a path specified with the -L option then it looks in paths found through the environment variable GCC_EXEC_PREFIX (see the man page for that) and if that fails it uses the environment variable LIBRARY_PATH.
When you run g++ with the -v option, it will print the LIBRARY_PATH used.
LIBRARY_PATH=/tmp/lib g++ -v -o main main.cpp -lboost_serialization -lboost_iostreams 2>&1 | grep LIBRARY_PATH
LIBRARY_PATH=/tmp/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.7/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/tmp/lib/:/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../:/lib/:/usr/lib/
Finally, note that especially for boost (but in general) you should
use header files that match the correct version! So, if the library that you
link with at run time is version xyz you should have used an -I command line option to get g++ to find the corresponding header files, or things might not link or worse, result in unexplainable crashes.
-nodefaultlibs
Do not use the standard system libraries when linking. Only the
libraries you specify are passed to the linker, and options
specifying linkage of the system libraries, such as
-static-libgcc or -shared-libgcc, are ignored. The standard
startup files are used normally, unless -nostartfiles is used.
The compiler may generate calls to "memcmp", "memset", "memcpy"
and "memmove". These entries are usually resolved by entries in
libc. These entry points should be supplied through some other
mechanism when this option is specified.
Haven't used it myself but it sounds exactly like what was asked for.

Difference between -shared and -Wl,-shared of the GCC options

I know -Wl,-shared is a option of ld. I've seen some person compile like this,
$ gcc -shared -Wl,-soname,libtest.so -o libtest.so *.o
And some person like this
$ gcc -Wl,-shared -Wl,-soname,libtest.so -o libtest.so *.o
So, I want to know if there is some difference between -shared and -Wl,-shared.
Thanks.
There is a difference between passing -shared to gcc or -shared to ld (via -Wl). Passing -shared to GCC may enable or disable other flags at link time. In particular, different crt* files might be involved.
To get more information, grep for -shared in GCC's gcc/config/ directory and subdirectories.
Edit: To give a specific example: on i386 FreeBSD, gcc -shared will link in object file crtendS.o, while without -shared, it will link in crtend.o instead. Thus, -shared and -Wl,-shared are not equivalent.
I don't think there is any difference. -shared is not a supported option of gcc and it is passed to linker whether you specify it with -Wl or not. -Wl option of gcc is used to specify that a comma separated list of options is to be passed to linker for further processing.

Resources