原創 C++應用程序在Windows下的編譯、連接:第二部分COFF/PE文件結構

2.1概述

在windows操做系統下,可執行文件的存儲格式是PE格式;在Linux操做系統下,可執行文件的存儲格式的WLF格式。它們都是COFF格式文件的變種,都是從COFF格式的文件演化而來的。程序員

在windows平臺下,目標文件(.obj),靜態庫文件(.lib)使用COFF格式存儲;而可執行文件(.exe),動態連接庫文件(.dll)使用PE格式存儲。靜態庫文件其實就是一堆目標文件的集合。windows

在「WinNT.h」頭文件中定義了COFF格式文件,以及PE格式文件的數據結構。這些定義是一系列的結構體,枚舉,以及#define宏定義。在ImageHlp.dll中定義了編輯和讀取PE文件內容的Win32API。數組

在64位Windows操做系統下,PE格式文件被作了少部分修改。沒有新的字段定義被加入,而且去除了一些字段的定義,同時將字段的寬度從32位擴充到64位。64位windows操做系統下的PE格式文件被命名爲:PE32+。cookie

2.2COFF文件的結構

2.2.1整體結構圖

COFF文件的整體結構以下圖所示:數據結構

從文件內容上來看,COFF文件由二進制數據組成。這些二進制數據從文件的零位置開始,依次存儲,直到文件末尾。從數據結構的角度來看,這些二進制數據又分別屬於不一樣的結構體或者結構體數組。這些結構體被定義在「WinNT.h」頭文件中。架構

在COFF文件中,這些結構體或結構體數組分別表示不一樣的含義,記錄着COFF文件中的不一樣內容。從文件的頂端開始,依次存儲了文件頭,可選頭,段表,段數據,重定位表,行號表,符號表,以及字符串表的信息。這些結構體數據之間存在關聯關係。好比:文件頭信息中存儲了符號表的開始位置,以及段表中數組元素的個數;在段表中存儲了各個段的位置,重定位表的位置,行號表的位置;重定位表中的項會關聯到符號表中的某個符號;而符號表中某個符號的名稱可能會存儲在字符串表中。app

使用dumpbin工具能夠將目標文件的內容導出,具體的命令格式以下:函數

Dumpbin /all DemoMath.obj >DemoMath.txt工具

    在上面的命令中,將目標文件「DemoMath.obj」的全部內容導出到文本文件「DemoMath.txt」中。命令選項「/all」表示導出全部內容,命令選項「>」表示將導出的內容存儲到文件中。佈局

2.2.2文件頭

文件頭以一個結構體的形式存儲在COFF文件的開始位置,佔20個字節的大小。每個COFF格式的二進制文件都必須包含一個文件頭,它用來保存COFF文件的基本信息,如:文件標識,各個表的位置等。

使用dumpbin工具導出「DemoMath.obj」目標文件的內容後,文件頭部分的信息內容以下:

Dump of file demomath.obj

File Type: COFF OBJECT                                //表示該文件格式爲COFF格式

FILE HEADER VALUES                                 //如下依次是文件頭中各項的值

             14C machine (x86)                      //魔數

              20 number of sections                  //段的數量

        519AFB7E time date stamp Tue May 21 12:43:42 2013   //創建時間

            288A file pointer to symbol table           //符號表的位置

              83 number of symbols                 //符號的數量

               0 size of optional header              //可選頭的大小

               0 characteristics                     //文件屬性標記。零表示有重定位信息,有符號表,有行號,不可執行,具體解釋可見「Characteristics字段的取值狀況表」的描述。

在「WinNT.h」頭文件中,文件頭被定義爲IMAGE_FILE_HEADER類型,具體的定義形式以下:

typedef struct _IMAGE_FILE_HEADER

{

    WORD    Machine;

    WORD    NumberOfSections;

    DWORD   TimeDateStamp;

    DWORD   PointerToSymbolTable;

    DWORD   NumberOfSymbols;

    WORD    SizeOfOptionalHeader;

    WORD    Characteristics;

} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

#define IMAGE_SIZEOF_FILE_HEADER             20        //文件頭的大小

   在文件頭中,各個字段的詳細解釋以下表所示:

字段名稱

類型

描述

Machine

Word

魔法數字,在i386平臺中,該值爲0x014c。這是一個平臺的標識。

NumberOfSections

Word

段的數量。段表的大小由它肯定。

段表的大小 =  Sizeof(IMAGE_SECTION_HEADER)

 * NumberOfSections

TimeDateStamp

Dword

該字段是一個時間戳,用來記錄COFF文件建立的時間。當COFF文件做爲一個可執行文件的時候,該值被用來看成加密用的比對標識。

PointerToSymbolTable

Dword

符號表在文件中的偏移量,該偏移量從文件的零位置爲基準。使用該值能夠肯定符號表的第一個字節的位置。

NumberOfSymbols

Dword

符號表中符號的個數。

SizeOfOptionalHeader

Word

可選頭的大小。一般爲零。經過此值可定位段表。

Characteristics

Word

文件的屬性標記,它標記了文件的類型,以及文件中所保存的數據的信息。該標記的詳細說明見下表。

   Characteristics字段的取值狀況以下表所示:

名稱

說明

F_RELFLG

0x0001

無重定位信息標記。值爲1表示無重定位信息。在目標文件中,該值爲1,可執行文件中,該值爲零。

F_EXEC

0x0002

可執行標記。值爲2表示該文件中全部符號都已經被解析完畢,能夠被執行。在目標文件中,該值爲零。

F_LNNO

0x0004

無行號標記。值爲4表示該文件中沒有行號表

F_LSYMS

0x0008

無符號標記。值爲8表示該文件中沒有符號表

F_AR32WR

0x0100

該標記指出文件是 32 位的 Little-Endian COFF 文件。

 

2.2.3可選頭

   該數據結構爲可選數據,在目標文件中不存在此數據結構。只有當COFF文件做爲可執行文件存在的時候,該數據結構纔有意義。

2.2.4段表

段表是各個段的目錄,用於檢索各個段的信息。它以結構體數組的形式存儲在可選頭或者文件頭的後面。在段表中,每一項的大小是36個字節,數組元素的個數記錄在文件頭的「NumberOfSections」字段中。

段的劃分是基於各組數據的共同屬性,而不是邏輯概念。每段是一塊擁有共同屬性的數據,好比代碼/數據、讀/寫等。若是COFF文件中的數據/代碼擁有相同屬性,它們就能被納入同一段中。

在段表中記錄了各個段在段數據區域中的位置(相對文件首位置的絕對偏移),以及各段重定位信息在重定位表中的位置。

 在COFF格式的目標文件中,每個函數造成一個.text段,所以會有多個名爲.text的段。在使用工具dumpbin導出「DemoMath.obj」目標文件的內容後,除了列出.text段的同時,也將與該段相對應的重定位段一塊兒列出。具體內容以下:

SECTION HEADER #9

   .text name                     //段表信息的內容

       0 physical address

       0 virtual address

      2A size of raw data

    16B0 file pointer to raw data (000016B0 to 000016D9)

    16DA file pointer to relocation table

       0 file pointer to line numbers

       1 number of relocations

       0 number of line numbers

60501020 flags

         Code

         COMDAT; sym= "int __cdecl GetOperTimes(void)" (?GetOperTimes@@YAHXZ)

         16 byte align

         Execute Read

RAW DATA #9                      //段的二進制數據

  00000000: 55 8B EC 81 EC C0 00 00 00 53 56 57 8D BD 40 FF  U.ì.ìà...SVW.?@?

  00000010: FF FF B9 30 00 00 00 B8 CC CC CC CC F3 AB A1 00  ??10...?ììììó??.

  00000020: 00 00 00 5F 5E 5B 8B E5 5D C3                    ..._^[.?]?

 

RELOCATIONS #9                   //段的重定位信息

                                                Symbol    Symbol

 Offset    Type              Applied To         Index     Name

 --------  ----------------  -----------------  --------  ------

 0000001F  DIR32                      00000000         F  ?nOperTimes@@3HA (int nOperTimes)

在「WinNT.h」頭文件中,文件頭被定義爲IMAGE_SECTION_HEADER類型,具體的定義形式以下:

#define IMAGE_SIZEOF_SHORT_NAME              8

typedef struct _IMAGE_SECTION_HEADER

{

    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];

union

{

            DWORD   PhysicalAddress;

            DWORD   VirtualSize;

    } Misc;

    DWORD   VirtualAddress;

    DWORD   SizeOfRawData;

    DWORD   PointerToRawData;

    DWORD   PointerToRelocations;

    DWORD   PointerToLinenumbers;

    WORD    NumberOfRelocations;

    WORD    NumberOfLinenumbers;

    DWORD   Characteristics;

} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

#define IMAGE_SIZEOF_SECTION_HEADER          40

在段表中,各個字段的詳細解釋以下表:

字段名稱

類型

描述

Name

BYTE

節的ASCII名稱。節名不保證必定是以NULL結尾的。若是你指定了長於8個字符的節名,連接器會把它截短爲8個字符。在OBJ文件中存在一個機制容許更長的節名。節名一般以一個句點開始,但這並非必須的。節名中有一個「$」時連接器會對之進行特殊處理。前面帶有「$」的相同名字的節將會被合併。合併的順序是按照「$」後面字符的字母順序進行合併的

PhysicalAddress

DWORD

 

VirtualSize

DWORD

指出實際被使用的節的大小。這個域的值能夠大於或小於SizeOfRawData域的值。若是VirtualSize的值大,SizeOfRawData就是可執行文件中已初始化數據的大小,剩下的字節用0填充。在OBJ文件中這個域被設爲0。

VirtualAddress

DWORD

在可執行文件中,是節被加載到內存中後的RVA。在OBJ文件中應該被設爲0

SizeOfRawData

DWORD

在可執行文件或OBJ文件中該節所佔用的字節大小。對於可執行文件,這個值必須是PE頭中給出的文件對齊值的倍數。若是是0,則說明這個節中的數據是未初始的。

PointerToRawData

DWORD

節在磁盤文件中的偏移。對於可執行文件,這個值必須是PE頭部給出的文件對齊值的倍數。

PointerToRelocations

DWORD

節的重定位數據的文件偏移。只用於OBJ文件,在可執行文件中被設爲0。對於OBJ文件,若是這個域的值不爲0的話,它就指向一個IMAGE_RELOCATION結構數組。

PointerToLinenumbers

DWORD

節的COFF樣式行號的文件偏移。若是非0,則指向一個IMAGE_LINENUMBER結構數組。只在COFF行號被生成時使用。

NumberOfRelocations

WORD

PointerToRelocations 指向的重定位的數目。在可執行文件中應該是0。

NumberOfLinenumbers

WORD

NumberOfRelocations 域指向的行號的數目。只在COFF行號被生成時使用。

Characteristics

WORD

被或到一塊兒的一些標記,用來表示節的屬性。這些標記中不少均可以經過連接器選項/SECTION來設置。

Characteristics字段的取值狀況以下表所示:

描述

IMAGE_SCN_CNT_CODE

節中包含代碼。

IMAGE_SCN_MEM_EXECUTE

節是可執行的。

IMAGE_SCN_CNT_INITIALIZED_DATA

節中包含已初始化數據。

IMAGE_SCN_CNT_UNINITIALIZED_DATA

節中包含未初始化數據。

IMAGE_SCN_MEM_DISCARDABLE

節可被丟棄。用於保存連接器使用的一些信息,包括.debug$節。

IMAGE_SCN_MEM_NOT_PAGED

節不可被頁交換,所以它老是存在於物理內存中。常常用於內核模式的驅動程序。

IMAGE_SCN_MEM_SHARED

包含節的數據的物理內存頁在全部用到這個可執行體的進程之間共享。所以,每一個進程看到這個節中的數據值都是徹底同樣的。這對一個進程的全部實例之間共享全局變量頗有用。要使一個節共享,可以使用/section:name,S 連接器選項。

IMAGE_SCN_MEM_READ

節是可讀的。幾乎老是被設置。

IMAGE_SCN_MEM_WRITE

節是可寫的。

IMAGE_SCN_LNK_INFO

節中包含連接器使用的信息。只在OBJ文件中存在。

IMAGE_SCN_LNK_REMOVE

節中的數據不會成爲映像的一部分。只出如今OBJ文件中。

IMAGE_SCN_LNK_COMDAT

節中的內容是公共數據(comdat)。公共數據是指可被定義在多個OBJ文件中的數據。連接器將選擇一個包含到可執行文件中。Comdat 對於支持C++模板函數和在函數級別上的連接是相當重要的。Comdat節只出如今OBJ文件中。

IMAGE_SCN_ALIGN_XBYTES

在最終的可執行文件中這個節中數據的對齊大小。它可有許多取值(_4BYTES,_8BYTES,_16BYTES等)。若是沒有被指定,缺省是16字節。這些標記只在OBJ文件中被設置。

 

2.2.5重定位表

在編譯階段,將某些源文件編譯成目標文件的時候,在目標文件中,某些被調用函數或者數據的位置是沒法肯定的。這時候,編譯器將這些被調用的函數或者數據的地址設定爲一個默認的假值。在連接階段,當可以肯定這些被調用函數或數據的地址的時候,再用真實的地址來替換這些假值。咱們將這個過程叫作重定位。

使用工具dumpbin將目標文件main.obj的內容輸出爲彙編格式的文件後,能夠觀察到這些假值的設定狀況,以及須要重定位的位置。命令格式以下:

Dumpbin /disasm main.obj >mainasm.txt

輸入的彙編文件的一部份內容以下:

//objMath.SubData(nGlobalData,3);如下是執行該函數調用的彙編代碼

00000080: 8B F4              mov         esi,esp

  00000082: 83 EC 08           sub         esp,8

  00000085: DD 05 00 00 00 00   fld         qword ptr [__real@4008000000000000]

  0000008B: DD 1C 24           fstp        qword ptr [esp]

  0000008E: DB 05 00 00 00 00    fild        dword ptr [?nGlobalData@@3HA]

  00000094: 83 EC 08            sub         esp,8

  00000097: DD 1C 24           fstp        qword ptr [esp]

  0000009A: 8D 4D EC           lea         ecx,[ebp-14h]

  0000009D: FF 15 00 00 00 00    call dword ptr [__imp_?SubData@DemoMath@@QAEXNN@Z]

  000000A3: 3B F4              cmp         esi,esp

  000000A5: E8 00 00 00 00       call        __RTC_CheckEsp

在上面的代碼中,地址0x0000008E處引用了全局變量nGlobalData,指令格式爲:DB 05 00 00 00 00。DB 05爲fild彙編指令的二進制碼,然後邊四個字節的零(紅色表示)是nGlobalData的地址,這個地址是個臨時的假值。

在當前目標文件中,若是被調用的函數或數據位於另一個目標文件中,那麼在連接的時候須要對被調用的函數或數據執行重定位;若是被調用的函數或數據是全局函數或者全局變量,那麼在連接的時候,須要對該全局函數或全局變量執行重定位。在示例代碼中,全局變量:nGlobalData, nOperTimes,全局函數:GetOperTimes()在連接的時候須要執行重定位。

重定位表只存在於目標文件中,它存儲了各個段的重定位信息。在每一個段的段表中,記錄了該段重定位信息在重定位表中的位置(相對於文件首位置的偏移)。

使用工具dumpbin將目標文件的內容導出後,若是某個代碼段存在重定位信息(該代碼段引用過了全局符號或者外部符號),那麼在該代碼段的後面就會列出該代碼段的重定位信息。該重定位信息是重定位表中的一個片斷。示例以下:

SECTION HEADER #16           //代碼段的信息摘要。Subdata函數所在的代碼段

   .text name

       0 physical address

       0 virtual address

      5C size of raw data

    2088 file pointer to raw data (00002088 to 000020E3)

    20E4 file pointer to relocation table

       0 file pointer to line numbers

       4 number of relocations

       0 number of line numbers

60501020 flags

         Code

         COMDAT; sym= "public: void __thiscall DemoMath::SubData(double,double)"

         16 byte align

         Execute Read

 

RAW DATA #16                  //代碼段的二進制數據內容,紅色字體表示須要重定位的位置。被//VirtualAddress字段指定。

  00000000: 55 8B EC 81 EC CC 00 00 00 53 56 57 51 8D BD 34  U.ì.ìì...SVWQ.?4

  00000010: FF FF FF B9 33 00 00 00 B8 CC CC CC CC F3 AB 59  ???13...?ììììó?Y

  00000020: 89 4D F8 A1 00 00 00 00 83 C0 01 A3 00 00 00 00  .M??.....à.£....

  00000030: DD 45 08 DC 65 10 83 EC 08 DD 1C 24 8B 45 F8 8B  YE.üe..ì.Y.$.E?.

  00000040: 08 E8 00 00 00 00 5F 5E 5B 81 C4 CC 00 00 00 3B  .è...._^[.?ì...;

  00000050: EC E8 00 00 00 00 8B E5 5D C2 10 00              ìè.....?]?..

RELOCATIONS #16               //代碼段的重定位信息。

                                                   Symbol     Symbol

 Offset     Type                    Applied To        Index      Name

 --------  ----------------  -----------------  --------  ------

 00000024  DIR32                     00000000         F  ?nOperTimes@@3HA (int nOperTimes)

 0000002C  DIR32                     00000000         F  ?nOperTimes@@3HA (int nOperTimes)

 00000042  REL32                     00000000        59  ?OutPutInfo@DemoOutPut@@QAEXN@Z

 00000052  REL32                     00000000        3F  __RTC_CheckEsp

 

這是類DemoMath的成員函數:SubData()所在的代碼段的重定位信息,在該重定位信息中,須要重定位的符號是:全局變量nOperTimes和外部函數OutPutInfo()。在上面代碼中,紅色字體的部分被重定位表中的字段:VirtualAddress指向,標記了須要重定位的位置。

在「WinNT.h」頭文件中,文件頭被定義爲IMAGE_RELOCATION類型,具體的定義形式以下:

typedef struct _IMAGE_RELOCATION

{

union

{

        DWORD   VirtualAddress;

        DWORD   RelocCount;      // Set to the real count when IMAGE_SCN_LNK_NRELOC_OVFL is set

    };

    DWORD   SymbolTableIndex;

    WORD    Type;

} IMAGE_RELOCATION;

typedef IMAGE_RELOCATION UNALIGNED *PIMAGE_RELOCATION;

在重定位表中,各個字段的詳細解釋以下表:

字段名稱

類型

描述

VirtualAddress

DWORD

該字段指向代碼段中的一個地址。該地址所包含的數據是須要重定位的符號的地址。這部分數據將要被重定位修正。該字段指向了這個數據的第一個字節。上面的示例中,紅色字體標記的部分被該字段指向。

RelocCount

DWORD

 

SymbolTableIndex

DWORD

須要重定位的符號在符號表中的索引,該值爲符號表數組的索引。經過該值能夠檢索符號在符號表中的信息。

Type

WORD

通常分兩種類型。DIR32表示32位絕對地址;REL32表示32位相對地址。在執行重定位的時候,對於絕對地址類型,將被替換爲符號的絕對地址;而對於相對地址類型,將被替換爲符號的相對地址,即:符號相對於被修正位置的地址差。

 

2.2.6行號表

行號表描述了二進制代碼與源代碼行號之間的關係,調試階段使用。在「WinNT.h」頭文件中,文件頭被定義爲IMAGE_RELOCATION類型,具體的定義形式以下:

typedef struct _IMAGE_LINENUMBER

{

union

{

        DWORD   SymbolTableIndex;    // Symbol table index of function name if Linenumber is 0.

        DWORD   VirtualAddress;      // Virtual address of line number.

    } Type;

    WORD    Linenumber;             // Line number.

} IMAGE_LINENUMBER;

typedef IMAGE_LINENUMBER UNALIGNED *PIMAGE_LINENUMBER;

在行號表中,各個字段的詳細解釋以下表:

字段名稱

類型

描述

SymbolTableIndex

DWORD  

符號在符號表中的索引

VirtualAddress

DWORD  

符號的地址值

Linenumber

WORD   

行號

 

2.2.7符號表

在編譯階段的詞法分析過程當中,編譯器掃描整個C++源代碼,將源代碼中的函數名稱,變量名稱收集起來,而後寫入符號表中。在符號表中主要包含以下內容:函數名稱,變量名稱,段的名稱,以及一些常量信息,這些名稱被統稱爲符號。

符號表中的信息被用於靜態連接階段,用來進行被引用的函數或變量的地址重定位。每個目標文件中都會包含一個符號表。在該符號表中的符號,要麼是在該目標文件中定義的函數名稱或變量名稱;要麼是被該目標文件引用的,定義於其餘目標文件中的函數名稱或變量名稱。在靜態連接階段,多個目標文件進行連接的時候,存在於這些目標文件中的符號表會被合併到一塊兒,造成一個全局符號表。在C++源代碼中出現的全部符號都應該能在全局符號表中被查找到。

將符號表中的符號進行分類,具體的分類狀況以下:

  • 定義在本目標文件中的全局符號,該符號可能會被其餘目標文件引用;
  • 在本目標文件中引用的全局符號,該符號定義在其餘目標文件中,該符號被稱爲外部符號;
  • 段的名稱,由編譯器加入到符號表中。該符號的值就是段的起始地址;
  • 局部符號。在編譯單元內部可見,連接的時候忽略。

    在執行連接的時候,只關注前兩種類型的符號。

若是符號的名稱小於8個字節,那麼將該符號的名稱直接存儲在符號表中;若是符號的名稱大於8個字節,那麼將符號的名稱存儲在字符串表中,原來符號表中存儲符號名稱的地方存儲了一個地址偏移量,該地址偏移量指向了字符串表中符號名稱的位置。

根據符號存儲類型以及符號在段中位置的不一樣,符號的值有不一樣的解釋。

使用工具dumpbin將DemoMath.obj的內容導出之後,其符號表中的一部分的內容描述以下:

000 00847809 ABS    notype       Static       | @comp.id    //絕對值常量

001 00000001 ABS    notype       Static       | @feat.00     //絕對值常量

002 00000000 SECT1  notype       Static       | .drectve      //段名稱

    //段名稱符號下面緊跟段的信息。每行佔用一個符號索引的位置,因此符號索引不是連續的。

    Section length  201, #relocs    0, #linenums    0, checksum        0 

Relocation CRC 00000000

005 00000000 SECT4  notype       External     | ?nOperTimes@@3HA (int nOperTimes)  //變量

006 00000000 SECT1A  notype ()    External     | ?DivData@DemoMath@@QAEXNN@Z  //函數

007 00000000 UNDEF  notype ()    External     | ?OutPutInfo@DemoOutPut@@QAEXPBD@Z //外部函數

 

   在上面的示例中,從左到右各字段的含義依次是:符號結構體所在數組的索引,符號大小,符號在段中位置,符號類型,符號的存儲類型,符號名稱。在該符號表的內容中,列出了全局變量名:nOperTimes,類成員函數名:DivData,被引用的外部函數名:OutPutInfo。段的名稱也被做爲一個符號寫入到符號表中,上面示例中的「.drectve」即爲一個段的名稱。

  在「WinNT.h」頭文件中,文件頭被定義爲IMAGE_SYMBOL類型,具體的定義形式以下:

typedef struct _IMAGE_SYMBOL

{

union

{

        BYTE    ShortName[8];

        Struct

 {               

            DWORD   Short;     // if 0, use LongName

            DWORD   Long;      // offset into string table

        } Name;

        DWORD   LongName[2];    // PBYTE [2]

    } N;

    DWORD   Value;

    SHORT   SectionNumber;

    WORD    Type;

    BYTE    StorageClass;

    BYTE    NumberOfAuxSymbols;

} IMAGE_SYMBOL;

typedef IMAGE_SYMBOL UNALIGNED *PIMAGE_SYMBOL;

在符號表中,各個字段的詳細解釋以下表:

字段名稱

類型

描述

ShortName

BYTE

小於8個字節的符號名稱存儲於此。

Short

DWORD

0表示符號名稱位於字符串表中。

Long

DWORD

符號名稱在字符串表中的偏移量。

LongName

DWORD

 

Value

DWORD

符號的值。對於變量或函數來講,符號值就是它們的地址。根據符號存儲類型的不一樣,符號值有不一樣的解釋。

SectionNumber

SHORT

符號所在的段落。ABS表示符號是個絕對值,是個常量;UNDEF表示符號是未定義的,即該符號的定義在其餘段中;SECT1表示該符號位於編號爲1的段中。

Type

WORD

符號的類型。Notype表示變量;notype()表示函數。

StorageClass

BYTE

符號的存儲類型。Static表示局部變量,文件內部可見;external表示全局變量,全局範圍內可見。

NumberOfAuxSymbols

BYTE

附加記錄的數量。

 

符號的值的具體含義須要根據符號所在的段落(SectionNumber)以及符號的存儲類型(StorageClass)來肯定,這三者之間的具體關係以下表所示:

StorageClass

SectionNumber

Value

Static

SECTn(n爲1,2,3…)

若是值不爲零,表示符號在段內偏移。

SECTn(n爲1,2,3…)

若是值爲零,表示這個符號爲段名。

ABS

常量的值。

External

UNDEF

符號爲全局變量/函數,符號定義在外部文件中,值待定。

SECTn(n爲1,2,3…)

符號爲全局變量/函數,符號定義在當前文件中,值表示符號在段內偏移。

 

2.2.8字符串表

字符串表用來保存長度大於8個字節的符號名稱。字符串表的前4個字節表示字符串的長度,後面的緊跟字符串的內容,它以字節爲單位,以’\0’做爲字符串的結束符。這裏的字符串長度不只僅是字符串自身的長度(字符串內容+’\0’),還包括前面4個字節的該數據自身的長度。

2.2.9各數據結構之間的關係

在COFF文件所包含的數據結構中,各個數據結構之間的關係以下圖所示:

重定位表和符號表之間經過符號表的索引進行關聯;在文件頭中保存了可選頭的大小和段表所包含項目的數量,經過計算能夠肯定段表的起始位置和結束位置。段表起始位置=文件頭大小+可選頭大小;其餘關係經過相對文件首位置的偏移表示。

2.3Lib文件的結構

2.3.1整體結構圖

靜態連接庫就是一組目標文件的集合,當執行靜態連接的時候,被選定的目標文件的內容就會被合併到相關的Pe文件中去。靜態連接庫的整體結構以下圖所示:

靜態連接庫以簽名開始,簽名的數據內容爲「(!<arch>\n」,長8個字節。緊跟在簽名後面的是三個特別成員,分別是第一連接器節,第二連接器節,以及長名稱節。在這三個特別成員以後,直到文件結束,存儲的都是目標文件節的內容。

第一連接器節,第二連接器節,長名稱節,以及目標文件節的數據結構都是由頭數據+節數據這樣的數據結構組成的。

第一連接器節。在靜態連接庫中必須存在該節,它包含了靜態連接庫中全部的符號名以及這些符號在靜態連接庫文件中的偏移;

第二連接器節。在靜態連接庫中該節可選,它包含了與第一連接器節相同的內容,可是它的內容是有序的,經過它查找符號要比在第一連接器節中查找的快;

長名稱節。在靜態連接庫中該節可選,它是一個字符串表,用於存儲名稱大於16個字節的目標文件的名稱。在目標文件節中,若是目標文件的名稱小於16個字節,那麼這個名稱會被存儲在頭文件的名稱域;若是這個名稱大於16個字節,那麼這個名稱就會被存儲到這裏。而在頭文件的名稱域存儲的則是該字符串在長名稱節的偏移。

目標文件節。該節是靜態連接庫的主要內容,節的數量不定,它存儲了若干各目標文件的內容,每一節都是頭信息+目標文件的結構。目標文件的結構與2.2節描述的一致。

在WinNT.h頭文件中,頭信息被定義爲IMAGE_ARCHIVE_MEMBER_HEADER類型,具體的定義內容以下:

typedef struct _IMAGE_ARCHIVE_MEMBER_HEADER

{

    BYTE     Name[16];                          // File member name - `/' terminated.

    BYTE     Date[12];                          // File member date - decimal.

    BYTE     UserID[6];                         // File member user id - decimal.

    BYTE     GroupID[6];                        // File member group id - decimal.

    BYTE     Mode[8];                           // File member mode - octal.

    BYTE     Size[10];                          // File member size - decimal.

    BYTE     EndHeader[2];                      // String to end header.

} IMAGE_ARCHIVE_MEMBER_HEADER, *PIMAGE_ARCHIVE_MEMBER_HEADER;

 

2.4PE文件的結構

2.4.1整體結構圖

從文件內容上來看,PE文件由二進制數據組成。這些二進制數據從文件的零位置開始,依次存儲,直到文件末尾。從數據結構的角度來看,這些二進制數據又分別屬於不一樣的結構體或者結構體數組。這些結構體被定義在「WinNT.h」頭文件中。

在PE文件中,這些結構體或結構體數組分別表示不一樣的含義,記錄着PE文件中的不一樣內容。從文件的頂端開始,依次存儲了DOS頭,PE頭,段表,各段詳細數據等信息。在進行信息字段定位的時候,PE文件採用兩種方式:1利用指針。好比:在Dos頭中存儲一個指向PE頭的指針;2利用數據結構的大小。在PE的頭部信息中,一些數據結構的大小是固定的。在數據存儲的時候,各個數據結構緊湊存放,中間沒有空隙。在這種狀況下,以一個數據結構的字段爲基點,經過計算數據結構佔用空間的大小,就能夠定位另一個數據結構的位置。

使用dumpbin工具能夠將PE文件的內容導出,具體的命令格式以下:

Dumpbin /all DemoDlld.dll >DemoDll.txt

    在上面的命令中,將PE文件「DemoDlld.dll」的全部內容導出到文本文件「DemoDll.txt」中。命令選項「/all」表示導出全部內容,命令選項「>」表示將導出的內容存儲到文件中。

 

2.4.2 DOS頭

2.4.2.1DOS MZ 頭

全部 PE文件都必須以DOS MZ header開始,它是一個IMAGE_DOS_HEADER的結構。有了它,一旦程序在DOS下執行,DOS就能識別出這是有效的執行體,而後運行緊隨MZ Header以後的DOS Stub。

   在「WinNT.h」頭文件中,DOS MZ 頭被定義爲IMAGE_DOS_HEADER類型,具體的定義形式以下:

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header

    WORD   e_magic;                     // 魔術數字

    WORD   e_cblp;                      // 文件最後頁的字節數

    WORD   e_cp;                        // 文件頁數

    WORD   e_crlc;                      // 重定義元素個數

    WORD   e_cparhdr;                   // 頭部尺寸,以段落爲單位

    WORD   e_minalloc;                  // 所需的最小附加段

    WORD   e_maxalloc;                  // 所需的最大附加段

    WORD   e_ss;                        // 初始的SS值

    WORD   e_sp;                        // 初始的SP值

    WORD   e_csum;                      // 校驗和

    WORD   e_ip;                        // 初始的IP值

    WORD   e_cs;                        // 初始的CS值

    WORD   e_lfarlc;                    // 重分配表文件地址

    WORD   e_ovno;                      // 覆蓋號

    WORD   e_res[4];                    // 保留字

    WORD   e_oemid;                     // OEM 標識符

    WORD   e_oeminfo;                   // OEM information; e_oemid specific

    WORD   e_res2[10];                  // 保留字

    LONG   e_lfanew;                    // PE頭的地址

  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

在DOS頭中,第一個域「e_magic」被稱爲魔術數字,它用於表示一個MS-DOS兼容的文件類型。全部MS-DOS兼容的可執行文件都將這個值設爲0x5A4D,表示ASCII字符MZ。MS-DOS頭部之因此有的時候被稱爲MZ頭部,就是這個緣故。

對於MS-DOS操做系統來講,許多其餘的域都是有用的。可是對於 Windows NT來講,只有最後一個域e_lfnew是有用的,該域是一個指針,佔用4個字節,用於指明PE頭在文件中的位置。

2.4.2.2DOS Stub

DOS Stub其實是個有效的EXE,在不支持PE文件格式的操做系統中,它將簡單顯示一個錯誤提示,相似於字符串「This program requires Windows」,或者程序員可根據本身的意圖實現完整的DOS代碼。大多數狀況下DOS Stub由彙編器/編譯器自動生成。

2.4.3 PE頭

PE頭緊跟在DOS MS頭以及實模式程序殘餘以後,在WinNt.h頭文件中,PE頭被定義爲IMAGE_NT_HEADER類型,具體的定義內容以下所示:

typedef struct _IMAGE_NT_HEADERS

{

    DWORD Signature;                          //PE頭標識

    IMAGE_FILE_HEADER FileHeader;             //PE文件物理分佈信息

    IMAGE_OPTIONAL_HEADER32 OptionalHeader;   //PE文件邏輯分佈信息

} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

   在該結構體數據中,除了包含PE頭標識外,又嵌套了兩個結構體數據,分別是PE文件頭的信息,以及PE可選頭的信息。

2.4.3.1PE頭標識

在一個有效的PE文件中,PE頭標識字段的值是0x00004550,用ASCII表示就是「PE00」。 #define IMAGE_NT_SIGNATURE定義了這個值。

2.4.3.2文件頭

見2.2.2節描述。PE中的文件頭與COFF中的文件頭定義一致。

2.4.3.3可選頭

在PE文件頭以後,是PE可選頭。該頭224字節大小,它包含了許多重要的信息,例如:初始堆棧大小,程序的入口地址,首選加載基地址,操做系統版本,段對齊等。該頭並不是可選,而是必需要有的頭。

在可選頭中,包含了三類主要信息,分別是:標準域信息,WinNT附加信息,以及數據目錄信息。

所謂標準域就是指和UNIX可執行文件的COFF格式所公共的部分,雖然標準域中保留了COFF文件中定義的名稱,可是WindowsNT仍然將它用做了不一樣的目的。

在操做系統的加載器加載PE文件的時候,WinNT附件域的信息爲加載器提供了支持。

在可執行文件中有許多數據結構須要被快速定位,數據目錄提供了這種支持。數據目錄是一個指針列表,在該列表中保存了一系列的指針值,這些指針指向了其餘的數據表。如:導入表,導出表,資源表,重定位表等。數據目錄以指針的形式提供了一種信息查找的方式。

使用工具dumpbin能夠將PE文件的內容導出,在該內容中包含了描述可選頭的摘要信息,具體的信息內容以下:

OPTIONAL HEADER VALUES

//如下爲標準域的信息

             10B magic # (PE32)

            9.00 linker version

            5800 size of code

            4800 size of initialized data

               0 size of uninitialized data

           11159 entry point (10011159) @ILT+340(__DllMainCRTStartup@12)

            1000 base of code

            1000 base of data

//如下爲WinNT附加域的信息

        10000000 image base (10000000 to 1001CFFF)

            1000 section alignment

             200 file alignment

            5.00 operating system version

            0.00 image version

            5.00 subsystem version

               0 Win32 version

           1D000 size of image

             400 size of headers

               0 checksum

               2 subsystem (Windows GUI)

             140 DLL characteristics

                   Dynamic base

                   NX compatible

          100000 size of stack reserve

            1000 size of stack commit

          100000 size of heap reserve

            1000 size of heap commit

               0 loader flags

              10 number of directories

//如下爲數據目錄的信息

           18AD0 [     2BA] RVA [size] of Export Directory

           1A000 [      50] RVA [size] of Import Directory

           1B000 [     C09] RVA [size] of Resource Directory

               0 [       0] RVA [size] of Exception Directory

               0 [       0] RVA [size] of Certificates Directory

           1C000 [     38C] RVA [size] of Base Relocation Directory

           17520 [      1C] RVA [size] of Debug Directory

               0 [       0] RVA [size] of Architecture Directory

               0 [       0] RVA [size] of Global Pointer Directory

               0 [       0] RVA [size] of Thread Storage Directory

               0 [       0] RVA [size] of Load Configuration Directory

               0 [       0] RVA [size] of Bound Import Directory

           1A224 [     1D4] RVA [size] of Import Address Table Directory

               0 [       0] RVA [size] of Delay Import Directory

               0 [       0] RVA [size] of COM Descriptor Directory

               0 [       0] RVA [size] of Reserved Directory

在WinNT.h頭文件中,可選頭被定義爲IMAGE_OPTIONAL_HEADER類型,具體的定義內容描述以下:

//數據目錄中數據元素的個數

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

//可選頭的定義

typedef struct _IMAGE_OPTIONAL_HEADER

{

    //標準域

    WORD    Magic;  //魔法數字

    BYTE    MajorLinkerVersion;//連接器的最大版本號

    BYTE    MinorLinkerVersion;//連接器的最小版本號

    DWORD   SizeOfCode;//可執行代碼長度

    DWORD   SizeOfInitializedData;//初始化數據的長度(data段)

    DWORD   SizeOfUninitializedData;//未初始化數據的長度(bss段)

    DWORD   AddressOfEntryPoint;//代碼的入口地址,程序今後處開始執行

    DWORD   BaseOfCode;//可執行代碼的起始位置

    DWORD   BaseOfData;//初始化數據的起始位置

    //NT附件域

    DWORD   ImageBase;//載入程序首選的相對虛擬地址

    DWORD   SectionAlignment;//段加載到內存之後的對齊方式

    DWORD   FileAlignment;//段在文件中的對齊方式

    WORD    MajorOperatingSystemVersion;//操做系統最大版本號

    WORD    MinorOperatingSystemVersion;//操做系統最小版本號

    WORD    MajorImageVersion;//程序最大版本號

    WORD    MinorImageVersion;//程序最小版本號

    WORD    MajorSubsystemVersion;//子程序最大版本號

    WORD    MinorSubsystemVersion;//子程序最小版本號

    DWORD   Win32VersionValue;//這個值一直爲零

    DWORD   SizeOfImage;//程序加載到內存之後,佔用內存的大小。

    DWORD   SizeOfHeaders;//文件頭部總大小

    DWORD   CheckSum;//校驗和

    WORD    Subsystem;//一個標明可執行文件所指望的子系統的枚舉值

    WORD    DllCharacteristics;//dll狀態

    DWORD   SizeOfStackReserve;//保留棧大小

    DWORD   SizeOfStackCommit;//啓動後實際申請棧數

    DWORD   SizeOfHeapReserve;//保留堆的大小

    DWORD   SizeOfHeapCommit;//啓動後實際申請堆數

    DWORD   LoaderFlags;

DWORD   NumberOfRvaAndSizes;

//數據目錄的定義

    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

在可選頭中,各個字段的詳細解釋以下表:

字段名稱

類型

描述

Magic

WORD   

一個簽名,肯定這是什麼類型的頭。兩個最經常使用的值是IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b

和IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b.

MajorLinkerVersion

BYTE   

建立可執行文件的連接器的主版本號。對於Microsoft的連接器生成的PE文件,這個版本號的Visual Studio的版本號相一致

MinorLinkerVersion

BYTE   

建立可執行文件的連接器的次版本號

SizeOfCode

DWORD  

全部具備IMAGE_SCN_CNT_CODE屬性的節的總的大小

SizeOfInitializedData

DWORD  

全部包含已初始數據的節的總的大小。

SizeOfUninitializedData

DWORD  

全部包含未初始化數據的節的總的大小。這個域老是0,由於連接器能夠把未初始化數據附加到常規數據節的末尾。

AddressOfEntryPoint

DWORD  

文件中將被執行的第一個代碼字節的RVA。對於DLL,這個進入點將在進程初始化和關閉時,以及線程被建立和銷燬時調用。在大多數可執行文件中,這個地址並不直接指向main,WinMain或DllMain函數,而是指向運行時庫代碼,由運行時庫調用前述函數。在DLL中,這個域能夠被設爲0,這樣的話上面所說的通知就不能被接收到。連接器選項/NOENTRY能夠設置這個域爲0。

BaseOfCode

DWORD  

加載到內存後代碼的第一個字節的RVA。

BaseOfData

DWORD  

理論上,它表示加載到內存後數據的第一個字節的RVA。然而,這個域的值對於不一樣版本的Microsoft連接器是不一致的。在64位的可執行文件中這個域不出現。

ImageBase

DWORD  

文件在內存中的首選加載地址。加載器儘量地把PE文件加載到這個地址(就是說,若是當前這塊內存沒有被佔用,它是對齊的而且是一個合法的地址,等等)。若是可執行文件被加載到這個地址,加載器就能夠跳過進行基址重定位(在這篇文章的第二部分描述)這一步。對於EXE,缺省的ImageBase是0x400000。對於DLL,缺省是0x10000000。在連接時能夠經過/BASE 選項來指定ImageBase,或者之後用REBASE工具從新設置。

SectionAlignment

DWORD  

加載到內存後節的對齊大小。這個值必須大於等於FileAlignment(下一個域)。缺省的對齊值是目標CPU的頁大上。對於運行在Windows 9x或Windows Me下的用戶模式可執行文件,最小對齊大小是一頁(4KB)。這個域能夠經過連接器選項/ALIGN來設置。

FileAlignment

DWORD  

在PE文件中節的對齊大小。對於x86下的可執行文件,這個值一般是0x200或0x1000。不一樣版本的Microsoft連接器缺省值不一樣。這個值必須是2的冪,而且若是SectionAlignment小於CPU的頁大小,這個域必須和SectionAlignment相匹配。連接器選項/OPT:WIN98可設置x86可執行文件的文件對齊爲0x1000,/OPT:NOWIN98設置文件對齊爲0x200。

MajorOperatingSystemVersion

WORD   

所要求的操做系統的主版本號。隨着那麼多版本Windows的出現,這個域的值就變得很不確切。

MinorOperatingSystemVersion

WORD   

所要求的操做系統的次版本號。

MajorImageVersion

WORD   

這個文件的主版本號。不被系統使用並可設爲0。能夠經過連接器選項/VERSION來設置。

MinorImageVersion

WORD   

這個文件的次版本號。

MajorSubsystemVersion

WORD   

可執行文件所要求的操做子系統的主版本號。它曾經被用來表示須要較新的Windows 95或Windows NT用戶界面,而不是老版本的Windows NT界面。今天隨着各類不一樣版本Windows的出現,這個域已不被系統使用,而且一般被設爲4。可經過連接器選項/SUBSYSTEM設置這個域的值。

MinorSubsystemVersion

WORD   

執行文件所要求的操做子系統的次版本號。

Win32VersionValue

DWORD  

不被使用的域,一般設爲0。

SizeOfImage

DWORD  

映像的大小。它表示了加載文件到內存中時系統必須保留的內存的數量。這個域的值必須是SectionAlignmnet的倍數。

SizeOfHeaders

DWORD  

MS-DOS頭,PE頭和節表的總的大小。PE文件中全部這些項目出如今任何代碼或數據節以前。這個域的值被調整爲文件對齊大小的整數倍。

CheckSum

DWORD  

映像的校驗和。IMAGEHLP.DLL中的CheckSumMappedFile函數能夠計算出這個值。校驗和用於內核模式的驅動和一些系統DLL。對於其它的,這個域能夠爲0。當使用連接器選項/RELEASE時校驗和被放入文件中。

Subsystem

WORD   

指示可執行文件指望的子系統(用戶界面類型)的枚舉值。這個域只用於EXE。一些重要的值包括:

IMAGE_SUBSYSTEM_NATIVE

                                         // 映像不須要子系統

IMAGE_SUBSYSTEM_WINDOWS_GUI

                                         // 使用Windows GUI

IMAGE_SUBSYSTEM_WINDOWS_CUI

                                         // 做爲控制檯程序運行。

                                         // 運行時,操做系統建立一個控制檯

                                         // 窗口並提供stdin,stdout和stderr

                                         // 文件句柄。

DllCharacteristics

WORD   

標記DLL的特性。對應於IMAGE_DLLCHARACTERISTICS_xxx定義。當前的值是:

IMAGE_DLLCHARACTERISTICS_NO_BIND

                                         // 不要綁定這個映像

IMAGE_DLLCHARACTERISTICS_WDM_DRIVER

                                         // WDM模式的驅動程序

IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE

                                         // 當終端服務加載一個不是

                                         // Terminal- Services-aware 的應用程

                                         // 序時,它也加載一個包含兼容代碼

                                         // 的DLL。

SizeOfStackReserve

DWORD  

在EXE文件中,爲線程保留的堆棧大小。缺省是1MB,但並非全部的內存一開始都被提交。

SizeOfStackCommit

DWORD  

在EXE文件中,爲堆棧初始提交的內存數量。缺省狀況下,這個域是4KB。

SizeOfHeapReserve

DWORD  

在EXE文件中,爲默認進程堆初始保留的內存大小。缺省是1MB。然而在當前版本的Windows中,堆不通過用戶干涉就能超出這裏指定的大小。

SizeOfHeapCommit

DWORD  

在EXE文件中,提交到堆的內存大小。缺省狀況下,這裏的值是4KB。

LoaderFlags

DWORD  

不使用。

NumberOfRvaAndSizes

DWORD  

在IMAGE_NT_HEADERS結構的末尾是一個IMAGE_DATA_DIRECTORY結構數組。此域包含了這個數組的元素個數。自從最先的Windows NT發佈以來這個域的值一直是16。

 

數據目錄一共有16項,每一項都存儲一個指向其餘數據表的指針,數據目錄爲數據的快速定位提供了支持。在WinNT.h頭文件中,數據目錄被定義爲IMAGE_DATA_DIRECTORY,具體的定義內容描述以下:

typedef struct _IMAGE_DATA_DIRECTORY

{

    DWORD   VirtualAddress; //被指向元素表的起始RVA地址。

    DWORD   Size;           //被指向元素表的長度

} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

 

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

在數據目錄的16項元素中,每一項都預約義了它的含義,IMAGE_DIRECTORY_ENTRY_XXX定義了數據目錄數組的索引。該索引的具體含義描述以下表:

序號

索引

描述

0

IMAGE_DIRECTORY_ENTRY_EXPORT

指向導出表(一個IMAGE_EXPORT_DIRECTORY結構)。

1

IMAGE_DIRECTORY_ENTRY_IMPORT

指向導入表(一個IMAGE_IMPORT_DESCRIPTOR結構數組)。

2

IMAGE_DIRECTORY_ENTRY_RESOURCE

指向資源(一個IMAGE_RESOURCE_DIRECTORY結構。

3

IMAGE_DIRECTORY_ENTRY_EXCEPTION

指向異常處理表(一個IMAGE_RUNTIME_FUNCTION_ENTRY結構數組)。CPU特定的而且基於表的異常處理。用於除x86以外的其它CPU上。

4

IMAGE_DIRECTORY_ENTRY_SECURITY

指向一個WIN_CERTIFICATE結構的列表,它定義在WinTrust.H中。不會被映射到內存中。所以,VirtualAddress域是一個文件偏移,而不是一個RVA。

5

IMAGE_DIRECTORY_ENTRY_BASERELOC

指向基址重定位信息。

6

IMAGE_DIRECTORY_ENTRY_DEBUG

指向一個IMAGE_DEBUG_DIRECTORY結構數組,其中每一個結構描述了映像的一些調試信息。早期的Borland連接器設置這個IMAGE_DATA_DIRECTORY結構的Size域爲結構的數目,而不是字節大小。要獲得IMAGE_DEBUG_DIRECTORY結構的數目,用IMAGE_DEBUG_DIRECTORY 的大小除以這個Size域。

7

IMAGE_DIRECTORY_ENTRY_ARCHITECTURE

指向特定架構數據,它是一個IMAGE_ARCHITECTURE_HEADER結構數組。不用於x86或IA-64,但看來已用於DEC/Compaq Alpha。

8

IMAGE_DIRECTORY_ENTRY_GLOBALPTR

在某些架構體系上VirtualAddress域是一個RVA,被用來做爲全局指針(gp)。不用於x86,而用於IA-64。Size域沒有被使用。參見2000年11月的Under The Hood 專欄可獲得關於IA-64 gp的更多信息。

9

IMAGE_DIRECTORY_ENTRY_TLS

指向線程局部存儲初始化節。

10

IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG

指向一個IMAGE_LOAD_CONFIG_DIRECTORY結構。IMAGE_LOAD_CONFIG_DIRECTORY中的信息是特定於Windows NT、Windows 2000和 Windows XP的(例如 GlobalFlag 值)。要把這個結構放到你的可執行文件中,你必須用名字__load_config_used 定義一個全局結構,類型是IMAGE_LOAD_CONFIG_DIRECTORY。對於非x86的其它體系,符號名是_load_config_used (只有一個下劃線)。若是你確實要包含一個IMAGE_LOAD_CONFIG_DIRECTORY,那麼在 C++ 中要獲得正確的名字比較棘手。連接器看到的符號名必須是__load_config_used (兩個下劃線)。C++ 編譯器會在全局符號前加一個下劃線。另外,它還用類型信息修飾全局符號名。所以,要使一切正常,在 C++ 中就必須像下面這樣使用:

extern "C"

IMAGE_LOAD_CONFIG_DIRECTORY _load_config_used = {...}

11

IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT

指向一個 IMAGE_BOUND_IMPORT_DESCRIPTOR結構數組,對應於這個映像綁定的每一個DLL。數組元素中的時間戳容許加載器快速判斷綁定是不是新的。若是不是,加載器忽略綁定信息而且按正常方式解決導入API。

12

IMAGE_DIRECTORY_ENTRY_IAT

指向第一個導入地址表(IAT)的開始位置。對應於每一個被導入DLL的IAT都連續地排列在內存中。Size域指出了全部IAT的總的大小。在寫入導入函數的地址時加載器使用這個地址和Size域指定的大小臨時地標記IAT爲可讀寫。

13

IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT

指向延遲加載信息,它是一個CImgDelayDescr結構數組,定義在Visual C++的頭文件DELAYIMP.H中。延遲加載的DLL直到對它們中的API進行第一次調用發生時纔會被裝入。Windows中並無關於延遲加載DLL的知識,認識到這一點很重要。延遲加載的特徵徹底是由連接器和運行時庫實現的。

14

IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR

在最近更新的系統頭文件中這個值已被更名爲IMAGE_DIRECTORY_ENTRY_COMHEADER。它指向可執行文件中.NET信息的最高級別信息,包括元數據。這個信息是一個IMAGE_COR20_HEADER結構。

15

IMAGE_DIRECTORY_ENTRY_EXPORT

指向導出表(一個IMAGE_EXPORT_DIRECTORY結構)。

 

2.4.4導入表

當一個可執行程序或者動態連接庫調用另一個動態連接庫中的變量或者函數的時候,必須將這些被調用函數或者變量導入。這些被導入的變量或函數是必須是被調用動態連接庫導出表中的一個子集。也就是說,只有當這些變量或者函數被導出之後,才能被其餘可執行程序或者動態連接庫導入。

在PE文件中,咱們將這些變量和函數統稱爲符號,這些被導入的符號的信息被存儲在導入表中。在PE文件中,導入表位於.idata段中,該段能夠單獨存在。

使用工具dumpbin將可執行成DemoExe.exe中的內容導出,.idata段的部份內容以下所示:

//.idata段的信息摘要

SECTION HEADER #5

  .idata name

    1130 virtual size

   1A000 virtual address (0041A000 to 0041B12F)

    1200 size of raw data

    7A00 file pointer to raw data (00007A00 to 00008BFF)

       0 file pointer to relocation table

       0 file pointer to line numbers

       0 number of relocations

       0 number of line numbers

C0000040 flags

         Initialized Data

         Read Write

//idata段的二進制數據內容

RAW DATA #5

  0041A000: 64 A0 01 00 00 00 00 00 00 00 00 00 B4 A5 01 00  d?..........′¥..

  0041A010: B0 A2 01 00 58 A1 01 00 00 00 00 00 00 00 00 00  °¢..X?..........

  0041A020: 30 AB 01 00 A4 A3 01 00 F4 A1 01 00 00 00 00 00  0?..¤£..??......

      …………….

//導入表的信息

Section contains the following imports:

 

    DemoDLLd.dll

                41A2B0 Import Address Table   //導入地址表地址

                41A064 Import Name Table    //導入名稱表地址

                     0 time date stamp

                     0 Index of first forwarder reference

  //導出地址表數組下標  符號名稱

                    6 ?GetOperTimes@@YAHXZ

                    4 ?Area@DemoMath@@QAEXN@Z

                    5 ?DivData@DemoMath@@QAEXNN@Z

                    8 ?SubData@DemoMath@@QAEXNN@Z

                    3 ?AddData@DemoMath@@QAEXNN@Z

                    0 ??0DemoMath@@QAE@XZ

                    1 ??1DemoMath@@QAE@XZ

對於每個可執行程序或者動態連接庫,只要它調用了其餘動態連接庫中的符號,那麼就會有一個或多個IMAGE_IMPORT_DESCRIPTOR類型的數組與之對應。該數組元素的個數與該可執行程序或者動態連接庫所依賴的動態連接庫的數量有關。

在IMAGE_IMPORT_DESCRIPTOR類型的數據結構中,存儲的是與導入表相關的信息。該數據結構與其餘數據結構發生關聯,與該數據結構發生關聯的數據實體包括:數據目錄,字符串表,導入地址表,導入名稱表。它們之間的關係以下圖所示:

在可選頭中包含了數據目錄,在數據目錄的第二項中,存儲了指向導入表的位置的指針,以及該數據結構的大小。在數據目錄的第十二項中,存儲了指向第一個IAT的位置的指針,以及全部IAT數組的大小。

導入表是一個數組,該數據元素的類型是IMAGE_IMPORT_DESCRIPTOR。在該數組中,每個數組元素都會對應一個被導入的DLL。在該數組的末尾,存儲了一個全部的域爲零的IMAGE_IMPORT_DESCRIPTOR類型的數組元素,表示該導入表結束。所以,在一個可執行程序或者動態連接庫的導入表中,至少會包含兩個數據元素。一個對應被導入符號的信息,一個全部域爲零。

在每一個導入表的數組的元素中,擁有兩個地址字段,它們分別指向了導入地址表(IAT)和導入名稱表(INT)。在導入地址表和導入名稱表中,存儲了被導入符號的名稱或地址,它們是一個擁有多個數據元素的數組。由於導入表的數組元素多是多個(當前可執行程序或動態連接庫依賴多個其餘的動態連接庫),因此在一個動態連接庫的導入信息中也會存在多個IAT數組以及INT數組。當PE文件被加載到內存之後,這些IAT數組會被存儲在一塊連續地內存區域中,它們首尾相連,中間沒有空隙。在數據目錄的第十二項中,存儲了第一個IAT的位置,以及全部IAT數組的大小。導入表的結構佈局以下圖所示:

導入地址表和導入名稱表的數據結構是一致的,它們均爲IMAGE_THUNK_DATA類型,並以一個全部域均爲零的數組元素結尾。導入地址表用於動態連接,而導入名稱表用於符號地址綁定。在PE文件中,導入地址表和導入名稱表中的數據內容是同樣的,它們都存儲着被導入符號的名稱或者序號;在程序加載階段,導入名稱表的數據內容不會發生變化,而導入地址表中的被導入符號的序號或者名稱將會被替換爲該符號的真實虛擬內存地址。導入名稱表的數據內容永遠不會發生變化。

在WinNT.h頭文件中,導入表被定義爲IMAGE_IMPORT_DESCRIPTOR類型,具體的定義內容以下所示:

typedef struct _IMAGE_IMPORT_DESCRIPTOR

{

union

{

        DWORD   Characteristics;      // 0 for terminating null import descriptor

        DWORD   OriginalFirstThunk;   // RVA to original unbound IAT (PIMAGE_THUNK_DATA)

    };

    DWORD   TimeDateStamp;           // 0 if not bound,

                                     // -1 if bound, and real date\time stamp

                                     //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)

                                     // O.W. date/time stamp of DLL bound to (Old BIND)

 

    DWORD   ForwarderChain;         // -1 if no forwarders

    DWORD   Name;

    DWORD   FirstThunk;            // RVA to IAT (if bound this IAT has actual addresses)

} IMAGE_IMPORT_DESCRIPTOR;

typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

在該定義中,各個字段的詳細解釋以下表所示:

字段名稱

類型

描述

Characteristics

DWORD  

 

OriginalFirstThunk

DWORD  

指向導入名稱表的相對虛擬地址。

TimeDateStamp

DWORD  

若是可執行文件不與被導入DLL綁定時,該值爲0;若是以舊的樣式綁定時,改值爲時間戳;若是以新的樣式綁定時,該值爲-1。

ForwarderChain

DWORD  

第一個被轉送符號的索引,若是沒有轉送,則設定爲-1。

Name

DWORD  

該字段存儲了一個相對虛擬地址。該地址指向字符串表中某個字符串,這個字符串是導入DLL的名稱。

FirstThunk

DWORD  

指向導入地址表的相對虛擬地址。

在WinNT.h頭文件中,IAT以及INT被定義爲IMAGE_THUNK_DATA類型,具體的定義內容以下所示:

typedef struct _IMAGE_THUNK_DATA32

{

union

{

        DWORD ForwarderString;      // PBYTE

        DWORD Function;             // PDWORD

        DWORD Ordinal;

        DWORD AddressOfData;        // PIMAGE_IMPORT_BY_NAME

    } u1;

} IMAGE_THUNK_DATA32;

typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

從上面的定義能夠看出,IAT數據結構是一個聯合。在該聯合中,每個字段表明着在不一樣條件或者時間上,該數據結構可能存儲不一樣意義的數據。在該定義中,各個字段的詳細解釋以下表所示:

字段名稱

類型

描述

ForwarderString

DWORD

指向一個轉向字符串的相對虛擬地址。轉向導入的時候使用。

Function

DWORD

被導入符號的虛擬地址。動態連接完成之後寫入該值。

Ordinal

DWORD

被導入符號在導出表中的序號。編譯連接完畢後,該值可能會被寫入到PE文件中。該值與AddressofData字段互斥。

AddressOfData

DWORD

指向一個IMAGE_IMPORT_BY_NAME類型的數據結構。編譯連接完畢後,該值可能會被寫入到PE文件中。該值與Ordinal字段互斥。

在WinNT.h頭文件中,IMAGE_IMPORT_BY_NAME的定義內容以下:

typedef struct _IMAGE_IMPORT_BY_NAME

{

    WORD    Hint;

    BYTE    Name[1];

} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

在上面的定義中,各字段的詳細解釋以下表:

字段名稱

類型

描述

Hint

WORD   

導入符號最有可能的序號值。在動態連接的時候,當用符號名導入時,首先用Hint值在導出符號表中查找該符號,若是可以查找到,則命中;若是不能查找到,則使用符號名進行二分法查找。

Name

BYTE   

符號名稱的字符串

 

在IAT數組中,每個數組元素都會對應一個被導入符號的地址,數組元素在不一樣的狀況下有不一樣的含義。具體的含義形式有四種,它們分別是:

  • 轉向字符串形式。
  • 符號虛擬地址形式。
  • 序號形式。
  • 符號名稱形式。

在不一樣的時間點上,或者在不一樣的狀況下,被導出符號的地址用不一樣的形式表示。所以在數據結構定義的時候使用了聯合。

在源程序被編譯、連接完畢之後生成的PE文件中,以PE文件被加載到內存中,還沒有執行動態連接的時候,在IAT中符號的地址以序號或者符號名稱的形式表示;當執行完畢動態連接之後,使用符號的序號或者符號名稱,經過對相關DLL導出表的查找,這些符號的序號或者符號的名稱被替換成了符號的虛擬內存地址。

當IAT中的符號地址以序號或者符號名稱表示的時候,能夠經過DWORD值的最高位來區分是使用序號表示仍是使用符號名稱表示。若是最高位被置1,那麼該符號地址使用序號的形式表示,DWORD值的低31位表示序號;若是最高爲被置0,那麼該符號地址使用符號名稱的形式表示,DWORD值表示一個地址,指向了一個IMAGE_IMPORT_BY_NAME類型的數據結構。

在IMAGE_IMPORT_BY_NAME類型的數據結構中,包含了一個符號的名稱字符串,以及一個WORD類型的提示值,該提示值指示了符號在導出表中最可能的序號。在符號查找的時候,若是使用Hint值查找不能命中,那麼就會使用符號名稱進行二分法查找。

2.4.5導出表

在一個動態連接庫文件中,總會有一些變量或者函數被聲明爲public,這些變量和函數被稱爲該動態連接庫的接口,它們能夠被其它可執行程序或者動態連接庫調用。在該動態連接庫中,並非全部被聲明爲public類型的接口都能被其餘可執行程序或動態連接庫調用,只有當這些接口被導出之後才能被其餘可執行程序或者動態連接庫調用。

在PE文件中,咱們將變量和函數統稱爲符號。這些被導出的符號的信息被存儲在導出表。通常狀況,導出表不會單獨存在,在輸出PE文件的時候,導出表可能會被合併到.rata段中,該段是隻讀數據段。

使用工具dumpbin將動態連接庫DemoDlld.dll的內容導出,.rdata段的部份內容以下所示:

//只讀段的摘要信息

SECTION HEADER #3

  .rdata name

    1D8A virtual size

   17000 virtual address (10017000 to 10018D89)

    1E00 size of raw data

    5C00 file pointer to raw data (00005C00 to 000079FF)

       0 file pointer to relocation table

       0 file pointer to line numbers

       0 number of relocations

       0 number of line numbers

40000040 flags

         Initialized Data

         Read Only

//只讀段的二進制數據內容

RAW DATA #3

  10017000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

  10017010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

  10017020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

…..

//導出表的信息

Section contains the following exports for DemoDLLd.dll

 

    00000000 characteristics

    51A86C37 time date stamp Fri May 31 17:24:07 2013

        0.00 version

           1 ordinal base

           9 number of functions  //符號地址表元素的數量

           9 number of names    //符號名稱表元素的數量

 

      ordinal  hint  RVA       name

//序號,數組下標,相對虛擬地址,符號名稱

          1    0 00011145 ??0DemoMath@@QAE@XZ

          2    1 00011109 ??1DemoMath@@QAE@XZ

          3    2 0001101E ??4DemoMath@@QAEAAV0@ABV0@@Z

          4    3 000111C7 ?AddData@DemoMath@@QAEXNN@Z

          5    4 0001116D ?Area@DemoMath@@QAEXN@Z

          6    5 00011221 ?DivData@DemoMath@@QAEXNN@Z

          7    6 000110B4 ?GetOperTimes@@YAHXZ

          8    7 00011005 ?MulData@DemoMath@@QAEXNN@Z

          9    8 0001114A ?SubData@DemoMath@@QAEXNN@Z

 

對於每個動態連接庫,都會有一個IMAGE_EXEPORT_DIRECTORY類型的數據結構與之對應。該數據結構保存了與導出表有關的信息,並與其餘數據結構發生關聯。與該數據結構有關聯的數據實體包括:數據目錄,字符串表,符號地址表,符號名稱表,名稱序號對應關係表。這些實體之間的關係以下圖所示:

可選頭中包含了數據目錄,數據目錄的第一項指向了導出表。導出表是一個結構體類型,在該結構體的字段中保存了與導出表相關的信息,如:DLL名稱字段指向了一個字符串表,在該字符串表中保存了DLL的名稱;符號地址表地址字段指向了一個地址數組,該數組中保存了符號的地址;符號名稱表地址字段指向了符號名稱數組,該數組中保存了符號名稱字符串的地址;名稱序號對應關係表地址指向了名稱序號對應關係的數組,該數組中存儲了符號的名稱與序號之間的對應關係。

     符號地址表是一個DWORD類型的數組,在該數組中存儲了被導出符號的相對虛擬地址,數組中每個非零的數組元素都指向了一個符號的相對虛擬地址;符號名稱表是一個DWORD類型的數組,數組中保存的是相對虛擬地址,該相對虛擬地址指向了字符串表中的相關位置,這些位置中保存了符號的名稱;名稱序號表是一個WORD類型的數組,該數組中保存了符號的序號。符號名稱表中的元素與名稱序號表中的元素經過數組下標對應。好比:在符號名稱表中,數組下標爲1的元素,它的序號存在在名稱序號表中,數組下標也爲1。

     序號的計算公式爲:序號 = 符號地址表數組下標 + Base字段值。在Windows16位時代,因爲受到硬件大小的限制,在執行動態連接的時候,使用序號查找符號的地址,即:用序號的值減去Base的值,得到符號地址表數組的下標,進而得到符號的相對虛擬內存地址。這種方式節省了內存空間以及符號查找的時間,可是易讀性差。隨着時間的發展,當硬件的物理內存不在是問題的時候,開始使用符號名稱查找符號的地址,具體的查找過程是:經過符號名稱在名稱序號對應關係表中查找到符號的序號,而後再用符號的序號查找符號的地址。雖然引入了符號名稱表,可是這個表不是必須的,依然能夠經過序號查找符號的地址。在一個DLL中,每個導出符號都有一個惟一對應的序號,而導出符號名是可選的。

在動態連接的時候,能夠經過兩種方式進行符號地址的查找,一種是直接利用符號的序號直接查找,另一種是利用符號的名稱間接查找。在進行符號地址查找的時候,符號地址表,符號名稱表,名稱序號對應表之間的關係以下圖所示:

在WinNT.h頭文件中,導出表被定義爲IMAGE_EXPORT_DIRECTORY類型,具體的定義內容以下:

typedef struct _IMAGE_EXPORT_DIRECTORY

{

    DWORD   Characteristics;

    DWORD   TimeDateStamp;

    WORD    MajorVersion;

    WORD    MinorVersion;

    DWORD   Name;

    DWORD   Base;

    DWORD   NumberOfFunctions;

    DWORD   NumberOfNames;

    DWORD   AddressOfFunctions;     // RVA from base of image

    DWORD   AddressOfNames;         // RVA from base of image

    DWORD   AddressOfNameOrdinals;  // RVA from base of image

} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

在該定義中,各字段的詳細解釋以下表所示:

字段名稱

類型

描述

Characteristics

DWORD

關於導出表的一些標記,目前沒有被定義。

TimeDateStamp

DWORD

導出表被建立的時間。

MajorVersion

WORD   

導出表的主版本號,目前沒有使用,設爲零。

MinorVersion

WORD   

導出表的次版本號,目前沒有使用,設爲零。

Name

DWORD

一個相對虛擬地址,指向字符串表的一個位置,該位置存儲了該Dll的名稱。

Base

DWORD

一般設定爲1,但也不是必須的。

序號 = Base + 符號地址數組下標。

NumberOfFunctions

DWORD

符號地址表中數據元素的個數。

NumberOfNames

DWORD

符號名稱表以及符號名稱序號對應關係表中數據元素的個數。因爲符號名稱表和符號名稱序號對應關係表是一一對應的,因此它們的數組元素個數相同;該值可能會小於NumberOfFunctions,若是小於這個值,表示有一部分符號只經過序號對應,沒有保存它們的名稱。

AddressOfFunctions

DWORD

相對虛擬地址,指向符號地址表。

AddressOfNames

DWORD

相對虛擬地址,指向符號名稱表。

AddressOfNameOrdinals

DWORD

相對虛擬地址,指向符號名稱序號對應關係表。

 

2.4.6基址重定位表

在目標文件中,全部符號的地址都是基於文件頭或者文件中某個位置的偏移地址。在將目標文件連接之後,在輸出的PE文件中,這些偏移地址會被轉化成相對虛擬內存地址,而且再加上一個默認內存加載位置的地址值,造成符號的基於默認內存加載位置的虛擬內存地址。基於默認內存加載位置的虛擬內存地址的計算公式爲:

符號虛擬內存地址 = 默認內存加載位置 + 相對虛擬內存地址

可執行程序默認加載到內存的0x0400000位置,而動態連接庫默認加載到內存的0x10000000位置。

當PE文件被加載到內存之後,若是該文件不能被加載到默認的內存位置,那麼在指令代碼中,全部使用絕對地址表示的符號的地址都須要被重定位。在Windows中,這一地址重定位的過程被叫作重定基地址。具體的操做過程是:在每個須要進行地址重定位的符號處,將該符號當前地址的數值上再加上一個固定的數值,這個新得到的地址值就是該符號正確的虛擬內存地址。

這個固定值的計算公式是:

固定值 = DLL當前內存加載的位置 – DLL默認內存加載位置(0x10000000)

地址重定位工做由操做系統的加載器來完成,在基地址重定位表中,記錄了每個須要進行地址重定位的符號的地址。在地址重定位的時候,加載器讀取該表中的數值,而後查找到須要進行地址重定位的符號的位置,最後修正該符號的虛擬內存地址。

在PE文件被加載到內存之後,這些文件內容是以頁爲單位存儲在內存中,每一個內存頁的大小是4KB。在基址重定位表中,數據表中的數據被分割成一個個數據塊,每個數據塊會對應一個虛擬內存頁,表示在該虛擬內存頁中的符號的地址重定位信息。

使用工具dumpbin將DemoDlld.dll中的內容導出,涉及到基址重定位表的內容以下所示:

//基址重定位表的摘要信息

SECTION HEADER #7

  .reloc name

     524 virtual size

   1C000 virtual address (1001C000 to 1001C523)

     600 size of raw data

    9A00 file pointer to raw data (00009A00 to 00009FFF)

       0 file pointer to relocation table

       0 file pointer to line numbers

       0 number of relocations

       0 number of line numbers

42000040 flags

         Initialized Data

         Discardable

         Read Only

//基址重定位表的二進制數據內容

RAW DATA #7

  1001C000: 00 10 01 00 68 00 00 00 4F 35 76 35 9F 35 A4 37 

  1001C010: AC 37 24 38 2C 38 A4 38 AC 38 30 39 41 39 49 39

  1001C020: C4 39 CC 39 D8 39 C6 3A D7 3A DD 3A EE 3A FD 3A

     ………………

//如下是對二進制內容的翻譯。

BASE RELOCATIONS #7

//基址重定位表的數據塊 相對虛擬地址爲:11000,塊大小爲68,該數據塊對應一個4KB大小的內存頁

   11000 RVA,       68 SizeOfBlock

低12位偏移  類型             符號的地址  符號名稱

     54F  HIGHLOW            10019140  ?nOperTimes@@3HA (int nOperTimes)

     576  HIGHLOW            100156AE

     59F  HIGHLOW            10019004  ___security_cookie

             …….

//下一個數據塊,相對虛擬地址爲:12000,塊大小爲F4,該數據塊對應一個4KB大小的內存頁

12000 RVA,       F4 SizeOfBlock

       C  HIGHLOW            10012010

      18  HIGHLOW            1001201C

     146  HIGHLOW            10015718

     16F  HIGHLOW            10019004  ___security_cookie

            ………

基址重定位表的結構以下圖所示:

可選頭中包含了數據目錄,數據目錄的第五項數據中包含了指向了基址重定位表的指針,以及基址重定位表的大小。基址重定位表之內存頁的大小爲依據進行分塊,在每個塊中,都以IMAGE_BASE_RELOCATION類型的數據結構開頭,後面跟隨着每一個符號的基地址重定位信息。這些符號的重定位信息是一系列的WORD值。這些WORD值的高4位指出了重定位的類型,而低12位是一個地址偏移。將該地址偏移數值與數據塊的虛擬內存地址數值(即:IMAGE_BASE_RELOCATION. VirtualAddress)相加,能夠獲得該符號須要進行重定位的位置。

在基址重定位表的數據塊中,所包含的重定位信息的個數的計算公式爲:

重定位信息個數 = (塊大小 – sizeof(IMAGE_BASE_RELOCATION))/2

由於塊大小以字節爲單位表示,而重定位信息以字爲單位表示,轉化成字須要除2

重定位的類型描述以下表:

類型

描述

IMAGE_REL_BASED_ABSOLUTE (0)

這種不需操做;用於將塊按32位邊界對齊。位置應該爲0。

IMAGE_REL_BASED_HIGH (1)

重定位的高16位必須被用於被偏移量所指向的那個16位的WORD單元,此WORD是一個32位的DWORD的高位WORD。

IMAGE_REL_BASED_LOW (2)

重定位的低16位必須被用於被偏移量所指向的那個16位的WORD單元,此WORD是一個32位的DWORD的低位WORD。

IMAGE_REL_BASED_HIGHLOW (3)

重定位的所有32位必須應用於上面所說的所有32位。PE文件採用該類型。

IMAGE_REL_BASED_HIGHADJ (4)

這種修正要求一個全32位值。高16位定位於偏移量處,低16位定位在下一個數組元素(此數組元素包括在大小的域中)的偏移量處。它們兩個須要被連成一個有符號的變量。加上32位的增量。而後加上0x8000 並將有符號變量的高16位存儲在偏移量處的16位域中。」

IMAGE_REL_BASED_MIPS_JMPADDR (5)

 

IMAGE_REL_BASED_SECTION (6)

 

IMAGE_REL_BASED_REL32 (7)

 

在WinNT.h頭文件中,基址重定位表被定義爲IMAGE_BASE_RELOCATION類型,具體的定義內容以下:

typedef struct _IMAGE_BASE_RELOCATION

{

    DWORD   VirtualAddress;

    DWORD   SizeOfBlock;

//  WORD    TypeOffset[1];

} IMAGE_BASE_RELOCATION;

typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

在該定義中,各字段的詳細解釋以下:

字段名稱

類型

描述

VirtualAddress

DWORD  

數據塊的虛擬內存地址

SizeOfBlock

DWORD  

數據塊的大小

   在如下內容中,將以一個示例的形式來講明如何查找符號重定位的位置。使用工具dumpbin將DemoDlld.dll的基址重定位表的內容導出,其中一個數據塊的內容以下:

BASE RELOCATIONS #7

//基址重定位表的數據塊 相對虛擬地址爲:11000,塊大小爲68,該數據塊對應一個4KB大小的內存頁

   11000 RVA,       68 SizeOfBlock

低12位偏移  類型             符號的地址  符號名稱

     54F  HIGHLOW            10019140  ?nOperTimes@@3HA (int nOperTimes)

     576  HIGHLOW            100156AE

     59F  HIGHLOW            10019004  ___security_cookie

             …….

 

   在上面的內容中,紅色信息表示引用了符號nOperTimes處的地址須要被重定位,該引用形式必然是使用了絕對地址。

重定位地址的計算公式爲:默認加載位置 + 數據塊相對虛擬內存地址 + 偏移 = 0x10000000 + 0x11000 + 0x54F = 0x1001154F。處於虛擬內存地址0x1001154F處的地址值須要被重定位。

將DemoDlld.dll的內容導出爲彙編格式,與地址0x1001154F相關的內容以下:

?GetOperTimes@@YAHXZ:

  10011530: 55                 push        ebp

  10011531: 8B EC              mov         ebp,esp

  10011533: 81 EC C0 00 00 00  sub         esp,0C0h

  10011539: 53                 push        ebx

  1001153A: 56                 push        esi

  1001153B: 57                 push        edi

  1001153C: 8D BD 40 FF FF FF  lea         edi,[ebp-0C0h]

  10011542: B9 30 00 00 00     mov         ecx,30h

  10011547: B8 CC CC CC CC     mov         eax,0CCCCCCCCh

  1001154C: F3 AB              rep stos    dword ptr es:[edi]

  1001154E: A1 40 91 01 10     mov         eax,dword ptr [?nOperTimes@@3HA]

  10011553: 5F                 pop         edi

  10011554: 5E                 pop         esi

  10011555: 5B                 pop         ebx

  10011556: 8B E5              mov         esp,ebp

  10011558: 5D                 pop         ebp

  10011559: C3                 ret

上面紅色字體標記出了關鍵代碼行,綠色的字體是須要被重定位的地址值,該地址的當前值爲0x10019140。該值的第一個字節正好對應地址0x1001154F,這就是須要被重定位的位置。該值是符號nOperTimes在PE文件中被分配的虛擬內存地址,因爲在此處使用了絕對地址的形式,因此當PE文件被加載到內存之後,該符號的地址須要被重定位。

假設DemoDlld.dll被加載到了內存位置爲:0x20000000,那麼該地址值將被修正爲:0x20000000 – 0x10000000 + 0x10019140 = 0x20019140。

2.4.7各數據結構之間的關係

在PE文件中,各個數據結構之間的關係以下圖所示:

相關文章
相關標籤/搜索