Chinaunix首页 | 论坛 | 博客
  • 博客访问: 116581
  • 博文数量: 22
  • 博客积分: 835
  • 博客等级: 准尉
  • 技术积分: 260
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-25 21:23
文章分类

全部博文(22)

文章存档

2011年(1)

2009年(21)

我的朋友

分类: LINUX

2009-11-13 13:55:01

0、编译一个可执行文件的步骤:
 0)Makefile根据工程属性组织gcc的各参数,并驱动gcc进行实际工作
 1)gcc驱动预处理程序CPP进行处理,得到.i文件
 2)gcc驱动编译器cc1进行编译,得到.s文件
 3)gcc驱动汇编器as进行汇编,得到.o文件
 4)gcc驱动ld进行链接,得到可执行文件
 
1、链接器的两个主要功能:
 1)符号解析:将每个符号的引用和符号的定义联系起来,
 2)重定位:把目标文件从0开始的代码和数据节,组装成可以执行的统一地址。并且把每个符号定义和一个存储位置联系起来。最终得到可执行的文件。
 
2、链接可以发生在下面三种情况:
 1)编译的时静态链接
 2)加载到内存的动态链接
 3)运行的时动态链接
 
3、目标文件的三种格式:
 1)可以重定位目标文件
 2)可以执行目标文件
 3)共享目标文件,可以在加载或者运行时被动态地加载到存储器并链接
 Linux系统使用的目标文件格式是Unix ELF可执行和可链接格式,除此之外Core Dump文件也是ELF格式。
 
4、目标文件格式:
可重定位和可共享目标文件格式:
 ELF头 + Secontions data + Sections Header Tabl
可执行目标文件格式:
 ELF头 + Program header table + segments data
在可重定位和可共享目标文件中,ELF是由各个区(section)组成的,每个section有下面几个属性:
Name   Type   Addr       Off       Size     ES   Flg   Lk        Inf     Al
名字 + 类型 + 起始地址 + 文件的偏移地址 + 区大小 + 表区的大小 + 区标志 + 相关区索引 + 其他区信息 + 对齐字节数
这些属性组成了区头表中的每个字段,可以用readelf来查看一个具体信息。除下面两个属性外,其他属性比较明了。
区类型:
  PROGBITS:程序内容,包括代码,数据和调试器信息。
  NOBITS:类似于PROGBITS,但文件本身中没有分配空间。用于在程序载入时分配的BSS数据。
  SYMTAB和DYNSYM:SYMTAB表包含所有的符号,主要用于普通的连接器,而DYNSYM只是用于动态连接的符号。   
  STRTAB:一个字符串表,通常为不同的目的保留不同的字符串表,如区名字,普通符号名字和动态连接名字。
  REL:重载信息。REL入口将代码或数据中保存的基地址加上重载值。
  DYNAMIC和HASH:动态连接类型和运行时符号哈希表。  
区标志:  
 每个区有三个标志位:ALLOC,在程序装载的时候这个区要占用内存;
       WRITE,当该区被载入的时候是可写的;
       EXECINSTR,该区包含了可执行机器代码。  
经常见到的段:
  .text 是带有ALLOC+EXECINSTR属性的PROGBITS类型的区。
  .data 是带有ALLOC+WRITE属性的PROGBITS类型的区。
  .rodata 是带有ALLOC属性的PROGBITS类型的区。是只读数据,所以没有WRITE属性。
  .bss 是带有ALLOC+WRITE属性的NOBITS类型的区。BSS区在文件中不占空间,所以是NOBITS类型,但在运行时分配,所以有ALLOC属性。
  .rel.txt,.rel.data,和.rel.rodata 每个都是REL类型的区。保存着对应的文本或数据区的重载信息。
  .init和.fini,每个都是带有ALLOC+EXECINSTR属性的PROGBITS类型的区,但是分别包含了在程序启动或终止的时候运行的代码。
    C++特有的区,因为C++使用了带可执行的初始化和终止代码的全局数据。
  .symtab和.dynsym 分别是STMTAB和DNYSYM的类型的区,普通和动态连接器符号表。动态连接器符号表带有ALLOC标志,因为需要在运行时装载。
  .strtab和.dynstr 都是STRTAB类型的区,一个命名字符串的表,用于符号表或区表的区名字。
  .dynstr区保存是动态连接器符号表的字符串,因为需要在运行时载入系统所以所有的ALLOC标志都设置为1。  
  .debug包含了调试器用的符号表,.line为调试器保存着从目标代码到源程序行数的对应,
  .comment保存着文档字符串,通常是版本控制中的版本号。
 还有一些特殊的区,如.got和.plt,是用于动态连接的全局偏移量表和进程连接表,
具体每个区在链接过程中的用途,后续分析
  
5、符号表.symtab中三种不同的符号:
 1)自己定义并能被其他模块引用的全局符号。一般不带static定义的C函数和不带static的全局变量都放在这里。
 2)由其他模块定义而被自己引用的全局符号。称为外部符号,由其他文件定义的函数和全局变量
 3)自己定义和引用的本地符号。自己定义的static函数和变量存放在这里。
 对于局部变量是因为是在运行时存放在栈中,这些东西不存放在符号表中。
 符号表是由汇编器生成的,在编译生成的.s文件中可以找到这些符号,在ELF的.symtab中包含一个符号数组,数组
 中的每一个元素由一个entry结构组成
typedef struct {
int name;   //在.strtab区中的一个offset值
int value;   //该符号相对于所在区的偏移地址
int size;   //该符号所占字节数
char type:4,   //符号类型:data,fun,section,file
     binding:4;   //本地符号还是全局符号
char reserved;   //保留
char section;   //定义该符号的区在区头表中的索引及ABS,UNDEF,COMMON 
}    
ABS:表示不该被重定位的符号
UNDEF:未定义的符号,可能在其他模块中进行定义
COMMON:还未被分配位置的未初始化的数据目标,这个时候value字段代表对齐请求,size给出最小的大小
# readelf -s test.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 test.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    5
     6: 00000000     0 SECTION LOCAL  DEFAULT    7
     7: 00000000     0 SECTION LOCAL  DEFAULT    6
     8: 00000000    38 FUNC    GLOBAL DEFAULT    1 main
     9: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND output
如下前面0-7用于ld内部使用,从上最后两行可以看到:
main是定义在区1(.text)中偏移0开始的占38字节的全局函数符号
output是定义在其他目标文件中(UND)的全局符号,这个符号还未能解析
解析的工作则要交给ld来进行,解析完了,则会获取到这些字段。    
         
6、符号解析:
链接器解析符号引用的方法是将每个引用和输入的可重定位目标文件的符号表中的一个确定的符号定义联系起来。
对于那些引用定义在相同模块中的本地符号的引用,符号解析非常简单。
对全局符号的引用解析相对复杂,因为相同的符号可能会被多个目标文件定义。函数和已经初始化的全局变量是强符号
未初始化的变量是弱符号。强符号只允许定义次,如果出现强符号和弱符号同名,则选择强符号。如果有多个弱符号同名
则任意选择一个弱符号。这种情况下可能会带来一些不能觉察到的潜在错误,编译时指定gcc -warn-common输出一条告警
信息。
当输入给ld的目标文件中有静态库时,则对符号的解析又有不同,见下面:
 
7、包括静态库的符号解析:
静态库是由多个可重定位的目标文件打包后的集合文件,它也可以作为链接器的输入
在Unix中静态库一般以存档Archive特殊文件格式存放在磁盘上(经常看到的***.a就是静态库,而***.so则是动态链接库)
它是一组链接起来的可重定位目标文件的集合。有一个头部描述每个目标文件的大小及位置信息。
创建静态库的方法:
1)gcc -c test1.c test2.c
2)ar rcs test.a test1.o test2.o
在链接的时候,链接器从静态库中只拷贝被程序引用的目标模块,具体的符号解析过程:
在符号解析的时候链接器从左到右读取输入的目标文件和存档文件,链接器维护一个可重定位目标文件的集合E,一个未解析的符号
集合U,以及包含前面输入文件中已定义的符号集合D。并有如下解析步骤:
1)ld判断每一个输入文件f是目标文件还是存档文件,如果是目标文件则,把f放到E中,并提取f中相应信息放到U和D中。
2)如果f是存档文件,则链接器尝试从f中包含的目标文件中定义的符号和U中符号进行匹配,如果找到,则更新E/U/D信息
   最后丢弃没有任何符号匹配的f中的目标文件。
3)如果链接器完成了对所有输入文件的扫描后发现U是非空的,则说明有未定义的符号,输出错误并终止;否则合并并重定位E中的
目标文件,从而生成可执行文件。

8、重定位:
当链接器完成了符号解析外,则要进行重新定位,把代码中的每个符号引用和确定的一个符号定义联系起来。
这个过程将合并目标模块,并为每个符号分配运行时地址,一般来说分为以下两步来做:
 1)重定位节和符号定义
 链接器将相同类型的节合并为同一类型的聚合节,然后将运行时地址赋给新的节,及赋给每个定义的符号
 2)重定位节中的符号引用
 修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。这个时候需要用到目标文件的重定位节.rel.text, .rel.data
汇编器在遇到对最终位置未知道的外部引用的时候,则会生成一个重定位表目,告诉链接器在将目标文件合并成可执行文件时任何修改这个
引用。代码的重定位表目放在.rel.text中,已初始化数据的重定位表目存放在.rel.data中,
重定位表目的格式:
typedef struct {
int offset;
int symbol:24,
    type:8;
}Elf32_Rel;
有11种不同的重定位类型,但只需要关注如下两种即可:
1)R_386_PC32:重定位一个时用32位PC相关的地址引用,是否是虚拟地址?
2)R_386_32:重定位一个绝对地址的引用,CPU可以直接时用该地址,是否是物理地址?
未完,代续。。。
阅读(3004) | 评论(0) | 转发(0) |
0

上一篇:Linux汇编语言学习记要

下一篇:LD学习记要

给主人留下些什么吧!~~