Ali Bahrami |
Tuesday Jun 12, 2007
Changing ELF Runpaths (Code Included) A recent change to Solaris ELF files makes it possible to change the runpath of a dynamic executable or sharable object, something that has not been safely possible up until now. This change 80is currently found in Solaris Nevada (the current development version of Solaris) and in OpenSolaris. It is not yet available in Solaris 10, but in time will appear in the standard shipping Solaris as well. This seems like a good time to talk about runpaths and the business of how the runtime linker finds dependencies. I also provide a small program named rpath that you can use to modify the runpaths in your file (assuming they were linked under Nevada or OpenSolaris). The Runpath ProblemThe runtime linker looks in the following places, in the order listed, to find the sharable objects it loads into a process at startup time:
The above scheme offers a great deal of flexibility, and it usually works well. There is however one notable exception the "Runpath Problem". The problem is that many objects are not built with a correct runpath, and once an object has been built, it has not been possible to change it. It is common to find objects where the runpath is correct on the system the object was built on, but not on the system where it is installed. Usually, we deal with this all too common situation by setting LD_LIBRARY_PATH, or by creating a linker configuration file with crle. Such solutions have serious downsides, as detailed in an earlier blog entry by Rod Evans entitled "LD_LIBRARY_PATH - just say no". Both approaches will cause unrelated programs to look in unnecessary additional directories for their dependencies. At best, this imposes unnecessary overhead on their operation. At worst, they may end up binding to the wrong version of a given library, leading to mysterious and hard to debug failures. The environment variable approach is simply too broad. One important technique that people sometimes use, is to set the environment variables in a wrapper shell script, that may look something like: This is a huge improvement over simply setting LD_CONFIG or LD_LIBRARY_PATH in your shell login config script (.profile, .cshrc, .bashrc, etc), for many reasons:#!/bin/sh # # Run myapp, setting LD_LIBRARY_PATH so it will run LD_LIBRARY_PATH="/this/that/theother:/someplace/else" export LD_LIBRARY_PATH exec /usr/local/myapp
It would be far better to modify the object in question and set a runpath that accurately reflects the actual location of its dependencies. The effect of a runpath is limited to the file that contains it, so this solution does not "bleed through" to unrelated files, and it imposes no unnecessary overhead on the general operation of the system. This would be a superior solution if it were possible. However it hasn't been an option until recently. How Runpaths Are ImplementedEvery dynamic executable contains a dynamic section. This is an array of items which convey the information required by the dynamic linker (ld.so.1) to do its work. If an object has a runpath, there will be a DT_RUNPATH and/or DT_RPATH item in the dynamic section (there is more than one of these for historical reasons). As an example, lets examine crle:
The string (in this case, "$ORIGIN/../lib") is not actually
stored in the dynamic section. Rather, it is contained in the
dynamic string table (.dynstr). The value 0x612 is the offset within
string table at which the desired string starts.
A string table is a section that contains NULL terminated strings, one immediately following the other. To access a given string, you add the offset of the string within the section to the base of the section data area. Consider a string table that contains the names of two variables "var1", and "var2" and a runpath "$ORIGIN/../lib". By ELF convention, string tables always have a 0-length NULL terminated string in the first position. In C language notation, we might declare the contents of the resulting string table section containing these 4 strings as The indexes of the 4 strings in our table are [0], [1], [6], and [11], and any item in the dynamic section or the dynamic symbol table that needs one of these strings will specify it using the appropriate index. An interesting result of the way that string tables are designed is that that every single offset into a string table represents a usable string. Although our intent with the C string above was to represent 4 strings, it actually contains 23 potential strings (26 if you count the duplicate NULL strings), and not just the 4 we intentionally inserted. Listing them by offset, they are:"\0var1\0var2\0$ORIGIN/../lib" This is a very efficient scheme, since each string can appear once in the string table, and multiple ELF items can refer to it. Also, it allows fixed size things, like ELF symbols or dynamic section entries, to efficiently reference variable length strings. There are two things to note, however:[0] "" [1] "var1" [2] "ar1" [3] "r1" [4] "1" [5] "" [6] "var2" [7] "ar2" [8] "r2" [9] "2" [10] "" [11] "$ORIGIN/../lib" [12] "ORIGIN/../lib" [13] "RIGIN/../lib" [14] "IGIN/../lib" [15] "GIN/../lib" [16] "IN/../lib" [17] "N/../lib" [18] "/../lib" [19] "../lib" [20] "./lib" [21] "/lib" [22] "lib" [23] "ib" [24] "b" [25] ""
The options for modifying a runpath in this situation are limited:
As a result, it has not been possible to support the modification of the runpath in an existing object up until recently. Making RoomI recently integrated a change to Solaris Nevada (and OpenSolaris) to add a little unused space to our ELF files, in order to facilitate a limited amount of post-link modification:
This change does two things:
The SUNW_STRPAD entry tells us that the dynamic string table has 512 (0x200)
bytes of unused space available at the end of its data area.
The way this works is very simple: If a file lacks a DT_SUNW_STRPAD dynamic entry, then we know that it is an older file, and that the dynamic string table does not have any extra space. If it does have a DT_SUNW_STRPAD, then its value tells us how much room is available. In this case, we can add the string, modify the DT_RUNPATH items, and reduce the DT_SUNW_STRPAD value by the number of bytes we used. If the value in DT_SUNW_STRPAD is too small for our new string, then we are out of luck and cannot add it. This extra room should help in the vast majority of cases, but as with any such approach, there are limits. We recommend the use of the special $ORIGIN token, both because it is a great way to organize objects, and because it is short. The rpath UtilityEventually, Solaris will ship with a standard utility for modifying runpaths. However, there is no need to wait. I have written an unofficial test program I call 'rpath' that you can download and build. To build rpath, you will need a version of Solaris Nevada newer than build 61, or a recent version of OpenSolaris. To check your system, try:If your grep doesn't find DT_SUNW_STRPAD, your system lacks the necessary support.% grep DT_SUNW_STRPAD /usr/include/sys/link.h #define DT_SUNW_STRPAD 0x60000019 /* # of unused bytes at the */ To build rpath, unpack the compressed tar file and type 'make'. If you are using gcc, first edit the Makefile and uncomment the CC line: rpath is used as follows:% gunzip < rpath.tgz | tar xvpf - % cd rpath % make
Using rpathLet's use rpath to look at its own runpath. We will see that it doesn't have one, something that can be verified using elfdump:
Now, let's add a runpath to it:
Notice that the amount of unused space reported by SUNW_STRPAD has gone down
from 512 (0x200) to 494 (0x1ee) bytes, a reduction of 18 bytes. This
makes sense,
since we added a 17 character string, and we must add a NULL termination.
We can observe the runtime linker looking in 'pointless' and 'runpath' as it loads rpath (note: output is edited for width):
Finally, we'll remove the runpath we just added:
Note that even though the runpath is gone, the amount of available extra
space in the dynamic string section did not go back up from 494 (0x1ee)
to 512 (0x200). Adding
strings is a one way operation. Once they are added, they are permanent.
So even though you now have the ability
to add strings of moderate length, you won't want to do it indiscriminately.
On the plus side, you can always re-add the same runpath back without using any more space:
rpath found that the string 'pointless:runpath' was already in
the string table, so it used it without inserting another copy.
ConclusionsOur best advice has always been that the LD_LIBRARY_PATH environment variable should not be used to work around objects with bad or missing runpaths. It is best to rebuild such objects and set the runpath correctly. This hasn't changed, and you should always do so if you can.The problem with that advice is that there are times when all you have is the object, and no option to rebuild. In that case, LD_LIBRARY_PATH has been a necessary evil (and one that we've been glad to have). With the advent of objects that can have their runpaths modified, we now have a better answer, and the use of LD_LIBRARY_PATH for this purpose should be allowed to slowly fade away.
Posted at 03:49PM Jun 12, 2007 by ali in Sun | Comments[3] |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted by Chris Quenelle on June 20, 2007 at 07:20 PM MDT #
Posted by Asaf Amit on July 09, 2007 at 10:35 PM MDT #
Posted by Ali Bahrami on July 09, 2007 at 11:21 PM MDT #