久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

【翻譯】“PE文件格式”1.9版 完整譯文(附注釋)

 昵稱18225 2007-02-04
原著:Bernd. Luevelsmeyer                              
翻譯:ah007

[注意:本譯文的所有大小標(biāo)題序號都是譯者添加,以方便大家閱讀,。圓圈內(nèi)的數(shù)字是注釋的編號,,其中注釋②譯自微軟的《PECOFF規(guī)范》,其它譯自網(wǎng)絡(luò),。----譯者]


一,、前言(Preface)
------------------

PE(“portable executable”,可移植的可執(zhí)行文件)文件格式,,是微軟WindwosNT,Windows95和Win32子集①中的可執(zhí)行的二進(jìn)制文件的格式,;在WindowsNT中,驅(qū)動程序也是這種格式,。它還能被應(yīng)用于各種目標(biāo)文件②和庫文件中,。

這種文件格式是由微軟設(shè)計(jì)的,并于1993年被TIS(tool interface standard,工具接口標(biāo)準(zhǔn))委員會(由Microsoft,Intel,Borland,Watcom,IBM,等等組成)所批準(zhǔn),,它明顯的基于COFF文件格式的許多知識,。COFF(“common object file fromat”,通用目標(biāo)文件格式)是應(yīng)用于好幾種UNIX系統(tǒng)③和VMS④系統(tǒng)中的目標(biāo)文件和可執(zhí)行文件的格式,。

Win32 SDK⑤中包含一個名叫<winnt.h>的頭文件,其中含有很多用于PE格式的#define和typedef定義,。我將逐步地提到其中的很多結(jié)構(gòu)成員名字和#define定義,。

你也可能發(fā)現(xiàn)DLL文件“imagehelp.dll”很有用途,它是WindowNT的一部分,,但其書面文件卻很缺乏,。它的一些功用在“Developer Network”(開發(fā)者網(wǎng)絡(luò))中有所描述。



二,、總覽(General Layout)
-------------------------

在一個PE文件的開始處,,我們會看到一個MS-DOS可執(zhí)行體(英語叫“stub”,意為“根,存根”),;它使任何PE文件都是一個有效的MS-DOS可執(zhí)行文件,。

在DOS-根之后是一個32位的簽名以及魔數(shù)0x00004550 (IMAGE_NT_SIGNATURE)(意為“NT簽名”,也就是PE簽名,;十六進(jìn)制數(shù)45和50分別代表ASCII碼字母E和P----譯者注)。

之后是文件頭(按COFF格式),,用來說明該二進(jìn)制文件將運(yùn)行在何種機(jī)器之上,、分幾個區(qū)段、鏈接的時間,、是可執(zhí)行文件還是DLL,、等等。(本文中可執(zhí)行文件和DLL文件的區(qū)別在于:DLL文件不能被啟動,,但能被別的二進(jìn)制文件使用,,而一個二進(jìn)制文件則不能鏈接到另一個可執(zhí)行文件。)

那些之后,,是可選頭(盡管它一直都存在,,卻仍被稱作“可選”----因?yàn)镃OFF文件格式僅為庫文件使用一個“可選頭”,卻不為目標(biāo)文件使用一個“可選頭”,,這就是為什么它被稱為“可選”的原因),。它會告訴我們該二進(jìn)制文件怎樣被載入的更多信息:開始的地址呀、保留的堆棧數(shù)呀,、數(shù)據(jù)段的大小呀,、等等。

可選頭的一個有趣的部分是尾部的“數(shù)據(jù)目錄”數(shù)組,;這些目錄包含許多指向各“節(jié)”數(shù)據(jù)的指針,。例如:如果一個二進(jìn)制文件擁有一個輸出目錄,那么你就會在數(shù)組成員“IMAGE_DIRECTORY_ENTRY_EXPORT”(輸出目錄項(xiàng))中找到一個指向那個目錄的指針,,而該指針指向文件中的某節(jié),。

跟在各種頭后面我們就發(fā)現(xiàn)各個“節(jié)”了,,它們都由“節(jié)頭”引導(dǎo)。本質(zhì)上講,,各節(jié)中的內(nèi)容才是你執(zhí)行一個程序真正需要的東西,,所有頭和目錄這些東西只是為了幫助你找到它們。
每節(jié)都含有和對齊,、包含什么樣的數(shù)據(jù)(如“已初始化數(shù)據(jù)”等等),、是否能共享等有關(guān)的一些標(biāo)記,還有就是數(shù)據(jù)本身,。大多數(shù)(并非所有)節(jié)都含有一個或多個可通過可選頭的“數(shù)據(jù)目錄”數(shù)組中的項(xiàng)來參見的目錄,,如輸出函數(shù)目錄和基址重定位目錄等。無目錄形式的內(nèi)容有:例如“可執(zhí)行代碼”或“已初始化數(shù)據(jù)”等,。

    +-------------------+
    | DOS-stub          |    --DOS-頭
    +-------------------+
    | file-header       |    --文件頭
    +-------------------+
    | optional header   |    --可選頭
    |- - - - - - - - - -|
    |                   |
    | data directories  |    --數(shù)據(jù)目錄
    |                   |
    +-------------------+
    |                   |
    | section headers   |     --節(jié)頭
    |                   |
    +-------------------+
    |                   |
    | section 1         |     --節(jié)1
    |                   |
    +-------------------+
    |                   |
    | section 2         |     --節(jié)2
    |                   |
    +-------------------+
    |                   |
    | ...               |
    |                   |
    +-------------------+
    |                   |
    | section n         |     --節(jié)n
    |                   |
    +-------------------+



三,、DOS-根和簽名(DOS-stub and Signature)
-----------------------------------------

DOS-根的概念很早從16位windows的可執(zhí)行文件(當(dāng)時是“NE”格式⑥)時就廣為人知了。根原來是用于OS/2⑦系統(tǒng)的可執(zhí)行文件的,,也用于自解壓檔案文件和其它的應(yīng)用程序,。對于PE文件來說,它是一個總是由大約100個字節(jié)所組成的和MS-DOS 2.0兼容的可執(zhí)行體,,用來輸出象“this program needs windows NT”之類的錯誤信息,。

你可以通過確認(rèn)DOS-頭部分是否為一個IMAGE_DOS_HEADER(DOS頭)結(jié)構(gòu)來認(rèn)出DOS-根,它的前兩個字節(jié)必須為連續(xù)的兩個字母“MZ”(有一個#define IMAGE_DOS_SIGNATURE的定義是針對這個WORD單元的),。

你可以通過跟在后面的簽名來將一個PE二進(jìn)制文件和其它含有根的二進(jìn)制文件區(qū)分開來,,跟在后面的簽名可由頭成員‘e_lfanew‘(它是從字節(jié)偏移地址60處開始的,有32字節(jié)長)所設(shè)定的偏移地址找到,。對于OS/2系統(tǒng)和Windows系統(tǒng)的二進(jìn)制文件來說,,簽名是一個16位的word單元;對于PE文件來說,,它是一個按照8位字節(jié)邊界對齊的32位的longword單元,,并且IMAGE_NT_SIGNATURE(NT簽名)的值已由#defined定義為0x00004550(即字母“PE/0/0”----譯者)。



四,、文件頭(File Header)
-------------------------

要到達(dá)IMAGE_FILE_HEADER(文件頭)結(jié)構(gòu),,請先確認(rèn)DOS-頭“MZ”(起始的2個字節(jié)),然后找出DOS-根的頭部的成員“e_lfanew”,,并從文件開始處跳過那么多的字節(jié),。在核實(shí)你在那里找到的簽名后,IMAGE_FILE_HEADER(文件頭)結(jié)構(gòu)的文件頭就緊跟其后開始了,,下面我們將從頭至尾的介紹其成員,。

1)第一個成員是“Machine(機(jī)器)”,一個16位的值,,用來指出該二進(jìn)制文件預(yù)定運(yùn)行于什么樣的系統(tǒng),。已知的合法的值有:

     IMAGE_FILE_MACHINE_I386 (0x14c)
            Intel 80386 處理器或更高

     0x014d
            Intel 80386 處理器或更高

     0x014e
            Intel 80386 處理器或更高

     0x0160          
             R3000 (MIPS⑧)處理器,,大尾⑨

     IMAGE_FILE_MACHINE_R3000 (0x162)
             R3000 (MIPS)處理器,小尾

     IMAGE_FILE_MACHINE_R4000 (0x166)
             R4000 (MIPS)處理器,,小尾

     IMAGE_FILE_MACHINE_R10000 (0x168)
             R10000 (MIPS)處理器,,小尾

     IMAGE_FILE_MACHINE_ALPHA (0x184)
             DEC Alpha AXP⑩處理器

     IMAGE_FILE_MACHINE_POWERPC (0x1F0)
             IBM Power PC,小尾

2)然后是“NumberOfSections(節(jié)數(shù))”成員,16位的值,。它是緊跟在頭后面的節(jié)的數(shù)目,。我們以后將討論節(jié)的問題。

3)下一個成員是時間戳“TimeDateStamp”(32位),,用來給出文件建立的時間,。即使它的“官方”版本號沒有改變,你也可通過這個值來區(qū)分同一個文件的不同版本,。(除了同一個文件的不同版本之間必須唯一,,時間戳的格式?jīng)]有明文規(guī)定,但似乎是按照UTC時間“從1970年1月1日00:00:00算起的秒數(shù)值”----也就是大多數(shù)C語言編譯器給time_t標(biāo)志使用的格式,。)
這個時間戳是用來綁定各個輸入目錄的,,我們稍后再討論它。
警告:有一些鏈接器往往將時間戳設(shè)為荒唐的值,,而不是如前所述的time_t格式的鏈接時間,。

4-5)成員“PointerToSymbolTable(符號表指針)”和成員“NumberOfSymbols(符號數(shù))”(都是32位)都用于調(diào)試信息的。我不知道該怎樣去解讀它,,并且我發(fā)現(xiàn)該指針的值總為0。

6)成員“SizeOfOptionalHeader(可選頭大?。?#8221;(16位)只是“IMAGE_OPTIONAL_HEADER(可選頭)”項(xiàng)的大小,,你能用它去驗(yàn)證PE文件結(jié)構(gòu)的正確性。

7)成員“Characteristics(特性)”是一個16位的,由許多標(biāo)志位形成的集合組成,但大多數(shù)標(biāo)志位只對目標(biāo)文件和庫文件有效,。具體如下:
    位0 IMAGE_FILE_RELOCS_STRIPPED(重定位被剝離文件) 表示如果文件中沒有重定位信息,,該位置1,這就表明各節(jié)的重定位信息都在它們各自的節(jié)中,;可執(zhí)行文件不使用該位,它們的重定位信息放在下面將要描述的“base relocation”(基址重定位)目錄中。

    位1 IMAGE_FILE_EXECUTABLE_IMAGE(可執(zhí)行映象文件) 表示如果文件是一個可執(zhí)行文件,,也即不是目標(biāo)文件或者庫文件時,置1,。如果鏈接器嘗試創(chuàng)建一個可執(zhí)行文件,,卻因?yàn)橐恍┰蚴×耍⒈4嬗诚褚员阆麓卫缭隽挎溄訒r使用,,此時此標(biāo)志位也可能置1,。

    位2 IMAGE_FILE_LINE_NUMS_STRIPPED(行數(shù)被剝離文件) 表示如果行數(shù)信息被剝除,,此位置1;此位也不用于可執(zhí)行文件,。

    位3 IMAGE_FILE_LOCAL_SYMS_STRIPPED(本地符號被剝離文件) 表示如果文件中沒有關(guān)于本地符號的信息時,,此位置1(此位也不用于可執(zhí)行文件)。

    位4 IMAGE_FILE_AGGRESIVE_WS_TRIM(強(qiáng)行工作集修剪文件) 表示如果操作系統(tǒng)被假定為:通過將正在運(yùn)行的進(jìn)程(它所使用的內(nèi)存數(shù)量)強(qiáng)行的頁清除來修剪它的工作集時,,此位置1,。如果一進(jìn)程是大部分時間處于等待,且一天中僅被喚醒一次的演示性的應(yīng)用程序之類時,,此位也應(yīng)該被置1,。
    
    位7 IMAGE_FILE_BYTES_REVERSED_LO(低字節(jié)變換文件)和 位15IMAGE_FILE_BYTES_REVERSED_HI(高字節(jié)變換文件) 表示如果一文件的字節(jié)序不是機(jī)器所預(yù)期的形式,因此它在讀入前必須調(diào)換字節(jié)時,,此位置1,。這樣做對可執(zhí)行文件是不可靠的(操作系統(tǒng)期望可執(zhí)行文件都已經(jīng)被正確地按字節(jié)排整齊了)。

    位8 IMAGE_FILE_32BIT_MACHINE(32位機(jī)器文件) 表示如果使用的機(jī)器被期望為32位的機(jī)器時,,此位置1?,F(xiàn)在的應(yīng)用程序總將此位置1;NT5系統(tǒng)可能工作不同,。

    位9 IMAGE_FILE_DEBUG_STRIPPED(調(diào)試信息被剝離文件) 表示如果文件中沒有調(diào)試信息,,此位置1。此位可執(zhí)行文件不用,。按照其它信息([6])(這里指的是參考書目中的第[6]種----譯者注),,此位被稱作“恒定”,并且當(dāng)一個映象文件只有在被裝入優(yōu)先的裝入地址才能運(yùn)行(亦即:此文件不可重定位)時,,此位置1,。
   
    位10 IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP(移動介質(zhì)文件從交換文件運(yùn)行) 表示如果一個應(yīng)用程序不可以從可移動的介質(zhì),如軟盤或CD-ROM上運(yùn)行時,,此位置1,。在這種情況下,建議操作系統(tǒng)將文件復(fù)制到交換文件并從那里執(zhí)行,。

    位11 IMAGE_FILE_NET_RUN_FROM_SWAP(網(wǎng)絡(luò)文件從交換文件運(yùn)行) 表示如果一個應(yīng)用程序不可以從網(wǎng)絡(luò)上運(yùn)行時,,此位置1。在這種情況下,,建議操作系統(tǒng)將文件復(fù)制到交換文件并從那里執(zhí)行,。

    位12 IMAGE_FILE_SYSTEM(系統(tǒng)文件) 表示如果文件是一個象驅(qū)動程序那樣的系統(tǒng)文件,此位置1,。此位可執(zhí)行文件不用,;我所見過的所有NT系統(tǒng)的驅(qū)動程序也不用。

    位13 IMAGE_FILE_DLL(DLL文件) 表示如果文件是一個DLL文件時,此位置1,。

    位14 IMAGE_FILE_UP_SYSTEM_ONLY(僅但處理器系統(tǒng)的文件) 表示如果文件不設(shè)計(jì)運(yùn)行在多處理器系統(tǒng)上(也就是說,,因?yàn)榇宋募?yán)格地依賴單一處理器的一些方式工作,所以它會發(fā)生沖突)時,,此位置1,。




五、相對虛擬地址(Relative Virtual Addresses)
---------------------------------------------

PE格式大量地使用所謂的RVA(相對虛擬地址),。一個RVA,,亦即一個“Relative Virtual Addresses(相對虛擬地址)”,是在你不知道基地址時,,被用來描述一個內(nèi)存地址的,。它是需要加上基地址才能獲得線性地址的數(shù)值?;刂肪褪荘E映象文件被裝入內(nèi)存的地址,,并且可能會隨著一次又一次的調(diào)用而變化。

例如:假若一個可執(zhí)行文件被裝入的地址是0x400000,,并且從RVA 0x1560處開始執(zhí)行,,那么有效的執(zhí)行開始處將位于0x401560地址處。假若它被裝入的地址為0x100000,,那么執(zhí)行開始處就位于0x101560地址處,。

因?yàn)镻E-文件中的各部分(各節(jié))不需要像已載入的映象文件那樣對齊,事情變得復(fù)雜起來,。例如,,文件中的各節(jié)常按照512(十六進(jìn)制的0x200----譯者注)字節(jié)邊界對齊,而已載入的映象文件則可能按照4096(十六進(jìn)制的0x1000----譯者注)字節(jié)邊界對齊,。參見下面的“SectionAlignment(節(jié)對齊)”和“FileAlignment(文件對齊)”,。


因此,為了在PE文件中找到一個特定RVA地址的信息,,你得按照文件已被載入時的那樣來計(jì)算偏移量,,但要按照文件的偏移量來跳過,。

試舉一例,,假若你已知道執(zhí)行開始處位于RVA 0x1560地址處,并且想從那里開始的代碼處反匯編,。為了從文件中找到這個地址,,你得先查明在RAM(內(nèi)存)中各節(jié)是按照4096字節(jié)對齊的,并且“.code”節(jié)是從RVA 0x1000地址處開始,,有16384字節(jié)長,;然后你才知道RVA 0x1560地址位于此節(jié)的偏移量0x560處。你還要查明在文件中那節(jié)是按512字節(jié)邊界對齊,且“.code”節(jié)在文件中從偏移量0x800處開始,,然后你就知道在文件中代碼的執(zhí)行開始處就在0x800+0x560=0xd60字節(jié)處,。

然后你反匯編它并發(fā)現(xiàn)訪問一個變量的線性地址位于0x1051d0處。二進(jìn)制文件的線性地址在裝入時將被重定位,,并常被假定使用的是優(yōu)先載入地址,。因?yàn)槟阋巡槊鲀?yōu)先載入地址為0x100000,因此我們可開始處理RVA 0x51d0了,。因數(shù)據(jù)節(jié)開始于RVA 0x5000處,且有2048字節(jié)長,,所以它處于數(shù)據(jù)節(jié)中。又因數(shù)據(jù)節(jié)在文件中開始于偏移量0x4800處,,所以該變量就可以在文件中的0x4800+0x51d0-0x5000=0x49d0處找到,。




六、可選頭(Optional Header)
----------------------------

緊跟在文件頭后面的就是IMAGE_OPTIONAL_HEADER(盡管它名叫“可選頭”,,它卻一直都在那里),。它包含有怎樣去準(zhǔn)確處理PE文件的信息。我們也將從頭至尾的介紹其成員,。

1)第一個16位的word單元叫“Magic(魔數(shù))”,,就我目前所觀察過的PE文件而言,它的值總是0x010b,。

2-3)下面2個字節(jié)是創(chuàng)建此文件的鏈接器的版本(‘MajorLinkerVersion’,,“鏈接器主版本號”和‘MinorLinkerVersion’,“鏈接器小版本號”),。這兩個值又是不可靠的,,并不能總是正確地反映鏈接器的版本號。(有好幾個鏈接器根本就不設(shè)置這個值,。)況且,,你可想象一下,你連使用的是“什么”鏈接器都不知道,,知道它的版本號又有什么作用呢,?

4-6)下面3個longword(每個32位)分別用來設(shè)定可執(zhí)行代碼的大小(“SizeOfCode”),、已初始化數(shù)據(jù)的大?。?#8220;SizeOfInitializedData”,所謂的“數(shù)據(jù)段”),、以及未初始化數(shù)據(jù)的大?。?#8220;SizeOfUninitializedData”,所謂的“bss段”),。這些值也是不可靠的(例如:數(shù)據(jù)段實(shí)際上可能會被編譯器或者鏈接器分成好幾段),,并且你可以通過查看可選頭后面的各個“節(jié)”來獲得更準(zhǔn)確的大小,。

7)下一個32位值是RVA。這個RVA是代碼入口點(diǎn)的偏移量(‘AddressOfEntryPoint’,,“入口點(diǎn)地址”),。執(zhí)行將從這里開始,它可以是:例如DLL文件的LibMain()的地址,,或者一個程序的開始代碼(這里相應(yīng)的叫main())的地址,,或者驅(qū)動程序的DriverEntry()的地址。如果你敢于“手工”裝載映象文件,,那么在你完成所有的修正和重定位后,,你可以從這個地址開始執(zhí)行你的進(jìn)程。

8-9)下兩個32位值分別是可執(zhí)行代碼的偏移值(‘BaseOfCode’,,“代碼基址”)和已初始化數(shù)據(jù)的偏移值(‘BaseOfData’,,“數(shù)據(jù)基址”),兩個都是RVA,,并且兩個對我們來說都沒有多少意義,,因?yàn)槟憧梢酝ㄟ^查看可選頭后面的各個“節(jié)”來獲得更可靠的信息。
未初始化的數(shù)據(jù)沒有偏移量,,正因?yàn)樗鼪]有初始化,,所以在映象文件中提供這些數(shù)據(jù)是沒有用處的。


10)下一項(xiàng)是個32位值,,提供整個二進(jìn)制文件包括所有頭的優(yōu)先(線性)載入地址(‘ImageBase’,,“映象文件基址”)。這是一個文件已被鏈接器重定位后的地址(總是64 KB的倍數(shù)),。如果二進(jìn)制文件事實(shí)上能被載入這個地址,,那么加載器就不用再重定位文件了,也就節(jié)省了一些載入時間,。
優(yōu)先載入地址在另一個映象文件已被先載入那個地址(“地址沖突”,,在當(dāng)你載入好幾個全部按照鏈接器的缺省值重定位的DLL文件時經(jīng)常發(fā)生)時,或者該內(nèi)存已被用于其它目的(堆棧,、malloc(),、未初始化數(shù)據(jù)、或不管是什么)時,,就不能用了,。在這些情況下,映象文件必須被載人其它的地址,,并且需要重定位(參見下面的“重定位目錄”),。如果是一個DLL文件,,這么做還會產(chǎn)生其它問題,,因?yàn)榇藭r的“綁定輸入”已不再有效,所以使用DLL的二進(jìn)制文件必須被修正----參見下面的“輸入目錄”一節(jié)。

11-12)下兩個32位值分別是RAM中的“SectionAlignment”(當(dāng)映象文件已被載入后,,意為“節(jié)對齊”)和文件中的“FileAlignment”(文件對齊),,它們都是PE文件的各節(jié)的對齊值。這兩個值通常都是32,,或者是:FileAlignment為512,,SectionAlignment為4096。節(jié)會在以后討論,。

13-14)下2個16位word單元都是預(yù)期的操作系統(tǒng)版本信息(MajorOperatingSystemVersion,“操作系統(tǒng)主版本號”)和(MinorOperatingSystemVersion,,“操作系統(tǒng)小版本號”)[它們都使用微軟自己書面確定的名字]。這個版本信息應(yīng)該為操作系統(tǒng)的版本號(如NT 或 Win95),,而不是子系統(tǒng)的版本信息(如Win32),。版本信息常常被不提供或者錯誤提供。很明顯的,,加載器并不使用它們,。

15-16)下2個16位word單元都是本二進(jìn)制文件的版本信息(‘MajorImageVersion‘“映象文件主版本號”和
‘MinorImageVersion‘“映象文件小版本號”)。很多鏈接器不正確地設(shè)定這個信息,,許多程序員也懶得提供這些,,因此即便存在這樣的信息,你最好也不要信賴它,。

17-18)下2個16位word單元都是預(yù)期的子系統(tǒng)版本信息(‘MajorSubsystemVersion‘“子系統(tǒng)主版本號”和‘MinorSubsystemVersion‘“子系統(tǒng)小版本號”),。此信息應(yīng)該為Win32或POSIX的版本信息,因?yàn)楹苊黠@的,,16位程序或OS/2程序都不是PE格式的,。子系統(tǒng)版本應(yīng)該被正確的提供,因?yàn)樗?#8220;會”被檢驗(yàn)和使用:
如果一個應(yīng)用程序是一個Win32-GUI應(yīng)用程序并運(yùn)行于NT4系統(tǒng)之上,,而且子系統(tǒng)版本“不是”4.0的話,,那么對話框就不會是以3D形式顯示,并且一些其它的特征也只會按“老式”的方式工作,,因?yàn)榇藨?yīng)用程序預(yù)期是在NT 3.51系統(tǒng)上運(yùn)行的,,而NT 3.51系統(tǒng)上只有程序管理器而沒有瀏覽器、等等,,于是NT 4.0系統(tǒng)就盡可能地仿照那個系統(tǒng)的行為來運(yùn)行程序,。

19)然后,我們便碰到32位的“Win32VersionValue”(Win32版本值),。我不清楚它有什么作用,。在我所觀察過的PE文件中,它全部都為0,。

20)下一個是32位值,,給出映象文件將要使用的內(nèi)存數(shù)量,,單位為字節(jié)(‘SizeOfImage’,“映象文件大小”),。如果是按照“SectionAlignment”對齊的,,它就是所有頭和節(jié)的長度的總和。它提示加載器,,為了載入映象文件需要多少頁,。

21)下一個是32位值,給出所有頭的總長度,,包括數(shù)據(jù)目錄和節(jié)頭(‘SizeOfHeaders’,,“頭的大小”)。同時,,它也是從文件的開頭到第一節(jié)的原始數(shù)據(jù)的偏移量,。

22)然后,我們發(fā)現(xiàn)一個32位的校驗(yàn)和(“CheckSum”),。這個校驗(yàn)和,,對于當(dāng)前的NT版本,只在映象文件是NT驅(qū)動程序時才校驗(yàn)(如果校驗(yàn)和不正確,,驅(qū)動就將裝載失?。τ谄渌亩M(jìn)制文件形式,,校驗(yàn)和不需提供并且可能為0,。計(jì)算校驗(yàn)和的算法是微軟的私產(chǎn),他們不會告訴你的,。但是,,Win32 SDK的好幾個工具都會計(jì)算和/或補(bǔ)正一個有效的校驗(yàn)和,而且imagehelp.dll中的CheckSumMappedFile()函數(shù)也會做同樣的工作,。
使用校驗(yàn)和的目的是為了防止載入無論如何都會沖突的,、已損壞的二進(jìn)制文件----況且一個沖突的驅(qū)動程序會導(dǎo)致一個BSOD錯誤,因此最好根本就不載入這樣的壞文件,。

23)然后,,就到了一個16位的word單元“Subsystem”(子系統(tǒng)),用來說明映象文件應(yīng)運(yùn)行于什么樣的NT子系統(tǒng)之上:
        IMAGE_SUBSYSTEM_NATIVE (1)
             二進(jìn)制文件不需要子系統(tǒng),。用于驅(qū)動程序,。
        
        IMAGE_SUBSYSTEM_WINDOWS_GUI (2)
             映象文件是一個Win32二進(jìn)制圖象文件。(它還是能用AllocConsole()打開一個控制臺界面,,但在開始時卻不能自動地打開,。)
             
        IMAGE_SUBSYSTEM_WINDOWS_CUI (3)
             二進(jìn)制文件是一個Win32控制臺界面二進(jìn)制文件。(它將在開始時按照缺省值打開一個控制臺,,或者繼承其父程序的控制臺,。)

        IMAGE_SUBSYSTEM_OS2_CUI (5)
              二進(jìn)制文件是一個OS/2控制臺界面二進(jìn)制文件,。(OS/2控制臺界面二進(jìn)制文件是OS/2格式,因此此值在PE文件中很少使用,。)

        IMAGE_SUBSYSTEM_POSIX_CUI (7)
              二進(jìn)制文件使用POSIX控制臺子系統(tǒng)。


Windows 95的二進(jìn)制文件總是使用Win32子系統(tǒng),,因此它的二進(jìn)制文件的合法值只有2和3,;我不知道windows 95的“原”二進(jìn)制文件是否可能(會有其它值----譯者添加,僅供參考),。

24)下一個是16位的值,,指明,如果是DLL文件,,何時調(diào)用DLL文件的入口點(diǎn)(‘DllCharacteristics’,,“DLL特性”)。此值似乎不用,;很明顯地,,DLL文件總是被通報所有的情況。
         如果位0被置1,,DLL文件被通知進(jìn)程附加(亦即DLL載入),。
         如果位1被置1,DLL文件被通知線程附加(亦即線程終止),。
         如果位2被置1,,DLL文件被通知線程附加(亦即線程創(chuàng)建)。
         如果位3被置1,,DLL文件被通知進(jìn)程附加(亦即DLL卸載),。

25-28)下4個32位值分別是:保留棧的大小(SizeOfStackReserve),、初始時指定棧大?。⊿izeOfStackCommit)、保留堆的大?。⊿izeOfHeapReserve)和指定堆大?。⊿izeOfHeapCommit)。
“保留的”數(shù)量是保留給特定目的的地址空間(不是真正的RAM),;在程序開始時,,“指定的”數(shù)量是指在RAM中實(shí)際分配的大小。如果需要的話,,“指定的”值也是指定的堆或棧用來增加的數(shù)量,。(有資料說,不管“SizeOfStackCommit”的值是多少,,棧都是按頁增加的,。我沒有驗(yàn)證過,。)
因此,舉例來說,,如一個程序的保留堆有1 MB,,指定堆為64 KB,那么啟動時堆的大小為64 KB,并且保證可以擴(kuò)大到1 MB。堆將按64 KB一塊來增加,。
“堆”在本文中是指主要(缺?。┒选H绻敢獾脑?,一個進(jìn)程可創(chuàng)建很多堆,。
棧是指第一個線程的棧(啟動main()的那個)。進(jìn)程可以創(chuàng)建很多線程,,每個線程都有自己的棧,。
DLL文件沒有自己的堆或棧,所以它們的映象文件忽略這些值,。我不知道驅(qū)動程序是否有它們自己的堆或棧,,但我認(rèn)為它們沒有。

29)堆和棧的這些描述之后,,我們就發(fā)現(xiàn)一個32位的“LoaderFlags(加載器標(biāo)志)”,,我沒有找到它的任何有用的描述。我只發(fā)現(xiàn)一篇時新的關(guān)于設(shè)置此標(biāo)志位的短文,,說設(shè)置此標(biāo)志位會在映象文件載入后自動地調(diào)用一個斷點(diǎn)或者調(diào)試器,;可似乎不正確。

30)接著我們會發(fā)現(xiàn)32位的“NumberOfRvaAndSizes(Rva數(shù)和大?。?#8221;,,它是緊隨其后的目錄的有效項(xiàng)的數(shù)目。我已發(fā)現(xiàn)此值不可靠,;你也許希望用常量IMAGE_NUMBEROF_DIRECTORY_ENTRIES(映象文件目錄項(xiàng)數(shù)目)來代替它,,或者用它們中的較小者。

NumberOfRvaAndSizes之后是一個IMAGE_NUMBEROF_DIRECTORY_ENTRIES (16)(映象文件目錄項(xiàng)數(shù)目)個IMAGE_DATA_DIRECTORY(映象文件數(shù)據(jù)目錄)數(shù)組,。這些目錄中的每一個目錄都描述了一個特定的,、位于目錄項(xiàng)后面的某一節(jié)中的信息的位置(32位的RVA,叫“VirtualAddress(虛擬地址)”)和大?。ㄒ彩?2位,,叫“Size(大小)”),。
例如,,安全目錄能在索引4中給定的RVA處發(fā)現(xiàn)并具有索引4中給定的大小。
稍后我將討論我知道其結(jié)構(gòu)的目錄。
已定義的目錄及索引有:

    IMAGE_DIRECTORY_ENTRY_EXPORT (0)
        輸出符號目錄,;大多用于DLL文件,。
        后面介紹。
    
    IMAGE_DIRECTORY_ENTRY_IMPORT (1)
        輸入符號目錄,;參見后面,。
    
    IMAGE_DIRECTORY_ENTRY_RESOURCE (2)
        資源目錄。后面介紹,。
    
    IMAGE_DIRECTORY_ENTRY_EXCEPTION (3)
        異常目錄 - 結(jié)構(gòu)和用途不詳,。
    
    IMAGE_DIRECTORY_ENTRY_SECURITY (4)
        安全目錄 - 結(jié)構(gòu)和用途不詳。
    
    IMAGE_DIRECTORY_ENTRY_BASERELOC (5)
        基址重定位表 - 參見后面,。
    
    IMAGE_DIRECTORY_ENTRY_DEBUG (6)
        調(diào)試目錄 - 內(nèi)容編譯器相關(guān),。此外, 許多編譯器將編譯信息填入代碼節(jié),,并不為此創(chuàng)建一個單獨(dú)的節(jié),。
    
    IMAGE_DIRECTORY_ENTRY_COPYRIGHT (7)
        描述字符串 - 一些隨意的版權(quán)信息之類。
    
    IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8)
        機(jī)器值 (MIPS GP) - 結(jié)構(gòu)和用途不詳,。
    
    IMAGE_DIRECTORY_ENTRY_TLS (9)
        線程級局部存儲目錄 - 結(jié)構(gòu)不詳,;包含聲明為“__declspec(thread)”的變量, 也就是每線程的全局變量。
    
    IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10)
        載入配置目錄 - 結(jié)構(gòu)和用途不詳,。
    
    IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11)
        綁定輸入目錄 - 參見輸入目錄的描述,。
    
    IMAGE_DIRECTORY_ENTRY_IAT (12)
        輸入地址表 - 參見輸入目錄的描述。

試舉一例,,如果我們在索引7中發(fā)現(xiàn)2個longword:0x12000 和 33,并且載入地址為0x10000,那么我們就知道版權(quán)信息數(shù)據(jù)位于地址0x10000+0x12000(在哪個節(jié)都有可能)處,,并且版權(quán)信息有33字節(jié)長。
如果二進(jìn)制文件中沒有使用特殊類型的目錄,,Size(大?。┖蚔irtualAddress(虛擬地址)的值就都為0。


七,、節(jié)目錄(Section directories)
---------------------------------

節(jié)由兩個主要部分組成:首先,,是一個節(jié)描述(IMAGE_SECTION_HEADER[意為“節(jié)頭”]類型的),然后是原始的節(jié)數(shù)據(jù),。因此,,我們會在數(shù)據(jù)目錄后發(fā)現(xiàn)一“NumberOfSections”個節(jié)頭組成的數(shù)組,它們按照各節(jié)的RVA排序,。

節(jié)頭包括:
1)一個IMAGE_SIZEOF_SHORT_NAME (8)(意為“短名的大小”)個字節(jié)的數(shù)組,,形成節(jié)的名稱(ASCII形式)。如果所有的8位都被用光,,該字符串就沒有0結(jié)束符,!典型的名稱象“.data”或“.text”或“.bss”形式。開頭的“.”不是必須的,,名稱也可能為“CODE”或“IAT”或類似的形式,。
請注意:并不是所有的名稱都和節(jié)中的內(nèi)容相關(guān),。一個名叫“.code”的節(jié)可能包含也可能不包含可執(zhí)行代碼;它還可能只包含輸入地址表,;它也可能包含代碼“和”地址表“和”未初始化數(shù)據(jù),。要找到節(jié)中的信息,你必須通過可選頭的數(shù)據(jù)目錄來查詢它,。既不要過分相信它的名稱,,也不要以為節(jié)的原始數(shù)據(jù)會從節(jié)的開頭就開始。

2)IMAGE_SECTION_HEADER(“節(jié)頭”)的下一個成員是一個32位的,、“PhysicalAddress(物理地址)”和“VirtualSize(虛擬大?。?#8221;組成的共用體。在目標(biāo)文件中,,它是內(nèi)容重定位到的地址,;在可執(zhí)行文件中,它是內(nèi)容的大小,。事實(shí)上,,此域似乎沒被使用;因?yàn)橛械逆溄悠鬏斎氪笮?,有的鏈接器輸入地址,,我還發(fā)現(xiàn)有一個鏈接器輸入0,而所有的可執(zhí)行文件都運(yùn)行如風(fēng),。

3)下一個成員是“VirtualAddress(虛擬地址)”,,是一個32位的值,用來保存載入RAM(內(nèi)存)后,,節(jié)中數(shù)據(jù)的RVA,。

4)然后,我們到了32位的“SizeOfRawData”(意味“原始數(shù)據(jù)大小”),,它表示節(jié)中數(shù)據(jù)被大約到下一個“FileAlignment”的整數(shù)倍時節(jié)的大小,。

5)下一個是“PointerToRawData”(意味“原始數(shù)據(jù)指針”,32位),,它特別有用,,因?yàn)樗菑奈募拈_頭到節(jié)中數(shù)據(jù)的偏移量。如果它為0,,那么節(jié)的數(shù)據(jù)就不包含在文件中,,并且要在載入時才定。

6-9)然后,,我們得到“PointerToRelocations”(意味“重定位指針”,,32位)和“PointerToLinenumbers”(意味“行數(shù)指針”,也是32位),以及“NumberOfRelocations”(意味“重定位數(shù)”,,16位)和“NumberOfLinenumbers”(意味“行數(shù)數(shù)”,,也是16位)。所以這些都是只用于目標(biāo)文件的信息,??蓤?zhí)行文件擁有一個特殊的基址重定位目錄,并且行數(shù)信息(如果真的存在的話)通常包含在有一個特殊目的的調(diào)試段中或者別的什么地方,。

10)節(jié)頭的最后一個成員是32位的“Characteristics”(意味“特性”),,它是一串描述節(jié)的內(nèi)存如何被處理的標(biāo)志:

    如果位5 IMAGE_SCN_CNT_CODE(含有代碼的節(jié))被置1,表示節(jié)中包含可執(zhí)行代碼,。
    
    如果位6 IMAGE_SCN_CNT_INITIALIZED_DATA(含有初始化數(shù)據(jù)的節(jié))被置1,,表示節(jié)中包含執(zhí)行開始前即取得已定義值的數(shù)據(jù)。換言之:文件中節(jié)的數(shù)據(jù)就是有意義的,。
    
    如果位7 IMAGE_SCN_CNT_UNINITIALIZED_DATA(含有未初始化數(shù)據(jù)的節(jié))被置1,, 表示節(jié)中包含未初始化數(shù)據(jù),并需于執(zhí)行開始前被初始化為全0,。這通常是BSS節(jié),。

    如果位9 IMAGE_SCN_LNK_INFO(鏈接器信息節(jié))被置1,, 表示節(jié)中不包含映象數(shù)據(jù),,只有一些注釋、描述或者其他的文檔,。這些信息是目標(biāo)文件的一部分,,并有可能是提供給鏈接器的信息,比如需要哪些庫文件,。

    如果位11 IMAGE_SCN_LNK_REMOVE(鏈接可刪除節(jié))被置1,,表示數(shù)據(jù)是目標(biāo)文件的、被預(yù)定于可執(zhí)行文件被鏈接后丟棄掉的節(jié)的一部分,。常和位9連用,。

    如果位12 IMAGE_SCN_LNK_COMDAT(鏈接通用塊節(jié))被置1, 表示節(jié)中包含“common block data”(通用塊數(shù)據(jù)),,也即某種形式的打包函數(shù),。

    如果位15 IMAGE_SCN_MEM_FARDATA(內(nèi)存遠(yuǎn)程數(shù)據(jù)節(jié))被置1,表示我們擁有遠(yuǎn)程數(shù)據(jù)----意味著什么,。此位的含義不明,。

    如果位17 IMAGE_SCN_MEM_PURGEABLE(內(nèi)存可清除節(jié))被置1, 表示節(jié)中的數(shù)據(jù)可清除----但我認(rèn)為它和“可丟棄”不是一回事,,可丟棄擁有自己的標(biāo)志位,,參見后面。同樣,它也明顯的不是用來指示16位信息的,,因?yàn)樗灿幸粋€IMAGE_SCN_MEM_16BIT定義,。此位的含義不明。

    如果位18 IMAGE_SCN_MEM_LOCKED(內(nèi)存被鎖節(jié))被置1,, 表示節(jié)不應(yīng)該被從內(nèi)存中移除,?抑或表明沒有重定位信息?此位的含義不明,。

    如果位19 IMAGE_SCN_MEM_PRELOAD(內(nèi)存預(yù)載入節(jié))被置1,,表示節(jié)在執(zhí)行開始前應(yīng)該被頁載入?此位的含義不明,。

    位20至23 指定我沒有找到信息的對齊,。諸如#defines IMAGE_SCN_ALIGN_16BYTES之類。我曾經(jīng)見過的唯一值為0,,是16位的缺省對齊,。 我懷疑它們是庫之類文件的目標(biāo)對齊。

    如果位24 IMAGE_SCN_LNK_NRELOC_OVFL(鏈接擴(kuò)展重定位節(jié))被置1,,表示節(jié)中包含一些我不知道的擴(kuò)展重定位,。

    如果位25 IMAGE_SCN_MEM_DISCARDABLE(內(nèi)存可丟棄節(jié))被置1,表示節(jié)中的數(shù)據(jù)在進(jìn)程啟動后就不需要了,。它是,,舉例來說,含有重定位信息的情況,。我曾經(jīng)見過它也用于只執(zhí)行一次的驅(qū)動和服務(wù)程序的啟動例程,,還用于輸入目錄。

    如果位26 IMAGE_SCN_MEM_NOT_CACHED(內(nèi)存不緩存節(jié))被置1,,表示節(jié)中的數(shù)據(jù)不應(yīng)該被緩存,。不要問我為什么不。這是不是意味著關(guān)掉2級緩存,?

    如果位27 IMAGE_SCN_MEM_NOT_PAGED(內(nèi)存不可頁換出節(jié))被置1,,表示節(jié)中的數(shù)據(jù)不應(yīng)該頁換出。它對驅(qū)動程序有意義,。

    如果位28 IMAGE_SCN_MEM_SHARED(內(nèi)存共享節(jié))被置1,,表示節(jié)中的數(shù)據(jù)在映象文件的所有正在運(yùn)行的實(shí)例中共享。如果它是,,例如DLL文件的未初始化數(shù)據(jù),,那么DLL的所有正在運(yùn)行的實(shí)例程序在任何時候都將擁有相同的變量內(nèi)容。
注意:只有第一個實(shí)例的節(jié)被初始化,。
含有代碼的節(jié)總是被共享寫時拷貝(copy-on-write)(亦即:如果重定位必不可少,,那么共享就不工作),。(譯注:“寫時拷貝”的譯法也許根本就是錯誤的,但我一時找不到更準(zhǔn)確的翻譯,,也不清楚其具體含義,,只能以此充數(shù)了。希望知情著指點(diǎn),。)

    如果位29 IMAGE_SCN_MEM_EXECUTE(內(nèi)存可執(zhí)行節(jié))被置1,,表示進(jìn)程對節(jié)的內(nèi)存有“執(zhí)行”的存取權(quán)限。
    
    如果位30 IMAGE_SCN_MEM_READ(內(nèi)存可讀節(jié))被置1,,表示進(jìn)程對節(jié)的內(nèi)存有“讀”的存取權(quán)限,。
    
    如果位31 IMAGE_SCN_MEM_WRITE(內(nèi)存可寫節(jié))被置1,表示進(jìn)程對節(jié)的內(nèi)存有“寫”的存取權(quán)限,。

在節(jié)頭之后,,我們就會發(fā)現(xiàn)節(jié)本身。在文件中,,它們按照“FileAlignment”(文件對齊)的字節(jié)數(shù)對齊(也就是說,,在可選頭之后和每個節(jié)的數(shù)據(jù)之后將要填充一些字節(jié))并按照它們的RVA排序。在載入后(內(nèi)存中),,  它們按照“SectionAlignment”(節(jié)對齊)的字節(jié)數(shù)對齊,。

試舉一例,如果可選頭在文件的偏移量981處結(jié)束,,“FileAlignment”(文件對齊)的值為512,,那么第一個節(jié)將于1024字節(jié)處開始。注意:你可通過“PointerToRawData”(原始數(shù)據(jù)指針)或者“VirtualAddress”(虛擬地址)的值來找到各節(jié),,因此實(shí)際上根本沒必要在對齊上小題大做,。

試畫映象文件的全圖如下:

    +-------------------+
    |     DOS-根        |
    +-------------------+
    |      文件頭       |
    +-------------------+
    |      可選頭       |
    |- - - - - - - - - -|
    |                   |----------------+
    |     數(shù)據(jù)目錄      |                |
    |                   |                |
    |   (指向節(jié)中       |-------------+  |
    |     目錄的RVA)    |             |  |
    |                   |---------+   |  |
    |                   |         |   |  |
    +-------------------+         |   |  |
    |                   |-----+   |   |  |
    |       節(jié)頭        |     |   |   |  |
    |     (指向節(jié)       |--+  |   |   |  |
    |     邊界的RVA)    |  |  |   |   |  |
    +-------------------+<-+  |   |   |  |
    |                   |     | <-+   |  |
    |      節(jié)數(shù)據(jù) 1     |     |       |  |
    |                   |     | <-----+  |
    +-------------------+<----+          |
    |                   |                |
    |      節(jié)數(shù)據(jù) 2     |                |
    |                   | <--------------+
    +-------------------+

每個節(jié)都有一個節(jié)頭,,并且每個數(shù)據(jù)目錄都會指向其中的一個節(jié)(幾個數(shù)據(jù)目錄有可能指向同一個節(jié),,而且也可能有的節(jié)沒有數(shù)據(jù)目錄指向它們)。




八,、節(jié)的原始數(shù)據(jù)(Sections‘ raw data)
--------------------------------------

1.概述(general)
-------
所有的節(jié)在載入內(nèi)存后都按“SectionAlignment”(節(jié)對齊)對齊,,在文件中則以“FileAlignment”(文件對齊)對齊。節(jié)由節(jié)頭中的相關(guān)項(xiàng)來描述:在文件中你可通過“PointerToRawData”(原始數(shù)據(jù)指針)來找到,,在內(nèi)存中你可通過“VirtualAddress”(虛擬地址)來找到,;長度由“SizeOfRawData”(原始數(shù)據(jù)長度)決定。

根據(jù)節(jié)中包含的內(nèi)容,,可分為好幾種節(jié),。大多數(shù)(并非所有)情況下,節(jié)中至少由一個數(shù)據(jù)目錄,,并在可選頭的數(shù)據(jù)目錄數(shù)組中有一個指針指向它,。


2.代碼節(jié)(code section)
------------------------
首先,,我將提到代碼節(jié)。此節(jié),,至少,,要將“IMAGE_SCN_CNT_CODE”(含有代碼節(jié))、“IMAGE_SCN_MEM_EXECUTE”(內(nèi)存可執(zhí)行節(jié))和“IMAGE_SCN_MEM_READ”(內(nèi)存可讀節(jié))等標(biāo)志位設(shè)為1,,并且“AddressOfEntryPoint”(入口點(diǎn)地址)將指向節(jié)中的某個地方,,指向開發(fā)者希望首先執(zhí)行的那個函數(shù)的開始處。
“BaseOfCode”(代碼基址)通常指向這一節(jié)的開始處,,但是,,如果一些非代碼字節(jié)被放在代碼之前的話,它也可能指向節(jié)中靠后的某個地方,。
通常,,除了可執(zhí)行代碼外,本節(jié)沒有別的東東,,并且通常只有一個代碼節(jié),,但是不要太迷信這一點(diǎn)。
典型的節(jié)名有“.text”,、“.code”,、“AUTO”之類。


3.數(shù)據(jù)節(jié)(data section)
------------------------
我們要討論的下一件事情就是已初始化變量,;本節(jié)包含的是已初始化的靜態(tài)變量(象“static int i = 5;”),。它將,至少,,使“IMAGE_SCN_CNT_INITIALIZED_DATA”(含有已初始化數(shù)據(jù)節(jié)),、“IMAGE_SCN_MEM_READ”(內(nèi)存可讀節(jié))和“IMAGE_SCN_MEM_WRITE”(內(nèi)存可寫節(jié))等標(biāo)志位被置為1。
一些鏈接器可能會將常量放在沒有可寫標(biāo)志位的它們自己的節(jié)中,。如果有一部分?jǐn)?shù)據(jù)可共享,,或者有其它的特定情況,那么可能會有更多的節(jié),,且它們的合適的標(biāo)志位會被設(shè)置,。
不管是一節(jié),還是多節(jié),,它們都將處于從“BaseOfData”(數(shù)據(jù)基址)到“BaseOfData”+“SizeOfInitializedData”(數(shù)據(jù)基址+已初始化數(shù)據(jù)的大?。┑姆秶畠?nèi)。
典型的名稱有“.data”,、“.idata”,、“DATA”、等等,。


4.BSS節(jié)(bss section)
----------------------
其后就是未初始化的數(shù)據(jù)(一些象“static int k;”之類的靜態(tài)變量),;本節(jié)十分象已初始化的數(shù)據(jù),,但它的“PointerToRawData”(文件偏移量)卻為0,表明它的內(nèi)容不存儲在文件中,;并且“IMAGE_SCN_CNT_UNINITIALIZED_DATA”(含有未初始化數(shù)據(jù)節(jié))而不是“IMAGE_SCN_CNT_INITIALIZED_DATA”(含有已初始化數(shù)據(jù)節(jié))標(biāo)志位被置為1,,表明在載入時它的內(nèi)容應(yīng)該被置為0。這就意味著,,在文件中只有節(jié)頭,,沒有節(jié)身;節(jié)身將由加載器創(chuàng)建,,并全部為0字節(jié),。
它的長度由“SizeOfUninitializedData”(未初始化數(shù)據(jù)大小)確定,。
典型的名稱有“.bss”,、“BSS”之類。

有些節(jié)數(shù)據(jù)“沒有”被數(shù)據(jù)目錄指向,。它們的內(nèi)容和結(jié)構(gòu)是由編譯器而不是鏈接器提供,。
(棧段和堆段不是二進(jìn)制文件中的節(jié),它們是由加載器根據(jù)可選頭中的棧大小和堆大小項(xiàng)來創(chuàng)建的,。)


5.版權(quán)(copyright)
-------------------
為了從一個簡單的目錄節(jié)開始講解,,讓我們來看一看數(shù)據(jù)目錄“IMAGE_DIRECTORY_ENTRY_COPYRIGHT”(版權(quán)目錄項(xiàng))項(xiàng)。它的內(nèi)容是一個版權(quán)信息或ASCII形式的描述字符串(不是以0結(jié)尾的),,象“Gonkulator control application, copyright (c) 1848 Hugendubel & Cie”這樣,。這個字符串,通常,,是用命令行或者描述文件提供給鏈接器的,。
這個字符串在運(yùn)行時并不需要,并可能被丟棄,。它是不可寫的,;事實(shí)上,應(yīng)用程序根本不需要存取它,。因此,,如果已有一個可丟棄的,、非可寫的節(jié)存在,鏈接器就會找到它,;如果沒有,,就創(chuàng)建一個(命名為“.descr”之類),。然后它就將那個字符串填入該節(jié)中并讓版權(quán)目錄項(xiàng)指針指向這個字符串。“IMAGE_SCN_CNT_INITIALIZED_DATA”(含有已初始化數(shù)據(jù)節(jié))標(biāo)志位應(yīng)置為1,。


6.輸出符號(exported symbols)
------------------------------
(注意:本文的1993年03月12日之前的各個版本中,,輸出目錄的描述有誤,。文中沒有描述中轉(zhuǎn)、只以序數(shù)輸出,、或者使用好幾個名稱輸出等內(nèi)容,。)

下一件最簡單的事情是輸出目錄,是由“IMAGE_DIRECTORY_ENTRY_EXPORT”(輸出目錄項(xiàng))指向的,。它是一個典型的在DLL中常見到的目錄,;包含一些輸出函數(shù)的入口點(diǎn)(以及輸出對象等的地址)。當(dāng)然可執(zhí)行文件也可能擁有輸出符號但一般沒有,。
包含它們的節(jié)應(yīng)該有“已初始化數(shù)據(jù)的”和“可讀的”特性,。這樣的節(jié)應(yīng)該是不可丟棄的,因?yàn)樵谶\(yùn)行時,,進(jìn)程有可能調(diào)用“GetProcAddress()”來尋找一個函數(shù)的入口點(diǎn),。如果單獨(dú)成節(jié)的話,本節(jié)通常被稱作“.edata”,;更常見的是,,它被并入象“已初始化數(shù)據(jù)”之類的節(jié)中。

輸出表(“IMAGE_EXPORT_DIRECTORY”)的結(jié)構(gòu)由一個頭和輸出數(shù)據(jù),,也就是:符號名稱,、它們的序號和它們的入口點(diǎn)偏移量等構(gòu)成。

1)首先,,我們有一個沒被使用并通常為0的,、32位的“Characteristics”(特性)。

2)然后是一個32位的“TimeDateStamp”(時間日期戳),,大概是提供此表被創(chuàng)建的time_t格式的時間,;天呀,它的值并不總是有效(有些鏈接器將它設(shè)置為0),。

3-4)往后我們看到2個16位的,、有關(guān)版本信息的word單元(“MajorVersion”和“MinorVersion”,含義分別為‘主版本號’和‘小版本號’),,同樣,,它們很多地被設(shè)為0。

5)下一個東東是32位的“Name”(名稱),;它是一個指向以0結(jié)尾的ASCII字符串為DLL名稱的RVA,。(為防DLL被改名時的錯誤,名稱是必須的----參見輸入目錄中的“綁定”部分,。)

6)然后是32位的“Base”(基址),。稍后我們再討論。

7)下一個32位值是輸出條目的總數(shù)(“NumberOfFunctions”,,意為‘函數(shù)數(shù)’),。除了它們的序數(shù)外,,各條目還可能使用一個或多個名稱來輸出。接下來的一個32位數(shù)字是輸出名稱的總數(shù)(“NumberOfNames”,,意為‘名字?jǐn)?shù)’),。
在大多數(shù)情況下,每一個輸出條目都準(zhǔn)確的有一個相應(yīng)的名稱,,并且將用這個名稱來使用它,;但是一個條目可能擁有好幾個相關(guān)聯(lián)的名稱(那樣它們的每一個名稱都可訪問);或者它也可能沒有名稱,,此時它只能以它的序數(shù)來訪問,。無名輸出項(xiàng)(只有序數(shù))的使用是不鼓勵的,因?yàn)榇藭r輸出DLL的所有版本都必須使用相同的序數(shù)法,,而這會造成維護(hù)的問題,。

8)下一個32位值“AddressOfFunctions”(函數(shù)地址)是指向輸出條目列表的RVA。它指向一個32位值的“NumberOfFunctions”(函數(shù)數(shù))數(shù)組,,數(shù)組的每一項(xiàng)都是一個指向輸出函數(shù)或變量的RVA,。

關(guān)于此列表有兩個怪事:第一,這樣一個輸出的RVA竟可能會為0,,在此情況下,,此值沒被使用。第二,,如果一RVA指向含有輸出目錄的節(jié),,那么它就是一個中轉(zhuǎn)輸出。一個中轉(zhuǎn)輸出就是指指向另一個二進(jìn)制文件中的輸出項(xiàng)的指針,;如果使用了它,,就可用另一個二進(jìn)制文件中的被指向的輸出項(xiàng)來代替使用。此時的RVA指向,,正如已提到的,,輸出目錄的節(jié)中,指向一個以以零結(jié)尾的字符串組成的,、被指向的DLL的名稱和一個用點(diǎn)分開的輸出項(xiàng)的名稱,,象“otherdll.exportname”這樣,或者是DLL的名稱和輸出序數(shù),,象“otherdll.#19”這樣,。

現(xiàn)在到了解釋輸出序數(shù)的時候了。一個輸出項(xiàng)的序數(shù)就是函數(shù)地址數(shù)組中的索引值加上上面提到的“Base”(基址)的值的和,。在大多數(shù)情況下,,“Base”(基址)的值為1,這就意味著第一個輸出項(xiàng)的序數(shù)為1,,第二個輸出項(xiàng)的序數(shù)為2,,以此類推。

9-10)“AddressOfFunctions”(函數(shù)地址)RVA之后,,我們發(fā)現(xiàn)二個RVA,,一個指向符號名稱的32位RVA的數(shù)組“AddressOfNames”(名字的地址),另一個指向16位序數(shù)“AddressOfNameOrdinals”(名字序數(shù)的地址)的數(shù)組,。兩個數(shù)組都有“NumberOfNames”(名字?jǐn)?shù))個元素,。
符號名稱可能會全部丟失,此時“AddressOfNames”(名字的地址)為0,;否則,,被指向的數(shù)組并行運(yùn)行,這意味著它們的每個索引中的元素共同擁有,。“AddressOfNames”(名字的地址)數(shù)組由以0結(jié)尾的輸出名稱的RVA組成,;這些名稱以一個分類的列表排列(即:數(shù)組的第一個成員是按照字母順序排列的最小的名稱的RVA;這使當(dāng)按名稱查找一個輸出符號時,,搜索的效率更高,。)
根據(jù)PE規(guī)范,“AddressOfNameOrdinals”(名字序數(shù)的地址)數(shù)組每個名稱擁有一個相應(yīng)的序數(shù),,然而,,我發(fā)現(xiàn)這個數(shù)組卻將實(shí)際的索引包含到“AddressOfFunctions”(函數(shù)地址)數(shù)組中去。

我將畫一個有關(guān)這三個表的圖:

        函數(shù)地址
            |
            |
            |
            v
    帶序數(shù)‘基址’的輸出RVA
    帶序數(shù)‘基址+1’的輸出RVA
     ...
    帶序數(shù)‘基址+函數(shù)數(shù)-1’的輸出RVA



       名字地址                       名字序數(shù)地址
           |                               |
           |                               |
           |                               |
           v                               v
      第一個名字的RVA      <->    第一個名字的輸出索引
      第二個名字的RVA      <->    第二個名字的輸出索引
       ...                             ...
    第‘名字?jǐn)?shù)’個名字的RVA  <->  第‘名字?jǐn)?shù)’個名字的輸出索引

舉一些例子是適宜的,。

為按序數(shù)找到一個輸出符號,,先減去“Base”(基址)值以得到索引值,再根據(jù)“AddressOfFunctions”(函數(shù)地址)的RVA得到輸出項(xiàng)數(shù)組,,并用索引值去找到數(shù)組中的輸出RVA,。如果結(jié)果沒有指向輸出節(jié)中,你就完了,。否則,,它就指向那里的一個描述輸出DLL和(輸出項(xiàng))名稱或序數(shù)的字符串,之后你就得在那里查找中轉(zhuǎn)輸出,。

為按名稱找到一個輸出符號,,先跟隨“AddressOfNames”(名字的地址)的RVA(如果是0就沒有名稱)找到輸出名稱的RVA數(shù)組。在列表中搜尋你要找的名稱,。用該名稱在“AddressOfNameOrdinals”(名字序數(shù)的地址)數(shù)組中的索引,,得到和找到的名稱相應(yīng)的16位數(shù)字。根據(jù)PE規(guī)范,,這是一個序數(shù),,你需先減去“Base”(基址)值以得到輸出索引值;但依據(jù)我的經(jīng)驗(yàn),這就是輸出索引值,,你不需要再減了,。使用輸出索引值,你就能在“AddressOfFunctions”(函數(shù)地址)數(shù)組中找到輸出RVA了,,要么是輸出RVA本身,,要么是一個描述中轉(zhuǎn)輸出的字符串的RVA。


7.輸入符號(imported symbols)
------------------------------

當(dāng)編譯器發(fā)現(xiàn)一個對別的可執(zhí)行文件(大多數(shù)是DLL文件)中的函數(shù)調(diào)用時,,在最簡單化的情況下,,它會對此情況一無所知,只是簡單地輸出一個對那個符號的正常調(diào)用指令,。鏈接器不得不修正那個符號的地址,,就象它為任何其它的外部符號所做的那樣。
鏈接器使用一個輸入庫來查找從哪個DLL文件輸入了哪個符號,,并為所有的輸入符號都建立存根,,每個存根包含一個跳轉(zhuǎn)指令;存根就是實(shí)際的調(diào)用目標(biāo),。這些跳轉(zhuǎn)指令實(shí)際上將跳往從所謂的輸入地址表中提取的一個地址,。在更復(fù)雜的應(yīng)用程序(使用“__declspec(dllimport)”時)中,編譯器會知道函數(shù)是輸入的,,并直接輸出一個位于輸入地址表中的地址的調(diào)用,,繞過跳轉(zhuǎn)。

不管怎樣,,DLL文件中的函數(shù)地址總是必要的,,并將于應(yīng)用程序載入時,由加載器從輸出DLL文件的輸出目錄中提供,。加載器知道哪個庫中的哪些符號需要被查找以及哪些地址需要通過搜索輸入目錄來修正,。

我最好給你一個例子。有或無__declspec(dllimport)的調(diào)用如下所示:

    源文件:
        int symbol(char *);
        __declspec(dllimport) int symbol2(char*);
        void foo(void)
        {
            int i=symbol("bar");
            int j=symbol2("baz");
        }

    匯編:
        ...
        call _symbol                 ; 沒有declspec(dllimport)的
        ...
        call [__imp__symbol2]        ; 含有declspec(dllimport)的
        ...

在第一種(沒有__declspec(dllimport))情況下,,編譯器不知道“_symbol”位于一個DLL文件中,,因此鏈接器必須要提供“_symbol”函數(shù)。因?yàn)榇撕瘮?shù)不存在,,它就為輸入符號提供一個存根函數(shù),,即一個間接跳轉(zhuǎn)。所有輸入存根的集合被稱為“轉(zhuǎn)移區(qū)”(有時也叫做“跳板”,,因?yàn)槟闾侥抢锏哪康氖菫榱颂絼e的地方),。

典型地,此轉(zhuǎn)移區(qū)位于代碼節(jié)中(它不是輸入目錄的一部分),。每一個函數(shù)存根都是一個跳往DLL文件中的實(shí)際函數(shù)的跳轉(zhuǎn),。轉(zhuǎn)移區(qū)的形式象這樣:

    _symbol:        jmp  [__imp__symbol]
    _other_symbol:  jmp  [__imp__other__symbol]
    ...

這意味著:如果你不指定“__declspec(dllimport)”來使用輸入符號,那么鏈接器將會為它們產(chǎn)生一個由間接跳轉(zhuǎn)所組成的轉(zhuǎn)移區(qū)。如果你真指定了“__declspec(dllimport)”,,那么編譯器就會自己做間接(跳轉(zhuǎn)),,轉(zhuǎn)移區(qū)也就不需要了。(這也意味著:如果你輸入的是變量或其它東西,,你就必須指定“__declspec(dllimport)”,,因?yàn)橐粋€具有jmp指令的存根只合適于函數(shù),。)

不管怎樣,,符號“x”的地址都被存在“__imp_x”的存儲單元。所有這樣的存儲單元一起形成所謂的“輸入地址表”,,此表是由被用到的各DLL文件中的輸入庫提供給鏈接器的,。輸入地址表就是由下面這種形式的一組地址組成的:

   __imp__symbol:   0xdeadbeef
   __imp__symbol2:  0x40100
   __imp__symbol3:  0x300100
   ...

這個輸入地址表是輸入目錄的一部分,并且被IMAGE_DIRECTORY_ENTRY_IAT(輸入地址表目錄項(xiàng))目錄指針?biāo)赶颍ūM管有些鏈接器不設(shè)置此目錄項(xiàng),,程序也能運(yùn)行,;很明顯地,這是因?yàn)榧虞d器不使用IMAGE_DIRECTORY_ENTRY_IAT(輸入地址表目錄項(xiàng))目錄也能解決輸入問題),。
這些地址并不被鏈接器所知,;鏈接器只插入一些偽地址(函數(shù)名稱的RVA;參見后面的更多信息),,這些偽地址會在載入時被加載器用輸出DLL文件中的輸出目錄來修正,。輸入地址表,以及它是怎樣被加載器找到的,,將會在本章的后面被詳細(xì)講述,。

注意:這個介紹是針對C語言規(guī)范的;有些別的應(yīng)用程序構(gòu)建環(huán)境是不使用輸入庫的,,盡管它們都需要建立一個輸入地址表,,用來讓它們的程序訪問輸入對象和函數(shù)。C語言編譯器往往使用輸入庫,,因?yàn)闊o論如何講,,這都有利于它們----它們的鏈接器使用好庫。別的環(huán)境使用的是例如:一個列出需要的DLL文件名稱和函數(shù)名稱的描述文件(比如“模塊定義文件”),,或者一個源文件中的聲明形式的列表等,。

這就是程序的代碼如何使用輸入函數(shù)的;現(xiàn)在我們再來看看輸入目錄是如何建立以便加載器使用的,。

輸入目錄應(yīng)該存在于是“已初始化數(shù)據(jù)”并且“可讀”的節(jié)中,。
輸入目錄是一個多IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))的數(shù)組,每個被使用的DLL文件都有一個,。(它們的)列表由一個全部用0填充的IMAGE_IMPORT_DESCRIPTOR(輸入地址表目錄項(xiàng))結(jié)構(gòu)作為結(jié)束,。
一個IMAGE_IMPORT_DESCRIPTOR(輸入地址表目錄項(xiàng))是一個擁有下列成員的結(jié)構(gòu)體:

    OriginalFirstThunk(原始第一個換長)(漢譯的說明見注釋)
        它是一個RVA(32位),指向一個以0結(jié)尾的、由IMAGE_THUNK_DATA(換長數(shù)據(jù))的RVA構(gòu)成的數(shù)組,,其每個IMAGE_THUNK_DATA(換長數(shù)據(jù))元素都描述一個函數(shù),。此數(shù)組永不改變。

    TimeDateStamp(時間日期戳)
        它是一個具有好幾個目的的32位的時間戳,。讓我們先假設(shè)時間戳為0,,一些高級的情況將在以后處理。

    ForwarderChain(中轉(zhuǎn)鏈)
        它是輸入函數(shù)列表中第一個中轉(zhuǎn)的,、32位的索引,。中轉(zhuǎn)也是高級的東東。對初學(xué)者先將所有位設(shè)為-1,。
        
    Name(名稱)
        它是一個DLL文件的名稱(0結(jié)尾的ASCII碼字符串)的,、32位的RVA。
        
    FirstThunk(第一換長)
        它也是一個RVA(32位),,指向一個0結(jié)尾的,、由IMAGE_THUNK_DATA(換長數(shù)據(jù))的RVA構(gòu)成的數(shù)組,其每個IMAGE_THUNK_DATA(換長數(shù)據(jù))元素都描述一個函數(shù),。此數(shù)組是輸入地址表的一部分,,并且可以改變。

因此,,數(shù)組中的每個IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))結(jié)構(gòu)體都給出輸出DLL文件的名稱,,并且,除了中轉(zhuǎn)和時間日期戳,,它還給出2個指向IMAGE_THUNK_DATA(換長數(shù)據(jù))的數(shù)組的RVA,,都是32位。(每個數(shù)組的最后一個成員都全部填充為0字節(jié),,以標(biāo)志結(jié)尾,。)
目前看來,每個IMAGE_THUNK_DATA(換長數(shù)據(jù))都是一個RVA,,指向一個描述輸入函數(shù)的IMAGE_IMPORT_BY_NAME(輸入名字)項(xiàng),。
現(xiàn)在,有趣的是兩個數(shù)組并行運(yùn)行,,也就是說:它們指向同一組IMAGE_IMPORT_BY_NAME(輸入名字),。

沒有必要失望,我將再畫一圖,。這里是IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))的關(guān)鍵內(nèi)容:

      原始第一個換長        第一個換長
            |                    |
            |                    |
            |                    |
            V                    V

            0-->    函數(shù)1     <--0
            1-->    函數(shù)2     <--1
            2-->    函數(shù)3     <--2
            3-->    foo       <--3
            4-->    mumpitz   <--4
            5-->    knuff     <--5
            6-->0            0<--6      /* 最后的RVA是0! */

圖當(dāng)中的名字就是尚未討論的IMAGE_IMPORT_BY_NAME(輸入名字),。每一個都是一個16位的數(shù)字(一個提示)跟著一些數(shù)量未定的字節(jié),它們都是以0結(jié)尾的,、輸入符號的ASCII碼名字,。
提示就是指向輸出DLL文件名字表的索引(參見上面的輸出目錄),。那個索引中的名字將被一一嘗試,如果沒有相符的,,再使用二進(jìn)制搜索來尋找名字,。
(有些鏈接器不愿意查找正確的提示,總是只簡單的將其指定為1,,或者其它的隨意數(shù)字,。這并無大害,只是使解決名字的第一次嘗試總是失敗,,并迫使每個名字都使用二進(jìn)制搜索來進(jìn)行,。)

總結(jié)一下:如果你想從“knurr”DLL中查找輸入函數(shù)“foo”的信息,第一步你先找到數(shù)據(jù)目錄中的IMAGE_DIRECTORY_ENTRY_IMPORT(輸入目錄項(xiàng))項(xiàng),,得到一個RVA,,再在原始節(jié)數(shù)據(jù)中找到那個地址,現(xiàn)在你就得到一個IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))數(shù)組了,。通過查看根據(jù)它們的“名稱”被指向的字符串,得到和“knurr”DLL有關(guān)的這個數(shù)組的成員(即一個輸入描述結(jié)構(gòu)),。在你找到正確的IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))后,,順著它的“OriginalFirstThunk”(原始第一個換長)得到被指向的IMAGE_THUNK_DATA(換長數(shù)據(jù))數(shù)組;再通過查詢RVA找到“foo”函數(shù),。

好了,,為什么我們有“兩”列指向IMAGE_IMPORT_BY_NAME(輸入名字)的指針呢?這是因?yàn)樵谶\(yùn)行時,,應(yīng)用程序不需要輸入函數(shù)的名字,,只需要地址。在這里輸入地址表又出現(xiàn)了,。加載器將從相關(guān)的DLL文件的輸出目錄中查找每一個輸入符號,,并用DLL文件入口點(diǎn)的線性地址替換“FirstThunk”( 第一個換長)列表中的IMAGE_THUNK_DATA(換長數(shù)據(jù))元素(到現(xiàn)在之前它還是指向IMAGE_IMPORT_BY_NAME(輸入名字)的)。
請記住帶有象“__imp__symbol”標(biāo)簽的地址列表,;被數(shù)據(jù)目錄IMAGE_DIRECTORY_ENTRY_IAT(輸入地址表目錄項(xiàng))所指向的輸入地址表,,就是被“FirstThunk”( 第一個換長)所指向的列表。[在從好幾個DLL文件輸入的情況下,,輸入地址表是包含所有DLL文件的“FirstThunk”( 第一個換長)數(shù)組,。目錄項(xiàng)IMAGE_DIRECTORY_ENTRY_IAT(輸入地址表目錄項(xiàng))可能會丟失,但輸入(函數(shù))仍能工作良好,。]
“OriginalFirstThunk”( 原始第一個換長)數(shù)組保持不變,,因此你總能通過“OriginalFirstThunk”( 原始第一個換長)列表查找原始的輸入名字列表。

現(xiàn)在輸入已經(jīng)被用正確的線性地址修正,,如下所示:

       原始第一個換長        第一個換長
            |                    |
            |                    |
            |                    |
            V                    V

            0-->    函數(shù)1        0-->  輸出函數(shù)1
            1-->    函數(shù)2        1-->  輸出函數(shù)2
            2-->    函數(shù)3        2-->  輸出函數(shù)3
            3-->    foo          3-->  輸出函數(shù)foo
            4-->    mumpitz      4-->  輸出函數(shù)mumpitz
            5-->    knuff        5-->  輸出函數(shù)knuff
            6-->0            0<--6

這是簡單情況下的基本結(jié)構(gòu)?,F(xiàn)在我們將要學(xué)習(xí)輸入目錄中的需細(xì)講的東西,。

第一,當(dāng)數(shù)組中IMAGE_THUNK_DATA元(換長數(shù)據(jù))素的IMAGE_ORDINAL_FLAG(序數(shù)標(biāo)志)位(也是:MSB,,參見注釋)被置1時,,表示列表中沒有符號的名字信息,符號只以序數(shù)輸入,。你可通過查看IMAGE_THUNK_DATA(換長數(shù)據(jù))中的低地址word來得到序數(shù),。
通過序數(shù)輸入是不鼓勵的,通過名字輸入會更安全,,因?yàn)槿绻敵鯠LL文件不是預(yù)期的版本時輸出序數(shù)可能會改變,。

第二,有所謂的“綁定輸入”,。

請思考一下加載器的工作:當(dāng)它想執(zhí)行的一個二進(jìn)制文件需要一個DLL中的函數(shù)時,,加載器會載入該DLL,找到它的輸出目錄,,查找函數(shù)的RVA并計(jì)算函數(shù)的入口點(diǎn),。然后用這樣找到的地址修正“FirstThunk”( 第一個換長)列表。
假設(shè)程序員很聰明,,給DLL文件提供的唯一優(yōu)先載入地址不會發(fā)生沖突,,那么我們就能認(rèn)為函數(shù)的入口點(diǎn)將總是相同的。它們在鏈接時能被算出并被補(bǔ)進(jìn)“FirstThunk”( 第一個換長)列表中,,這就是“綁定輸入”所發(fā)生的一切,。(“綁定”工具就是干這個的,它是Win32 SDK的一部分,。)            

當(dāng)然,,你得慎重:用戶的DLL可能是不同的版本,或者DLL必須重定位,,這些都會使先前修正的“FirstThunk”( 第一個換長)列表不再有效,;此時,加載器仍能查尋“OriginalFirstThunk”( 原始第一個換長)列表,,找出輸入符號并重新補(bǔ)正“FirstThunk”( 第一個換長)列表,。加載器知道這是必須的,當(dāng):1)輸出DLL文件的版本不符,,或2)輸出DLL文件需要重定位時,。

確定有沒有重定位表對加載器來說不是問題,但該怎樣找出版本的不同呢,?這時IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))的“時間戳”就派上用場了,。如果它是0,表明輸入列表沒有被綁定,,加載器總是要修復(fù)入口點(diǎn),。否則的話,,輸入被綁定,“時間戳”必須要和“文件頭”中的輸出DLL文件的“時間戳”相符,;如果不符的話,,加載器就認(rèn)為該二進(jìn)制文件被綁到一個“錯誤”的DLL文件上并重新補(bǔ)正輸入列表。

這里有另外一個有關(guān)輸入列表中的“中轉(zhuǎn)”的怪事,。一個DLL文件能輸出一個不定義在本DLL文件中卻需從另一個DLL文件中輸入的符號,;這樣的符號據(jù)說就是被中轉(zhuǎn)的(參見上面的輸出目錄描述)。
現(xiàn)在,,很明顯的,,你不能通過查看那個實(shí)際上并不包含入口點(diǎn)信息的DLL文件的時間戳來確定一個符號的入口點(diǎn)是否有效。因此,,出于安全的原因,,中轉(zhuǎn)符號的入口點(diǎn)必須總是被修正。在二進(jìn)制文件的輸入列表中,,中轉(zhuǎn)符號的輸入必須被找出,,以便加載器能補(bǔ)正它們。

這一點(diǎn)可通過“ForwarderChain”(中轉(zhuǎn)鏈)來做到,。它是一個指向換長列表中的索引值,;被索引位置的輸入就是一個中轉(zhuǎn)輸出,并且此位置的“FirstThunk”( 第一個換長)列表中的內(nèi)容就是“下一個”中轉(zhuǎn)輸入的索引值,,以此類推,直到索引值為-1,,就表明已沒有其他的中轉(zhuǎn)了,。如果根本就沒有中轉(zhuǎn),那么“ForwarderChain”(中轉(zhuǎn)鏈)的值本身就為-1,。

這就是所謂的“老式”綁定,。

至此,我們應(yīng)該總結(jié)一下我們目前已掌握的情況 :-)

OK,,我將認(rèn)為你已找到了IMAGE_DIRECTORY_ENTRY_IMPORT(輸入目錄項(xiàng))并且已根據(jù)它找到了它的輸入目錄,,位于某個節(jié)中。現(xiàn)在你已處于IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))數(shù)組的開頭了,,此類數(shù)組的最后一個將以全0字節(jié)填充,。
要讀懂一個IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu)),你得先查看它的“名字”項(xiàng),,根據(jù)它的RVA,,你就能找到輸出DLL文件的名字。下一步你得確定輸入是否是綁定的,;如果輸入是綁定的,,“時間戳”就會是非“0”的,。如果它們是綁定的,現(xiàn)在就是你通過比較“時間戳”來檢查DLL文件的版本是否相符的好機(jī)會了,。
現(xiàn)在你根據(jù)“OriginalFirstThunk”( 原始第一個換長)的RVA來到了IMAGE_THUNK_DATA(換長數(shù)據(jù))數(shù)組,;過完這些數(shù)組(它是0結(jié)尾的),它的每個成員都將是一個IMAGE_IMPORT_BY_NAME(輸入名字)的RVA(除非它的高位被置1,,此時你找不到名字只有序數(shù)),。根據(jù)那個RVA,并跳過2字節(jié)(即‘提示’),,現(xiàn)在你就得到一個以0結(jié)尾的字符串,,這就是輸入函數(shù)的名字。
在綁定輸入時要找到提供的入口點(diǎn),,先根據(jù)“FirstThunk”( 第一個換長)平行的來到“OriginalFirstThunk”( 原始第一個換長)數(shù)組,;數(shù)組成員就是入口點(diǎn)的線性地址(暫時不考慮中轉(zhuǎn)的話題)。

還有一件我到現(xiàn)在都沒有提及的事情:明顯地有些鏈接器在構(gòu)建輸入目錄時會產(chǎn)生bug(我就發(fā)現(xiàn)一個還在被一個Borland C鏈接器使用的bug),。這些鏈接器把IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))中的“OriginalFirstThunk”( 原始第一個換長)設(shè)為0,,并只建立“FirstThunk”( 第一個換長)。很明顯的,,這樣的輸入目錄不能被綁定(否則重修輸入的必須信息就會丟失----你根本找不到函數(shù)名字),。在這種情況下,你得根據(jù)“FirstThunk”( 第一個換長)數(shù)組來取得輸入符號名字,,你將永遠(yuǎn)得不到預(yù)先補(bǔ)正的入口地址,。我已發(fā)現(xiàn)一個TIS文件(參考書目[6]),講述一個在某種程度上和此bug兼容的輸入目錄,,因此那個文件可能就是該bug的起源,。

TIS文件規(guī)定:
    IMPORT FLAGS(輸入標(biāo)志)
    TIME/DATE STAMP(時間/日期戳)
    MAJOR VERSION - MINOR VERSION(主版本號 - 小版本號)
    NAME RVA(名字的RVA)
    IMPORT LOOKUP TABLE RVA(輸入查詢表的RVA)
    IMPORT ADDRESS TABLE RVA(輸入地址表的RVA)

而別處使用的對應(yīng)結(jié)構(gòu)是:
    OriginalFirstThunk( 原始第一個換長)
    TimeDateStamp(時間日期戳)
    ForwarderChain(中轉(zhuǎn)鏈)
    Name(名字)
    FirstThunk(第一個換長)

最后一個關(guān)于輸入目錄的需要細(xì)講的就是所謂的“新式”綁定(在參考書目[3]中講述),它也可以由“綁定”工具來處理,。當(dāng)使用這種方式時,,“時間日期戳”的所有位被置為1,并且沒有中轉(zhuǎn)鏈,;此時所有輸入符號的地址都將被補(bǔ)正,,而不管它們是不是中轉(zhuǎn)的。盡管如此,,你還是需要知道DLL的版本,,并且你還是需要將序數(shù)符號從中轉(zhuǎn)符號中區(qū)分開來。為了達(dá)到這個目的,,IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT(綁定輸入目錄項(xiàng))目錄被創(chuàng)建了,。就我所見,它將不被放在節(jié)中,,而是被放在頭中,,處于節(jié)頭之后第一節(jié)之前,。(咳,這不是我的發(fā)明,,我只是講述它而已?。?br>這個目錄告訴你,每一個已使用的DLL文件的中轉(zhuǎn)輸出是從哪些別的DLL文件中來的,。
結(jié)構(gòu)是IMAGE_BOUND_IMPORT_DESCRIPTOR(綁定輸入描述結(jié)構(gòu))形式的,,包括(按這個順序):
一個32位數(shù)字,“時間戳”,。
一個16位數(shù)字,,“OffsetModuleName(模塊名字偏移量)”,是從目錄開頭到以0結(jié)尾的DLL文件名的偏移量,;
一個16位數(shù)字,,“NumberOfModuleForwarderRefs(模塊中轉(zhuǎn)參考的數(shù)字)”,給出這個DLL文件為它的中轉(zhuǎn)使用的DLL文件數(shù),。

緊隨這個結(jié)構(gòu)之后你會發(fā)現(xiàn)“NumberOfModuleForwarderRefs(模塊中轉(zhuǎn)參考的數(shù)字)”結(jié)構(gòu),,告訴你這個DLL文件的中轉(zhuǎn)所來自的DLL文件的名稱和版本。這些結(jié)構(gòu)就是“IMAGE_BOUND_FORWARDER_REF(綁定中轉(zhuǎn)參考)”結(jié)構(gòu)的:
一個32位的數(shù)字“時間日期戳”(TimeDateStamp),;
一個16位的數(shù)字“模塊名稱偏移量”(OffsetModuleName),,它就是從目錄開頭到中轉(zhuǎn)來自的那個DLL文件的0結(jié)尾的名字處的偏移量;
一個16位的未使用單元,。

跟在“IMAGE_BOUND_FORWARDER_REF(綁定中轉(zhuǎn)參考)”后的是下一個“IMAGE_BOUND_IMPORT_DESCRIPTOR(綁定輸入描述結(jié)構(gòu))”,,以此類推;列表最終以一個全部為0位的IMAGE_BOUND_IMPORT_DESCRIPTOR(綁定輸入描述結(jié)構(gòu))結(jié)束,。


我對由此(描述)造成的不便表示歉意,,但這就是它看起來的樣子:-)


現(xiàn)在,如果你有一個新的綁定輸入目錄,,你得載入所有的DLL文件,并使用目錄指針I(yè)MAGE_DIRECTORY_ENTRY_BOUND_IMPORT(綁定輸入目錄項(xiàng))找到IMAGE_BOUND_IMPORT_DESCRIPTOR(綁定輸入描述結(jié)構(gòu)),,掃描整個IMAGE_BOUND_IMPORT_DESCRIPTOR(綁定輸入描述結(jié)構(gòu)),,并檢查被載入的DLL文件的“時間日期戳”和這個目錄中提供的是否相符。如果不符,,就將輸入目錄中“第一換長”(FirstThunk)中的錯誤全部修改過來,。



8.資源(resources)
-------------------
資源,比如對話框,、菜單,、圖標(biāo)等等,都存儲在IMAGE_DIRECTORY_ENTRY_RESOURCE(“資源目錄項(xiàng)”)指向的數(shù)據(jù)目錄中,。它們處于一個至少“IMAGE_SCN_CNT_INITIALIZED_DATA(已初始化數(shù)據(jù)內(nèi)容節(jié))”和“IMAGE_SCN_MEM_READ(內(nèi)存可讀節(jié))”標(biāo)志位都被置為1的節(jié)中,。

資源的基礎(chǔ)是“資源目錄”(IMAGE_RESOURCE_DIRECTORY),;它包含好幾個“資源目錄項(xiàng)”(IMAGE_RESOURCE_DIRECTORY_ENTRY),其中的每一項(xiàng)反過來又可能指向一個“資源目錄”,。按照這種方式,,你就得到一個以“資源目錄項(xiàng)”為樹葉的“資源目錄”樹;它們的樹葉指向?qū)嶋H的資源數(shù)據(jù),。

在實(shí)際使用中,,情況會稍微簡單些。一般你不會遇到不可能理清的特別復(fù)雜的樹的,。
通常,,它的層次結(jié)構(gòu)是這樣的:一個目錄作為根。它指向很多目錄,,每種資源類型都有一個,。這些目錄又指向子目錄,每個子目錄都有一個名字或者ID號并指向這個資源所提供的各種語言的目錄,;每種語言你都能找到一個資源項(xiàng),,資源項(xiàng)最終指向(具體的)數(shù)據(jù)。(注意:多語言資源不能在Win95上運(yùn)行,。即使程序有好幾種語言,,Win95也總是使用相同的資源----我沒有查出是哪一種,但我猜測肯定是它最先碰到的那種,。多語言資源在NT系統(tǒng)上可以運(yùn)行,。)

沒有指針的樹大致象這樣:

                           ( 根 )
                              |
             +----------------+------------------+
             |                |                  |
            菜單           對話框              圖標(biāo)
             |                |                  |
       +-----+-----+        +-+----+           +-+----+----+
       |           |        |      |           |      |    |
    "main"      "popup"   0x10   "maindlg"    0x100 0x110 0x120
       |            |       |      |           |      |    | 
   +---+-+          |       |      |           |      |    |
   |     |     default   english   default    def.   def.  def.
german english

一個“資源目錄項(xiàng)”(IMAGE_RESOURCE_DIRECTORY)包含:
32位未使用標(biāo)志,叫做“特征”(Characteristics),;
32位“時間日期戳”(同樣按常用的time_t表示法),,告訴你資源被創(chuàng)建的時間(如果此項(xiàng)被設(shè)置的話);
16位“主版本號”(MajorVersion)和16位“小版本號”(MinorVersion),,以允許你據(jù)此維護(hù)資源的幾個版本,;
16位“已命名項(xiàng)目數(shù)”(NumberOfNamedEntries)和另一個16位的“ID項(xiàng)目數(shù)”(NumberOfIdEntries)。

緊隨此結(jié)構(gòu)后的是“已命名項(xiàng)目數(shù)”+“ID項(xiàng)目數(shù)”兩結(jié)構(gòu)體,,它們都是“資源目錄項(xiàng)”格式,,都以名字開頭。它們可能指向下一個“資源目錄”或者指向?qū)嶋H的資源數(shù)據(jù),。

一個“資源目錄項(xiàng)”由下面組成:
32位單元提供你它所描述的資源的ID或者是目錄,;
32位的到數(shù)據(jù)的偏移量或者是到下一個子目錄的偏移量。

ID的含義取決于樹中的層次,;ID可能是一個數(shù)字(如果最高位為0)也可能是一個名字(如果最高位為1),。如果是一個名字,它的低31位就是從資源節(jié)原始數(shù)據(jù)的開始到這個名字(名字有16位長并由unicode的寬字符而不是0結(jié)尾符作為結(jié)束)的偏移量。

如果你位于根目錄之中,,且如果ID是一個數(shù)字的話,,那么它指的就是下面的一種資源類型:
    1: 光標(biāo)
    2: 位圖
    3: 圖標(biāo)
    4: 菜單
    5: 對話框
    6: 字串表
    7: 字體目錄
    8: 字體
    9: 快捷鍵
    10: 未格式化資源數(shù)據(jù)
    11: 信息表
    12: 組光標(biāo)
    14: 組圖標(biāo)
    16: 版本信息
任何其它數(shù)字都是用戶自定義的。任何有類型名的資源類型也是用戶自定義的,。

如果你處于(樹的)下一層當(dāng)中,,此時ID一定是一個數(shù)字,且就是資源的一個特例的語言ID號,;例如,,你可以(同時)擁有澳大利亞英語、加拿大法語和瑞士德語等本地化形式的對話框,,并且它們分享同一個資源ID,。系統(tǒng)會根據(jù)線程的地點(diǎn)來選擇要使用的對話框,反過來地點(diǎn)又反映了用戶的“區(qū)域設(shè)置”,。(如果資源找不到線程地點(diǎn),,系統(tǒng)將先使用一個中性的子語言資源作為地點(diǎn),比如它將尋找標(biāo)準(zhǔn)法語而不是用戶所擁有的加拿大法語,;如果它還是找不到,,就使用最小語言ID號的那個實(shí)例。必須注意,,所有這些只工作于NT系統(tǒng)之上的,。)
為便于辨認(rèn)語言ID,使用宏P(guān)RIMARYLANGID()(意為“主語言ID”)和SUBLANGID()(意為“子語言ID”)將它分開為主語言ID和子語言ID,,分別使用它的0-9位和10-15位,。這些值定義在“winresrc.h”文件中。
語言資源只支持快捷鍵,、對話框,、菜單、資源數(shù)據(jù)或字符串等,;其它資源類型必須為LANG_NEUTRAL/SUBLANG_NEUTRAL(中性語言/中性子語言),。

要確定資源目錄的下一層是不是另一個目錄,你可查看它的偏移量的最高位,。如果它是1,,剩下的31位就是從資源節(jié)原始數(shù)據(jù)的開始到下一層目錄的偏移量,還是按“資源目錄”后接“資源目錄項(xiàng)”的格式,。如果高位為0,它就是從資源節(jié)原始數(shù)據(jù)的開始到資源的原始數(shù)據(jù)描述,,即一個資源數(shù)據(jù)項(xiàng)的偏移量,。資源的原始數(shù)據(jù)描述包含32位的“OffsetToData”(到數(shù)據(jù)的偏移量)(指的是到原始數(shù)據(jù)的偏移量,從資源節(jié)原始數(shù)據(jù)的開頭算起),32位的數(shù)據(jù)的“Size”(大?。?,32位的“CodePage”(代碼頁)和一個未使用的32位單元。
(不鼓勵使用代碼頁,,你應(yīng)該使用“語言”的特性來支持多地域,。)


原始數(shù)據(jù)格式依賴于資源類型;詳細(xì)的介紹可在微軟的SDK文檔中找到,。注意:除了用戶自定義資源,,資源中的任何字符串總是按UNICODE格式,明顯的,,用戶自定義的資源按的是開發(fā)者選定的格式,。



9.重定位(relocations)
-----------------------
我將要描述的最后一個數(shù)據(jù)目錄是基址重定位目錄。它是由可選頭數(shù)據(jù)目錄中的IMAGE_DIRECTORY_ENTRY_BASERELOC(基址重定位目錄項(xiàng))項(xiàng)來指向的,。典型的,,它包含在自己的節(jié)中,名字象“.reloc”這樣,,并且IMAGE_SCN_CNT_INITIALIZED_DATA(已初始化數(shù)據(jù)內(nèi)容節(jié)),、 IMAGE_SCN_MEM_DISCARDABLE(內(nèi)存可丟棄節(jié))和IMAGE_SCN_MEM_READ(內(nèi)存可讀節(jié))等標(biāo)志位被置1。

如果映象文件不能被加載到可選頭中提到的優(yōu)先載入地址“ImageBase”(映象基址)時,,重定位數(shù)據(jù)對加載器來說就是必須的,。此時,鏈接器所提供的固定地址就不再有效,,并且加載器將不得不對靜態(tài)變量,、字符串文字等使用的絕對地址進(jìn)行修正。

所謂重定位目錄就是一些連續(xù)的塊,,每一塊都包含4K映象文件的重定位信息,。塊由一個“IMAGE_BASE_RELOCATION(基址重定位)”結(jié)構(gòu)體開始,這個結(jié)構(gòu)體包含一個32位的“VirtualAddress(虛擬地址)”項(xiàng)和一個32位的“SizeOfBlock(塊大?。?#8221;項(xiàng),。跟在它們后面的就是塊的實(shí)際重定位數(shù)據(jù),每一條都是16位的,。
“VirtualAddress(虛擬地址)”就是重定位所在塊需要應(yīng)用的基本的RVA,;“SizeOfBlock(塊大小)”就是整個塊的字節(jié)大??;跟在后面的重定位的數(shù)目是:(‘SizeOfBlock‘-sizeof(IMAGE_BASE_RELOCATION))/2個。當(dāng)你碰到一個“VirtualAddress(虛擬地址)”值為0的“IMAGE_BASE_RELOCATION(基址重定位)”結(jié)構(gòu)體時,,重定位信息就結(jié)束了,。

每一個16位的重定位信息由低12位的重定位位置和高4位的重定位類型組成。要得到重定位的RVA,你需要用這個12位的位置加上“IMAGE_BASE_RELOCATION(基址重定位)”中的“VirtualAddress(虛擬地址)”,。類型是下面之一:

    IMAGE_REL_BASED_ABSOLUTE (0)
        這種不需操作,;用于將塊按32位邊界對齊。位置應(yīng)該為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位必須應(yīng)用于上面所說的全部32位。這種(和不需操作的第“0”種)是我在二進(jìn)制文件種實(shí)際發(fā)現(xiàn)的僅有的重定位類型,。
    IMAGE_REL_BASED_HIGHADJ (4)
        這是一種復(fù)雜的,。請自己參閱(參考文獻(xiàn)[6]),并努力弄懂它的意思:“高調(diào)整,。這種修正要求一個全32位值,。高16位定位于偏移量處,低16位定位在下一個數(shù)組元素(此數(shù)組元素包括在大小的域中)的偏移量處,。它們兩個需要被連成一個有符號的變量,。加上32位的增量。然后加上0x8000并將有符號變量的高16位存儲在偏移量處的16位域中,。”
    IMAGE_REL_BASED_MIPS_JMPADDR (5)
        不清楚
    IMAGE_REL_BASED_SECTION (6)
        不清楚
    IMAGE_REL_BASED_REL32 (7)
        不清楚

舉一個例子,,如果你發(fā)現(xiàn)重定位信息是
    0x00004000      (32位, 開始的RVA)
    0x00000010      (32位, 塊的大小)
    0x3012          (16位的重定位數(shù)據(jù))
    0x3080          (16位的重定位數(shù)據(jù))
    0x30f6          (16位的重定位數(shù)據(jù))
    0x0000          (16位的重定位數(shù)據(jù))
    0x00000000      (下一塊的RVA)
    0xff341234
你知道第一塊描述的重定位開始于RVA 0x4000處,有16字節(jié)長,。因?yàn)轭^用掉了8字節(jié),,并且一個重定位要用2字節(jié),所以塊中計(jì)有(16-8)/2=4個重定位,。第一個重定位被應(yīng)用于0x4012處的DWORD,,第二個于0x4080處的DWORD,第三個于0x40f6處的DWORD,。最后一個不需操作,。
下一塊的RVA是0,列表結(jié)束,。

好,,你怎么處理一個重定位呢?
你能知道映象文件“被”重定位到可選頭“ImageBase(映象基址)”的優(yōu)先載入地址,;你也能知道你真正載入的地址,。如果它們相同,你什么也不用做,。如果它們不同,,你需計(jì)算出實(shí)際基址-優(yōu)先基址的差并加上重定位位置的值(有符號,,可能為負(fù)值),此值你可通過上面講述的方法找到,。



九、致謝(Acknowledgments)
---------------------------
感謝David Binette的調(diào)試和校讀,。(剩下的錯誤全部都是我的,。)
也感謝wotsit.org網(wǎng)站讓我將此文放到他們的網(wǎng)站上。



十,、版權(quán)(Copyright)
---------------------
本文的版權(quán)屬于B. Luevelsmeyer,,1999年。它是免費(fèi)的,,你可以任意的使用,,但后果自負(fù)。它含有錯誤并不完整,,特此警告,。



十一、Bug報告(Bug reports)
----------------------------
Bug報告(或其他建議)請發(fā)送至:[email protected]



十二,、版本(Versions)
----------------------
你可在文件的頂部找到當(dāng)前的版本號,。

1998-04-06
  第一次公開發(fā)表

1998-07-29
  將映象文件版本和子系統(tǒng)版本中錯誤的“byte”改為“word”
  更正“棧只限于1 MB”的錯誤(實(shí)際上沒有上限)
  更正一些輸入錯誤

1999-03-15
  更正輸出目錄的描述,原來非常不全
  調(diào)整輸入目錄的描述,,原來講的不清
  更正輸入錯誤并為其它節(jié)改了一些詞句
  


十三,、參考文獻(xiàn)(Literature)
----------------------------
[1]
"Peering Inside the PE: A Tour of the Win32 Portable Executable File
Format" (M. Pietrek), in: Microsoft Systems Journal 3/1994

[2]
"Why to Use _declspec(dllimport) & _declspec(dllexport) In Code", MS
Knowledge Base Q132044

[3]《Windows 問與答》
"Windows Q&A" (M. Pietrek), in: Microsoft Systems Journal 8/1995

[4]《編寫多語言資源》
"Writing Multiple-Language Resources", MS Knowledge Base Q89866

[5]
"The Portable Executable File Format from Top to Bottom" (Randy Kath),
in: Microsoft Developer Network

[6]《Windows下TIS格式規(guī)范1.0版》
Tool Interface Standard (TIS) Formats Specification for Windows Version
1.0 (Intel Order Number 241597, Intel Corporation 1993)




附錄(Appendix: hello world):
-------------------------------
在這個附錄中我將給大家展示一下怎樣手工建立一個程序。因?yàn)槲也粫﨑EC Alpha的,,本例將使用Intel匯編語言,。

本程序相當(dāng)于

    #include <stdio.h>
    int main(void)
    {
        puts(hello,world);
        return 0;
    }

首先,我使用Win32函數(shù)來翻譯它以取代C運(yùn)行時庫: 

    #define STD_OUTPUT_HANDLE -11UL
    #define hello "hello, world\n"

    __declspec(dllimport) unsigned long __stdcall
    GetStdHandle(unsigned long hdl);

    __declspec(dllimport) unsigned long __stdcall
    WriteConsoleA(unsigned long hConsoleOutput,
                    const void *buffer,
                    unsigned long chrs,
                    unsigned long *written,
                    unsigned long unused
                    );

    static unsigned long written;

    void startup(void)
    {
        WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE),hello,sizeof(hello)-1,&written,0);
        return;
    }

現(xiàn)在我將笨拙的將它匯編出來:
    startup:
                ; WriteConsole()的參數(shù), 反向的
    6A 00                     push      0x00000000
    68 ?? ?? ?? ??            push      offset _written
    6A 0D                     push      0x0000000d
    68 ?? ?? ?? ??            push      offset hello
                ; GetStdHandle()的參數(shù)
    6A F5                     push      0xfffffff5
    2E FF 15 ?? ?? ?? ??      call      dword ptr cs:__imp__GetStdHandle@4
                ; 結(jié)果是WriteConsole()的參數(shù)
    50                        push      eax
    2E FF 15 ?? ?? ?? ??      call      dword ptr cs:__imp__WriteConsoleA@20
    C3                        ret       

    hello:
    68 65 6C 6C 6F 2C 20 77 6F 72 6C 64 0A   "hello, world\n"
    _written:
    00 00 00 00

以上就是編譯的部分,。任何人都能做到這點(diǎn),。從現(xiàn)在起讓我們扮演起鏈接器的角色,這會非常有趣 :-)

我需要先找出函數(shù)WriteConsoleA()和GetStdHandle(),。碰巧它們都在“kernel32.dll”中,。(這是“輸入庫”部分。)

現(xiàn)在我開始做可執(zhí)行文件,。問號代表待定的值,;它們將在以后被修正。

首先是DOS-根,,開始于0x0,,有0x40字節(jié)長:
    00 | 4d 5a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    10 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    20 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    30 | 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00
正如你所見到的,這不是真正的MS-DOS程序,。它只是一個開始部分有“MZ”簽名的頭和緊跟在頭后面的e_lfanew指針,,沒有任何代碼,。這是因?yàn)樗⒎谴蛩氵\(yùn)行于MS-DOS之上;它之所以在這里只是因?yàn)橐?guī)范的需要,。

然后是PE簽名,,開始于0x40,有0x4字節(jié)長:
        50 45 00 00

現(xiàn)在到了文件頭,,開始于0x44,,有0x14字節(jié)長:
    Machine                     4c 01       ; i386
    NumberOfSections            02 00       ; 代碼段和數(shù)據(jù)段
    TimeDateStamp               00 00 00 00 ; 誰管它?
    PointerToSymbolTable        00 00 00 00 ; 未用
    NumberOfSymbols             00 00 00 00 ; 未用
    SizeOfOptionalHeader        e0 00       ; 常量
    Characteristics             02 01       ; 32位機(jī)器上的可執(zhí)行文件

接著是可選頭,,開始于0x58,,有0x60字節(jié)長:
    Magic                       0b 01       ; 常量
    MajorLinkerVersion          00          ; 我是 0.0 版:-)
    MinorLinkerVersion          00          ;
    SizeOfCode                  20 00 00 00 ; 32字節(jié)代碼
    SizeOfInitializedData       ?? ?? ?? ?? ; 待找出
    SizeOfUninitializedData     00 00 00 00 ; 我們沒有BSS節(jié)
    AddressOfEntryPoint         ?? ?? ?? ?? ; 待定
    BaseOfCode                  ?? ?? ?? ?? ; 待定
    BaseOfData                  ?? ?? ?? ?? ; 待定
    ImageBase                   00 00 10 00 ; 1 MB, 隨意選
    SectionAlignment            20 00 00 00 ; 32字節(jié)對齊
    FileAlignment               20 00 00 00 ; 32字節(jié)對齊
    MajorOperatingSystemVersion  04 00      ; NT 4.0
    MinorOperatingSystemVersion  00 00      ;
    MajorImageVersion           00 00       ;0.0版
    MinorImageVersion           00 00       ;
    MajorSubsystemVersion       04 00       ; Win32 4.0
    MinorSubsystemVersion       00 00       ;
    Win32VersionValue           00 00 00 00 ; 未使用?
    SizeOfImage                 ?? ?? ?? ?? ; 待定
    SizeOfHeaders               ?? ?? ?? ?? ; 待定
    CheckSum                    00 00 00 00 ; 非驅(qū)動不用
    Subsystem                   03 00       ; Win32控制臺
    DllCharacteristics          00 00       ; 未用 (不是一個DLL)
    SizeOfStackReserve          00 00 10 00 ; 1 MB棧
    SizeOfStackCommit           00 10 00 00 ; 開始時4 KB
    SizeOfHeapReserve           00 00 10 00 ; 1 MB堆
    SizeOfHeapCommit            00 10 00 00 ; 開始時4 KB
    LoaderFlags                 00 00 00 00 ; 未知
    NumberOfRvaAndSizes         10 00 00 00 ; 常量

正如你所見,我計(jì)劃只用2個節(jié),,一個用于代碼,,一個用于所有剩余的東西(數(shù)據(jù)、常量和輸入目錄等),。沒有重定位和象資源之類其它東西,。我也不用BSS節(jié)并將變量“written”放入已初始化數(shù)據(jù)。文件和RAM中的節(jié)對齊都是一樣的(32字節(jié)),;這將有助于使任務(wù)簡單,,否則我就得來回地計(jì)算RVA很多次。

現(xiàn)在我們設(shè)置數(shù)據(jù)目錄,,開始于0xb8字節(jié),,有 0x80字節(jié)長:
       地址           大小
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_EXPORT (0)
                       ; IMAGE_DIRECTORY_ENTRY_IMPORT (1)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_RESOURCE (2)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_EXCEPTION (3)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_SECURITY (4)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_BASERELOC (5)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_DEBUG (6)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_COPYRIGHT (7)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_TLS (9)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_IAT (12)
    00 00 00 00    00 00 00 00         ; 13
    00 00 00 00    00 00 00 00         ; 14
    00 00 00 00    00 00 00 00         ; 15
僅使用輸入目錄。

下一個使節(jié)頭,。首先我們做代碼節(jié)的,,代碼節(jié)將包含前面所編的匯編語句。它有32字節(jié)長,,所以代碼節(jié)也就是這么長,。節(jié)頭從0x138處開始,有0x28字節(jié)長:

    Name            2e 63 6f 64 65 00 00 00     ; ".code"的ASCII碼值
    VirtualSize         00 00 00 00             ; 未用
    VirtualAddress      ?? ?? ?? ??             ; 待定
    SizeOfRawData       20 00 00 00             ; 代碼的大小
    PointerToRawData    ?? ?? ?? ??             ; 待定
    PointerToRelocations 00 00 00 00            ; 未用
    PointerToLinenumbers 00 00 00 00            ; 未用
    NumberOfRelocations  00 00                  ; 未用
    NumberOfLinenumbers  00 00                  ; 未用
    Characteristics     20 00 00 60             ; 代碼節(jié),,可執(zhí)行,,可讀

第二節(jié)將包含數(shù)據(jù)。節(jié)頭開始于0x160處,,有0x28字節(jié)長:

    Name            2e 64 61 74 61 00 00 00     ; ".data"的ASCII碼值
    VirtualSize         00 00 00 00             ; 未用
    VirtualAddress      ?? ?? ?? ??             ; 待定
    SizeOfRawData       ?? ?? ?? ??             ; 待定
    PointerToRawData    ?? ?? ?? ??             ; 待定
    PointerToRelocations 00 00 00 00            ; 未用
    PointerToLinenumbers 00 00 00 00            ; 未用
    NumberOfRelocations  00 00                  ; 未用
    NumberOfLinenumbers  00 00                  ; 未用
    Characteristics     40 00 00 c0             ; 已初始化的,,可讀,可寫

下一個字節(jié)位于0x188處,,但節(jié)需要按32字節(jié)(的倍數(shù))對齊(因?yàn)槲沂沁@樣選擇的),,所以我們需要添一些(0)字節(jié)直到0x1a0處:

    00 00 00 00 00 00       ; 填充的
    00 00 00 00 00 00
    00 00 00 00 00 00
    00 00 00 00 00 00

現(xiàn)在第一節(jié),就是上面所匯編的代碼節(jié),,“到”了,。它開始于0x1a0處,,有0x20字節(jié)長:
    6A 00                    ; push      0x00000000
    68 ?? ?? ?? ??           ; push      offset _written
    6A 0D                    ; push      0x0000000d
    68 ?? ?? ?? ??           ; push      offset hello_string
    6A F5                    ; push      0xfffffff5
    2E FF 15 ?? ?? ?? ??     ; call      dword ptr cs:__imp__GetStdHandle@4
    50                       ; push      eax
    2E FF 15 ?? ?? ?? ??     ; call      dword ptr cs:__imp__WriteConsoleA@20
    C3                       ; ret       

因?yàn)檫@一節(jié)的長度(剛好32字節(jié)),在下一節(jié)(數(shù)據(jù)節(jié))前我們不需要填充任何字節(jié),。下一節(jié)到了,,從0x1c0處開始:

    68 65 6C 6C 6F 2C 20 77 6F 72 6C 64 0A  ; "hello, world\n"的ASCII碼值
    00 00 00                                ; 填充幾個0以和_written對齊
    00 00 00 00                             ; _written

現(xiàn)在剩下的只有輸入目錄了。本文件將從"kernel32.dll"庫中輸入2個函數(shù),,輸入目錄將從本節(jié)的變量后面立即開始,。首先我們先將上面的數(shù)據(jù)按32字節(jié)對齊: 

    00 00 00 00 00 00 00 00 00 00 00 00     ; 填充的

在0x1e0處開始輸入描述(IMAGE_IMPORT_DESCRIPTOR):
    OriginalFirstThunk      ?? ?? ?? ??     ; 待定
    TimeDateStamp           00 00 00 00     ; 未綁定
    ForwarderChain          ff ff ff ff     ; 無中轉(zhuǎn)
    Name                    ?? ?? ?? ??     ; 待定
    FirstThunk              ?? ?? ?? ??     ; 待定

我們需要用一個0字節(jié)項(xiàng)來結(jié)束輸入目錄(我們現(xiàn)在位于0x1f4):
    OriginalFirstThunk      00 00 00 00     ; 結(jié)束符號
    TimeDateStamp           00 00 00 00     ;
    ForwarderChain          00 00 00 00     ;
    Name                    00 00 00 00     ;
    FirstThunk              00 00 00 00     ;

現(xiàn)在只剩下DLL名字,還有2個換長,,以及換長數(shù)據(jù)和函數(shù)名字了。但現(xiàn)在我們真的很快就要完成了,。

DLL名字,,以0結(jié)尾,開始于0x208處:
    6b 65 72 6e 65 6c 33 32 2e 64 6c 6c 00  ; "kernel32.dll"的ASCII碼值
    00 00 00                                ; 填充到32位邊界

原始第一個換長,,開始于0x218處:
    AddressOfData   ?? ?? ?? ??             ; "WriteConsoleA"函數(shù)名的RVA
    AddressOfData   ?? ?? ?? ??             ; "GetStdHandle"函數(shù)名的RVA
                    00 00 00 00             ; 結(jié)束符號

第一個換長就是同樣的列表,,開始于0x224處:
(__imp__WriteConsoleA@20, at 0x224)
    AddressOfData   ?? ?? ?? ??             ; "WriteConsoleA"函數(shù)名的RVA
(__imp__GetStdHandle@4, at 0x228)
    AddressOfData   ?? ?? ?? ??             ; "GetStdHandle"函數(shù)名的RVA
                    00 00 00 00             ; 結(jié)束符號

現(xiàn)在剩下的只有輸入名字(IMAGE_IMPORT_BY_NAME)形式的兩個函數(shù)名了。我們現(xiàn)處于0x230字節(jié),。
    01 00                                      ; 序數(shù),,不需要正確
    57 72 69 74 65 43 6f 6e 73 6f 6c 65 41 00  ; "WriteConsoleA"的ASCII碼值
    02 00                                      ; 序數(shù),不需要正確
    47 65 74 53 74 64 48 61 6e 64 6c 65 00     ; "GetStdHandle"的ASCII碼值

Ok, 這就全部結(jié)束了,。下一個字節(jié),,我們并不真正需要,是0x24f,。我們必須將節(jié)填充到0x260處:
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; 填充的
    00

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

我們已經(jīng)完成了,。因?yàn)槲覀円呀?jīng)知道了所有的字節(jié)偏移量,我們可以應(yīng)用我們的修正到所有原先被用“,?,?”符號標(biāo)為“未知”的地址和大小了。
我將不強(qiáng)迫你一步一步地去讀它(很好懂的),,只直接給出結(jié)果來:

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

DOS-頭, 開始于0x0:
    00 | 4d 5a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    10 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    20 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    30 | 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00

簽名, 開始于0x40:
        50 45 00 00

文件頭, 開始于0x44:
    Machine                     4c 01       ; i386
    NumberOfSections            02 00       ; 代碼和數(shù)據(jù)
    TimeDateStamp               00 00 00 00 ; 誰管它?
    PointerToSymbolTable        00 00 00 00 ; 未用
    NumberOfSymbols             00 00 00 00 ; 未用
    SizeOfOptionalHeader        e0 00       ; 常量
    Characteristics             02 01       ; 可執(zhí)行于32位機(jī)器上

可選頭, 開始于0x58:
    Magic                       0b 01       ; 常量
    MajorLinkerVersion          00          ; 我是 0.0版 :-)
    MinorLinkerVersion          00          ;
    SizeOfCode                  20 00 00 00 ; 32字節(jié)代碼
    SizeOfInitializedData       a0 00 00 00 ; 數(shù)據(jù)節(jié)大小
    SizeOfUninitializedData     00 00 00 00 ; 我們沒有 BSS節(jié)
    AddressOfEntryPoint         a0 01 00 00 ; 代碼節(jié)的開始處
    BaseOfCode                  a0 01 00 00 ; 代碼節(jié)的RVA
    BaseOfData                  c0 01 00 00 ; 數(shù)據(jù)節(jié)的RVA
    ImageBase                   00 00 10 00 ; 1 MB, 任意選擇
    SectionAlignment            20 00 00 00 ; 32字節(jié)對齊
    FileAlignment               20 00 00 00 ; 32字節(jié)對齊
    MajorOperatingSystemVersion  04 00      ; NT 4.0
    MinorOperatingSystemVersion  00 00      ;
    MajorImageVersion           00 00       ; 0.0版本
    MinorImageVersion           00 00       ;
    MajorSubsystemVersion       04 00       ; Win32 4.0
    MinorSubsystemVersion       00 00       ;
    Win32VersionValue           00 00 00 00 ; 未用?
    SizeOfImage                 c0 00 00 00 ; 所有節(jié)大小的總數(shù)
    SizeOfHeaders               a0 01 00 00 ; 第一節(jié)的偏移量
    CheckSum                    00 00 00 00 ; 非驅(qū)動程序不須用
    Subsystem                   03 00       ; Win32控制臺程序
    DllCharacteristics          00 00       ; 未用(不是一個DLL)
    SizeOfStackReserve          00 00 10 00 ; 1 MB 棧
    SizeOfStackCommit           00 10 00 00 ; 開始時4 KB 
    SizeOfHeapReserve           00 00 10 00 ; 1 MB 堆
    SizeOfHeapCommit            00 10 00 00 ; 開始時4 KB
    LoaderFlags                 00 00 00 00 ; 未知
    NumberOfRvaAndSizes         10 00 00 00 ; 常量

數(shù)據(jù)目錄,,開始于 0xb8:
      地址            大小
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_EXPORT (0)
    e0 01 00 00    6f 00 00 00         ; IMAGE_DIRECTORY_ENTRY_IMPORT (1)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_RESOURCE (2)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_EXCEPTION (3)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_SECURITY (4)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_BASERELOC (5)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_DEBUG (6)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_COPYRIGHT (7)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_TLS (9)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11)
    00 00 00 00    00 00 00 00         ; IMAGE_DIRECTORY_ENTRY_IAT (12)
    00 00 00 00    00 00 00 00         ; 13
    00 00 00 00    00 00 00 00         ; 14
    00 00 00 00    00 00 00 00         ; 15

節(jié)頭(代碼節(jié)), 開始于0x138:
    Name            2e 63 6f 64 65 00 00 00     ; ".code"
    VirtualSize         00 00 00 00             ; 未用
    VirtualAddress      a0 01 00 00             ; 代碼節(jié)的RVA
    SizeOfRawData       20 00 00 00             ; 代碼的大小
    PointerToRawData    a0 01 00 00             ; 代碼節(jié)的文件偏移量
    PointerToRelocations 00 00 00 00            ; 未用
    PointerToLinenumbers 00 00 00 00            ; 未用
    NumberOfRelocations  00 00                  ; 未用
    NumberOfLinenumbers  00 00                  ; 未用
    Characteristics     20 00 00 60             ; 代碼節(jié),可執(zhí)行,,可讀

節(jié)頭(數(shù)據(jù)節(jié)),開始于0x160:
    Name            2e 64 61 74 61 00 00 00     ; ".data"
    VirtualSize         00 00 00 00             ; 未用
    VirtualAddress      c0 01 00 00             ; 數(shù)據(jù)節(jié)的RVA
    SizeOfRawData       a0 00 00 00             ; 數(shù)據(jù)節(jié)的大小
    PointerToRawData    c0 01 00 00             ; 數(shù)據(jù)節(jié)的文件偏移量
    PointerToRelocations 00 00 00 00            ; 未用
    PointerToLinenumbers 00 00 00 00            ; 未用
    NumberOfRelocations  00 00                  ; 未用
    NumberOfLinenumbers  00 00                  ; 未用
    Characteristics     40 00 00 c0             ; 已初始化,,可讀,可寫

(填充)
    00 00 00 00 00 00       ; 填充的
    00 00 00 00 00 00
    00 00 00 00 00 00
    00 00 00 00 00 00

代碼節(jié), 開始于0x1a0:
    6A 00                    ; push      0x00000000
    68 d0 01 10 00           ; push      offset _written
    6A 0D                    ; push      0x0000000d
    68 c0 01 10 00           ; push      offset hello_string
    6A F5                    ; push      0xfffffff5
    2E FF 15 28 02 10 00     ; call      dword ptr cs:__imp__GetStdHandle@4
    50                       ; push      eax
    2E FF 15 24 02 10 00     ; call      dword ptr cs:__imp__WriteConsoleA@20
    C3                       ; ret       

數(shù)據(jù)節(jié),,開始于0x1c0:
    68 65 6C 6C 6F 2C 20 77 6F 72 6C 64 0A  ; "hello, world\n"
    00 00 00                                ; 填充到和_written對齊
    00 00 00 00                             ; _written
填充:
    00 00 00 00 00 00 00 00 00 00 00 00     ; 填充的

輸入描述(IMAGE_IMPORT_DESCRIPTOR),,開始于0x1e0:
    OriginalFirstThunk      18 02 00 00     ; 原始第一個換長的RVA
    TimeDateStamp           00 00 00 00     ; 未綁定
    ForwarderChain          ff ff ff ff     ; -1,無中轉(zhuǎn)
    Name                    08 02 00 00     ; DLL名字的RVA
    FirstThunk              24 02 00 00     ; 第一個換長的RVA
結(jié)束標(biāo)志(0x1f4):
    OriginalFirstThunk      00 00 00 00     ; 結(jié)束標(biāo)志
    TimeDateStamp           00 00 00 00     ;
    ForwarderChain          00 00 00 00     ;
    Name                    00 00 00 00     ;
    FirstThunk              00 00 00 00     ;

DLL名字, 開始于0x208:
    6b 65 72 6e 65 6c 33 32 2e 64 6c 6c 00  ; "kernel32.dll"
    00 00 00                                ; 填充到32位邊界

原始第一個換長, 開始于0x218:
    AddressOfData   30 02 00 00             ; 函數(shù)名"WriteConsoleA"的RVA
    AddressOfData   40 02 00 00             ; 函數(shù)名"GetStdHandle"的RVA
                    00 00 00 00             ; 結(jié)束標(biāo)志

第一個換長,開始于0x224:
    AddressOfData   30 02 00 00             ; 函數(shù)名"WriteConsoleA"的RVA
    AddressOfData   40 02 00 00             ; 函數(shù)名"GetStdHandle"的RVA
                    00 00 00 00             ; 結(jié)束標(biāo)志

輸入函數(shù)名稱(IMAGE_IMPORT_BY_NAME),,開始于0x230:
    01 00                                      ; 序數(shù),,不需要正確
    57 72 69 74 65 43 6f 6e 73 6f 6c 65 41 00  ; "WriteConsoleA"的ASCII碼值

IMAGE_IMPORT_BY_NAME,開始于0x240:
    02 00                                      ; 序數(shù),,不需要正確
    47 65 74 53 74 64 48 61 6e 64 6c 65 00     ; "GetStdHandle"的ASCII碼值
(填充)
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; 填充的
    00
第一個未使用字節(jié)開始于: 0x260

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


噢,這個文件能在NT上卻不能在windows 95上運(yùn)行,。windows 95不能運(yùn)行按32字節(jié)節(jié)對齊的應(yīng)用程序,,它要求節(jié)對齊為4 KB;并且很明顯的,,文件對齊也應(yīng)為512字節(jié),。因此要想在windows 95上運(yùn)行,你得插入很多的0字節(jié)(為了對齊)并調(diào)整RVA,。感謝D. Binette在windows 95上的(運(yùn)行)試驗(yàn),。


        -- 全文結(jié)束 --          


[譯后記]:
由于時間等因素,遺漏,、重復(fù),、不準(zhǔn)確甚至錯誤等情況在所難免,敬請各位批評,、指正,!另外,由于我保留了所有的英文術(shù)語(譯文就在后面),,所以譯文看起來有點(diǎn)亂,,請大家見諒!
本文的原文寫于1999年,,由于時間關(guān)系,,文中所說的有關(guān)公司、某某項(xiàng)目應(yīng)用的操作系統(tǒng)范圍等等介紹可能已經(jīng)不對或不準(zhǔn)確了,,請大家自己分析,、鑒別。
最后再談點(diǎn)個人感想:
1)個人覺得本文的難點(diǎn)在于輸入符號(表)部分,,而其精華乃在附錄之中,。在學(xué)習(xí)前面的各種項(xiàng)目成員名稱、說明等的同時,,如能對照后面的附錄來學(xué)習(xí),,將會起到事半功倍的效果。另外,,文中所說的什么結(jié)構(gòu)體,、共用體之類術(shù)語都是針對編程而言,如果你并不想或不會編程的話,,可以將其理解為一個將其它東西集合在一起的一個容器就行了,。
2)原文發(fā)表于1998-1999年之間,而相應(yīng)的中文譯文至今也難在網(wǎng)上搜尋得到,,這對中國的破界來說不能說不是一個很大的遺憾,!本文僅起拋磚引玉之用,,希望能有更多、更好,、更及時的國外類似資料出現(xiàn)在我們的網(wǎng)絡(luò)之上,,以造福于我們這些廣大的菜鳥。
                                                                      沈忠平    2006.02 于和州




===========================
|“PE文件格式”1.9版注釋:|
===========================
①Win32s和Win32
Win32s是“WIN32 subset”的縮寫,,它是一個可被加入到Windows 3.1和Windows for Workgroups系統(tǒng)中以使它們能夠運(yùn)行32位應(yīng)用程序的軟件包,。正如它的名字所暗示的那樣,Win32s只是Windows 95和Windows NT系統(tǒng)中使用的Win32 API的一個子集,。Win32s的主要功能就是在32位和16位內(nèi)存地址間相互轉(zhuǎn)換,,也就是一種被稱為換長的操作。

Win32是32位Windows(包括Windows NT,95, 98 和2000等)操作系統(tǒng)的編程接口(API),。當(dāng)應(yīng)用程序是按Win32 API編寫時,,它們就具有16位API(Win16)所不具備的一些高級性能。一個按Win32編寫的程序能運(yùn)行在所有的操作系統(tǒng)之上,,除非這個程序要求特定的操作系統(tǒng)特性,而這些特性別的操作系統(tǒng)又沒有時,。例如,,Windows NT提供的安全特性Windows 95/98就沒有。一個為NT系統(tǒng)的這些特性編寫的程序就不能運(yùn)行在其它的Windows系統(tǒng)之上,。

   使用此API的程序                        能運(yùn)行在...上
     Win32                                  95, 98, NT, 2000, XP 
     Win32s                            3.1, 95, 98, NT, 2000, XP
     Win32c                                 95   
     Win16                        3.0, 3.1, 95, 98, NT, 2000, XP 


②目標(biāo)文件(Object file  )和映象文件(Image file)
目標(biāo)文件(Object file)指的是鏈接程序(鏈接器)的輸入文件,。鏈接器輸出的是映象文件,映象文件反過來又是加載器的輸入文件,。“object file”一詞未必含有任何和面向?qū)ο蟮木幊逃嘘P(guān)的聯(lián)系,。

映象文件(Image file)指的就是可執(zhí)行文件:或者是.EXE,或者是.DLL,。一個映象文件可被想象為“內(nèi)存映象”,。“映象文件”一詞常被用來代替“可執(zhí)行文件”,因?yàn)楹笳哂袝r被用來專指.EXE文件,。


③UNIX 
是一個很流行的多用戶,、多任務(wù)的操作系統(tǒng),由貝爾實(shí)驗(yàn)室于上世紀(jì)70年代早期開發(fā)出來的,。只有很少的程序員建立的UNIX系統(tǒng)本來是設(shè)計(jì)給他們這些程序員專用的,、小巧的、靈活的系統(tǒng),。UNIX是用高級編程語言,,就是C語言,編寫的第一批操作系統(tǒng)之一,。這就意味著只要電腦上有C語言編譯器,,UNIX就可以被虛擬地安裝到任何電腦上,。天生的可移植性加上低廉的價格使得UNIX成為各大學(xué)的流行選擇。(因?yàn)榉葱庞脳l款禁止貝爾實(shí)驗(yàn)室將UNIX作為它的全權(quán)產(chǎn)品推向市場,,所以UNIX的價格不貴,。)
貝爾實(shí)驗(yàn)室只發(fā)布它自己源語言形式的UNIX操作系統(tǒng),所以任何獲得一份拷貝的人都可以按照自己的意愿來修改和定制它,。到上世紀(jì)70年代末時,,有好幾十種不同版本的UNIX運(yùn)行在世界各地。(更多信息請參閱別的資料,。)


④VMS
“Open Virtual Memory System”或僅VMS,,是運(yùn)行于VAX和Alpha系列電腦之上的高端電腦服務(wù)器操作系統(tǒng)的名字,現(xiàn)在用于使用英特爾Itanium CPU的Hewlett-Packard(惠普)系統(tǒng)之上,。VAX和Alpha系列電腦由美國馬薩諸塞州Maynard市的數(shù)據(jù)設(shè)備(DEC)公司(現(xiàn)在由HP擁有)生產(chǎn)的,。OpenVMS 是一個基于多用戶、多處理虛擬存儲的操作系統(tǒng),,設(shè)計(jì)用于時間共享,、批處理和事項(xiàng)處理等。


⑤SDK 
是“software development kit”(軟件開發(fā)工具箱)的縮寫,,它是一個供程序員為特定平臺開發(fā)應(yīng)用程序的編程包,。典型的,一個SDK包含一個或多個API庫,、各種編程工具和相關(guān)文檔等,。


⑥Ne Format(New-style EXE Format的縮寫)
是一個早期Windows操作系統(tǒng)的可執(zhí)行文件(.EXE),包含一個代碼和數(shù)據(jù)的集合或者一個代碼,、數(shù)據(jù)和資源的集合,。這種可執(zhí)行文件也包括兩個頭:一個MS-DOS頭和一個Windows頭,和一些節(jié),。(具體參看其他資料)


⑦OS/2(IBM Operating System/2,,IBM 操作系統(tǒng)/2)
  操作系統(tǒng)/2(OS/2)最初是由 Microsoft 和 IBM 共同合作開發(fā)的一種應(yīng)用于 PC 機(jī)的操作系統(tǒng)。現(xiàn)在只由 IBM 銷售,、支持和管理,。其設(shè)計(jì)目標(biāo)是替換傳統(tǒng)的 DOS 操作系統(tǒng)。OS/2 與 DOS,、Windows 都相兼容,。換句話說,OS/2 操作系統(tǒng)可運(yùn)行所有的 DOS 和 Windows 程序,,但在 OS/2 下運(yùn)行的某些特殊寫程序卻不能在 DOS 或 Windows 下運(yùn)行,。
  OS/2 是一個32位的、為個人計(jì)算機(jī)而設(shè)計(jì)的、支持保護(hù)模式和多任務(wù)的操作系統(tǒng),。OS/2 系統(tǒng)中的圖形表示管理器(Presentation Manager)作為其圖形系統(tǒng),,主要負(fù)責(zé)管理窗口、字體及控件等,。OS/2 系統(tǒng)頂部是 Workplace 命令解釋程序(WPS - 該內(nèi)容在 OS/2 2.0中有具體介紹),,WPS 以文檔為中心,允許用戶訪問文件和打印機(jī),,并可以啟動程序,。WPS 遵循 IBM 的用戶界面標(biāo)準(zhǔn),即“通用用戶訪問”,。
  OS/2 操作系統(tǒng)中包含一種系統(tǒng)對象模型(SOM),,包括磁盤、文件夾,、文件,、程序?qū)ο蠹按蛴C(jī)等對象。SOM 允許應(yīng)用程序間代碼共享,,但這與編程語言無關(guān),。一種稱為 DSOM 的分布式版本支持不同計(jì)算機(jī)上對象間的相互通信。DSOM 建立在 CORBA 基礎(chǔ)上,。SOM 類似于微軟的組件對象模型(Component Object Model),,同時兩者相互競爭。目前人們對 SOM 和 DSOM 已停止深度開發(fā),。
  OS/2 操作系統(tǒng)也包括一種叫做 OpenDoc 的混合文檔技術(shù),它由 Apple 開發(fā)而成,。但目前人們對 OpenDoc 也已停止深度開發(fā),。
  由于 OS/2 存在市場局限性,IBM 公司已于2003年3月12日按照電子商務(wù)計(jì)劃停止了 OS/2 的發(fā)展市場,。 
 


⑧MIPS
MIPS是世界上很流行的一種RISC處理器,。MIPS的意思是“無內(nèi)部互鎖流水級的微處理器”(Microprocessor without interlocked piped stages),其機(jī)制是盡量利用軟件辦法避免流水線中的數(shù)據(jù)相關(guān)問題,。它最早是在80年代初期由斯坦福(Stanford)大學(xué)Hennessy教授領(lǐng)導(dǎo)的研究小組研制出來的,。MIPS公司的R系列就是在此基礎(chǔ)上開發(fā)的RISC工業(yè)產(chǎn)品的微處理器。這些系列產(chǎn)品為很多計(jì)算機(jī)公司采用構(gòu)成各種工作站和計(jì)算機(jī)系統(tǒng),。如R3000,、R4000、R10000等都是其生產(chǎn)的處理器,。

MIPS技術(shù)公司是美國著名的芯片設(shè)計(jì)公司,,它采用精簡指令系統(tǒng)計(jì)算結(jié)構(gòu)(RISC)來設(shè)計(jì)芯片。和英特爾采用的復(fù)雜指令系統(tǒng)計(jì)算結(jié)構(gòu)(CISC)相比,RISC具有設(shè)計(jì)更簡單,、設(shè)計(jì)周期更短等優(yōu)點(diǎn),,并可以應(yīng)用更多先進(jìn)的技術(shù),開發(fā)更快的下一代處理器,。MIPS是出現(xiàn)最早的商業(yè)RISC架構(gòu)芯片之一,,新的架構(gòu)集成了所有原來MIPS指令集,并增加了許多更強(qiáng)大的功能,。

⑨big-endian,、Little-endian和endian  
Big-endian和Little-endian是用來表述一組有序的字節(jié)數(shù)存放在計(jì)算機(jī)內(nèi)存中時的順序的術(shù)語。Big-endian(即“大端結(jié)束”或者“大尾”)是將高位字節(jié)(序列中最重要的值)先存放在低地址處的順序,,而Little-endian(即“小端結(jié)束”或者“小尾”)是將低位字節(jié)(序列中最不重要的值)先存放在低地址處的順序,。舉例來說,在使用Big-endian順序的計(jì)算機(jī)中,,要存儲一個十六進(jìn)制數(shù)4F52所需要的字節(jié)將會以4F52的形式存儲(比如4F存放在內(nèi)存的1000位置,,而52將會被存儲在1001位置)。而在使用Little-endian順序的系統(tǒng)中,,存儲的形式將會是524F(52在地址1000處,,4F在地址1001處)。IBM的370種大型機(jī),、大多數(shù)基于RISC的計(jì)算機(jī)以及Motorola的微處理器使用的是Big-endian順序,,TCP/IP協(xié)議也是。而Intel的處理器和DEC公司的一些程序則使用的Little-endian方式,。 
“endian”這個詞出自《格列佛游記》,。小人國的內(nèi)戰(zhàn)就源于吃雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開,由此曾發(fā)生過六次叛亂,,其中一個皇帝送了命,,另一個丟了王位。
我們一般將endian翻譯成“字節(jié)序”,,將big endian和little endian稱作“大尾”和“小尾”,。

 
⑩Alpha AXP
“DEC Alpha”,也被稱作“Alpha AXP”,是一個原來由美國數(shù)據(jù)設(shè)備公司(DEC)開發(fā)和制造的64位RISC微處理器(例如:DEC Alpha AXP 21064 微處理器),他們將它用在自己的工作站和服務(wù)器系列上,。被設(shè)計(jì)作為VAX系列計(jì)算機(jī)的繼承者,,Alpha AXP不但支持VMS操作系統(tǒng),同時也支持Digital UNIX操作系統(tǒng),。后來的一些開放源碼操作系統(tǒng)也能運(yùn)行于Alpha之上,,著名的Linux和BSD UNIX操作系統(tǒng)特別支持。微軟直到Windows NT 4.0 SP6才支持這種處理器,,但Windows 2000第2版之后就又不支持了,。


UTC
是“Coordinated Universal Time”的縮寫,,意為“協(xié)調(diào)通用時間”,它是綜合了只以地球的不停旋轉(zhuǎn)速率為基準(zhǔn)的格林威治標(biāo)準(zhǔn)時間(Greenwich Mean Time)和高度精確的原子時間的一種時標(biāo),。當(dāng)原子時間和地球時間達(dá)到一秒的時差時,,一個閏秒就被算進(jìn)UTC時間中。UTC設(shè)計(jì)于1972年1月1日,,并被國際度量衡局(International Bureau of Weights and Measures)于巴黎協(xié)調(diào)通過,。跟格林威治標(biāo)準(zhǔn)時間一樣,UTC也被設(shè)定于0經(jīng)度的本初子午線,。


BSS
是“Block Started by Symbol”的縮寫,,意為“以符號開始的塊”。BSS是Unix鏈接器產(chǎn)生的未初始化數(shù)據(jù)段,。其他的段分別是包含程序代碼的“text”段和包含已初始化數(shù)據(jù)的“data”段,。BSS段的變量只有名稱和大小卻沒有值。此名后來被許多文件格式使用,,包括PE,。
“以符號開始的塊”指的是編譯器處理未初始化數(shù)據(jù)的地方。BSS節(jié)不包含任何數(shù)據(jù),,只是簡單的維護(hù)開始和結(jié)束的地址,,以便內(nèi)存區(qū)能在運(yùn)行時被有效地清零。BSS節(jié)在應(yīng)用程序的二進(jìn)制映象文件中并不存在,,例如:
unsigned char var;    // 分配到.bss節(jié)的8位未初始化變量
unsigned char var2 = 25;   // 分配到.data節(jié)的8位已初始化變量


BSOD(blue screen of death,,藍(lán)屏死機(jī))
是運(yùn)行在Windows環(huán)境下的計(jì)算機(jī)上出現(xiàn)的一個錯誤,甚至包括最早版本的Windows,,比如Windows 3.0和3.1,,在后來的Windows版本比如Microsoft Windows 95, Windows 98, Windows NT,和Windows 2000上仍能出現(xiàn)。它被開玩笑地稱為藍(lán)屏之死是因?yàn)殄e誤發(fā)生時,,屏幕變成藍(lán)色,,電腦總是不能正常運(yùn)轉(zhuǎn)并需要重新啟動。
 

POSIX
是“Portable Operating System Interface for UNIX”(UNIX可移植操作系統(tǒng)接口)的首字母縮寫,,它是定義程序和操作系統(tǒng)之間的接口的一套IEEE和ISO標(biāo)準(zhǔn)。通過將他們的程序設(shè)計(jì)為符合POSIX標(biāo)準(zhǔn),,開發(fā)者就能獲得一些讓他們的程序可以容易地被移植到其他POSIX兼容的操作系統(tǒng)上的保證,,主要包括大多數(shù)UNIX操作系統(tǒng)。POSIX標(biāo)準(zhǔn)目前由IEEE下叫做“Portable Applications Standards Committee”(PASC)(可移植的應(yīng)用程序標(biāo)準(zhǔn)委員會)維護(hù),。


thunk
(動詞) 換長,,變長;已經(jīng)想到的,,預(yù)先想到的
(指在個人電腦中,,將一個16位內(nèi)存地址轉(zhuǎn)換為一個32位的地址,或者相反。換長是必須的,,因?yàn)橛⑻貭柕睦?6位微處理器使用一種叫分段內(nèi)存的定址方式,,而它的32位微處理器使用的卻是一個統(tǒng)一的地址空間。Window 95支持一種允許32位程序調(diào)用16位DLL的換長機(jī)制,,叫統(tǒng)一換長,。而另一方面,運(yùn)行在Windows 3.x和Windows for Workgroup下的16位應(yīng)用程序不能使用32位DLL,,除非32位地址被轉(zhuǎn)換為16位地址,。這就是Win32的功能,并被稱為通用換長,。
根據(jù)民間傳說,,thunk一詞是由一位Algol-60編程語言的開發(fā)者編出的,他在一天深夜意識到參數(shù)的數(shù)據(jù)類型是可以被編譯器稍先一點(diǎn)知道的,。也就是說,,到了編譯器處理參數(shù)的時候,它就已經(jīng)想到了(thunked)數(shù)據(jù)類型了,。該詞的含義近年來已變化很大了,。)
(名詞)換長,變長(在一個分段內(nèi)存地址空間和一個統(tǒng)一地址空間之間互相轉(zhuǎn)換的操作)
(我查遍書店中所有的大大小小的英漢和英英詞典,,都沒有找到thunk這個詞的含義,。后在網(wǎng)上找到了它的英語解釋,卻找不到它對應(yīng)的漢語譯法,,現(xiàn)根據(jù)它的意思,,姑且譯之。各位勿笑,,還請高手指點(diǎn),。)
(英文參見:
http://www./TERM/T/thunk.html)


MSB
“Most Significant Bit”的首字母縮寫,意為“最重要的位”,。在一個二進(jìn)制的數(shù)字中,,它就是最左邊的那一位,也是最重要的那一位,。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn),。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多