Glibc的辅助程序启动文件分析


_start  // sysdep/i386/start.S    (实际上就是编译后的crt1.o)

--->  __libc_start_main (main, argc, argv, __libc_csu_init, __libc_csu_fini, rtld_fini, stack_end)  // csu/libc-start.c

--->  pthread_minimal_initialize

--->  __libc_init_first        

                --->  __libc_scu_init                  // csu/elf-init.c

                                     --->  _init                      // .init(crti.o) + .init(*.o) + .init(crtn.o)

---> __do_global_ctors_aux {

while()

}

        ---> main

                        --->  exit


我们可以看一下对应的反汇编文件

1. objdump -d /usr/lib/i386-linux-gnu/crti.o


2. objdump -d /usr/lib/i386-linux-gnu/crtn.o



3. 下面是我们最熟悉的hello world可执行文件对应的.init section 和 .fini section



下面内容是摘自《程序员的自我修养--链接、装载和库》

11.2.3  glibc与MSVC CRT(2)

于是在最终链接完成之后,输出的目标文件中的".init"段只包含了一个函数_init(),这个函数的开始部分来自于crti.o的".init"段,结束部分来自于crtn.o的".init"段。为了保证最终输出文件中".init"和".finit"的正确性,我们必须保证在链接时,crti.o必须在用户目标文件和系统库之前,而crtn.o必须在用户目标文件和系统库之后。链接器的输入文件顺序一般是:

ld crt1.o crti.o [user_objects] [system_libraries] crtn.o
由于crt1.o(crt0.o)不包含".init"段和".finit"段,所以不会影响最终生成".init"和".finit"段时的顺序。输出文件中的".init"段看上去应该如图11-8所示(对于".finit"来说也一样)。
 
图11-8  .init段的组成

在默认情况下,ld链接器会将libc、crt1.o等这些CRT和启动文件与程序的模块链接起来,但是有些时候,我们可能不需要这些文件,或者希望使用自己的libc和crt1.o等启动文件,以替代系统默认的文件,这种情况在嵌入式系统或操作系统内核编译的时候很常见。GCC提高了两个参数"-nostartfile"和"-nostdlib",分别用来取消默认的启动文件和C语言运行库。

其实C++全局对象的构造函数和析构函数并不是直接放在.init和.finit段里面的,而是把一个执行所有构造/析构的函数的调用放在里面,由这个函数进行真正的构造和析构,我们在后面的章节还会再详细分析ELF/Glib和PE/MSVC对全局对象构造和析构的过程。

除了全局对象构造和析构之外,.init和.finit还有其他的作用。由于它们的特殊性(在main之前/后执行),一些用户监控程序性能、调试等工具经常利用它们进行一些初始化和反初始化的工作。当然我们也可以使用"__attribute__((section(".init")))"将函数放到.init段里面,但是要注意的是普通函数放在".init"是会破坏它们的结构的,因为函数的返回指令使得_init()函数会提前返回,必须使用汇编指令,不能让编译器产生"ret"指令。

GCC平台相关目标文件

就这样,在第2章中我们在链接时碰到过的诸多输入文件中,已经解决了crt1.o、crti.o和crtn.o,剩下的还有几个crtbeginT.o、libgcc.a、libgcc_eh.a、crtend.o。严格来讲,这几个文件实际上不属于glibc,它们是GCC的一部分,它们都位于GCC的安装目录下:

/usr/lib/gcc/i486-Linux-gnu/4.1.3/crtbeginT.o

/usr/lib/gcc/i486-Linux-gnu/4.1.3/libgcc.a

/usr/lib/gcc/i486-Linux-gnu/4.1.3/libgcc_eh.a

/usr/lib/gcc/i486-Linux-gnu/4.1.3/crtend.o

首先是crtbeginT.o及crtend.o,这两个文件是真正用于实现C++全局构造和析构的目标文件。那么为什么已经有了crti.o和crtn.o之后,还需要这两个文件呢?我们知道,C++这样的语言的实现是跟编译器密切相关的,而glibc只是一个C语言运行库,它对C++的实现并不了解。而GCC是C++的真正实现者,它对C++的全局构造和析构了如指掌。于是它提供了两个目标文件crtbeginT.o和crtend.o来配合glibc实现C++的全局构造和析构。事实上是crti.o和crtn.o中的".init"和".finit"提供一个在main()之前和之后运行代码的机制,而真正全局构造和析构则由crtbeginT.o和crtend.o来实现。我们在后面的章节还会详细分析它们的实现机制。

由于GCC支持诸多平台,能够正确处理不同平台之间的差异性也是GCC的任务之一。比如有些32位平台不支持64位的long long类型的运算,编译器不能够直接产生相应的CPU指令,而是需要一些辅助的例程来帮助实现计算。libgcc.a里面包含的就是这种类似的函数,这些函数主要包括整数运算、浮点数运算(不同的CPU对浮点数的运算方法很不相同)等,而libgcc_eh.a则包含了支持C++的异常处理(Exception Handling)的平台相关函数。另外GCC的安装目录下往往还有一个动态链接版本的libgcc.a,为libgcc_s.so。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值