关于如何执行库或动态加载可执行文件,SO上有large的number questions。据我所知,所有答案都归结为:将可执行文件编译为与位置无关的代码,并用dlopen
加载。直到recent change in glibc显式禁用了dlopen
ing PIE,这在MacOS上仍然有效。例如,此更改现在在ArchLinux的当前版本的glibc(2.30)中,并且尝试dlopen
与位置无关的可执行文件会产生错误:“无法动态加载与位置无关的可执行文件”。
很难猜测是什么促成这种根本性的改变,从而破坏了如此多的代码和有用的用例。 (对Patchwork和Bugzilla的解释对我来说没有多大意义。)但是现在有一个问题:如果要创建同时也是动态库的可执行文件,该怎么办?反之亦然?
A solution从其中一个评论链接。为了后代在此复制:
#include <stdio.h>
#include <unistd.h>
const char service_interp[] __attribute__((section(".interp"))) = "/lib/ld-linux-x86-64.so.2";
extern "C" {
void lib_entry(void)
{
printf("Entry point of the service library\n");
_exit(0);
}
}
用g++ -shared test-no-pie.cpp -o test-no-pie -Wl,-e,lib_entry
编译会产生一个共享对象(动态库),该对象也可以在Linux上执行。
我有两个问题:
arc,argv
?很难猜测是什么导致了如此根本的改变
不是真的:它永远无法正常工作。
会破坏很多代码
该代码已经以微妙的方式被破坏。现在,您可以清楚地知道它将不起作用。
还有其他选择吗?
不这样做?
dlopen
可执行文件解决了什么问题?
如果是真正的问题,请打开GLIBC bugzilla功能请求,解释该问题并请求受支持的机制以实现所需的结果。
更新:
至少说出为什么“它永远无法正确工作”。是一些琐碎的事情,例如可能在可执行文件之间冲突全局变量,还是真实的东西?
线程局部变量是无法正常工作的示例。无论您认为它们是“真实的”,我都不知道。
这里是代码:
// foo.c
#include <stdio.h>
__thread int var;
__attribute__((constructor))
static void init()
{
var = 42;
printf("foo.c init: %d %p\n", var, &var);
}
int bar() {
printf("foo.c bar: %d %p\n", var, &var);
return var;
}
int main()
{
printf("foo.c main: %d %p bar()=%d\n", var, &var, bar());
return 0;
}
gcc -g foo.c -o foo -Wl,-E && ./foo
foo.c init: 42 0x7fb5dfd7d4fc
foo.c bar: 42 0x7fb5dfd7d4fc
foo.c main: 42 0x7fb5dfd7d4fc bar()=42
// main.c
// Error checking omitted for brevity
#include <dlfcn.h>
#include <stdio.h>
int main()
{
void *h1 = dlopen("./foo", RTLD_LOCAL|RTLD_LAZY);
int (*bar)(void) = dlsym(h1, "bar");
printf("main.c: %d\n", bar());
return 0;
}
gcc -g main.c -ldl && ./a.out
foo.c init: 42 0x7fb7305da73c
foo.c bar: 0 0x7fb7305da73c <<< what?
main.c: 0 <<< what?
正在使用GNU C Library (Debian GLIBC 2.28-10) stable release version 2.28.
底线:这从来没有设计成能起作用的,只是碰巧没有踩到许多地雷,所以您以为它正在起作用,而实际上您正在执行未定义的行为。