Runtime linking on Mac

来源:互联网 发布:专业网络k歌声卡 编辑:程序博客网 时间:2024/06/07 12:24

Some files for testing

With thanks to https://dev.lsstcorp.org/trac/wiki/LinkingDarwin

Create files a.cc through d.cc

mkdir \$HOME/dyldtestcd \$HOME/dyldtestcat << EOF > a.cc#include <iostream>void a() { std::cout << "a()" << std::endl; }EOFcat > b.cc << EOF#include <iostream>void a();void b() { std::cout << "b()" << std::endl; a(); }EOFcat > c.cc << EOF#include <iostream>void b();void c() { std::cout << "c()" << std::endl; b(); }EOFcat > d.cc << EOFvoid 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.oclang++ -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 -laclang++ -o test-lib d.o -L. -lcexport DYLD_PRINT_LIBRARIES=y./test-libunset DYLD_PRINT_LIBRARIES

This gives, among other output:

dyld: loaded: /Users/mb312/dyldtest/./test-libdyld: loaded: libc.dylibdyld: loaded: /usr/lib/libc++.1.dylibdyld: loaded: /usr/lib/libSystem.B.dylibdyld: loaded: libb.dylibdyld: loaded: liba.dylibdyld: 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:

  1. The name / location that a library provides to something that links against it. This is also the install name id (see man page for install_name_tool). The install_name idin a library you are linking to only matters at link time
  2. 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 amv liba.dylib a

Of course now our application fails:

\$ ./test-libdyld: Library not loaded: liba.dylibReferenced from: /Users/mb312/dyldtest/libc.dylibReason: image not foundTrace/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.dylibinstall_name_tool -change liba.dylib a/liba.dylib libc.dylib

Then, sure enough, test-lib works again:

\$ ./test-libc()b()a()

Now move the other libraries to a new directory:

mkdir libsmv 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-libdyld: Library not loaded: libb.dylibReferenced from: /Users/mb312/dyldtest/libs/libc.dylibReason: image not foundTrace/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-libc()b()a()

We wanted to make it possible to call our executable from any directory, so we try that:

mkdir nice-placecd nice-place../test-lib

This gives:

dyld: Library not loaded: a/liba.dylibReferenced from: /Users/mb312/dyldtest/libs/libc.dylibReason: image not foundTrace/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.dylibinstall_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.dylibinstall_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-libdyld: Library not loaded: @rpath/liba.dylibReferenced from: /Users/mb312/dyldtest/libs/libc.dylibReason: image not foundTrace/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 executableinstall_name_tool -delete_rpath @loader_path/a ../test-lib# put into the library insteadinstall_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:

cdcp -r dyldtest dyldtest2./dyldtest2/test-lib

All is still good. You are ready for great things related to OSX run-time loading.