所以每个人都可能知道glibc的/lib/libc.so.6
可以像普通的可执行文件一样在shell中执行,在这种情况下它可以打印出版本信息并退出。这是通过在.so中定义入口点来完成的。对于某些情况,将其用于其他项目可能会很有趣。不幸的是,您可以通过ld的-e选项设置的低级入口点有点太低级:动态加载器不可用,因此您无法调用任何正确的库函数。因此,glibc在此入口点通过裸系统调用实现write()系统调用。
现在我的问题是,有人能想出一个很好的方法,如何从该入口点引导一个完整的动态链接器,以便可以从其他.so中访问函数吗?
答案 0 :(得分:48)
使用-pie
选项构建共享库似乎可以为您提供所需的一切:
/* pie.c */
#include <stdio.h>
int foo()
{
printf("in %s %s:%d\n", __func__, __FILE__, __LINE__);
return 42;
}
int main()
{
printf("in %s %s:%d\n", __func__, __FILE__, __LINE__);
return foo();
}
/* main.c */
#include <stdio.h>
extern int foo(void);
int main()
{
printf("in %s %s:%d\n", __func__, __FILE__, __LINE__);
return foo();
}
$ gcc -fPIC -pie -o pie.so pie.c -Wl,-E
$ gcc main.c ./pie.so
$ ./pie.so
in main pie.c:9
in foo pie.c:4
$ ./a.out
in main main.c:6
in foo pie.c:4
$
P.S。 glibc通过系统调用实现write(3)
,因为它没有其他地方可以调用(它已经是最低级别)。这与能够执行libc.so.6
无关。
答案 1 :(得分:4)
我一直希望在 pam_cap.so
中添加对此的支持,并发现了这个问题。正如@EmployedRussian 在他们自己的帖子的后续行动中所指出的那样,接受的答案在某些时候停止了工作。花了一段时间才弄清楚如何让这个工作再次起作用,所以这是一个有效的例子。
这个工作示例涉及 5 个文件,以展示一些相应的测试是如何工作的。
首先,考虑这个微不足道的程序(称之为 empty.c
):
int main(int argc, char **argv) { return 0; }
编译它,我们可以看到它是如何解析我系统上的动态符号的,如下所示:
$ gcc -o empty empty.c
$ objcopy --dump-section .interp=/dev/stdout empty ; echo
/lib64/ld-linux-x86-64.so.2
$ DL_LOADER=/lib64/ld-linux-x86-64.so.2
最后一行设置了一个 shell 变量供以后使用。
以下是构建我的示例共享库的两个文件:
/* multi.h */
void multi_main(void);
void multi(const char *caller);
和
/* multi.c */
#include <stdio.h>
#include <stdlib.h>
#include "multi.h"
void multi(const char *caller) {
printf("called from %s\n", caller);
}
void multi_main(void) {
multi(__FILE__);
exit(42);
}
const char dl_loader[] __attribute__((section(".interp"))) =
DL_LOADER ;
我们可以如下编译和运行:
$ gcc -fPIC -shared -o multi.so -DDL_LOADER="\"${DL_LOADER}\"" multi.c -Wl,-e,multi_main
$ ./multi.so
called from multi.c
$ echo $?
42
因此,这是一个可以作为独立二进制文件执行的 .so
。接下来,我们验证它可以作为共享对象加载。
/* opener.c */
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
void *handle = dlopen("./multi.so", RTLD_NOW);
if (handle == NULL) {
perror("no multi.so load");
exit(1);
}
void (*multi)(const char *) = dlsym(handle, "multi");
multi(__FILE__);
}
也就是说,我们动态加载共享对象并从中运行一个函数:
$ gcc -o opener opener.c -ldl
$ ./opener
called from opener.c
最后,我们链接到这个共享对象:
/* main.c */
#include "multi.h"
int main(int argc, char **argv) {
multi(__FILE__);
}
我们编译和运行的地方如下:
$ gcc main.c -o main multi.so
$ LD_LIBRARY_PATH=./ ./main
called from main.c
(注意,因为 multi.so
不在标准系统库位置,我们需要覆盖运行时使用 LD_LIBRARY_PATH
环境变量查找共享对象文件的位置。)
答案 2 :(得分:1)
不是很好的方法,但我会在.so周围创建一个小的包装器可执行文件,它调用我想要运行的入口点函数。
答案 3 :(得分:0)
我想你的ld -e
指向一个入口点,然后使用dlopen()
函数族来查找和引导其余的动态链接器。当然,你必须确保dlopen()
本身是静态链接的,或者你可能必须实现足够的自己的链接器存根来获取它(使用系统调用接口,如mmap()
就像libc一样本身就是这样。
这些对我来说都不是“好看”。事实上,仅仅想到阅读glibc源(以及ld-linux
源代码,作为一个例子)足以评估工作的大小听起来对我来说非常古怪。它也可能是一个可携带的噩梦。 Linux如何实现ld-linux
以及如何在OpenSolaris,FreeBSD等下完成链接之间可能存在重大差异。 (我不知道)。