pe檔案

pe檔案

PE檔案的全稱是Portable Executable,意為可移植的可執行的檔案,常見的EXE、DLL、OCX、SYS、COM都是PE檔案,PE檔案是微軟Windows作業系統上的程式檔案(可能是間接被執行,如DLL)

基本介紹

  • 中文名:pe檔案
  • 外文名:Portable Executable
  • 全稱:Portable Executable
  • 意為:可移植的執行體
  • 作業系統:Windows
定義,相關概念,PE首部,檔案格式,

定義

一個作業系統的執行檔格式在很多方面是這個系統的一面鏡子。雖然學習一個執行檔格式通常不是一個程式設計師的首要任務,但是你可以從這其中學到大量的知識。在這篇文章中,我會給出 Microsoft 的所有基於win32系統(如winnt,win9x)的可移植可執行(PE)檔案格式的詳細介紹。在可預知的未來,包括Windows2000, PE檔案格式在 MicroSoft 的作業系統中扮演一個重要的角色。如果你在使用 Win32 或 Winnt ,那么你已經在使用 PE 檔案了。甚至你只是在 Windows3.1 下使用 Visual C++編程,你使用的仍然是 PE 檔案(Visual C++ 的 32 位MS-DOS擴展組件用這個格式)。簡而言之,PE 格式已經普遍套用,並且在不短的將來仍是不可避免的。現在是時候找出這種新的執行檔格式為作業系統帶來的東西了。
執行緒局部變數
我最後不會讓你盯住無窮無盡的十六進制Dump,也不會詳細討論頁面的每一個單獨的位的重要性。代替的,我會向你介紹包含在 PE 檔案中的概念,並且將他們和你每天都遇到的東西聯繫起來。比如,執行緒局部變數的概念,如下所述:
declspec(thread) int i;
我快要發瘋了,直到我發現它在執行檔中實現起來是如此的簡單並且優雅。既然你們中的許多人都有使用 16 Windows 的背景,我將把 Win32 PE 檔案的構造追溯到和它等價的16 位 NE 檔案。
除了一個不同的執行檔格式, MicroSoft 還引入了一個用它的編譯器彙編器生成的新的目標模組格式。這個新的 OBJ 檔案格式有許多和PE 檔案共同的東東。我做了許多無用功去查找這個新的OBJ 檔案格式的文檔。所以我以自己的理解對它進行解析,並且,在這裡,除了 PE 檔案,我會描述它的一部分。
微軟系統工具
大家都知道,Windows NT繼承了 VAX? VMS? 和 UNIX? 的傳統。許多 Windows NT 的創始人在進入微軟前都在這些平台上進行設計和編碼。當他們開始設計 Windows NT 時,很自然的,為了最小化項目啟動時間,他們會使用以前寫好的並且已經測試過的工具。用這些工具生成的並且工作的可執行和 OBJ 檔案格式叫做 COFF (Common Object File Format 的首字母縮寫)。COFF 的相對年齡可以用八進制的域來指定。COFF 本身是一個好的起點,但是需要擴展到一個現代作業系統如 Windows 95 和 Windows NT 的需要。這個更新的結果就是(PE格式)可移植執行檔格式。它被稱為"可移植的"是因為在所有平台(如x86,Alpha,MIPS等等)上實現的WindowsNT 都使用相同的執行檔格式。當然了,也有許多不同的東西如二進制代碼的CPU指令。重要的是作業系統的裝入器和程式設計工具不需要為任何一種CPU完全重寫就能達到目的。
MicroSoft 拋棄現存的32位工具和執行檔格式的事實證實了他們想讓 WindowsNT 升級並且運行的更快的決心。為16位Windows編寫的虛擬設備驅動程式用一種不同的32位檔案布局--LE檔案格式--WindowsNT出現很早以前就存在了。比這更重要的是對 OBJ 檔案的替換!在 WindowsNT 的 C編譯器以前,所有的微軟編譯器都用 Intel 的 OMF ( Object Module Format ) 規範。就像前面提到的,MicroSoft 的 Win32編譯器生成 COFF 格式的 OBJ 檔案。一些微軟的競爭者,如 Borland 和 Symentec ,選擇放棄了 COFF 格式並堅持 Intel 的 OMF檔案格式。這樣的結果是製作 OBJ 和 LIB 的公司為了使用多個不同的編譯器,不得不為每個不同的編譯器分發這些庫的不同版本(如果他們不這么做)。
PE 檔案格式在 winnt.h 頭檔案中文檔化了(用最不精確的語言)!大約在 winnt.h 的中間部分標題為"Image Format"的一個塊。在把 MS-DOS 的 MZ檔案頭和 NE 檔案頭移入新的PE檔案頭之前,這個塊就開始於一個小欄。WINNT.H提供PE檔案用到的生鮮數據結構的定義,但只有很少有助於理解這些數據結構和標誌變數的注釋。不管誰為PE檔案格式寫出這樣的頭檔案都肯定是一個信徒無疑(突然持續地冒出Michael J. O'Leary的名字來)。描述名字,連同深嵌的結構體和宏。當你配套winnt.h進行編碼時,類似下面這樣的表達式並不鮮見:
pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]
.VirtualAddress;
為了有助於邏輯的理解這些winnt.h中的信息,閱讀可移植可執行和公共對象檔案格式的規格說明,這些在MSDN既看光碟中是可用的,一直包括到2001年8月。
現在讓我們轉換到COFF格式的OBJ檔案的主體上來,WINNT.H包括COFF OBJ和LIB的結構化定義和類型定義。不幸的是,我還沒有找到上面提到的執行檔格式的類似文檔。既然PE檔案和COFF OBJ檔案是如此的相似,我決定是時間把這些檔案帶到重點上來,並且把它們也文檔化。僅僅讀過了關於PE檔案的組成,你自己也想Dump一些PE檔案來看這些概念。如果你用微軟基於32位WINDOWS的開發工具,DUMPBIN 程式可以將PE檔案和COFF OBJ/LIB檔案轉化為可讀的形式。在所有的PEDump器中,DUMPBIN是最容易理解的。它恰好有一些很好的選項來反彙編它正解析的檔案的代碼塊,Borland用戶可以使用tdump來瀏覽PE檔案,但tdump不能解析 COFF OBJ/LIB 檔案。這不是一個重要的東西因為Borland的編譯器首先就不生成 COFF 格式的OBJ檔案。
我寫了一個PE和COFF OBJ 檔案的Dump程式--PEDUMP,我想提供一些比DUMPBIN更加可理解的輸出。雖然它沒有反彙編器以及和LIB庫檔案一起工作,它在其他方面和DUMPBIN是一樣的,並且加入了一些新的特性來使它值得被認同。它的原始碼在任何一個MSJ電子公報版上都可以找到,所有我不打算在這裡把他全部列出。作為代替,我展示一些從PEDUMP得到的示例輸出來闡明我為它們描述的概念。
譯註:--說實話,我從這份代碼中幾乎唯一學到的東西就是"如何處理命令行",其它的都沒學到。
程式代碼
表 1 PEDUMP.C
file://--------------------/
// PROGRAM: PEDUMP// FILE: PEDUMP.C// AUTHOR: Matt Pietrek - 1993file://--------------------/#include <windows.h>#include <stdio.h>#include "objdump.h"#include "exedump.h"#include "extrnvar.h"// Global variables set here, and used in EXEDUMP.C and OBJDUMP.CBOOL fShowRelocations = FALSE;BOOL fShowRawSectionData = FALSE;BOOL fShowSymbolTable = FALSE;BOOL fShowLineNumbers = FALSE;char HelpText[] ="PEDUMP - Win32/COFF .EXE/.OBJ file dumper - 1993 Matt Pietrek\n\n""Syntax: PEDUMP [switches] filename\n\n"" /A include everything in dump\n"" /H include hex dump of sections\n"" /L include line number information\n"" /R show base relocations\n"" /S show symbol table\n";// Open up a file, memory map it, and call the appropriate dumping routinevoid DumpFile(LPSTR filename)HANDLE hFile;HANDLE hFileMapping;LPVOID lpFileBase;PIMAGE_DOS_HEADER dosHeader;hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);if ( hFile = = INVALID_HANDLE_VALUE ){ printf("Couldn't open file with CreateFile()\n");return; }hFileMapping = CreateFileMapping(hFile, NULL,PAGE_READONLY, 0, 0, NULL);if ( hFileMapping = = 0 ){CloseHandle(hFile);printf("Couldn't open file mapping with CreateFileMapping()\n");return;lpFileBase = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);if ( lpFileBase = = 0 )CloseHandle(hFileMapping);CloseHandle(hFile);printf("Couldn't map view of file with MapViewOfFile()\n");return;printf("Dump of file %s\n\n", filename);dosHeader = (PIMAGE_DOS_HEADER)lpFileBase;if ( dosHeader->e_magic = = IMAGE_DOS_SIGNATURE ){ DumpExeFile( dosHeader ); }else if ( (dosHeader->e_magic = = 0x014C) // Does it look like a i386&& (dosHeader->e_sp = = 0) ) // COFF OBJ file???// The two tests above aren't what they look like. They're// really checking for IMAGE_FILE_HEADER.Machine = = i386 (0x14C)// and IMAGE_FILE_HEADER.SizeOfOptionalHeader = = 0;DumpObjFile( (PIMAGE_FILE_HEADER)lpFileBase );elseprintf("unrecognized file format\n");UnmapViewOfFile(lpFileBase);CloseHandle(hFileMapping);CloseHandle(hFile);// process all the command line arguments and return a pointer to// the filename argument.PSTR ProcessCommandLine(int argc, char *argv[])int i;for ( i=1; i < argc; i++ )strupr(argv);// Is it a switch character?if ( (argv[0] = = '-') || (argv[0] = = '/') )if ( argv[1] = = 'A' ){ fShowRelocations = TRUE;fShowRawSectionData = TRUE;fShowSymbolTable = TRUE;fShowLineNumbers = TRUE; }else if ( argv[1] = = 'H' )fShowRawSectionData = TRUE;else if ( argv[1] = = 'L' )fShowLineNumbers = TRUE;else if ( argv[1] = = 'R' )fShowRelocations = TRUE;else if ( argv[1] = = 'S' )fShowSymbolTable = TRUE;else // Not a switch character. Must be the filename{ return argv; }int main(int argc, char *argv[])PSTR filename;if ( argc = = 1 ){ printf( HelpText );return 1; }filename = ProcessCommandLine(argc, argv);if ( filename )DumpFile( filename );return 0;}

相關概念

MODULE標識模組
讓我們複習一下幾個透過PE檔案的設計了解到的基本概念。我用術語"MODULE"來表示一個執行檔或一個DLL載入記憶體的代碼(CODE)、數據(DATA)、資源(RESOURCES),除了代碼和數據是你的程式直接使用的,一個模組還可以由WINDOWS用來確定數據和代碼載入的位置的支撐數據結構組成。在16位WINDOWS中,這些支撐數據結構在模組資料庫(用一個HMODULE來指示的段)中。在WIN32裡面,這些數據結構在PE檔案頭中,這些我將會簡要地解釋一下。
PE檔案的兩因素
關於PE檔案最重要的是,磁碟上的執行檔和它被WINDOWS調入記憶體之後是非常相像的。WINDOWS載入器不必為從磁碟上載入一個檔案而辛辛苦苦創建一個進程。載入器使用記憶體映射檔案機制來把檔案中相似的塊映射到虛擬空間中。用一個構造式的分析模型,一個PE檔案類似一個預製的屋子。它本質上開始於這樣一個空間,這個空間後面有幾個把它連到其餘空間的機件(就是說,把它聯繫到它的DLL上,等等)。這對PE格式的DLL是一樣容易套用的。一旦這個模組被載入,Windows 就可以有效的把它和其它記憶體映射檔案同等對待。
和16位Windows不同的是。16位NE檔案的載入器讀取檔案的一部分並且創建完全不同的數據結構在記憶體中表示模組。當數據段或者代碼段需要載入時,載入器必須從全局堆中新申請一個段,從執行檔中找出生鮮數據,轉到這個位置,讀入這些生鮮數據,並且要進行適當的修正。除此而外,每個16位模組都有責任記住當前它使用的所有段選擇器,而不管這個段是否被丟棄了,如此等等。
對Win32來講,模組所使用的所有代碼,數據,資源,導入表,和其它需要的模組數據結構都在一個連續的記憶體塊中。在這種形勢下,你只需要知道載入器把執行檔映射到了什麼地方。通過作為映像的一部分的指針,你可以很容易的找到這個模組所有不同的塊。
相對虛擬地址
另一個你需要知道的概念是相對虛擬地址(RVA)。PE檔案中的許多域都用術語RVA來指定。一個RVA只是一些項目相對於檔案映射到記憶體的偏移。比如說,載入器把一個檔案映射到虛擬地址0x10000開始的記憶體塊。如果一個映像中的實際的表的首址是0x10464,那么它的RVA就是0x464。
(虛擬地址 0x10464)-(基地址 0x10000)=RVA 0x00464
為了把一個RVA轉化成一個有用的指針,只需要把RVA值加到模組的基地址上即可。基地址是記憶體映射EXE和DLL檔案的首址,在Win32中這是一個很重要的概念。為了方便起見,WindowsNT 和 Windows9x用模組的基地址作為這個模組的實例句柄(HINSTANCE)。在Win32中,把模組的基地址叫做HINSTANCE可能導致混淆,因為術語"實例句柄"來自16位Windows。一個程式在16位Windows中的每個拷貝得到它自己分開的數據段(和一個聯繫起來的全局句柄)來把它和這個程式其它的拷貝分別開來,就形成了術語"實例句柄"。在Win32中,每個程式不必和其它程式區別開來,因為他們不共享相同的地址空間。術語INSTANCE仍然保持16位windows和32位Windows之間的連續性。在Win32中重要的是你可以對任何DLL調用GetModuleHandle()得到一個指針去訪問它的組件(譯註)。
譯註
如果 dllname 為 NULL,則得到執行體自己的模組句柄。這是非常有用的,如通常編譯器產生的啟動代碼將取得這個句柄並將它作為一個參數hInstance傳給WinMain !
你最終需要理解的PE檔案的概念是"塊(Section)"。PE檔案中的一個塊和NE檔案中的一個段或者資源等價。塊可以包含代碼或者數據。和段不同的是,塊是記憶體中連續的空間,而沒有尺寸限制。當你的連線器和庫為你建立,並且包含對作業系統非常重要的信息的其它的數據塊時,這些塊包含你的程式直接聲明和使用的代碼或數據。在一些PE格式的描述中,塊也叫做對象。術語對象有如此多的涵義,以至於只能把代碼和數據叫做"塊"。

PE首部

和其它執行檔格式一樣,PE檔案在眾所周知的地方有一些定義檔案其餘部分面貌的域。首部就包含這樣像代碼和數據的位置和尺寸的地方,作業系統要對它進行干預,比如初始堆疊大小,和其它重要的塊的信息,我將要簡短的介紹一下。和微軟其它可執行格式相比,主要的首部不是在檔案的最開始。典型的PE檔案最開始的數百個位元組被DOS殘留部分占用。這個殘留部分是一個可以列印如"這個程式不能在DOS下運行!"這類信息的小程式。所以,你在一個不支持Win32的系統中運行這個程式,便可以得到這類錯誤信息。當載入器把一個Win32程式映射到記憶體,這個映射檔案的第一個位元組對應於DOS殘留部分的第一個位元組。那是無疑的。和你啟動的任一個基於Win32 的程式一起,都有一個基於DOS的程式連帶被載入。
和微軟的其它可執行格式一樣,你可以通過查找它的起始偏移來得到真實首部,這個偏移放在DOS殘留首部中。WINNT.H頭檔案包含了DOS殘留程式的數據結構定義,使得很容易找到PE首部的起始位置。e_lfanew 域是PE真實首部的偏移。為了得到PE首部在記憶體中的指針,只需要把這個值加到映像的基址上即可。
file://忽/略類型轉化和指針轉化 ...
pNTHeader = dosHeader + dosHeader->e_lfanew;
一旦你有了PE主首部的指針,遊戲就可以開始了!PE主首部是一個IMAGE_NT_HEADERS的結構,在WINNT.H中定義。這個結構由一個雙字(DWORD)和兩個子結構組成,布局如下:
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader;
標誌域用ASCII表示就是"PE"。如果在DOS首部中用了e_lfanew域,你得到一個NE標誌而不是PE,那么這是16位NE檔案。同樣的,在標誌域中的LE表示這是一個Windows3.x 的虛擬設備驅動程式(VxD)。LX表示這個檔案是OS/2 2.0檔案。
PE DWORD標誌後的是結構 IMAGE_FILE_HEADER 。這個域只包含這個檔案最基本的信息。這個結構表現為並未從它的原始COFF實現更改過。除了是PE首部的一部分,它還表現在微軟Win32編譯器生成的COFF OBJ 檔案的最開始部分。IMAGE_FILE_HEADER的這個域顯示在下面:
表2 IMAGE_FILE_HEADER Fields
WORD Machine
表示CPU的類型,下面定義了一些CPU的ID
0x14d Intel i860
0x14c Intel I386 (same ID used for 486 and 586)
0x162 MIPS R3000
0x166 MIPS R4000
0x183 DEC Alpha AXP
WORD NumberOfSections
這個檔案中的塊數目。
DWORD TimeDateStamp
連線器產生這個檔案的日期(對OBJ檔案是編譯器),這個域保存的數是從1969年12月下午4:00開始到現在經過的秒數。
DWORD PointerToSymbolTable
COFF符號表的檔案偏移量。這個域只用於有COFF調試信息的OBJ檔案和PE檔案,PE檔案支持多種調試信息格式,所以調試器應該指向數據目錄的IMAGE_DIRECTORY_ENTRY_DEBUG條目。
DWORD NumberOfSymbols
COFF符號表的符號數目。見上面。
WORD SizeOfOptionalHeader
這個結構後面的可選首部的尺寸。在OBJ檔案中,這個域是0。在執行檔中,這是跟在這個結構後的IMAGE_OPTIONAL_HEADER結構的尺寸。
WORD Characteristics
關於這個檔案信息的標誌。一些重要的域如下:
0x0001 這個檔案中沒有重定位信息
0x0002 執行檔映像(不是OBJ或LIB檔案)
0x2000 檔案是動態連線庫,而非程式
其它域定義在WINNT.H中。
PE首部的第三個組成部分是一個IMAGE_OPTIONAL_HEADER型的結構。對PE檔案,這一部分當然不是"可選的"。COFF格式允許單獨實現來定義一個超出標準IMAGE_FILE_HEADER附加信息的結構。IMAGE_OPTIONAL_HEADER裡面的域是PE的實現者感到超出IMAGE_FILE_HEADER基本信息以外非常關鍵的信息。
並非 IMAGE_OPTIONAL_HEADER 的所有域都是重要的(見圖4)。比較重要,需要知道的是ImageBase 和 SubSystem 域。你可以忽略其它域的描述。
表3 IMAGE_FILE_HEADER 的域:
WORD Magic
表現為一些類別的標誌字,通常是0X010B 。
BYTE MajorLinkerVersion
BYTE MinorLinkerVersion
生成這個檔案的連線器的版本。這個數字以十進制顯示比用十六進制好。一個典型的連線器版本是2.23。
DWORD SizeOfCode
所有代碼塊的進位尺寸。通常大多數檔案只有一個代碼塊,所以這個域和 .TEXT 塊匹配。
DWORD SizeOfInitializedData
已初始化的數據組成的塊的大小(不包括代碼段)。然而,和它在檔案中的表現形式並不一致。
DWORD SizeOfUninitializedData
載入器虛擬記憶體中申請空間,但在磁碟上的檔案中並不占用空間的塊的尺寸。這些塊在程式啟動時不需要指定初值,因此術語名就是"未初始化的數據"。未初始化的數據通常在一個名叫 .bss 的塊中。
DWORD AddressOfEntryPoint
載入器開始執行這個程式的地址,即這個PE檔案的入口地址。這是一個RVA,通常在 .text 塊中。
DWORD BaseOfCode
代碼塊起始地址的RVA 。在記憶體中,代碼塊通常在PE首部之後,數據塊之前。在微軟的連線器產生的EXE檔案中,這個值通常是0x1000 。Borland 的連線器 TLINK32 也一樣,把映像第一個代碼塊的RVA和映像基址相加,填入這個域。
譯註:這個域好像一直沒有什麼用
DWORD BaseOfData
數據塊起始地址的RVA 。在記憶體中,數據塊經常在最後,在PE首部和代碼塊之後。
譯註:這個域好像也一直沒有什麼用
DWORD ImageBase
連線器創建一個執行檔時,它假定這個檔案被映射到記憶體中的一個指定的地方,這個地址就存在這個域中,假定一個載入地址可以使連線器最佳化以便節省空間。如果載入器真的把這個檔案映射到了這個地方,在運行之前代碼不需要任何改變。在為WindowsNT 創建的執行檔中,默認的ImageBase 是0x10000。對DLL,默認是0x40000。在Window95中,地址0x10000不能用來載入32位EXE檔案,因為這個區域在一個被所有進程共享的線性地址空間中。因此,微軟把Win32執行檔的默認基址改為0x40000,假定基址為0x10000 的老程式坐在Windows95 中需要更長的載入時間,這是因為載入器需要重定位基址。
譯註:這個域即"Prefered Load Address",如果沒有什麼意外,這就是該PE檔案載入記憶體後的地址。
DWORD SectionAlignment
映射到記憶體中時,每個塊都必須保證開始於這個值的整數倍。為了分頁的目的,默認的SectionAlignment 是 0x1000。
DWORD FileAlignment
在PE檔案中,組成每個塊的生鮮數據必須保證開始於這個值的整數倍。默認值是0x200 位元組,也許是為了保證塊都開始於一個磁碟扇區(一個扇區通常是 512 位元組)。這個域和NE檔案中的段/資源對齊(segment/resource alignment)尺寸是等價的。和NE檔案不同的是,PE檔案通常沒有數百個的塊,所以,為了對齊而浪費的通常空間很少。
WORD MajorOperatingSystemVersion
WORD MinorOperatingSystemVersion
這個程式運行需要的作業系統的最小版本號。這個域有點含糊,因為Subsystem 域(後面將會說到)可以提供類似的功能。這個域在到目前為止的Win32中默認是1.0。
WORD MajorImageVersion
WORD MinorImageVersion
一個可由用戶定義的域。這允許你有不同的EXE和DLL版本。你可以通過連結器的 /version 選項設定這個域的值。例如:"link /version:2.0 myobj.obj"。
WORD MajorSubsystemVersion
WORD MinorSubsystemVersion
這個程式運行需要的最小子系統版本號。這個域的一個典型值是3.10 (表示WindowsNT 3.1)。
DWORD Reserved1
通常是 0 。
DWORD SizeOfImage
載入器必須關心的這個映像所有部分的大小總和。是從映像的開始到最後一個塊結尾這段區域的大小。最後一個塊結尾按SectionAlignment進位。
譯註:這個很重要,可以大,但不可以小!
DWORD SizeOfHeaders
PE首部和塊表的大小。塊的實際數據緊跟在所有首部組件之後。
DWORD CheckSum
這個檔案的CRC校驗和。在微軟可執行格式中,這個域被忽略並且置為0 。這個規則的一個例外情況是信任服務,這類EXE檔案必須有一個合法的校驗和。
WORD Subsystem
執行檔的用戶界面使用的子系統類型。WINNT.H 定義了下面這些值:
NATIVE 1 不需要子系統(比如設備驅動
WINDOWS_GUI 2 在Windows圖形用戶界面子系統下運行
WINDOWS_CUI 3 在Windows字元子系統下運行(控制台程式
OS2_CUI 5 在OS/2字元子系統下運行(僅對OS/2 1.x)
POSIX_CUI 7 在 Posix 字元子系統下運行
WORD DllCharacteristics
指定在何種環境下一個DLL的初始化函式(比如DllMain)將被調用的標誌變數。這個值經常被置為0 。但是作業系統在下面四種情況下仍然調用DLL的初始化函式。
下面的值定義為:
1 DLL第一次載入到進程中的地址空間中時調用
2 一個執行緒結束時調用
4 一個執行緒開始時調用
8 退出DLL時調用
DWORD SizeOfStackReserve
為初始執行緒保留的虛擬記憶體總數。然而並不是所有這些記憶體都被提交(見下一個域)。這個域的默認值是0x100000(1Mbytes)。如果你在CreateThread 中把堆疊尺寸指定為 0 ,結果將是用這個相同的值(0x10000)。
DWORD SizeOfStackCommit
開始提交的初始執行緒堆疊總數。對微軟的連線器,這個域默認是0x1000位元組(一頁),TLINK32 是兩頁。
DWORD SizeOfHeapReserve
為初始進程的堆保留的虛擬記憶體總數。這個堆的句柄可以用GetPocessHeap 得到。並不是所有這些記憶體都被提交(見下一個域)。
DWORD SizeOfHeapCommit
開始為進程堆提交的記憶體總數。默認是一頁。

檔案格式

檔案的含義
PE檔案的意思是Portable Executable(可移植,可執行),它是win32執行檔的標準格式.它的一些特性繼承unix的COFF檔案格式,同時保留了與舊版MS-DOS和WINDOWS的兼容.其可移植可執行意味著是跨win32平台的.
檔案的層次結構
PE檔案最前面緊隨DOS MZ檔案頭的是一個DOS執行檔(Stub).這使得PE檔案成為一個合法的MS-DOS執行檔.DOS MZ檔案頭後面是一個32位的PE檔案標誌0x50450000(IMAGE_NT_SIGNATURE),即PE00.接下來的是PE的映像檔案頭,包含的信息有該程式的運行平台,有多少個節,檔案連結的時間,檔案的命名格式.後面還緊跟一個可選映像頭,包含PE檔案的邏輯分布信息,程式載入信息,開始地址,保留的堆疊數量,數據段大小等.可選頭還有一個重要的域,稱為:數據目錄表"的數組,表的每一項都是指向某一節的指針.可選映像頭後面緊跟的是節表和節.節通過節表來實現索引.實際上,節的內容才是真正執行的數據和程式.每一個節都有相關的標誌.每一個節會被一個或多個目錄表指向,目錄表可通過可選頭的"數據目錄表"的入口找到.就像輸出函式表或基址重定位表.也存在沒有目錄表指向的節.
檔案層次解釋
A.DOS STUB和DOS頭
DOS插樁程式在大多數情況下由彙編器/編譯器自動產生.通常它調用INT 21H服務9來顯示上述字元串.可以通過IMAGE_DOS_HEADER結構來識別一個合法的DOS頭.這個結構的頭兩個位元組肯定是"MZ".可通過該結構的e_lfanew成員來找到PE檔案的開始標誌.MS-DOS頭部占據了PE檔案的頭64個位元組.在微軟的WINNT.H中可以找到其內容結構的描述.
檔案頭
在DOS STUB後是PE檔案頭(PE header).PE檔案頭是PE相關結構IMGAE_NT_HEADERS的簡稱,即NT映像頭,存放PE整個檔案信息發布的重要欄位,包含了PE裝載器用到的重要域.執行體在作業系統中執行時,PE裝載器將從DOS MZ頭中找到PE頭檔案的起始偏移量e_lfanew,從而跳過DOS STUB直接定位真正的PE檔案.它由3部分組成:
(1)PE檔案標誌(4H位元組)
PE檔案標誌0x50450000即PE00,標誌著NT映像頭的開始,也是PE檔案中與windows有關內容的開始.
(2)映像檔案(14H位元組)
是NT映像檔案的主要部分,包含PE檔案的基本信息
(3)可選映像頭
包含PE檔案的邏輯分布信息.
C.節表
節表其實是緊跟NT映像檔案的一個結構數組.其成員數目由映像檔案頭結構NumberOFSectios域的值來決定.
D.節
PE檔案的真正內容劃分為塊,稱之為節.節的劃分基於各組數據的共同屬性.惟有節的屬性設定決定了節的特性和功能.典型的windows NT應用程式可以具有9個節:.texr,.bss.rdata,.data,.rsrc,edata,idata,pdata,.debug
判斷一個檔案是否為PE檔案
var //檢測指定檔案是否有效PE檔案
PEDosHead: TImageDosHeader;
PENTHead: TImageNtHeaders;
m_file: integer;
begin
Result := False;
m_file := FileOpen(filename, fmOpenRead or fmShareDenyNone); //唯讀和其它任意
if m_File > 0 then
try
FileSeek(m_file, 0, soFromBeginning); //將指針挪至檔案頭
FileRead(m_file, PEDosHead, SizeOf(PEDosHead)); //讀PEDosHead結構
FileSeek(m_file, PEDosHead._lfanew, soFromBeginning); //將指針挪至_lfanew
FileRead(m_file, PENTHead, SizeOf(PENTHead)); //讀PENTHead結構
finally
FileClose(m_file);
end;
if (PENTHead.Signature = IMAGE_NT_SIGNATURE) then //檢驗檔案頭部第一個字的值是否等於 IMAGE_DOS_SIGNATURE
Result := True;
end;
pe檔案結構圖

相關詞條

熱門詞條

聯絡我們