1.程序的编译过程
假设现在有如下两个c程序文件,sum.c内容如下:
int sum(int *a, int n){ int i, s = 0; for( i = 0; i < n ; i++){ s += a[i]; } return s; }
主程序main.c的内容如下:
int sum(int *a,int n); int array[2] = {1, 2}; int main(){ int val = sum(array,2 ); return val; }
在linux上执行如下语句:
gcc -Og -o prog main.c sum.c
以main.c为例,程序首先调用C预处理器,将main.c翻译成一个Ascii码的中间件,mian.i,接下来将main.i翻译成一个汇编语言文件main.s,然后调用汇编器将main.s翻译可重定位的目标文件main.o,同理会生成相应的sum.o文件。最后,运行链接器程序ld,将main.o和sum.o以及一些必要的文件组合起来,创建一个可执行文件prog。
在构造可执行文件的时候,链接器要完成两个任务:
1、符号解析,符号解析的目的是将每个全局符号引用与符号定义关联起来,即可重定位的目标文件定义了符号,而可执行的目标文件引用符号,将两者的符号相互绑定,这里的符号对应一个函数、一个全局变量、或者一个静态变量。
2、重定位,编译器和汇编器生成从0开始的的代码和数据节,链接器将符号定义和内存的位置关联起来来重定位代码和数据节,然后修改所有这些对符号的引用,使得它们指向内存的位置。
2.一些基础知识
目标文件有三种类型:
可重定位的目标文件:包含二进制数据和代码,其形式可以在编译时与其他可重定位合并起来,创建一个可执行的目标文件
可执行的目标文件:包含二进制和代码,可以直接复制到内存并执行
共享目标文件:一类特殊的可重定位目标文件,可以在加载或者运行时被动态的加载到内存执行。
一个典型的ELF(可执行、可链接格式)的可重定位目标文件包含几个重要的部分,如下图:
.text 已编译程序的机器代码
.data 已初始化的全局和静态C变量,局部C变量的值保存在栈中。
.bss 未初始化的全局和静态C变量,以及所有被初始化为0的静态或全局变量。
.sysmtab 一个符号表,存放程序中定义和引用的函数以及全局或静态变量,不包含局部变量的信息。
对重定位的理解,在重定位,有两个步骤。
首先重定位节和符号定义,链接器将不同目标文件所有相同类型的节合并到同一个新的聚合节中,例如,来自所有输入模块的.data节会被合并到同一个节中。然后链接器将运行时内存的地址赋给新的聚合节,赋给输入模块中定义的那个节和其中定义的每个符号。此时,程序中的每条指令和全局变量都有一个唯一的运行时内存了。
其次是重定位符号的引用,修改数据节和代码节中符号的引用,使得他们指向正确的运行时内存地址。
处理目标文件的一些工具,如下:
AR :创建静态库,插入、删除、列出和提取成员。
NM :列出一个目标文件的符号表中定义的符号。
收藏:不周山之读薄 CSAPP
参考:《深入理解计算机系统》