Sunichi's Blog

sunichi@DUBHE | Linux & Pwn & Fuzz

0%

.init and .fini

基于Oracle关于Solaris的文档对程序初始化和终止部分进行学习。

初始化和终止步骤

初始化

在运行时链接器(runtime linker)将控制权交给一个应用之前,它会对程序和其它已经加载的依赖的初始化段进行处理。初始化段包括 .preinit_array.init_array.init,它们在构建动态对象(dynamic object)时由链接编辑器(link-editor)创建。

运行时链接器将会执行地址保存在.preinit_array.init_array段中的函数。这些函数会按照在数组中的顺序依次被执行。运行时链接器将.init段作为一个单独的函数来执行。如果一个动态对象同时拥有.init_array.init段,.init段会被优先处理。

动态对象可能会在.preinit_array段中提供预初始化函数。这些函数会在动态连接器完成进程映像(process image)建立和重定位后、其它任何初始化函数执行之前进行调用。共享对象中不允许使用预初始化函数。

任何在动态可执行文件中的.init都是由编译器驱动程序(compiler driver)提供的进程启动机制从应用程序本身调用的。在执行所有依赖的初始化部分后,动态可执行文件中的.init段将被调用。

终止

动态对象同样可以提供终止段,包括 .fini_array.fini ,它们由链接编辑器在构建动态对象时创建。任何终止段会被例如atexit记录。当进程调用exit或者dlclose从正在运行的进程中删除对象时,将会调用这些例程(routine)。

运行时链接器执行这些地址包含在 .fini_array 中的函数,这些函数的执行顺序与它们出现在数组中的顺序相反。运行时链接器将.fini作为单个函数执行。如果同时包含 .fini.fini_array ,则优先处理.fini_array

任何在动态可执行文件中的.fini都是由编译器驱动程序(compiler driver)提供的进程终止机制从应用程序本身调用的。在执行所有依赖的终止部分后,动态可执行文件中的.fini段将被调用。

初始化和终止顺序

确定运行时在进程内执行初始化和终止代码的顺序是涉及依赖性分析的复杂问题。这个技术发展至今,试图满足现代编程语言和编程技术的期望,但仍存在一些令用户难以满足的情况。理解和限制初始化和终止代码的内容可以提供灵活和可预测的程序运行时行为。

在Solaris 2.6发行版之前,依赖初始化过程以反向加载顺序调用,这与使用ldd显示的依赖项顺序相反。类似的,依赖终止顺序以加载顺序相同。但是,随着依赖层次结构变得更为复杂,这种简单的排序方法变得不合时宜。

从Solaris 2.6开始,运行时链接器构造已加载对象的拓扑列表,这个列表根据每个对象的依赖关系以及依赖关系之外的任何符号绑定所构建。初始化部分以依赖性的反向拓扑顺序执行,如果找到循环依赖关系,则不能对形成循环的对象进行拓扑排序。任何循环依赖的初始化部分以其反向加载顺序执行。类似的,终止过程以依赖关系的拓扑顺序执行,循环依赖关系以其加载顺序执行。

使用ldd与- i选项可以显示对象依赖项的初始化顺序。例如以下动态可执行文件及其依赖表现出循环依赖关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ dump -Lv B.so.1 | grep NEEDED
[1] NEEDED C.so.1
$ dump -Lv C.so.1 | grep NEEDED
[1] NEEDED B.so.1
$ dump -Lv main | grep NEEDED
[1] NEEDED A.so.1
[2] NEEDED B.so.1
[3] NEEDED libc.so.1
$ ldd -i main
A.so.1 => ./A.so.1
B.so.1 => ./B.so.1
libc.so.1 => /usr/lib/libc.so.1
C.so.1 => ./C.so.1
libdl.so.1 => /usr/lib/libdl.so.1

cyclic dependencies detected, group[1]:
./libC.so.1
./libB.so.1

init object=/usr/lib/libc.so.1
init object=./A.so.1
init object=./C.so.1 - cyclic group [1], referenced by:
./B.so.1
init object=./B.so.1 - cyclic group [1], referenced by:
./C.so.1

对于使用dlopen添加到正在运行的进程的任何对象,将重复初始化处理。对于调用dlclose,会对从进程卸载的任何对象执行终止处理。

符号绑定作为依赖分析的一部分进行,因为存在许多不能准确表达其依赖关系的共享对象,因此,合并符号绑定有助于产生更准确的依赖关系。但是向不表达所有依赖关系的对象添加符号绑定信息仍可能不足以确定对象的完全依赖关系。最常见的加载对象模型使用延迟绑定。使用此模型,在初始化处理之前仅处理直接引用符号绑定,来自延迟引用的符号绑定可能处于未决状态,并且可能扩展到目前为止建立的依赖关系。

由于对象的依赖性分析可能不完整,并且经常存在循环依赖,因此运行时链接器还提供动态初始化。此初始化尝试在调用同一对象中的任何函数之前执行初始化部分。在延迟符号绑定期间,运行时链接器确定是否已调用绑定的对象的初始化部分,如果不是则在符号绑定过程返回之前调用它们。

使用ldd无法显示动态初始化,但是,通过设置LD_DEBUG可以在运行时观察到确切的初始化调用序列。动态初始化尽在处理延迟引用时可用,使用LD_BIND_NOW-z now构建对象或dlopen使用模式RTLD_NOW引用的对象可以绕过任何动态初始化。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ cat main.c

#include <stdio.h>

void foo() {
(void) printf("initializing: foo()\n");
}

void bar() {
(void) printf("finalizing: bar()\n");
}

main() {
(void) printf("main()\n");
return (0);
}


$ cc -o main -zinitarray=foo -zfiniarray=bar main.c
$ main
initializing: foo()
main()
finalizing: bar()

原文:https://docs.oracle.com/cd/E19683-01/817-1983/6mhm6r4es/index.html

示例:https://docs.oracle.com/cd/E19253-01/819-7050/chapter2-48195/index.html