我刚刚阅读了ELF文件中的init和fini部分并尝试了一下:
#include <stdio.h>
int main(){
puts("main");
return 0;
}
void init(){
puts("init");
}
void fini(){
puts("fini");
}
Run Code Online (Sandbox Code Playgroud)
如果我这样做gcc -Wl,-init,init -Wl,-fini,fini foo.c并运行结果,则不会打印"init"部分:
$ ./a.out
main
fini
Run Code Online (Sandbox Code Playgroud)
init部分没有运行,或者它无法以某种方式打印?
是否有关于init/fini的任何"官方"文档?
man ld 说:
-init=name
When creating an ELF executable or shared object, call
NAME when the executable or shared object is loaded, by
setting DT_INIT to the address of the function. By
default, the linker uses "_init" as the function to call.
Run Code Online (Sandbox Code Playgroud)
这不应该意味着,命名init函数就足够了_init吗?(如果我做gcc抱怨多重定义.)
Nom*_*mal 13
不要那样做; 让您的编译器和链接器按其认为合适的方式填写这些部分.
相反,使用适当的函数属性标记函数,以便编译器和链接器将它们放在正确的部分中.
例如,
static void before_main(void) __attribute__((constructor));
static void after_main(void) __attribute__((destructor));
static void before_main(void)
{
/* This is run before main() */
}
static void after_main(void)
{
/* This is run after main() returns (or exit() is called) */
}
Run Code Online (Sandbox Code Playgroud)
您还可以分配优先级(例如__attribute__((constructor (300)))),介于101和65535之间的整数,包括优先级较小的函数.
请注意,为了说明,我标记了功能static.也就是说,函数在文件范围之外是不可见的.这些函数不需要导出符号即可自动调用.
为了测试,我建议将以下内容保存在一个单独的文件中,例如tructor.c:
#include <unistd.h>
#include <string.h>
#include <errno.h>
static int outfd = -1;
static void wrout(const char *const string)
{
if (string && *string && outfd != -1) {
const char *p = string;
const char *const q = string + strlen(string);
while (p < q) {
ssize_t n = write(outfd, p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1 || errno != EINTR)
break;
}
}
}
void before_main(void) __attribute__((constructor (101)));
void before_main(void)
{
int saved_errno = errno;
/* This is run before main() */
outfd = dup(STDERR_FILENO);
wrout("Before main()\n");
errno = saved_errno;
}
static void after_main(void) __attribute__((destructor (65535)));
static void after_main(void)
{
int saved_errno = errno;
/* This is run after main() returns (or exit() is called) */
wrout("After main()\n");
errno = saved_errno;
}
Run Code Online (Sandbox Code Playgroud)
所以你可以编译并链接它作为任何程序或库的一部分.要将其编译为共享库,请使用eg
gcc -Wall -Wextra -fPIC -shared tructor.c -Wl,-soname,libtructor.so -o libtructor.so
Run Code Online (Sandbox Code Playgroud)
你可以使用它将它插入到任何动态链接的命令或二进制文件中
LD_PRELOAD=./libtructor.so some-command-or-binary
Run Code Online (Sandbox Code Playgroud)
函数保持errno不变,尽管在实践中无关紧要,并使用低级write()系统调用将消息输出到标准错误.初始标准错误被复制到一个新的描述符,因为在许多情况下,标准错误本身在最后一个全局析构函数 - 我们的析构函数 - 运行之前被关闭.
(一些偏执的二进制文件,通常是安全敏感的二进制文件,关闭所有他们不知道的描述符,因此在所有情况下都可能看不到该After main()消息.)
它不是一个bug,ld而是在主要可执行文件的glibc启动代码中.对于共享对象,调用该-init选项设置的函数.
ld添加选项-init和提交的提交-fini.
_init程序
的功能不是由动态链接器glibc-2.21/elf/dl-init.c:58的DT_INIT条目从文件调用,而是由主可执行__libc_csu_init文件从文件中调用glibc-2.21/csu/elf-init.c:83.
也就是说,DT_INIT启动会忽略程序中的函数指针.
如果你编译-static,fini也没有调用.
DT_INIT而DT_FINI绝对不应该被使用,因为它们是旧式的,请参阅线255.
以下作品:
#include <stdio.h>
static void preinit(int argc, char **argv, char **envp) {
puts(__FUNCTION__);
}
static void init(int argc, char **argv, char **envp) {
puts(__FUNCTION__);
}
static void fini(void) {
puts(__FUNCTION__);
}
__attribute__((section(".preinit_array"), used)) static typeof(preinit) *preinit_p = preinit;
__attribute__((section(".init_array"), used)) static typeof(init) *init_p = init;
__attribute__((section(".fini_array"), used)) static typeof(fini) *fini_p = fini;
int main(void) {
puts(__FUNCTION__);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
$ gcc -Wall a.c
$ ./a.out
preinit
init
main
fini
$
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
10235 次 |
| 最近记录: |