I wrote C program that calculates the time step iterations of wavefunctions to solve the time depended Schrödinger equation. At some step I need to do forward and backward Fast Fourier Transformations (FFT) for which I used the library kissfft. (https://github.com/mborgerding/kissfft)
My programs structure is somewhat like this:
- TDSE (working directory)
- modules
- include
- scripts
- test
- inttest_analytical.c
- kissfft
- libkissfft-double.dylib
Now when I compile my inttest_analytical.c it works.
But when trying to run the executable afterwards I get the following error:
(base) user TDSE % ./inttest_analytical
dyld: Library not loaded: libkissfft-double.dylib
Referenced from: /Users/user/Documents/Uni/HU Berlin/Computational Physics 2/Project 3 - Time-dependet Schroedinger Equation/TDSE/./inttest_analytical
Reason: image not found
zsh: abort ./inttest_analytical
After running otool -L ./inttest_analytical I get
/inttest_analytical:
libkissfft-double.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
As far as I understand from reading about this in other questions on google searches, libkissfft-double.dylib is a static library but I don't manage to tell gcc where to find the library. And the path it was given (by the compiler or linker?) is the working directory TDSE instead of TDSE/kissfft
For compilation I ran:
gcc -g -Wall -fPIC -I include -I kissff ./modules/wavefunction.c ./modules/integrator.c ./modules/geometry.c ./modules/linearalgebra.c ./modules/assert.c ./modules/hamiltonian.c ./modules/conjugategradient.c ./test/inttest_analytical.c -Lkissfft -lkissfft-double -o inttest_analytical
So I guess I am using the flags -L and -l wrong??
Thanks for any help.
I guess I'll use this question to write a canonical answer for all "image not found" issues.
1. The issue
Let's start with a minimal setup consisting of a main binary and a library, like so:
main.c:xyz.c:command line:
This works. You can run
./mainand it will print42.However, if you now create a folder
lib, movelibxyz.dylibthere and recompilemainlike so:Then the compilation will still succeed, however launching it will not:
But if you go back and recompile
libxyz.dylibto thelibfolder directly and then rebuildmain, like so:Then it will once again work. But just to illustrate, this is the error you get if you move
libxyz.dylibonce more:Just to make things worse though, you can also produce this error without even moving the library: simply
cd liband invoke../main.Also note the difference to before,
libxyz.dylibvslib/libxyz.dylib. This brings us to the core of the issue.2. The reason
On macOS, each shared library has an "install name", i.e. the path at which it is expected to be found at runtime. This path can take three forms:
/usr/lib/libxyz.dylib.lib/libxyz.dylib.@rpath/libxyz.dylib.This path is embedded in the Mach-O header of the library, via the
LC_ID_DYLIBload command. It can be viewed withotoollike so:This load command is created by the linker, whose man page (
man ld) tells us the following:This tells us the three steps of how install names work:
This will obviously cause issues if libraries are moved, or aren't even being compiled with the install name matching the path at which they will end up.
3. The solution
The solution is to change the install name path. Where and how depends on your setup. You can change it by two means:
-Wl,-install_name,...or outright-o ...), then recompile the main binary to link against that.install_name_tool. This is a bit more involved.In either case, you need to decide what form of install name you want to use:
Absolute.
This is recommended for libraries in global paths, shared by all users. You can also use this to point to your user directory, but it's a bit ugly since you can't move the binaries around or distribute them to someone else.
Relative.
Being relative to your working directory means this is entirely unreliable. Never use this. Just don't.
Magic.
There are three "special" tokens that go beyond absolute and relative paths:
@executable_pathis the runtime directory of the main binary of the process. This is the simplest form, but only works if your libraries are only used in a single main binary.@loader_pathis the runtime directory of the binary depending on the library. I recommend not using this, as it breaks if you have two binaries in different folders that want to link to the same library.@rpathis a list of runtime directories assembled fromLC_RPATHload commands. This is a bit more complex, but it's the most flexible solution, since it can itself contain@executable_pathand@loader_path.Use of those allows you to build binaries that can be moved around freely, so long as they all retain their relative position.
For a full description of them, see
man dyld.With that out of the way, let's look at implementing the possible solutions. We have:
cc -Wl,-install_name,...to specify an install name at compile time.install_name_tool -id ...to change the path embedded in a library.install_name_tool -change old newto change the path embedded in a binary linking against a library.3.1 Absolute paths
If you can recompile both the library and the main binary:
If you can only recompile the main binary:
If you cannot recompile either:
3.2
@executable_pathIf you can recompile both the library and the main binary:
If you can only recompile the main binary:
If you cannot recompile either:
3.3
@rpathRpath requires manual addition of runtime paths, which requires some planning. Suppose you have the follwing file hierarchy:
abin/blibx.dyliblib/liby.dyliblibz.dylibaandbare binaries that both link againstlibxandliby, which in turn both link againstlibz. For the install name oflibz, you can use neither@executable_path(becauseaandbare in different directories) nor@loader_path(becauselibxandlibyare in different directories). But you can use either of them inside@rpath, and here is the decision you have to make:@executable_pathinaand@executable_path/..inb. Then you can use@rpathto refer to the project root from all binaries.libzwould have an install name of@rpath/lib/libz.dylib.@loader_path/libinlibxand@loader_pathinliby. Then you can use@rpathto refer to the directory containing each binary.libzwould have an install name of@rpath/libz.dylib.I generally find the former to be easier to deal with, but the latter may be preferable if you have a large number of binaries scattered over many directories and only a few libraries.
To actually add an rpath to a binary, you can use:
cc -Wl,-rpath,...at compile time.install_name_tool -add_rpath ...afterwards.So if you can recompile both the library and the main binary:
If you can only recompile the main binary:
If you cannot recompile either:
Note that if any of your binaries are signed, this will of course invalidate that signature. Use
codesign -f ...to replace the existing signature(s).