【问题标题】:building a .so that is also an executable构建一个 .so 这也是一个可执行文件
【发布时间】:2010-11-29 19:35:42
【问题描述】:

所以大家可能都知道glibc 的/lib/libc.so.6 可以像普通的可执行文件一样在shell 中执行,在这种情况下它会打印其版本信息并退出。这是通过在 .so 中定义一个入口点来完成的。在某些情况下,将其用于其他项目也可能很有趣。不幸的是,您可以通过 ld 的 -e 选项设置的低级入口点有点太低级:动态加载程序不可用,因此您无法调用任何适当的库函数。出于这个原因,glibc 在这个入口点通过一个裸系统调用来实现 write() 系统调用。

我现在的问题是,任何人都可以想出一种好方法,如何从该入口点引导一个完整的动态链接器,以便可以访问其他 .so 的函数?

【问题讨论】:

标签: c linux shared-libraries glibc dlopen


【解决方案1】:

更新 2:请参阅 Andrew G Morgan 的稍微复杂一点的解决方案,该解决方案适用于任何 GLIBC(该解决方案也用于 libc.so.6 本身(自永远以来),这就是您可以运行它的原因如./libc.so.6(以这种方式调用时会打印版本信息)。

更新 1:这不再适用于较新的 GLIBC 版本:

./a.out: error while loading shared libraries: ./pie.so: cannot dynamically load position-independent executable

2009 年的原始答案:

使用-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
$

附: glibc 通过系统调用实现write(3),因为它没有其他地方可以调用(它已经是最低 级别了)。这与能否执行libc.so.6无关。

【讨论】:

  • 我发现-shared 选项可能会阻止它工作,并给你一个分段错误。您必须确保 -shared 选项不存在或将其放在 -pie 之前,以便它被忽略。
  • 所以这就像在我的共享库中添加一个 main() 和 -pie 一样简单?
  • 这不再适用于较新的 GLIBC:./a.out: error while loading shared libraries: ./pie.so: cannot dynamically load position-independent executable
  • 看起来现在有一个可行的答案。
【解决方案2】:

我一直在寻求向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);
}

__attribute__((force_align_arg_pointer))
void multi_main(void) {
    multi(__FILE__);
    exit(42);
}

const char dl_loader[] __attribute__((section(".interp"))) =
    DL_LOADER ;

(2021-11-13 更新:强制对齐到 help __i386__ code be SSE compatible - 没有它我们很难调试 glibc SIGSEGV 崩溃。)

我们可以如下编译运行:

$ 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 环境变量覆盖运行时查找共享对象文件的位置。)

【讨论】:

    【解决方案3】:

    我想你会让你的ld -e 指向一个入口点,然后使用dlopen() 系列函数来查找和引导动态链接器的其余部分。当然,您必须确保 dlopen() 本身是静态链接的,或者您可能必须实现足够的自己的链接器存根来获取它(使用诸如 mmap() 之类的系统调用接口,就像 libc 本身正在做的那样。

    这些对我来说都不是“好”的。事实上,仅仅阅读 glibc 源代码(以及 ld-linux 源代码,作为一个例子)就足以评估工作的规模对我来说听起来很老套。这也可能是便携性的噩梦。 Linux 实现ld-linux 的方式与OpenSolaris、FreeBSD 等下的链接方式之间可能存在重大差异。 (我不知道)。

    【讨论】:

      猜你喜欢
      • 2021-06-18
      • 2014-07-03
      • 2011-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多