如果你知道ld-linux.so
这个文件,在我的环境下,它似乎区分32和64位。32位的它位于/usr/lib/ld-linux.so.2
并链接到/lib32/ld-linux.so.2
。64位的则位于/usr/lib64/ld-linux-x86-64.so.2
并链接到/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
。接下来我只考虑64位的ld-linux.so
。其他情况请自行查找自己的ld-linux.so
文件位置。
ld-linux.so
是一个动态库(共享库)文件,可以通过readelf -h查看这个文件类型,在Type中显示为DYN (Shared object file)
,是一个动态链接库文件。
它的用处是负责加载程序时查找并加载该程序依赖的动态库,另外你可以直接通过ld-linux.so [prog]
的方式来直接运行程序并跳过执行这个程序时的权限检查,但是可能并不能越权执行某些无权运行的程序。因为某些共享库可能在当前用户下无可读权限。
作为一个动态链接库,它可以被直接运行,你可能会比较好奇为什么作为一个动态库它可以被直接运行。在c - building a .so that is also an executable - Stack Overflow中的Andrew G Morgan
用户的回答有解释如何实现这个。
实际上,一个需要动态链接的ELF可执行程序包含.interp
段,其中包含由该程序指定的自己需要的动态链接器,对于这样一个动态链接的可执行程序,我们可以通过readelf -l a.out |grep interpreter
来查看这个可执行程序指定的动态链接器路径。而动态库不包括该段。
另外,elf文件是否能被运行的一个重要差别就是其程序入口的不同,一个动态库的程序入口为0,而一个可执行的elf文件的程序入口不为0。
其他差别也有,但是应该不会明显影响
因此,我们要实现最简单的一种情况,即:为动态库指定入口,让我们可以显式地使用ld-linux.so
动态链接器运行它。
为动态库指定入口
编写以下程序
1 |
|
使用gcc指令编译为共享库,并为链接器指定入口函数:gcc -fPIC -shared -o exe_so.so exe_so.c -Wl,-e,hello_world
。编译后如果你直接运行该程序,则会产生segment fault错误,这可能是由于未指定动态链接器导致,因为就如上边所说,可动态链接的elf可执行程序必须要有.interp
段。
但是我们依然有运行这个程序的方式,通过使用ld-linux.so来运行这个程序:执行/lib64/ld-linux-x86-64.so.2 ./exe_so.so
。运行并动态链接该程序。
1 |
|
它可以被成功运行。
为动态库添加.interp 段
我们使用gcc编译器,可以通过__attribute((section(".interp")))
指定.interp
段的内容。
使用方式const char dl_loader[] __attribute__((section(".interp"))) = "/lib64/ld-linux-x86-64.so.2";
Andrew G Morgan提到,需要为入口函数添加__attribute__((force_align_arg_pointer))
参数,但是我不知道为什么要这么写。如果不添加而运行,编译期不会报错,但是运行时依旧会报segment fault。
1 |
|
稍微修改
1 |
|
编译gcc -fPIC -shared -o exe_so.so exe_so.c -Wl,-e,hello_world_main
。
同样的,这个动态库可以被链接。
1 |
|
编译gcc use_hello_world.c ./exe_so.so
使用./exe_so.so
为了将exe_so动态库的位置写入输入到程序的可执行文件,便于运行时直接链接到动态库并调用。
其他
实际上这样做并不能像平常样子去使用argc和argv,我没有去研究为什么不能实现,或许查看ld-linux.so的源码可以了解到它为什么能实现可以作为动态库被链接,可以被直接运行,甚至可以传参的这样的一个程序。