PE文件格式(一)

Matt Pietrek March 1994
這篇文章來自 MicroSoft 系統期刊,1994 年 3 月。版權所有,? 1994 Miller Freeman,Inc.保留所有權利!未經 Miller Freeman同意,這篇文章的任何部分不得以任何形式抄襲(除了在論文中或評論中以摘要引用)。
一個操作系統的可執行文件格式在很多方面是這個系統的一面鏡子。雖然學習一個可執行文件格式通常不是一個程序員的首要任務,但是你可以從這其中學到大量的知識。在這篇文章中,我會給出 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(見表1),我想提供一些比DUMPBIN更加可理解的輸出。雖然它沒有反彙編器以及和LIB庫文件一起工作,它在其他方面和DUMPBIN是一樣的,並且加入了一些新的特性來使它值得被認同。它的源代碼在任何一個MSJ電子公報版上都可以找到,所有我不打算在這裏把他全部列出。作爲代替,我展示一些從PEDUMP得到的示例輸出來闡明我爲它們描述的概念。
譯註:--說實話,我從這這份代碼中幾乎唯一學到的東西就是"如何處理命令行",其它的都沒學到。
表 1 PEDUMP.C
file://--------------------
// PROGRAM: PEDUMP
// FILE:    PEDUMP.C
// AUTHOR:  Matt Pietrek - 1993
file://--------------------
#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.C
BOOL 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 routine
void 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 );
    }
    else
        printf("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++ )
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章