Next: , Previous: Label Output, Up: Assembler Format


17.21.5 如何处理初始化函数

一些语言被编译后的代码中会包含构造者(也称为初始化例程)—当程序启动时, 用来初始化程序中的数据的函数。这些函数需要在程序开始前被调用—也就是说, 在调用main之前。

编译一些语言还会生成析构者(也成为终止例程),将在程序终止时被调用。

为了让初始化和终止函数工作,编译器必须在汇编代码中输出一些东西, 使得那些函数在适当的时候被调用。当你将编译器移植到一个新的系统时, 你需要指定如何做。

GCC目前支持两种主要的方式,来执行初始化和终止函数。 每种方式都有两个变体。大多数结构体对于这四个变体都是通用的。

连接器必须构建两个这些函数的链表—一个是初始化函数链表, 叫做__CTOR_LIST__,一个是终止函数,叫做__DTOR_LIST__

每个列表总是起始于一个被忽略的函数指针(其可能为0,-1, 或者之后的函数指针的个数,这取决于具体环境)。 随后是一系列指向构造者(或析构者)的零个或多个函数指针, 然后是一个包含0的函数指针。

取决于操作系统和它的可执行文件格式,或者crtstuff.c, 或者libgcc2.c会在起始时间和退出时间遍历这些列表。 构造者按照列表相反的顺序被调用;析构者按照向前的顺序。

处理静态构造者的最好的方式, 只有在目标文件格式提供任意命名section的时候才可以工作。 这会在构造者列表旁边设置一个section,析构者列表旁边设置另一个, 通常称作‘.ctors’和‘.dtors’。 每个定义了初始化函数的目标文件,还在构造section中放入一个字, 以指向那个函数。 连接器将所有这些字累积放入到一个邻近的‘.ctors’ section中。 终止函数按照类似的方式处理。

如果定义了TARGET_ASM_NAMED_SECTION, 则该方法将被target-def.h作为缺省方式选择。 不支持任意section,但是支持特定的构造者和析构者section的target, 可以定义CTORS_SECTION_ASM_OPDTORS_SECTION_ASM_OP 来达到相同的效果。

当支持任意section时,有两个变体, 取决于如何调用crtstuff.c中的代码。 在支持.init section(其在程序起始时被执行)的系统上, crtstuff.c的部分代码被编译到那个section中。 程序按照类似于下面的方式被gcc驱动连接:

     ld -o output_file crti.o crtbegin.o ... -lgcc crtend.o crtn.o

函数(__init)的序言出现在crti.o.init section中; 尾声出现在crtn.o中。同样的,函数__fini.fini section中。 通常这些文件由操作系统或者GNU C库提供,但GCC也为一些target提供。

目标文件crtbegin.ocrtend.o是从crtstuff.c 中编译出来的(对于大多target)。它们包含了,在.init.fini section中的代码片断,用于跳转到.text section中的例程中。 连接器会把section的所有部分放在一起,形成完整的__init函数, 其可以在起始处调用我们需要的例程。

macro properly. 要使用这个变体,你必须适当的定义INIT_SECTION_ASM_OP宏。

如果init section不可用,GCC在编译任何叫做main(或者更加确切的说, 任何被语言前端expand_main_function指定为程序入口点的函数)的函数时, 其会插入一个调用__main的程序,以作为在函数序言之后首先执行的代码。 __main函数在libgcc2.c中定义,并运行全局的构造者。

对于文件格式不支持任意section的,也有两个变体。对于最简单的变体, 必须使用GNU连接器(GNU ld)和'a.out'格式。在这种情况下, TARGET_ASM_CONSTRUCTOR被定义, 用来生成一个‘N_SETT’类型的.stabs条目, 来引用名字__CTOR_LIST__, 并且其值为一个包含了初始化代码的void函数地址。 GNU连接器将其识别为一个要设定的值;该值会被累积, 并最终作为一个向量放在可执行程序中, 一个前导(被忽略的)数目和一个尾部的0元素。 TARGET_ASM_DESTRUCTOR按照类似的情况被处理。 由于init section不可用,所以使得编译main来调用__main, 以开始初始化。

最后一个变体既不使用任意section,也不使用GNU连接器。这适合于, 你想进行动态连接并使用GNU连接器不支持的文件格式的时候,例如 'ECOFF'。 在这种情况下,TARGET_HAVE_CTORS_DTORS为假, 初始化和终止函数简单的通过它们的名字来识别。这要求在连接过程中的额外程序, 叫做collect2。该程序作为GCC使用的连接器; 它通过运行普通的连接器来完成工作,但是还负责包含初始化和终止函数的向量。 这些函数通过__main调用。要使用这种方式, 必须在config.gcc中定义use_collect2