如何使一个Linux共享对象(库)自行运行?

注意到gcc -shared创build了一个可执行文件,我只是有一个奇怪的想法来检查当我尝试运行它时发生了什么…以及结果是我自己的lib的段错误。 所以,对此很好奇,我尝试“运行”glibc( /lib/x86_64-linux-gnu/libc.so.6在我的系统上)。 果然,它没有崩溃,但提供了一些输出:

 GNU C Library (Debian GLIBC 2.19-18) stable release version 2.19, by Roland McGrath et al. Copyright (C) 2014 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Compiled by GNU CC version 4.8.4. Compiled on a Linux 3.16.7 system on 2015-04-14. Available extensions: crypt add-on version 2.1 by Michael Glad and others GNU Libidn by Simon Josefsson Native POSIX Threads Library by Ulrich Drepper et al BIND-8.2.3-T5B libc ABIs: UNIQUE IFUNC For bug reporting instructions, please see: <http://www.debian.org/Bugs/>. 

所以我的问题是:这背后的魔法是什么? 我不能只在图书馆里定义一个main标志 – 或者我可以吗?

Solutions Collecting From Web of "如何使一个Linux共享对象(库)自行运行?"

我写了一篇关于这个主题的博客文章 ,因为我觉得这很有趣。 你可以在下面找到我的原始答案。


您可以使用-Wl,-e,entry_point选项指定链接器的自定义入口点,其中entry_point是库的“main”函数的名称。

 void entry_point() { printf("Hello, world!\n"); } 

链接器不会期望与-shared链接的东西作为可执行文件运行,并且必须提供一些关于程序可运行的更多信息。 如果现在尝试运行库,则会遇到分段错误。

.interp部分是操作系统运行应用程序所需的二进制文件的一部分。 如果不使用-shared它由链接器自动设置。 如果要构建要自行执行的共享库,则必须在C代码中手动设置此部分。 看到这个问题 。

解释器的工作是查找和加载程序所需的共享库,准备程序运行,然后运行。 对于Linux上的ELF格式(现代* nix无处不在),使用ld-linux.so程序。 看到它的手册页获取更多信息。

下面的行使用GCC属性在.interp节中放置一个字符串。 把它放在你的库的全局范围内,以明确地告诉链接器你想在你的二进制文件中包含一个动态链接器路径。

 const char interp_section[] __attribute__((section(".interp"))) = "/path/to/ld-linux"; 

找到ld-linux.so路径的最简单方法是在任何正常的应用程序上运行ldd 。 来自我的系统的示例输出:

 jacwah@jacob-mint17 ~ $ ldd $(which gcc) linux-vdso.so.1 => (0x00007fff259fe000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007faec5939000) /lib64/ld-linux-x86-64.so.2 (0x00007faec5d23000) 

一旦你指定了解释器,你的库应该是可执行的! 只有一个小小的缺陷:当entry_point返回时它会发生段entry_point

当你用main编译一个程序的时候,它不是执行它的第一个被调用的函数。 main实际上是由另一个名为_start函数调用的。 这个函数负责设置argvargc等初始化。 然后它调用main 。 当main返回时, _start调用main的返回值exit

_start堆栈没有返回地址,因为它是第一个被调用的函数。 如果尝试返回,则发生无效读取(最终导致分段错误)。 这正是我们的入口点功能所发生的。 添加一个电话exit作为您的输入功能的最后一行,以妥善清理,而不是崩溃。

example.c

 #include <stdio.h> #include <stdlib.h> const char interp_section[] __attribute__((section(".interp"))) = "/path/to/ld-linux"; void entry_point() { printf("Hello, world!\n"); exit(0); } 

gcc example.c -shared -fPIC -Wl,-e,entry_point