您的位置:首页 > 其它

『阿男的工程世界』*04 ldd,objdump与patchelf*

2017-01-05 00:00 197 查看
『阿男的工程世界』*04 ldd,objdump与patchelf*

我们可以使用ldd命令来查看一个binary file(二进制可执行文件)的共享库,以httpd为例:

[weli@fedora sbin]$ ldd httpd
linux-vdso.so.1 (0x00007fffe38da000)
libm.so.6 => /lib64/libm.so.6 (0x00007f236dbfc000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f236d98c000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f236d768000)
libaprutil-1.so.0 => not found
libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007f236d532000)
libexpat.so.1 => /lib64/libexpat.so.1 (0x00007f236d307000)
libdb-5.3.so => /lib64/libdb-5.3.so (0x00007f236cf45000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f236cd41000)
libapr-1.so.0 => not found
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f236cb23000)
libc.so.6 => /lib64/libc.so.6 (0x00007f236c762000)
/lib64/ld-linux-x86-64.so.2 (0x000055754302b000)
libfreebl3.so => /lib64/libfreebl3.so (0x00007f236c55e000)

从上面的执行情况来看,ldd的命令不但输出了使用到的共享库,还把调用到的共享库的目录列出来了。根据ldd的man page我们可以看到ldd命令的运行原理:

In the usual case, ldd invokes the standard dynamic linker (see ld.so(8)) with the LD_TRACE_LOADED_OBJECTS environment variable set to 1, which causes the linker to display the library dependencies.

我们因此可以知道ldd是调用ld.so去实际地加载程序,因此在加载过程中也就得知了所调用共享库的路径。此外在ldd的man page中还写道:

Be aware, however, that in some circumstances, some versions of ldd may attempt to obtain the dependency information by directly executing the program. Thus, you should never employ ldd on an untrusted executable, since this may result in the execution of arbitrary code. A safer alternative when dealing with untrusted executables is:

$ objdump -p /path/to/program | grep NEEDED


可以看到ldd的man page中建议我们使用更为安全的objdump命令,因此我们可以试试看:

[weli@fedora sbin]$ objdump -p httpd | grep NEEDED
NEEDED               libm.so.6
NEEDED               libpcre.so.1
NEEDED               libselinux.so.1
NEEDED               libaprutil-1.so.0
NEEDED               libcrypt.so.1
NEEDED               libexpat.so.1
NEEDED               libdb-5.3.so
NEEDED               libdl.so.2
NEEDED               libapr-1.so.0
NEEDED               libpthread.so.0
NEEDED               libc.so.6

因为objdump并不会加载和执行binary file,只是读取文件的elf头信息,因此安全。此外因为没有实际的加载过程,所以从上面的输出可以看到,并不能获得实际运行时加载的共享库位置信息。

接下来我们来聊一聊patchelf这个工具:首先这个工具的链接在这里^1。patchelf的能力是修改binary file的elf信息。因此,我们可以使用它修改httpd的共享库的默认加载路径。在elf中,定义默认加载路径的参数叫做RPATH。我们来使用patchelf来设置httpd的RPATH,下面是使用方法:

[weli@fedora sbin]$ ./patchelf --set-rpath '$ORIGIN/' httpd

我们设置httpd查找共享库的默认位置为httpd所在目录。$ORIGIN就是指代binary file所在的当前目录,具体定义^2如下:

$ORIGIN is a special variable that means ‘this executable’, and it means the actual executable filename, as readlink would see it, so symlinks are followed. In other words, $ORIGIN is special and resolves to wherever the binary is at runtime.

接下来我们可以使用objdump命令来验证RPATH被正确设置了:

[weli@fedora sbin]$ objdump -p httpd | grep PATH
RUNPATH              $ORIGIN/

我们还可以使用readelf命令达到同样的读取目的:

[weli@fedora sbin]$ readelf --dynamic httpd | grep PATH
0x000000000000001d (RUNPATH)            Library runpath: [$ORIGIN/]

有趣的一点是,我们使用rpath设置的是RPATH,但显示出来设置的是RUNPATH。这个在patchelf的文档中有说明:

* `--set-rpath', `--shrink-rpath' and `--print-rpath' now prefer DT_RUNPATH over DT_RPATH, which is obsolete.  When updating, if both are present, both are updated.  If only DT_RPATH is present, it is converted to DT_RUNPATH unless `--force-rpath' is specified.  If neither is present, a DT_RUNPATH is added unless `--force-rpath' is specified, in which case a DT_RPATH is added.

也就是说patchelf默认会设置RUNPATH,因为RPATH已经“过时”了。RPATH和RUNPATH的区别在于配置顺序。在这篇文档^3中,有关于共享库加载路径的配置顺序的说明:

1.	the RPATH binary header (set at build-time) of the library causing the lookup (if any)
2.	the RPATH binary header (set at build-time) of the executable
3.	the LD_LIBRARY_PATH environment variable (set at run-time)
4.	the RUNPATH binary header (set at build-time) of the executable
5.	/etc/ld.so.cache
6.	base library directories (/lib and /usr/lib)

也就是说RPATH的设置在LD_LIBRARY_PATH之前,而RUNPATH在之后。RUNPATH的好处是,用户在运行代码时,可以自己设置LD_LIBRARY_PATH变量来override掉RUNPATH的默认配置,使得整个配置更为灵活。

最后,让我们用ldd命令来验证RUNPATH的作用:

[weli@fedora sbin]$ ldd httpd
……
libm.so.6 => /lib64/libm.so.6 (0x00007fe457ce4000)
……

我们把libm.so.6这个文件copy到与httpd同目录:

[weli@fedora sbin]$ cp /usr/lib64/libm.so.6 .

因为我们设置了RUNPATH为$ORIGIN,所以我们再次使用ldd验证:

[weli@fedora sbin]$ ldd httpd
……
libm.so.6 => /home/weli/rpms/jboss-ews-2.1/httpd/sbin/./
……

可以看到RUNPATH生效了。最后我们再用LD_LIBRARY_PATH设置override掉RUNPATH的设置:

[weli@fedora sbin]$ LD_LIBRARY_PATH=/usr/lib64 ldd httpd
……
libm.so.6 => /usr/lib64/libm.so.6 (0x00007f8fb6a19000)
……

可以看到结果与我们预期的一样。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  阿男的工程世界