关于linux:加载可执行文件或执行库

Loading executable or executing a library

关于如何执行库或动态加载可执行文件有大量关于 SO 的问题。据我所知,所有答案都归结为:将您的可执行文件编译为与位置无关的代码并使用 dlopen 加载它。这很好用——并且在 macOS 上仍然很好用——直到最近 glibc 发生了变化,它明确禁用了 dlopening PIE。例如,此更改现在在 ArchLinux 上的当前 glibc (2.30) 版本中,并且尝试 dlopen 位置无关的可执行文件会给出错误:"无法动态加载与位置无关的可执行文件"。

很难猜测是什么促成了如此彻底的改变,破坏了如此多的代码和有用的用例。 (关于 Patchwork 和 Bugzilla 的解释对我来说没有多大意义。)但是现在有一个问题:如果您想创建一个也是动态库的可执行文件,或者反之亦然,该怎么办?

其中一条评论链接了一个解决方案。在这里复制它以供后代使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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\
");    
  _exit(0);
}

}

g++ -shared test-no-pie.cpp -o test-no-pie -Wl,-e,lib_entry 编译会生成一个共享对象(动态库),它也可以在 Linux 上执行。

我有两个问题:

  • 如果我想传递命令行参数怎么办?如何修改此解决方案以使其接受 arc,argv
  • 还有其他选择吗?

  • 请看这个答案:

    https://stackoverflow.com/a/68339111/14760867

    argc, argv 问题在那里没有得到解答,但是当我发现我需要一个时,我一起破解了一些东西以在运行时解析 /proc/self/cmdline 以供 pam_cap.so 使用。


    It's difficult to guess what prompted such a radical change

    并非如此:它从未正常工作过。

    that breaks so much code

    该代码已经以微妙的方式被破坏了。现在您可以清楚地看到它不起作用。

    Are there other alternatives?

    不要那样做吗?

    dlopen执行一个可执行文件解决了什么问题?

    如果这是一个真正的问题,请打开 GLIBC bugzilla 功能请求,解释该问题并请求支持的机制以达到预期的结果。

    更新:

    at least say why"it never worked correctly". Is it some triviality like potentially clashing globals between the executables, or something real?

    线程局部变量是一个不能正常工作的例子。你是否认为它们是"真实的"我不知道。

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // foo.c
    #include <stdio.h>

    __thread int var;

    __attribute__((constructor))
    static void init()
    {
      var = 42;
      printf("foo.c init: %d %p\
    ", var, &var);
    }

    int bar() {
      printf("foo.c bar:  %d %p\
    ", var, &var);
      return var;
    }

    int main()
    {
      printf("foo.c main: %d %p bar()=%d\
    ", var, &var, bar());
      return 0;
    }
    1
    2
    3
    4
    gcc -g foo.c -o foo -Wl,-E -fpie -pie && ./foo
    foo.c init: 42 0x7fb5dfd7d4fc
    foo.c bar:  42 0x7fb5dfd7d4fc
    foo.c main: 42 0x7fb5dfd7d4fc bar()=42
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 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\
    ", bar());
      return 0;
    }
    1
    2
    3
    4
    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.

    底线:这从未被设计为有效,而且您只是碰巧没有踩到许多地雷,所以您认为它有效,而实际上您正在执行未定义的行为。