【翻譯】“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)文件②和庫文件中。
這種文件格式是由微軟設(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的一部分,但其書面文件卻很缺乏。它的一些功用在“Developer Network”(開發(fā)者網(wǎng)絡(luò))中有所描述。
?
二、總覽(General Layout)
-------------------------
在一個(gè)PE文件的開始處,我們會(huì)看到一個(gè)MS-DOS可執(zhí)行體(英語叫“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格式),用來說明該二進(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文件格式僅為庫文件使用一個(gè)“可選頭”,卻不為目標(biāo)文件使用一個(gè)“可選頭”,這就是為什么它被稱為“可選”的原因)。它會(huì)告訴我們?cè)摱M(jìn)制文件怎樣被載入的更多信息:開始的地址呀、保留的堆棧數(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è)可通過可選頭的“數(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)時(shí)是“NE”格式⑥)時(shí)就廣為人知了。根原來是用于OS/2⑦系統(tǒng)的可執(zhí)行文件的,也用于自解壓檔案文件和其它的應(yīng)用程序。對(duì)于PE文件來說,它是一個(gè)總是由大約100個(gè)字節(jié)所組成的和MS-DOS 2.0兼容的可執(zhí)行體,用來輸出象“this program needs windows NT”之類的錯(cuò)誤信息。
你可以通過確認(rèn)DOS-頭部分是否為一個(gè)IMAGE_DOS_HEADER(DOS頭)結(jié)構(gòu)來認(rèn)出DOS-根,它的前兩個(gè)字節(jié)必須為連續(xù)的兩個(gè)字母“MZ”(有一個(gè)#define IMAGE_DOS_SIGNATURE的定義是針對(duì)這個(gè)WORD單元的)。
你可以通過跟在后面的簽名來將一個(gè)PE二進(jìn)制文件和其它含有根的二進(jìn)制文件區(qū)分開來,跟在后面的簽名可由頭成員'e_lfanew'(它是從字節(jié)偏移地址60處開始的,有32字節(jié)長(zhǎng))所設(shè)定的偏移地址找到。對(duì)于OS/2系統(tǒng)和Windows系統(tǒng)的二進(jìn)制文件來說,簽名是一個(gè)16位的word單元;對(duì)于PE文件來說,它是一個(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”,并從文件開始處跳過那么多的字節(jié)。在核實(shí)你在那里找到的簽名后,IMAGE_FILE_HEADER(文件頭)結(jié)構(gòu)的文件頭就緊跟其后開始了,下面我們將從頭至尾的介紹其成員。
1)第一個(gè)成員是“Machine(機(jī)器)”,一個(gè)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)下一個(gè)成員是時(shí)間戳“TimeDateStamp”(32位),用來給出文件建立的時(shí)間。即使它的“官方”版本號(hào)沒有改變,你也可通過這個(gè)值來區(qū)分同一個(gè)文件的不同版本。(除了同一個(gè)文件的不同版本之間必須唯一,時(shí)間戳的格式?jīng)]有明文規(guī)定,但似乎是按照UTC時(shí)間“從1970年1月1日00:00:00算起的秒數(shù)值”----也就是大多數(shù)C語言編譯器給time_t標(biāo)志使用的格式。)
這個(gè)時(shí)間戳是用來綁定各個(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)文件和庫文件有效。具體如下:
??? 位0 IMAGE_FILE_RELOCS_STRIPPED(重定位被剝離文件) 表示如果文件中沒有重定位信息,該位置1,這就表明各節(jié)的重定位信息都在它們各自的節(jié)中;可執(zhí)行文件不使用該位,它們的重定位信息放在下面將要描述的“base relocation”(基址重定位)目錄中。
??? 位1 IMAGE_FILE_EXECUTABLE_IMAGE(可執(zhí)行映象文件) 表示如果文件是一個(gè)可執(zhí)行文件,也即不是目標(biāo)文件或者庫文件時(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)被剝離文件) 表示如果文件中沒有關(guān)于本地符號(hào)的信息時(shí),此位置1(此位也不用于可執(zhí)行文件)。
??? 位4 IMAGE_FILE_AGGRESIVE_WS_TRIM(強(qiáng)行工作集修剪文件) 表示如果操作系統(tǒng)被假定為:通過將正在運(yùn)行的進(jìn)程(它所使用的內(nèi)存數(shù)量)強(qiáng)行的頁清除來修剪它的工作集時(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)試信息被剝離文件) 表示如果文件中沒有調(diào)試信息,此位置1。此位可執(zhí)行文件不用。按照其它信息([6])(這里指的是參考書目中的第[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ì),如軟盤或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í)行文件不用;我所見過的所有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)上(也就是說,因?yàn)榇宋募?yán)格地依賴單一處理器的一些方式工作,所以它會(huì)發(fā)生沖突)時(shí),此位置1。
?
五、相對(duì)虛擬地址(Relative Virtual Addresses)
---------------------------------------------
PE格式大量地使用所謂的RVA(相對(duì)虛擬地址)。一個(gè)RVA,亦即一個(gè)“Relative Virtual Addresses(相對(duì)虛擬地址)”,是在你不知道基地址時(shí),被用來描述一個(gè)內(nèi)存地址的。它是需要加上基地址才能獲得線性地址的數(shù)值。基地址就是PE映象文件被裝入內(nèi)存的地址,并且可能會(huì)隨著一次又一次的調(diào)用而變化。
例如:假若一個(gè)可執(zhí)行文件被裝入的地址是0x400000,并且從RVA 0x1560處開始執(zhí)行,那么有效的執(zhí)行開始處將位于0x401560地址處。假若它被裝入的地址為0x100000,那么執(zhí)行開始處就位于0x101560地址處。
因?yàn)镻E-文件中的各部分(各節(jié))不需要像已載入的映象文件那樣對(duì)齊,事情變得復(fù)雜起來。例如,文件中的各節(jié)常按照512(十六進(jìn)制的0x200----譯者注)字節(jié)邊界對(duì)齊,而已載入的映象文件則可能按照4096(十六進(jìn)制的0x1000----譯者注)字節(jié)邊界對(duì)齊。參見下面的“SectionAlignment(節(jié)對(duì)齊)”和“FileAlignment(文件對(duì)齊)”。
因此,為了在PE文件中找到一個(gè)特定RVA地址的信息,你得按照文件已被載入時(shí)的那樣來計(jì)算偏移量,但要按照文件的偏移量來跳過。
試舉一例,假若你已知道執(zhí)行開始處位于RVA 0x1560地址處,并且想從那里開始的代碼處反匯編。為了從文件中找到這個(gè)地址,你得先查明在RAM(內(nèi)存)中各節(jié)是按照4096字節(jié)對(duì)齊的,并且“.code”節(jié)是從RVA 0x1000地址處開始,有16384字節(jié)長(zhǎng);然后你才知道RVA 0x1560地址位于此節(jié)的偏移量0x560處。你還要查明在文件中那節(jié)是按512字節(jié)邊界對(duì)齊,且“.code”節(jié)在文件中從偏移量0x800處開始,然后你就知道在文件中代碼的執(zhí)行開始處就在0x800+0x560=0xd60字節(jié)處。
然后你反匯編它并發(fā)現(xiàn)訪問一個(gè)變量的線性地址位于0x1051d0處。二進(jìn)制文件的線性地址在裝入時(shí)將被重定位,并常被假定使用的是優(yōu)先載入地址。因?yàn)槟阋巡槊鲀?yōu)先載入地址為0x100000,因此我們可開始處理RVA 0x51d0了。因數(shù)據(jù)節(jié)開始于RVA 0x5000處,且有2048字節(jié)長(zhǎng),所以它處于數(shù)據(jù)節(jié)中。又因數(shù)據(jù)節(jié)在文件中開始于偏移量0x4800處,所以該變量就可以在文件中的0x4800+0x51d0-0x5000=0x49d0處找到。
?
六、可選頭(Optional Header)
----------------------------
緊跟在文件頭后面的就是IMAGE_OPTIONAL_HEADER(盡管它名叫“可選頭”,它卻一直都在那里)。它包含有怎樣去準(zhǔn)確處理PE文件的信息。我們也將從頭至尾的介紹其成員。
1)第一個(gè)16位的word單元叫“Magic(魔數(shù))”,就我目前所觀察過的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位)分別用來設(shè)定可執(zhí)行代碼的大小(“SizeOfCode”)、已初始化數(shù)據(jù)的大小(“SizeOfInitializedData”,所謂的“數(shù)據(jù)段”)、以及未初始化數(shù)據(jù)的大小(“SizeOfUninitializedData”,所謂的“bss段”)。這些值也是不可靠的(例如:數(shù)據(jù)段實(shí)際上可能會(huì)被編譯器或者鏈接器分成好幾段),并且你可以通過查看可選頭后面的各個(gè)“節(jié)”來獲得更準(zhǔn)確的大小。
7)下一個(gè)32位值是RVA。這個(gè)RVA是代碼入口點(diǎn)的偏移量(‘AddressOfEntryPoint’,“入口點(diǎn)地址”)。執(zhí)行將從這里開始,它可以是:例如DLL文件的LibMain()的地址,或者一個(gè)程序的開始代碼(這里相應(yīng)的叫main())的地址,或者驅(qū)動(dòng)程序的DriverEntry()的地址。如果你敢于“手工”裝載映象文件,那么在你完成所有的修正和重定位后,你可以從這個(gè)地址開始執(zhí)行你的進(jìn)程。
8-9)下兩個(gè)32位值分別是可執(zhí)行代碼的偏移值(‘BaseOfCode’,“代碼基址”)和已初始化數(shù)據(jù)的偏移值(‘BaseOfData’,“數(shù)據(jù)基址”),兩個(gè)都是RVA,并且兩個(gè)對(duì)我們來說都沒有多少意義,因?yàn)槟憧梢酝ㄟ^查看可選頭后面的各個(gè)“節(jié)”來獲得更可靠的信息。
未初始化的數(shù)據(jù)沒有偏移量,正因?yàn)樗鼪]有初始化,所以在映象文件中提供這些數(shù)據(jù)是沒有用處的。
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í),就不能用了。在這些情況下,映象文件必須被載人其它的地址,并且需要重定位(參見下面的“重定位目錄”)。如果是一個(gè)DLL文件,這么做還會(huì)產(chǎn)生其它問題,因?yàn)榇藭r(shí)的“綁定輸入”已不再有效,所以使用DLL的二進(jì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)”)[它們都使用微軟自己書面確定的名字]。這個(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)樗皶?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)上只有程序管理器而沒有瀏覽器、等等,于是NT 4.0系統(tǒng)就盡可能地仿照那個(gè)系統(tǒng)的行為來運(yùn)行程序。
19)然后,我們便碰到32位的“Win32VersionValue”(Win32版本值)。我不清楚它有什么作用。在我所觀察過的PE文件中,它全部都為0。
20)下一個(gè)是32位值,給出映象文件將要使用的內(nèi)存數(shù)量,單位為字節(jié)(‘SizeOfImage’,“映象文件大小”)。如果是按照“SectionAlignment”對(duì)齊的,它就是所有頭和節(jié)的長(zhǎng)度的總和。它提示加載器,為了載入映象文件需要多少頁。
21)下一個(gè)是32位值,給出所有頭的總長(zhǎng)度,包括數(shù)據(jù)目錄和節(jié)頭(‘SizeOfHeaders’,“頭的大小”)。同時(shí),它也是從文件的開頭到第一節(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)和的目的是為了防止載入無論如何都會(huì)沖突的、已損壞的二進(jìn)制文件----況且一個(gè)沖突的驅(qū)動(dòng)程序會(huì)導(dǎo)致一個(gè)BSOD錯(cuò)誤,因此最好根本就不載入這樣的壞文件。
23)然后,就到了一個(gè)16位的word單元“Subsystem”(子系統(tǒng)),用來說明映象文件應(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()打開一個(gè)控制臺(tái)界面,但在開始時(shí)卻不能自動(dòng)地打開。)
????????????
??????? IMAGE_SUBSYSTEM_WINDOWS_CUI (3)
???????????? 二進(jìn)制文件是一個(gè)Win32控制臺(tái)界面二進(jìn)制文件。(它將在開始時(shí)按照缺省值打開一個(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);在程序開始時(shí),“指定的”數(shù)量是指在RAM中實(shí)際分配的大小。如果需要的話,“指定的”值也是指定的堆或棧用來增加的數(shù)量。(有資料說,不管“SizeOfStackCommit”的值是多少,棧都是按頁增加的。我沒有驗(yàn)證過。)
因此,舉例來說,如一個(gè)程序的保留堆有1 MB,指定堆為64 KB,那么啟動(dòng)時(shí)堆的大小為64 KB,并且保證可以擴(kuò)大到1 MB。堆將按64 KB一塊來增加。
“堆”在本文中是指主要(缺省)堆。如果它愿意的話,一個(gè)進(jìn)程可創(chuàng)建很多堆。
棧是指第一個(gè)線程的棧(啟動(dòng)main()的那個(gè))。進(jìn)程可以創(chuàng)建很多線程,每個(gè)線程都有自己的棧。
DLL文件沒有自己的堆或棧,所以它們的映象文件忽略這些值。我不知道驅(qū)動(dòng)程序是否有它們自己的堆或棧,但我認(rèn)為它們沒有。
29)堆和棧的這些描述之后,我們就發(fā)現(xiàn)一個(gè)32位的“LoaderFlags(加載器標(biāo)志)”,我沒有找到它的任何有用的描述。我只發(fā)現(xiàn)一篇時(shí)新的關(guān)于設(shè)置此標(biāo)志位的短文,說設(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ù)目)來代替它,或者用它們中的較小者。
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)目錄;參見后面。
???
??? 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)建一個(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)
??????? 綁定輸入目錄 - 參見輸入目錄的描述。
???
??? IMAGE_DIRECTORY_ENTRY_IAT (12)
??????? 輸入地址表 - 參見輸入目錄的描述。
試舉一例,如果我們?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)制文件中沒有使用特殊類型的目錄,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位都被用光,該字符串就沒有0結(jié)束符!典型的名稱象“.data”或“.text”或“.bss”形式。開頭的“.”不是必須的,名稱也可能為“CODE”或“IAT”或類似的形式。
請(qǐng)注意:并不是所有的名稱都和節(jié)中的內(nèi)容相關(guān)。一個(gè)名叫“.code”的節(jié)可能包含也可能不包含可執(zhí)行代碼;它還可能只包含輸入地址表;它也可能包含代碼“和”地址表“和”未初始化數(shù)據(jù)。要找到節(jié)中的信息,你必須通過可選頭的數(shù)據(jù)目錄來查詢它。既不要過分相信它的名稱,也不要以為節(jié)的原始數(shù)據(jù)會(huì)從節(jié)的開頭就開始。
2)IMAGE_SECTION_HEADER(“節(jié)頭”)的下一個(gè)成員是一個(gè)32位的、“PhysicalAddress(物理地址)”和“VirtualSize(虛擬大小)”組成的共用體。在目標(biāo)文件中,它是內(nèi)容重定位到的地址;在可執(zhí)行文件中,它是內(nèi)容的大小。事實(shí)上,此域似乎沒被使用;因?yàn)橛械逆溄悠鬏斎氪笮。械逆溄悠鬏斎氲刂罚疫€發(fā)現(xiàn)有一個(gè)鏈接器輸入0,而所有的可執(zhí)行文件都運(yùn)行如風(fēng)。
3)下一個(gè)成員是“VirtualAddress(虛擬地址)”,是一個(gè)32位的值,用來保存載入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)樗菑奈募拈_頭到節(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í)行開始前即取得已定義值的數(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)樗灿幸粋€(gè)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 指定我沒有找到信息的對(duì)齊。諸如#defines IMAGE_SCN_ALIGN_16BYTES之類。我曾經(jīng)見過的唯一值為0,是16位的缺省對(duì)齊。 我懷疑它們是庫之類文件的目標(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)后就不需要了。它是,舉例來說,含有重定位信息的情況。我曾經(jīng)見過它也用于只執(zhí)行一次的驅(qū)動(dòng)和服務(wù)程序的啟動(dòng)例程,還用于輸入目錄。
??? 如果位26 IMAGE_SCN_MEM_NOT_CACHED(內(nèi)存不緩存節(jié))被置1,表示節(jié)中的數(shù)據(jù)不應(yīng)該被緩存。不要問我為什么不。這是不是意味著關(guān)掉2級(jí)緩存?
??? 如果位27 IMAGE_SCN_MEM_NOT_PAGED(內(nèi)存不可頁換出節(jié))被置1,表示節(jié)中的數(shù)據(jù)不應(yīng)該頁換出。它對(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é)總是被共享寫時(shí)拷貝(copy-on-write)(亦即:如果重定位必不可少,那么共享就不工作)。(譯注:“寫時(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)存可寫節(jié))被置1,表示進(jìn)程對(duì)節(jié)的內(nèi)存有“寫”的存取權(quán)限。
在節(jié)頭之后,我們就會(huì)發(fā)現(xiàn)節(jié)本身。在文件中,它們按照“FileAlignment”(文件對(duì)齊)的字節(jié)數(shù)對(duì)齊(也就是說,在可選頭之后和每個(gè)節(jié)的數(shù)據(jù)之后將要填充一些字節(jié))并按照它們的RVA排序。在載入后(內(nèi)存中),? 它們按照“SectionAlignment”(節(jié)對(duì)齊)的字節(jié)數(shù)對(duì)齊。
試舉一例,如果可選頭在文件的偏移量981處結(jié)束,“FileAlignment”(文件對(duì)齊)的值為512,那么第一個(gè)節(jié)將于1024字節(jié)處開始。注意:你可通過“PointerToRawData”(原始數(shù)據(jù)指針)或者“VirtualAddress”(虛擬地址)的值來找到各節(jié),因此實(shí)際上根本沒必要在對(duì)齊上小題大做。
試畫映象文件的全圖如下:
??? +-------------------+
??? |???? 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é)沒有數(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)來描述:在文件中你可通過“PointerToRawData”(原始數(shù)據(jù)指針)來找到,在內(nèi)存中你可通過“VirtualAddress”(虛擬地址)來找到;長(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è)地方,指向開發(fā)者希望首先執(zhí)行的那個(gè)函數(shù)的開始處。
“BaseOfCode”(代碼基址)通常指向這一節(jié)的開始處,但是,如果一些非代碼字節(jié)被放在代碼之前的話,它也可能指向節(jié)中靠后的某個(gè)地方。
通常,除了可執(zhí)行代碼外,本節(jié)沒有別的東東,并且通常只有一個(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)存可寫節(jié))等標(biāo)志位被置為1。
一些鏈接器可能會(huì)將常量放在沒有可寫標(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é)頭,沒有節(jié)身;節(jié)身將由加載器創(chuàng)建,并全部為0字節(jié)。
它的長(zhǎng)度由“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)
-------------------
為了從一個(gè)簡(jiǎn)單的目錄節(jié)開始講解,讓我們來看一看數(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í)并不需要,并可能被丟棄。它是不可寫的;事實(shí)上,應(yīng)用程序根本不需要存取它。因此,如果已有一個(gè)可丟棄的、非可寫的節(jié)存在,鏈接器就會(huì)找到它;如果沒有,就創(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è)版本中,輸出目錄的描述有誤。文中沒有描述中轉(zhuǎn)、只以序數(shù)輸出、或者使用好幾個(gè)名稱輸出等內(nèi)容。)
下一件最簡(jiǎn)單的事情是輸出目錄,是由“IMAGE_DIRECTORY_ENTRY_EXPORT”(輸出目錄項(xiàng))指向的。它是一個(gè)典型的在DLL中常見到的目錄;包含一些輸出函數(shù)的入口點(diǎn)(以及輸出對(duì)象等的地址)。當(dāng)然可執(zhí)行文件也可能擁有輸出符號(hào)但一般沒有。
包含它們的節(jié)應(yīng)該有“已初始化數(shù)據(jù)的”和“可讀的”特性。這樣的節(jié)應(yīng)該是不可丟棄的,因?yàn)樵谶\(yùn)行時(shí),進(jìn)程有可能調(diào)用“GetProcAddress()”來尋找一個(gè)函數(shù)的入口點(diǎn)。如果單獨(dú)成節(jié)的話,本節(jié)通常被稱作“.edata”;更常見的是,它被并入象“已初始化數(shù)據(jù)”之類的節(jié)中。
輸出表(“IMAGE_EXPORT_DIRECTORY”)的結(jié)構(gòu)由一個(gè)頭和輸出數(shù)據(jù),也就是:符號(hào)名稱、它們的序號(hào)和它們的入口點(diǎn)偏移量等構(gòu)成。
1)首先,我們有一個(gè)沒被使用并通常為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ò)誤,名稱是必須的----參見輸入目錄中的“綁定”部分。)
6)然后是32位的“Base”(基址)。稍后我們?cè)儆懻摗?/p>
7)下一個(gè)32位值是輸出條目的總數(shù)(“NumberOfFunctions”,意為‘函數(shù)數(shù)’)。除了它們的序數(shù)外,各條目還可能使用一個(gè)或多個(gè)名稱來輸出。接下來的一個(gè)32位數(shù)字是輸出名稱的總數(shù)(“NumberOfNames”,意為‘名字?jǐn)?shù)’)。
在大多數(shù)情況下,每一個(gè)輸出條目都準(zhǔn)確的有一個(gè)相應(yīng)的名稱,并且將用這個(gè)名稱來使用它;但是一個(gè)條目可能擁有好幾個(gè)相關(guān)聯(lián)的名稱(那樣它們的每一個(gè)名稱都可訪問);或者它也可能沒有名稱,此時(shí)它只能以它的序數(shù)來訪問。無名輸出項(xiàng)(只有序數(shù))的使用是不鼓勵(lì)的,因?yàn)榇藭r(shí)輸出DLL的所有版本都必須使用相同的序數(shù)法,而這會(huì)造成維護(hù)的問題。
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,在此情況下,此值沒被使用。第二,如果一RVA指向含有輸出目錄的節(jié),那么它就是一個(gè)中轉(zhuǎn)輸出。一個(gè)中轉(zhuǎn)輸出就是指指向另一個(gè)二進(jìn)制文件中的輸出項(xiàng)的指針;如果使用了它,就可用另一個(gè)二進(jìn)制文件中的被指向的輸出項(xiàng)來代替使用。此時(shí)的RVA指向,正如已提到的,輸出目錄的節(jié)中,指向一個(gè)以以零結(jié)尾的字符串組成的、被指向的DLL的名稱和一個(gè)用點(diǎn)分開的輸出項(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ù)組中去。
我將畫一個(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é)果沒有指向輸出節(jié)中,你就完了。否則,它就指向那里的一個(gè)描述輸出DLL和(輸出項(xiàng))名稱或序數(shù)的字符串,之后你就得在那里查找中轉(zhuǎn)輸出。
為按名稱找到一個(gè)輸出符號(hào),先跟隨“AddressOfNames”(名字的地址)的RVA(如果是0就沒有名稱)找到輸出名稱的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ì)此情況一無所知,只是簡(jiǎn)單地輸出一個(gè)對(duì)那個(gè)符號(hào)的正常調(diào)用指令。鏈接器不得不修正那個(gè)符號(hào)的地址,就象它為任何其它的外部符號(hào)所做的那樣。
鏈接器使用一個(gè)輸入庫來查找從哪個(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)用,繞過跳轉(zhuǎn)。
不管怎樣,DLL文件中的函數(shù)地址總是必要的,并將于應(yīng)用程序載入時(shí),由加載器從輸出DLL文件的輸出目錄中提供。加載器知道哪個(gè)庫中的哪些符號(hào)需要被查找以及哪些地址需要通過搜索輸入目錄來修正。
我最好給你一個(gè)例子。有或無__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”位于一個(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)”來使用輸入符號(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文件中的輸入庫提供給鏈接器的。輸入地址表就是由下面這種形式的一組地址組成的:
?? __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))目錄也能解決輸入問題)。
這些地址并不被鏈接器所知;鏈接器只插入一些偽地址(函數(shù)名稱的RVA;參見后面的更多信息),這些偽地址會(huì)在載入時(shí)被加載器用輸出DLL文件中的輸出目錄來修正。輸入地址表,以及它是怎樣被加載器找到的,將會(huì)在本章的后面被詳細(xì)講述。
注意:這個(gè)介紹是針對(duì)C語言規(guī)范的;有些別的應(yīng)用程序構(gòu)建環(huán)境是不使用輸入庫的,盡管它們都需要建立一個(gè)輸入地址表,用來讓它們的程序訪問輸入對(duì)象和函數(shù)。C語言編譯器往往使用輸入庫,因?yàn)闊o論如何講,這都有利于它們----它們的鏈接器使用好庫。別的環(huán)境使用的是例如:一個(gè)列出需要的DLL文件名稱和函數(shù)名稱的描述文件(比如“模塊定義文件”),或者一個(gè)源文件中的聲明形式的列表等。
這就是程序的代碼如何使用輸入函數(shù)的;現(xiàn)在我們?cè)賮砜纯摧斎肽夸浭侨绾谓⒁员慵虞d器使用的。
輸入目錄應(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))(漢譯的說明見注釋)
??????? 它是一個(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é)尾。)
目前看來,每個(gè)IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))都是一個(gè)RVA,指向一個(gè)描述輸入函數(shù)的IMAGE_IMPORT_BY_NAME(輸入名字)項(xiàng)。
現(xiàn)在,有趣的是兩個(gè)數(shù)組并行運(yùn)行,也就是說:它們指向同一組IMAGE_IMPORT_BY_NAME(輸入名字)。
沒有必要失望,我將再畫一圖。這里是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文件名字表的索引(參見上面的輸出目錄)。那個(gè)索引中的名字將被一一嘗試,如果沒有相符的,再使用二進(jìn)制搜索來尋找名字。
(有些鏈接器不愿意查找正確的提示,總是只簡(jiǎn)單的將其指定為1,或者其它的隨意數(shù)字。這并無大害,只是使解決名字的第一次嘗試總是失敗,并迫使每個(gè)名字都使用二進(jìn)制搜索來進(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ù)組了。通過查看根據(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ù)組;再通過查詢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ù)組保持不變,因此你總能通過“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,參見注釋)被置1時(shí),表示列表中沒有符號(hào)的名字信息,符號(hào)只以序數(shù)輸入。你可通過查看IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))中的低地址word來得到序數(shù)。
通過序數(shù)輸入是不鼓勵(lì)的,通過名字輸入會(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í)。
確定有沒有重定位表對(duì)加載器來說不是問題,但該怎樣找出版本的不同呢?這時(shí)IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu))的“時(shí)間戳”就派上用場(chǎng)了。如果它是0,表明輸入列表沒有被綁定,加載器總是要修復(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ù)說就是被中轉(zhuǎn)的(參見上面的輸出目錄描述)。
現(xiàn)在,很明顯的,你不能通過查看那個(gè)實(shí)際上并不包含入口點(diǎn)信息的DLL文件的時(shí)間戳來確定一個(gè)符號(hào)的入口點(diǎn)是否有效。因此,出于安全的原因,中轉(zhuǎn)符號(hào)的入口點(diǎn)必須總是被修正。在二進(jìn)制文件的輸入列表中,中轉(zhuǎn)符號(hào)的輸入必須被找出,以便加載器能補(bǔ)正它們。
這一點(diǎn)可通過“ForwarderChain”(中轉(zhuǎn)鏈)來做到。它是一個(gè)指向換長(zhǎng)列表中的索引值;被索引位置的輸入就是一個(gè)中轉(zhuǎn)輸出,并且此位置的“FirstThunk”( 第一個(gè)換長(zhǎng))列表中的內(nèi)容就是“下一個(gè)”中轉(zhuǎn)輸入的索引值,以此類推,直到索引值為-1,就表明已沒有其他的中轉(zhuǎn)了。如果根本就沒有中轉(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ù)組的開頭了,此類數(shù)組的最后一個(gè)將以全0字節(jié)填充。
要讀懂一個(gè)IMAGE_IMPORT_DESCRIPTOR(輸入描述結(jié)構(gòu)),你得先查看它的“名字”項(xiàng),根據(jù)它的RVA,你就能找到輸出DLL文件的名字。下一步你得確定輸入是否是綁定的;如果輸入是綁定的,“時(shí)間戳”就會(huì)是非“0”的。如果它們是綁定的,現(xiàn)在就是你通過比較“時(shí)間戳”來檢查DLL文件的版本是否相符的好機(jī)會(huì)了。
現(xiàn)在你根據(jù)“OriginalFirstThunk”( 原始第一個(gè)換長(zhǎng))的RVA來到了IMAGE_THUNK_DATA(換長(zhǎng)數(shù)據(jù))數(shù)組;過完這些數(shù)組(它是0結(jié)尾的),它的每個(gè)成員都將是一個(gè)IMAGE_IMPORT_BY_NAME(輸入名字)的RVA(除非它的高位被置1,此時(shí)你找不到名字只有序數(shù))。根據(jù)那個(gè)RVA,并跳過2字節(jié)(即‘提示’),現(xiàn)在你就得到一個(gè)以0結(jié)尾的字符串,這就是輸入函數(shù)的名字。
在綁定輸入時(shí)要找到提供的入口點(diǎn),先根據(jù)“FirstThunk”( 第一個(gè)換長(zhǎng))平行的來到“OriginalFirstThunk”( 原始第一個(gè)換長(zhǎng))數(shù)組;數(shù)組成員就是入口點(diǎn)的線性地址(暫時(shí)不考慮中轉(zhuǎn)的話題)。
還有一件我到現(xiàn)在都沒有提及的事情:明顯地有些鏈接器在構(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ù)組來取得輸入符號(hào)名字,你將永遠(yuǎn)得不到預(yù)先補(bǔ)正的入口地址。我已發(fā)現(xiàn)一個(gè)TIS文件(參考書目[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ì)講的就是所謂的“新式”綁定(在參考書目[3]中講述),它也可以由“綁定”工具來處理。當(dāng)使用這種方式時(shí),“時(shí)間日期戳”的所有位被置為1,并且沒有中轉(zhuǎn)鏈;此時(shí)所有輸入符號(hào)的地址都將被補(bǔ)正,而不管它們是不是中轉(zhuǎn)的。盡管如此,你還是需要知道DLL的版本,并且你還是需要將序數(shù)符號(hào)從中轉(zhuǎn)符號(hào)中區(qū)分開來。為了達(dá)到這個(gè)目的,IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT(綁定輸入目錄項(xiàng))目錄被創(chuàng)建了。就我所見,它將不被放在節(jié)中,而是被放在頭中,處于節(jié)頭之后第一節(jié)之前。(咳,這不是我的發(fā)明,我只是講述它而已!)
這個(gè)目錄告訴你,每一個(gè)已使用的DLL文件的中轉(zhuǎn)輸出是從哪些別的DLL文件中來的。
結(jié)構(gòu)是IMAGE_BOUND_IMPORT_DESCRIPTOR(綁定輸入描述結(jié)構(gòu))形式的,包括(按這個(gè)順序):
一個(gè)32位數(shù)字,“時(shí)間戳”。
一個(gè)16位數(shù)字,“OffsetModuleName(模塊名字偏移量)”,是從目錄開頭到以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)所來自的DLL文件的名稱和版本。這些結(jié)構(gòu)就是“IMAGE_BOUND_FORWARDER_REF(綁定中轉(zhuǎn)參考)”結(jié)構(gòu)的:
一個(gè)32位的數(shù)字“時(shí)間日期戳”(TimeDateStamp);
一個(gè)16位的數(shù)字“模塊名稱偏移量”(OffsetModuleName),它就是從目錄開頭到中轉(zhuǎn)來自的那個(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ì)由此(描述)造成的不便表示歉意,但這就是它看起來的樣子:-)
現(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ò)誤全部修改過來。
?
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)反過來又可能指向一個(gè)“資源目錄”。按照這種方式,你就得到一個(gè)以“資源目錄項(xiàng)”為樹葉的“資源目錄”樹;它們的樹葉指向?qū)嶋H的資源數(shù)據(jù)。
在實(shí)際使用中,情況會(huì)稍微簡(jiǎn)單些。一般你不會(huì)遇到不可能理清的特別復(fù)雜的樹的。
通常,它的層次結(jié)構(gòu)是這樣的:一個(gè)目錄作為根。它指向很多目錄,每種資源類型都有一個(gè)。這些目錄又指向子目錄,每個(gè)子目錄都有一個(gè)名字或者ID號(hào)并指向這個(gè)資源所提供的各種語言的目錄;每種語言你都能找到一個(gè)資源項(xiàng),資源項(xiàng)最終指向(具體的)數(shù)據(jù)。(注意:多語言資源不能在Win95上運(yùn)行。即使程序有好幾種語言,Win95也總是使用相同的資源----我沒有查出是哪一種,但我猜測(cè)肯定是它最先碰到的那種。多語言資源在NT系統(tǒng)上可以運(yùn)行。)
沒有指針的樹大致象這樣:
?????????????????????????? ( 根 )
????????????????????????????? |
???????????? +----------------+------------------+
???????????? |??????????????? |????????????????? |
??????????? 菜單?????????? 對(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)”格式,都以名字開頭。它們可能指向下一個(gè)“資源目錄”或者指向?qū)嶋H的資源數(shù)據(jù)。
一個(gè)“資源目錄項(xiàng)”由下面組成:
32位單元提供你它所描述的資源的ID或者是目錄;
32位的到數(shù)據(jù)的偏移量或者是到下一個(gè)子目錄的偏移量。
ID的含義取決于樹中的層次;ID可能是一個(gè)數(shù)字(如果最高位為0)也可能是一個(gè)名字(如果最高位為1)。如果是一個(gè)名字,它的低31位就是從資源節(jié)原始數(shù)據(jù)的開始到這個(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ù)字都是用戶自定義的。任何有類型名的資源類型也是用戶自定義的。
如果你處于(樹的)下一層當(dāng)中,此時(shí)ID一定是一個(gè)數(shù)字,且就是資源的一個(gè)特例的語言ID號(hào);例如,你可以(同時(shí))擁有澳大利亞英語、加拿大法語和瑞士德語等本地化形式的對(duì)話框,并且它們分享同一個(gè)資源ID。系統(tǒng)會(huì)根據(jù)線程的地點(diǎn)來選擇要使用的對(duì)話框,反過來地點(diǎn)又反映了用戶的“區(qū)域設(shè)置”。(如果資源找不到線程地點(diǎn),系統(tǒng)將先使用一個(gè)中性的子語言資源作為地點(diǎn),比如它將尋找標(biāo)準(zhǔn)法語而不是用戶所擁有的加拿大法語;如果它還是找不到,就使用最小語言ID號(hào)的那個(gè)實(shí)例。必須注意,所有這些只工作于NT系統(tǒng)之上的。)
為便于辨認(rèn)語言ID,使用宏P(guān)RIMARYLANGID()(意為“主語言ID”)和SUBLANGID()(意為“子語言ID”)將它分開為主語言ID和子語言ID,分別使用它的0-9位和10-15位。這些值定義在“winresrc.h”文件中。
語言資源只支持快捷鍵、對(duì)話框、菜單、資源數(shù)據(jù)或字符串等;其它資源類型必須為L(zhǎng)ANG_NEUTRAL/SUBLANG_NEUTRAL(中性語言/中性子語言)。
要確定資源目錄的下一層是不是另一個(gè)目錄,你可查看它的偏移量的最高位。如果它是1,剩下的31位就是從資源節(jié)原始數(shù)據(jù)的開始到下一層目錄的偏移量,還是按“資源目錄”后接“資源目錄項(xiàng)”的格式。如果高位為0,它就是從資源節(jié)原始數(shù)據(jù)的開始到資源的原始數(shù)據(jù)描述,即一個(gè)資源數(shù)據(jù)項(xiàng)的偏移量。資源的原始數(shù)據(jù)描述包含32位的“OffsetToData”(到數(shù)據(jù)的偏移量)(指的是到原始數(shù)據(jù)的偏移量,從資源節(jié)原始數(shù)據(jù)的開頭算起),32位的數(shù)據(jù)的“Size”(大小),32位的“CodePage”(代碼頁)和一個(gè)未使用的32位單元。
(不鼓勵(lì)使用代碼頁,你應(yīng)該使用“語言”的特性來支持多地域。)
原始數(shù)據(jù)格式依賴于資源類型;詳細(xì)的介紹可在微軟的SDK文檔中找到。注意:除了用戶自定義資源,資源中的任何字符串總是按UNICODE格式,明顯的,用戶自定義的資源按的是開發(fā)者選定的格式。
?
9.重定位(relocations)
-----------------------
我將要描述的最后一個(gè)數(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í),重定位數(shù)據(jù)對(duì)加載器來說就是必須的。此時(shí),鏈接器所提供的固定地址就不再有效,并且加載器將不得不對(duì)靜態(tài)變量、字符串文字等使用的絕對(duì)地址進(jìn)行修正。
所謂重定位目錄就是一些連續(xù)的塊,每一塊都包含4K映象文件的重定位信息。塊由一個(gè)“IMAGE_BASE_RELOCATION(基址重定位)”結(jié)構(gòu)體開始,這個(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)用于上面所說的全部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位, 開始的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é)長(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ù)值),此值你可通過上面講述的方法找到。
?
九、致謝(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
? 第一次公開發(fā)表
1998-07-29
? 將映象文件版本和子系統(tǒng)版本中錯(cuò)誤的“byte”改為“word”
? 更正“棧只限于1 MB”的錯(cuò)誤(實(shí)際上沒有上限)
? 更正一些輸入錯(cuò)誤
1999-03-15
? 更正輸出目錄的描述,原來非常不全
? 調(diào)整輸入目錄的描述,原來講的不清
? 更正輸入錯(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 問與答》
"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):
-------------------------------
在這個(gè)附錄中我將給大家展示一下怎樣手工建立一個(gè)程序。因?yàn)槲也粫?huì)DEC Alpha的,本例將使用Intel匯編語言。
本程序相當(dāng)于
??? #include <stdio.h>
??? int main(void)
??? {
??????? puts(hello,world);
??????? return 0;
??? }
首先,我使用Win32函數(shù)來翻譯它以取代C運(yùn)行時(shí)庫:
??? #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)在起讓我們扮演起鏈接器的角色,這會(huì)非常有趣 :-)
我需要先找出函數(shù)WriteConsoleA()和GetStdHandle()。碰巧它們都在“kernel32.dll”中。(這是“輸入庫”部分。)
現(xiàn)在我開始做可執(zhí)行文件。問號(hào)代表待定的值;它們將在以后被修正。
首先是DOS-根,開始于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
正如你所見到的,這不是真正的MS-DOS程序。它只是一個(gè)開始部分有“MZ”簽名的頭和緊跟在頭后面的e_lfanew指針,沒有任何代碼。這是因?yàn)樗⒎谴蛩氵\(yùn)行于MS-DOS之上;它之所以在這里只是因?yàn)橐?guī)范的需要。
然后是PE簽名,開始于0x40,有0x4字節(jié)長(zhǎng):
??????? 50 45 00 00
現(xiàn)在到了文件頭,開始于0x44,有0x14字節(jié)長(zhǎng):
??? 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é)長(zhǎng):
??? 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é)對(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 ; 開始時(shí)4 KB
??? SizeOfHeapReserve?????????? 00 00 10 00 ; 1 MB堆
??? SizeOfHeapCommit??????????? 00 10 00 00 ; 開始時(shí)4 KB
??? LoaderFlags???????????????? 00 00 00 00 ; 未知
??? NumberOfRvaAndSizes???????? 10 00 00 00 ; 常量
正如你所見,我計(jì)劃只用2個(gè)節(jié),一個(gè)用于代碼,一個(gè)用于所有剩余的東西(數(shù)據(jù)、常量和輸入目錄等)。沒有重定位和象資源之類其它東西。我也不用BSS節(jié)并將變量“written”放入已初始化數(shù)據(jù)。文件和RAM中的節(jié)對(duì)齊都是一樣的(32字節(jié));這將有助于使任務(wù)簡(jiǎn)單,否則我就得來回地計(jì)算RVA很多次。
現(xiàn)在我們?cè)O(shè)置數(shù)據(jù)目錄,開始于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é)將包含前面所編的匯編語句。它有32字節(jié)長(zhǎng),所以代碼節(jié)也就是這么長(zhǎng)。節(jié)頭從0x138處開始,有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é)頭開始于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???????????? ; 已初始化的,可讀,可寫
下一個(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é),“到”了。它開始于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處開始:
??? 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"庫中輸入2個(gè)函數(shù),輸入目錄將從本節(jié)的變量后面立即開始。首先我們先將上面的數(shù)據(jù)按32字節(jié)對(duì)齊:
??? 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????????????? ?? ?? ?? ?????? ; 待定
我們需要用一個(gè)0字節(jié)項(xiàng)來結(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é)尾,開始于0x208處:
??? 6b 65 72 6e 65 6c 33 32 2e 64 6c 6c 00? ; "kernel32.dll"的ASCII碼值
??? 00 00 00??????????????????????????????? ; 填充到32位邊界
原始第一個(gè)換長(zhǎng),開始于0x218處:
??? AddressOfData?? ?? ?? ?? ?????????????? ; "WriteConsoleA"函數(shù)名的RVA
??? AddressOfData?? ?? ?? ?? ?????????????? ; "GetStdHandle"函數(shù)名的RVA
??????????????????? 00 00 00 00???????????? ; 結(jié)束符號(hào)
第一個(gè)換長(zhǎng)就是同樣的列表,開始于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é)果來:
------------
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é)對(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 ; 開始時(shí)4 KB
??? SizeOfHeapReserve?????????? 00 00 10 00 ; 1 MB 堆
??? SizeOfHeapCommit??????????? 00 10 00 00 ; 開始時(shí)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對(duì)齊
??? 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???? ; 原始第一個(gè)換長(zhǎng)的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???? ; 第一個(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名字, 開始于0x208:
??? 6b 65 72 6e 65 6c 33 32 2e 64 6c 6c 00? ; "kernel32.dll"
??? 00 00 00??????????????????????????????? ; 填充到32位邊界
原始第一個(gè)換長(zhǎng), 開始于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),開始于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
第一個(gè)未使用字節(jié)開始于: 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é)束 --?????????
[譯后記]:
由于時(shí)間等因素,遺漏、重復(fù)、不準(zhǔn)確甚至錯(cuò)誤等情況在所難免,敬請(qǐng)各位批評(píng)、指正!另外,由于我保留了所有的英文術(shù)語(譯文就在后面),所以譯文看起來有點(diǎn)亂,請(qǐng)大家見諒!
本文的原文寫于1999年,由于時(shí)間關(guān)系,文中所說的有關(guān)公司、某某項(xiàng)目應(yīng)用的操作系統(tǒng)范圍等等介紹可能已經(jīng)不對(duì)或不準(zhǔn)確了,請(qǐng)大家自己分析、鑒別。
最后再談點(diǎn)個(gè)人感想:
1)個(gè)人覺得本文的難點(diǎn)在于輸入符號(hào)(表)部分,而其精華乃在附錄之中。在學(xué)習(xí)前面的各種項(xiàng)目成員名稱、說明等的同時(shí),如能對(duì)照后面的附錄來學(xué)習(xí),將會(huì)起到事半功倍的效果。另外,文中所說的什么結(jié)構(gòu)體、共用體之類術(shù)語都是針對(duì)編程而言,如果你并不想或不會(huì)編程的話,可以將其理解為一個(gè)將其它東西集合在一起的一個(gè)容器就行了。
2)原文發(fā)表于1998-1999年之間,而相應(yīng)的中文譯文至今也難在網(wǎng)上搜尋得到,這對(duì)中國(guó)的破界來說不能說不是一個(gè)很大的遺憾!本文僅起拋磚引玉之用,希望能有更多、更好、更及時(shí)的國(guó)外類似資料出現(xiàn)在我們的網(wǎng)絡(luò)之上,以造福于我們這些廣大的菜鳥。
????????????????????????????????????????????????????????????????????? 沈忠平??? 2006.02 于和州
===========================
|“PE文件格式”1.9版注釋:|
===========================
①Win32s和Win32
Win32s是“WIN32 subset”的縮寫,它是一個(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編寫時(shí),它們就具有16位API(Win16)所不具備的一些高級(jí)性能。一個(gè)按Win32編寫的程序能運(yùn)行在所有的操作系統(tǒng)之上,除非這個(gè)程序要求特定的操作系統(tǒng)特性,而這些特性別的操作系統(tǒng)又沒有時(shí)。例如,Windows NT提供的安全特性Windows 95/98就沒有。一個(gè)為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。一個(gè)映象文件可被想象為“內(nèi)存映象”。“映象文件”一詞常被用來代替“可執(zhí)行文件”,因?yàn)楹笳哂袝r(shí)被用來專指.EXE文件。
③UNIX
是一個(gè)很流行的多用戶、多任務(wù)的操作系統(tǒng),由貝爾實(shí)驗(yàn)室于上世紀(jì)70年代早期開發(fā)出來的。只有很少的程序員建立的UNIX系統(tǒng)本來是設(shè)計(jì)給他們這些程序員專用的、小巧的、靈活的系統(tǒng)。UNIX是用高級(jí)編程語言,就是C語言,編寫的第一批操作系統(tǒng)之一。這就意味著只要電腦上有C語言編譯器,UNIX就可以被虛擬地安裝到任何電腦上。天生的可移植性加上低廉的價(jià)格使得UNIX成為各大學(xué)的流行選擇。(因?yàn)榉葱庞脳l款禁止貝爾實(shí)驗(yàn)室將UNIX作為它的全權(quán)產(chǎn)品推向市場(chǎng),所以UNIX的價(jià)格不貴。)
貝爾實(shí)驗(yàn)室只發(fā)布它自己源語言形式的UNIX操作系統(tǒng),所以任何獲得一份拷貝的人都可以按照自己的意愿來修改和定制它。到上世紀(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”(軟件開發(fā)工具箱)的縮寫,它是一個(gè)供程序員為特定平臺(tái)開發(fā)應(yīng)用程序的編程包。典型的,一個(gè)SDK包含一個(gè)或多個(gè)API庫、各種編程工具和相關(guān)文檔等。
⑥Ne Format(New-style EXE Format的縮寫)
是一個(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 共同合作開發(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 是一個(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 以文檔為中心,允許用戶訪問文件和打印機(jī),并可以啟動(dòng)程序。WPS 遵循 IBM 的用戶界面標(biāo)準(zhǔn),即“通用用戶訪問”。
OS/2 操作系統(tǒng)中包含一種系統(tǒng)對(duì)象模型(SOM),包括磁盤、文件夾、文件、程序?qū)ο蠹按蛴C(jī)等對(duì)象。SOM 允許應(yīng)用程序間代碼共享,但這與編程語言無關(guān)。一種稱為 DSOM 的分布式版本支持不同計(jì)算機(jī)上對(duì)象間的相互通信。DSOM 建立在 CORBA 基礎(chǔ)上。SOM 類似于微軟的組件對(duì)象模型(Component Object Model),同時(shí)兩者相互競(jìng)爭(zhēng)。目前人們對(duì) SOM 和 DSOM 已停止深度開發(fā)。
OS/2 操作系統(tǒng)也包括一種叫做 OpenDoc 的混合文檔技術(shù),它由 Apple 開發(fā)而成。但目前人們對(duì) OpenDoc 也已停止深度開發(fā)。
由于 OS/2 存在市場(chǎng)局限性,IBM 公司已于2003年3月12日按照電子商務(wù)計(jì)劃停止了 OS/2 的發(fā)展市場(chǎng)。
?
⑧MIPS
MIPS是世界上很流行的一種RISC處理器。MIPS的意思是“無內(nèi)部互鎖流水級(jí)的微處理器”(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ù)公司是美國(guó)著名的芯片設(shè)計(jì)公司,它采用精簡(jiǎn)指令系統(tǒng)計(jì)算結(jié)構(gòu)(RISC)來設(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ù),開發(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í)的順序的術(shù)語。Big-endian(即“大端結(jié)束”或者“大尾”)是將高位字節(jié)(序列中最重要的值)先存放在低地址處的順序,而Little-endian(即“小端結(jié)束”或者“小尾”)是將低位字節(jié)(序列中最不重要的值)先存放在低地址處的順序。舉例來說,在使用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)敲開還是從小頭(Little-Endian)敲開,由此曾發(fā)生過六次叛亂,其中一個(gè)皇帝送了命,另一個(gè)丟了王位。
我們一般將endian翻譯成“字節(jié)序”,將big endian和little endian稱作“大尾”和“小尾”。
⑩Alpha AXP
“DEC Alpha”,也被稱作“Alpha AXP”,是一個(gè)原來由美國(guó)數(shù)據(jù)設(shè)備公司(DEC)開發(fā)和制造的64位RISC微處理器(例如:DEC Alpha AXP 21064 微處理器),他們將它用在自己的工作站和服務(wù)器系列上。被設(shè)計(jì)作為VAX系列計(jì)算機(jī)的繼承者,Alpha AXP不但支持VMS操作系統(tǒng),同時(shí)也支持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)通用時(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)通過。跟格林威治標(biāo)準(zhǔn)時(shí)間一樣,UTC也被設(shè)定于0經(jīng)度的本初子午線。
BSS
是“Block Started by Symbol”的縮寫,意為“以符號(hào)開始的塊”。BSS是Unix鏈接器產(chǎn)生的未初始化數(shù)據(jù)段。其他的段分別是包含程序代碼的“text”段和包含已初始化數(shù)據(jù)的“data”段。BSS段的變量只有名稱和大小卻沒有值。此名后來被許多文件格式使用,包括PE。
“以符號(hào)開始的塊”指的是編譯器處理未初始化數(shù)據(jù)的地方。BSS節(jié)不包含任何數(shù)據(jù),只是簡(jiǎn)單的維護(hù)開始和結(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,在后來的Windows版本比如Microsoft Windows 95, Windows 98, Windows NT,和Windows 2000上仍能出現(xiàn)。它被開玩笑地稱為藍(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)接口)的首字母縮寫,它是定義程序和操作系統(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)委員會(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ù)民間傳說,thunk一詞是由一位Algol-60編程語言的開發(fā)者編出的,他在一天深夜意識(shí)到參數(shù)的數(shù)據(jù)類型是可以被編譯器稍先一點(diǎn)知道的。也就是說,到了編譯器處理參數(shù)的時(shí)候,它就已經(jīng)想到了(thunked)數(shù)據(jù)類型了。該詞的含義近年來已變化很大了。)
(名詞)換長(zhǎng),變長(zhǎng)(在一個(gè)分段內(nèi)存地址空間和一個(gè)統(tǒng)一地址空間之間互相轉(zhuǎn)換的操作)
(我查遍書店中所有的大大小小的英漢和英英詞典,都沒有找到thunk這個(gè)詞的含義。后在網(wǎng)上找到了它的英語解釋,卻找不到它對(duì)應(yīng)的漢語譯法,現(xiàn)根據(jù)它的意思,姑且譯之。各位勿笑,還請(qǐng)高手指點(diǎn)。)
(英文參見:http://www.webopedia.com/TERM/T/thunk.html)
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1475359