当前位置:Linux教程 - Linux - ELF文件格式(中文)(二)

ELF文件格式(中文)(二)




=================== String Table 字符串表=========================


String table sections 保存着以NULL终止的一系列字符,一般我们称为字
符串。object文件使用这些字符串来描绘符号和section名。一个字符串的
参考是一个string table section的索引。第一个字节,即索引0,被定义保
存着一个NULL字符。同样的,一个string table的最后一个字节保存着一个
NULL字符,所有的字符串都是以NULL终止。索引0的字符串是没有名字或者说
是NULL,它的解释依靠上下文。一个空的string table section是允许的;
它的section header的成员sh_size将为0。对空的string table来说,非0的
索引是没有用的。

一个 settion 头的 sh_name 成员保存了一个对应于该 setion 头字符表部分
的索引(就象ELF头的 e_shstrndx 成员所特指的那样。下表列出了一个有 25 字节
的字符串表(这些字符串和不同的索引相关联):


Index +0 +1 +2 +3 +4 +5 +6 +7 +8 +9
===== == == == == == == == == == ==
0 n a m e . V a r
10 i a b l e a b l e
20 x x


+ Figure 1-15: String Table Indexes

Index String
===== ======
0 none
1 "name."
7 "Variable"
11 "able"
16 "able"
24 null string


如上所示,一个字符串表可能涉及该 section 中的任意字节。一个字符串可能
引用不止一次;引用子串的情况是可能存在的;一个字符串也可能被引用若干次;而
不被引用的字符串也是允许存在的。

==================== Symbol Table 符号表=========================


一个object文件的符号表保存了一个程序在定位和重定位时需要的定义和引用的信息。
一个符号表索引是相应的下标。0表项特指了该表的第一个入口,就象未定义的符号
索引一样。初始入口的内容在该 section 的后续部分被指定。

Name Value
==== =====
STN_UNDEF 0

一个符号表入口有如下的格式:

+ Figure 1-16: Symbol Table Entry

typedef struct {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;

* st_name

该成员保存了进入该object文件的符号字符串表入口的索引(保留了符号名的表达字符)。
如果该值不为 0 ,则它代表了给出符号名的字符串表索引。否则,该符号无名。

注意:External C 符号和object文件的符号表有相同的名称。

* st_value

该成员给出了相应的符号值。它可能是绝对值或地址等等(依赖于上下文);
细节如下所述。

* st_size

许多符号和大小相关。比如,一个数据对象的大小是该对象所包含的字节数目。
如果该符号的大小未知或没有大小则这个成员为 0 。

* st_info

成员指出了符号的类型和相应的属性。相应的列表如下所示。下面的代码说明了
如何操作该值。

#define ELF32_ST_BIND(i) ((i)>>4)
#define ELF32_ST_TYPE(i) ((i)&0xf)
#define ELF32_ST_INFO(b, t) (((b)<<4)+((t)&0xf))

* st_other

该成员目前为 0 ,没有含义。

* st_shndx

每一个符号表的入口都定义为和某些 section 相关;该成员保存了相关的 section
头索引。就象 Figure 1-8 {*}和相关的文字所描述的那样,某些 section 索引
指出了特殊的含义。

一个符号的属性决定了可链接性能和行为。

+ Figure 1-17: Symbol Binding, ELF32_ST_BIND

Name Value
==== =====
STB_LOCAL 0
STB_GLOBAL 1
STB_WEAK 2
STB_LOPROC 13
STB_HIPROC 15

* STB_LOCAL

在包含了其定义的object文件之外的局部符号是不可见的。不同文件中的具有相同
名称的局部符号并不相互妨碍。

* STB_GLOBAL

全局符号是对所有的object目标文件可见的。一个文件中的全局符号的定义可以
满足另一个文件中对(该文件中)未定义的全局符号的引用。

* STB_WEAK

弱符号相似于全局符号,但是他们定义的优先级比较低一些。

* STB_LOPROC through STB_HIPROC

其所包含范围中的值由相应的处理器语义所保留。

全局符号和弱符号的区别主要在两个方面。

* 当链接器链接几个可重定位的目标文件时,它不允许 STB_GLOBAL 符号的同名
多重定义。另一方面,如果一个全局符号的定义存在则具有相同名称的弱符号名不会
引起错误。链接器将认可全局符号的定义而忽略弱符号的定义。与此相似,如果有一个
普通符号(比如,一个符号的 st_shndx 域包含 SHN_COMMON),则一个同名的弱符号
不会引起错误。链接器同样认可普通符号的定义而忽略弱符号。

* 当链接器搜索档案库的时候,它选出包含了未定义的全局符号的存档成员。该成员
的定义或者是全局的或者是一个弱符号。链接器不会为了解决一个未定义的弱符号
选出存档成员。未定义的弱符号具有 0 值。

在每一个符号表中,所有具有 STB_LOCAL 约束的符号优先于弱符号和全局符号。
就象上面 "sections" 中描述的那样,一个符号表部分的 sh_info 头中的成员
保留了第一个非局部符号的符号表索引。

符号的类型提供了一个为相关入口的普遍分类。

+ Figure 1-18: Symbol Types, ELF32_ST_TYPE

Name Value
==== =====
STT_NOTYPE 0
STT_OBJECT 1
STT_FUNC 2
STT_SECTION 3
STT_FILE 4
STT_LOPROC 13
STT_HIPROC 15

* STT_NOTYPE

该符号的类型没有指定。

* STT_OBJECT

该符号和一个数据对象相关,比如一个变量、一个数组等。

* STT_FUNC

该符号和一个函数或其他可执行代码相关。

* STT_SECTION

该符号和一个 section 相关。这种类型的符号表入口主要是为了重定位,一般的
具有 STB_LOCAL 约束。


* STT_FILE

按惯例而言,该符号给出了和目标文件相关的源文件名称。一个具有 STB_LOCAL
约束的文件符号,其 section 索引为 SHN_ABS ,并且它优先于当前对应该文件的
其他 STB_LOCAL 符号。

* STT_LOPROC through STT_HIPROC

该范围中的值是为处理器语义保留的。

共享文件中的函数符号(具有 STT_FUNC 类型)有特殊的意义。当其他的目标文件
从一个共享文件中引用一个函数时,链接器自动的为引用符号创建一个链接表。除了
STT_FUNC 之外,共享的目标符号将不会自动的通过链接表引用。


如果一个符号涉及到一个 section 的特定定位,则其 section 索引成员 st_shndx
将保留一个到该 section 头的索引。当该 section 在重定位过程中不断
移动一样,符号的值也相应变化,而该符号的引用在程序中指向同样的定位。某些
特殊的 section 索引有其他的语义。


* SHN_ABS

该符号有一个不会随重定位变化的绝对值。

* SHN_COMMON

该符号标识了一个没有被分配的普通块。该符号的值给出了相应的系统参数,就象
一个 section 的 sh_addralign 成员。也就是说,链接器将分配一个地址给
该符号,地址的值是 st_value 的倍数。该符号的大小指出了需要的字节数。


* SHN_UNDEF

该 section 表索引表明该符号是未定义的。当链接器将该目标文件和另一个定义
该符号的文件相装配的时候,该文件内对该符号的引用将链接到当前实际的定义。

如上所述,符号表的 0 索引(STN_UNDEF)是保留的,它包含了如下内容:

+ Figure 1-19: Symbol Table Entry: Index 0

Name Value Note
==== ===== ====
st_name 0 No name
st_value 0 Zero value
st_size 0 No size
st_info 0 No type, local binding
st_other 0
st_shndx SHN_UNDEF No section


Symbol Values(符号值)

符号表入口对于不同的目标文件而言对 st_value 成员有一些不同的解释。

* 在可重定位文件中, st_value 保存了 section 索引为 SHN_COMMON 符号
的强制对齐值。

* 在可重定位文件中, st_value 保存了一个符号的 section 偏移。也就是说,
st_value 是从 st_shndx 定义的 section 开头的偏移量。

* 在可执行的和可共享的目标文件中, st_value 保存了一个虚拟地址。为了使
这些文件符号对于动态链接器更为有效,文件层面上的 section 偏移让位于内存
层面上的虚拟地址( section 编号无关的)。


尽管符号表值对于不同的目标文件有相似的含义,相应的程序还是可以有效地访问数据。


====================== Relocation (重定位)==========================


重定位是连接符号引用和符号定义的过程。比如,当一个程序调用一个函数的时候,
相关的调用必须在执行时把控制传送到正确的目标地址。换句话说,重定位文件应当
包含有如何修改他们的 section 内容的信息,从而允许可执行文件或共享目标文件
为一个进程的程序映像保存正确的信息。重定位入口就是这样的数据。

+ Figure 1-20: Relocation Entries

typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;

typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Sword r_addend;
} Elf32_Rela;

* r_offset

该成员给出了应用重定位行为的地址。对于一个重定位文件而言,该值是从该
section 开始处到受到重定位影响的存储单位的字节偏移量。对一个可执行文件
或一个共享目标而言,该值是受到重定位影响的存储单位的虚拟地址。


* r_info

该成员给出了具有受重定位影响因素的符号表索引和重定位应用的类型。比如,
一个调用指令的重定位入口应当包含被调用函数的符号索引。如果该索引是
STN_UNDEF (未定义的符号索引),重定位将使用 0 作为该符号的值。重定位
类型是和处理器相关的。当正文(text)引用到一个重定位入口的重定位类型或符
号表索引,它表明相应的应用 ELF32_R_TYPE或 ELF32_R_SYM 于入口的 r_info
成员。

#define ELF32_R_SYM(i) ((i)>>8)
#define ELF32_R_TYPE(i) ((unsigned char)(i))
#define ELF32_R_INFO(s, t) ((s)<<8+(unsigned char)(t))

* r_addend

该成员指定一个常量加数(用于计算将要存储于重定位域中的值)。

如上所述,只有 Elf32_Rela 入口包含一个明确的加数。Elf32_Rel 类型
的入口在可以修改的地址中存储一个隐含的加数。依赖于处理器结构,一种形式
或其他形式也许是必须的或更为方便的。因此,特定机器的应用应当使用一种排他
性的形式或依赖于上下文的另一种形式。

一个重定位 section 关联了两个其他的 section :一个符号表和一个可修改
的 section 。该 section 头的成员 sh_info 和 sh_link (在上文中的
“ section ”部分中有描述)指示了这种关系。重定位入口中的成员 r_offset
对于不同的目标文件有少许差异。

* 在可重定位文件中,r_offset 表示了一个 section 偏移。也就是说,重定位
section自己描述了如何修改其他在文件中的其他section; 重定位偏移量指
明了一个在第二个section中的存储器单元。

* 在可执行和共享的目标文件中,r_offset 表示一个虚拟地址。为了使得这些
文件的重定位入口更为有用(对于动态链接器而言),该 section 偏移(文件
中)应当让位于一个虚拟地址(内存中的)。

尽管为了允许相关的程序更为有效的访问而让 r_offset 的解释对于不同的目标
文件有所不同,重定位类型的含义是相同的。


Relocation Types(重定位类型)

重定位入口描述了怎样变更下面的指令和数据域(位数在表的两边角下)。

+ Figure 1-21: Relocatable Fields

+---------------------------+
| word32 |
31---------------------------0


* word32

指定一个以任意字节对齐方式占用 4 字节的 32 位域。这些值使用与 32 位 Intel
体系相同的字节顺序。

3------2------1------0------+
0x01020304 | 01 | 02 | 03 | 04 |
31------+------+------+------0

下面的计算假设正在将一个可重定位文件转换为一个可执行或共享的目标文件。
从概念上来说,链接器合并一个或多个可重定位文件来组成输出。它首先决定
怎样合并、定位输入文件,然后更新符号值,最后进行重定位。对于可执行文件
和共享的目标文件而言,重定位过程是相似的并有相同的结果。下面的描述使用
如下的约定符号。

* A

表示用于计算可重定位的域值的加数。

* B

表示了在执行过程中一个共享目标被加载到内存时的基地址。一般情况下,一个
共享object文件使用的基虚地址为0,但是一个可执行地址就跟共享object文件
不同了。

* G

表示了在执行过程中重定位入口符号驻留在全局偏移表中的偏移。请参阅
第二部分中的“ Global Offset Table (全局偏移表)”获得更多
的信息。

* GOT

表示了全局偏移表的地址。请参阅第二部分中的“ Global Offset Table
(全局偏移表)”获得更多的信息。

* L

表示一个符号的过程链接表入口的位置( section 偏移或地址)。一个过程
链接表入口重定位一个函数调用到正确的目的单元。链接器创建初始的链接表,
而动态链接器在执行中修改入口。
请参阅第二部分中的“ Procedure Linkage Table (过程链接表)”获得更多
的信息

* P

表示(section 偏移或地址)被重定位的存储单元位置(使用 r_offset 计算的)。

* S

表示索引驻留在重定位入口处的符号值。

一个重定位入口的 r_offset 值指定了受影响的存储单元的首字节的偏移
或虚拟地址。重定位类型指定了哪一位(bit)将要改变,以及怎样计算它们的值。
在 SYSTEM V 体系中仅仅使用 Elf32_Rel 重定位入口,将要被重定位的域中
保留了加数。在所有的情况下,加数和计算结果使用相同字节顺序。

+ Figure 1-22(表 1-22): Relocation Types(重定位类型)

Name Value Field Calculation
==== ===== ===== ===========
R_386_NONE 0 none none
R_386_32 1 word32 S + A
R_386_PC32 2 word32 S + A - P
R_386_GOT32 3 word32 G + A - P
R_386_PLT32 4 word32 L + A - P
R_386_COPY 5 none none
R_386_GLOB_DAT 6 word32 S
R_386_JMP_SLOT 7 word32 S
R_386_RELATIVE 8 word32 B + A
R_386_GOTOFF 9 word32 S + A - GOT
R_386_GOTPC 10 word32 GOT + A - P

有的重定位类型有不同于简单计算的语义。

* R_386_GOT32

这种重定位类型计算全局偏移表基地址到符号的全局偏移表
入口之间的间隔。这样另外通知了 link editor 建立一个全局偏移表 。


* R_386_PLT32

这种重定位类型计算符号的过程链接表入口地址,并另外通知链接器建立一个
过程链接表。


* R_386_COPY

链接器创建该重定位类型用于动态链接。它的偏移成员涉及一个可写段中的一个
位置。符号表索引指定一个可能存在于当前 object file 或在一个shared object
中的符号。在执行过程中,动态链接器把和 shared object 符号相关的数据
拷贝到该偏移所指定的位置。

* R_386_GLOB_DAT

这种重定位类型用于设置一个全局偏移表入口为指定符号的地址。该特定的重定位
类型允许你决定符号和全局偏移表入口之间的一致性。


* R_386_JMP_SLOT {*}

链接器创建该重定位类型用于动态链接。其偏移成员给出了一个过程链接表入口的
位置。动态链接器修改该过程链接表入口以便向特定的符号地址传递控制。
[参阅第二部分中的 "Procedure Linkage Table(过程链接表)"]

* R_386_RELATIVE

链接器创建该重定位类型用于动态链接。其偏移成员给出了包含表达相关地址值
的一个 shared object 中的位置。动态链接器计算相应的虚拟地址(把该
shared object 装载地址和相对地址相加)。该类型的重定位入口必须为
符号表索引指定为 0 。


* R_386_GOTOFF

这种重定位类型计算符号值和全局偏移表地址之间的不同。另外还通知链接器
建立全局偏移表(GOT)。



* R_386_GOTPC

这种重定位类型类似于 R_386_PC32 ,不同的是它在计算中使用全局偏移表。
这种重定位中引用的符号通常是 _GLOBAL_OFFSET_TABLE_ ,该符号通知了
链接器建立全局偏移表(GOT)。

________________________________________________________________


2. PROGRAM LOADING AND DYNAMIC LINKING
程序装入和动态链接
________________________________________________________________


======================== Introduction(介绍) =========================


第二部分描述了 object file 信息和创建运行程序的系统行为。其中部分信息
适合所有的系统,其他信息是和特定处理器相关的。


可执行和共享的 object file 静态的描绘了程序。为了执行这样的程序,系统
用这些文件创建动态的程序表现,或进程映像。一个进程映像有用于保存其代码、
数据、堆栈等等的段。这个部分的主要章节讨论如下的内容。


* 程序头(Program header)。该章节补充第一部分,描述和程序运行相关的
object file 结构。即文件中主要的数据结构、程序头表、定位段映像,也
包含了为该程序创建内存映像所需要的信息。

* 载入程序(Program loading)。在给定一个 object file 时,系统为了
让它运行必须将它载入内存。

* 动态链接(Dynamic linking)。在载入了程序之后,系统必须通过解决组
成该进程的 object file之间的符号引用问题来完成进程映像的过程。

注意:指定了处理器范围的 ELF 常量是有命名约定的。比如,DT_ , PT_ ,
用于特定处理器扩展名,组合了处理器的名称(如 DT_M32_SPECIAL )。
没有使用这种约定但是预先存在的处理器扩展名是允许的。


Pre-existing Extensions
(预先存在的扩展名)
=======================
DT_JMP_REL


====================== Program Header(程序头) ======================


一个可执行的或共享的 object file 的程序头表是一个结构数组,每一个
结构描述一个段或其他系统准备执行该程序所需要的信息。一个 object file
段包含一个或多个部分(就象下面的“段目录”所描述的那样)。程序头仅仅对于
可执行或共享的 object file 有意义。一个文件使用 ELF 头的 e_phentsize
和 e_phnum 成员来指定其拥有的程序头大小。[参阅 第一部分中的 "ELF 头"]


+ Figure 2-1: Program Header

typedef struct {
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;

* p_type

该成员指出了这个数组的元素描述了什么类型的段,或怎样解释该数组元素的信息。
类型值和含义如下所述。

* p_offset

该成员给出了该段的驻留位置相对于文件开始处的偏移。

* p_vaddr

该成员给出了该段在内存中的首字节地址。

* p_paddr

在物理地址定位有关联的系统中,该成员是为该段的物理地址而保留的。由于
System V 忽略了应用程序的物理地址定位,该成员对于可执行文件和共享的
object 而言是未指定内容的。


* p_filesz

该成员给出了文件映像中该段的字节数;它可能是 0 。

* p_memsz

该成员给出了内存映像中该段的字节数;它可能是 0 。

* p_flags

该成员给出了和该段相关的标志。定义的标志值如下所述。

* p_align

就象在后面“载入程序”部分中所说的那样,可载入的进程段必须有合适的
p_vaddr 、 p_offset 值,取页面大小的模。该成员给出了该段在内存和
文件中排列值。 0 和 1 表示不需要排列。否则, p_align 必须为正的 2 的幂,
并且 p_vaddr 应当等于 p_offset 模 p_align 。


某些入口描述了进程段;其他的则提供补充信息并且无益于进程映像。已经
定义的入口可以以任何顺序出现,除非是下面明确声明的。后面是段类型值;
其他的值保留以便将来用于其他用途。


+ Figure 2-2: Segment Types, p_type

Name Value
==== =====
PT_NULL 0
PT_LOAD 1
PT_DYNAMIC 2
PT_INTERP 3
PT_NOTE 4
PT_SHLIB 5
PT_PHDR 6
PT_LOPROC 0x70000000
PT_HIPROC 0x7fffffff

* PT_NULL

该数组元素未使用;其他的成员值是未定义的。这种类型让程序头表忽略入口。

* PT_LOAD

该数组元素指定一个可载入的段,由 p_filesz 和 p_memsz 描述。文件中
字节被映射到内存段中。如果该段的内存大小( p_memsz )比文件大小( p_filesz )
要大,则多出的字节将象段初始化区域那样保持为 0 。文件的大小不会比内存大小值大。
在程序头表中,可载入段入口是以 p_vaddr 的升序排列的。

* PT_DYNAMIC

该数组元素指定动态链接信息。参阅 后面的“动态部分”以获得更多信息。

* PT_INTERP

该数组元素指定一个 null-terminated 路径名的位置和大小(作为解释程序)。
这种段类型仅仅对可执行文件有意义(尽管它可能发生在一个共享 object 上);
它在一个文件中只能出现一次。如果它出现,它必须先于任何一个可载入段入口。
参阅 后面的“程序解释器”(Program Interpreter)以获得更多的信息。


* PT_NOTE

该数组元素指定辅助信息的位置和大小。参阅 后面的“注意部分”以获得细节。

* PT_SHLIB

该段类型保留且具有未指定的语义。具有一个这种类型数组元素的程序并不
遵守 ABI 。

* PT_PHDR

该数组元素(如果出现),指定了程序头表本身的位置和大小(包括在文件中
和在该程序的内存映像中)。更进一步来说,它仅仅在该程序头表是程序内存映像
的一部分时才有效。如果它出现,它必须先于任何可载入段入口。参阅 后面的
“程序解释器”(Program Interpreter)以获得更多的信息。


* PT_LOPROC through PT_HIPROC

该范围中的值保留用于特定处理器的语义。

注意:除非在别处的特殊要求,所有的程序头的段类型是可选的。也就是说,
一个文件的程序头表也许仅仅包含和其内容相关的元素。



Base Address(基地址)

可执行和共享的 object file 有一个基地址,该基地址是与程序的 object file
在内存中映像相关的最低虚拟地址。基地址的用途之一是在动态链接过程中重定位
该程序的内存映像。


一个可执行的 object file 或 一个共享的 object file 的基地址是在
执行的时候从三个值计算而来的:内存载入地址、页面大小的最大值 和 程序可
载入段的最低虚拟地址。就象在“程序载入”中所描述的那样,程序头中的虚拟地址
也许和程序的内存映像中实际的虚拟地址并不相同。为了计算基地址,必须确定与
PT_LOAD 段 p_vaddr 的最小值相关的内存地址。获得基地址的方法是将内存
地址截去最大页面大小的最接近的整数倍。由于依赖载入内存中的文件类型,
该内存地址和 p_vaddr 值可能匹配也可能不匹配。


就象在第一部分中 "Section" 中描述的那样, .bss section 具有 SHT_NOBITS
的类型。尽管在文件中不占用空间,它在段的内存映像中起作用。通常,没有初始化
的数据驻留在段尾,因此使得在相关的程序头元素中的 p_memsz 比 p_filesz 大。


Note Section(注解部分)

有的时候供应商或系统设计者需要用特定的信息标记一个
object file 以便其他程序检查其兼容的一致性,等等此类。 SHT_NOTE
类型的 section 和 PT_NOTE 类型的程序头元素能够被用于此目的。 section
和程序头中的注解信息包含了任意数目的入口,每一个入口的格式都是对应于特定
处理器格式的 4-字节数组。下面的标签有助于解释注释信息的组织形式,但是这些
标签不是规格说明的一部分。


+ Figure 2-3: Note Information

namesz
descsz
type
name ...
desc ...

* namesz and name

名字中 namesz 的第一个字节包含了一个 null-terminated 字符
表达了该入口的拥有者或始发者。没有正式的机制来避免名字冲突。从
惯例来说,供应商使用他们自己的名称,比如 "XYZ Computer Company" ,
作为标志。如果没有提供名字, namesz 值为 0 。 如果有必要,确定
描述信息4-字节对齐。 这样的填充信息并不包含在namesz 中。


* descsz and desc

desc 中 descsz 的首字节包含了注解描述符。ABI 不会在一个描述符内容中
放入任何系统参数。如果没有描述符, descsz 将为 0 。 如果有必要,确定
描述信息4-字节对齐。 这样的填充信息并不包含在descsz中。


* type

该 word 给出了描述符的解释。每一个创造着(originator) 控制着自己的类型;
对于单单一个类型值的多种解释是可能存在的。因此,一个程序必须辨认出该名字
和其类型以便理解一个描述符。这个时候的类型必须是非负的。ABI 没有定义
描述符的含义。

为了举例说明,下面的解释段包含两个入口。

+ Figure 2-4: Example Note Segment

+0 +1 +2 +3
-------------------
namesz 7
descsz 0 No descriptor
type 1
name X Y Z spc
C o pad
namesz 7
descsz 8
type 3
name X Y Z spc
C o pad
desc word0
word1

注意:系统保留的注解信息没有名字 (namesz==0) ,有一个零长度的名字
(name[0]==‘‘) 现在还没有类型为其定义。所有其他的名字必须至少有
一个非空的字符。


注意:注解信息是可选的。注解信息的出现并不影响一个程序的 ABI 一致性,
前提是该信息不影响程序的执行行为。否则,该程序将不遵循 ABI 并将出现
未定义的行为。


===================== Program Loading(程序载入) =====================

当创建或增加一个进程映像的时候,系统在理论上将拷贝一个文件的段到一个虚拟
的内存段。系统什么时候实际地读文件依赖于程序的执行行为,系统载入等等。一个
进程仅仅在执行时需要引用逻辑页面的时候才需要一个物理页面,实际上进程通常会
留下许多未引用的页面。因此推迟物理上的读取常常可以避免这些情况,改良系统的
特性。为了在实践中达到这种效果,可执行的和共享的 object file 必须具有
合适于页面大小取模值的文件偏移和虚拟地址这样条件的段映像。

虚拟地址和文件偏移在 SYSTEM V 结构的段中是模 4KB(0x1000) 或大的 2 的幂。
由于 4KB 是最大的页面大小,因此无论物理页面大小是多少,文件必须去适合页面。


+ Figure 2-5: Executable File

File Offset File Virtual Address
=========== ==== ===============
0 ELF header
Program header table
Other information
0x100 Text segment 0x8048100
...
0x2be00 bytes 0x8073eff
0x2bf00 Data segment 0x8074f00
...
0x4e00 bytes 0x8079cff
0x30d00 Other information
...

+ Figure 2-6: Program Header Segments(程序头段)

Member Text Data
====== ==== ====
p_type PT_LOAD PT_LOAD
p_offset 0x100 0x2bf00
p_vaddr 0x8048100 0x8074f00
p_paddr unspecified unspecified
p_filesz 0x2be00 0x4e00
p_memsz 0x2be00 0x5e24
p_flags PF_R+PF_X PF_R+PF_W+PF_X
p_align 0x1000 0x1000

尽管示例中的文件偏移和虚拟地址在文本和数据两方面都适合模 4KB ,但是还有
4 个文件页面混合了代码和数据(依赖于页面大小和文件系统块的大小)。


* 第一个文本页面包含了 ELF 头、程序头以及其他信息。
* 最后的文本页包含了一个数据开始的拷贝。
* 第一个数据页面有一个文本结束的拷贝。
* 最后的数据页面也许会包含与正在运行的进程无关的文件信息。

理论上,系统强制内存中段的区别;段地址被调整为适应每一个逻辑页面在地址空间
中有一个简单的准许集合。在上面的示例中,包含文本结束和数据开始的文件区域将
被映射两次:在一个虚拟地址上为文本而另一个虚拟地址上为数据。


数据段的结束处需要对未初始化的数据进行特殊处理(系统定义的以 0 值开始)。
因此如果一个文件包含信息的最后一个数据页面不在逻辑内存页面中,则无关的
数据应当被置为 0 (这里不是指未知的可执行文件的内容)。在其他三个页面中
"Impurities" 理论上并不是进程映像的一部分;系统是否擦掉它们是未指定的。
下面程序的内存映像假设了 4KB 的页面。


+ Figure 2-7: Process Image Segments(进程映像段)

Virtual Address Contents Segment
=============== ======== =======
0x8048000 Header padding Text
0x100 bytes
0x8048100 Text segment
...
0x2be00 bytes
0x8073f00 Data padding
0x100 bytes
0x8074000 Text padding Data
0xf00 bytes
0x8074f00 Data segment
...
0x4e00 bytes
0x8079d00 Uninitialized data
0x1024 zero bytes
0x807ad24 Page padding
0x2dc zero bytes

可执行文件和共享文件在段载入方面有所不同。典型地,可执行文件段包含了
绝对代码。为了让进程正确执行,这些段必须驻留在建立可执行文件的虚拟地址
处。因此系统使用不变的 p_vaddr 作为虚拟地址。


另一方面,共享文件段包含与位置无关的代码。这让不同进程的相应段虚拟地址
各不相同,且不影响执行。虽然系统为各个进程选择虚拟地址,它还要维护各个
段的相对位置。因为位置无关的代码在段间使用相对定址,故而内存中的虚拟地址
的不同必须符合文件中虚拟地址的不同。下表给出了几个进程可能的共享对象虚拟
地址的分配,演示了不变的相对定位。该表同时演示了基地址的计算。


+ Figure 2-8: Example Shared Object Segment Addresses

Sourc Text Data Base Address
===== ==== ==== ============
File 0x200 0x2a400 0x0
Process 1 0x80000200 0x8002a400 0x80000000
Process 2 0x80081200 0x800ab400 0x80081000
Process 3 0x900c0200 0x900ea400 0x900c0000
Process 4 0x900c6200 0x900f0400 0x900c6000