使用patchelf来解决shared library的解析问题


patchelf[1]可以在代码编译后直接修改elf及elf64格式的可执行文件,修改其RUNPATH,非常方便。这篇文章里给出一个例子。首先我们写两个c文件:foo.c以及bar.c。

localhost:work weli$ cat foo.c
int main(void) {
  say_hello();
  return 0;
}


foo.c是主程序,调用say\_hello方法。这个方法定义在bar.c当中:

localhost:work weli$ cat bar.c
#include <stdio.h>

void say_hello() {
  printf("Hello, Martian!\n");
}


我们将bar.c编译为共享库:

localhost:work weli$ cc -g -c -fPIC bar.c 
localhost:work weli$ ls
bar.c  bar.o  foo.c


注意到编译器的'-fPIC'选项,这个是编译共享库必须。PIC的全称是"Position Independent Code",它的含义[2]如下:

If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on the m68k, PowerPC and SPARC. Position-independent code requires special support, and therefore works only on certain machines.


接下来我们使用下述命令生成共享库:

localhost:work weli$ cc -g -shared -Wl,-soname,libbar.so -o libbar.so bar.o
localhost:work weli$ ls
bar.c  bar.o  foo.c  libbar.so


和可以看见libbar.so被生成了[3]。然后编译foo.c并让其引用libbar.so:

localhost:work weli$ cc -g -o foo foo.c libbar.so 
localhost:work weli$ ls
bar.c  bar.o  foo  foo.c  libbar.so


我们可以使用ldd命令来查看foo的库依赖关系:

localhost:work weli$ ldd foo
	linux-vdso.so.1 =>  (0x00007fff8abfe000)
	libbar.so => not found
	libc.so.6 => /lib64/libc.so.6 (0x00000034c5c00000)
	/lib64/ld-linux-x86-64.so.2 (0x00000034c5800000)' 


注意到这一行:

libbar.so => not found


因为libbar.so在当前目录,不在默认的搜索路径中,因此foo在执行时是找不到libbar.so的:

localhost:work weli$ ./foo 
./foo: error while loading shared libraries: libbar.so: cannot open shared object file: No such file or directory


我们可以在运行foo时制定LD_LIBRARY_PATH来解决这个问题:

localhost:work weli$ LD_LIBRARY_PATH=./ ./foo
Hello, Martian!


有的时候我们希望不需要用户自己定制LD_LIBRARY_PATH,因此我们可以使用patchelf这个命令直接修改foo的RUN_PATH:

localhost:work weli$ patchelf --set-rpath "$(pwd)" ./foo


这样RUNPATH就被加进foo了:

localhost:work weli$ readelf -d foo | grep RUNPATH
 0x000000000000001d (RUNPATH)            Library runpath: [/home/weli/Desktop/work]


此时可以正确运行foo:

localhost:work weli$ ./foo 
Hello, Martian!


但是现在仍然有一个问题:加进foo的RUNPATH是一个绝对路径,而不是相对路径。解决这个问题的方法是使用特殊的$ORIGIN变量。在ld.so(8)的man page当中对$ORIGIN的说明是这样的:

> $ORIGIN (or equivalently $ORIGIN)
> 	This expands to the directory containing the program or shared library.


因此这个相对路径永远对应于程序本身所在的目录,也就是foo所在的目录。因为我们的libbar.so与foo位于同一目录,因此就可以将RUNPATH定义如下:

localhost:work weli$ patchelf --shrink-rpath foo
localhost:work weli$ patchelf --set-rpath '$ORIGIN/' foo


在上面的命令中,我们首先使用shrink-rpath将原有的RUNPATH清掉,然后再将其设置为$ORIGIN。然后我们可以用ldd命令来验证相对路径被正确解析了:

localhost:work weli$ ldd foo
	linux-vdso.so.1 =>  (0x00007fff1a29f000)
	libbar.so => /home/weli/Desktop/work/./libbar.so (0x00007fcbe43be000)
	libc.so.6 => /lib64/libc.so.6 (0x00000034c5c00000)
	/lib64/ld-linux-x86-64.so.2 (0x00000034c5800000)


小结

在本文中,我们简单介绍了patchelf的使用方法。除了修改RUNPATH,patchelf还提供了很多其它实用的功能,有兴趣可以看看它的文档作进一步学习。


[1] https://github.com/NixOS/patchelf
[2] http://stackoverflow.com/questions/966960/what-does-fpic-mean-when-building-a-shared-library
[3] 有关共享库的生成细节,SONAME的制定等等内容,我将放在单独的文章中解说。

你可能感兴趣的:(c,gcc,library,shared,patchelf)