【问题标题】:Loading executable or executing a library加载可执行文件或执行库
【发布时间】:2021-02-15 22:42:24
【问题描述】:

在 SO 上有一个 large numberquestions 关于如何执行库或动态加载可执行文件。据我所知,所有答案都归结为:将您的可执行文件编译为与位置无关的代码并使用dlopen 加载它。这很好用 --- 在 macOS 上仍然很好用 --- 直到 recent change in glibc,它明确禁用了 dlopening PIE。例如,此更改现在在 ArchLinux 上的当前版本的 glibc (2.30) 中,并且尝试 dlopen 与位置无关的可执行文件会给出错误:“无法动态加载与位置无关的可执行文件”。

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

A solution 是从其中一个 cmets 链接的。在这里复制它以供后代使用:

#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 上执行。

我有两个问题:

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

【问题讨论】:

标签: linux shared-libraries executable glibc dlopen


【解决方案1】:

请看这个答案:

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

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

【讨论】:

    【解决方案2】:

    很难猜测是什么促成了如此彻底的变化

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

    破坏了这么多代码

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

    还有其他选择吗?

    不要那样做?

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

    如果确实存在问题,请打开 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 -fpie -pie && ./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.

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

    【讨论】:

    • 感谢您的示例。所以全局变量是问题所在。似乎仍然像把婴儿和洗澡水一起扔出去。另外,如果我将foo.c 编译为共享库,这是什么原因?即,这两种情况有何不同?
    • 我同意...我们使用可执行共享库是为了方便共享库,该共享库可以通过与 GDB 独立运行来进行调试。 (而不是编译另一个 dlopen 并运行 main 的可执行文件)但是,我们看到了许多微妙的问题,包括全局变量......
    • 如果示例代码使用-fPIC(如question 中已演示的那样)编译,则示例代码将正常工作。因此,它不能证明任何事情,并且在拿出更多证据之前,此答案中的所有内容都应仅视为意见和毫无根据的主张。
    • @foxcub 全局变量不是问题。
    猜你喜欢
    • 2014-09-05
    • 2015-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多