我刚刚阅读了ELF文件中的init and fini sections并尝试了一下:
#include <stdio.h>
int main(){
puts("main");
return 0;
}
void init(){
puts("init");
}
void fini(){
puts("fini");
}
如果我执行gcc -Wl,-init,init -Wl,-fini,fini foo.c
并运行结果,那么&#34; init&#34;部分未打印:
$ ./a.out
main
fini
init部分没有运行,或者它无法以某种方式打印?
是否有任何&#34;官员&#34;关于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.
不应该意味着,命名init函数_init
就足够了吗? (如果我做gcc抱怨多重定义。)
答案 0 :(得分:11)
相反,使用适当的function attributes标记您的函数,以便编译器和链接器将它们放在正确的部分中。
例如,
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) */
}
您还可以指定一个优先级(例如__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;
}
因此您可以将其编译并链接为任何程序或库的一部分。要将其编译为共享库,请使用例如
gcc -Wall -Wextra -fPIC -shared tructor.c -Wl,-soname,libtructor.so -o libtructor.so
您可以使用
将其插入任何动态链接的命令或二进制文件中LD_PRELOAD=./libtructor.so some-command-or-binary
函数保持errno
不变,尽管在实践中无关紧要,并使用低级write()
系统调用将消息输出到标准错误。初始标准错误被复制到一个新的描述符,因为在许多情况下,标准错误本身在最后一个全局析构函数 - 我们的析构函数 - 运行之前被关闭。
(一些偏执的二进制文件,通常是安全敏感的二进制文件,关闭他们不知道的所有描述符,因此可能在所有情况下都看不到After main()
消息。)< / p>
答案 1 :(得分:6)
这不是ld
中的错误,而是主要可执行文件的glibc启动代码中的错误。对于共享对象,调用-init
选项设置的函数。
ld
添加选项-init
和-fini
的提交。
程序的_init
函数不是由动态链接器的DT_INIT
条目从文件glibc-2.21/elf/dl-init.c:58
调用,而是从文件glibc-2.21/csu/elf-init.c:83
中的__libc_csu_init
调用。主要的可执行文件
也就是说,启动会忽略程序的DT_INIT
中的函数指针。
如果使用-static
进行编译,则不会调用fini
。
DT_INIT
和DT_FINI
,因为它们是old-style, see line 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;
}
$ gcc -Wall a.c
$ ./a.out
preinit
init
main
fini
$