I have been struggling with this concept for a while and I cannot really understand what the difference is between -change
and -id
.The man page states
-id name
Changes the shared library identification name of a dynamic shared library to name. If the Mach-O binary is not a dynamic
shared library and the -id option is specified it is ignored.
-change old new
Changes the dependent shared library install name old to new in the specified Mach-O binary. More than one of these options
can be specified. If the Mach-O binary does not contain the old install name in a specified -change option the option is
ignored.
So far I have experimented with -change
. Suppose I have the following structure
Test.App
|_Contents
|_MacOS
| |_test -----> item A
|_Library
|_test_library.dylib --->item B
|_another_library.dylib --->item C
Now suppose I ran the following on itemB
$ otool -L test_library.dylib
test_library.dylib
/some/path/another_library.dylib -->item D
The above result indicates that test_library.dylib
depends on another_library.dylib
now if I needed to change the location of another_library.dylib
I would do this
install_name_tool -change /some/path/another_library.dylib some/new/path/another_library.dylib test_library.dylib
this would change the location of item D. My question is what does install-name_tool -id
do and when do I use that ?
The term install name refers to the exact path of the .dylib
file in the end-user system so the runtime linker can find and load the dynamic library.
The name can be either:
.dylib
will be embedded in the app bundle and on the developer system they will be either pre-built in /usr/local
, /opt/local
or somewhere else, or they will be built from source as part of the app build.The latter is the main problem as when the .dylib
is built, its install name is stamped into the .dylib
by the linker and that's where it's expected to be found and loaded from at runtime. Obviously this won't work on the end-user system as that path only exists on the developer's system, so the solution is to use install_name_tool
to modify the install name of the libraries, and executables that reference those libraries, when putting the app bundle together.
As executables/app bundles can be installed in different places on the end-user system you can use a placeholder system to abstract the install name location:
@executable_path
: The full path of the main executable.@loader_path
: The full path of the referencing executable or .dylib
.@rpath
: The RPATH set in the main executable. This can also be changed using install_name_tool
.So for example in a macOS app bundle the executable would be in TheApp.app/Contents/MacOS/TheApp
and libraries would be in TheApp.app/Contents/Frameworks
so you would want to reference the libraries using the path @executable_path/../Frameworks/Library.dylib
.
It's better to set RPATH of the main executable to @executable_path/../Frameworks
however, and refer to the libraries using @rpath/Library.dylib
.
install_name_tool
has two main options:
-id
: This sets the install name of the .dylib
file itself and will be used as the prototype install name from that point forward when something links with the .dylib
. You could "correct" the install name immediately after building the .dylib
, however that's an unusual workflow as how would a library know about the environment of whatever is using it?
-change
: This changes the install name of a .dylib
within a referencing executable (or dylib).
What happens when the -id
name doesn't match the -change
name? Nothing. The -change
option is the important one to get right as once the runtime linker has found the .dylib
then that's mission accomplished.
You would obviously script all the fixing up using a script, however that's a bit tedious, so I have developed the copy_dylibs.py
script to do it all for you. You configure it to run after linking your app executable and it looks at your executable to recursively find .dylib
files to copy into the app bundle. It then fixes their install names, leaving the original .dylib
files alone.
install_name_tool -id
is used for change the install name
of dylib
, you can use the otool -D
see a dylib install name
in the terminal, it will show the default value for you, the /some/path/another_library.dylib
is the default install name
of another_library.dylib
, of course, you can change it use install_name_tool -id
in the terminal, just use like this in terminal
install_name_tool -id /some/path/another_library_newname.dylib /some/path/another_library.dylib
now,you use the otool -D /some/path/another_library.dylib
, you will find the install name
is /some/path/another_library_newname.dylib
here is my example in picture
id
is used at link time and install name
is used at runtime. They are all information provided for the linker to locate the dylib. I followed this tutorial.
Let me show an example,
$ cat a.cc
#include <iostream>
void a() { std::cout << "a()" << std::endl; }
$ clang++ -c a.cc
$ clang++ -o liba.dylib -dynamiclib a.o
$ otool -L liba.dylib
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 400.9.4)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.250.1)
As you can see, the first line is the id
. Let's link with libb.dylib,
$ cat b.cc
#include <iostream>
void a();
void b() { std::cout << "b()" << std::endl; a(); }
$ clang++ -c b.cc
$ clang++ -o libb.dylib -dynamiclib b.o -L. -la
$ otool -L libb.dylib
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 400.9.4)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.250.1)
Just notice the second line, the id
for liba.dylib is used here. Let's change the id to foo/liba.dylib
and link again,
$ install_name_tool -id foo/liba.dylib liba.dylib
$ otool -D liba.dylib
liba.dylib:
foo/liba.dylib
liba.dylib:
foo/liba.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.4)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.250.1)
So you see the -D
and -L
all outputs the current id
as foo/liba.dylib
.
Let's link again with liba.dylib again,
$ clang++ -o libb.dylib -dynamiclib b.o -L. -la
$ otool -L libb.dylib
libb.dylib:
libb.dylib (compatibility version 0.0.0, current version 0.0.0)
foo/liba.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.4)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.250.1)
See the difference? The run time location to find liba.dylib is changed to foo/liba.dylib
at the second line.
Basically, it tells libb.dylib to find liba.dylib from current_dir/foo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With