程序的编译过程之三——静态链接2

时间:2022-05-07 22:46:31

欢迎访问我的博客新地址:www.heybrt.tk

从helloworld回顾程序的编译过程之三——静态链接2  

      本文上接:从helloworld回顾程序的编译过程之三——静态链接1

   http://blog.csdn.net/you1314520me/article/details/8916113

3、符号的解析和重定位

                   符号的解析和重定位主要是说将符号引用的位置正确地对应到其定义位置,在程序链接之前,符号的定义和引用位置所在的段都还没有分配地址空间,从上面2中所示main.o等目标文件看到它们段表的VMALMA都是空的,

         因此这时候也没有办法确定符号的引用位置和定义位置,但是在链接之后,符号的定义和引用位置所在的段都将被分配地址空间,因此需要在链接过程中确定引用和定义的位置。

        

         1)确定符号的定义位置

                   在链接器将各目标的文件的段合并并为之分配地址空间之后,各符号所在段的位置也就确定了,再回头看2中给出的test.o文件的段表内容,可以看到合并之后test目标文件的代码段.text段的地址为08048094

         这时链接器需要根据相对偏移量计算出符号表的定义位置,这里以add.c文件中的add函数为例进行说明:

                   假设函数add相对于add.o.text段的偏移量为Pos  经过链接之后main.o中的.text段相对于test.o.text的段偏移量为Pos_Sec,而test.o.text段在虚地址空间里的位置为Pos_Des

         则符号add定义位置就是:Pos_Des + Pos_Sec + Pos,如下图所示:

                   程序的编译过程之三——静态链接2

 

         2)确定符号的引用位置      

         下面是main.oadd.otest三个目标文件的反汇编代码,使用的命令为:objdump -d 目标文件名

         目标文件main.o的代码段的反汇编内容为:

[plain]  view plain copy print ?
  1. # objdump -d main.o  
  2.         main.o:     file format elf32-i386  
  3.         Disassembly of section .text:  
  4.         00000000 <main>:  
  5.            0:   55                      push   %ebp  
  6.            1:   89 e5                   mov    %esp,%ebp  
  7.            3:   83 e4 f0                and    $0xfffffff0,%esp  
  8.            6:   83 ec 20                sub    $0x20,%esp  
  9.            9:   c7 44 24 1c 0d 00 00    movl   $0xd,0x1c(%esp)  
  10.           10:   00   
  11.           11:   c7 44 24 18 1a 00 00    movl   $0x1a,0x18(%esp)  
  12.           18:   00   
  13.           19:   8b 44 24 18             mov    0x18(%esp),%eax  
  14.           1d:   8b 54 24 1c             mov    0x1c(%esp),%edx  
  15.           21:   01 c2                   add    %eax,%edx  
  16.           23:   a1 00 00 00 00          mov    0x0,%eax  
  17.           28:   8d 04 02                lea    (%edx,%eax,1),%eax  
  18.           2b:   89 44 24 1c             mov    %eax,0x1c(%esp)  
  19.           2f:   a1 00 00 00 00          mov    0x0,%eax  
  20.           34:   dd 05 00 00 00 00       fldl   0x0  
  21.           3a:   89 44 24 0c             mov    %eax,0xc(%esp)  
  22.           3e:   dd 5c 24 04             fstpl  0x4(%esp)  
  23.           42:   8b 44 24 1c             mov    0x1c(%esp),%eax  
  24.           46:   89 04 24                mov    %eax,(%esp)  
  25.           49:   e8 fc ff ff ff          call   4a <main+0x4a>  
  26.           4e:   b8 00 00 00 00          mov    $0x0,%eax  
  27.           53:   c9                      leave    
  28.           54:   c3                      ret   

               

这里需要注意:

                   1 49行的fc ff ff ff是指一个暂时未确定的空地址,也就是这里调用了一个外部的函数。

                   2main.o的代码段的起始位置为00000000,这是因为此时还没有对main.o.text段进行地址空间分配

                  

         目标文件add.o的代码段的反汇编内容为:

[plain]  view plain copy print ?
  1. # objdump -d add.o  
  2.         add.o:     file format elf32-i386  
  3.         Disassembly of section .text:  
  4.         00000000 <add>:  
  5.            0:   55                      push   %ebp  
  6.            1:   89 e5                   mov    %esp,%ebp  
  7.            3:   8b 45 0c                mov    0xc(%ebp),%eax  
  8.            6:   8b 55 08                mov    0x8(%ebp),%edx  
  9.            9:   8d 04 02                lea    (%edx,%eax,1),%eax  
  10.            c:   89 c2                   mov    %eax,%edx  
  11.            e:   03 55 10                add    0x10(%ebp),%edx  
  12.           11:   a1 00 00 00 00          mov    0x0,%eax  
  13.           16:   8d 04 02                lea    (%edx,%eax,1),%eax  
  14.           19:   5d                      pop    %ebp  
  15.           1a:   c3                      ret   


 

         目标文件test的代码段的反汇编内容为   

[plain]  view plain copy print ?
  1. # objdump -d test  
  2.         test:     file format elf32-i386  
  3.         Disassembly of section .text:  
  4.         08048094 <add>:  
  5.          8048094:       55                      push   %ebp  
  6.          8048095:       89 e5                   mov    %esp,%ebp  
  7.          8048097:       8b 45 0c                mov    0xc(%ebp),%eax  
  8.          804809a:       8b 55 08                mov    0x8(%ebp),%edx  
  9.          804809d:       8d 04 02                lea    (%edx,%eax,1),%eax  
  10.          80480a0:       89 c2                   mov    %eax,%edx  
  11.          80480a2:       03 55 10                add    0x10(%ebp),%edx  
  12.          80480a5:       a1 08 91 04 08          mov    0x8049108,%eax  
  13.          80480aa:       8d 04 02                lea    (%edx,%eax,1),%eax  
  14.          80480ad:       5d                      pop    %ebp  
  15.          80480ae:       c3                      ret      
  16.          80480af:       90                      nop  
  17.   
  18.         080480b0 <main>:  
  19.          80480b0:       55                      push   %ebp  
  20.          80480b1:       89 e5                   mov    %esp,%ebp  
  21.          80480b3:       83 e4 f0                and    $0xfffffff0,%esp  
  22.          80480b6:       83 ec 20                sub    $0x20,%esp  
  23.          80480b9:       c7 44 24 1c 0d 00 00    movl   $0xd,0x1c(%esp)  
  24.          80480c0:       00   
  25.          80480c1:       c7 44 24 18 1a 00 00    movl   $0x1a,0x18(%esp)  
  26.          80480c8:       00   
  27.          80480c9:       8b 44 24 18             mov    0x18(%esp),%eax  
  28.          80480cd:       8b 54 24 1c             mov    0x1c(%esp),%edx  
  29.          80480d1:       01 c2                   add    %eax,%edx  
  30.          80480d3:       a1 10 91 04 08          mov    0x8049110,%eax  
  31.          80480d8:       8d 04 02                lea    (%edx,%eax,1),%eax  
  32.          80480db:       89 44 24 1c             mov    %eax,0x1c(%esp)  
  33.          80480df:       a1 0c 91 04 08          mov    0x804910c,%eax  
  34.          80480e4:       dd 05 18 91 04 08       fldl   0x8049118  
  35.          80480ea:       89 44 24 0c             mov    %eax,0xc(%esp)  
  36.          80480ee:       dd 5c 24 04             fstpl  0x4(%esp)  
  37.          80480f2:       8b 44 24 1c             mov    0x1c(%esp),%eax  
  38.          80480f6:       89 04 24                mov    %eax,(%esp)  
  39.          80480f9:       e8 96 ff ff ff          call   8048094 <add>  
  40.          80480fe:       b8 00 00 00 00          mov    $0x0,%eax  
  41.          8048103:       c9                      leave    
  42.          8048104:       c3                      ret    

                

可以看到:

         1)在main.oadd.o中的main函数和add函数的地址都是空的,说明此时还没用进行空间分配,但是链接之后的test文件中main函数和add函数的地址都已经确定为080480b008048094了,这说明在链接之后,已经完成地址空间的分配。

         2)在链接之前的目标文件中,外部符号的引用位置都是空着,链接之后其位置已经被修改为真实的地址了,这说明已经完成对符号的重定位工作。

                   例如main.o的反汇编内容里49行的fc ff ff ff是指一个暂时未确定的空地址,就像main.c中引用的外部符号g_iAddValue2add,在将main.c编译成目标文件main.o时,由于main.cadd.c是独立编译的,

                   所以此时main.o中并不知道这两个符号的定义位置在哪里。此时编译器只是暂时用假地址"0xfc ff ff ff"代替。

                  

         3)符号的重定位是怎么完成的呢?

                   重定位是通过重定位表(段)来完成的,它主要描述对应段的重定位信息,每个包含需要重定位符号的段都会有一个重定位表,也就是,如果一个段中有需要重定位的符号,那就会为这个段再生成一个重定位表,这个重定位表中就描述了如何对段内的符号进行重定位。

         可以通过命令:objdump -r目标文件名,来查看各目标文件的重定位表:

         目标文件 main.o的重定位表为:

[plain]  view plain copy print ?
  1. # objdump -r main.o  
  2. main.o:     file format elf32-i386  
  3. RELOCATION RECORDS FOR [.text]:  
  4. OFFSET   TYPE              VALUE   
  5. 00000024 R_386_32          g_iMainValue  
  6. 00000030 R_386_32          g_iAddValue2  
  7. 00000036 R_386_32          g_dMainValue  
  8. 0000004a R_386_PC32        add  


 

         可以看到在main.o中还有四个符号需要在链接时分配地址空间之后才能确定位置。

        

         目标文件 add.o的重定位表为:

[plain]  view plain copy print ?
  1. # objdump -r add.o  
  2. add.o:     file format elf32-i386  
  3. RELOCATION RECORDS FOR [.text]:  
  4. OFFSET   TYPE              VALUE   
  5. 00000012 R_386_32          g_iAddValue1  


 

         可以看到在add.o中还有一个符号需要在链接时分配地址空间之后才能确定位置。

        

         目标文件 test的重定位表为:

[plain]  view plain copy print ?
  1. # objdump -r test  
  2. test:     file format elf32-i386  


       

         对于链接之后的目标文件test中已经没有需要再重定位的符号了,它也就没有重定位表了,如果一个目标文件还有重定位表就说明它还有符号的位置未被确定。    

         23)小节中对三个目标文件main.oadd.otest的符号表内容中可以看到,在main.o中有两个被标识为UND的符号:g_iAddValue2add,但是这两个符号在test的符号表中已经完成重定位。

        

         4)综合上面所述的内容,可以得到链接器对符号的重定位过程如下所述:

                   1)合并目标文件;链接器扫描所有的目标文件main.oadd.o,得到它们各自段的信息,并将对应的段合并起来。

                   2)确定各符号的定义位置;连接器在合并各段之后就对各段进行虚地址空间分配,同时也就确定了各个符号的定义位置。

                   1)查询符号引用位置,将符号的引用位置指向其正确的定义位置。连接器在第一遍扫描各个目标文件main.oadd.o时,就会将它们的符号表统一收集起来,形成一个全局的符号表,经过此次扫描之后的所有的符号引用都被统一到了该表中,

                            从该表中就可以看到哪些符号没有位置还未确定,未确定的索引为UND,将所有的UND符号全部都修改为其定义位置。

                           

附:

         文件中什么东西才是文中所述的符号呢?什么样的符号会被存储到符号表中?

         通常所说的符号就是指函数名和变量名,但是局部变量就不在符号表中,具体而言就是下面的符号将会被放置到符号表中:

         1)定义在本目标文件中的全局变量。例如main.c里面的g_iMainValueadd.c里面的g_iAddValue2

         2)引用的外部函数和变量

         3)段名,它由编译器产生。

         4)静态变量

         5)行号信息。


转自网易名为“逍遥子”的博客