gcc编译文件的过程:gcc -v hello.c 可以看到完整的编译过程
使用gcc编译器编译C源程序时,gcc hello.c,会在当前目录中生成一个a.out的可执行文件。file a.out可以查看文件的类型
这是一个ELF的文件,executable可执行。使用-o选项可将生成的可执行文件进行重命名。gcc -o hello hello.c
而程序编译经历了 预处理 -> 编译 -> 汇编 -> 链接的过程。
预处理:cpp预处理器,gcc -E
程序的预处理不会对文件c源文件进行编译,但是会将#include的文件和程序中定义的宏展开,如图
定义了一个MAXSIZE的宏,在main函数中将宏复制给了变量a,预处理展开后,
MAXSIZE的值被100替换了。
2. 编译 gcc -S hello.i -o hello.s
文件被编译器编译成hello.s文件,文件内容为原来c程序的汇编代码,如下所示
3. 汇编 gcc -c hello.s -o hello.o; as是汇编器
通过汇编器生成目标文件hello.o,目标文件不能够通过vim编辑器打开阅读
4. 连接 ld为连接器,gcc会默认使用ld连接器进行连接,并添加了默认选项用于文件的连接
使用ld连接器连接目标程序
ld /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o hello.o -o hello -lc -dynamic-linker /lib/ld-linux.so.2
连接器连接三个目标文件,分别是crt1.o, crti.o, crtn.o,使用file查看
发现文件crtn.o stripped,即该文件去除了全局符号表等信息,使用nm查看不到符号表。,使用readelf -a crtn.o可以查看该文件没有了哪些信息。
crti.o来之crti.S,crtn.o来之crtn.S
而crti.S和crtn.S则都来之initfini.s
initfini.s则来之与sysdeps/generic/initfini.c
crti和crtn就是将两个函数init和fini分别切成两截,他们的前半截放入crti,后半截放入crtn.
为什么要这么做呢?
如果有某些工作需要在main之间完成和main之后完成,那么,就可以将相应的代码放入目标文件的
.init和.fini节。在ld链接时,会按照给定的目标文件顺序,依次合并同名节。
这样就能将相应代码插入_init函数和_fini函数中,从而达到目的。
使用nm 查看crt1.o和crti.o的符号表
nm crt1.o nm crti.o
00000000 R _IO_stdin_used U _GLOBAL_OFFSET_TABLE_
00000000 D __data_start w __gmon_start__
U __libc_csu_fini 00000000 T _fini
U __libc_csu_init 00000000 T _init
U __libc_start_main
00000000 R _fp_hw
00000000 T _start
00000000 W data_start
U main
crt1.o中出现的U main表示crt1.o文件中使用到了main,但是没有定义(U表示Undefined),函数的入口就是_start,T表示代码段(Text),_start首先做一些初始化工作(即启动例程Startup Routine),然后调用C代码中的main函数,所以函数的入口函数真正是_start,main函数是被_start调用的。
而在crt1.o中出现了一个未定义的__libc_start_main符号,在其他几个文件中也没有相应的定义,这个是在运行时做动态链接的,连接选项中指定了连接库 -lc 即需要连接libc库, -dynamic-linker /lib/ld-linux.so.2指定了动态链接的库。