ELF文件格式詳解

ARM的可執行文件的格式是ELF格式文件,下文對ELF格式做個詳細的介紹。

序言
1. OBJECT文件
   導言
   ELF頭(ELF Header)
   Sections
   String表(String Table)
   Symbol表(Symbol Table)
   重定位(Relocation)
2. 程序裝載與動態連接
   導言
   Program頭(Program Header)
   Program裝載(Program Loading)
   Dynamic連接(Dynamic Linking)
3. C LIBRARY
   C Library

   ________________________________________________________________


          導言

   ________________________________________________________________


  ELF: 可執行連接格式

可執行連接格式是UNIX系統實驗室(USL)作爲應用程序二進制接口
(Application Binary Interface(ABI)而開發和發佈的。工具接口標準委
員會(TIS)選擇了正在發展中的ELF標準作爲工作在32位INTEL體系上不同操
作系統之間可移植的二進制文件格式。

假定開發者定義了一個二進制接口集合,ELF標準用它來支持流線型的軟件
發展。 應該減少不同執行接口的數量。因此可以減少重新編程重新編譯的
代碼。


關於這片文檔

這篇文檔是爲那些想創建目標文件或者在不同的操作系統上執行文件的開發
着準備的。它分以下三個部分:

* 第一部分, “目標文件Object Files”描述了ELF目標文件格式三種主要
  的類型。
* 第二部分, “程序轉載和動態連接”描述了目標文件的信息和系統在創建
  運行時程序的行爲。
* 第三部分, “C 語言庫”列出了所有包含在libsys中的符號,標準的ANSI C
  和libc的運行程序,還有libc運行程序所需的全局的數據符號。

注意: 參考的X86體系已經被改成了Intel體系。

   ________________________________________________________________


   1. 目標文件(Object file)

   ________________________________________________________________


   ========================= 序言 =========================


第一部分描述了iABI的object文件的格式, 被稱爲ELF(Executable
and Linking Format). 在object文件中有三種主要的類型。

* 一個可重定位(relocatable)文件保存着代碼和適當的數據,用來和其他的
  object文件一起來創建一個可執行文件或者是一個共享文件。
* 一個可執行(executable)文件保存着一個用來執行的程序;該文件指出了
  exec(BA_OS)如何來創建程序進程映象。
* 一個共享object文件保存着代碼和合適的數據,用來被下面的兩個鏈接器
  鏈接。第一個是連接編輯器[請參看ld(SD_CMD)],可以和其他的可重定位和
  共享object文件來創建其他的object。第二個是動態鏈接器,聯合一個
  可執行文件和其他的共享object文件來創建一個進程映象。

一個object文件被彙編器和聯接器創建, 想要在處理機上直接運行的object
文件都是以二進制來存放的。那些需要抽象機制的程序,比如象shell腳本,
是不被接受的。

在介紹性的材料過後,第一部分主要圍繞着文件的格式和關於如何建立程序。
第二部分也描述了object文件的幾個組成部分,集中在執行程序所必須的信息上。


     文件格式

Object文件參與程序的聯接(創建一個程序)和程序的執行(運行一個程序)。
object 文件格式提供了一個方便有效的方法並行的視角看待文件的內容,
在他們的活動中,反映出不同的需要。例 1-1圖顯示了一個object文件的
組織圖。

+ 圖1-1: Object文件格式

  Linking 視角                      Execution 視角
  ============                      ==============
  ELF header                        ELF header
  Program header table (optional)   Program header table
  Section 1                         Segment 1
  ...                               Segment 2
  Section n                         ...
  Section header table              Section header table (optional)

一個ELF頭在文件的開始,保存了路線圖(road map),描述了該文件的組織情況。
sections保存着object 文件的信息,從連接角度看:包括指令,數據,
符號表,重定位信息等等。特別sections的描述會出項在以後的第一部分。
第二部分討論了段和從程序的執行角度看文件。

假如一個程序頭表(program header table)存在,那麼它告訴系統如何來創建一
個進程的內存映象。被用來建立進程映象(執行一個程序)的文件必須要有一個程
序頭表(program header table);可重定位文件不需要這個頭表。一個
section頭表(section header table)包含了描述文件sections的信息。每個
section在這個表中有一個入口;每個入口給出了該section的名字,大小,
等等信息。在聯接過程中的文件必須有一個section頭表;其他object文件可要
可不要這個section頭表。

注意: 雖然圖顯示出程序頭表立刻出現在一個ELF頭後,section頭表跟着其他
section部分出現,事實是的文件是可以不同的。此外,sections和段(segments)
沒有特別的順序。只有ELF頭(elf header)是在文件的固定位置。


數據表示
object文件格式支持8位、32位不同的處理器。不過,它試圖努力的在更大
或更小的體系上運行。因此,object文件描繪一些控制數據需要用與機器
無關的格式,使它儘可能的用一般的方法甄別object文件和描述他們的內容。
在object文件中剩餘的數據使用目標處理器的編碼方式,不管文件是在哪臺
機子上創建的。

+ 圖 1-2: 32-Bit Data Types

  Name           Size Alignment   Purpose
  ====           ==== =========   =======
  Elf32_Addr      4       4       Unsigned program address
  Elf32_Half      2       2       Unsigned medium integer
  Elf32_Off       4       4       Unsigned file offset
  Elf32_Sword     4       4       Signed large integer
  Elf32_Word      4       4       Unsigned large integer
  unsigned char   1       1       Unsigned small integer

所有的object文件格式定義的數據結構是自然大小(natural size),爲相關
的類型調整指針。如果需要,數據結構中明確的包含了確保4字節對齊的填
充字段。來使結構大小是4的倍數。數據從文件的開始也有適當的對齊。
例如,一個包含了Elf32_Addr成員的結構將會在文件內對齊到4字節的邊界上。

因爲移植性的原因,ELF不使用位字段。


   ========================== ELF Header ==========================


一些object文件的控制結構能夠增長的,所以ELF頭包含了他們目前的大小。
假如object文件格式改變,程序可能會碰到或大或小他們不希望的控制結構。
程序也有可能忽略額外(extra)的信息。
對待來歷不明(missing)的信息依靠上下文來解釋,假如擴展被定義,它們
將會被指定。

+ 圖 1-3: ELF Header

  #define EI_NIDENT       16

  typedef struct {
      unsigned char       e_ident[EI_NIDENT];
      Elf32_Half          e_type;
      Elf32_Half          e_machine;
      Elf32_Word          e_version;
      Elf32_Addr          e_entry;
      Elf32_Off           e_phoff;
      Elf32_Off           e_shoff;
      Elf32_Word          e_flags;
      Elf32_Half          e_ehsize;
      Elf32_Half          e_phentsize;
      Elf32_Half          e_phnum;
      Elf32_Half          e_shentsize;
      Elf32_Half          e_shnum;
      Elf32_Half          e_shstrndx;
  } Elf32_Ehdr;

* e_ident

  這個最初的字段標示了該文件爲一個object文件,提供了一個機器無關
  的數據,解釋文件的內容。在下面的ELF的鑑別(ELF Identification)
  部分有更詳細的信息。

* e_type

  該成員確定該object的類型。

               Name        Value  Meaning
               ====        =====  =======
               ET_NONE         0  No file type
               ET_REL          1  Relocatable file
               ET_EXEC         2  Executable file
               ET_DYN          3  Shared object file
       ET_CORE         4  Core file
       ET_LOPROC  0xff00  Processor-specific
       ET_HIPROC  0xffff  Processor-specific

  雖然CORE的文件內容未被指明,類型ET_CORE是保留的。
  值從 ET_LOPROC 到 ET_HIPROC(包括ET_HIPROC)是爲特殊的處理器保留的。
  如有需要,其他保留的變量將用在新的object文件類型上。

* e_machine

  該成員變量指出了運行該程序需要的體系結構。

                    Name      Value  Meaning
            ====      =====  =======
                    EM_NONE       0  No machine
    EM_M32        1  AT&T WE 32100
                    EM_SPARC      2  SPARC
                    EM_386        3  Intel 80386
                    EM_68K        4  Motorola 68000
                    EM_88K        5  Motorola 88000
                    EM_860        7  Intel 80860
                    EM_MIPS       8  MIPS RS3000

  如有需要,其他保留的值將用到新的機器類型上。特殊處理器名使用機器名來
  區別他們。例如,下面將要被提到的成員flags使用前綴EF_;在一臺EM_XYZ機器
  上,flag稱爲WIDGET,那麼就稱爲EF_XYZ_WIDGET。

* e_version

  這個成員確定object文件的版本。

                 Name         Value  Meaning
                 ====         =====  =======
                 EV_NONE          0  Invalid version
EV_CURRENT       1  Current version

  值1表示原來的文件格式;創建新版本就用>1的數。EV_CURRENT值(上面給
  出爲1)如果需要將指向當前的版本號。

* e_entry

  該成員是系統第一個傳輸控制的虛擬地址,在那啓動進程。假如文件沒有
  如何關聯的入口點,該成員就保持爲0。

* e_phoff

  該成員保持着程序頭表(program header table)在文件中的偏移量(以字節計數)。
  假如該文件沒有程序頭表的的話,該成員就保持爲0。

* e_shoff

  該成員保持着section頭表(section header table)在文件中的偏移量(以字節
  計數)。假如該文件沒有section頭表的的話,該成員就保持爲0。

* e_flags

  該成員保存着相關文件的特定處理器標誌。
  flag的名字來自於EF_<machine>_<flag>。看下機器信息“Machine Information”
  部分的flag的定義。

* e_ehsize

  該成員保存着ELF頭大小(以字節計數)。

* e_phentsize

  該成員保存着在文件的程序頭表(program header table)中一個入口的大小
  (以字節計數)。所有的入口都是同樣的大小。

* e_phnum

  該成員保存着在程序頭表中入口的個數。因此,e_phentsize和e_phnum
  的乘機就是表的大小(以字節計數).假如沒有程序頭表(program header table),
  e_phnum變量爲0。

* e_shentsize

  該成員保存着section頭的大小(以字節計數)。一個section頭是在section
  頭表(section header table)的一個入口;所有的入口都是同樣的大小。

* e_shnum

  該成員保存着在section header table中的入口數目。因此,e_shentsize和
  e_shnum的乘積就是section頭表的大小(以字節計數)。
  假如文件沒有section頭表,e_shnum值爲0。

* e_shstrndx

  該成員保存着跟section名字字符表相關入口的section頭表(section header 
  table)索引。假如文件中沒有section名字字符表,該變量值爲SHN_UNDEF。
  更詳細的信息 看下面“Sections”和字符串表(“String Table”) 。


  ELF 鑑別(Identification)

在上面提到的,ELF提供了一個object文件的框架結構來支持多種處理機,多
樣的數據編碼方式,多種機器類型。爲了支持這個object文件家族,最初的幾
個字節指定用來說明如何解釋該文件,獨立於處理器,與文件剩下的內容無關。

ELF頭(也就是object文件)最初的幾個字節與成員e_ident相一致。

+ 圖 1-4: e_ident[] Identification Indexes

  Name           Value  Purpose
  ====           =====  =======
  EI_MAG0      0  File identification
  EI_MAG1      1  File identification
  EI_MAG2      2  File identification
  EI_MAG3      3  File identification
  EI_CLASS      4  File class
  EI_DATA      5  Data encoding
  EI_VERSION      6  File version
  EI_PAD      7  Start of padding bytes
  EI_NIDENT     16  Size of e_ident[]

通過索引訪問字節,以下的變量被定義。

* EI_MAG0 to EI_MAG3

  文件的前4個字符保存着一個魔術數(magic number),用來確定該文件是否
  爲ELF的目標文件。

                  Name       Value  Position
                  ====       =====  ========
  ELFMAG0    0x7f   e_ident[EI_MAG0]
                  ELFMAG1    'E'    e_ident[EI_MAG1]
                  ELFMAG2    'L'    e_ident[EI_MAG2]
                  ELFMAG3    'F'    e_ident[EI_MAG3]

* EI_CLASS

  接下來的字節是e_ident[EI_CLASS],用來確定文件的類型或者說是能力。

                 Name           Value  Meaning
                 ====           =====  =======
                 ELFCLASSNONE       0  Invalid class
                 ELFCLASS32         1  32-bit objects
ELFCLASS64         2  64-bit objects

  文件格式被設計成在不同大小機器中可移植的,在小型機上的不用大型機上
  的尺寸。類型ELFCLASS32支持虛擬地址空間最大可達4GB的機器;使用上面
  定義過的基本類型。

  類型ELFCLASS64爲64位體系的機器保留。它的出現表明了object文件可能 
  改變,但是64位的格式還沒有被定義。如果需要,其他類型將被定義,會
  有不同的類型和不同大小的數據尺寸。

* EI_DATA

  字節e_ident[EI_DATA]指定了在object文件中特定處理器數據的編碼
  方式。當前定義了以下編碼方式。

             Name           Value  Meaning
             ====           =====  =======
     ELFDATANONE        0  Invalid data encoding
             ELFDATA2LSB        1  See below
             ELFDATA2MSB        2  See below

  更多的關於編碼的信息出現在下面。其他值保留,將被分配一個新的編碼
  方式,當然如果必要的話。

* EI_VERSION

  字節e_ident[EI_VERSION]表明了ELF頭的版本號。
  現在這個變量的是一定要設爲EV_CURRENT,作爲上面e_version的解釋。

* EI_PAD

  該變量標識了在e_ident中開始的未使用的字節。那些字節保留並被設置爲
  0;程序把它們從object 文件中讀出但應該忽略。假如當前未被使用的字節
  有了新的定義,EI_PAD變量將來會被改變。

一個文件的數據編碼指出瞭如何來解釋一個基本的object文件。在上述的
描述中,類型ELFCLAS32文件使用佔用1,2和4字節的目標文件。下面定義的
編碼方式,用下面的圖來描繪。數據出現在左上角。


ELFDATA2LSB編碼指定了2的補數值。
最小有意義的字節佔有最低的地址。

+ 圖1-5: Data Encoding ELFDATA2LSB

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

ELFDATA2LSB編碼指定了2的補數值。
最大有意義的字節佔有最低的地址。

+ 圖1-6: Data Encoding ELFDATA2MSB

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


機器信息

爲了確定文件,32位Intel體系結構的需要以下的變量。

+ 圖1-7: 32-bit Intel Architecture Identification, e_ident

  Position           Value
  ========           =====
  e_ident[EI_CLASS]  ELFCLASS32
  e_ident[EI_DATA]   ELFDATA2LSB

處理器確認ELF頭裏的e_machine成員,該成員必須爲EM_386。

ELF報頭裏的e_flags成員保存了和文件相關的位標記。32位Intel體系上未
定義該標記;所以這個成員應該爲0;


   =========================== Sections ===========================


一個object文件的section header table可以讓我們定位所有的sections。
section header table是個Elf32_Shdr結構的數組(下面描述)。一個section
報頭表(section header table)索引是這個數組的下標。ELF header table
的e_shoff成員給出了section報頭表的偏移量(從文件開始的計數)。e_shnum
告訴我們section報頭表中包含了多少個入口;e_shentsize 給出了每個
入口的大小。

一些section報頭表索引是保留的;那些特別的索引在一個object文件中
將沒有與之對應sections。

+ 圖1-8: Special Section Indexes

  Name             Value
  ====             =====
  SHN_UNDEF            0
  SHN_LORESERVE   0xff00
  SHN_LOPROC      0xff00
  SHN_HIPROC      0xff1f
  SHN_ABS         0xfff1
  SHN_COMMON      0xfff2
  SHN_HIRESERVE   0xffff

* SHN_UNDEF

  該值表明沒有定義,缺少,不相關的或者其他涉及到的無意義的section。
  例如,標號“defined”相對於section索引號SHN_UNDEF是一個沒有被
  定義的標號。

注意: 雖然索引0保留作爲未定義的值,section報頭表包含了一個索引0的
入口。因此,假如ELF報頭說一個文件的section報頭表中有6個section入口
的話,e_shnum的值應該是從0到5。最初的入口的內容以後在這個section中
被指定。

* SHN_LORESERVE

  該值指定保留的索引範圍的最小值。

* SHN_LOPROC through SHN_HIPROC

  該值包含了特定處理器語意的保留範圍。


* SHN_ABS

  該變量是相對於相應參考的絕對地址。
  例如,section號的標號是絕對地址,不被重定位影響。

* SHN_COMMON

  該section的標號是一個公共(common)的標號,就象FORTRAN COMMON
  或者不允許的C擴展變量。

* SHN_HIRESERVE

  該值指定保留的索引範圍的上限。系統保留的索引值是從SHN_LORESERVE
  到SHN_HIRESERVE;該變量不涉及到section報頭表(section header table)。
  因此,section報頭表不爲保留的索引值包含入口。

sections包含了在一個object文件中的所有信息,除了ELF報頭,程序報頭
表(program header table),和section報頭表(section header table)。
此外,object文件的sections滿足幾天條件:

* 每個在object文件中的section都有自己的一個section的報頭來描述它。
  section頭可能存在但section可以不存在。
* 每個section在文件中都佔有一個連續順序的空間(但可能是空的)。
* 文件中的Sections不可能重複。文件中沒有一個字節既在這個section中
  又在另外的一個section中。
* object文件可以有"非活動的"空間。不同的報頭和sections可以不覆蓋到
  object文件中的每個字節。"非活動"數據內容是未指定的。

一個section頭有如下的結構。

+ 圖1-9: Section Header

  typedef struct {
      Elf32_Word sh_name;
      Elf32_Word sh_type;
      Elf32_Word sh_flags;
      Elf32_Addr sh_addr;
      Elf32_Off sh_offset;
      Elf32_Word sh_size;
      Elf32_Word sh_link;
      Elf32_Word sh_info;
      Elf32_Word sh_addralign;
      Elf32_Word sh_entsize;
  } Elf32_Shdr;

* sh_name

  該成員指定了這個section的名字。它的值是section報頭字符表section的
  索引。[看以下的“String Table”], 以NULL空字符結束。

* sh_type

  該成員把sections按內容和意義分類。section的類型和他們的描述在下面。

* sh_flags

  sections支持位的標記,用來描述多個屬性。  
  Flag定義出現在下面。

* sh_addr

  假如該section將出現在進程的內存映象空間裏,該成員給出了一個該section
  在內存中的位置。否則,該變量爲0。

* sh_offset

  該成員變量給出了該section的字節偏移量(從文件開始計數)。SHT_NOBITS
  類型的section(下面討論)在文件中不佔空間,它的sh_offset成員定位在
  文件中的概念上的位置。

* sh_size
  該成員給你了section的字節大小。除非這個section的類型爲SHT_NOBITS,
  否則該section將在文件中將佔有sh_size個字節。SHT_NOBITS類型的section
  可能爲非0的大小,但是不佔文件空間。

* sh_link

  該成員保存了一個section報頭表的索引連接,它的解釋依靠該section的
  類型。以下一個表描述了這些值。

* sh_info

  該成員保存着額外的信息,它的解釋依靠該section的類型。以下一個表描
  述了這些值。

* sh_addralign

  一些sections有地址對齊的約束。例如,假如一個section保存着雙字,系統
  就必須確定整個section是否雙字對齊。所以sh_addr的值以sh_addralign的值
  作爲模,那麼一定爲0。當然的,僅僅0和正的2的次方是允許的。值0和1意味
  着該section沒有對齊要求。

* sh_entsize

  一些sections保存着一張固定大小入口的表,就象符號表。對於這樣一個
  section來說,該成員給出了每個入口的字節大小。如果該section沒有
  保存着一張固定大小入口的表,該成員就爲0。

section頭成員sh_type指出了section的語意。

+ 圖1-10: Section Types, sh_type

  Name               Value
  ====               =====
  SHT_NULL               0
  SHT_PROGBITS           1
  SHT_SYMTAB             2
  SHT_STRTAB          3
  SHT_RELA          4
  SHT_HASH          5
  SHT_DYNAMIC            6
  SHT_NOTE          7
  SHT_NOBITS          8
  SHT_REL          9
  SHT_SHLIB             10
  SHT_DYNSYM            11
  SHT_LOPROC    0x70000000
  SHT_HIPROC    0x7fffffff
  SHT_LOUSER    0x80000000
  SHT_HIUSER    0xffffffff

* SHT_NULL

  該值表明該section頭是無效的;它沒有相關的section。
  該section的其他成員的值都是未定義的。

* SHT_PROGBITS

  該section保存被程序定義了的一些信息,它的格式和意義取決於程序本身。

* SHT_SYMTAB and SHT_DYNSYM

  那些sections保存着一個符號表(symbol table)。一般情況下,一個
  object文件每個類型section僅有一個,但是,在將來,這個約束可能被放寬。
  典型的是,SHT_SYMTAB爲連接器提供標號,當然它也有可能被動態連接時使用。
  作爲一個完整的符號表,它可能包含了一些動態連接時不需要的標號。
  因此,一個object文件可能也包含了一個SHT_DYNSYM的section,它保存着
  一個動態連接時所需最小的標號集合來節省空間。
  看下面符號表“Symbol Table”的細節。

* SHT_STRTAB

  該section保存着一個字符串表。一個object文件可以包含多個字符串表的
  section。看下面字符串表“String Table”的細節。

* SHT_RELA

  該section保存着具有明確加數的重定位入口。就象object文件32位的
  Elf32_Rela類型。一個object文件可能有多個重定位的sections。具體細節
  看重定位``Relocation''部分。

* SHT_HASH

  該標號保存着一個標號的哈希(hash)表。所有的參與動態連接的object
  一定包含了一個標號哈希表(hash table)。當前的,一個object文件
  可能只有一個哈希表。詳細細節看第二部分的哈希表"Hash Table"。

* SHT_DYNAMIC

  該section保存着動態連接的信息。當前的,一個object可能只有一個動態
  的section,但是,將來這個限制可能被取消。詳細細節看第二部分的動態
  section(“Dynamic Section”)。

* SHT_NOTE

  該section保存着其他的一些標誌文件的信息。詳細細節看第二部分的“Note 
  Section” 。

* SHT_NOBITS

  該類型的section在文件中不佔空間,但是類似SHT_PROGBITS。儘管該section
  不包含字節,sh_offset成員包含了概念上的文件偏移量。

* SHT_REL

  該section保存着具有明確加數的重定位的入口。
  就象object文件32位類型Elf32_Rel類型。一個object文件可能有多個
  重定位的sections。具體細節看重定位``Relocation''部分。

* SHT_SHLIB

  該section類型保留但語意沒有指明。包含這個類型的section的程序
  是不符合ABI的。

* SHT_LOPROC through SHT_HIPROC

  在這範圍之間的值爲特定處理器語意保留的。

* SHT_LOUSER

  該變量爲應用程序保留的索引範圍的最小邊界。

* SHT_HIUSER

  該變量爲應用程序保留的索引範圍的最大邊界。在SHT_LOUSER和HIUSER的
  section類型可能被應用程序使用,這和當前或者將來系統定義的section
  類型是不矛盾的。

其他section類型的變量是保留的。前面提到過,索引0(SHN_UNDEF)的section
頭存在的,甚至索引標記的是未定義的section引用。這個入口保存着以下的
信息。

+ 圖1-11: Section Header Table Entry: Index 0

  Name            Value    Note
  ====            =====    ====
  sh_name           0      No name
  sh_type        SHT_NULL  Inactive
  sh_flags          0      No flags
  sh_addr           0      No address
  sh_offset         0      No file offset
  sh_size           0      No size
  sh_link SHN_UNDEF  No link information
  sh_info     0      No auxiliary information
  sh_addralign     0      No alignment
  sh_entsize        0      No entries

一個section報頭(section header table)的sh_flags成員保存着1位標記,
用來描述section的屬性。以下是定義的值;其他的值保留。

+ 圖1-12: Section Attribute Flags, sh_flags

  Name                Value
  ====                =====
  SHF_WRITE             0x1
  SHF_ALLOC             0x2
  SHF_EXECINSTR         0x4
  SHF_MASKPROC   0xf0000000

假如在sh_flags中的一個標記位被設置,該section相應的屬性也被打開。
否則,該屬性沒有被應用。未明的屬性就設爲0。

* SHF_WRITE

  該section包含了在進程執行過程中可被寫的數據。

* SHF_ALLOC

  該section在進程執行過程中佔據着內存。一些控制section不存在一個
  object文件的內存映象中;對於這些sections,這個屬性應該關掉。

* SHF_EXECINSTR

  該section包含了可執行的機器指令。

* SHF_MASKPROC

  所有的包括在這掩碼中的位爲特定處理語意保留的。

在section報頭中,兩個成員sh_link和sh_info的解釋依靠該section的類型。

+ 圖1-13: sh_link and sh_info Interpretation

  sh_type      sh_link                        sh_info
  =======      =======                        =======
  SHT_DYNAMIC  The section header index of    0
               the string table used by
               entries in the section.
  SHT_HASH     The section header index of    0
               the symbol table to which the
               hash table applies.
  SHT_REL,     The section header index of    The section header index of
  SHT_RELA     the associated symbol table.   the section to which the
                                              relocation applies.
  SHT_SYMTAB,  The section header index of    One greater than the symbol
  SHT_DYNSYM   the associated string table.   table index of the last local
                                              symbol (binding STB_LOCAL).
  other        SHN_UNDEF                      0


Special Sections   特殊的Sections

不同的sections保存着程序和控制信息。下面列表中的section被系統使用,
指示了類型和屬性。

+ 圖1-14: Special Sections

  Name         Type           Attributes
  ====         ====           ==========
  .bss         SHT_NOBITS     SHF_ALLOC+SHF_WRITE
  .comment     SHT_PROGBITS   none
  .data        SHT_PROGBITS   SHF_ALLOC+SHF_WRITE
  .data1       SHT_PROGBITS   SHF_ALLOC+SHF_WRITE
  .debug       SHT_PROGBITS   none
  .dynamic     SHT_DYNAMIC    see below
  .dynstr      SHT_STRTAB     SHF_ALLOC
  .dynsym      SHT_DYNSYM     SHF_ALLOC
  .fini        SHT_PROGBITS   SHF_ALLOC+SHF_EXECINSTR
  .got         SHT_PROGBITS   see below
  .hash        SHT_HASH       SHF_ALLOC
  .init        SHT_PROGBITS   SHF_ALLOC+SHF_EXECINSTR
  .interp      SHT_PROGBITS   see below
  .line        SHT_PROGBITS   none
  .note        SHT_NOTE       none
  .plt         SHT_PROGBITS   see below
  .rel<name>   SHT_REL        see below
  .rela<name>  SHT_RELA       see below
  .rodata      SHT_PROGBITS   SHF_ALLOC
  .rodata1     SHT_PROGBITS   SHF_ALLOC
  .shstrtab    SHT_STRTAB     none
  .strtab      SHT_STRTAB     see below
  .symtab      SHT_SYMTAB     see below
  .text        SHT_PROGBITS   SHF_ALLOC+SHF_EXECINSTR

* .bss

  該sectiopn保存着未初始化的數據,這些數據存在於程序內存映象中。
  通過定義,當程序開始運行,系統初始化那些數據爲0。該section不佔
  文件空間,正如它的section類型SHT_NOBITS指示的一樣。
  
* .comment

   該section保存着版本控制信息。

* .data and .data1
  這些sections保存着初始化了的數據,那些數據存在於程序內存映象中。

* .debug

  該section保存着爲標號調試的信息。該內容是未指明的。

* .dynamic

  該section保存着動態連接的信息。該section的屬性將包括SHF_ALLOC位。
  是否需要SHF_WRITE是跟處理器有關。第二部分有更詳細的信息。

* .dynstr

  該section保存着動態連接時需要的字符串,一般情況下,名字字符串關聯着
  符號表的入口。第二部分有更詳細的信息。

* .dynsym

  該section保存着動態符號表,如“Symbol Table”的描述。第二部分有更
  詳細的信息。
  
* .fini

  該section保存着可執行指令,它構成了進程的終止代碼。
  因此,當一個程序正常退出時,系統安排執行這個section的中的代碼。

* .got

  該section保存着全局的偏移量表。看第一部分的“Special Sections”和
  第二部分的“Global Offset Table”獲得更多的信息。

* .hash

  該section保存着一個標號的哈希表。看第二部分的“Hash Table”獲得更多
  的信息。

* .init

  該section保存着可執行指令,它構成了進程的初始化代碼。
  因此,當一個程序開始運行時,在main函數被調用之前(c語言稱爲main),
  系統安排執行這個section的中的代碼。

* .interp

  該section保存了程序的解釋程序(interpreter)的路徑。假如在這個section
  中有一個可裝載的段,那麼該section的屬性的SHF_ALLOC位將被設置;否則,
  該位不會被設置。看第二部分獲得更多的信息。

* .line

  該section包含編輯字符的行數信息,它描述源程序與機器代碼之間的對於
  關係。該section內容不明確的。

* .note

  該section保存一些信息,使用“Note Section”(在第二部分)中提到的格式。

* .plt

  該section保存着過程連接表(Procedure Linkage Table)。看第一部分的
  ``Special Sections''和第二部分的“Procedure Linkage Table”。

* .rel<name> and .rela<name>

  這些section保存着重定位的信息,看下面的``Relocation''描述。
  假如文件包含了一個可裝載的段,並且這個段是重定位的,那麼該section的
  屬性將設置SHF_ALLOC位;否則該位被關閉。按照慣例,<name>由重定位適用
  的section來提供。因此,一個重定位的section適用的是.text,那麼該名字
  就爲.rel.text或者是.rela.text。

* .rodata and .rodata1

  這些section保存着只讀數據,在進程映象中構造不可寫的段。看第二部分的
  ``Program Header''獲得更多的資料。

* .shstrtab

  該section保存着section名稱。

* .strtab

  該section保存着字符串,一般地,描述名字的字符串和一個標號的入口相關
  聯。假如文件有一個可裝載的段,並且該段包括了符號字符串表,那麼section
  的SHF_ALLOC屬性將被設置;否則不設置。

* .symtab

  該section保存着一個符號表,正如在這個section裏``Symbol Table''的
  描述。假如文件有一個可裝載的段,並且該段包含了符號表,那麼section
  的SHF_ALLOC屬性將被設置;否則不設置。

* .text

  該section保存着程序的``text''或者說是可執行指令。


前綴是點(.)的section名是系統保留的,儘管應用程序可以用那些保留的
section名。應用程序可以使用不帶前綴的名字以避免和系統的sections
衝突。object文件格式可以讓一個定義的section部分不出現在上面的列
表中。一個object文件可以有多個同樣名字的section。

爲處理器體系保留的section名的形成是通過置換成一個體系名的縮寫。
該名字應該取自體系名,e_machine使用的就是。例如,.Foo.psect就是
在FOO體系上定義的名字。

現存的擴展名是歷史遺留下來的。

       Pre-existing Extensions
       =======================
.sdata     .tdesc
.sbss      .lit4
.lit8      .reginfo
.gptab     .liblist
.conflict


   =================== 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    /0   n    a    m    e    .    /0   V    a    r     
         10    i    a    b    l    e    /0   a    b    l    e
         20    /0   /0   x    x    /0


+ 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    /0   pad
  namesz           7
  descsz           8
    type           3
    name   X    Y    Z    spc
           C    o    /0   pad
    desc         word0
                 word1

注意:系統保留的註解信息沒有名字 (namesz==0) ,有一個零長度的名字
(name[0]=='/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


   ==================== Dynamic Linking (動態鏈接) =====================


一個可執行文件可能有一個 PT_INTERP 程序頭元素。在 exec(BA_OS) 的
過程中,系統從 PT_INTERP 段中取回一個路徑名並由解釋器文件的段創建初始的
進程映像。也就是說,系統爲解釋器“編寫”了一個內存映像,而不是使用原始
的可執行文件的段映像。此時該解釋器就負責接收系統來的控制並且爲應用程序
提供一個環境變量。


解釋器使用兩種方法中的一種來接收系統來的控制。首先,它會接收一個文件描述符
來讀取該可執行文件,定位於開頭。它可以使用這個文件描述符來讀取 並且(或者)
映射該可執行文件的段到內存中。其次,依賴於該可執行文件的格式,系統會載入
這個可執行文件到內存中而不是給該解釋器一個文件描述符。伴隨着可能的文件描述符
異常的情況,解釋器的初始進程聲明應匹配該可執行文件應當收到的內容。解釋器本身
並不需要第二個解釋器。一個解釋器可能是一個共享對象也可能是一個可執行文件。


* 一個共享對象(通常的情況)在被載入的時候是位置無關的,各個進程可能不同;
  系統在 mmap(KE_OS) 使用的動態段域爲它創建段和相關的服務。因而,一個
  共享對象的解釋器將不會和原始的可執行文件的原始段地址相沖突。


* 一個可執行文件被載入到固定地址;系統使用程序頭表中的虛擬地址爲其創建段。
  因而,一個可執行文件解釋器的虛擬地址可能和第一個可執行文件相沖突;這種
  衝突由解釋器來解決。

Dynamic Linker(動態鏈接器)

當使用動態鏈接方式建立一個可執行文件時,鏈接器把一個 PT_INTERP 類型
的元素加到可執行文件中,告訴系統把動態鏈接器做爲該程序的解釋器。

注意:由系統提供的動態鏈接器是和特定處理器相關的。

Exec(BA_OS) 和動態鏈接器合作爲程序創建進程,必須有如下的動作:

* 將可執行文件的內存段加入進程映像中;
* 將共享對象的內存段加入進程映像中;
* 爲可執行文件和它的共享對象進行重定位;
* 如果有一個用於讀取可執行文件的文件描述符傳遞給了動態鏈接器,那麼關閉它。
* 向程序傳遞控制,就象該程序已經直接從 exec(BA_OS) 接收控制一樣。

鏈接器同時也爲動態鏈接器構建各種可執行文件和共享對象文件的相關數據。就象
在上面“程序頭”中說的那樣,這些數據駐留在可載入段中,使得它們在執行過程
中有效。(再一次的,要記住精確的段內容是處理器相關的。可以參閱相應處理器
的補充說明來獲得詳盡的信息。)


* 一個具有 SHT_DYNAMIC 類型的 .dynamic section 包含各種數據。駐留在
  section 開頭的結構包含了其他動態鏈接信息的地址。

* SHT_HASH 類型的 .hash section 包含了一個 symbol hash table.

* SHT_PROGBITS 類型的 .got 和 .plt section 包含了兩個分離的 table:
  全局偏移表和過程鏈接表。 下面的 section 演示了動態鏈接器使用和改變
  這些表來爲 object file 創建內存映像。


由於每一個遵循 ABI 的程序從一個共享對象庫中輸入基本的系統服務,因此動態
鏈接器分享於每一個遵循 ABI 的程序的執行過程中。


就象在處理器補充說明的“程序載入”所解釋的那樣,共享對象也許會佔用與記錄在
文件的程序頭表中的地址不同的虛擬內存地址。動態鏈接器重定位內存映像,在應用程序
獲得控制之前更新絕對地址。儘管在庫被載入到由程序頭表指定的地址的情況下絕對地址
應當是正確的,通常的情況卻不是這樣。


如果進程環境 [see exec(BA_OS)] 包含了一個非零的 LD_BIND_NOW 變量,
動態鏈接器將在控制傳遞到程序之前進行所有的重定位。舉例而言,所有下面的
環境入口將指定這種行爲。


* LD_BIND_NOW=1
* LD_BIND_NOW=on
* LD_BIND_NOW=off

其他情況下, LD_BIND_NOW 或者不在環境中或者爲空值。動態鏈接器可以不急於
處理過程鏈接表入口,因而避免了對沒有調用的函數的符號解析和重定位。參閱
"Procedure Linkage Table"獲取更多的信息。

   Dynamic Section(動態section)


假如一個object文件參與動態的連接,它的程序頭表將有一個類型爲PT_DYNAMIC
的元素。該“段”包含了.dynamic section。一個_DYNAMIC特別的符號,表明了
該section包含了以下結構的一個數組。

+ Figure 2-9: Dynamic Structure

  typedef struct {
      Elf32_Sword d_tag;
      union {
          Elf32_Sword d_val;
          Elf32_Addr d_ptr;
      } d_un;
  } Elf32_Dyn;

  extern Elf32_Dyn _DYNAMIC[];

對每一個有該類型的object,d_tag控制着d_un的解釋。

* d_val

  那些Elf32_Word object描繪了具有不同解釋的整形變量。

* d_ptr

  那些Elf32_Word object描繪了程序的虛擬地址。就象以前提到的,在執行時,
  文件的虛擬地址可能和內存虛擬地址不匹配。當解釋包含在動態結構中的地址
  時是基於原始文件的值和內存的基地址。爲了一致性,文件不包含在
  重定位入口來糾正在動態結構中的地址。


以下的表格總結了對可執行和共享object文件需要的tag。假如tag被標爲
mandatory,ABI-conforming文件的動態連接數組必須有一個那樣的入口。
同樣的,“optional”意味着一個可能出現tag的入口,但是不是必須的。

+ Figure 2-10: Dynamic Array Tags, d_tag

  Name               Value  d_un         Executable   Shared Object
  ====               =====  ====         ==========   =============
  DT_NULL                0  ignored mandatory    mandatory
  DT_NEEDED 1  d_val optional     optional
  DT_PLTRELSZ 2  d_val optional     optional
  DT_PLTGOT 3  d_ptr optional     optional
  DT_HASH 4  d_ptr mandatory    mandatory
  DT_STRTAB 5  d_ptr mandatory    mandatory
  DT_SYMTAB 6  d_ptr mandatory    mandatory
  DT_RELA 7  d_ptr mandatory    optional
  DT_RELASZ 8  d_val mandatory    optional
  DT_RELAENT 9  d_val mandatory    optional
  DT_STRSZ 10  d_val mandatory    mandatory
  DT_SYMENT 11  d_val mandatory    mandatory
  DT_INIT 12  d_ptr optional     optional
  DT_FINI 13  d_ptr optional     optional
  DT_SONAME 14  d_val ignored      optional
  DT_RPATH 15  d_val optional     ignored
  DT_SYMBOLIC 16  ignored ignored      optional
  DT_REL 17  d_ptr mandatory    optional
  DT_RELSZ 18  d_val mandatory    optional
  DT_RELENT 19  d_val mandatory    optional
  DT_PLTREL 20  d_val optional     optional
  DT_DEBUG 21  d_ptr optional     ignored
  DT_TEXTREL 22  ignored optional     optional
  DT_JMPREL 23  d_ptr optional     optional
  DT_LOPROC     0x70000000  unspecified  unspecified  unspecified
  DT_HIPROC     0x7fffffff  unspecified  unspecified  unspecified

* DT_NULL

   一個DT_NULL標記的入口表示了_DYNAMIC數組的結束。

* DT_NEEDED

  這個元素保存着以NULL結尾的字符串表的偏移量,那些字符串是所需庫的名字。
  該偏移量是以DT_STRTAB 爲入口的表的索引。看“Shared Object Dependencies”
  關於那些名字的更多信息。動態數組可能包含了多個這個類型的入口。那些
  入口的相關順序是重要的,雖然它們跟其他入口的關係是不重要的。

* DT_PLTRELSZ

  該元素保存着跟PLT關聯的重定位入口的總共字節大小。假如一個入口類型
  DT_JMPREL存在,那麼DT_PLTRELSZ也必須存在。

* DT_PLTGOT

  該元素保存着跟PLT關聯的地址和(或者)是GOT。具體細節看處理器補充
  (processor supplement)部分。

* DT_HASH

  該元素保存着符號哈希表的地址,在“哈希表”有描述。該哈希表指向
  被DT_SYMTAB元素引用的符號表。

* DT_STRTAB

  該元素保存着字符串表地址,在第一部分有描述,包括了符號名,庫名,
  和一些其他的在該表中的字符串。

* DT_SYMTAB

  該元素保存着符號表的地址,在第一部分有描述,對32-bit類型的文件來
  說,關聯着一個Elf32_Sym入口。

* DT_RELA

  該元素保存着重定位表的地址,在第一部分有描述。在表中的入口有明確的
  加數,就象32-bit類型文件的Elf32_Rela。一個object文件可能好多個重定位
  section。當爲一個可執行和共享文件建立重定位表的時候,連接編輯器連接
  那些section到一個單一的表。儘管在object文件中那些section是保持獨立的。
  動態連接器只看成是一個簡單的表。當動態連接器爲一個可執行文件創建一個
  進程映象或者是加一個共享object到進程映象中,它讀重定位表和執行相關的
  動作。假如該元素存在,動態結構必須也要有DT_RELASZ和DT_RELAENT元素。
  當文件的重定位是mandatory,DT_RELA 或者 DT_REL可能出現(同時出現是
  允許的,但是不必要的)。


* DT_RELASZ

  該元素保存着DT_RELA重定位表總的字節大小。

* DT_RELAENT

  該元素保存着DT_RELA重定位入口的字節大小。

* DT_STRSZ

  該元素保存着字符串表的字節大小。

* DT_SYMENT

  該元素保存着符號表入口的字節大小。

* DT_INIT

  該元素保存着初始化函數的地址,在下面“初始化和終止函數”中討論。

* DT_FINI

  該元素保存着終止函數的地址,在下面“初始化和終止函數”中討論。

* DT_SONAME

  該元素保存着以NULL結尾的字符串的字符串表偏移量,那些名字是共享
  object的名字。偏移量是在DT_STRTAB入口記錄的表的索引。關於那些名字看
  Shared Object Dependencies 部分獲得更多的信息。

* DT_RPATH

  該元素保存着以NULL結尾的搜索庫的搜索目錄字符串的字符串表偏移量。
  在共享object依賴關係(Shared Object Dependencies)中有討論

* DT_SYMBOLIC

  在共享object庫中出現的該元素爲在庫中的引用改變動態連接器符號解析的算法。
  替代在可執行文件中的符號搜索,動態連接器從它自己的共享object開始。假如
  一個共享的object提供引用參考失敗,那麼動態連接器再照常的搜索可執行文件
  和其他的共享object。

* DT_REL

  該元素相似於DT_RELA,除了它的表有潛在的加數,正如32-bit文件類型的
  Elf32_Rel一樣。假如這個元素存在,它的動態結構必須也同時要有DT_RELSZ
  和DT_RELENT的元素。

* DT_RELSZ

  該元素保存着DT_REL重定位表的總字節大小。

* DT_RELENT

  該元素保存着DT_RELENT重定爲入口的字節大小。  

* DT_PLTREL

  該成員指明瞭PLT指向的重定位入口的類型。適當地, d_val成員保存着 
  DT_REL或DT_RELA。在一個PLT中的所有重定位必須使用相同的轉換。

* DT_DEBUG

  該成員被調試使用。它的內容沒有被ABI指定;訪問該入口的程序不是
  ABI-conforming的。

* DT_TEXTREL

  如在程序頭表中段許可所指出的那樣,這個成員的缺乏代表沒有重置入
  口會引起非寫段的修改。假如該成員存在,一個或多個重定位入口可能
  請求修改一個非寫段,並且動態連接器能因此有準備。

* DT_JMPREL

  假如存在,它的入口d_ptr成員保存着重定位入口(該入口單獨關聯着
  PLT)的地址。假如lazy方式打開,那麼分離它們的重定位入口讓動態連接
  器在進程初始化時忽略它們。假如該入口存在,相關聯的類型入口DT_PLTRELSZ
  和DT_PLTREL一定要存在。

* DT_LOPROC through DT_HIPROC

  在該範圍內的變量爲特殊的處理器語義保留。除了在數組末尾的DT_NULL元素,
  和DT_NEEDED元素相關的次序,入口可能出現在任何次序中。在表中不出
  現的Tag值是保留的。


  Shared Object Dependencies(共享Object的依賴關係)

當連接器處理一個文檔庫時,它取出庫中成員並且把它們拷貝到一個輸出的
object文件中。當運行時沒有包括一個動態連接器的時候,那些靜態的連接服
務是可用的。共享object也提供服務,動態連接器必須把正確的共享object
文件連接到要實行的進程映象中。因此,可執行文件和共享的object文件之間
存在着明確的依賴性。

當動態連接器爲一個object文件創建內存段時,依賴關係(在動態結構的
DT_NEEDED入口中記錄)表明需要哪些object來爲程序提供服務。通過
重複的連接參考的共享object和他們的依賴關係,動態連接器可以建造一個
完全的進程映象。當解決一個符號引用的時候,動態連接器以寬度優先搜索
(breadth-first)來檢查符號表,換句話說,它先查看自己的可實行程序
中的符號表,然後是頂端DT_NEEDED入口(按順序)的符號表,再接下來是
第二級的DT_NEEDED入口,依次類推。共享object文件必須對進程是可讀的;
其他權限是不需要的。

注意:即使當一個共享object被引用多次(在依賴列關係表中),動態連接器
只把它連接到進程中一次。

在依賴關係列表中的名字既被DT_SONAME字符串拷貝,又被建立object文件
時的路徑名拷貝。例如,動態連接器建立一個可執行文件(使用帶DT_SONAME
入口的lib1共享文件)和一個路徑名爲/usr/lib/lib2的共享object庫,
那麼可執行文件將在它自己的依賴關係列表中包含lib1和/usr/bin/lib2。

假如一個共享object名字有一個或更多的反斜槓字符(/)在這名字的如何地方,
例如上面的/usr/lib/lib2文件或目錄,動態連接器把那個字符串自己做爲路徑名。
假如名字沒有反斜槓字符(/),例如上面的lib1,三種方法指定共享文件的
搜索路徑,如下:

* 第一,動態數組標記DT_RPATH保存着目錄列表的字符串(用冒號(:)分隔)。
  例如,字符串/home/dir/lib:/home/dir2/lib:告訴動態連接器先搜索
  /home/dir/lib,再搜索/home/dir2/lib,再是當前目錄。

* 第二,在進程環境中(see exec(BA_OS)),有一個變量稱爲LD_LIBRARY_PATH
  可以保存象上面一樣的目錄列表(隨意跟一個分號(;)和其他目錄列表)。
  以下變量等於前面的例子:
    LD_LIBRARY_PATH=/home/dir/lib:/home/dir2/lib:
    LD_LIBRARY_PATH=/home/dir/lib;/home/dir2/lib:
    LD_LIBRARY_PATH=/home/dir/lib:/home/dir2/lib:;
  所以的LD_LIBRARY_PATH目錄在DT_RPATH指向的目錄之後被搜索。儘管一些
  程序(例如連接編輯器)不同的處理分號前和分號後的目錄,但是動態連接
  不會。不過,動態連接器接受分號符號,具體語意在如上面描述。

* 最後,如果上面的兩個目錄查找想要得到的庫失敗,那麼動態連接器搜索
  /usr/lib.

注意:出於安全考慮,動態連接器忽略set-user和set-group的程序的
LD_LIBRARY_PATH所指定的搜索目錄。但它會搜索DT_RPATH指明的目錄和
/usr/lib。


Global Offset Table(GOT全局偏移量表)

一般情況下,位置無關的代碼不包含絕對的虛擬地址。全局偏移量表在私有數據
中保存着絕對地址,所以應該使地址可用的,而不是和位置無關性和程序代碼段
共享能力妥協。一個程序引用它的GOT(全局偏移量表)來使用位置無關的地址並且
提取絕對的變量,所以重定位位置無關的參考到絕對的位置。

初始時,GOT(全局偏移量表)保存着它重定位入口所需要的信息 [看第一部分的
“Relocation”]。在系統爲一個可裝載的object文件創建內存段以後,動態
連接器處理重定位入口,那些類型爲R_386_GLOB_DAT的指明瞭GOT(全局偏移量表)。
動態連接器決定了相關的標號變量,計算他們的絕對地址,並且設置適當的內存
表入口到正確的變量。雖然當連接編輯器建造object文件的時候,絕對地址
是不知道,連接器知道所以內存段的地址並且能夠因此計算出包含在那裏的
標號地址。

假如程序需要直接訪問符號的絕對地址,那麼這個符號將有一個GOT(全局偏移量表)
入口。因爲可執行文件和共享文件有獨立的GOT(全局偏移量表),一個符號地址
可能出現在不同的幾個表中。在交給進程映象的代碼控制權以前,動態連接器處
理所有的重定位的GOT(全局偏移量表),所以在執行時,確認絕對地址是可用的。

該表的入口0是爲保存動態結構地址保留的(參考_DYNAMIC標號)。這允許
象動態連接程序那樣來找出他們自己的動態結構(還沒有處理他們的重
定向入口)。這些對於動態連接器是重要的,因爲它必要初始化自己而不
能依賴於其他程序來重定位他們的內存映象。在32位Interl系統結構中,在
GOT中的人口1和2也是保留的,具體看以下的過程連接表(Procedure Linkage
Table)。

系統可以爲在不同的程序中相同的共享object選擇不同的內存段;它甚至可以
爲相同的程序不同的進程選擇不同的庫地址。雖然如此,一旦進程映象被建立
以後,內存段不改變地址。只要一個進程存在,它的內存段駐留在固定的虛擬
地址。

GOT表的格式和解釋是處理器相關的。在32位Intel體系結構下,標號
_GLOBAL_OFFSET_TABLE_可能被用來訪問該表。

+ Figure 2-11: Global Offset Table

  extern Elf32_Addr _GLOBAL_OFFSET_TABLE_[];

標號_GLOBAL_OFFSET_TABLE_可能駐留在.got section的中間,允許負的和非負
的下標索引這個數組。


       Procedure Linkage Table(PLT過程連接表)

就象GOT重定位把位置無關的地址計算成絕對地址一樣,PLT過程連接表重定位
位置無關的函數調用到絕對的地址。從一個可執行或者共享的object文件到另外的,
連接編輯器不解析執行的傳輸(例如函數的調用)。因此,連接編輯器安排程序
的傳遞控制到PLT中的入口。在SYSTEM V體系下,PLT存在共享文本中,但是它們
使用的地址是在私有的GOT中。符號連接器決定了目標的絕對地址並且修改GOT的
內存映象。因此,在沒有危及到位置無關、程序文本的共享能力的情況下。動態
連接器能重定位人口。


+ Figure 2-12: Absolute Procedure Linkage Table {*}
       絕對的過程連接表

  .PLT0:pushl   got_plus_4
        jmp     *got_plus_8
        nop; nop
        nop; nop
  .PLT1:jmp     *name1_in_GOT
        pushl   $offset
        jmp     .PLT0@PC
  .PLT2:jmp     *name2_in_GOT
        pushl   $offset
        jmp     .PLT0@PC
        ...

+ Figure 2-13: Position-Independent Procedure Linkage Table
       位置無關(或者說位置獨立)的過程連接表
  .PLT0:pushl   4(%ebx)
        jmp     *8(%ebx)
        nop; nop
        nop; nop
  .PLT1:jmp     *name1@GOT(%ebx)
        pushl   $offset
        jmp     .PLT0@PC
  .PLT2:jmp     *name2@GOT(%ebx)
        pushl   $offset
        jmp     .PLT0@PC
        ...

注意:如圖所示,PLT的指令使用了不同的操作數地址方式,對絕對代碼和
對位置無關的代碼。但是,他們的界面對於動態連接器是相同的。

以下的步驟,動態連接器和程序協作(cooperate)通過PLT和GOT來解析符號
引用。

1. 當第一次創建程序的內存映象時,動態連接器爲在GOT中特別的變量設置
   第二次和第三次的入口。下面關於那些變量有更多的解釋。

2. 假如PLT是位置無關的,那麼GOT的地址一定是保留在%ebx中的。每個在進程
   映象中共享的object文件有它自己的PLT,並且僅僅在同一個object文件中,
   控制傳輸到PLT入口。從而,要調用的函數有責任在調用PLT入口前,設置PLT
   地址到寄存器中。

3. 舉例說明,假如程序調用函數name1,它的傳輸控制到標號.PLT1.

4. 第一個指令跳到在GOT入口的name1地址。初始話時,GOT保存着緊跟着的push1
   指令的地址,而不是真實的name1的地址。

5. 因此,程序在堆棧中壓入(push)一個重定位的偏移量。重定位的偏移量是
   一個32位,非負的字節偏移量(從定位表算起)。指派的重定位入口將是
   一個R_386_JMP_SLOT類型,它的偏移量指明瞭GOT入口(在前面的jmp指令中
   被使用)。該重定位入口也包含一個符號表的索引,因此告訴動態連接器
   哪個符號要被引用,在這裏是name1。

6. 在壓入(push)一個重定位的偏移量後,程序跳到.PLT0,在PLT中的第一個入口。
   push1指令在堆棧中放置第二個GOT入口(got_plus_4 or 4(%ebx))的值,
   因此,給動態連接器一個word的鑑別信息。然後程序跳到第三個GOT入口
   (got_plus_8 or 8(%ebx)),它傳輸控制到動態連接器。

7. 當動態連接器接到控制權,它展開堆棧,查看指派的重定位入口,尋找符號的
   值,在GOT入口中存儲真實的name1地址,然後傳輸控制想要目的地。

8. PLT入口的併發執行將直接傳輸控制到name1,而不用第二次調用動態連接器
   了。所以,在.PLT1中的jmp指令將轉到name1,代替“falling through”
   轉到pushl指令。

LD_BIND_NOW環境變量能改變動態連接器的行爲。假如這個變量爲非空,動態
連接器在傳輸控制到程序前計算PLT入口。換句話說,動態連接器處理重定位
類型爲R_386_JMP_SLOT的入口在進程初始化時。否則,動態連接器計算PLT入口
懶惰的,推遲到符號解析和重定位直到一個表入口的第一次執行。


注意:一般來說,以懶惰(Lazy)方式綁定是對全應用程序執行的改進。
因爲不使用的符號就不會招致動態連接器做無用功。然而,對一些應用程序,
兩種情況使用懶惰(Lazy)方式是不受歡迎的。

第一 初始的引用一個共享object函數比後來的調用要花的時間長,因爲動
     態連接器截取調用來解析符號。一些應用程序是不能容忍這樣的。
第二 假如這個錯誤發生並且動態連接器不能解析該符號,動態連接器將終止
     程序。在懶惰(Lazy)方式下,這可能發生在任意的時候。一再的,一
     些應用程序是不能容忍這樣的。通過關掉懶惰(Lazy)方式,在應用程
     序接到控制前,當在處理初始話時發生錯誤,動態連接器強迫程序,使
     之失敗。

      Hash Table(哈希表)

Elf32_Word object的哈希表支持符號表的訪問。
標號出現在下面幫助解釋哈希表的組織,但是它們不是規範的一部分。

+ Figure 2-14: Symbol Hash Table

  nbucket
  nchain
  bucket[0]
  ...
  bucket[nbucket - 1]
  chain[0]
  ...
  chain[nchain - 1]


bucket數組包含了nbucket入口,並且chain數組包含了nchain個入口;索引從0開始。
bucket和chain保存着符號表的索引。Chain表入口類似於符號表。符號表入口的
數目應該等於nchain;所以符號表的索引也選擇chain表的入口。
一個哈希函數(如下的)接受一個符號名並且返回一個可以被計算機使用的bucket索引
的值。因此,假如一個哈希函數返回一些名字的值爲X,那麼bucket[x%nbucket]
將給出一個索引y(既是符號表和chain表的索引)。假如符號表入口不是期望的,
chain[y]給出下一個符號表的入口(使用相同的哈希變量)。可以沿着chain
鏈直到選擇到了期望名字的符號表入口或者是碰到了STN_UNDEF的入口。

+ Figure 2-15: Hashing Function

  unsigned long
  elf_hash(const unsigned char *name)
  {
      unsigned long       h = 0, g;
  
      while (*name) {
          h = (h << 4) + *name++;
          if (g = h & 0xf0000000)
              h ^= g >> 24;
          h &= ~g;
      }
      return h;
  }


       Initialization and Termination Functions
          初始化和終止函數


在動態連接妻建立進程映象和執行重定位以後,每一個共享object得到適當
的機會來執行一些初始話代碼。初始化函數不按特別的順序被調用,但是
所有的共享object初始化發生在執行程序獲得控制之前。

類似地,共享的object可能包含終止函數,它們在進程本身開始它的終止之後
被執行(以atexit(BA_OS)的機制)。

共享object通過設置在動態結構中的DT_INIT和DT_FINI入口來指派它們的初始化
和終止函數,如上動態section(Dynamic Section)部分描述。典型的,那些函數
代碼存在.init和.fini section中,第一部分的“section”已經提到過。

注意:儘管atexit(BA_OS)的終止處理一般可可正常完成,但是不保證在死進程上
被執行。特別的,假如_exit被調用(看exit(BA_OS))或者假如進程死掉,那麼
進程是不執行終止處理的。因爲它收到一個信號,該信號可捕獲或忽略。

   ________________________________________________________________


     3. C LIBRARY

   ________________________________________________________________


   ========================== C Library ===========================

C庫,libc,包含了所有的符號(包含在libsys),另外,包含在在下面兩個
表中列出的運行函數。第一個表中的運行函數是ANSI C標準的。

+ Figure 3-1: libc Contents, Names without Synonyms

  abort        fputc        isprint      putc         strncmp
  abs        fputs        ispunct      putchar      strncpy
  asctime      fread        isspace      puts         strpbrk
  atof        freopen      isupper      qsort        strrchr
  atoi        frexp        isxdigit     raise        strspn
  atol        fscanf       labs         rand         strstr
  bsearch      fseek        ldexp        rewind       strtod
  clearerr     fsetpos      ldiv         scanf        strtok
  clock        ftell        localtime    setbuf       strtol
  ctime        fwrite       longjmp      setjmp       strtoul
  difftime     getc         mblen        setvbuf      tmpfile
  div        getchar      mbstowcs     sprintf      tmpnam
  fclose       getenv       mbtowc       srand        tolower
  feof        gets         memchr       sscanf       toupper
  ferror       gmtime       memcmp       strcat       ungetc
  fflush       isalnum      memcpy       strchr       vfprintf
  fgetc        isalpha      memmove      strcmp       vprintf
  fgetpos      iscntrl      memset       strcpy       vsprintf
  fgets        isdigit      mktime       strcspn      wcstombs
  fopen        isgraph      perror       strlen       wctomb
  fprintf      islower      printf       strncat   

再加上, libc 保存着以下的服務。

+ Figure 3-2: libc Contents, Names with Synonyms

  __assert     getdate      lockf **     sleep        tell ** 
  cfgetispeed  getopt       lsearch      strdup       tempnam
  cfgetospeed  getpass      memccpy      swab         tfind
  cfsetispeed  getsubopt    mkfifo       tcdrain      toascii
  cfsetospeed  getw         mktemp       tcflow       _tolower
  ctermid      hcreate      monitor      tcflush      tsearch
  cuserid      hdestroy     nftw         tcgetattr    _toupper
  dup2        hsearch      nl_langinfo  tcgetpgrp    twalk
  fdopen       isascii      pclose       tcgetsid     tzset
  __filbuf     isatty       popen        tcsendbreak  _xftw
  fileno       isnan        putenv       tcsetattr    
  __flsbuf     isnand **    putw         tcsetpgrp    
  fmtmsg **    lfind        setlabel     tdelete     

  ** = Function is at Level 2 in the SVID Issue 3 and therefore at
       Level 2 in the ABI.

包括上面同義(Synonyms)表列出的標號,對於<name> 入口已經存在的_<name> 
形式(帶一個下劃線,上面沒有列出來)優先權高於它們的名字。所以,例如,
libc同時包含了getopt和_getopt。

在常規的上列中,其他地方以下沒有被定義。

int __filbuf(FILE *f);
This function returns the next input character for f, filling
its buffer as appropriate. It returns EOF if an error occurs.

int __flsbuf(int x, FILE *f);
This function flushes the output characters for f as if
putc(x, f) had been called and then appends the value of x to
the resulting output stream. It returns EOF if an error occurs
and x otherwise.

int _xftw(int, char *, int (*)(char *, struct stat *, int), int);
Calls to the ftw(BA_LIB) function are mapped to this function
when applications are compiled. This function is identical to
ftw(BA_LIB), except that _xftw() takes an interposed first
argument, which must have the value 2.


要了解更多的關於SVID,ANSI C,POSIX的知識,可看該章節其他的庫section部分。
該節“System Data Interfaces”後有更多的描述。

Global Data Symbols
                    全局數據符號


libc庫需要一些外部的全局數據符號(爲了它自己的常規工作而定義的)。
所有向libsys庫請求的數據符號一定要讓libc提供,就象下面表中的數據符號。

正式定義的數據object被他們的符號描述,看System V接口定義,第三版本
或者第6章節的數據定義(Data Definitions)section(在適當的處理器
補充到System V ABI)。

在下面表中的入口有<name>-_<name>的形式。一對符號都代表了一些數據。
下劃線的synonyms假設滿足ANSI C標準。


+ Figure 3-3: libc Contents, Global External Data Symbols

  getdate_err optarg
  _getdate_err opterr
  __iob optind
optopt

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/yyt7529/archive/2009/06/06/4245280.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章