Leopard Linking: making relocatable libraries

This post has been sitting around waiting for my NDA to lift as Leopard is released into the wild. This post is about migrating from executable_path to rpath as a means of bundling relocatable libraries with your application. This is a technique that I used to make Maya relocatable.

The Problem

Sometimes developers find it is necessary to pack up a few extra dylib shared libraries inside an application bundle. In that case, it becomes necessary to do some fancy linking so that your application is relocatable.

Normally a Mach-O binary (eg a dylib, bundle, or executable) maintains a list of shared libraries that it links to. By default that list will contain a list of absolute paths to the libraries. Lets look at a random example using otool -L to dump the list of linked libraries.

#  otool -L /usr/bin/more
/usr/bin/more:
    /usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0,...
    /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, cur...
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, cu...

You can see that the executable "more" links against three system dylibs. The absolute path to each dylib is listed. If you moved one of those dylibs, more would cease to function.

The system libraries are going to stay put. The real problem occurs when you use a non-system library (such as an open source library) in your application, and you'd like to stick it in your application bundle under .../Contents/Libraries. Lets say my example application is CodeShorts.app and I'm using the library liblua.dylib to use to embed the Lua scripting language (note I'd use Python, but it comes with OS X and therefore makes a terrible example). I'd like to have the following application layout.

CodeShorts.app/
    Contents/
        MacOS/
            CodeShorts
        Libraries/
            liblua.dylib

Now, you could link to liblua.dylib using the path /Applications/CodeShorts.app/Contents/Libraries/liblua.dylib, and hope that nobody moves your app. However, many Mac users love to spend hours a day reorganizing their beloved Macs, and who knows where CodeShorts.app will end up (hopefully not the trash).

The Old Way: executable_path

Apple has a couple of solutions for this. Until recently (pre Leopard), this solution was to stick the special name @executable_path into the library path as referenced by your application. So, CodeShorts would look for @executable_path/../Libraries/liblua.dylib. At runtime dyld (the dynamic linker) would substitute @executable_path for /Applications/CodeShorts.app/Contents/MacOS and you'd be off to the races. You could move CodeShorts.app around all afternoon and it would still run (unless you moved it to the Trash). This works reasonably well, but it does impose some restrictions. The two main restrictions are as follows: 1. the relative path between the executable and the library is fixed. If you want to use the same shared library from two executables, then both executables must share the same relative path to the dylib. 2. you can only specify one place to look for the shared library. You cannot have a multi-location search path

The New Way: rpath

If you are a developer who has used this technique, then you're in for a surprise when you try to use the old -executable_path flag on ld under Leopard. It's gone. Leaving you to wonder "what do I do now??"

One of the lovable, but frustrating things about Apple is that they force forward progress on their users and developers.

Now that Leopard is the prime cat, the new mechanism for this is rpath which will be familiar at least in name to developers from other platforms such as Linux. Apple has, of course, thrown in a few extras as they are wont to do. The main resources for the rpath changes are the dyld Release Notes for Mac OS X v10.5 and the ld manpage on Leopard.

At its most basic, the -rpath flag to ld allows you to specify a list of directories where dyld should look for library dependencies. So, for example if you link CodeShorts to liblua.dylib, you could specify -rpath /Applications/CodeShorts.app/Contents/Libraries -rpath /opt/local/lib. That would make the CodeShorts application look in two places for liblua.dylib and any other dependencies. That's a good start, but it does not address the relocation problem we set out to solve, unless you can guess every place the user might stick your application (-rpath /Users/JoeUser/.Trash).

To solve this problem, Apple has introduced two new special funky @ names that you can use. One is for use in your library's name that is embedded in the library. And one is for use in your rpath. We'll get to those in a minute.

First we need to look a bit deeper into library dependencies on OS X. When you link a shared library, framework, or bundle on OS X, its path is embedded into the Mach-O binary. That name is later used by other Mach-O binaries that reference your library. So, lets go back to our example above where we looked at /usr/bin/more.

#  otool -L /usr/bin/more
/usr/bin/more:
    /usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0,...
    /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, cur...
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, cu...

You can see that more uses /usr/lib/libncurses.5.4.dylib. Now, where did that path come from? When more was linked, the linker looked at libncurses.5.4.dylib and read the path name for the library and then added it to the list of dependencies for more. We can look at /usr/lib/libncurses.5.4.dylib and see what the path name is:

# otool -L /usr/lib/libncurses.5.4.dylib
/usr/lib/libncurses.5.4.dylib:
    /usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0,...
    /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, cur... 
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, cu...

The first entry is the name of the Mach-O binary. When more was linked, ld looked at libncurses.5.4.dylib and read the path /usr/lib/libncurses.5.4.dylib and added it to more's dependencies.

Now, back to those two special @ names. The first special name is @rpath. This name can be used to lookup a library at a path relative to the directories in the loader's rpath. You would use this when the library will live in a certain subdirectory somewhere along the rpath. While it might not be obvious why you would need this, it becomes quickly apparent when you think of Frameworks where the actual Mach-O library lives buried in a sub directory. You don't want to have to specify an rpath entry for each individual framework. You just want to add your Framework directory to the rpath and have each framework picked up via its own relative install name.

It's time to create a contrived example. So, lets say you have two frameworks: BigLibrary.framework and SmallLibrary.framework. Now, lets say that they both live in /opt/Frameworks (of course, you would usually put them in /Library/Frameworks, but OS X knows about that directory, so its not a good enough contrived example). Your two Mach-O binaries would be as follows:

/opt/
    Frameworks/
        SmallLibrary.framework/
            Versions/
                1.0/
                    SmallLibrary
        BigLibrary.framework/
            Versions/
                1.0/
                    BigLibrary

When you link to those Frameworks from your application, you would use the flag -rpath /opt/Frameworks. That way, your application will search /opt/Frameworks when it loads. However, there's no Mach-O dylibs in that directory. But, that's ok. Because, when you linked SmallLibrary you cleverly specified -install_name @rpath/SmallLibrary.framework/Versions/1.0/SmallLibrary. Likewise, you specified the relative path for BigLibrary as well. That means that dyld will look in /opt/Frameworks for SmallLibrary.framework/Versions/1.0/SmallLibrary and, it will find it. So, that takes care of @rpath.

Now, this still doesn't solve the problem of relocatable libraries (get on with it will you!).

This brings us to the second special name, @loader_path. This special name may be used at the beginning of a path that is passed to the -rpath flag. At runtime, dyld replaces @loader_path with the path to the Mach-O binary that loaded the library. The nice thing here is that the loader can be an executable or another shared library of some sort. This is much more flexible than the old @executable_path solution, which only referred to the main executable.

Now, we have the tools to do what we really want to do. We can now hide libraries and framework bundles inside our application bundle and have them found no matter where the user drags the application.

Lets say that we want the following disk structure for our application CodeShorts, using all of the frameworks and libraries that we described above.

CodeShorts.app/
    Contents/
        MacOS/
            CodeShorts
        Libraries/
            liblua.dylib
        Frameworks/
            SmallLibrary.framework/
                Versions/
                    1.0/
                        SmallLibrary
            BigLibrary.framework/
                Versions/
                    1.0/
                        BigLibrary

We've got two frameworks and a library embedded in our application. We need to tell the application CodeShorts what directories to look in when searching for the libraries and frameworks, and we've got to give the libraries and frameworks the correct relative paths so that they'll be found.

When we link the application CodeShorts we are going to give it two places to look for libraries and frameworks. We'll do that using the -rpath flag something like this:

Mach-O Binary: 
  CodeShorts.app/Contents/MacOS/CodeShorts

Linker Flags:  
  -rpath @loader_path/../Libraries -rpath @loader_path/../Frameworks

NOTE: gcc doesn't know the -rpath flag, so you've got to pass it through to ld like this: -Wl,-rpath,@loader_path/...

This means that, when dyld loads the CodeShorts application, it will look for libraries in the Libraries and Frameworks directories inside the application.

When the libraries are linked (obviously needs to happen before the app is linked) they should be told that their install name is relative to the rpath. For liblua.dylib this is easy.

Mach-O Binary: 
  CodeShorts.app/Contents/Libraries/liblua.dylib

Linker Flags:  
  -install_name @rpath/liblua.dylib

For the frameworks, this is a little trickier, but not much. You need to give the location of the actual dylib, which is burried in a sub directory of the framework. But, you need to give it relative to the rpath.

Mach-O Binary: 
  CodeShorts.app/Contents/Frameworks/SmallLibrary.framework/Versions/1.0/SmallLibrary
Linker Flags:  
  -install_name @rpath/SmallLibrary.framework/Versions/1.0/SmallLibrary

Mach-O Binary: 
  CodeShorts.app/Contents/Frameworks/BigLibrary.framework/Versions/1.0/BigLibrary
Linker Flags:  
  -install_name @rpath/BigLibrary.framework/Versions/1.0/BigLibrary

That should be it. This is probably a long winded explanation of a simple concept. However maybe my verbosity balances out the terseness of the Apple documentation.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值