wiki:LinkingDarwin
Last modified 9 years ago Last modified on 04/11/2008 06:42:20 PM

Below are some tests that illustrate the behaviour of the OSX linker on 10.4.10 and 10.5.2 (Intel) with two level namespaces.

Create a test directory

mkdir /Users/smm/dyldtest
cd /Users/smm/dyldtest

Create the following .cc files

a.cc:

#include <iostream>
void a() { std::cout << "a()" << std::endl; }

b.cc:

#include <iostream>
void a();
void b() { std::cout << "b()" << std::endl; a(); }

c.cc:

#include <iostream>
void b();
void c() { std::cout << "c()" << std::endl; b(); }

d.cc:

void c();
int main(int, char**) { c(); return 0; }

and compile them all:

g++ -c a.cc b.cc c.cc d.cc

Create libraries from a.o and b.o:

g++ -o liba.dylib -dynamiclib a.o
g++ -o libb.dylib -dynamiclib b.o -L. -la

Notice the otool -L output for these libraries:

liba.dylib:
        liba.dylib
        /usr/lib/libstdc++.6.dylib
        /usr/lib/libgcc_s.1.dylib
        /usr/lib/libSystem.B.dylib
 
libb.dylib:
        libb.dylib
        liba.dylib
        /usr/lib/libstdc++.6.dylib
        /usr/lib/libgcc_s.1.dylib
        /usr/lib/libSystem.B.dylib

Create a library from c.o in various ways

g++ -o libc.dylib -dynamiclib c.o -L. -lb
  • 10.4.10: works
  • 10.5.2: works
rm libc.dylib
mkdir a
mv liba.dylib a/
export LD_LIBRARY_PATH=/Users/smm/dyldtest/a
g++ -o libc.dylib -dynamiclib c.o -L. -lb
  • 10.4.10: fails
  • 10.5.2: works
export DYLD_LIBRARY_PATH=/Users/smm/dyldtest/a
g++ -o libc.dylib -dynamiclib c.o -L. -lb
  • 10.4.10: fails
  • 10.5.2: works
export DYLD_FALLBACK_LIBRARY_PATH=/Users/smm/dyldtest/a
g++ -o libc.dylib -dynamiclib c.o -L. -lb
  • 10.4.10: fails
  • 10.5.2: works

OK, what about specifying -L options for all directories that might contain dependent libraries?

g++ -o libc.dylib -dynamiclib c.o -L. -L/Users/smm/dyldtest/a -lb
  • 10.4.10: fails
  • 10.5.2: works

However:

g++ -o libc.dylib -dynamiclib c.o -L. -L/Users/smm/dyldtest/a -lb -la
  • 10.4.10: works
  • 10.5.2: works

Lets try a correct relative path to liba.dylib in libb.dylib:

install_name_tool -change liba.dylib a/liba.dylib libb.dylib
g++ -o libc.dylib -dynamiclib c.o -L. -lb

works (both on 10.4.10 and 10.5.2). Investigating further:

rm libc.dylib
mkdir arg
mv a/ arg/.
g++ -o libc.dylib -dynamiclib c.o -L. -lb
  • 10.4.10: fails as expected
  • 10.5.2: works
g++ -o libc.dylib -dynamiclib c.o -L. -L/Users/smm/dyldtest/arg -lb
  • 10.4.10: fails
  • 10.5.2: works
export DYLD_LIBRARY_PATH=/Users/smm/dyldtest/arg
g++ -o libc.dylib -dynamiclib c.o -L. -L/Users/smm/dyldtest/arg -lb
  • 10.4.10: fails
  • 10.5.2: works
cd arg
mv a ../
g++ -o libc.dylib -dynamiclib ../c.o -L../ -lb
  • 10.4.10: fails
  • 10.5.2: works

10.4.2

It looks like the relative path name in the install_name for liba.dylib stored inside libb.dylib is evaluated relative to the current working directory of the link command, not the -L options or [DY]LD_LIBRARY_PATH.

10.5.2

It looks like indirect dylibs aren't looked for at all at link time. Indeed, from man ld:

Indirect dynamic libraries

     If the command line specifies to link against dylib A, and when dylib A was built it linked against
     dylib B, then B is considered an indirect dylib.  When linking for two-level namespace, ld does not
     look at indirect dylibs, except when re-exported by a direct dylibs.

10.4.10: Does DYLD_LIBRARY_PATH override an install_name with an absolute path?

cd /Users/smm/dyldtest
rm -rf a/ arg/ *.dylib
g++ -o liba.dylib -dynamiclib a.o
mkdir a/
mv liba.dylib a/
install_name_tool -id /Users/smm/dyldtest/a/liba.dylib a/liba.dylib
g++ -o libb.dylib -dynamiclib b.o -La -la
g++ -o libc.dylib -dynamiclib c.o -L. -lb
g++ -o test d.o -L. -lc
export DYLD_PRINT_LIBRARIES=y
./test

results in this:

dyld: loaded: /Users/smm/dyldtest/./test
dyld: loaded: /usr/lib/libstdc++.6.dylib, cpu-sub-type: 3
dyld: loaded: libc.dylib
dyld: loaded: /usr/lib/libgcc_s.1.dylib, cpu-sub-type: 3
dyld: loaded: /usr/lib/libSystem.B.dylib, cpu-sub-type: 3
dyld: loaded: /usr/lib/system/libmathCommon.A.dylib, cpu-sub-type: 3
dyld: loaded: libb.dylib
dyld: loaded: /Users/smm/dyldtest/a/liba.dylib
c()
b()
a()

Next:

mkdir adagger
cp a/liba.dylib adagger
export DYLD_LIBRARY_PATH=/Users/smm/dyldtest/adagger
./test

which gives

dyld: loaded: /Users/smm/dyldtest/./test
dyld: loaded: /usr/lib/libstdc++.6.dylib, cpu-sub-type: 3
dyld: loaded: libc.dylib
dyld: loaded: /usr/lib/libgcc_s.1.dylib, cpu-sub-type: 3
dyld: loaded: /usr/lib/libSystem.B.dylib, cpu-sub-type: 3
dyld: loaded: /usr/lib/system/libmathCommon.A.dylib, cpu-sub-type: 3
dyld: loaded: libb.dylib
dyld: loaded: /Users/smm/dyldtest/adagger/liba.dylib
c()
b()
a()

So the answer is yes.

10.5.2: Does [DY]LD_LIBRARY_PATH override an @rpath/ prefixed install_name combined with -Wl,-rpath -Wl,directory1 ...?

cd /Users/smm/dyldtest
rm -rf a/ *.dylib
g++ -o liba.dylib -dynamiclib a.o -Wl,-install_name -Wl,@rpath/liba.dylib
mkdir a/
cp liba.dylib a/
g++ -o libb.dylib -dynamiclib b.o -L. -la -Wl,-rpath -Wl,.
g++ -o libc.dylib -dynamiclib c.o -L. -lb
g++ -o test d.o -L. -lc
export DYLD_PRINT_LIBRARIES=y
./test

which gives

dyld: loaded: /Users/smm/dyldtest/./test
dyld: loaded: /usr/lib/libstdc++.6.dylib
dyld: loaded: /Users/smm/dyldtest/libc.dylib
dyld: loaded: /usr/lib/libgcc_s.1.dylib
dyld: loaded: /usr/lib/libSystem.B.dylib
dyld: loaded: /usr/lib/system/libmathCommon.A.dylib
dyld: loaded: /Users/smm/dyldtest/libb.dylib
dyld: loaded: /Users/smm/dyldtest/liba.dylib
c()
b()
a()

Now,

export DYLD_LIBRARY_PATH=/Users/smm/dyldtest/a
./test

gives

dyld: loaded: /Users/smm/dyldtest/./test
dyld: loaded: /usr/lib/libstdc++.6.dylib
dyld: loaded: /Users/smm/Desktop/test/libc.dylib
dyld: loaded: /usr/lib/libgcc_s.1.dylib
dyld: loaded: /usr/lib/libSystem.B.dylib
dyld: loaded: /usr/lib/system/libmathCommon.A.dylib
dyld: loaded: /Users/smm/dyldtest/libb.dylib
dyld: loaded: /Users/smm/dyldtest/a/liba.dylib
c()
b()
a()

So again the answer is yes for DYLD_LIBRARY_PATH (note though that LD_LIBRARY_PATH has no effect).

On Linux, behaviour is similar. We could use -rpath rather than -rpath-link without giving up any flexibility since -rpath sets both DT_RPATH and DT_RUNPATH in ELF headers, and, according to http://linux.die.net/man/8/ld-linux:

The shared libraries needed by the program are searched for in various places:

  • (ELF only) Using the DT_RPATH dynamic section attribute of the binary if present and DT_RUNPATH attribute does not exist. Use of DT_RPATH is deprecated.
  • Using the environment variable LD_LIBRARY_PATH. Except if the executable is a set-user-ID/set-group-ID binary, in which case it is ignored.
  • (ELF only) Using the DT_RUNPATH dynamic section attribute of the binary if present.
  • From the cache file /etc/ld.so.cache which contains a compiled list of candidate libraries previously found in the augmented library path. If, however, the binary was linked with -z nodeflib linker option, libraries in the default library paths are skipped.
  • In the default path /lib, and then /usr/lib. If the binary was linked with -z nodeflib linker option, this step is skipped.

The consequence of using rpath would be that executables and libs would be useable without a (DY)LD_LIBRARY_PATH, e.g. without having to setup all dependencies for a package.

Conclusion

If we drop support for OSX 10.4.x, we should be able to leave both SConsUtils and SConstruct/SConscript files written on Linux alone. Supporting 10.4.x will (at least in my experience) require explicit linkage of indirect dylibs.