Changing ELF Runpaths (Code Included) |
Ali Bahrami Tuesday June 12, 2007
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 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.
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.% elfdump -d /usr/bin/crle | grep 'R*PATH' [4] RUNPATH 0x612 $ORIGIN/../lib [5] RPATH 0x612 $ORIGIN/../lib
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.
This change does two things:PSARC 2007/127 Reserved space for editing ELF dynamic sections 6516118 Reserved space needed in ELF dynamic section and string table
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.% elfdump -d /usr/bin/crle | egrep 'R*PATH|STRPAD' [4] RUNPATH 0x612 $ORIGIN/../lib [5] RPATH 0x612 $ORIGIN/../lib [32] SUNW_STRPAD 0x200
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.
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
NAME rpath - set/get runpath of ELF dynamic objects SYNOPSIS rpath [-dr] file [runpath] DESCRIPTION rpath can display, modify, or delete the runpath of a dynamic ELF object. If called without a runpath argument and without the -r option, the current runpath, if any, is written to stdout. If -r is specified, the existing runpath is removed. If run- path is supplied, the runpath of the object is set to the new value. OPTIONS The following options are supported: -d Cause detailed ELF information about the ELF file and the changes being made to it to be written to stderr. -r Instead of adding or modifying the file runpath, rpath removes any DT_RPATH or DT_RUNPATH entries from the dynamic section of the file. This action completely removes any existing from the file. When this option is used, rpath does not allow the runpath argument.
Now, let's add a runpath to it:% rpath rpath % elfdump -d rpath | egrep 'R*PATH|STRPAD' [28] SUNW_STRPAD 0x200
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.% rpath rpath pointless:runpath % rpath rpath pointless:runpath % elfdump -d rpath | egrep 'R*PATH|STRPAD' [28] SUNW_STRPAD 0x1ee [30] RUNPATH 0x33f pointless:runpath
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:% LD_DEBUG=libs ./rpath 13707: 13707: hardware capabilities - 0x25ff7 [ AHF SSE3 SSE2 SSE FXSR AMD_3DNowx AMD_3DNow AMD_MMX MMX CMOV AMD_SYSC CX8 TSC FPU ] 13707: 13707: 13707: configuration file=/var/ld/ld.config: unable to process file 13707: 13707: 13707: find object=libelf.so.1; searching 13707: search path=pointless:runpath (RUNPATH/RPATH from file rpath) 13707: trying path=pointless/libelf.so.1 13707: trying path=runpath/libelf.so.1 13707: search path=/lib (default) 13707: search path=/usr/lib (default) 13707: trying path=/lib/libelf.so.1 13707: 13707: find object=libc.so.1; searching 13707: search path=pointless:runpath (RUNPATH/RPATH from file rpath) 13707: trying path=pointless/libc.so.1 13707: trying path=runpath/libc.so.1 13707: search path=/lib (default) 13707: search path=/usr/lib (default) 13707: trying path=/lib/libc.so.1 13707: 13707: find object=libc.so.1; searching 13707: search path=/lib (default) 13707: search path=/usr/lib (default) 13707: trying path=/lib/libc.so.1 13707: 13707: 1: 13707: 1: transferring control: rpath 13707: 1: usage: rpath [-dr] file [runpath] 13707: 1:
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.% rpath -r rpath % rpath rpath % elfdump -d rpath | egrep 'R*PATH|STRPAD' [28] SUNW_STRPAD 0x1ee
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.% rpath rpath pointless:runpath % rpath rpath pointless:runpath % elfdump -d rpath | egrep 'R*PATH|STRPAD' [28] SUNW_STRPAD 0x1ee [30] RUNPATH 0x33f pointless:runpath
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.
[6] Which...Files Are Stripped? | [8] Fake ELF Section Headers |