读书频道 > 系统 > linux > 深度探索Linux操作系统系统构建和原理解析
1. 目标文件
2013-09-28 13:04:51     我来说两句 
收藏    我要投稿   
全书一共8章:第1章介绍了如何准备工作环境。在第2章中构建了编译工具链,这是后面构建操作系统各个组件的基础。在这一章中,不仅详细讲解了工具链的构建过程,而且还通过对编译链接过程的探讨,深入讨论了工具链  立即去当当网订购

汇编过程的产物是目标文件,同前面的预编译和编译阶段产生的文本文件不同,目标文件的格式更复杂,其中包括链接需要的信息,所以在理解汇编过程前,我们需要了解一下目标文件的格式。Linux下的二进制文件包括可执行文件、静态库和动态库等,均采用ELF格式存储,目标文件的格式也不例外,也采用ELF格式存储。

对于32位的ELF文件来说,其最前部是文件头部信息,描述了整个文件的基本属性,除了包括该文件运行在什么操作系统中、运行在什么硬件体系结构上、程序入口地址是什么等基本信息外,最重要的是记录了两个表格的相关信息,如表格所在的位置、其中包括的条目数等。这两个表格一个是Section Header Table,主要是供编译时链接使用的,表格中定义了各个段的位置、长度、属性等信息;另外一个是Program Header Table,主要是供内核和动态加载器从磁盘加载ELF文件到内存时使用的。对于目标文件,由于其只是编译过程的一个中间产物,不涉及装载运行,因此,在目标文件中不会创建Program Header Table。

在后续内容中,我们将Segment和Section都翻译为段,读者可根据上下文区分。在有的上下文中,段指的是真正加载到内存中的Segment,而有的指的是ELF中链接时使用的Section。
下面我们通过命令readelf列出目标文件foo2.o的ELF头信息。
root@baisheng:~/demo# gcc -c hello.c foo1.c foo2.c
root@baisheng:~/demo# readelf -h foo2.o
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                               ELF32
  Data:                                2's complement, little endian
  Version:                             1 (current)
  OS/ABI:                              UNIX - System V
  ABI Version:                         0
  Type:                                REL (Relocatable file)
  Machine:                             Intel 80386
  Version:                             0x1
  Entry point address:                 0x0
  Start of program headers:            0 (bytes into file)
  Start of section headers:            264 (bytes into file)
  Flags:                               0x0
  Size of this header:                 52 (bytes)
  Size of program headers:             0 (bytes)
  Number of program headers:           0
  Size of section headers:             40 (bytes)
  Number of section headers:           12
  Section header string table index:   9

foo2.o的ELF头占用了52字节,通过ELF头可见该文件是32位的ELF文件;使用“little endian”字节序存储字节;ABI遵循UNIX-System V标准;运行在类UNIX系统上;该文件是一个“REL (Relocatable file)”类型的文件,通常,可执行文件的类型是“EXEC (Executable file)”,动态共享库的类型是“DYN (Shared object file)”,静态库和目标文件的类型是“REL (Relocatable file)”;该目标文件是为IA32架构编译的;因为是目标文件,不存在执行的概念,所以程序入口“Entry point address”在这里不适用(同样的道理,Program Header Table也不适用);foo2.o中的Section Header Table在偏移264字节处,Section Header Table中的每个Section Header占用40字节,Section Header Table共包含12个Section Header。

在文件头信息后,就是各个段了。毫不夸张地说,ELF文件就是段的组合。大体上,段可以分为如下几类:一类是存储指令的,通常称为代码段;第二类是存储数据的,通常称为数据段。但是存储数据的又细分为两个段,已经初始化的全局数据存放在“.data”段中,未初始化的全局数据存储在“.bss”段。不要被BSS这个令人困惑的名称迷惑,这个名称不是非常贴切,完全是历史遗留的,“.data”段和“.bss”段本质并没有什么不同,但是因为未初始化的变量不包含数据,所以在ELF文件中不需要占用空间,程序装载时在内存中即时分配就可以了。所以,为了节省存储器空间,人为地将存储数据的部分划分为两个段。除了最重要的代码段和数据段外,汇编器还将在目标文件中创建辅助链接段,存储如符号表、重定位表等。

我们考察目标文件foo2.o的Section Header Table ,因为排版篇幅的关系,删除了后面几列,这不影响我们讨论。有兴趣的读者,可以自行查看完整的命令输出。
root@baisheng:~/demo# readelf -S foo2.o
There are 12 section headers, starting at offset 0x104:

Section Headers:
  [Nr] Name               Type         Addr        Off        Size
  [ 0]                    NULL         00000000    000000    000000
  [ 1] .text              PROGBITS     00000000    000034    000010
  [ 2] .rel.text          REL          00000000    00039c    000008
  [ 3] .data              PROGBITS     00000000    000044    000004
  [ 4] .bss               NOBITS       00000000    000048    000000
  [ 5] .comment           PROGBITS     00000000    000048    00002b
  [ 6] .note.GNU-stack    PROGBITS     00000000    000073    000000
  [ 7] .eh_frame          PROGBITS     00000000    000074    000038
  [ 8] .rel.eh_frame      REL          00000000    0003a4    000008
  [ 9] .shstrtab          STRTAB       00000000    0000ac    000057
  [10] .symtab            SYMTAB       00000000    0002e4    0000a0
  [11] .strtab            STRTAB       00000000    000384    000017

根据输出可见,目标文件foo2.o的Section Header Table中包含12个Section Header:

“.text”段存储在文件中偏移0x34处,占据0x10个字节。读者不要将“.text”段和进程的代码段混淆,进程的代码段不仅包括“.text”段,在后面链接时,我们还会看到,包括.init、.fini等段存储的代码都属于代码段。这些段都被映射到Program Header Table中的一个段,在ELF加载时,统一作为进程的代码段。

“.data”段存储在文件中偏移0x44字节处,占据0x4字节空间。

如我们在前面讨论的,虽然目标文件的Section Header Table中包含“.bss”段,但是因为其不必记录数据,所以“.bss”段在文件中只占据Section Header Table中的一个Section Header,而并没有对应的段。在加载程序时,加载器将依据“.bss”段的Section Header中的信息,在内存中为其分配空间。考察程序hello的Section Header Table:
root@baisheng:~/demo# readelf -S hello
There are 30 section headers, starting at offset 0x1198:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size
...
  [25] .bss              NOBITS          0804a024 001024 000004
  [26] .comment          PROGBITS        00000000 001024 00006b
...

根据输出可见,“.bss”段在文件中偏移为0x001024,但是占用的空间(Size)并不是0字节,而是0x4个字节,这是为什么呢?而我们再观察“.comment”段在文件中的偏移,也为0x001024。也就是说,正如我们前面讨论的,“.bss”段在磁盘文件中并未占据任何空间,“.bss”段的Size只是告诉程序加载器在加载程序时,在内存中为该段分配的内存空间。

“.symtab”段记录的是符号表。因为符号的名字字串长度可变,所以目标文件将符号的名字字符串剥离出来,记录在另外一个段“.strtab”中,符号表使用符号名字的索引在段“.strtab”中的偏移来确定符号的名字。

同样的道理,“.shstrtab”中记录的是段的名字(sh是section header的简写)。

以“rel”开头的,如“.rel.text”、“.rel.eh_frame”,记录的是段中需要重定位的符号。

“.eh_frame”段中记录的是调试和异常处理时用到的信息。

“.comment”、“.note.GNU-stack”等段如其名字所示,都是一些“comment”和“note”,无论是链接还是装载都不会用到,我们不必关心。

综上所述,目标文件的格式如图2-3所示。

 

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:2.1.3 汇编
下一篇:2. 翻译机器指令
相关文章
图文推荐
3.3.6 GNOME的软件管
3.3.5 GNOME的文件管
3.3.4 GNOME的窗口管
3.3.3 收藏夹和快捷
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站