Dynamic Library Identification

This thread has been locked by a moderator.

I often find myself helping folks with dynamic library problems. These problems manifest in many different ways, but they all have one common factor: The dynamic library has an atypical install name. Sometimes this was inherited from macOS’s deep past, but most often it’s because folks aren’t aware of the two well-trodden paths through this particular minefield. This post is my attempt to rectify that.

If you have questions or comments about this, start a new thread here on DevForums. Tag it with Linker so that I see it.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


Dynamic Library Identification

Apple’s dynamic linker has a lot of flexibility. Much of this flexibility exists for historical reasons, and it’s better for modern programs to follow one of two well-trodden paths:

To understand these options, you need to know a little bit about how the dynamic linker works, which is the subject of this post.

Note This post covers some of the same ground as Embedding nonstandard code structures in a bundle, but in a less official way (-:

Install Name

Every dynamic library has an install name. This name identifies the library to the dynamic linker. When you link to a dynamic library, the static linker records the library’s install name in your Mach-O image. When the dynamic linker loads your Mach-O image, it uses those recorded install names to find and load the libraries you depend on.

Note There are many different aliases for the term install name, including id, identification name, or install path.

To see a library’s install name, run otool and examine the LC_ID_DYLIB load command:

% otool -l libWaffle.dylib | grep -A 2 LC_ID_DYLIB   
          cmd LC_ID_DYLIB
      cmdsize 48
         name @rpath/libWaffle.dylib …

Note The Mach-O load commands and their associated structures are defined in <mach-o/loader.h>. For example LC_ID_DYLIB is associated with the dylib_command structure.

To see the install names of the libraries imported by a Mach-O image, run otool and examine the LC_LOAD_DYLIB load commands:

% otool -l libVarnish.dylib | grep -A 2 LC_LOAD_DYLIB
          cmd LC_LOAD_DYLIB
      cmdsize 48
         name @rpath/libWaffle.dylib …
--
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name /usr/lib/libSystem.B.dylib …

Alternatively, use the -L option:

% otool -L libVarnish.dylib                              
…
    @rpath/libVarnish.dylib …
    @rpath/libWaffle.dylib …
    /usr/lib/libSystem.B.dylib …

This displays the library’s own install name first, followed by the install names of all the libraries it imports.

If you’re building a dynamic library with Xcode, use the Dynamic Library Install Name build setting to set the install name. If you’re building from the command line, pass the -install_name option to ld. For more about this, see the ld man page.

It’s best to set the install name at build time and not change it. However, if you’re working with an existing dynamic library that has the wrong install name, you can usually change it using install_name_tool. See the install_name_tool man page for details.

Runtime Path List

Way back in the day, a dynamic library’s install name was the full path of the installed library. That’s why it’s called the install name. However, full paths don’t work in some situations. For example, if you have a library embedded within an app, you can’t use an full path because the app might not be installed in the Applications folder.

To address this problem Apple updated the dynamic linker to support a number of special install name prefixes. At runtime it replaces the prefix with an appropriate path. For example, @executable_path is replaced with the path to the directory containing the processes main executable.

For a full list of these prefixes see the dyld man page. However, if you’re creating a dynamic library you’ll want to focus on the runtime path, @rpath, or just rpath for short. The exact details of how this works are complex, see the man page for the full story, but the basic gist is that:

  • The dynamic linker maintains a list of rpath directories.

  • When it loads a Mach-O image, the dynamic linker looks for LC_RPATH load commands in the image. For each one it finds, it adds a new directory to the rpath list.

  • When a Mach-O image imports a library with an rpath-relative install name, the dynamic linker searches for that library in each directory in the list.

This may sound a bit abstract, so you might want to hop on over to Dynamic Library Standard Setup for Apps for some examples of how this works in practice.

If you’re building a Mach-O image with Xcode, use the Runpath Search Paths build setting to add rpath directories. If you’re building from the command line, pass the -rpath option to ld. For more about this, see the ld man page.

It’s best to configure your rpath directories at build time. However, if you’re working with an existing Mach-O that has the wrong rpath directories, you can add, delete, and change them using install_name_tool. See the install_name_tool man page for details.

Up vote post of eskimo
1.3k views