標(biāo) 題: 【翻譯】“PE文件格式”1.9版 完整譯文(附注釋)
作 者: ah007
時(shí) 間: 2006-02-28,13:32
鏈 接: http://bbs.pediy.com/showthread.php?t=21932
$Id: pe.txt,v 1.9 1999/03/20 23:55:09 LUEVELSMEYER Exp $
PE文件格式系列譯文之一----
【翻譯】“PE文件格式”1.9版 完整譯文(附注釋)
=========================================================
原著:Bernd. Luevelsmeyer
翻譯:ah007
[注意:本譯文的所有大小標(biāo)題序號(hào)都是譯者添加,以方便大家閱讀。圓圈內(nèi)的數(shù)字是注釋的編號(hào),其中注釋②譯自微軟的《PECOFF規(guī)范》,其它譯自網(wǎng)絡(luò)。----譯者]
一、前言(Preface)
------------------
PE(“portable executable”,可移植的可執(zhí)行文件)文件格式,是微軟WindwosNT,Windows95和Win32子集①中的可執(zhí)行的二進(jìn)制文件的格式;在WindowsNT中,驅(qū)動(dòng)程序也是這種格式。它還能被應(yīng)用于各種目標(biāo)文件②和庫(kù)文件中。
這種文件格式是由微軟設(shè)計(jì)的,并于1993年被TIS(tool interface standard,工具接口標(biāo)準(zhǔn))委員會(huì)(由Microsoft,Intel,Borland,Watcom,IBM,等等組成)所批準(zhǔn),它明顯的基于COFF文件格式的許多知識(shí)。COFF(“common object file fromat”,通用目標(biāo)文件格式)是應(yīng)用于好幾種UNIX系統(tǒng)③和VMS④系統(tǒng)中的目標(biāo)文件和可執(zhí)行文件的格式。
Win32 SDK⑤中包含一個(gè)名叫<winnt.h>的頭文件,其中含有很多用于PE格式的#define和typedef定義。我將逐步地提到其中的很多結(jié)構(gòu)成員名字和#define定義。
你也可能發(fā)現(xiàn)DLL文件“imagehelp.dll”很有用途,它是WindowNT的一部分,但其書(shū)面文件卻很缺乏。它的一些功用在“Developer Network”(開(kāi)發(fā)者網(wǎng)絡(luò))中有所描述。
二、總覽(General Layout)
-------------------------
在一個(gè)PE文件的開(kāi)始處,我們會(huì)看到一個(gè)MS-DOS可執(zhí)行體(英語(yǔ)叫“stub”,意為“根,存根”);它使任何PE文件都是一個(gè)有效的MS-DOS可執(zhí)行文件。
在DOS-根之后是一個(gè)32位的簽名以及魔數(shù)0x00004550 (IMAGE_NT_SIGNATURE)(意為“NT簽名”,也就是PE簽名;十六進(jìn)制數(shù)45和50分別代表ASCII碼字母E和P----譯者注)。
之后是文件頭(按COFF格式),用來(lái)說(shuō)明該二進(jìn)制文件將運(yùn)行在何種機(jī)器之上、分幾個(gè)區(qū)段、鏈接的時(shí)間、是可執(zhí)行文件還是DLL、等等。(本文中可執(zhí)行文件和DLL文件的區(qū)別在于:DLL文件不能被啟動(dòng),但能被別的二進(jìn)制文件使用,而一個(gè)二進(jìn)制文件則不能鏈接到另一個(gè)可執(zhí)行文件。)
那些之后,是可選頭(盡管它一直都存在,卻仍被稱作“可選”----因?yàn)镃OFF文件格式僅為庫(kù)文件使用一個(gè)“可選頭”,卻不為目標(biāo)文件使用一個(gè)“可選頭”,這就是為什么它被稱為“可選”的原因)。它會(huì)告訴我們?cè)摱M(jìn)制文件怎樣被載入的更多信息:開(kāi)始的地址呀、保留的堆棧數(shù)呀、數(shù)據(jù)段的大小呀、等等。
可選頭的一個(gè)有趣的部分是尾部的“數(shù)據(jù)目錄”數(shù)組;這些目錄包含許多指向各“節(jié)”數(shù)據(jù)的指針。例如:如果一個(gè)二進(jìn)制文件擁有一個(gè)輸出目錄,那么你就會(huì)在數(shù)組成員“IMAGE_DIRECTORY_ENTRY_EXPORT”(輸出目錄項(xiàng))中找到一個(gè)指向那個(gè)目錄的指針,而該指針指向文件中的某節(jié)。
跟在各種頭后面我們就發(fā)現(xiàn)各個(gè)“節(jié)”了,它們都由“節(jié)頭”引導(dǎo)。本質(zhì)上講,各節(jié)中的內(nèi)容才是你執(zhí)行一個(gè)程序真正需要的東西,所有頭和目錄這些東西只是為了幫助你找到它們。
每節(jié)都含有和對(duì)齊、包含什么樣的數(shù)據(jù)(如“已初始化數(shù)據(jù)”等等)、是否能共享等有關(guān)的一些標(biāo)記,還有就是數(shù)據(jù)本身。大多數(shù)(并非所有)節(jié)都含有一個(gè)或多個(gè)可通過(guò)可選頭的“數(shù)據(jù)目錄”數(shù)組中的項(xiàng)來(lái)參見(jiàn)的目錄,如輸出函數(shù)目錄和基址重定位目錄等。無(wú)目錄形式的內(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)時(shí)是“NE”格式⑥)時(shí)就廣為人知了。根原來(lái)是用于OS/2⑦系統(tǒng)的可執(zhí)行文件的,也用于自解壓檔案文件和其它的應(yīng)用程序。對(duì)于PE文件來(lái)說(shuō),它是一個(gè)總是由大約100個(gè)字節(jié)所組成的和MS-DOS 2.0兼容的可執(zhí)行體,用來(lái)輸出象“this program needs windows NT”之類的錯(cuò)誤信息。
你可以通過(guò)確認(rèn)DOS-頭部分是否為一個(gè)IMAGE_DOS_HEADER(DOS頭)結(jié)構(gòu)來(lái)認(rèn)出DOS-根,它的前兩個(gè)字節(jié)必須為連續(xù)的兩個(gè)字母“MZ”(有一個(gè)#define IMAGE_DOS_SIGNATURE的定義是針對(duì)這個(gè)WORD單元的)。
你可以通過(guò)跟在后面的簽名來(lái)將一個(gè)PE二進(jìn)制文件和其它含有根的二進(jìn)制文件區(qū)分開(kāi)來(lái),跟在后面的簽名可由頭成員'e_lfanew'(它是從字節(jié)偏移地址60處開(kāi)始的,有32字節(jié)長(zhǎng))所設(shè)定的偏移地址找到。對(duì)于OS/2系統(tǒng)和Windows系統(tǒng)的二進(jìn)制文件來(lái)說(shuō),簽名是一個(gè)16位的word單元;對(duì)于PE文件來(lái)說(shuō),它是一個(gè)按照8位字節(jié)邊界對(duì)齊的32位的longword單元,并且IMAGE_NT_SIGNATURE(NT簽名)的值已由#defined定義為0x00004550(即字母“PE/0/0”----譯者)。
四、文件頭(File Header)
-------------------------
要到達(dá)IMAGE_FILE_HEADER(文件頭)結(jié)構(gòu),請(qǐng)先確認(rèn)DOS-頭“MZ”(起始的2個(gè)字節(jié)),然后找出DOS-根的頭部的成員“e_lfanew”,并從文件開(kāi)始處跳過(guò)那么多的字節(jié)。在核實(shí)你在那里找到的簽名后,IMAGE_FILE_HEADER(文件頭)結(jié)構(gòu)的文件頭就緊跟其后開(kāi)始了,下面我們將從頭至尾的介紹其成員。
1)第一個(gè)成員是“Machine(機(jī)器)”,一個(gè)16位的值,用來(lái)指出該二進(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é)的問(wèn)題。
3)下一個(gè)成員是時(shí)間戳“TimeDateStamp”(32位),用來(lái)給出文件建立的時(shí)間。即使它的“官方”版本號(hào)沒(méi)有改變,你也可通過(guò)這個(gè)值來(lái)區(qū)分同一個(gè)文件的不同版本。(除了同一個(gè)文件的不同版本之間必須唯一,時(shí)間戳的格式?jīng)]有明文規(guī)定,但似乎是按照UTC?時(shí)間“從1970年1月1日00:00:00算起的秒數(shù)值”----也就是大多數(shù)C語(yǔ)言編譯器給time_t標(biāo)志使用的格式。)
這個(gè)時(shí)間戳是用來(lái)綁定各個(gè)輸入目錄的,我們稍后再討論它。
警告:有一些鏈接器往往將時(shí)間戳設(shè)為荒唐的值,而不是如前所述的time_t格式的鏈接時(shí)間。
4-5)成員“PointerToSymbolTable(符號(hào)表指針)”和成員“NumberOfSymbols(符號(hào)數(shù))”(都是32位)都用于調(diào)試信息的。我不知道該怎樣去解讀它,并且我發(fā)現(xiàn)該指針的值總為0。
6)成員“SizeOfOptionalHeader(可選頭大小)”(16位)只是“IMAGE_OPTIONAL_HEADER(可選頭)”項(xiàng)的大小,你能用它去驗(yàn)證PE文件結(jié)構(gòu)的正確性。
7)成員“Characteristics(特性)”是一個(gè)16位的,由許多標(biāo)志位形成的集合組成,但大多數(shù)標(biāo)志位只對(duì)目標(biāo)文件和庫(kù)文件有效。具體如下:
位0 IMAGE_FILE_RELOCS_STRIPPED(重定位被剝離文件) 表示如果文件中沒(méi)有重定位信息,該位置1,這就表明各節(jié)的重定位信息都在它們各自的節(jié)中;可執(zhí)行文件不使用該位,它們的重定位信息放在下面將要描述的“base relocation”(基址重定位)目錄中。
位1 IMAGE_FILE_EXECUTABLE_IMAGE(可執(zhí)行映象文件) 表示如果文件是一個(gè)可執(zhí)行文件,也即不是目標(biāo)文件或者庫(kù)文件時(shí),置1。如果鏈接器嘗試創(chuàng)建一個(gè)可執(zhí)行文件,卻因?yàn)橐恍┰蚴×耍⒈4嬗诚褚员阆麓卫缭隽挎溄訒r(shí)使用,此時(shí)此標(biāo)志位也可能置1。
位2 IMAGE_FILE_LINE_NUMS_STRIPPED(行數(shù)被剝離文件) 表示如果行數(shù)信息被剝除,此位置1;此位也不用于可執(zhí)行文件。
位3 IMAGE_FILE_LOCAL_SYMS_STRIPPED(本地符號(hào)被剝離文件) 表示如果文件中沒(méi)有關(guān)于本地符號(hào)的信息時(shí),此位置1(此位也不用于可執(zhí)行文件)。
位4 IMAGE_FILE_AGGRESIVE_WS_TRIM(強(qiáng)行工作集修剪文件) 表示如果操作系統(tǒng)被假定為:通過(guò)將正在運(yùn)行的進(jìn)程(它所使用的內(nèi)存數(shù)量)強(qiáng)行的頁(yè)清除來(lái)修剪它的工作集時(shí),此位置1。如果一進(jìn)程是大部分時(shí)間處于等待,且一天中僅被喚醒一次的演示性的應(yīng)用程序之類時(shí),此位也應(yīng)該被置1。
位7 IMAGE_FILE_BYTES_REVERSED_LO(低字節(jié)變換文件)和 位15IMAGE_FILE_BYTES_REVERSED_HI(高字節(jié)變換文件) 表示如果一文件的字節(jié)序不是機(jī)器所預(yù)期的形式,因此它在讀入前必須調(diào)換字節(jié)時(shí),此位置1。這樣做對(duì)可執(zhí)行文件是不可靠的(操作系統(tǒng)期望可執(zhí)行文件都已經(jīng)被正確地按字節(jié)排整齊了)。
位8 IMAGE_FILE_32BIT_MACHINE(32位機(jī)器文件) 表示如果使用的機(jī)器被期望為32位的機(jī)器時(shí),此位置1。現(xiàn)在的應(yīng)用程序總將此位置1;NT5系統(tǒng)可能工作不同。
位9 IMAGE_FILE_DEBUG_STRIPPED(調(diào)試信息被剝離文件) 表示如果文件中沒(méi)有調(diào)試信息,此位置1。此位可執(zhí)行文件不用。按照其它信息([6])(這里指的是參考書(shū)目中的第[6]種----譯者注),此位被稱作“恒定”,并且當(dāng)一個(gè)映象文件只有在被裝入優(yōu)先的裝入地址才能運(yùn)行(亦即:此文件不可重定位)時(shí),此位置1。
位10 IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP(移動(dòng)介質(zhì)文件從交換文件運(yùn)行) 表示如果一個(gè)應(yīng)用程序不可以從可移動(dòng)的介質(zhì),如軟盤(pán)或CD-ROM上運(yùn)行時(shí),此位置1。在這種情況下,建議操作系統(tǒng)將文件復(fù)制到交換文件并從那里執(zhí)行。
位11 IMAGE_FILE_NET_RUN_FROM_SWAP(網(wǎng)絡(luò)文件從交換文件運(yùn)行) 表示如果一個(gè)應(yīng)用程序不可以從網(wǎng)絡(luò)上運(yùn)行時(shí),此位置1。在這種情況下,建議操作系統(tǒng)將文件復(fù)制到交換文件并從那里執(zhí)行。
位12 IMAGE_FILE_SYSTEM(系統(tǒng)文件) 表示如果文件是一個(gè)象驅(qū)動(dòng)程序那樣的系統(tǒng)文件,此位置1。此位可執(zhí)行文件不用;我所見(jiàn)過(guò)的所有NT系統(tǒng)的驅(qū)動(dòng)程序也不用。
位13 IMAGE_FILE_DLL(DLL文件) 表示如果文件是一個(gè)DLL文件時(shí),此位置1。
位14 IMAGE_FILE_UP_SYSTEM_ONLY(僅但處理器系統(tǒng)的文件) 表示如果文件不設(shè)計(jì)運(yùn)行在多處理器系統(tǒng)上(也就是說(shuō),因?yàn)榇宋募?yán)格地依賴單一處理器的一些方式工作,所以它會(huì)發(fā)生沖突)時(shí),此位置1。
五、相對(duì)虛擬地址(Relative Virtual Addresses)
---------------------------------------------
PE格式大量地使用所謂的RVA(相對(duì)虛擬地址)。一個(gè)RVA,亦即一個(gè)“Relative Virtual Addresses(相對(duì)虛擬地址)”,是在你不知道基地址時(shí),被用來(lái)描述一個(gè)內(nèi)存地址的。它是需要加上基地址才能獲得線性地址的數(shù)值。基地址就是PE映象文件被裝入內(nèi)存的地址,并且可能會(huì)隨著一次又一次的調(diào)用而變化。
例如:假若一個(gè)可執(zhí)行文件被裝入的地址是0x400000,并且從RVA 0x1560處開(kāi)始執(zhí)行,那么有效的執(zhí)行開(kāi)始處將位于0x401560地址處。假若它被裝入的地址為0x100000,那么執(zhí)行開(kāi)始處就位于0x101560地址處。
因?yàn)镻E-文件中的各部分(各節(jié))不需要像已載入的映象文件那樣對(duì)齊,事情變得復(fù)雜起來(lái)。例如,文件中的各節(jié)常按照512(十六進(jìn)制的0x200----譯者注)字節(jié)邊界對(duì)齊,而已載入的映象文件則可能按照4096(十六進(jìn)制的0x1000----譯者注)字節(jié)邊界對(duì)齊。參見(jiàn)下面的“SectionAlignment(節(jié)對(duì)齊)”和“FileAlignment(文件對(duì)齊)”。
因此,為了在PE文件中找到一個(gè)特定RVA地址的信息,你得按照文件已被載入時(shí)的那樣來(lái)計(jì)算偏移量,但要按照文件的偏移量來(lái)跳過(guò)。
試舉一例,假若你已知道執(zhí)行開(kāi)始處位于RVA 0x1560地址處,并且想從那里開(kāi)始的代碼處反匯編。為了從文件中找到這個(gè)地址,你得先查明在RAM(內(nèi)存)中各節(jié)是按照4096字節(jié)對(duì)齊的,并且“.code”節(jié)是從RVA 0x1000地址處開(kāi)始,有16384字節(jié)長(zhǎng);然后你才知道RVA 0x1560地址位于此節(jié)的偏移量0x560處。你還要查明在文件中那節(jié)是按512字節(jié)邊界對(duì)齊,且“.code”節(jié)在文件中從偏移量0x800處開(kāi)始,然后你就知道在文件中代碼的執(zhí)行開(kāi)始處就在0x800+0x560=0xd60字節(jié)處。
然后你反匯編它并發(fā)現(xiàn)訪問(wèn)一個(gè)變量的線性地址位于0x1051d0處。二進(jìn)制文件的線性地址在裝入時(shí)將被重定位,并常被假定使用的是優(yōu)先載入地址。因?yàn)槟阋巡槊鲀?yōu)先載入地址為0x100000,因此我們可開(kāi)始處理RVA 0x51d0了。因數(shù)據(jù)節(jié)開(kāi)始于RVA 0x5000處,且有2048字節(jié)長(zhǎng),所以它處于數(shù)據(jù)節(jié)中。又因數(shù)據(jù)節(jié)在文件中開(kāi)始于偏移量0x4800處,所以該變量就可以在文件中的0x4800+0x51d0-0x5000=0x49d0處找到。
六、可選頭(Optional Header)
----------------------------
緊跟在文件頭后面的就是IMAGE_OPTIONAL_HEADER(盡管它名叫“可選頭”,它卻一直都在那里)。它包含有怎樣去準(zhǔn)確處理PE文件的信息。我們也將從頭至尾的介紹其成員。
1)第一個(gè)16位的word單元叫“Magic(魔數(shù))”,就我目前所觀察過(guò)的PE文件而言,它的值總是0x010b。
2-3)下面2個(gè)字節(jié)是創(chuàng)建此文件的鏈接器的版本(‘MajorLinkerVersion’,“鏈接器主版本號(hào)”和‘MinorLinkerVersion’,“鏈接器小版本號(hào)”)。這兩個(gè)值又是不可靠的,并不能總是正確地反映鏈接器的版本號(hào)。(有好幾個(gè)鏈接器根本就不設(shè)置這個(gè)值。)況且,你可想象一下,你連使用的是“什么”鏈接器都不知道,知道它的版本號(hào)又有什么作用呢?
4-6)下面3個(gè)longword(每個(gè)32位)分別用來(lái)設(shè)定可執(zhí)行代碼的大小(“SizeOfCode”)、已初始化數(shù)據(jù)的大小(“SizeOfInitializedData”,所謂的“數(shù)據(jù)段”)、以及未初始化數(shù)據(jù)的大小(“SizeOfUninitializedData”,所謂的“bss?段”)。這些值也是不可靠的(例如:數(shù)據(jù)段實(shí)際上可能會(huì)被編譯器或者鏈接器分成好幾段),并且你可以通過(guò)查看可選頭后面的各個(gè)“節(jié)”來(lái)獲得更準(zhǔn)確的大小。
7)下一個(gè)32位值是RVA。這個(gè)RVA是代碼入口點(diǎn)的偏移量(‘AddressOfEntryPoint’,“入口點(diǎn)地址”)。執(zhí)行將從這里開(kāi)始,它可以是:例如DLL文件的LibMain()的地址,或者一個(gè)程序的開(kāi)始代碼(這里相應(yīng)的叫main())的地址,或者驅(qū)動(dòng)程序的DriverEntry()的地址。如果你敢于“手工”裝載映象文件,那么在你完成所有的修正和重定位后,你可以從這個(gè)地址開(kāi)始執(zhí)行你的進(jìn)程。
8-9)下兩個(gè)32位值分別是可執(zhí)行代碼的偏移值(‘BaseOfCode’,“代碼基址”)和已初始化數(shù)據(jù)的偏移值(‘BaseOfData’,“數(shù)據(jù)基址”),兩個(gè)都是RVA,并且兩個(gè)對(duì)我們來(lái)說(shuō)都沒(méi)有多少意義,因?yàn)槟憧梢酝ㄟ^(guò)查看可選頭后面的各個(gè)“節(jié)”來(lái)獲得更可靠的信息。
未初始化的數(shù)據(jù)沒(méi)有偏移量,正因?yàn)樗鼪](méi)有初始化,所以在映象文件中提供這些數(shù)據(jù)是沒(méi)有用處的。
10)下一項(xiàng)是個(gè)32位值,提供整個(gè)二進(jìn)制文件包括所有頭的優(yōu)先(線性)載入地址(‘ImageBase’,“映象文件基址”)。這是一個(gè)文件已被鏈接器重定位后的地址(總是64 KB的倍數(shù))。如果二進(jìn)制文件事實(shí)上能被載入這個(gè)地址,那么加載器就不用再重定位文件了,也就節(jié)省了一些載入時(shí)間。
優(yōu)先載入地址在另一個(gè)映象文件已被先載入那個(gè)地址(“地址沖突”,在當(dāng)你載入好幾個(gè)全部按照鏈接器的缺省值重定位的DLL文件時(shí)經(jīng)常發(fā)生)時(shí),或者該內(nèi)存已被用于其它目的(堆棧、malloc()、未初始化數(shù)據(jù)、或不管是什么)時(shí),就不能用了。在這些情況下,映象文件必須被載人其它的地址,并且需要重定位(參見(jiàn)下面的“重定位目錄”)。如果是一個(gè)DLL文件,這么做還會(huì)產(chǎn)生其它問(wèn)題,因?yàn)榇藭r(shí)的“綁定輸入”已不再有效,所以使用DLL的二進(jìn)制文件必須被修正----參見(jiàn)下面的“輸入目錄”一節(jié)。
11-12)下兩個(gè)32位值分別是RAM中的“SectionAlignment”(當(dāng)映象文件已被載入后,意為“節(jié)對(duì)齊”)和文件中的“FileAlignment”(文件對(duì)齊),它們都是PE文件的各節(jié)的對(duì)齊值。這兩個(gè)值通常都是32,或者是:FileAlignment為512,SectionAlignment為4096。節(jié)會(huì)在以后討論。
13-14)下2個(gè)16位word單元都是預(yù)期的操作系統(tǒng)版本信息(MajorOperatingSystemVersion,“操作系統(tǒng)主版本號(hào)”)和(MinorOperatingSystemVersion,“操作系統(tǒng)小版本號(hào)”)[它們都使用微軟自己書(shū)面確定的名字]。這個(gè)版本信息應(yīng)該為操作系統(tǒng)的版本號(hào)(如NT 或 Win95),而不是子系統(tǒng)的版本信息(如Win32)。版本信息常常被不提供或者錯(cuò)誤提供。很明顯的,加載器并不使用它們。
15-16)下2個(gè)16位word單元都是本二進(jìn)制文件的版本信息('MajorImageVersion'“映象文件主版本號(hào)”和
'MinorImageVersion'“映象文件小版本號(hào)”)。很多鏈接器不正確地設(shè)定這個(gè)信息,許多程序員也懶得提供這些,因此即便存在這樣的信息,你最好也不要信賴它。
17-18)下2個(gè)16位word單元都是預(yù)期的子系統(tǒng)版本信息('MajorSubsystemVersion'“子系統(tǒng)主版本號(hào)”和'MinorSubsystemVersion'“子系統(tǒng)小版本號(hào)”)。此信息應(yīng)該為Win32或POSIX的版本信息,因?yàn)楹苊黠@的,16位程序或OS/2程序都不是PE格式的。子系統(tǒng)版本應(yīng)該被正確的提供,因?yàn)樗?#8220;會(huì)”被檢驗(yàn)和使用:
如果一個(gè)應(yīng)用程序是一個(gè)Win32-GUI應(yīng)用程序并運(yùn)行于NT4系統(tǒng)之上,而且子系統(tǒng)版本“不是”4.0的話,那么對(duì)話框就不會(huì)是以3D形式顯示,并且一些其它的特征也只會(huì)按“老式”的方式工作,因?yàn)榇藨?yīng)用程序預(yù)期是在NT 3.51系統(tǒng)上運(yùn)行的,而NT 3.51系統(tǒng)上只有程序管理器而沒(méi)有瀏覽器、等等,于是NT 4.0系統(tǒng)就盡可能地仿照那個(gè)系統(tǒng)的行為來(lái)運(yùn)行程序。
19)然后,我們便碰到32位的“Win32VersionValue”(Win32版本值)。我不清楚它有什么作用。在我所觀察過(guò)的PE文件中,它全部都為0。
20)下一個(gè)是32位值,給出映象文件將要使用的內(nèi)存數(shù)量,單位為字節(jié)(‘SizeOfImage’,“映象文件大小”)。如果是按照“SectionAlignment”對(duì)齊的,它就是所有頭和節(jié)的長(zhǎng)度的總和。它提示加載器,為了載入映象文件需要多少頁(yè)。
21)下一個(gè)是32位值,給出所有頭的總長(zhǎng)度,包括數(shù)據(jù)目錄和節(jié)頭(‘SizeOfHeaders’,“頭的大小”)。同時(shí),它也是從文件的開(kāi)頭到第一節(jié)的原始數(shù)據(jù)的偏移量。
22)然后,我們發(fā)現(xiàn)一個(gè)32位的校驗(yàn)和(“CheckSum”)。這個(gè)校驗(yàn)和,對(duì)于當(dāng)前的NT版本,只在映象文件是NT驅(qū)動(dòng)程序時(shí)才校驗(yàn)(如果校驗(yàn)和不正確,驅(qū)動(dòng)就將裝載失敗)。對(duì)于其他的二進(jìn)制文件形式,校驗(yàn)和不需提供并且可能為0。計(jì)算校驗(yàn)和的算法是微軟的私產(chǎn),他們不會(huì)告訴你的。但是,Win32 SDK的好幾個(gè)工具都會(huì)計(jì)算和/或補(bǔ)正一個(gè)有效的校驗(yàn)和,而且imagehelp.dll中的CheckSumMappedFile()函數(shù)也會(huì)做同樣的工作。
使用校驗(yàn)和的目的是為了防止載入無(wú)論如何都會(huì)沖突的、已損壞的二進(jìn)制文件----況且一個(gè)沖突的驅(qū)動(dòng)程序會(huì)導(dǎo)致一個(gè)BSOD?錯(cuò)誤,因此最好根本就不載入這樣的壞文件。
23)然后,就到了一個(gè)16位的word單元“Subsystem”(子系統(tǒng)),用來(lái)說(shuō)明映象文件應(yīng)運(yùn)行于什么樣的NT子系統(tǒng)之上:
IMAGE_SUBSYSTEM_NATIVE (1)
二進(jìn)制文件不需要子系統(tǒng)。用于驅(qū)動(dòng)程序。
IMAGE_SUBSYSTEM_WINDOWS_GUI (2)
映象文件是一個(gè)Win32二進(jìn)制圖象文件。(它還是能用AllocConsole()打開(kāi)一個(gè)控制臺(tái)界面,但在開(kāi)始時(shí)卻不能自動(dòng)地打開(kāi)。)
IMAGE_SUBSYSTEM_WINDOWS_CUI (3)
二進(jìn)制文件是一個(gè)Win32控制臺(tái)界面二進(jìn)制文件。(它將在開(kāi)始時(shí)按照缺省值打開(kāi)一個(gè)控制臺(tái),或者繼承其父程序的控制臺(tái)。)
IMAGE_SUBSYSTEM_OS2_CUI (5)
二進(jìn)制文件是一個(gè)OS/2控制臺(tái)界面二進(jìn)制文件。(OS/2控制臺(tái)界面二進(jìn)制文件是OS/2格式,因此此值在PE文件中很少使用。)
IMAGE_SUBSYSTEM_POSIX_CUI (7)
二進(jìn)制文件使用POSIX?控制臺(tái)子系統(tǒng)。
Windows 95的二進(jìn)制文件總是使用Win32子系統(tǒng),因此它的二進(jìn)制文件的合法值只有2和3;我不知道windows 95的“原”二進(jìn)制文件是否可能(會(huì)有其它值----譯者添加,僅供參考)。
24)下一個(gè)是16位的值,指明,如果是DLL文件,何時(shí)調(diào)用DLL文件的入口點(diǎn)(‘DllCharacteristics’,“DLL特性”)。此值似乎不用;很明顯地,DLL文件總是被通報(bào)所有的情況。
如果位0被置1,DLL文件被通知進(jìn)程附加(亦即DLL載入)。
如果位1被置1,DLL文件被通知線程附加(亦即線程終止)。
如果位2被置1,DLL文件被通知線程附加(亦即線程創(chuàng)建)。
如果位3被置1,DLL文件被通知進(jìn)程附加(亦即DLL卸載)。
25-28)下4個(gè)32位值分別是:保留棧的大小(SizeOfStackReserve)、初始時(shí)指定棧大小(SizeOfStackCommit)、保留堆的大小(SizeOfHeapReserve)和指定堆大小(SizeOfHeapCommit)。
“保留的”數(shù)量是保留給特定目的的地址空間(不是真正的RAM);在程序開(kāi)始時(shí),“指定的”數(shù)量是指在RAM中實(shí)際分配的大小。如果需要的話,“指定的”值也是指定的堆或棧用來(lái)增加的數(shù)量。(有資料說(shuō),不管“SizeOfStackCommit”的值是多少,棧都是按頁(yè)增加的。我沒(méi)有驗(yàn)證過(guò)。)
因此,舉例來(lái)說(shuō),如一個(gè)程序的保留堆有1 MB,指定堆為64 KB,那么啟動(dòng)時(shí)堆的大小為64 KB,并且保證可以擴(kuò)大到1 MB。堆將按64 KB一塊來(lái)增加。
“堆”在本文中是指主要(缺省)堆。如果它愿意的話,一個(gè)進(jìn)程可創(chuàng)建很多堆。
棧是指第一個(gè)線程的棧(啟動(dòng)main()的那個(gè))。進(jìn)程可以創(chuàng)建很多線程,每個(gè)線程都有自己的棧。
DLL文件沒(méi)有自己的堆或棧,所以它們的映象文件忽略這些值。我不知道驅(qū)動(dòng)程序是否有它們自己的堆或棧,但我認(rèn)為它們沒(méi)有。
29)堆和棧的這些描述之后,我們就發(fā)現(xiàn)一個(gè)32位的“LoaderFlags(加載器標(biāo)志)”,我沒(méi)有找到它的任何有用的描述。我只發(fā)現(xiàn)一篇時(shí)新的關(guān)于設(shè)置此標(biāo)志位的短文,說(shuō)設(shè)置此標(biāo)志位會(huì)在映象文件載入后自動(dòng)地調(diào)用一個(gè)斷點(diǎn)或者調(diào)試器;可似乎不正確。
30)接著我們會(huì)發(fā)現(xiàn)32位的“NumberOfRvaAndSizes(Rva數(shù)和大小)”,它是緊隨其后的目錄的有效項(xiàng)的數(shù)目。我已發(fā)現(xiàn)此值不可靠;你也許希望用常量IMAGE_NUMBEROF_DIRECTORY_ENTRIES(映象文件目錄項(xiàng)數(shù)目)來(lái)代替它,或者用它們中的較小者。
NumberOfRvaAndSizes之后是一個(gè)IMAGE_NUMBEROF_DIRECTORY_ENTRIES (16)(映象文件目錄項(xiàng)數(shù)目)個(gè)IMAGE_DATA_DIRECTORY(映象文件數(shù)據(jù)目錄)數(shù)組。這些目錄中的每一個(gè)目錄都描述了一個(gè)特定的、位于目錄項(xiàng)后面的某一節(jié)中的信息的位置(32位的RVA,叫“VirtualAddress(虛擬地址)”)和大小(也是32位,叫“Size(大小)”)。
例如,安全目錄能在索引4中給定的RVA處發(fā)現(xiàn)并具有索引4中給定的大小。
稍后我將討論我知道其結(jié)構(gòu)的目錄。
已定義的目錄及索引有:
IMAGE_DIRECTORY_ENTRY_EXPORT (0)
輸出符號(hào)目錄;大多用于DLL文件。
后面介紹。
IMAGE_DIRECTORY_ENTRY_IMPORT (1)
輸入符號(hào)目錄;參見(jiàn)后面。
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)
基址重定位表 - 參見(jiàn)后面。
IMAGE_DIRECTORY_ENTRY_DEBUG (6)
調(diào)試目錄 - 內(nèi)容編譯器相關(guān)。此外, 許多編譯器將編譯信息填入代碼節(jié),并不為此創(chuàng)建一個(gè)單獨(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)
線程級(jí)局部存儲(chǔ)目錄 - 結(jié)構(gòu)不詳;包含聲明為“__declspec(thread)”的變量, 也就是每線程的全局變量。
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10)
載入配置目錄 - 結(jié)構(gòu)和用途不詳。
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11)
綁定輸入目錄 - 參見(jiàn)輸入目錄的描述。
IMAGE_DIRECTORY_ENTRY_IAT (12)
輸入地址表 - 參見(jiàn)輸入目錄的描述。
試舉一例,如果我們?cè)谒饕?中發(fā)現(xiàn)2個(gè)longword:0x12000 和 33,并且載入地址為0x10000,那么我們就知道版權(quán)信息數(shù)據(jù)位于地址0x10000+0x12000(在哪個(gè)節(jié)都有可能)處,并且版權(quán)信息有33字節(jié)長(zhǎng)。
如果二進(jìn)制文件中沒(méi)有使用特殊類型的目錄,Size(大小)和VirtualAddress(虛擬地址)的值就都為0。
七、節(jié)目錄(Section directories)
---------------------------------
節(jié)由兩個(gè)主要部分組成:首先,是一個(gè)節(jié)描述(IMAGE_SECTION_HEADER[意為“節(jié)頭”]類型的),然后是原始的節(jié)數(shù)據(jù)。因此,我們會(huì)在數(shù)據(jù)目錄后發(fā)現(xiàn)一“NumberOfSections”個(gè)節(jié)頭組成的數(shù)組,它們按照各節(jié)的RVA排序。
節(jié)頭包括:
1)一個(gè)IMAGE_SIZEOF_SHORT_NAME (8)(意為“短名的大小”)個(gè)字節(jié)的數(shù)組,形成節(jié)的名稱(ASCII形式)。如果所有的8位都被用光,該字符串就沒(méi)有0結(jié)束符!典型的名稱象“.data”或“.text”或“.bss”形式。開(kāi)頭的“.”不是必須的,名稱也可能為“CODE”或“IAT”或類似的形式。
請(qǐng)注意:并不是所有的名稱都和節(jié)中的內(nèi)容相關(guān)。一個(gè)名叫“.code”的節(jié)可能包含也可能不包含可執(zhí)行代碼;它還可能只包含輸入地址表;它也可能包含代碼“和”地址表“和”未初始化數(shù)據(jù)。要找到節(jié)中的信息,你必須通過(guò)可選頭的數(shù)據(jù)目錄來(lái)查詢它。既不要過(guò)分相信它的名稱,也不要以為節(jié)的原始數(shù)據(jù)會(huì)從節(jié)的開(kāi)頭就開(kāi)始。
2)IMAGE_SECTION_HEADER(“節(jié)頭”)的下一個(gè)成員是一個(gè)32位的、“PhysicalAddress(物理地址)”和“VirtualSize(虛擬大小)”組成的共用體。在目標(biāo)文件中,它是內(nèi)容重定位到的地址;在可執(zhí)行文件中,它是內(nèi)容的大小。事實(shí)上,此域似乎沒(méi)被使用;因?yàn)橛械逆溄悠鬏斎氪笮。械逆溄悠鬏斎氲刂罚疫€發(fā)現(xiàn)有一個(gè)鏈接器輸入0,而所有的可執(zhí)行文件都運(yùn)行如風(fēng)。
3)下一個(gè)成員是“VirtualAddress(虛擬地址)”,是一個(gè)32位的值,用來(lái)保存載入RAM(內(nèi)存)后,節(jié)中數(shù)據(jù)的RVA。
4)然后,我們到了32位的“SizeOfRawData”(意味“原始數(shù)據(jù)大小”),它表示節(jié)中數(shù)據(jù)被大約到下一個(gè)“FileAlignment”的整數(shù)倍時(shí)節(jié)的大小。
5)下一個(gè)是“PointerToRawData”(意味“原始數(shù)據(jù)指針”,32位),它特別有用,因?yàn)樗菑奈募拈_(kāi)頭到節(jié)中數(shù)據(jù)的偏移量。如果它為0,那么節(jié)的數(shù)據(jù)就不包含在文件中,并且要在載入時(shí)才定。
6-9)然后,我們得到“PointerToRelocations”(意味“重定位指針”,32位)和“PointerToLinenumbers”(意味“行數(shù)指針”,也是32位),以及“NumberOfRelocations”(意味“重定位數(shù)”,16位)和“NumberOfLinenumbers”(意味“行數(shù)數(shù)”,也是16位)。所以這些都是只用于目標(biāo)文件的信息。可執(zhí)行文件擁有一個(gè)特殊的基址重定位目錄,并且行數(shù)信息(如果真的存在的話)通常包含在有一個(gè)特殊目的的調(diào)試段中或者別的什么地方。
10)節(jié)頭的最后一個(gè)成員是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í)行開(kāi)始前即取得已定義值的數(shù)據(jù)。換言之:文件中節(jié)的數(shù)據(jù)就是有意義的。
如果位7 IMAGE_SCN_CNT_UNINITIALIZED_DATA(含有未初始化數(shù)據(jù)的節(jié))被置1, 表示節(jié)中包含未初始化數(shù)據(jù),并需于執(zhí)行開(kāi)始前被初始化為全0。這通常是BSS節(jié)。
如果位9 IMAGE_SCN_LNK_INFO(鏈接器信息節(jié))被置1, 表示節(jié)中不包含映象數(shù)據(jù),只有一些注釋、描述或者其他的文檔。這些信息是目標(biāo)文件的一部分,并有可能是提供給鏈接器的信息,比如需要哪些庫(kù)文件。
如果位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)志位,參見(jiàn)后面。同樣,它也明顯的不是用來(lái)指示16位信息的,因?yàn)樗灿幸粋€(gè)IMAGE_SCN_MEM_16BIT定義。此位的含義不明。
如果位18 IMAGE_SCN_MEM_LOCKED(內(nèi)存被鎖節(jié))被置1, 表示節(jié)不應(yīng)該被從內(nèi)存中移除?抑或表明沒(méi)有重定位信息?此位的含義不明。
如果位19 IMAGE_SCN_MEM_PRELOAD(內(nèi)存預(yù)載入節(jié))被置1,表示節(jié)在執(zhí)行開(kāi)始前應(yīng)該被頁(yè)載入?此位的含義不明。
位20至23 指定我沒(méi)有找到信息的對(duì)齊。諸如#defines IMAGE_SCN_ALIGN_16BYTES之類。我曾經(jīng)見(jiàn)過(guò)的唯一值為0,是16位的缺省對(duì)齊。 我懷疑它們是庫(kù)之類文件的目標(biāo)對(duì)齊。
如果位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)程啟動(dòng)后就不需要了。它是,舉例來(lái)說(shuō),含有重定位信息的情況。我曾經(jīng)見(jiàn)過(guò)它也用于只執(zhí)行一次的驅(qū)動(dòng)和服務(wù)程序的啟動(dòng)例程,還用于輸入目錄。
如果位26 IMAGE_SCN_MEM_NOT_CACHED(內(nèi)存不緩存節(jié))被置1,表示節(jié)中的數(shù)據(jù)不應(yīng)該被緩存。不要問(wèn)我為什么不。這是不是意味著關(guān)掉2級(jí)緩存?
如果位27 IMAGE_SCN_MEM_NOT_PAGED(內(nèi)存不可頁(yè)換出節(jié))被置1,表示節(jié)中的數(shù)據(jù)不應(yīng)該頁(yè)換出。它對(duì)驅(qū)動(dòng)程序有意義。
如果位28 IMAGE_SCN_MEM_SHARED(內(nèi)存共享節(jié))被置1,表示節(jié)中的數(shù)據(jù)在映象文件的所有正在運(yùn)行的實(shí)例中共享。如果它是,例如DLL文件的未初始化數(shù)據(jù),那么DLL的所有正在運(yùn)行的實(shí)例程序在任何時(shí)候都將擁有相同的變量?jī)?nèi)容。
注意:只有第一個(gè)實(shí)例的節(jié)被初始化。
含有代碼的節(jié)總是被共享寫(xiě)時(shí)拷貝(copy-on-write)(亦即:如果重定位必不可少,那么共享就不工作)。(譯注:“寫(xiě)時(shí)拷貝”的譯法也許根本就是錯(cuò)誤的,但我一時(shí)找不到更準(zhǔn)確的翻譯,也不清楚其具體含義,只能以此充數(shù)了。希望知情著指點(diǎn)。)
如果位29 IMAGE_SCN_MEM_EXECUTE(內(nèi)存可執(zhí)行節(jié))被置1,表示進(jìn)程對(duì)節(jié)的內(nèi)存有“執(zhí)行”的存取權(quán)限。
如果位30 IMAGE_SCN_MEM_READ(內(nèi)存可讀節(jié))被置1,表示進(jìn)程對(duì)節(jié)的內(nèi)存有“讀”的存取權(quán)限。
如果位31 IMAGE_SCN_MEM_WRITE(內(nèi)存可寫(xiě)節(jié))被置1,表示進(jìn)程對(duì)節(jié)的內(nèi)存有“寫(xiě)”的存取權(quán)限。
在節(jié)頭之后,我們就會(huì)發(fā)現(xiàn)節(jié)本身。在文件中,它們按照“FileAlignment”(文件對(duì)齊)的字節(jié)數(shù)對(duì)齊(也就是說(shuō),在可選頭之后和每個(gè)節(jié)的數(shù)據(jù)之后將要填充一些字節(jié))并按照它們的RVA排序。在載入后(內(nèi)存中), 它們按照“SectionAlignment”(節(jié)對(duì)齊)的字節(jié)數(shù)對(duì)齊。
試舉一例,如果可選頭在文件的偏移量981處結(jié)束,“FileAlignment”(文件對(duì)齊)的值為512,那么第一個(gè)節(jié)將于1024字節(jié)處開(kāi)始。注意:你可通過(guò)“PointerToRawData”(原始數(shù)據(jù)指針)或者“VirtualAddress”(虛擬地址)的值來(lái)找到各節(jié),因此實(shí)際上根本沒(méi)必要在對(duì)齊上小題大做。
試畫(huà)映象文件的全圖如下:
+-------------------+
| DOS-根 |
+-------------------+
| 文件頭 |
+-------------------+
| 可選頭 |
|- - - - - - - - - -|
| |----------------+
| 數(shù)據(jù)目錄 | |
| | |
| (指向節(jié)中 |-------------+ |
| 目錄的RVA) | | |
| |---------+ | |
| | | | |
+-------------------+ | | |
| |-----+ | | |
| 節(jié)頭 | | | | |
| (指向節(jié) |--+ | | | |
| 邊界的RVA) | | | | | |
+-------------------+<-+ | | | |
| | | <-+ | |
| 節(jié)數(shù)據(jù) 1 | | | |
| | | <-----+ |
+-------------------+<----+ |
| | |
| 節(jié)數(shù)據(jù) 2 | |
| | <--------------+
+-------------------+
每個(gè)節(jié)都有一個(gè)節(jié)頭,并且每個(gè)數(shù)據(jù)目錄都會(huì)指向其中的一個(gè)節(jié)(幾個(gè)數(shù)據(jù)目錄有可能指向同一個(gè)節(jié),而且也可能有的節(jié)沒(méi)有數(shù)據(jù)目錄指向它們)。
八、節(jié)的原始數(shù)據(jù)(Sections' raw data)
--------------------------------------
1.概述(general)
-------
所有的節(jié)在載入內(nèi)存后都按“SectionAlignment”(節(jié)對(duì)齊)對(duì)齊,在文件中則以“FileAlignment”(文件對(duì)齊)對(duì)齊。節(jié)由節(jié)頭中的相關(guān)項(xiàng)來(lái)描述:在文件中你可通過(guò)“PointerToRawData”(原始數(shù)據(jù)指針)來(lái)找到,在內(nèi)存中你可通過(guò)“VirtualAddress”(虛擬地址)來(lái)找到;長(zhǎng)度由“SizeOfRawData”(原始數(shù)據(jù)長(zhǎng)度)決定。
根據(jù)節(jié)中包含的內(nèi)容,可分為好幾種節(jié)。大多數(shù)(并非所有)情況下,節(jié)中至少由一個(gè)數(shù)據(jù)目錄,并在可選頭的數(shù)據(jù)目錄數(shù)組中有一個(gè)指針指向它。
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é)中的某個(gè)地方,指向開(kāi)發(fā)者希望首先執(zhí)行的那個(gè)函數(shù)的開(kāi)始處。
“BaseOfCode”(代碼基址)通常指向這一節(jié)的開(kāi)始處,但是,如果一些非代碼字節(jié)被放在代碼之前的話,它也可能指向節(jié)中靠后的某個(gè)地方。
通常,除了可執(zhí)行代碼外,本節(jié)沒(méi)有別的東東,并且通常只有一個(gè)代碼節(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)存可寫(xiě)節(jié))等標(biāo)志位被置為1。
一些鏈接器可能會(huì)將常量放在沒(méi)有可寫(xiě)標(biāo)志位的它們自己的節(jié)中。如果有一部分?jǐn)?shù)據(jù)可共享,或者有其它的特定情況,那么可能會(huì)有更多的節(jié),且它們的合適的標(biāo)志位會(huì)被設(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)容不存儲(chǔ)在文件中;并且“IMAGE_SCN_CNT_UNINITIALIZED_DATA”(含有未初始化數(shù)據(jù)節(jié))而不是“IMAGE_SCN_CNT_INITIALIZED_DATA”(含有已初始化數(shù)據(jù)節(jié))標(biāo)志位被置為1,表明在載入時(shí)它的內(nèi)容應(yīng)該被置為0。這就意味著,在文件中只有節(jié)頭,沒(méi)有節(jié)身;節(jié)身將由加載器創(chuàng)建,并全部為0字節(jié)。
它的長(zhǎng)度由“SizeOfUninitializedData”(未初始化數(shù)據(jù)大小)確定。
典型的名稱有“.bss”、“BSS”之類。
有些節(jié)數(shù)據(jù)“沒(méi)有”被數(shù)據(jù)目錄指向。它們的內(nèi)容和結(jié)構(gòu)是由編譯器而不是鏈接器提供。
(棧段和堆段不是二進(jìn)制文件中的節(jié),它們是由加載器根據(jù)可選頭中的棧大小和堆大小項(xiàng)來(lái)創(chuàng)建的。)
5.版權(quán)(copyright)
-------------------
為了從一個(gè)簡(jiǎn)單的目錄節(jié)開(kāi)始講解,讓我們來(lái)看一看數(shù)據(jù)目錄“IMAGE_DIRECTORY_ENTRY_COPYRIGHT”(版權(quán)目錄項(xiàng))項(xiàng)。它的內(nèi)容是一個(gè)版權(quán)信息或ASCII形式的描述字符串(不是以0結(jié)尾的),象“Gonkulator control application, copyright (c) 1848 Hugendubel & Cie”這樣。這個(gè)字符串,通常,是用命令行或者描述文件提供給鏈接器的。
這個(gè)字符串在運(yùn)行時(shí)并不需要,并可能被丟棄。它是不可寫(xiě)的;事實(shí)上,應(yīng)用程序根本不需要存取它。因此,如果已有一個(gè)可丟棄的、非可寫(xiě)的節(jié)存在,鏈接器就會(huì)找到它;如果沒(méi)有,就創(chuàng)建一個(gè)(命名為“.descr”之類)。然后它就將那個(gè)字符串填入該節(jié)中并讓版權(quán)目錄項(xiàng)指針指向這個(gè)字符串。“IMAGE_SCN_CNT_INITIALIZED_DATA”(含有已初始化數(shù)據(jù)節(jié))標(biāo)志位應(yīng)置為1。
6.輸出符號(hào)(exported symbols)
------------------------------
(注意:本文的1993年03月12日之前的各個(gè)版本中,輸出目錄的描述有誤。文中沒(méi)有描述中轉(zhuǎn)、只以序數(shù)輸出、或者使用好幾個(gè)名稱輸出等內(nèi)容。)
下一件最簡(jiǎn)單的事情是輸出目錄,是由“IMAGE_DIRECTORY_ENTRY_EXPORT”(輸出目錄項(xiàng))指向的。它是一個(gè)典型的在DLL中常見(jiàn)到的目錄;包含一些輸出函數(shù)的入口點(diǎn)(以及輸出對(duì)象等的地址)。當(dāng)然可執(zhí)行文件也可能擁有輸出符號(hào)但一般沒(méi)有。
包含它們的節(jié)應(yīng)該有“已初始化數(shù)據(jù)的”和“可讀的”特性。這樣的節(jié)應(yīng)該是不可丟棄的,因?yàn)樵谶\(yùn)行時(shí),進(jìn)程有可能調(diào)用“GetProcAddress()”來(lái)尋找一個(gè)函數(shù)的入口點(diǎn)。如果單獨(dú)成節(jié)的話,本節(jié)通常被稱作“.edata”;更常見(jiàn)的是,它被并入象“已初始化數(shù)據(jù)”之類的節(jié)中。
輸出表(“IMAGE_EXPORT_DIRECTORY”)的結(jié)構(gòu)由一個(gè)頭和輸出數(shù)據(jù),也就是:符號(hào)名稱、它們的序號(hào)和它們的入口點(diǎn)偏移量等構(gòu)成。
1)首先,我們有一個(gè)沒(méi)被使用并通常為0的、32位的“Characteristics”(特性)。
2)然后是一個(gè)32位的“TimeDateStamp”(時(shí)間日期戳),大概是提供此表被創(chuàng)建的time_t格式的時(shí)間;天呀,它的值并不總是有效(有些鏈接器將它設(shè)置為0)。
3-4)往后我們看到2個(gè)16位的、有關(guān)版本信息的word單元(“MajorVersion”和“MinorVersion”,含義分別為‘主版本號(hào)’和‘小版本號(hào)’),同樣,它們很多地被設(shè)為0。
5)下一個(gè)東東是32位的“Name”(名稱);它是一個(gè)指向以0結(jié)尾的ASCII字符串為DLL名稱的RVA。(為防DLL被改名時(shí)的錯(cuò)誤,名稱是必須的----參見(jiàn)輸入目錄中的“綁定”部分。)
6)然后是32位的“Base”(基址)。稍后我們?cè)儆懻摗?br>
7)下一個(gè)32位值是輸出條目的總數(shù)(“NumberOfFunctions”,意為‘函數(shù)數(shù)’)。除了它們的序數(shù)外,各條目還可能使用一個(gè)或多個(gè)名稱來(lái)輸出。接下來(lái)的一個(gè)32位數(shù)字是輸出名稱的總數(shù)(“NumberOfNames”,意為‘名字?jǐn)?shù)’)。
在大多數(shù)情況下,每一個(gè)輸出條目都準(zhǔn)確的有一個(gè)相應(yīng)的名稱,并且將用這個(gè)名稱來(lái)使用它;但是一個(gè)條目可能擁有好幾個(gè)相關(guān)聯(lián)的名稱(那樣它們的每一個(gè)名稱都可訪問(wèn));或者它也可能沒(méi)有名稱,此時(shí)它只能以它的序數(shù)來(lái)訪問(wèn)。無(wú)名輸出項(xiàng)(只有序數(shù))的使用是不鼓勵(lì)的,因?yàn)榇藭r(shí)輸出DLL的所有版本都必須使用相同的序數(shù)法,而這會(huì)造成維護(hù)的問(wèn)題。
8)下一個(gè)32位值“AddressOfFunctions”(函數(shù)地址)是指向輸出條目列表的RVA。它指向一個(gè)32位值的“NumberOfFunctions”(函數(shù)數(shù))數(shù)組,數(shù)組的每一項(xiàng)都是一個(gè)指向輸出函數(shù)或變量的RVA。
關(guān)于此列表有兩個(gè)怪事:第一,這樣一個(gè)輸出的RVA竟可能會(huì)為0,在此情況下,此值沒(méi)被使用。第二,如果一RVA指向含有輸出目錄的節(jié),那么它就是一個(gè)中轉(zhuǎn)輸出。一個(gè)中轉(zhuǎn)輸出就是指指向另一個(gè)二進(jìn)制文件中的輸出項(xiàng)的指針;如果使用了它,就可用另一個(gè)二進(jìn)制文件中的被指向的輸出項(xiàng)來(lái)代替使用。此時(shí)的RVA指向,正如已提到的,輸出目錄的節(jié)中,指向一個(gè)以以零結(jié)尾的字符串組成的、被指向的DLL的名稱和一個(gè)用點(diǎn)分開(kāi)的輸出項(xiàng)的名稱,象“otherdll.exportname”這樣,或者是DLL的名稱和輸出序數(shù),象“otherdll.#19”這樣。
現(xiàn)在到了解釋輸出序數(shù)的時(shí)候了。一個(gè)輸出項(xiàng)的序數(shù)就是函數(shù)地址數(shù)組中的索引值加上上面提到的“Base”(基址)的值的和。在大多數(shù)情況下,“Base”(基址)的值為1,這就意味著第一個(gè)輸出項(xiàng)的序數(shù)為1,第二個(gè)輸出項(xiàng)的序數(shù)為2,以此類推。
9-10)“AddressOfFunctions”(函數(shù)地址)RVA之后,我們發(fā)現(xiàn)二個(gè)RVA,一個(gè)指向符號(hào)名稱的32位RVA的數(shù)組“AddressOfNames”(名字的地址),另一個(gè)指向16位序數(shù)“AddressOfNameOrdinals”(名字序數(shù)的地址)的數(shù)組。兩個(gè)數(shù)組都有“NumberOfNames”(名字?jǐn)?shù))個(gè)元素。
符號(hào)名稱可能會(huì)全部丟失,此時(shí)“AddressOfNames”(名字的地址)為0;否則,被指向的數(shù)組并行運(yùn)行,這意味著它們的每個(gè)索引中的元素共同擁有。“AddressOfNames”(名字的地址)數(shù)組由以0結(jié)尾的輸出名稱的RVA組成;這些名稱以一個(gè)分類的列表排列(即:數(shù)組的第一個(gè)成員是按照字母順序排列的最小的名稱的RVA;這使當(dāng)按名稱查找一個(gè)輸出符號(hào)時(shí),搜索的效率更高。)
根據(jù)PE規(guī)范,“AddressOfNameOrdinals”(名字序數(shù)的地址)數(shù)組每個(gè)名稱擁有一個(gè)相應(yīng)的序數(shù),然而,我發(fā)現(xiàn)這個(gè)數(shù)組卻將實(shí)際的索引包含到“AddressOfFunctions”(函數(shù)地址)數(shù)組中去。
我將畫(huà)一個(gè)有關(guān)這三個(gè)表的圖:
函數(shù)地址
|
|
|
v
帶序數(shù)‘基址’的輸出RVA
帶序數(shù)‘基址+1’的輸出RVA
...
帶序數(shù)‘基址+函數(shù)數(shù)-1’的輸出RVA
名字地址 名字序數(shù)地址
| |
| |
| |
v v
第一個(gè)名字的RVA <-> 第一個(gè)名字的輸出索引
第二個(gè)名字的RVA <-> 第二個(gè)名字的輸出索引
... ...
第‘名字?jǐn)?shù)’個(gè)名字的RVA <-> 第‘名字?jǐn)?shù)’個(gè)名字的輸出索引
舉一些例子是適宜的。
為按序數(shù)找到一個(gè)輸出符號(hào),先減去“Base”(基址)值以得到索引值,再根據(jù)“AddressOfFunctions”(函數(shù)地址)的RVA得到輸出項(xiàng)數(shù)組,并用索引值去找到數(shù)組中的輸出RVA。如果結(jié)果沒(méi)有指向輸出節(jié)中,你就完了。否則,它就指向那里的一個(gè)描述輸出DLL和(輸出項(xiàng))名稱或序數(shù)的字符串,之后你就得在那里查找中轉(zhuǎn)輸出。
為按名稱找到一個(gè)輸出符號(hào),先跟隨“AddressOfNames”(名字的地址)的RVA(如果是0就沒(méi)有名稱)找到輸出名稱的RVA數(shù)組。在列表中搜尋你要找的名稱。用該名稱在“AddressOfNameOrdinals”(名字序數(shù)的地址)數(shù)組中的索引,得到和找到的名稱相應(yīng)的16位數(shù)字。根據(jù)PE規(guī)范,這是一個(gè)序數(shù),你需先減去“Base”(基址)值以得到輸出索引值;但依據(jù)我的經(jīng)驗(yàn),這就是輸出索引值,你不需要再減了。使用輸出索引值,你就能在“AddressOfFunctions”(函數(shù)地址)數(shù)組中找到輸出RVA了,要么是輸出RVA本身,要么是一個(gè)描述中轉(zhuǎn)輸出的字符串的RVA。
7.輸入符號(hào)(imported symbols)
------------------------------
當(dāng)編譯器發(fā)現(xiàn)一個(gè)對(duì)別的可執(zhí)行文件(大多數(shù)是DLL文件)中的函數(shù)調(diào)用時(shí),在最簡(jiǎn)單化的情況下,它會(huì)對(duì)此情況一無(wú)所知,只是簡(jiǎn)單地輸出一個(gè)對(duì)那個(gè)符號(hào)的正常調(diào)用指令。鏈接器不得不修正那個(gè)符號(hào)的地址,就象它為任何其它的外部符號(hào)所做的那樣。
鏈接器使用一個(gè)輸入庫(kù)來(lái)查找從哪個(gè)DLL文件輸入了哪個(gè)符號(hào),并為所有的輸入符號(hào)都建立存根,每個(gè)存根包含一個(gè)跳轉(zhuǎn)指令;存根就是實(shí)際的調(diào)用目標(biāo)。這些跳轉(zhuǎn)指令實(shí)際上將跳往從所謂的輸入地址表中提取的一個(gè)地址。在更復(fù)雜的應(yīng)用程序(使用“__declspec(dllimport)”時(shí))中,編譯器會(huì)知道函數(shù)是輸入的,并直接輸出一個(gè)位于輸入地址表中的地址的調(diào)用,繞過(guò)跳轉(zhuǎn)。
不管怎樣,DLL文件中的函數(shù)地址總是必要的,并將于應(yīng)用程序載入時(shí),由加載器從輸出DLL文件的輸出目錄中提供。加載器知道哪個(gè)庫(kù)中的哪些符號(hào)需要被查找以及哪些地址需要通過(guò)搜索輸入目錄來(lái)修正。
我最好給你一個(gè)例子。有或無(wú)__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 ; 沒(méi)有declspec(dllimport)的
...
call [__imp__symbol2] ; 含有declspec(dllimport)的
...
在第一種(沒(méi)有__declspec(dllimport))情況下,編譯器不知道“_symbol”位于一個(gè)DLL文件中,因此鏈接器必須要提供“_symbol”函數(shù)。因?yàn)榇撕瘮?shù)不存在,它就為輸入符號(hào)提供一個(gè)存根函數(shù),即一個(gè)間接跳轉(zhuǎn)。所有輸入存根的集合被稱為“轉(zhuǎn)移區(qū)”(有時(shí)也叫做“跳板”,因?yàn)槟闾侥抢锏哪康氖菫榱颂絼e的地方)。
典型地,此轉(zhuǎn)移區(qū)位于代碼節(jié)中(它不是輸入目錄的一部分)。每一個(gè)函數(shù)存根都是一個(gè)跳往DLL文件中的實(shí)際函數(shù)的跳轉(zhuǎn)。轉(zhuǎn)移區(qū)的形式象這樣:
_symbol: jmp [__imp__symbol]
_other_symbol: jmp [__imp__other__symbol]
...
這意味著:如果你不指定“__declspec(dllimport)”來(lái)使用輸入符號(hào),那么鏈接器將會(huì)為它們產(chǎn)生一個(gè)由間接跳轉(zhuǎn)所組成的轉(zhuǎn)移區(qū)。如果你真指定了“__declspec(dllimport)”,那么編譯器就會(huì)自己做間接(跳轉(zhuǎn)),轉(zhuǎn)移區(qū)也就不需要了。(這也意味著:如果你輸入的是變量或其它東西,你就必須指定“__declspec(dllimport)”,因?yàn)橐粋€(gè)具有jmp指令的存根只合適于函數(shù)。)
不管怎樣,符號(hào)“x”的地址都被存在“__imp_x”的存儲(chǔ)單元。所有這樣的存儲(chǔ)單元一起形成所謂的“輸入地址表”,此表是由被用到的各DLL文件中的輸入庫(kù)提供給鏈接器的。輸入地址表就是由下面這種形式的一組地址組成的:
__imp__symbol: 0xdeadbeef
__imp__symbol2: 0x40100
__imp__symbol3: 0x300100
...
這個(gè)輸入地址表是輸入目錄的一部分,并且被IMAGE_DIRECTORY_ENTRY_IAT(輸入地址表目錄項(xiàng))目錄指針?biāo)赶颍ūM管有些鏈接器不設(shè)置此目錄項(xiàng),程序也能運(yùn)行;很明顯地,這是因?yàn)榧虞d器不使用IMAGE_DIRECTORY_ENTRY_IAT(輸入地址表目錄項(xiàng))目錄也能解決輸入問(wèn)題)。
這些地址并不被鏈接器所知;鏈接器只插入一些偽地址(函數(shù)名稱的RVA;參見(jiàn)后面的更多信息),這些偽地址會(huì)在載入時(shí)被加載器用輸出DLL文件中的輸出目錄來(lái)修正。輸入地址表,以及它是怎樣被加載器找到的,將會(huì)在本章的后面被詳細(xì)講述。
注意:這個(gè)介紹是針對(duì)C語(yǔ)言規(guī)范的;有些別的應(yīng)用程序構(gòu)建環(huán)境是不使用輸入庫(kù)的,盡管它們都需要建立一個(gè)輸入地址表,用來(lái)讓它們的程序訪問(wèn)輸入對(duì)象和函數(shù)。C語(yǔ)言編譯器往往使用輸入庫(kù),因?yàn)闊o(wú)論如何講,這都有利于它們----它們的鏈接器使用好庫(kù)。別的環(huán)境使用的是例如:一個(gè)列出需要的DLL文件名稱和函數(shù)名稱的描述文件(比如“模塊定義文件”),或者一個(gè)源文件中的聲明形式的列表等。
這就是程序的代碼如何使用輸入函數(shù)的;現(xiàn)在我們?cè)賮?lái)看看輸入目錄是如何建立以便加載器使用的。
輸入目錄應(yīng)該存在于是“已初始化數(shù)據(jù)”并且“可讀”的節(jié)中。
輸入目錄是一個(gè)多IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))的數(shù)組,每個(gè)被使用的DLL文件都有一個(gè)。(它們的)列表由一個(gè)全部用0填充的IMAGE_IMPORT_DESCRIPTOR(輸入地址表目錄項(xiàng))結(jié)構(gòu)作為結(jié)束。
一個(gè)IMAGE_IMPORT_DESCRIPTOR(輸入地址表目錄項(xiàng))是一個(gè)擁有下列成員的結(jié)構(gòu)體:
OriginalFirstThunk(原始第一個(gè)換長(zhǎng))(漢譯的說(shuō)明見(jiàn)注釋?)
它是一個(gè)RVA(32位),指向一個(gè)以0結(jié)尾的、由IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))的RVA構(gòu)成的數(shù)組,其每個(gè)IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))元素都描述一個(gè)函數(shù)。此數(shù)組永不改變。
TimeDateStamp(時(shí)間日期戳)
它是一個(gè)具有好幾個(gè)目的的32位的時(shí)間戳。讓我們先假設(shè)時(shí)間戳為0,一些高級(jí)的情況將在以后處理。
ForwarderChain(中轉(zhuǎn)鏈)
它是輸入函數(shù)列表中第一個(gè)中轉(zhuǎn)的、32位的索引。中轉(zhuǎn)也是高級(jí)的東東。對(duì)初學(xué)者先將所有位設(shè)為-1。
Name(名稱)
它是一個(gè)DLL文件的名稱(0結(jié)尾的ASCII碼字符串)的、32位的RVA。
FirstThunk(第一換長(zhǎng))
它也是一個(gè)RVA(32位),指向一個(gè)0結(jié)尾的、由IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))的RVA構(gòu)成的數(shù)組,其每個(gè)IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))元素都描述一個(gè)函數(shù)。此數(shù)組是輸入地址表的一部分,并且可以改變。
因此,數(shù)組中的每個(gè)IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))結(jié)構(gòu)體都給出輸出DLL文件的名稱,并且,除了中轉(zhuǎn)和時(shí)間日期戳,它還給出2個(gè)指向IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))的數(shù)組的RVA,都是32位。(每個(gè)數(shù)組的最后一個(gè)成員都全部填充為0字節(jié),以標(biāo)志結(jié)尾。)
目前看來(lái),每個(gè)IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))都是一個(gè)RVA,指向一個(gè)描述輸入函數(shù)的IMAGE_IMPORT_BY_NAME(輸入名字)項(xiàng)。
現(xiàn)在,有趣的是兩個(gè)數(shù)組并行運(yùn)行,也就是說(shuō):它們指向同一組IMAGE_IMPORT_BY_NAME(輸入名字)。
沒(méi)有必要失望,我將再畫(huà)一圖。這里是IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))的關(guān)鍵內(nèi)容:
原始第一個(gè)換長(zhǎng) 第一個(gè)換長(zhǎng)
| |
| |
| |
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(輸入名字)。每一個(gè)都是一個(gè)16位的數(shù)字(一個(gè)提示)跟著一些數(shù)量未定的字節(jié),它們都是以0結(jié)尾的、輸入符號(hào)的ASCII碼名字。
提示就是指向輸出DLL文件名字表的索引(參見(jiàn)上面的輸出目錄)。那個(gè)索引中的名字將被一一嘗試,如果沒(méi)有相符的,再使用二進(jìn)制搜索來(lái)尋找名字。
(有些鏈接器不愿意查找正確的提示,總是只簡(jiǎn)單的將其指定為1,或者其它的隨意數(shù)字。這并無(wú)大害,只是使解決名字的第一次嘗試總是失敗,并迫使每個(gè)名字都使用二進(jìn)制搜索來(lái)進(jìn)行。)
總結(jié)一下:如果你想從“knurr”DLL中查找輸入函數(shù)“foo”的信息,第一步你先找到數(shù)據(jù)目錄中的IMAGE_DIRECTORY_ENTRY_IMPORT(輸入目錄項(xiàng))項(xiàng),得到一個(gè)RVA,再在原始節(jié)數(shù)據(jù)中找到那個(gè)地址,現(xiàn)在你就得到一個(gè)IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))數(shù)組了。通過(guò)查看根據(jù)它們的“名稱”被指向的字符串,得到和“knurr”DLL有關(guān)的這個(gè)數(shù)組的成員(即一個(gè)輸入描述結(jié)構(gòu))。在你找到正確的IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))后,順著它的“OriginalFirstThunk”(原始第一個(gè)換長(zhǎng))得到被指向的IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))數(shù)組;再通過(guò)查詢RVA找到“foo”函數(shù)。
好了,為什么我們有“兩”列指向IMAGE_IMPORT_BY_NAME(輸入名字)的指針呢?這是因?yàn)樵谶\(yùn)行時(shí),應(yīng)用程序不需要輸入函數(shù)的名字,只需要地址。在這里輸入地址表又出現(xiàn)了。加載器將從相關(guān)的DLL文件的輸出目錄中查找每一個(gè)輸入符號(hào),并用DLL文件入口點(diǎn)的線性地址替換“FirstThunk”( 第一個(gè)換長(zhǎng))列表中的IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))元素(到現(xiàn)在之前它還是指向IMAGE_IMPORT_BY_NAME(輸入名字)的)。
請(qǐng)記住帶有象“__imp__symbol”標(biāo)簽的地址列表;被數(shù)據(jù)目錄IMAGE_DIRECTORY_ENTRY_IAT(輸入地址表目錄項(xiàng))所指向的輸入地址表,就是被“FirstThunk”( 第一個(gè)換長(zhǎng))所指向的列表。[在從好幾個(gè)DLL文件輸入的情況下,輸入地址表是包含所有DLL文件的“FirstThunk”( 第一個(gè)換長(zhǎng))數(shù)組。目錄項(xiàng)IMAGE_DIRECTORY_ENTRY_IAT(輸入地址表目錄項(xiàng))可能會(huì)丟失,但輸入(函數(shù))仍能工作良好。]
“OriginalFirstThunk”( 原始第一個(gè)換長(zhǎng))數(shù)組保持不變,因此你總能通過(guò)“OriginalFirstThunk”( 原始第一個(gè)換長(zhǎng))列表查找原始的輸入名字列表。
現(xiàn)在輸入已經(jīng)被用正確的線性地址修正,如下所示:
原始第一個(gè)換長(zhǎng) 第一個(gè)換長(zhǎ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ǎn)單情況下的基本結(jié)構(gòu)。現(xiàn)在我們將要學(xué)習(xí)輸入目錄中的需細(xì)講的東西。
第一,當(dāng)數(shù)組中IMAGE_THUNK_DATA元(換長(zhǎng)數(shù)據(jù))素的IMAGE_ORDINAL_FLAG(序數(shù)標(biāo)志)位(也是:MSB,參見(jiàn)注釋?)被置1時(shí),表示列表中沒(méi)有符號(hào)的名字信息,符號(hào)只以序數(shù)輸入。你可通過(guò)查看IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))中的低地址word來(lái)得到序數(shù)。
通過(guò)序數(shù)輸入是不鼓勵(lì)的,通過(guò)名字輸入會(huì)更安全,因?yàn)槿绻敵鯠LL文件不是預(yù)期的版本時(shí)輸出序數(shù)可能會(huì)改變。
第二,有所謂的“綁定輸入”。
請(qǐng)思考一下加載器的工作:當(dāng)它想執(zhí)行的一個(gè)二進(jìn)制文件需要一個(gè)DLL中的函數(shù)時(shí),加載器會(huì)載入該DLL,找到它的輸出目錄,查找函數(shù)的RVA并計(jì)算函數(shù)的入口點(diǎn)。然后用這樣找到的地址修正“FirstThunk”( 第一個(gè)換長(zhǎng))列表。
假設(shè)程序員很聰明,給DLL文件提供的唯一優(yōu)先載入地址不會(huì)發(fā)生沖突,那么我們就能認(rèn)為函數(shù)的入口點(diǎn)將總是相同的。它們?cè)阪溄訒r(shí)能被算出并被補(bǔ)進(jìn)“FirstThunk”( 第一個(gè)換長(zhǎng))列表中,這就是“綁定輸入”所發(fā)生的一切。(“綁定”工具就是干這個(gè)的,它是Win32 SDK的一部分。)
當(dāng)然,你得慎重:用戶的DLL可能是不同的版本,或者DLL必須重定位,這些都會(huì)使先前修正的“FirstThunk”( 第一個(gè)換長(zhǎng))列表不再有效;此時(shí),加載器仍能查尋“OriginalFirstThunk”( 原始第一個(gè)換長(zhǎng))列表,找出輸入符號(hào)并重新補(bǔ)正“FirstThunk”( 第一個(gè)換長(zhǎng))列表。加載器知道這是必須的,當(dāng):1)輸出DLL文件的版本不符,或2)輸出DLL文件需要重定位時(shí)。
確定有沒(méi)有重定位表對(duì)加載器來(lái)說(shuō)不是問(wèn)題,但該怎樣找出版本的不同呢?這時(shí)IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))的“時(shí)間戳”就派上用場(chǎng)了。如果它是0,表明輸入列表沒(méi)有被綁定,加載器總是要修復(fù)入口點(diǎn)。否則的話,輸入被綁定,“時(shí)間戳”必須要和“文件頭”中的輸出DLL文件的“時(shí)間戳”相符;如果不符的話,加載器就認(rèn)為該二進(jìn)制文件被綁到一個(gè)“錯(cuò)誤”的DLL文件上并重新補(bǔ)正輸入列表。
這里有另外一個(gè)有關(guān)輸入列表中的“中轉(zhuǎn)”的怪事。一個(gè)DLL文件能輸出一個(gè)不定義在本DLL文件中卻需從另一個(gè)DLL文件中輸入的符號(hào);這樣的符號(hào)據(jù)說(shuō)就是被中轉(zhuǎn)的(參見(jiàn)上面的輸出目錄描述)。
現(xiàn)在,很明顯的,你不能通過(guò)查看那個(gè)實(shí)際上并不包含入口點(diǎn)信息的DLL文件的時(shí)間戳來(lái)確定一個(gè)符號(hào)的入口點(diǎn)是否有效。因此,出于安全的原因,中轉(zhuǎn)符號(hào)的入口點(diǎn)必須總是被修正。在二進(jìn)制文件的輸入列表中,中轉(zhuǎn)符號(hào)的輸入必須被找出,以便加載器能補(bǔ)正它們。
這一點(diǎn)可通過(guò)“ForwarderChain”(中轉(zhuǎn)鏈)來(lái)做到。它是一個(gè)指向換長(zhǎng)列表中的索引值;被索引位置的輸入就是一個(gè)中轉(zhuǎn)輸出,并且此位置的“FirstThunk”( 第一個(gè)換長(zhǎng))列表中的內(nèi)容就是“下一個(gè)”中轉(zhuǎn)輸入的索引值,以此類推,直到索引值為-1,就表明已沒(méi)有其他的中轉(zhuǎn)了。如果根本就沒(méi)有中轉(zhuǎn),那么“ForwarderChain”(中轉(zhuǎn)鏈)的值本身就為-1。
這就是所謂的“老式”綁定。
至此,我們應(yīng)該總結(jié)一下我們目前已掌握的情況 :-)
OK,我將認(rèn)為你已找到了IMAGE_DIRECTORY_ENTRY_IMPORT(輸入目錄項(xiàng))并且已根據(jù)它找到了它的輸入目錄,位于某個(gè)節(jié)中。現(xiàn)在你已處于IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))數(shù)組的開(kāi)頭了,此類數(shù)組的最后一個(gè)將以全0字節(jié)填充。
要讀懂一個(gè)IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu)),你得先查看它的“名字”項(xiàng),根據(jù)它的RVA,你就能找到輸出DLL文件的名字。下一步你得確定輸入是否是綁定的;如果輸入是綁定的,“時(shí)間戳”就會(huì)是非“0”的。如果它們是綁定的,現(xiàn)在就是你通過(guò)比較“時(shí)間戳”來(lái)檢查DLL文件的版本是否相符的好機(jī)會(huì)了。
現(xiàn)在你根據(jù)“OriginalFirstThunk”( 原始第一個(gè)換長(zhǎng))的RVA來(lái)到了IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))數(shù)組;過(guò)完這些數(shù)組(它是0結(jié)尾的),它的每個(gè)成員都將是一個(gè)IMAGE_IMPORT_BY_NAME(輸入名字)的RVA(除非它的高位被置1,此時(shí)你找不到名字只有序數(shù))。根據(jù)那個(gè)RVA,并跳過(guò)2字節(jié)(即‘提示’),現(xiàn)在你就得到一個(gè)以0結(jié)尾的字符串,這就是輸入函數(shù)的名字。
在綁定輸入時(shí)要找到提供的入口點(diǎn),先根據(jù)“FirstThunk”( 第一個(gè)換長(zhǎng))平行的來(lái)到“OriginalFirstThunk”( 原始第一個(gè)換長(zhǎng))數(shù)組;數(shù)組成員就是入口點(diǎn)的線性地址(暫時(shí)不考慮中轉(zhuǎn)的話題)。
還有一件我到現(xiàn)在都沒(méi)有提及的事情:明顯地有些鏈接器在構(gòu)建輸入目錄時(shí)會(huì)產(chǎn)生bug(我就發(fā)現(xiàn)一個(gè)還在被一個(gè)Borland C鏈接器使用的bug)。這些鏈接器把IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))中的“OriginalFirstThunk”( 原始第一個(gè)換長(zhǎng))設(shè)為0,并只建立“FirstThunk”( 第一個(gè)換長(zhǎng))。很明顯的,這樣的輸入目錄不能被綁定(否則重修輸入的必須信息就會(huì)丟失----你根本找不到函數(shù)名字)。在這種情況下,你得根據(jù)“FirstThunk”( 第一個(gè)換長(zhǎng))數(shù)組來(lái)取得輸入符號(hào)名字,你將永遠(yuǎn)得不到預(yù)先補(bǔ)正的入口地址。我已發(fā)現(xiàn)一個(gè)TIS文件(參考書(shū)目[6]),講述一個(gè)在某種程度上和此bug兼容的輸入目錄,因此那個(gè)文件可能就是該bug的起源。
TIS文件規(guī)定:
IMPORT FLAGS(輸入標(biāo)志)
TIME/DATE STAMP(時(shí)間/日期戳)
MAJOR VERSION - MINOR VERSION(主版本號(hào) - 小版本號(hào))
NAME RVA(名字的RVA)
IMPORT LOOKUP TABLE RVA(輸入查詢表的RVA)
IMPORT ADDRESS TABLE RVA(輸入地址表的RVA)
而別處使用的對(duì)應(yīng)結(jié)構(gòu)是:
OriginalFirstThunk( 原始第一個(gè)換長(zhǎng))
TimeDateStamp(時(shí)間日期戳)
ForwarderChain(中轉(zhuǎn)鏈)
Name(名字)
FirstThunk(第一個(gè)換長(zhǎng))
最后一個(gè)關(guān)于輸入目錄的需要細(xì)講的就是所謂的“新式”綁定(在參考書(shū)目[3]中講述),它也可以由“綁定”工具來(lái)處理。當(dāng)使用這種方式時(shí),“時(shí)間日期戳”的所有位被置為1,并且沒(méi)有中轉(zhuǎn)鏈;此時(shí)所有輸入符號(hào)的地址都將被補(bǔ)正,而不管它們是不是中轉(zhuǎn)的。盡管如此,你還是需要知道DLL的版本,并且你還是需要將序數(shù)符號(hào)從中轉(zhuǎn)符號(hào)中區(qū)分開(kāi)來(lái)。為了達(dá)到這個(gè)目的,IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT(綁定輸入目錄項(xiàng))目錄被創(chuàng)建了。就我所見(jiàn),它將不被放在節(jié)中,而是被放在頭中,處于節(jié)頭之后第一節(jié)之前。(咳,這不是我的發(fā)明,我只是講述它而已!)
這個(gè)目錄告訴你,每一個(gè)已使用的DLL文件的中轉(zhuǎn)輸出是從哪些別的DLL文件中來(lái)的。
結(jié)構(gòu)是IMAGE_BOUND_IMPORT_DESCRIPTOR(綁定輸入描述結(jié)構(gòu))形式的,包括(按這個(gè)順序):
一個(gè)32位數(shù)字,“時(shí)間戳”。
一個(gè)16位數(shù)字,“OffsetModuleName(模塊名字偏移量)”,是從目錄開(kāi)頭到以0結(jié)尾的DLL文件名的偏移量;
一個(gè)16位數(shù)字,“NumberOfModuleForwarderRefs(模塊中轉(zhuǎn)參考的數(shù)字)”,給出這個(gè)DLL文件為它的中轉(zhuǎn)使用的DLL文件數(shù)。
緊隨這個(gè)結(jié)構(gòu)之后你會(huì)發(fā)現(xiàn)“NumberOfModuleForwarderRefs(模塊中轉(zhuǎn)參考的數(shù)字)”結(jié)構(gòu),告訴你這個(gè)DLL文件的中轉(zhuǎn)所來(lái)自的DLL文件的名稱和版本。這些結(jié)構(gòu)就是“IMAGE_BOUND_FORWARDER_REF(綁定中轉(zhuǎn)參考)”結(jié)構(gòu)的:
一個(gè)32位的數(shù)字“時(shí)間日期戳”(TimeDateStamp);
一個(gè)16位的數(shù)字“模塊名稱偏移量”(OffsetModuleName),它就是從目錄開(kāi)頭到中轉(zhuǎn)來(lái)自的那個(gè)DLL文件的0結(jié)尾的名字處的偏移量;
一個(gè)16位的未使用單元。
跟在“IMAGE_BOUND_FORWARDER_REF(綁定中轉(zhuǎn)參考)”后的是下一個(gè)“IMAGE_BOUND_IMPORT_DESCRIPTOR(綁定輸入描述結(jié)構(gòu))”,以此類推;列表最終以一個(gè)全部為0位的IMAGE_BOUND_IMPORT_DESCRIPTOR(綁定輸入描述結(jié)構(gòu))結(jié)束。
我對(duì)由此(描述)造成的不便表示歉意,但這就是它看起來(lái)的樣子:-)
現(xiàn)在,如果你有一個(gè)新的綁定輸入目錄,你得載入所有的DLL文件,并使用目錄指針I(yè)MAGE_DIRECTORY_ENTRY_BOUND_IMPORT(綁定輸入目錄項(xiàng))找到IMAGE_BOUND_IMPORT_DESCRIPTOR(綁定輸入描述結(jié)構(gòu)),掃描整個(gè)IMAGE_BOUND_IMPORT_DESCRIPTOR(綁定輸入描述結(jié)構(gòu)),并檢查被載入的DLL文件的“時(shí)間日期戳”和這個(gè)目錄中提供的是否相符。如果不符,就將輸入目錄中“第一換長(zhǎng)”(FirstThunk)中的錯(cuò)誤全部修改過(guò)來(lái)。
8.資源(resources)
-------------------
資源,比如對(duì)話框、菜單、圖標(biāo)等等,都存儲(chǔ)在IMAGE_DIRECTORY_ENTRY_RESOURCE(“資源目錄項(xiàng)”)指向的數(shù)據(jù)目錄中。它們處于一個(gè)至少“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);它包含好幾個(gè)“資源目錄項(xiàng)”(IMAGE_RESOURCE_DIRECTORY_ENTRY),其中的每一項(xiàng)反過(guò)來(lái)又可能指向一個(gè)“資源目錄”。按照這種方式,你就得到一個(gè)以“資源目錄項(xiàng)”為樹(shù)葉的“資源目錄”樹(shù);它們的樹(shù)葉指向?qū)嶋H的資源數(shù)據(jù)。
在實(shí)際使用中,情況會(huì)稍微簡(jiǎn)單些。一般你不會(huì)遇到不可能理清的特別復(fù)雜的樹(shù)的。
通常,它的層次結(jié)構(gòu)是這樣的:一個(gè)目錄作為根。它指向很多目錄,每種資源類型都有一個(gè)。這些目錄又指向子目錄,每個(gè)子目錄都有一個(gè)名字或者ID號(hào)并指向這個(gè)資源所提供的各種語(yǔ)言的目錄;每種語(yǔ)言你都能找到一個(gè)資源項(xiàng),資源項(xiàng)最終指向(具體的)數(shù)據(jù)。(注意:多語(yǔ)言資源不能在Win95上運(yùn)行。即使程序有好幾種語(yǔ)言,Win95也總是使用相同的資源----我沒(méi)有查出是哪一種,但我猜測(cè)肯定是它最先碰到的那種。多語(yǔ)言資源在NT系統(tǒng)上可以運(yùn)行。)
沒(méi)有指針的樹(shù)大致象這樣:
( 根 )
|
+----------------+------------------+
| | |
菜單 對(duì)話框 圖標(biāo)
| | |
+-----+-----+ +-+----+ +-+----+----+
| | | | | | |
"main" "popup" 0x10 "maindlg" 0x100 0x110 0x120
| | | | | | |
+---+-+ | | | | | |
| | default english default def. def. def.
german english
一個(gè)“資源目錄項(xiàng)”(IMAGE_RESOURCE_DIRECTORY)包含:
32位未使用標(biāo)志,叫做“特征”(Characteristics);
32位“時(shí)間日期戳”(同樣按常用的time_t表示法),告訴你資源被創(chuàng)建的時(shí)間(如果此項(xiàng)被設(shè)置的話);
16位“主版本號(hào)”(MajorVersion)和16位“小版本號(hào)”(MinorVersion),以允許你據(jù)此維護(hù)資源的幾個(gè)版本;
16位“已命名項(xiàng)目數(shù)”(NumberOfNamedEntries)和另一個(gè)16位的“ID項(xiàng)目數(shù)”(NumberOfIdEntries)。
緊隨此結(jié)構(gòu)后的是“已命名項(xiàng)目數(shù)”+“ID項(xiàng)目數(shù)”兩結(jié)構(gòu)體,它們都是“資源目錄項(xiàng)”格式,都以名字開(kāi)頭。它們可能指向下一個(gè)“資源目錄”或者指向?qū)嶋H的資源數(shù)據(jù)。
一個(gè)“資源目錄項(xiàng)”由下面組成:
32位單元提供你它所描述的資源的ID或者是目錄;
32位的到數(shù)據(jù)的偏移量或者是到下一個(gè)子目錄的偏移量。
ID的含義取決于樹(shù)中的層次;ID可能是一個(gè)數(shù)字(如果最高位為0)也可能是一個(gè)名字(如果最高位為1)。如果是一個(gè)名字,它的低31位就是從資源節(jié)原始數(shù)據(jù)的開(kāi)始到這個(gè)名字(名字有16位長(zhǎng)并由unicode的寬字符而不是0結(jié)尾符作為結(jié)束)的偏移量。
如果你位于根目錄之中,且如果ID是一個(gè)數(shù)字的話,那么它指的就是下面的一種資源類型:
1: 光標(biāo)
2: 位圖
3: 圖標(biāo)
4: 菜單
5: 對(duì)話框
6: 字串表
7: 字體目錄
8: 字體
9: 快捷鍵
10: 未格式化資源數(shù)據(jù)
11: 信息表
12: 組光標(biāo)
14: 組圖標(biāo)
16: 版本信息
任何其它數(shù)字都是用戶自定義的。任何有類型名的資源類型也是用戶自定義的。
如果你處于(樹(shù)的)下一層當(dāng)中,此時(shí)ID一定是一個(gè)數(shù)字,且就是資源的一個(gè)特例的語(yǔ)言ID號(hào);例如,你可以(同時(shí))擁有澳大利亞英語(yǔ)、加拿大法語(yǔ)和瑞士德語(yǔ)等本地化形式的對(duì)話框,并且它們分享同一個(gè)資源ID。系統(tǒng)會(huì)根據(jù)線程的地點(diǎn)來(lái)選擇要使用的對(duì)話框,反過(guò)來(lái)地點(diǎn)又反映了用戶的“區(qū)域設(shè)置”。(如果資源找不到線程地點(diǎn),系統(tǒng)將先使用一個(gè)中性的子語(yǔ)言資源作為地點(diǎn),比如它將尋找標(biāo)準(zhǔn)法語(yǔ)而不是用戶所擁有的加拿大法語(yǔ);如果它還是找不到,就使用最小語(yǔ)言ID號(hào)的那個(gè)實(shí)例。必須注意,所有這些只工作于NT系統(tǒng)之上的。)
為便于辨認(rèn)語(yǔ)言ID,使用宏P(guān)RIMARYLANGID()(意為“主語(yǔ)言ID”)和SUBLANGID()(意為“子語(yǔ)言ID”)將它分開(kāi)為主語(yǔ)言ID和子語(yǔ)言ID,分別使用它的0-9位和10-15位。這些值定義在“winresrc.h”文件中。
語(yǔ)言資源只支持快捷鍵、對(duì)話框、菜單、資源數(shù)據(jù)或字符串等;其它資源類型必須為L(zhǎng)ANG_NEUTRAL/SUBLANG_NEUTRAL(中性語(yǔ)言/中性子語(yǔ)言)。
要確定資源目錄的下一層是不是另一個(gè)目錄,你可查看它的偏移量的最高位。如果它是1,剩下的31位就是從資源節(jié)原始數(shù)據(jù)的開(kāi)始到下一層目錄的偏移量,還是按“資源目錄”后接“資源目錄項(xiàng)”的格式。如果高位為0,它就是從資源節(jié)原始數(shù)據(jù)的開(kāi)始到資源的原始數(shù)據(jù)描述,即一個(gè)資源數(shù)據(jù)項(xiàng)的偏移量。資源的原始數(shù)據(jù)描述包含32位的“OffsetToData”(到數(shù)據(jù)的偏移量)(指的是到原始數(shù)據(jù)的偏移量,從資源節(jié)原始數(shù)據(jù)的開(kāi)頭算起),32位的數(shù)據(jù)的“Size”(大小),32位的“CodePage”(代碼頁(yè))和一個(gè)未使用的32位單元。
(不鼓勵(lì)使用代碼頁(yè),你應(yīng)該使用“語(yǔ)言”的特性來(lái)支持多地域。)
原始數(shù)據(jù)格式依賴于資源類型;詳細(xì)的介紹可在微軟的SDK文檔中找到。注意:除了用戶自定義資源,資源中的任何字符串總是按UNICODE格式,明顯的,用戶自定義的資源按的是開(kāi)發(fā)者選定的格式。
9.重定位(relocations)
-----------------------
我將要描述的最后一個(gè)數(shù)據(jù)目錄是基址重定位目錄。它是由可選頭數(shù)據(jù)目錄中的IMAGE_DIRECTORY_ENTRY_BASERELOC(基址重定位目錄項(xiàng))項(xiàng)來(lái)指向的。典型的,它包含在自己的節(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í),重定位數(shù)據(jù)對(duì)加載器來(lái)說(shuō)就是必須的。此時(shí),鏈接器所提供的固定地址就不再有效,并且加載器將不得不對(duì)靜態(tài)變量、字符串文字等使用的絕對(duì)地址進(jìn)行修正。
所謂重定位目錄就是一些連續(xù)的塊,每一塊都包含4K映象文件的重定位信息。塊由一個(gè)“IMAGE_BASE_RELOCATION(基址重定位)”結(jié)構(gòu)體開(kāi)始,這個(gè)結(jié)構(gòu)體包含一個(gè)32位的“VirtualAddress(虛擬地址)”項(xiàng)和一個(gè)32位的“SizeOfBlock(塊大小)”項(xiàng)。跟在它們后面的就是塊的實(shí)際重定位數(shù)據(jù),每一條都是16位的。
“VirtualAddress(虛擬地址)”就是重定位所在塊需要應(yīng)用的基本的RVA;“SizeOfBlock(塊大小)”就是整個(gè)塊的字節(jié)大小;跟在后面的重定位的數(shù)目是:('SizeOfBlock'-sizeof(IMAGE_BASE_RELOCATION))/2個(gè)。當(dāng)你碰到一個(gè)“VirtualAddress(虛擬地址)”值為0的“IMAGE_BASE_RELOCATION(基址重定位)”結(jié)構(gòu)體時(shí),重定位信息就結(jié)束了。
每一個(gè)16位的重定位信息由低12位的重定位位置和高4位的重定位類型組成。要得到重定位的RVA,你需要用這個(gè)12位的位置加上“IMAGE_BASE_RELOCATION(基址重定位)”中的“VirtualAddress(虛擬地址)”。類型是下面之一:
IMAGE_REL_BASED_ABSOLUTE (0)
這種不需操作;用于將塊按32位邊界對(duì)齊。位置應(yīng)該為0。
IMAGE_REL_BASED_HIGH (1)
重定位的高16位必須被用于被偏移量所指向的那個(gè)16位的WORD單元,此WORD是一個(gè)32位的DWORD的高位WORD。
IMAGE_REL_BASED_LOW (2)
重定位的低16位必須被用于被偏移量所指向的那個(gè)16位的WORD單元,此WORD是一個(gè)32位的DWORD的低位WORD。
IMAGE_REL_BASED_HIGHLOW (3)
重定位的全部32位必須應(yīng)用于上面所說(shuō)的全部32位。這種(和不需操作的第“0”種)是我在二進(jìn)制文件種實(shí)際發(fā)現(xiàn)的僅有的重定位類型。
IMAGE_REL_BASED_HIGHADJ (4)
這是一種復(fù)雜的。請(qǐng)自己參閱(參考文獻(xiàn)[6]),并努力弄懂它的意思:“高調(diào)整。這種修正要求一個(gè)全32位值。高16位定位于偏移量處,低16位定位在下一個(gè)數(shù)組元素(此數(shù)組元素包括在大小的域中)的偏移量處。它們兩個(gè)需要被連成一個(gè)有符號(hào)的變量。加上32位的增量。然后加上0x8000并將有符號(hào)變量的高16位存儲(chǔ)在偏移量處的16位域中。”
IMAGE_REL_BASED_MIPS_JMPADDR (5)
不清楚
IMAGE_REL_BASED_SECTION (6)
不清楚
IMAGE_REL_BASED_REL32 (7)
不清楚
舉一個(gè)例子,如果你發(fā)現(xiàn)重定位信息是
0x00004000 (32位, 開(kāi)始的RVA)
0x00000010 (32位, 塊的大小)
0x3012 (16位的重定位數(shù)據(jù))
0x3080 (16位的重定位數(shù)據(jù))
0x30f6 (16位的重定位數(shù)據(jù))
0x0000 (16位的重定位數(shù)據(jù))
0x00000000 (下一塊的RVA)
0xff341234
你知道第一塊描述的重定位開(kāi)始于RVA 0x4000處,有16字節(jié)長(zhǎng)。因?yàn)轭^用掉了8字節(jié),并且一個(gè)重定位要用2字節(jié),所以塊中計(jì)有(16-8)/2=4個(gè)重定位。第一個(gè)重定位被應(yīng)用于0x4012處的DWORD,第二個(gè)于0x4080處的DWORD,第三個(gè)于0x40f6處的DWORD。最后一個(gè)不需操作。
下一塊的RVA是0,列表結(jié)束。
好,你怎么處理一個(gè)重定位呢?
你能知道映象文件“被”重定位到可選頭“ImageBase(映象基址)”的優(yōu)先載入地址;你也能知道你真正載入的地址。如果它們相同,你什么也不用做。如果它們不同,你需計(jì)算出實(shí)際基址-優(yōu)先基址的差并加上重定位位置的值(有符號(hào),可能為負(fù)值),此值你可通過(guò)上面講述的方法找到。
九、致謝(Acknowledgments)
---------------------------
感謝David Binette的調(diào)試和校讀。(剩下的錯(cuò)誤全部都是我的。)
也感謝wotsit.org網(wǎng)站讓我將此文放到他們的網(wǎng)站上。
十、版權(quán)(Copyright)
---------------------
本文的版權(quán)屬于B. Luevelsmeyer,1999年。它是免費(fèi)的,你可以任意的使用,但后果自負(fù)。它含有錯(cuò)誤并不完整,特此警告。
十一、Bug報(bào)告(Bug reports)
----------------------------
Bug報(bào)告(或其他建議)請(qǐng)發(fā)送至:bernd.luevelsmeyer@iplan.heitec.net
十二、版本(Versions)
----------------------
你可在文件的頂部找到當(dāng)前的版本號(hào)。
1998-04-06
第一次公開(kāi)發(fā)表
1998-07-29
將映象文件版本和子系統(tǒng)版本中錯(cuò)誤的“byte”改為“word”
更正“棧只限于1 MB”的錯(cuò)誤(實(shí)際上沒(méi)有上限)
更正一些輸入錯(cuò)誤
1999-03-15
更正輸出目錄的描述,原來(lái)非常不全
調(diào)整輸入目錄的描述,原來(lái)講的不清
更正輸入錯(cuò)誤并為其它節(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 問(wèn)與答》
"Windows Q&A" (M. Pietrek), in: Microsoft Systems Journal 8/1995
[4]《編寫(xiě)多語(yǔ)言資源》
"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):
-------------------------------
在這個(gè)附錄中我將給大家展示一下怎樣手工建立一個(gè)程序。因?yàn)槲也粫?huì)DEC Alpha的,本例將使用Intel匯編語(yǔ)言。
本程序相當(dāng)于
#include <stdio.h>
int main(void)
{
puts(hello,world);
return 0;
}
首先,我使用Win32函數(shù)來(lái)翻譯它以取代C運(yùn)行時(shí)庫(kù):
#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)在我將笨拙的將它匯編出來(lái):
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)在起讓我們扮演起鏈接器的角色,這會(huì)非常有趣 :-)
我需要先找出函數(shù)WriteConsoleA()和GetStdHandle()。碰巧它們都在“kernel32.dll”中。(這是“輸入庫(kù)”部分。)
現(xiàn)在我開(kāi)始做可執(zhí)行文件。問(wèn)號(hào)代表待定的值;它們將在以后被修正。
首先是DOS-根,開(kāi)始于0x0,有0x40字節(jié)長(zhǎng):
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
正如你所見(jiàn)到的,這不是真正的MS-DOS程序。它只是一個(gè)開(kāi)始部分有“MZ”簽名的頭和緊跟在頭后面的e_lfanew指針,沒(méi)有任何代碼。這是因?yàn)樗⒎谴蛩氵\(yùn)行于MS-DOS之上;它之所以在這里只是因?yàn)橐?guī)范的需要。
然后是PE簽名,開(kāi)始于0x40,有0x4字節(jié)長(zhǎng):
50 45 00 00
現(xiàn)在到了文件頭,開(kāi)始于0x44,有0x14字節(jié)長(zhǎng):
Machine 4c 01 ; i386
NumberOfSections 02 00 ; 代碼段和數(shù)據(jù)段
TimeDateStamp 00 00 00 00 ; 誰(shuí)管它?
PointerToSymbolTable 00 00 00 00 ; 未用
NumberOfSymbols 00 00 00 00 ; 未用
SizeOfOptionalHeader e0 00 ; 常量
Characteristics 02 01 ; 32位機(jī)器上的可執(zhí)行文件
接著是可選頭,開(kāi)始于0x58,有0x60字節(jié)長(zhǎng):
Magic 0b 01 ; 常量
MajorLinkerVersion 00 ; 我是 0.0 版:-)
MinorLinkerVersion 00 ;
SizeOfCode 20 00 00 00 ; 32字節(jié)代碼
SizeOfInitializedData ?? ?? ?? ?? ; 待找出
SizeOfUninitializedData 00 00 00 00 ; 我們沒(méi)有BSS節(jié)
AddressOfEntryPoint ?? ?? ?? ?? ; 待定
BaseOfCode ?? ?? ?? ?? ; 待定
BaseOfData ?? ?? ?? ?? ; 待定
ImageBase 00 00 10 00 ; 1 MB, 隨意選
SectionAlignment 20 00 00 00 ; 32字節(jié)對(duì)齊
FileAlignment 20 00 00 00 ; 32字節(jié)對(duì)齊
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ū)動(dòng)不用
Subsystem 03 00 ; Win32控制臺(tái)
DllCharacteristics 00 00 ; 未用 (不是一個(gè)DLL)
SizeOfStackReserve 00 00 10 00 ; 1 MB棧
SizeOfStackCommit 00 10 00 00 ; 開(kāi)始時(shí)4 KB
SizeOfHeapReserve 00 00 10 00 ; 1 MB堆
SizeOfHeapCommit 00 10 00 00 ; 開(kāi)始時(shí)4 KB
LoaderFlags 00 00 00 00 ; 未知
NumberOfRvaAndSizes 10 00 00 00 ; 常量
正如你所見(jiàn),我計(jì)劃只用2個(gè)節(jié),一個(gè)用于代碼,一個(gè)用于所有剩余的東西(數(shù)據(jù)、常量和輸入目錄等)。沒(méi)有重定位和象資源之類其它東西。我也不用BSS節(jié)并將變量“written”放入已初始化數(shù)據(jù)。文件和RAM中的節(jié)對(duì)齊都是一樣的(32字節(jié));這將有助于使任務(wù)簡(jiǎn)單,否則我就得來(lái)回地計(jì)算RVA很多次。
現(xiàn)在我們?cè)O(shè)置數(shù)據(jù)目錄,開(kāi)始于0xb8字節(jié),有 0x80字節(jié)長(zhǎng):
地址 大小
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
僅使用輸入目錄。
下一個(gè)使節(jié)頭。首先我們做代碼節(jié)的,代碼節(jié)將包含前面所編的匯編語(yǔ)句。它有32字節(jié)長(zhǎng),所以代碼節(jié)也就是這么長(zhǎng)。節(jié)頭從0x138處開(kāi)始,有0x28字節(jié)長(zhǎng):
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é)頭開(kāi)始于0x160處,有0x28字節(jié)長(zhǎng):
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 ; 已初始化的,可讀,可寫(xiě)
下一個(gè)字節(jié)位于0x188處,但節(jié)需要按32字節(jié)(的倍數(shù))對(duì)齊(因?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é),“到”了。它開(kāi)始于0x1a0處,有0x20字節(jié)長(zhǎng):
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é)的長(zhǎng)度(剛好32字節(jié)),在下一節(jié)(數(shù)據(jù)節(jié))前我們不需要填充任何字節(jié)。下一節(jié)到了,從0x1c0處開(kāi)始:
68 65 6C 6C 6F 2C 20 77 6F 72 6C 64 0A ; "hello, world\n"的ASCII碼值
00 00 00 ; 填充幾個(gè)0以和_written對(duì)齊
00 00 00 00 ; _written
現(xiàn)在剩下的只有輸入目錄了。本文件將從"kernel32.dll"庫(kù)中輸入2個(gè)函數(shù),輸入目錄將從本節(jié)的變量后面立即開(kāi)始。首先我們先將上面的數(shù)據(jù)按32字節(jié)對(duì)齊:
00 00 00 00 00 00 00 00 00 00 00 00 ; 填充的
在0x1e0處開(kāi)始輸入描述(IMAGE_IMPORT_DESCRIPTOR):
OriginalFirstThunk ?? ?? ?? ?? ; 待定
TimeDateStamp 00 00 00 00 ; 未綁定
ForwarderChain ff ff ff ff ; 無(wú)中轉(zhuǎn)
Name ?? ?? ?? ?? ; 待定
FirstThunk ?? ?? ?? ?? ; 待定
我們需要用一個(gè)0字節(jié)項(xiàng)來(lái)結(jié)束輸入目錄(我們現(xiàn)在位于0x1f4):
OriginalFirstThunk 00 00 00 00 ; 結(jié)束符號(hào)
TimeDateStamp 00 00 00 00 ;
ForwarderChain 00 00 00 00 ;
Name 00 00 00 00 ;
FirstThunk 00 00 00 00 ;
現(xiàn)在只剩下DLL名字,還有2個(gè)換長(zhǎng),以及換長(zhǎng)數(shù)據(jù)和函數(shù)名字了。但現(xiàn)在我們真的很快就要完成了。
DLL名字,以0結(jié)尾,開(kāi)始于0x208處:
6b 65 72 6e 65 6c 33 32 2e 64 6c 6c 00 ; "kernel32.dll"的ASCII碼值
00 00 00 ; 填充到32位邊界
原始第一個(gè)換長(zhǎng),開(kāi)始于0x218處:
AddressOfData ?? ?? ?? ?? ; "WriteConsoleA"函數(shù)名的RVA
AddressOfData ?? ?? ?? ?? ; "GetStdHandle"函數(shù)名的RVA
00 00 00 00 ; 結(jié)束符號(hào)
第一個(gè)換長(zhǎng)就是同樣的列表,開(kāi)始于0x224處:
(__imp__WriteConsoleA@20, at 0x224)
AddressOfData ?? ?? ?? ?? ; "WriteConsoleA"函數(shù)名的RVA
(__imp__GetStdHandle@4, at 0x228)
AddressOfData ?? ?? ?? ?? ; "GetStdHandle"函數(shù)名的RVA
00 00 00 00 ; 結(jié)束符號(hào)
現(xiàn)在剩下的只有輸入名字(IMAGE_IMPORT_BY_NAME)形式的兩個(gè)函數(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é)束了。下一個(gè)字節(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)用我們的修正到所有原先被用“??”符號(hào)標(biāo)為“未知”的地址和大小了。
我將不強(qiáng)迫你一步一步地去讀它(很好懂的),只直接給出結(jié)果來(lái):
------------
DOS-頭, 開(kāi)始于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
簽名, 開(kāi)始于0x40:
50 45 00 00
文件頭, 開(kāi)始于0x44:
Machine 4c 01 ; i386
NumberOfSections 02 00 ; 代碼和數(shù)據(jù)
TimeDateStamp 00 00 00 00 ; 誰(shuí)管它?
PointerToSymbolTable 00 00 00 00 ; 未用
NumberOfSymbols 00 00 00 00 ; 未用
SizeOfOptionalHeader e0 00 ; 常量
Characteristics 02 01 ; 可執(zhí)行于32位機(jī)器上
可選頭, 開(kāi)始于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 ; 我們沒(méi)有 BSS節(jié)
AddressOfEntryPoint a0 01 00 00 ; 代碼節(jié)的開(kāi)始處
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é)對(duì)齊
FileAlignment 20 00 00 00 ; 32字節(jié)對(duì)齊
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ū)動(dòng)程序不須用
Subsystem 03 00 ; Win32控制臺(tái)程序
DllCharacteristics 00 00 ; 未用(不是一個(gè)DLL)
SizeOfStackReserve 00 00 10 00 ; 1 MB 棧
SizeOfStackCommit 00 10 00 00 ; 開(kāi)始時(shí)4 KB
SizeOfHeapReserve 00 00 10 00 ; 1 MB 堆
SizeOfHeapCommit 00 10 00 00 ; 開(kāi)始時(shí)4 KB
LoaderFlags 00 00 00 00 ; 未知
NumberOfRvaAndSizes 10 00 00 00 ; 常量
數(shù)據(jù)目錄,開(kāi)始于 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é)), 開(kāi)始于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é)),開(kāi)始于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 ; 已初始化,可讀,可寫(xiě)
(填充)
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é), 開(kāi)始于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é),開(kāi)始于0x1c0:
68 65 6C 6C 6F 2C 20 77 6F 72 6C 64 0A ; "hello, world\n"
00 00 00 ; 填充到和_written對(duì)齊
00 00 00 00 ; _written
填充:
00 00 00 00 00 00 00 00 00 00 00 00 ; 填充的
輸入描述(IMAGE_IMPORT_DESCRIPTOR),開(kāi)始于0x1e0:
OriginalFirstThunk 18 02 00 00 ; 原始第一個(gè)換長(zhǎng)的RVA
TimeDateStamp 00 00 00 00 ; 未綁定
ForwarderChain ff ff ff ff ; -1,無(wú)中轉(zhuǎn)
Name 08 02 00 00 ; DLL名字的RVA
FirstThunk 24 02 00 00 ; 第一個(gè)換長(zhǎng)的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名字, 開(kāi)始于0x208:
6b 65 72 6e 65 6c 33 32 2e 64 6c 6c 00 ; "kernel32.dll"
00 00 00 ; 填充到32位邊界
原始第一個(gè)換長(zhǎng), 開(kāi)始于0x218:
AddressOfData 30 02 00 00 ; 函數(shù)名"WriteConsoleA"的RVA
AddressOfData 40 02 00 00 ; 函數(shù)名"GetStdHandle"的RVA
00 00 00 00 ; 結(jié)束標(biāo)志
第一個(gè)換長(zhǎng),開(kāi)始于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),開(kāi)始于0x230:
01 00 ; 序數(shù),不需要正確
57 72 69 74 65 43 6f 6e 73 6f 6c 65 41 00 ; "WriteConsoleA"的ASCII碼值
IMAGE_IMPORT_BY_NAME,開(kāi)始于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
第一個(gè)未使用字節(jié)開(kāi)始于: 0x260
--------------
噢,這個(gè)文件能在NT上卻不能在windows 95上運(yùn)行。windows 95不能運(yùn)行按32字節(jié)節(jié)對(duì)齊的應(yīng)用程序,它要求節(jié)對(duì)齊為4 KB;并且很明顯的,文件對(duì)齊也應(yīng)為512字節(jié)。因此要想在windows 95上運(yùn)行,你得插入很多的0字節(jié)(為了對(duì)齊)并調(diào)整RVA。感謝D. Binette在windows 95上的(運(yùn)行)試驗(yàn)。
-- 全文結(jié)束 --
[譯后記](méi):
由于時(shí)間等因素,遺漏、重復(fù)、不準(zhǔn)確甚至錯(cuò)誤等情況在所難免,敬請(qǐng)各位批評(píng)、指正!另外,由于我保留了所有的英文術(shù)語(yǔ)(譯文就在后面),所以譯文看起來(lái)有點(diǎn)亂,請(qǐng)大家見(jiàn)諒!
本文的原文寫(xiě)于1999年,由于時(shí)間關(guān)系,文中所說(shuō)的有關(guān)公司、某某項(xiàng)目應(yīng)用的操作系統(tǒng)范圍等等介紹可能已經(jīng)不對(duì)或不準(zhǔn)確了,請(qǐng)大家自己分析、鑒別。
最后再談點(diǎn)個(gè)人感想:
1)個(gè)人覺(jué)得本文的難點(diǎn)在于輸入符號(hào)(表)部分,而其精華乃在附錄之中。在學(xué)習(xí)前面的各種項(xiàng)目成員名稱、說(shuō)明等的同時(shí),如能對(duì)照后面的附錄來(lái)學(xué)習(xí),將會(huì)起到事半功倍的效果。另外,文中所說(shuō)的什么結(jié)構(gòu)體、共用體之類術(shù)語(yǔ)都是針對(duì)編程而言,如果你并不想或不會(huì)編程的話,可以將其理解為一個(gè)將其它東西集合在一起的一個(gè)容器就行了。
2)原文發(fā)表于1998-1999年之間,而相應(yīng)的中文譯文至今也難在網(wǎng)上搜尋得到,這對(duì)中國(guó)的破界來(lái)說(shuō)不能說(shuō)不是一個(gè)很大的遺憾!本文僅起拋磚引玉之用,希望能有更多、更好、更及時(shí)的國(guó)外類似資料出現(xiàn)在我們的網(wǎng)絡(luò)之上,以造福于我們這些廣大的菜鳥(niǎo)。
沈忠平 2006.02 于和州
===========================
|“PE文件格式”1.9版注釋:|
===========================
①Win32s和Win32
Win32s是“WIN32 subset”的縮寫(xiě),它是一個(gè)可被加入到Windows 3.1和Windows for Workgroups系統(tǒng)中以使它們能夠運(yùn)行32位應(yīng)用程序的軟件包。正如它的名字所暗示的那樣,Win32s只是Windows 95和Windows NT系統(tǒng)中使用的Win32 API的一個(gè)子集。Win32s的主要功能就是在32位和16位內(nèi)存地址間相互轉(zhuǎn)換,也就是一種被稱為換長(zhǎng)的操作。
Win32是32位Windows(包括Windows NT,95, 98 和2000等)操作系統(tǒng)的編程接口(API)。當(dāng)應(yīng)用程序是按Win32 API編寫(xiě)時(shí),它們就具有16位API(Win16)所不具備的一些高級(jí)性能。一個(gè)按Win32編寫(xiě)的程序能運(yùn)行在所有的操作系統(tǒng)之上,除非這個(gè)程序要求特定的操作系統(tǒng)特性,而這些特性別的操作系統(tǒng)又沒(méi)有時(shí)。例如,Windows NT提供的安全特性Windows 95/98就沒(méi)有。一個(gè)為NT系統(tǒng)的這些特性編寫(xiě)的程序就不能運(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)指的是鏈接程序(鏈接器)的輸入文件。鏈接器輸出的是映象文件,映象文件反過(guò)來(lái)又是加載器的輸入文件。“object file”一詞未必含有任何和面向?qū)ο蟮木幊逃嘘P(guān)的聯(lián)系。
映象文件(Image file)指的就是可執(zhí)行文件:或者是.EXE,或者是.DLL。一個(gè)映象文件可被想象為“內(nèi)存映象”。“映象文件”一詞常被用來(lái)代替“可執(zhí)行文件”,因?yàn)楹笳哂袝r(shí)被用來(lái)專指.EXE文件。
③UNIX
是一個(gè)很流行的多用戶、多任務(wù)的操作系統(tǒng),由貝爾實(shí)驗(yàn)室于上世紀(jì)70年代早期開(kāi)發(fā)出來(lái)的。只有很少的程序員建立的UNIX系統(tǒng)本來(lái)是設(shè)計(jì)給他們這些程序員專用的、小巧的、靈活的系統(tǒng)。UNIX是用高級(jí)編程語(yǔ)言,就是C語(yǔ)言,編寫(xiě)的第一批操作系統(tǒng)之一。這就意味著只要電腦上有C語(yǔ)言編譯器,UNIX就可以被虛擬地安裝到任何電腦上。天生的可移植性加上低廉的價(jià)格使得UNIX成為各大學(xué)的流行選擇。(因?yàn)榉葱庞脳l款禁止貝爾實(shí)驗(yàn)室將UNIX作為它的全權(quán)產(chǎn)品推向市場(chǎng),所以UNIX的價(jià)格不貴。)
貝爾實(shí)驗(yàn)室只發(fā)布它自己源語(yǔ)言形式的UNIX操作系統(tǒng),所以任何獲得一份拷貝的人都可以按照自己的意愿來(lái)修改和定制它。到上世紀(jì)70年代末時(shí),有好幾十種不同版本的UNIX運(yùn)行在世界各地。(更多信息請(qǐng)參閱別的資料。)
④VMS
“Open Virtual Memory System”或僅VMS,是運(yùn)行于VAX和Alpha系列電腦之上的高端電腦服務(wù)器操作系統(tǒng)的名字,現(xiàn)在用于使用英特爾Itanium CPU的Hewlett-Packard(惠普)系統(tǒng)之上。VAX和Alpha系列電腦由美國(guó)馬薩諸塞州Maynard市的數(shù)據(jù)設(shè)備(DEC)公司(現(xiàn)在由HP擁有)生產(chǎn)的。OpenVMS 是一個(gè)基于多用戶、多處理虛擬存儲(chǔ)的操作系統(tǒng),設(shè)計(jì)用于時(shí)間共享、批處理和事項(xiàng)處理等。
⑤SDK
是“software development kit”(軟件開(kāi)發(fā)工具箱)的縮寫(xiě),它是一個(gè)供程序員為特定平臺(tái)開(kāi)發(fā)應(yīng)用程序的編程包。典型的,一個(gè)SDK包含一個(gè)或多個(gè)API庫(kù)、各種編程工具和相關(guān)文檔等。
⑥Ne Format(New-style EXE Format的縮寫(xiě))
是一個(gè)早期Windows操作系統(tǒng)的可執(zhí)行文件(.EXE),包含一個(gè)代碼和數(shù)據(jù)的集合或者一個(gè)代碼、數(shù)據(jù)和資源的集合。這種可執(zhí)行文件也包括兩個(gè)頭:一個(gè)MS-DOS頭和一個(gè)Windows頭,和一些節(jié)。(具體參看其他資料)
⑦OS/2(IBM Operating System/2,IBM 操作系統(tǒng)/2)
操作系統(tǒng)/2(OS/2)最初是由 Microsoft 和 IBM 共同合作開(kāi)發(fā)的一種應(yīng)用于 PC 機(jī)的操作系統(tǒng)。現(xiàn)在只由 IBM 銷售、支持和管理。其設(shè)計(jì)目標(biāo)是替換傳統(tǒng)的 DOS 操作系統(tǒng)。OS/2 與 DOS、Windows 都相兼容。換句話說(shuō),OS/2 操作系統(tǒng)可運(yùn)行所有的 DOS 和 Windows 程序,但在 OS/2 下運(yùn)行的某些特殊寫(xiě)程序卻不能在 DOS 或 Windows 下運(yùn)行。
OS/2 是一個(gè)32位的、為個(gè)人計(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 以文檔為中心,允許用戶訪問(wèn)文件和打印機(jī),并可以啟動(dòng)程序。WPS 遵循 IBM 的用戶界面標(biāo)準(zhǔn),即“通用用戶訪問(wèn)”。
OS/2 操作系統(tǒng)中包含一種系統(tǒng)對(duì)象模型(SOM),包括磁盤(pán)、文件夾、文件、程序?qū)ο蠹按蛴C(jī)等對(duì)象。SOM 允許應(yīng)用程序間代碼共享,但這與編程語(yǔ)言無(wú)關(guān)。一種稱為 DSOM 的分布式版本支持不同計(jì)算機(jī)上對(duì)象間的相互通信。DSOM 建立在 CORBA 基礎(chǔ)上。SOM 類似于微軟的組件對(duì)象模型(Component Object Model),同時(shí)兩者相互競(jìng)爭(zhēng)。目前人們對(duì) SOM 和 DSOM 已停止深度開(kāi)發(fā)。
OS/2 操作系統(tǒng)也包括一種叫做 OpenDoc 的混合文檔技術(shù),它由 Apple 開(kāi)發(fā)而成。但目前人們對(duì) OpenDoc 也已停止深度開(kāi)發(fā)。
由于 OS/2 存在市場(chǎng)局限性,IBM 公司已于2003年3月12日按照電子商務(wù)計(jì)劃停止了 OS/2 的發(fā)展市場(chǎng)。
⑧MIPS
MIPS是世界上很流行的一種RISC處理器。MIPS的意思是“無(wú)內(nèi)部互鎖流水級(jí)的微處理器”(Microprocessor without interlocked piped stages),其機(jī)制是盡量利用軟件辦法避免流水線中的數(shù)據(jù)相關(guān)問(wèn)題。它最早是在80年代初期由斯坦福(Stanford)大學(xué)Hennessy教授領(lǐng)導(dǎo)的研究小組研制出來(lái)的。MIPS公司的R系列就是在此基礎(chǔ)上開(kāi)發(fā)的RISC工業(yè)產(chǎn)品的微處理器。這些系列產(chǎn)品為很多計(jì)算機(jī)公司采用構(gòu)成各種工作站和計(jì)算機(jī)系統(tǒng)。如R3000、R4000、R10000等都是其生產(chǎn)的處理器。
MIPS技術(shù)公司是美國(guó)著名的芯片設(shè)計(jì)公司,它采用精簡(jiǎn)指令系統(tǒng)計(jì)算結(jié)構(gòu)(RISC)來(lái)設(shè)計(jì)芯片。和英特爾采用的復(fù)雜指令系統(tǒng)計(jì)算結(jié)構(gòu)(CISC)相比,RISC具有設(shè)計(jì)更簡(jiǎn)單、設(shè)計(jì)周期更短等優(yōu)點(diǎn),并可以應(yīng)用更多先進(jìn)的技術(shù),開(kāi)發(fā)更快的下一代處理器。MIPS是出現(xiàn)最早的商業(yè)RISC架構(gòu)芯片之一,新的架構(gòu)集成了所有原來(lái)MIPS指令集,并增加了許多更強(qiáng)大的功能。
⑨big-endian、Little-endian和endian
Big-endian和Little-endian是用來(lái)表述一組有序的字節(jié)數(shù)存放在計(jì)算機(jī)內(nèi)存中時(shí)的順序的術(shù)語(yǔ)。Big-endian(即“大端結(jié)束”或者“大尾”)是將高位字節(jié)(序列中最重要的值)先存放在低地址處的順序,而Little-endian(即“小端結(jié)束”或者“小尾”)是將低位字節(jié)(序列中最不重要的值)先存放在低地址處的順序。舉例來(lái)說(shuō),在使用Big-endian順序的計(jì)算機(jī)中,要存儲(chǔ)一個(gè)十六進(jìn)制數(shù)4F52所需要的字節(jié)將會(huì)以4F52的形式存儲(chǔ)(比如4F存放在內(nèi)存的1000位置,而52將會(huì)被存儲(chǔ)在1001位置)。而在使用Little-endian順序的系統(tǒng)中,存儲(chǔ)的形式將會(huì)是524F(52在地址1000處,4F在地址1001處)。IBM的370種大型機(jī)、大多數(shù)基于RISC的計(jì)算機(jī)以及Motorola的微處理器使用的是Big-endian順序,TCP/IP協(xié)議也是。而Intel的處理器和DEC公司的一些程序則使用的Little-endian方式。
“endian”這個(gè)詞出自《格列佛游記》。小人國(guó)的內(nèi)戰(zhàn)就源于吃雞蛋時(shí)是究竟從大頭(Big-Endian)敲開(kāi)還是從小頭(Little-Endian)敲開(kāi),由此曾發(fā)生過(guò)六次叛亂,其中一個(gè)皇帝送了命,另一個(gè)丟了王位。
我們一般將endian翻譯成“字節(jié)序”,將big endian和little endian稱作“大尾”和“小尾”。
⑩Alpha AXP
“DEC Alpha”,也被稱作“Alpha AXP”,是一個(gè)原來(lái)由美國(guó)數(shù)據(jù)設(shè)備公司(DEC)開(kāi)發(fā)和制造的64位RISC微處理器(例如:DEC Alpha AXP 21064 微處理器),他們將它用在自己的工作站和服務(wù)器系列上。被設(shè)計(jì)作為VAX系列計(jì)算機(jī)的繼承者,Alpha AXP不但支持VMS操作系統(tǒng),同時(shí)也支持Digital UNIX操作系統(tǒng)。后來(lái)的一些開(kāi)放源碼操作系統(tǒng)也能運(yùn)行于Alpha之上,著名的Linux和BSD UNIX操作系統(tǒng)特別支持。微軟直到Windows NT 4.0 SP6才支持這種處理器,但Windows 2000第2版之后就又不支持了。
?UTC
是“Coordinated Universal Time”的縮寫(xiě),意為“協(xié)調(diào)通用時(shí)間”,它是綜合了只以地球的不停旋轉(zhuǎn)速率為基準(zhǔn)的格林威治標(biāo)準(zhǔn)時(shí)間(Greenwich Mean Time)和高度精確的原子時(shí)間的一種時(shí)標(biāo)。當(dāng)原子時(shí)間和地球時(shí)間達(dá)到一秒的時(shí)差時(shí),一個(gè)閏秒就被算進(jìn)UTC時(shí)間中。UTC設(shè)計(jì)于1972年1月1日,并被國(guó)際度量衡局(International Bureau of Weights and Measures)于巴黎協(xié)調(diào)通過(guò)。跟格林威治標(biāo)準(zhǔn)時(shí)間一樣,UTC也被設(shè)定于0經(jīng)度的本初子午線。
?BSS
是“Block Started by Symbol”的縮寫(xiě),意為“以符號(hào)開(kāi)始的塊”。BSS是Unix鏈接器產(chǎn)生的未初始化數(shù)據(jù)段。其他的段分別是包含程序代碼的“text”段和包含已初始化數(shù)據(jù)的“data”段。BSS段的變量只有名稱和大小卻沒(méi)有值。此名后來(lái)被許多文件格式使用,包括PE。
“以符號(hào)開(kāi)始的塊”指的是編譯器處理未初始化數(shù)據(jù)的地方。BSS節(jié)不包含任何數(shù)據(jù),只是簡(jiǎn)單的維護(hù)開(kāi)始和結(jié)束的地址,以便內(nèi)存區(qū)能在運(yùn)行時(shí)被有效地清零。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)的一個(gè)錯(cuò)誤,甚至包括最早版本的Windows,比如Windows 3.0和3.1,在后來(lái)的Windows版本比如Microsoft Windows 95, Windows 98, Windows NT,和Windows 2000上仍能出現(xiàn)。它被開(kāi)玩笑地稱為藍(lán)屏之死是因?yàn)殄e(cuò)誤發(fā)生時(shí),屏幕變成藍(lán)色,電腦總是不能正常運(yùn)轉(zhuǎn)并需要重新啟動(dòng)。
?POSIX
是“Portable Operating System Interface for UNIX”(UNIX可移植操作系統(tǒng)接口)的首字母縮寫(xiě),它是定義程序和操作系統(tǒng)之間的接口的一套IEEE和ISO標(biāo)準(zhǔn)。通過(guò)將他們的程序設(shè)計(jì)為符合POSIX標(biāo)準(zhǔn),開(kāi)發(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)委員會(huì))維護(hù)。
?thunk
(動(dòng)詞) 換長(zhǎng),變長(zhǎng);已經(jīng)想到的,預(yù)先想到的
(指在個(gè)人電腦中,將一個(gè)16位內(nèi)存地址轉(zhuǎn)換為一個(gè)32位的地址,或者相反。換長(zhǎng)是必須的,因?yàn)橛⑻貭柕睦?6位微處理器使用一種叫分段內(nèi)存的定址方式,而它的32位微處理器使用的卻是一個(gè)統(tǒng)一的地址空間。Window 95支持一種允許32位程序調(diào)用16位DLL的換長(zhǎng)機(jī)制,叫統(tǒng)一換長(zhǎng)。而另一方面,運(yùn)行在Windows 3.x和Windows for Workgroup下的16位應(yīng)用程序不能使用32位DLL,除非32位地址被轉(zhuǎn)換為16位地址。這就是Win32的功能,并被稱為通用換長(zhǎng)。
根據(jù)民間傳說(shuō),thunk一詞是由一位Algol-60編程語(yǔ)言的開(kāi)發(fā)者編出的,他在一天深夜意識(shí)到參數(shù)的數(shù)據(jù)類型是可以被編譯器稍先一點(diǎn)知道的。也就是說(shuō),到了編譯器處理參數(shù)的時(shí)候,它就已經(jīng)想到了(thunked)數(shù)據(jù)類型了。該詞的含義近年來(lái)已變化很大了。)
(名詞)換長(zhǎng),變長(zhǎng)(在一個(gè)分段內(nèi)存地址空間和一個(gè)統(tǒng)一地址空間之間互相轉(zhuǎn)換的操作)
(我查遍書(shū)店中所有的大大小小的英漢和英英詞典,都沒(méi)有找到thunk這個(gè)詞的含義。后在網(wǎng)上找到了它的英語(yǔ)解釋,卻找不到它對(duì)應(yīng)的漢語(yǔ)譯法,現(xiàn)根據(jù)它的意思,姑且譯之。各位勿笑,還請(qǐng)高手指點(diǎn)。)
(英文參見(jiàn):
http://www.webopedia.com/TERM/T/thunk.html)?MSB
“Most Significant Bit”的首字母縮寫(xiě),意為“最重要的位”。在一個(gè)二進(jìn)制的數(shù)字中,它就是最左邊的那一位,也是最重要的那一位。