一个编译链接的过程详解
2008-06-19 15:43
Operating Systems A Modern Perspective笔记之编译链接 先扫盲:
3. 动态地址重定位小结: 4. 页式虚存 实例篇——静态地址重定位时一个编译链接加载的简单示例 有代码段: static int gVar; int proc_a( int arg ){ ... gVar = 7; put_record(gVar); ... }} 1.gVar是一个模块内局部变量(因为static关键字),编译器为其在proc_a同一模块内分配内存空间。假设给其分配到相对位置0x36.并在模块内符号表里记录符号的内存地址。 2.外部应用和定义组成了一张External reference table.编译器在链接时会对ERT中的每一个表项,在全局范围内查找其绝对位置(通过查找别的模块的符号表),将变量的内存地址写入符号表中。 3.对于在当前模块外的put_record()函数入口地址,编译器会在编译生成的中间代码中(.lib中),临时的将变量符号名填入需要用到变量的地方,等待在链接时替换为其绝对地址。 通过编译过程,生成的中间代码如下: 0000 .... 0008 entry proc_a .... 0036 [space for gVar variable] .... 0220 load R1,=7 0224 store R1,0036 0228 push 0036 0232 call 'put_record' .... 0400 [external definition table] 0404 put_record 0232
.... 0600 [optional symbol table] 0604 gVar 0036 0630 proc_a 0008 .... 0799 [last location in the module] 链接时将各个可重定位模块绑定到一起。首先用户声明的前后顺序,将各个模块连接起来。在一个个模块绑定的时候,会根据模块被连接到的绝对基地址,对模块内被引用了的地址进行重新运算。 在将各个模块连接到一起后,链接程序搜索整个模块中的外部地址引用表[external definition table],将相应位置的临时符号(如这里的'put_record')替换为该符号的实际地址。 在链接过程以后,external definition table失去了作用,被删去,而符号表被汇集成一个全局符号表,可以有选择的保存在程序的某些位置(通常是尾部)。 上面的模块经过链接后,得到下面的代码: 相对地址 生成的代码 0000 .... 1008 extry proc_a .... 1036 [space for gVar variable] .... 1220 load R1,=7 1224 store R1,1036 1228 push 1036 1232 call 2334 1399 (end of proc_a) (other modules) 2334 entry put_record .... 2670 (optional symbol table) .... gVar 1036 proc_a 1008 put_record 2334 2999 (last location in the module) 通过上面的过程,便形成了一个可以被加载器加载的二进制文件(.out,.bin等等【不清楚.exe算不算,所以没列出来】)。当然,二进制文件也可以通过无损压缩,形成容量小的多的镜像文件(如.srec) 在实际加载程序时(如用户要求操作系统加载文件系统中的.out文件,或者用户双击了一个.exe文件),操作系统的加载程序会将二进制代码拷贝到内存中,并在运行前做最后一次地址重定位。 假设程序被加载到从内存地址4000开始放置。则程序中所有要访问到的程序、数据位置都要在加载前调整。调整后的最终内存中程序如下: 0000 (别的程序,这个地址多半被映射到ROM上去了) 4000 (other module) 5008 extry proc_a .... 5036 [space for gVar variable] .... 5220 load R1,=7 5224 store R1,5036 5228 push 5036 5232 call 5334 5399 (end of proc_a) (other modules) 6334 entry put_record .... 6670 (optional symbol table) .... gVar 5036 proc_a 5008 put_record 6334 6999 (last location in the module) 最后来小结一下: 1.符号表的作用是为了debug的时候能让debugger把代码和当前内存位置对上号。当然如果没有符号表代码尺寸会减小很多。所以final release里面肯定不能带符号表。 2.外部引用符号表主要是在编译阶段使用,在链接确定外部符号地址后,这个表项就可以删除了。 3.试想一下这种情况:一个模块里面的外部符号引用太多了(extern关键字用的太多了),导致编译的时候外部符号表溢出! 一个哥们以前在中兴工作的时候就遇到过这样的情况。 4.通过写这篇读书笔记,感觉自己对编译链接过程的理解也加深了不少。 当然我们不会去做编译器,有机会做操作系统内存管理部分的人也不会太多。不过每天都接触到的compile and link,了解深一点总是没有错的,有些时候面对一大堆的link error,想一下背后的原理,也就觉得了然了。 |