第十一章
运行库
静态
glibc
1
入口函数和程序初始化
入口函数
:在
main
函数之前和之后运行的函数称为入口函数或入口点(
entry point
)。
一个典型的程序运行如下:
(1)
进程创建,控制权交给入口点,入口点为运行库中的入口函数
(2)
入口函数对运行库和程序运行环境进行初始化,包括堆,
I/O
、线程、全局变量构造等
(3)
初始化后,调用
main
函数
(4)
Main
函数完毕,调用入口函数,做清理工作,包括全局变量析构、堆销毁、关闭
I/O
,然后系统调用结束进程。
Glibc
入口函数
Glibc
的入口为
_start
,该入口为
ld
链接器默认的链接脚本指令的。
i386
的源码如下:
libc\sysdeps\i386\elf\Start.S
_start:
xorl %ebp, %ebp
popl %esi
movl $esp %ecx
pushl %esp //sp
pushl %edx //rtld_fini
pushl $__libc_csu_fini //fini
pushl $__libc_csu_init //init
pushl %ecx //ubp_av
pushl %esi //argc
pushl main //main
call __libc_start_main
上面右边注释几位
__libc_start_main
的参数。
__libc_start_main
的工作如下:
__pthread_initialize_minimal();
_cax_atexit(rtld_fini, NULL, NULL);
__libc_init_first(argc, argv, __environ);
__cxa_atexit(fini, NULL, NULL);
(*init)(argc, argv, __environ);
加粗部分为注册函数,
main
函数结束时调用。在
__libc_start_main
末尾有:
result = main(argc, argv, __environ);
exit
( result );
在
exit
函数中,依次调用注册过的函数,然后调用
_exit
系统调用。因为程序要么按
main
函数返回,要么
exit()
结束,都要进入
exit
函数,能保证注册函数顺利结束。
MSVC CRT
入口函数
运行库与I/O
用户打开一个文件,获得该文件的文件描述符(
file description
)或者句柄。文件句柄总是内核的文件对象相关联的。
fd
为
0
、
1
、
2
分别代表标准输入、标准输出和错误输出。那么
fd
是什么呢?
每个进程都有一个私有的“打开文件表”,该表由操作系统内核管理,打开文件表是一个指针数组,而
fd
则是数组的下表。
I/O
初始化的职责是:需要在用户空间中建立
stdio
、
stdout
、
stderr
及其他对应的
FILE
结构,使得程序进入
main
之后可以直接使用
printf
、
scanf
等函数。
2
C/C++
运行库
C
语言运行库
C
语言标准库
Glibc
的启动文件
Glibc
除了
C
标准库之外,还有几个辅助程序运行时的运行库。分别是
crt1.o
、
crti.o
、
crtn.o
Crt1.o
中包含
_start
,由它负责调用
__libc_start_main
初始化
libc
并且调用
main
函数。由于
C++
和
ELF
文件改进,出现了在
main
函数之前
\
后执行的全局
\
静态对象的构造和析构,在目标文件中引入了
.init
和
.finit
。链接器在静态链接时会把输入目标文件总的
.init
和
.finit
段收集起来,并且与
crti.o
和
crtn.o
中的指令进行合并,形成一个
.init
和
.finit
。其中
crti.o
和
crtn.o
中的内容是整个初始化段的前部分和后部分。经过连接后,最终的形成
_init()
和
_finit
两个函数,由
__libc_csu_init
和
__libc_csu_fini
调用。
GCC
平台相关的文件
crtbeginT.o
和
crtend.o
实现
C++
全局对象的构造和析构。
.init
和
.finit
段提供了一个在
main
之前和之后运行代码的机制,真正全局构造和析构则由
crtbeginT.o
和
crtend.o
完成。
libgcc.a
为
GCC
提供的一个仿真计算库,软件仿真各种硬件不提供的运算。
libgcc_eh.a
支持
C++
的异常处理。
3
运行库与多线程
4
C++
全局构造与析构
_start
à__libc_start_main
àinit\__libc_csu_init
à_init
这里的
_init
正是
crti
中的
_init()
函数,经过反汇编,可以发现函数进入了
_start
à__libc_start_main
àinit\__libc_csu_init
à_init
à__do_global_crt_aux
在
__do_global_crt_aux
中有对
__CTOR_LIST__[i]
的循环调用。
GCC
在编译某个文件时,会遍历该文件中的所有全局对象,生成一个特殊的函数,对全局对象进行构造和析构:(以
helloworld
为例)
Static void GLOBAL__I_Hw(void)
{
Hw::Hw(); //
构造
Atexit(__tcf_1); //
析构
}
GCC
会在该文件的目标文件中生成
.ctors
段
,存放指向改函数的指针。
链接器在链接的时候,所有的
.ctors
段被合并成一个
.ctors
段,因为合并后的
.ctors
成了一个指针数组,存放全局构造函数的地址列表
__CTOR_LIST__
。
在链接的时候,
crtbegin.o
和
crtend.o
也包含
.ctors
段,
crtbegin.o
作为其实,存放的内容为
-1
,会被链接器修改为全局构造函数的数量,并且将起始地址定义为
__CTOR_LIST__
。
crtend.o
段的内容为全
0
,并定义符号
__CTOR_END__
指令段尾。
析构刚好相反,命名跟构造函数对应。
5
fread
实现