Runtime linking on Mac¶
Some files for testing¶
With thanks to https://dev.lsstcorp.org/trac/wiki/LinkingDarwin
Create files a.cc
through d.cc
mkdir \$HOME/dyldtest
cd \$HOME/dyldtest
cat << EOF > a.cc
#include <iostream>
void a() { std::cout << "a()" << std::endl; }
EOF
cat > b.cc << EOF
#include <iostream>
void a();
void b() { std::cout << "b()" << std::endl; a(); }
EOF
cat > c.cc << EOF
#include <iostream>
void b();
void c() { std::cout << "c()" << std::endl; b(); }
EOF
cat > d.cc << EOF
void c();
int main(int, char**) { c(); return 0; }
EOF
Compile them to object files:
clang++ -c a.cc b.cc c.cc d.cc
Create libraries from a.o and b.o:
clang++ -o liba.dylib -dynamiclib a.o
clang++ -o libb.dylib -dynamiclib b.o -L. -la
Using otool -L
to show linked library locations¶
Notice the otool -L
output for these libraries:
liba.dylib:
liba.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)
libb.dylib:
libb.dylib (compatibility version 0.0.0, current version 0.0.0)
liba.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)
Link another library against these libraries:
clang++ -o libc.dylib -dynamiclib c.o -L. -lb -la
clang++ -o test-lib d.o -L. -lc
export DYLD_PRINT_LIBRARIES=y
./test-lib
unset DYLD_PRINT_LIBRARIES
This gives, among other output:
dyld: loaded: /Users/mb312/dyldtest/./test-lib
dyld: loaded: libc.dylib
dyld: loaded: /usr/lib/libc++.1.dylib
dyld: loaded: /usr/lib/libSystem.B.dylib
dyld: loaded: libb.dylib
dyld: loaded: liba.dylib
dyld: loaded: /usr/lib/libc++abi.dylib
otool -L
on libc.dylib
:
libc.dylib:
libc.dylib (compatibility version 0.0.0, current version 0.0.0)
libb.dylib (compatibility version 0.0.0, current version 0.0.0)
liba.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)
The install_name
¶
So – the install_name
is two things:
- The name / location that a library provides to something that links against
it. This is also the install name
id
(see man page forinstall_name_tool
). Theinstall_name
id
in a library you are linking to only matters at link time - The name that the linking application uses to say where it should find a
library. The
install_name
values stored in your linking application or library matter at run time - because they tell the application or library where to find the library.
Install names and static absolute or relative paths¶
So far we’ve used library paths that are relative paths - in fact they are relative to the current directory from which we are executing our commands.
We can try moving our library:
mkdir a
mv liba.dylib a
Of course now our application fails:
\$ ./test-lib
dyld: Library not loaded: liba.dylib
Referenced from: /Users/mb312/dyldtest/libc.dylib
Reason: image not found
Trace/BPT trap: 5
From the otool -L
output above, we have to change the install_name
in
libb.dylib
, and libc.dylib
:
install_name_tool -change liba.dylib a/liba.dylib libb.dylib
install_name_tool -change liba.dylib a/liba.dylib libc.dylib
Then, sure enough, test-lib
works again:
\$ ./test-lib
c()
b()
a()
Now move the other libraries to a new directory:
mkdir libs
mv libb.dylib libc.dylib libs
Obviously we first have to tell test-lib
where libc.dylib
went:
install_name_tool -change libc.dylib libs/libc.dylib test-lib
But - oh dear - now libc.dylib
is confused:
\$ ./test-lib
dyld: Library not loaded: libb.dylib
Referenced from: /Users/mb312/dyldtest/libs/libc.dylib
Reason: image not found
Trace/BPT trap: 5
So we need to tell libc.dylib
where libb.dylib
is:
install_name_tool -change libb.dylib libs/libb.dylib libs/libc.dylib
test-lib
then runs OK.
@loader_path¶
At the moment all our install_name
values are relative to the current
directory. That’s not satisfying because it means if we run our command from
anywhere but the current directory, the paths will be wrong. @loader_path
is one way to fix this. Here we tell test-lib
that it should look for
libc.dylib
in the lib
directory relative to itself:
install_name_tool -change libs/libc.dylib @loader_path/libs/libc.dylib test-lib
Note the use of @loader_path
. This will be the directory containing the loading
application or library - in our case the directory containing test-lib
. We
can do the same trick to tell libc.dylib
where to find libb.dylib
,
relative to itself:
install_name_tool -change libs/libb.dylib @loader_path/libb.dylib libs/libc.dylib
Note @loader_path
again. See rpath etc and linking and install
names for more explanation. In this case @loader_path
will be the path
of libc.dylib
- the library doing the loading of libb.dylib
. Now
all’s good:
\$ ./test-lib
c()
b()
a()
We wanted to make it possible to call our executable from any directory, so we try that:
mkdir nice-place
cd nice-place
../test-lib
This gives:
dyld: Library not loaded: a/liba.dylib
Referenced from: /Users/mb312/dyldtest/libs/libc.dylib
Reason: image not found
Trace/BPT trap: 5
Why? Because libc.dylib
is still looking for liba.dylib
at
a/liba.dylib
– starting at the current working directory. How to fix? Of
course:
install_name_tool -change a/liba.dylib @loader_path/../a/liba.dylib \
../libs/libc.dylib
install_name_tool -change a/liba.dylib @loader_path/../a/liba.dylib \
../libs/libb.dylib
@rpath¶
Another option is to use @rpath
– see rpath etc:
install_name_tool -change @loader_path/../a/liba.dylib @rpath/liba.dylib \
../libs/libc.dylib
install_name_tool -change @loader_path/../a/liba.dylib @rpath/liba.dylib \
../libs/libb.dylib
This won’t work yet because we haven’t told anything what @rpath
is:
\$ ../test-lib
dyld: Library not loaded: @rpath/liba.dylib
Referenced from: /Users/mb312/dyldtest/libs/libc.dylib
Reason: image not found
Trace/BPT trap: 5
We can set @rpath
in our executable:
install_name_tool -add_rpath @loader_path/a ../test-lib
That works. Or in the libraries doing the loading:
# delete rpath we just set in executable
install_name_tool -delete_rpath @loader_path/a ../test-lib
# put into the library instead
install_name_tool -add_rpath @loader_path/../a ../libs/libc.dylib
All good again.
One advantage of @rpath
is that you can put several different search paths
into the the library or executable @rpath
. For example, you could set the
@rpath
so that the library or executable looks for its libraries in a
relative path and also an absolute system path.
@loader_path and @rpath make code relocatable¶
Now notice this entire stack is relocatable (and has been since we started using
@loader_path
:
cd
cp -r dyldtest dyldtest2
./dyldtest2/test-lib
All is still good. You are ready for great things related to OSX run-time loading.