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

时间:2021-04-26 22:38:05

 

本文关于静态链接库的链接过程分析是对《程序员的自我修养——链接、装载与库》这本书的一点学习总结,另外,本文是在linux操作系统下进行验证和测试,所使用的测试文件为:main.cadd.c,其内容如下:

         ---------------文件main.c---------------

        

//main.c

         int g_iMainValue = 35;

         double g_dMainValue = 10.25;

         extern int g_iAddValue2;

 

         int main()

         {

                   int iTmpNum1= 13;

                   int iTmpNum2= 26;

                   iTmpNum1 = iTmpNum1 + iTmpNum2 + g_iMainValue;

                   add(iTmpNum1,g_dMainValue,g_iAddValue2);

                   return 0;

         }

 


         ---------------文件add.c---------------

         

//add.c

 

         int g_iAddValue1 = 50;

         int g_iAddValue2 = 33;

 

         int add(int iNum1, int iNum2, int iNum3)

         {

                   return iNum1 + iNum2 + iNum3 + g_iAddValue1;

         }


 

1、编译和链接上述两个源文件,

         1)将上述两文件编译成目标文件main.oadd.o,可使用如下命令:

                   # gcc -c add.c main.c

         2)将目标文件main.oadd.o链接成可执行文件:test,可使用如下命令:

                   # ld main.o add.o -e main -o test

         其中:-e main表示要将函数main作为程序的入口

        

2、查看和分析目标文件main.oadd.otest,这三个都是ELF文件格式的目标文件,这里需要看下它们之间有哪些区别,

         1)回顾一下目标文件main.o的内容,使用命令:objdump -s -d main.o

# objdump -s -d main.o

		main.o:     file format elf32-i386

		Contents of section .text:
		 0000 5589e583 e4f083ec 20c74424 1c0d0000  U....... .D$....
		 0010 00c74424 181a0000 008b4424 188b5424  ..D$......D$..T$
		 0020 1c01c2a1 00000000 8d040289 44241ca1  ............D$..
		 0030 00000000 dd050000 00008944 240cdd5c  ...........D$..\
		 0040 24048b44 241c8904 24e8fcff ffffb800  $..D$...$.......
		 0050 000000c9 c3                          .....           
		Contents of section .data:
		 0000 23000000 00000000 00000000 00802440  #.............$@
		Contents of section .comment:
		 0000 00474343 3a202855 62756e74 7520342e  .GCC: (Ubuntu 4.
		 0010 342e332d 34756275 6e747535 2e312920  4.3-4ubuntu5.1) 
		 0020 342e342e 3300                        4.4.3.          

		Disassembly of section .text:

		00000000 <main>:
		   0:   55                      push   %ebp
		   1:   89 e5                   mov    %esp,%ebp
		   3:   83 e4 f0                and    $0xfffffff0,%esp
		   6:   83 ec 20                sub    $0x20,%esp
		   9:   c7 44 24 1c 0d 00 00    movl   $0xd,0x1c(%esp)
		  10:   00 
		  11:   c7 44 24 18 1a 00 00    movl   $0x1a,0x18(%esp)
		  18:   00 
		  19:   8b 44 24 18             mov    0x18(%esp),%eax
		  1d:   8b 54 24 1c             mov    0x1c(%esp),%edx
		  21:   01 c2                   add    %eax,%edx
		  23:   a1 00 00 00 00          mov    0x0,%eax
		  28:   8d 04 02                lea    (%edx,%eax,1),%eax
		  2b:   89 44 24 1c             mov    %eax,0x1c(%esp)
		  2f:   a1 00 00 00 00          mov    0x0,%eax
		  34:   dd 05 00 00 00 00       fldl   0x0
		  3a:   89 44 24 0c             mov    %eax,0xc(%esp)
		  3e:   dd 5c 24 04             fstpl  0x4(%esp)
		  42:   8b 44 24 1c             mov    0x1c(%esp),%eax
		  46:   89 04 24                mov    %eax,(%esp)
		  49:   e8 fc ff ff ff          call   4a <main+0x4a>
		  4e:   b8 00 00 00 00          mov    $0x0,%eax
		  53:   c9                      leave  
		  54:   c3                      ret 


 

                从上面的输出的内容中可以看到:

        1.text段的内容,以及对其反汇编之后生成的汇编代码,留心反汇编之后的第3行,这里2f行的00 00 00 0049行的fc ff ff ff都是指一个暂时未确定的空地址,后面在3将会对其位置确定进行描述:

                3:   83 e4 f0                and    $0xfffffff0,%esp


 

        2.data段的内容为:

                0000 23000000 00000000 00000000 00802440

         最前面的0000为段内偏移量,即main.c中定义的两个全局变量的值:23000000 00000000表示整数3500000000 00802440表示浮点数10.25,这里需要留心两个问题:

               [1] 数据的大小端存储的问题;在本人的机子中数据35表示为:23000000 00000000,即从低到高位:0x23 0x00 0x00 0x00 0x00 0x00 0x00 0x00,这是由于大小端的问题,某些机子可能采用小端的方式存储数据时就会将35表示成 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x23

              [2] 内存对齐的问题,本来只用4个字节的整形数据g_iMainValue却占用了8个字节,高位被补充了8个字节的0;这是因为我们在main.c程序中先定义了4字节整形变量g_iMainValue后定义了8字节双精度变量g_dMainValue,如果反过来先定义g_dMainValue,后定义g_iMainValue的话上述.data段的内容就会变成下面的形式:

        3)局部变量会在栈中分配内存,因此编译的时候,就没有对main.c中的局部变量iTmpNum1iTmpNum2分配空间。

                  

         2)查看这三个目标文件的段表信息,使用命令:objdump -h文件名

         main.o的段表内容为:

 

# objdump -h main.o 

		main.o:     file format elf32-i386

		Sections:
		Idx Name          Size      VMA       LMA       File off  Algn
		  0 .text         00000055  00000000  00000000  00000034  2**2
						  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
		  1 .data         00000010  00000000  00000000  00000090  2**3
						  CONTENTS, ALLOC, LOAD, DATA
		  2 .bss          00000000  00000000  00000000  000000a0  2**2
						  ALLOC
		  3 .comment      00000026  00000000  00000000  000000a0  2**0
						  CONTENTS, READONLY
		  4 .note.GNU-stack 00000000  00000000  00000000  000000c6  2**0
						  CONTENTS, READONLY


         可以看到源文件main.c被编译成目标main.o之后,

         (1) 目标文件的代码段.text的大小为00000055;

         (2) 其虚地址空间(VMA)和加载地址空间(LMA)都是空;

         (3) 数据段.data的大小为00000010,即10进制的16,这就是我们在main.c中定义的两个全局变量:8字节double类型变量和8字节的int变量(内存对齐补充了4字节)所占用的总内存大小。

        

         同样步骤也可以看到add.o的段表内容为:

# objdump -h add.o 

		add.o:     file format elf32-i386

		Sections:
		Idx Name          Size      VMA       LMA       File off  Algn
		  0 .text         0000001b  00000000  00000000  00000034  2**2
						  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
		  1 .data         00000008  00000000  00000000  00000050  2**2
						  CONTENTS, ALLOC, LOAD, DATA
		  2 .bss          00000000  00000000  00000000  00000058  2**2
						  ALLOC
		  3 .comment      00000026  00000000  00000000  00000058  2**0
						  CONTENTS, READONLY
		  4 .note.GNU-stack 00000000  00000000  00000000  0000007e  2**0
						  CONTENTS, READONLY


 

         test的段表内容为:

# objdump -h test

		test:     file format elf32-i386

		Sections:
		Idx Name          Size      VMA       LMA       File off  Algn
		  0 .rel.plt      00000000  08048094  08048094  00000094  2**2
						  CONTENTS, ALLOC, LOAD, READONLY, DATA
		  1 .plt          00000000  08048094  08048094  00000094  2**2
						  CONTENTS, ALLOC, LOAD, READONLY, CODE
		  2 .text         00000071  08048094  08048094  00000094  2**2
						  CONTENTS, ALLOC, LOAD, READONLY, CODE
		  3 .got.plt      00000000  08049108  08049108  00000108  2**2
						  CONTENTS, ALLOC, LOAD, DATA
		  4 .data         00000018  08049108  08049108  00000108  2**3
						  CONTENTS, ALLOC, LOAD, DATA
		  5 .comment      00000025  00000000  00000000  00000120  2**0
						  CONTENTS, READONLY


 

         对比分析main.oadd.otest三个目标文件的段表可以看出三者的关系:

         (1)test对应段的是对main.oadd.o对应段的综合,例如:数据段中main.o.data段大小为00000010add.o.data段大小为00000008,而test.data段的大小为00000018 = 00000010 + 00000008

         (2)test目标文件中虚地址空间(VMA)和加载地址空间(LMA)都已经被填充了地址,而main.oadd.o中的这些地址都是空的。

        

         3)查看三个目标文件的符号表,使用命令:readelf -s目标文件名,分析如下:

        

                   main.o的符号表为:

	# readelf -s main.o

		Symbol table '.symtab' contains 12 entries:
		   Num:    Value  Size Type    Bind   Vis      Ndx Name
			 0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
			 1: 00000000     0 FILE    LOCAL  DEFAULT  ABS main.c
			 2: 00000000     0 SECTION LOCAL  DEFAULT    1 
			 3: 00000000     0 SECTION LOCAL  DEFAULT    3 
			 4: 00000000     0 SECTION LOCAL  DEFAULT    4 
			 5: 00000000     0 SECTION LOCAL  DEFAULT    6 
			 6: 00000000     0 SECTION LOCAL  DEFAULT    5 
			 7: 00000000     4 OBJECT  GLOBAL DEFAULT    3 g_iMainValue
			 8: 00000008     8 OBJECT  GLOBAL DEFAULT    3 g_dMainValue
			 9: 00000000    85 FUNC    GLOBAL DEFAULT    1 main
			10: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND g_iAddValue2
			11: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND add


 

         分析上述main.o的符号表,可以看到:

         (1) main.o目标文件*有下列符号:g_iMainValueg_dMainValuemaing_iAddValue2add

         (2) 变量符号g_iMainValueg_dMainValue的为全局变量,类型为OBJECTg_iMainValue的大小为4g_dMainValue的大小为8,至于索引值,这里看到的情况就与《程序员的自我修养》一书不一致,这里看到的是3,无法与2)中看到的的main.o中的段表对应上,

         (3) 函数符号main在目标文件main.o1个段.text中,而add没有在本目标文件中定义,因此其段索引为UND

         (4) 外部符号g_iAddValue2add的索引都没有确定,暂时被标识为UND

        

         add.o的符号表为:

# readelf -s add.o

		Symbol table '.symtab' contains 10 entries:
		   Num:    Value  Size Type    Bind   Vis      Ndx Name
			 0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
			 1: 00000000     0 FILE    LOCAL  DEFAULT  ABS add.c
			 2: 00000000     0 SECTION LOCAL  DEFAULT    1 
			 3: 00000000     0 SECTION LOCAL  DEFAULT    3 
			 4: 00000000     0 SECTION LOCAL  DEFAULT    4 
			 5: 00000000     0 SECTION LOCAL  DEFAULT    6 
			 6: 00000000     0 SECTION LOCAL  DEFAULT    5 
			 7: 00000000     4 OBJECT  GLOBAL DEFAULT    3 g_iAddValue1
			 8: 00000004     4 OBJECT  GLOBAL DEFAULT    3 g_iAddValue2
			 9: 00000000    27 FUNC    GLOBAL DEFAULT    1 add


 

         分析上述add.o的符号表,可以看到:

         (1) 目标文件add.o*有三个符号,其中变量名符号为g_iAddValue1g_iAddValue2和函数名符号add,在Ndx一列中也可以看到变量符号g_iAddValue1g_iAddValue2所处段的下标为3

         (2) 变量符号为全局变量g_iAddValue1g_iAddValue2,它们的所在段的索引值也与《程序员的自我修养》一书中所述不一致,还需后续研究。

         (3) 函数符号为add

        

         test的符号表为:

# readelf -s test

		Symbol table '.symtab' contains 18 entries:
		   Num:    Value  Size Type    Bind   Vis      Ndx Name
			 0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
			 1: 08048094     0 SECTION LOCAL  DEFAULT    1 
			 2: 08048094     0 SECTION LOCAL  DEFAULT    2 
			 3: 08048094     0 SECTION LOCAL  DEFAULT    3 
			 4: 08049108     0 SECTION LOCAL  DEFAULT    4 
			 5: 08049108     0 SECTION LOCAL  DEFAULT    5 
			 6: 00000000     0 SECTION LOCAL  DEFAULT    6 
			 7: 00000000     0 FILE    LOCAL  DEFAULT  ABS add.c
			 8: 00000000     0 FILE    LOCAL  DEFAULT  ABS main.c
			 9: 0804910c     4 OBJECT  GLOBAL DEFAULT    5 g_iAddValue2
			10: 08049110     4 OBJECT  GLOBAL DEFAULT    5 g_iMainValue
			11: 08048094    27 FUNC    GLOBAL DEFAULT    3 add
			12: 08049120     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
			13: 080480b0    85 FUNC    GLOBAL DEFAULT    3 main
			14: 08049108     4 OBJECT  GLOBAL DEFAULT    5 g_iAddValue1
			15: 08049118     8 OBJECT  GLOBAL DEFAULT    5 g_dMainValue
			16: 08049120     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
			17: 08049120     0 NOTYPE  GLOBAL DEFAULT  ABS _end


 

        分析上述test的符号表,可以看到:

                   (1) 目标文件test中的符号包含了它的链接来源目标文件main.oadd.o中的符号,也即:test的符号表就是main.oadd.o两个文件符号表的整合。

                   (2) 确定所有外部符号的段索引,可以看到在main.o中未确定段索引的外部分符号g_iAddValue2addtest中都已经被填充,

        

         4)综合上面的分析可以看到链接过程主要完成了下面的几件事情:

                   (1) 将各目标文件合并,主要是将各自的对应的段进行合并,生成一个目标文件。

                            1)扫描所有输入的目标文件,获得各目标文件的各个段的长度、属性和位置,并将它们合并,计算出合并后的段的长度和位置;

                            2)将各目标文件(例如main.oadd.o)符号表中的所有符号定义和符号引用收集起来统一放到链接后目标文件test的符号表中。

                   (2) 对合并后的各个段进行空间和地址的分配,即为链接后的目标文件test的各个段分配地址空间。

                            2中也可以看到链接之前main.oadd.o中的虚地址空间(VMA)和加载地址空间(LMA)都是空,但是到test中都被填充了值,也就是进行了地址空间的分配。                  

                   (3) 对各个符号进行解析和重新定位。

                            例如,在扫描test合并之后的符号表,查找其中位置未定的引用符号add(main.o中合并过来的符号引用add)等,该add的定义位置也被从add.o中合并到了test的符号表,因此其位置也可以确定了。

                            如果在test合并之后的符号表中有查找不到定义位置的符号,则会报出链接错误。

 

         其中最重要步骤就是符号的解析和重定位。

        

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,如下图所示:

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

         2)确定符号的引用位置      

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

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

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

               

这里需要注意:

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

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

                  

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

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


 

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

# objdump -d test
		test:     file format elf32-i386
		Disassembly of section .text:
		08048094 <add>:
		 8048094:       55                      push   %ebp
		 8048095:       89 e5                   mov    %esp,%ebp
		 8048097:       8b 45 0c                mov    0xc(%ebp),%eax
		 804809a:       8b 55 08                mov    0x8(%ebp),%edx
		 804809d:       8d 04 02                lea    (%edx,%eax,1),%eax
		 80480a0:       89 c2                   mov    %eax,%edx
		 80480a2:       03 55 10                add    0x10(%ebp),%edx
		 80480a5:       a1 08 91 04 08          mov    0x8049108,%eax
		 80480aa:       8d 04 02                lea    (%edx,%eax,1),%eax
		 80480ad:       5d                      pop    %ebp
		 80480ae:       c3                      ret    
		 80480af:       90                      nop

		080480b0 <main>:
		 80480b0:       55                      push   %ebp
		 80480b1:       89 e5                   mov    %esp,%ebp
		 80480b3:       83 e4 f0                and    $0xfffffff0,%esp
		 80480b6:       83 ec 20                sub    $0x20,%esp
		 80480b9:       c7 44 24 1c 0d 00 00    movl   $0xd,0x1c(%esp)
		 80480c0:       00 
		 80480c1:       c7 44 24 18 1a 00 00    movl   $0x1a,0x18(%esp)
		 80480c8:       00 
		 80480c9:       8b 44 24 18             mov    0x18(%esp),%eax
		 80480cd:       8b 54 24 1c             mov    0x1c(%esp),%edx
		 80480d1:       01 c2                   add    %eax,%edx
		 80480d3:       a1 10 91 04 08          mov    0x8049110,%eax
		 80480d8:       8d 04 02                lea    (%edx,%eax,1),%eax
		 80480db:       89 44 24 1c             mov    %eax,0x1c(%esp)
		 80480df:       a1 0c 91 04 08          mov    0x804910c,%eax
		 80480e4:       dd 05 18 91 04 08       fldl   0x8049118
		 80480ea:       89 44 24 0c             mov    %eax,0xc(%esp)
		 80480ee:       dd 5c 24 04             fstpl  0x4(%esp)
		 80480f2:       8b 44 24 1c             mov    0x1c(%esp),%eax
		 80480f6:       89 04 24                mov    %eax,(%esp)
		 80480f9:       e8 96 ff ff ff          call   8048094 <add>
		 80480fe:       b8 00 00 00 00          mov    $0x0,%eax
		 8048103:       c9                      leave  
		 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的重定位表为:

		# objdump -r main.o
		main.o:     file format elf32-i386
		RELOCATION RECORDS FOR [.text]:
		OFFSET   TYPE              VALUE 
		00000024 R_386_32          g_iMainValue
		00000030 R_386_32          g_iAddValue2
		00000036 R_386_32          g_dMainValue
		0000004a R_386_PC32        add


 

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

        

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

		# objdump -r add.o
		add.o:     file format elf32-i386
		RELOCATION RECORDS FOR [.text]:
		OFFSET   TYPE              VALUE 
		00000012 R_386_32          g_iAddValue1


 

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

        

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

		# objdump -r test
		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)行号信息。