<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>http://wiki.foofun.cn//index.php?action=history&amp;feed=atom&amp;title=Calling_Global_Constructors</id>
	<title>Calling Global Constructors - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="http://wiki.foofun.cn//index.php?action=history&amp;feed=atom&amp;title=Calling_Global_Constructors"/>
	<link rel="alternate" type="text/html" href="http://wiki.foofun.cn//index.php?title=Calling_Global_Constructors&amp;action=history"/>
	<updated>2026-04-06T23:28:07Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.37.1</generator>
	<entry>
		<id>http://wiki.foofun.cn//index.php?title=Calling_Global_Constructors&amp;diff=906&amp;oldid=prev</id>
		<title>Zhang3：创建页面，内容为“本教程讨论如何正确调用全局构造函数，例如全局C++对象上的构造函数。 这些应该在你的main函数之前运行，这就是为什么程序入口点通常是一个名为 _start的函数。 此函数负责解析命令行参数，初始化标准库（内存分配、信号等），运行全局构造函数并最终exit(main(argc, argv))。 如果你更改编译器，自制操作系统上的情况可能会有所不同，但是如果你使…”</title>
		<link rel="alternate" type="text/html" href="http://wiki.foofun.cn//index.php?title=Calling_Global_Constructors&amp;diff=906&amp;oldid=prev"/>
		<updated>2022-03-17T07:11:50Z</updated>

		<summary type="html">&lt;p&gt;创建页面，内容为“本教程讨论如何正确调用全局构造函数，例如全局C++对象上的构造函数。 这些应该在你的main函数之前运行，这就是为什么程序入口点通常是一个名为 _start的函数。 此函数负责解析命令行参数，初始化标准库（内存分配、信号等），运行全局构造函数并最终exit(main(argc, argv))。 如果你更改编译器，自制操作系统上的情况可能会有所不同，但是如果你使…”&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;本教程讨论如何正确调用全局构造函数，例如全局C++对象上的构造函数。 这些应该在你的main函数之前运行，这就是为什么程序入口点通常是一个名为 _start的函数。 此函数负责解析命令行参数，初始化标准库（内存分配、信号等），运行全局构造函数并最终exit(main(argc, argv))。 如果你更改编译器，自制操作系统上的情况可能会有所不同，但是如果你使用的是GNU编译器套件(GCC)，遵循System V ABI可能比较好。&lt;br /&gt;
&lt;br /&gt;
在大多数平台上，全局构造函数/析构函数存储在函数指针的有序数组中，调用这些就像遍历数组再运行每个元素一样简单。 但是，编译器并不总是允许访问此列表，一些编译器会认为这算是内部实现细节。 在这种情况下，你将不得不与编译器合作 - 与编译器对着干只会造成麻烦。&lt;br /&gt;
&lt;br /&gt;
== GNU Compiler Collection - System V ABI ==&lt;br /&gt;
&lt;br /&gt;
System V ABI(用于 &amp;lt;tt&amp;gt;i686-elf-gcc&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;x86_64-elf-gcc&amp;lt;/tt&amp;gt;和其它ELF平台)指定使用五个不同的目标文件，它们一起处理程序初始化。 这些传统上称为 &amp;lt;tt&amp;gt;crt0.o&amp;lt;/tt&amp;gt;，&amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt;，&amp;lt;tt&amp;gt;crtbegin.o&amp;lt;/tt&amp;gt;，&amp;lt;tt&amp;gt;crtend.o&amp;lt;/tt&amp;gt; 和 &amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;。 这些目标文件一起实现两个特殊功能：&amp;lt;tt&amp;gt;_init&amp;lt;/tt&amp;gt;运行全局构造函数和其它初始化任务， 以及运行全局析构函数和其他终止任务的 &amp;lt;tt&amp;gt;_fini&amp;lt;/tt&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
此方案使编译器可以很好地控制程序初始化，使你的工作变得容易，但你必须与编译器合作，否则会发生不好的事情。 你的交叉编译器将为你提供 &amp;lt;tt&amp;gt;crtbegin.o&amp;lt;/tt&amp;gt; 和 &amp;lt;tt&amp;gt;crtend.o&amp;lt;/tt&amp;gt;。 这些文件包含编译器希望对你隐藏但对你有用的内部文件。 要访问此信息，你需要提供你自己的&amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;实现。 幸运的是，这很容易，并且在本教程中进行了详细描述。 第五个文件&amp;lt;tt&amp;gt;crt0.o&amp;lt;/tt&amp;gt;包含程序入口点（通常为&amp;lt;tt&amp;gt;_start&amp;lt;/tt&amp;gt;），并调用特殊的&amp;lt;tt&amp;gt;_init&amp;lt;/tt&amp;gt;函数，该函数运行运行&amp;lt;tt&amp;gt;crti的“程序初始化任务”（由&amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;crtbegin.o&amp;lt;/tt&amp;gt;组成）。 crtend.o和crtn.o组合在一起， 你的exit函数通常会调用这些目标生成的函数。 但是，&amp;lt;tt&amp;gt;crt0&amp;lt;/tt&amp;gt;.o超出了本文的讨论范围。 (请注意，包含 &amp;lt;tt&amp;gt;_start&amp;lt;/tt&amp;gt; 的目标文件在内核中充当 &amp;lt;tt&amp;gt;crt0.o&amp;lt;/tt&amp;gt;。)&lt;br /&gt;
&lt;br /&gt;
为了理解这种明显的复杂性，考虑一个由&amp;lt;tt&amp;gt;foo.o&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;bar.o&amp;lt;/tt&amp;gt;组成的程序。由以下代码链接：&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;i686-elf-gcc foo.o bar.o -o program&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
编译器将重写命令行并将其传递给链接器，如下所示：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;i686-elf-ld crt0.o crti.o crtbegin.o foo.o bar.o crtend.o crtn.o&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这里的意图是，这些文件在链接过程中一起形成 &amp;lt;tt&amp;gt;_init&amp;lt;/tt&amp;gt; 和 &amp;lt;tt&amp;gt;_fini&amp;lt;/tt&amp;gt; 函数。 这是通过将&amp;lt;tt&amp;gt;_init&amp;lt;/tt&amp;gt;函数存储在&amp;lt;tt&amp;gt;.init&amp;lt;/tt&amp;gt;节（section）， 以及 &amp;lt;tt&amp;gt;_fini&amp;lt;/tt&amp;gt;函数在&amp;lt;tt&amp;gt;.fini&amp;lt;/tt&amp;gt;节中来实现的。 然后，每个文件为这些部分贡献一点，链接器将命令行中指定的代码中的片段粘在一起。 &amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt;提供函数头， &amp;lt;tt&amp;gt;crtbegin.o&amp;lt;/tt&amp;gt; 和 &amp;lt;tt&amp;gt;crtend.o&amp;lt;/tt&amp;gt; 提供主体， 和&amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;提供脚（返回语句）。 重要的是要理解链接顺序很重要，如果目标没有完全按照这个顺序链接，可能会发生奇怪的事情。&lt;br /&gt;
&lt;br /&gt;
=== 使用来自C的全局构造函数 ===&lt;br /&gt;
作为一个特殊的扩展，GCC允许C程序作为全局构造函数运行函数。 有关详细信息，请参阅编译器文档。 这通常用作:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
__attribute__ ((constructor)) void foo(void)&lt;br /&gt;
{&lt;br /&gt;
	printf(&amp;quot;foo is running and printf is available at this point\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char* argv[])&lt;br /&gt;
{&lt;br /&gt;
	printf(&amp;quot;%s: main is running with argc=%i\n&amp;quot;, argv[0], argc);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 在内核中使用crti.o, crtbegin.o, crtend.o和crtn.o ===&lt;br /&gt;
&lt;br /&gt;
在内核中，你没有用户空间C库可以使用。 你可能正在使用特殊的内核 “C库”，或者根本没有。 编译器总是提供&amp;lt;tt&amp;gt;crtbegin.o&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;crtend.o&amp;lt;/tt&amp;gt;， 但通常C库提供crti.o和crtn.o， 但是在这种情况下不是这样。 内核应该提供自己的&amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;实现 (即使它在其它方面与用户空间libc版本相同)。 内核用 &amp;lt;tt&amp;gt;-nostdlib&amp;lt;/tt&amp;gt; 选项链接 （这与使用&amp;lt;tt&amp;gt;-nodefaultlibs&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;-nostartfiles&amp;lt;/tt&amp;gt;选项相同） 这将禁用通常自动添加到链接命令行的“start files”&amp;lt;tt&amp;gt;crt*.o&amp;lt;/tt&amp;gt;。 通过使用&amp;lt;tt&amp;gt;-nostartfiles&amp;lt;/tt&amp;gt;选项，我们向编译器保证，我们自己负责调用&amp;lt;tt&amp;gt;crtbegin.o&amp;lt;/tt&amp;gt; 和 &amp;lt;tt&amp;gt;crtend.o&amp;lt;/tt&amp;gt;文件中的“程序初始化任务”。 这意味着我们需要手动添加&amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt;，&amp;lt;tt&amp;gt;crtbegin.o&amp;lt;/tt&amp;gt;，&amp;lt;tt&amp;gt;crtend.o&amp;lt;/tt&amp;gt;，和&amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;到命令行。 由于我们自己提供了&amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;，因此将其添加到内核命令行有点琐碎。 但是，由于 &amp;lt;tt&amp;gt;crtbegin.o&amp;lt;/tt&amp;gt; 和 &amp;lt;tt&amp;gt;crtend.o&amp;lt;/tt&amp;gt; 安装在特定于编译器的目录中，因此我们需要找出路径。 幸运的是，gcc提供了一个选项来实现这一点。 如果&amp;lt;tt&amp;gt;i686-elf-gcc&amp;lt;/tt&amp;gt;是你的交叉编译器，&amp;lt;tt&amp;gt;$CFLAGS&amp;lt;/tt&amp;gt;是你通常会提供给编译器的标志，则&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;i686-elf-gcc $CFLAGS -print-file-name=crtbegin.o&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
将使编译器将正确的 &amp;lt;tt&amp;gt;crtbegin.o&amp;lt;/tt&amp;gt; 文件 (与 $CFLAGS选项兼容) 的路径打印到标准输出。 这同样适用于&amp;lt;tt&amp;gt;crtend.o&amp;lt;/tt&amp;gt;。 如果你使用的是GNU Make，则可以在Makefile中轻松完成此操作，假设&amp;lt;tt&amp;gt;$(CC)&amp;lt;/tt&amp;gt;是你的交叉编译器，而&amp;lt;tt&amp;gt;$(CFLAGS)&amp;lt;/tt&amp;gt;是你通常传递给它的标志：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;make&amp;quot;&amp;gt;&lt;br /&gt;
CRTBEGIN_OBJ:=$(shell $(CC) $(CFLAGS) -print-file-name=crtbegin.o)&lt;br /&gt;
CRTEND_OBJ:=$(shell $(CC) $(CFLAGS) -print-file-name=crtend.o)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
然后，你可以这样使用它们 (适应你的真实构建系统):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;make&amp;quot;&amp;gt;&lt;br /&gt;
OBJS:=foo.o bar.o&lt;br /&gt;
&lt;br /&gt;
CRTI_OBJ=crti.o&lt;br /&gt;
CRTBEGIN_OBJ:=$(shell $(CC) $(CFLAGS) -print-file-name=crtbegin.o)&lt;br /&gt;
CRTEND_OBJ:=$(shell $(CC) $(CFLAGS) -print-file-name=crtend.o)&lt;br /&gt;
CRTN_OBJ=crtn.o&lt;br /&gt;
&lt;br /&gt;
OBJ_LINK_LIST:=$(CRTI_OBJ) $(CRTBEGIN_OBJ) $(OBJS) $(CRTEND_OBJ) $(CRTN_OBJ)&lt;br /&gt;
INTERNAL_OBJS:=$(CRTI_OBJ) $(OBJS) $(CRTN_OBJ)&lt;br /&gt;
&lt;br /&gt;
myos.kernel: $(OBJ_LINK_LIST)&lt;br /&gt;
	$(CC) -o myos.kernel $(OBJ_LINK_LIST) -nostdlib -lgcc&lt;br /&gt;
&lt;br /&gt;
clean:&lt;br /&gt;
	rm -f myos.kernel $(INTERNAL_OBJS)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
重要的是要记住，这些目标文件必须按照这个确切的顺序链接，否则你会遇到奇怪的错误。&lt;br /&gt;
&lt;br /&gt;
然后，你的内核将有一个&amp;lt;tt&amp;gt;_init&amp;lt;/tt&amp;gt;和一个&amp;lt;tt&amp;gt;_fini&amp;lt;/tt&amp;gt;函数链接在一起，可以从&amp;lt;tt&amp;gt;boot.o&amp;lt;/tt&amp;gt;调用它们 (boot.o是你的内核入口点目标文件名称)，然后将控制权传递给 &amp;lt;tt&amp;gt;kernel_main&amp;lt;/tt&amp;gt; (kernel_main是你的内核主例程)。 请注意，此时内核可能根本没有初始化，你只能从全局构造函数中执行一些琐碎的操作。 此外，&amp;lt;tt&amp;gt;_fini&amp;lt;/tt&amp;gt;可能永远不会被调用，因为你的操作系统将保持运行状态，并且当需要关机时，对于马上会处理器重置，做什么工作也没有价值了。 可能值得设置一个 &amp;lt;tt&amp;gt;kernel_early_main&amp;lt;/tt&amp;gt; 函数来初始化堆，日志和其它核心内核功能。 然后你的&amp;lt;tt&amp;gt;boot.o&amp;lt;/tt&amp;gt;可以调用&amp;lt;tt&amp;gt;kernel_early_main&amp;lt;/tt&amp;gt;，然后调用&amp;lt;tt&amp;gt;_init&amp;lt;/tt&amp;gt;，最后将控制权传递给真正的&amp;lt;tt&amp;gt;kernel_main&amp;lt;/tt&amp;gt;。 这类似于在用户空间中的工作方式，其中 &amp;lt;tt&amp;gt;crt0.o&amp;lt;/tt&amp;gt; 调用&amp;lt;tt&amp;gt;_initialize_c_library&amp;lt;/tt&amp;gt; (进行C库初始化)， 然后&amp;lt;tt&amp;gt;_init&amp;lt;/tt&amp;gt;，最后&amp;lt;tt&amp;gt;exit(main(argc, argv))&amp;lt;/tt&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
===在用户空间中使用crti.o，crtbegin.o，crtend.o和crtn.o===&lt;br /&gt;
{{Main|Creating a C Library}}&lt;br /&gt;
&lt;br /&gt;
在用户空间中使用这些目标文件非常容易，因为交叉编译器会自动以正确的顺序将它们链接到最终程序中。 编译器将一如既往地提供&amp;lt;tt&amp;gt;crtbegin.o&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;crtend.o&amp;lt;/tt&amp;gt;。 然后，你的C库将提供&amp;lt;tt&amp;gt;crt0.o&amp;lt;/tt&amp;gt;(程序入口点文件)、&amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;。 如果你有[[OS Specific Toolchain|操作系统特定工具链]]，你可以更改程序入口点的名称 (通常是_start)，编译器搜索路径下的&amp;lt;tt&amp;gt;crt{0,i,n}.o&amp;lt;/tt&amp;gt; 文件， 通过修改&amp;lt;tt&amp;gt;STARTFILE_SPEC&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;ENDFILE_SPEC&amp;lt;/tt&amp;gt;，了解使用了哪些文件(可能带有其它名称)和顺序。 当你开始创建一个用户空间时，创建一个特定于操作系统的工具链可能是值得的，因为它允许你很好地控制所有这些工作的具体方式。&lt;br /&gt;
&lt;br /&gt;
=== x86 (32-bit) ===&lt;br /&gt;
在x86下实现这一点非常简单。 只需在&amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt;中定义两个函数的头即可， 并定义在&amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;中的脚，让后在你的C库或内核中使用这些目标。 然后，你可以简单地调用 &amp;lt;tt&amp;gt;_init&amp;lt;/tt&amp;gt; 来执行初始化任务，并调用 &amp;lt;tt&amp;gt;_fini&amp;lt;/tt&amp;gt; 来执行终止任务 （通常从&amp;lt;tt&amp;gt;crt0.o&amp;lt;/tt&amp;gt;或&amp;lt;tt&amp;gt;my-kernel-boot-object.o&amp;lt;/tt&amp;gt;执行）。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/* x86 crti.s */&lt;br /&gt;
.section .init&lt;br /&gt;
.global _init&lt;br /&gt;
.type _init, @function&lt;br /&gt;
_init:&lt;br /&gt;
	push %ebp&lt;br /&gt;
	movl %esp, %ebp&lt;br /&gt;
	/* GCC会很好地将crtegin.o的.init节的内容放在这里。*/&lt;br /&gt;
&lt;br /&gt;
.section .fini&lt;br /&gt;
.global _fini&lt;br /&gt;
.type _fini, @function&lt;br /&gt;
_fini:&lt;br /&gt;
	push %ebp&lt;br /&gt;
	movl %esp, %ebp&lt;br /&gt;
	/* GCC会很好地将crtbegin.o的.fini节的内容放在这里。*/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/* x86 crtn.s */&lt;br /&gt;
.section .init&lt;br /&gt;
	/* GCC会很好地将crtend.o的.init节的内容放在这里。*/&lt;br /&gt;
	popl %ebp&lt;br /&gt;
	ret&lt;br /&gt;
&lt;br /&gt;
.section .fini&lt;br /&gt;
	/* GCC会很好地将crtend.o的.fini节的内容放在这里。*/&lt;br /&gt;
	popl %ebp&lt;br /&gt;
	ret&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== x86_64 (64-bit) ===&lt;br /&gt;
&lt;br /&gt;
x86_64上的系统ABI类似于32位，我们还需要提供函数头和函数脚，编译器将插入其余的 &amp;lt;tt&amp;gt;_init&amp;lt;/tt&amp;gt; 以及通过&amp;lt;tt&amp;gt;crtbegin.o&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;crtend.o&amp;lt;/tt&amp;gt;提供&amp;lt;tt&amp;gt;_fini&amp;lt;/tt&amp;gt;函数&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/* x86_64 crti.s */&lt;br /&gt;
.section .init&lt;br /&gt;
.global _init&lt;br /&gt;
.type _init, @function&lt;br /&gt;
_init:&lt;br /&gt;
	push %rbp&lt;br /&gt;
	movq %rsp, %rbp&lt;br /&gt;
	/*GCC会很好地将crtegin.o的.init节的内容放在这里。*/&lt;br /&gt;
&lt;br /&gt;
.section .fini&lt;br /&gt;
.global _fini&lt;br /&gt;
.type _fini, @function&lt;br /&gt;
_fini:&lt;br /&gt;
	push %rbp&lt;br /&gt;
	movq %rsp, %rbp&lt;br /&gt;
	/*GCC会很好地将crtbegin.o的.fini节内容放在这里。*/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/* x86_64 crtn.s */&lt;br /&gt;
.section .init&lt;br /&gt;
	/*GCC会很好地将crtend.o的.init节内容放在这里。*/&lt;br /&gt;
	popq %rbp&lt;br /&gt;
	ret&lt;br /&gt;
&lt;br /&gt;
.section .fini&lt;br /&gt;
	/*GCC会很好地将crtend.o的.fini节内容放在这里。*/&lt;br /&gt;
	popq %rbp&lt;br /&gt;
	ret&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== ARM (BPABI) ===&lt;br /&gt;
&lt;br /&gt;
在这种情况下，情况略有不同。 ABI系统要求使用名为&amp;lt;tt&amp;gt;.init_array&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;.fini_array&amp;lt;/tt&amp;gt;的特殊部分， 而不是常见的&amp;lt;tt&amp;gt;.init&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;.fini&amp;lt;/tt&amp;gt;节。 这意味着交叉编译器提供的 &amp;lt;tt&amp;gt;crtbegin.o&amp;lt;/tt&amp;gt; 和 &amp;lt;tt&amp;gt;crtend.o&amp;lt;/tt&amp;gt; 不会在 &amp;lt;tt&amp;gt;.init&amp;lt;/tt&amp;gt; 和 &amp;lt;tt&amp;gt;.fini&amp;lt;/tt&amp;gt; 部分中插入指令。 结果是，如果你遵循Intel/AMD系统的方法，则你的&amp;lt;tt&amp;gt;_init&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;_fini&amp;lt;/tt&amp;gt;函数将不会起任何作用。 你的交叉编译器实际上可能带有默认的 &amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;目标，但是它们也会受到这个ABI决定的影响，它们的&amp;lt;tt&amp;gt;_init&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;_fini&amp;lt;/tt&amp;gt;函数也不会执行任何操作。&lt;br /&gt;
&lt;br /&gt;
解决方案是提供自己的&amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt;目标文件，这个目标文件在&amp;lt;tt&amp;gt;.init_array&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;.fini_array&amp;lt;/tt&amp;gt;节开头插入一个符号， 如同你自己的&amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;，它在节的末尾插入一个符号。 在这种情况下，实际上可以在C中编写 &amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt; 和 &amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;，因为我们没有编写不完整的函数。 这些文件应该像内核的其它部分一样编译，像普通&amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt;和&amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;一样使用。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
/* crti.c for ARM - BPABI - use -std=c99 */&lt;br /&gt;
typedef void (*func_ptr)(void);&lt;br /&gt;
&lt;br /&gt;
extern func_ptr _init_array_start[0], _init_array_end[0];&lt;br /&gt;
extern func_ptr _fini_array_start[0], _fini_array_end[0];&lt;br /&gt;
&lt;br /&gt;
void _init(void)&lt;br /&gt;
{&lt;br /&gt;
	for ( func_ptr* func = _init_array_start; func != _init_array_end; func++ )&lt;br /&gt;
		(*func)();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void _fini(void)&lt;br /&gt;
{&lt;br /&gt;
	for ( func_ptr* func = _fini_array_start; func != _fini_array_end; func++ )&lt;br /&gt;
		(*func)();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
func_ptr _init_array_start[0] __attribute__ ((used, section(&amp;quot;.init_array&amp;quot;), aligned(sizeof(func_ptr)))) = { };&lt;br /&gt;
func_ptr _fini_array_start[0] __attribute__ ((used, section(&amp;quot;.fini_array&amp;quot;), aligned(sizeof(func_ptr)))) = { };&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
/* crtn.c for ARM - BPABI - use -std=c99 */&lt;br /&gt;
typedef void (*func_ptr)(void);&lt;br /&gt;
&lt;br /&gt;
func_ptr _init_array_end[0] __attribute__ ((used, section(&amp;quot;.init_array&amp;quot;), aligned(sizeof(func_ptr)))) = { };&lt;br /&gt;
func_ptr _fini_array_end[0] __attribute__ ((used, section(&amp;quot;.fini_array&amp;quot;), aligned(sizeof(func_ptr)))) = { };&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
此外，如果你使用构造函数/析构函数优先级，编译器会将这些优先级附加到节名。 链接器脚本会试图对这些脚本进行排序，因此你必须将以下内容添加到链接器脚本中。 注意，我们必须特别对待&amp;lt;tt&amp;gt;crti.o&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;crtn.o&amp;lt;/tt&amp;gt;目标，因为我们需要将符号按正确的顺序排列。 或者，你也可以自己从链接器脚本发出&amp;lt;tt&amp;gt;_init_array_start&amp;lt;/tt&amp;gt;，&amp;lt;tt&amp;gt;_init_array_end&amp;lt;/tt&amp;gt;，&amp;lt;tt&amp;gt;_ fini_array_start&amp;lt;/tt&amp;gt;，&amp;lt;tt&amp;gt;_ fini_array_end&amp;lt;/tt&amp;gt;符号。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/* 包括已排序的初始化函数列表*/&lt;br /&gt;
.init_array :&lt;br /&gt;
{&lt;br /&gt;
    crti.o(.init_array)&lt;br /&gt;
    KEEP (*(SORT(EXCLUDE_FILE(crti.o crtn.o) .init_array.*)))&lt;br /&gt;
    KEEP (*(EXCLUDE_FILE(crti.o crtn.o) .init_array))&lt;br /&gt;
    crtn.o(.init_array)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 包含已排序的终止函数列表。*/&lt;br /&gt;
.fini_array :&lt;br /&gt;
{&lt;br /&gt;
    crti.o(.fini_array)&lt;br /&gt;
    KEEP (*(SORT(EXCLUDE_FILE(crti.o crtn.o) .fini_array.*)))&lt;br /&gt;
    KEEP (*(EXCLUDE_FILE(crti.o crtn.o) .fini_array))&lt;br /&gt;
    crtn.o(.fini_array)&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CTOR/DTOR ===&lt;br /&gt;
&lt;br /&gt;
执行全局构造函数/析构函数的另一种方法是手动执行.ctors / .dtors 符号 （假设你有自己的ELF加载程序，请参见[[ELF_Tutorial|ELF_教程]]）。 将每个ELF文件加载到内存中，并且所有符号都已解析和重新定位后，可以使用.ctors/.dtor手动执行全局构造函数/析构函数 (显然，这同样适用于.init_array和.fini_array)。 要执行此操作，必须首先找到.ctors / .dtors 节头：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
for (i = 0; i &amp;lt; ef-&amp;gt;ehdr-&amp;gt;e_shnum; i++)&lt;br /&gt;
{&lt;br /&gt;
    char name[250];&lt;br /&gt;
    struct elf_shdr *shdr;&lt;br /&gt;
&lt;br /&gt;
    ret = elf_section_header(ef, i, &amp;amp;shdr);&lt;br /&gt;
    if (ret != ELF_SUCCESS)&lt;br /&gt;
        return ret;&lt;br /&gt;
&lt;br /&gt;
    ret = elf_section_name_string(ef, shdr, &amp;amp;name);&lt;br /&gt;
    if (ret != BFELF_SUCCESS)&lt;br /&gt;
        return ret;&lt;br /&gt;
&lt;br /&gt;
    if (strcmp(name, &amp;quot;.ctors&amp;quot;) == 0)&lt;br /&gt;
    {&lt;br /&gt;
        ef-&amp;gt;ctors = shdr;&lt;br /&gt;
        continue;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (strcmp(name, &amp;quot;.dtors&amp;quot;) == 0)&lt;br /&gt;
    {&lt;br /&gt;
        ef-&amp;gt;dtors = shdr;&lt;br /&gt;
        continue;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
现在你有了.ctors/.dtors节头，你可以使用以下内容解析每个构造函数。 请注意.ctors / .dtors是一个指针表 (ELF32为32bit，ELF64为64bit)。 每个指针都是必须执行的函数。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
typedef void(*ctor_func)(void);&lt;br /&gt;
&lt;br /&gt;
for(i = 0; i &amp;lt; ef-&amp;gt;ctors-&amp;gt;sh_size / sizeof(void *); i++)&lt;br /&gt;
{&lt;br /&gt;
    ctor_func func;&lt;br /&gt;
    elf64_addr sym = 0;&lt;br /&gt;
&lt;br /&gt;
    sym = ((elf64_addr *)(ef-&amp;gt;file + ef-&amp;gt;ctors-&amp;gt;sh_offset))[i];&lt;br /&gt;
    func = ef-&amp;gt;exec + sym;&lt;br /&gt;
    func();&lt;br /&gt;
&lt;br /&gt;
    /* elf-&amp;gt;文件是存储你使用的ELF文件的字符 *。 Could be binary or shared library */&lt;br /&gt;
    /*elf-&amp;gt;exec是一个char*，它存储ELF文件已加载到的内存位置，并重新放置*/&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
如果在.ctors / .dtors.中你只有一个条目，不要惊讶。 至少在x86_64上，GCC似乎将单个条目添加到一组名为_GLOBAL__SUB_I_XXX和_GLOBAL_SUB_D_XXX的函数中，这两个函数调用的_Z41__static_initialization_and_destruction_0ii实际上为你调用了每个构造函数。 添加更多全局定义的构造函数/析构函数将导致此函数增长，而不是ctors/.dtors。 &lt;br /&gt;
&lt;br /&gt;
=== 稳定性问题===&lt;br /&gt;
&lt;br /&gt;
如果不调用GCC提供的构造函数/析构函数，则GCC将生成代码，该代码将在某些条件下使用x86_64发生段错误（segfault）。 以此为例：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
class A&lt;br /&gt;
{&lt;br /&gt;
    public: &lt;br /&gt;
        A() {}&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
A g_a;&lt;br /&gt;
&lt;br /&gt;
void foo(void)&lt;br /&gt;
{&lt;br /&gt;
    A *p_a = &amp;amp;g_a;&lt;br /&gt;
    p_a-&amp;gt;anything();     // &amp;lt;---- segfault&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
GCC似乎在使用构造函数/析构函数初始化例程时不仅仅是调用每个全局定义类的构造函数/析构函数。 执行.ctors/.dtors中定义的函数不仅可以初始化所有的构造函数/析构函数，还可以解决这些类型的段错误(上面只是许多已解决的错误中的一个示例)。 据我所知，当全局定义的对象存在时，GCC也可能创建 “.data.rel.ro”，这是GCC需要处理的另一个重定位表。 它被标记为PROGBITS，而不是REL/RELA，这意味着ELF加载程序不会为你重新定位。 相反，执行.ctors中定义的函数将执行_Z41__static_initialization_and_destruction_0ii，它似乎会为我们执行重定位。 有关更多信息，请参见以下内容: [https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68738]&lt;br /&gt;
&lt;br /&gt;
== Clang ==&lt;br /&gt;
'''注意：''' 由于Clang试图在很大程度上与GCC兼容，因此这里列出的信息很可能会变化。 如果有你尝试过，请在此处记录调查结果。&lt;br /&gt;
&lt;br /&gt;
据我所知，clang不需要你在传递给链接器的目标中指定正确的crt{begin，end}.o，前提是你在正确的位置传递crt[in].o，能够输出到许多目标， 并不总是有一个可用的crt{begin，end}.o在手边，它似乎是按需编译的。 调用_init似乎也不是一项要求。 对_init的额外调用不会执行两次，因此手动调用仍然是安全的。&lt;br /&gt;
&lt;br /&gt;
总之，为了将GCC的代码修改为clang，从你的链接程序行中去掉crt{Begin，End}.o就可以了。&lt;br /&gt;
&lt;br /&gt;
== 其它编译器/平台 ==&lt;br /&gt;
如果你的编译器或系统ABI未在此处列出，你将需要手动查阅相应的文档，如果相关信息，请也帮忙在此处记录。&lt;br /&gt;
&lt;br /&gt;
==另见==&lt;br /&gt;
&lt;br /&gt;
=== 外部链接 ===&lt;br /&gt;
* [https://gcc.gnu.org/onlinedocs/gccint/Initialization.html 初始化函数的处理方式] 在GCC的文档中。&lt;/div&gt;</summary>
		<author><name>Zhang3</name></author>
	</entry>
</feed>