反匯編基本原理與x86指令構造 概要:旨在講述程序的二進制代碼轉換到匯編。即反匯編的基本原理。以及 x86 架構的 CPU 的指令構造,有這個基礎后就能夠自己編寫匯編程序了,也能夠將二進制代碼數據轉換成匯編助記指令。當然,把本文當作手冊的閱讀指導也是能夠的。本文還講述了 DEBUG 工具的部分功能。32位平台下有一個 DEBUG32 版本號能夠配合 DOSBOX 工具執行在 Windos 7 這些 NT 系統上,DEBUG 要使用 MSDOS 5.0 版本號中的。這是一個十分實用的工具,它同一時候是 x86 的匯編程序。又是反匯編程序,同一時候又能夠作為交互命令使用,如讀寫磁盤扇區等系統功能。本文須要匯編與計算機原理等基礎知識。假設閱讀時發現無法理解的內容就表明須要補充基礎知識,此時請停下來,或者跳過部分內容也是不錯的閱讀方法。再次。祝賀你,當你看到這篇文章時。你已經開始學會怎樣把握計算機軟件領域的核心所在了! “無論如今流行什么語言,你都能夠肯定十年二十年之后它不再風光。我總是在自己的書中寫些不時髦的東西,但這些東西卻值得后代子孫記取。” -- Donald E. Knuth 問題由反匯編開始 對已經開始接觸反匯編深層的讀者,能夠已經使用過甚至自己編寫過反匯編引擎了,如 x86 Disassembler Librarys 。
所謂反匯編即通過 CPU 的指令構造原理將指令的二進制代碼轉換成助記符 Mnemonic 的過程。而二進制表達的指令就稱為操作碼 OpCode, 這是 CPU 能夠理解的指令形式。
那么先來看看一條簡單的代碼片斷,操作碼為數據:eb 00 eb fe 90。
使用 DEBUG 工具,通過 e cs:ip 命令在當前代碼段的入口來輸入下面指令代碼,輸入完一個字節按空格,完畢時按回車結束。然后通過 u cs:ip 得到類似下面反匯編代碼: 1C8D:0100 eb00 jmp short 0102 1C8D:0102 ebfe jmp short 0102 1C8D:0103 90 NOP 通過 DEBUG 的匯編命令也能夠直接輸入以上的匯編程序,如今通過 t 命令來執行單步調試,看看程序怎樣執行。在 DEBUG 會高亮顯示補指令修改過的內容,通過 r 命令來顯示當前寄存器的值。當中就有 IP 寄存器,調試時第一條指令執行后。顯示 IP 改變為 0102。這是由於 jmp 跳轉指令起作用了。第二條指令也是一條 jmp 指令,可留意到其操作碼極為相似。能夠聯想,操作碼的第二個字節其實就是跳轉的地址,即指令的操作數 Operand。
由於第一條指令操作碼為 2-Byte,跳轉的偏移量為 0x00+2 即跳轉到 0x0100+2=0x0102;因此第二條指令跳轉的偏移地址應為0xFE+2=0x100,可是由於此指令的操作數為一個字節。因此截留結果的低 8-bit 即終於偏移量為 0,正因此,程序在繼續執行時 IP 始終停留在 0x0102。 這里就出現了一個疑問,eb00 這種操作碼是怎樣得到的呢?這個問題關系到,DEBUG 怎樣反匯編 eb00 得到 jmp 這種匯編指令。也關系到 DEBUG 怎樣將匯編指令轉譯為 CPU 認識的操作碼。 指令操作碼構造 先來看一段代碼: 1C8D:0100 B80000 mov ax, 0 1C8D:0103 B80100 mov ax, 1 1C8D:0106 BB0000 mov bx, 0 1C8D:0109 BB0100 mov bx, 1 以上程序能夠通過判斷了解到。操作碼 B8 可能代表了 mov ax,BB 代表了 mov bx,寄存器的信息已經包括在操作碼。CPU 能夠將其與特定的寄存器關聯。0、1 這些操作數也包括在指令操作碼內。
有一個重要的環節,x86 CPU 最初是從 16 位機發展起來的。這種機器的執行時內存通過地址總線直接存取,這種模式就稱作實地址模式 Real Mode,與之后來研發的 32 位機 80386 的引入內存分頁機制及保護機制。內存能夠通過分頁的形式來管理並在保護機制下執行,這種模式稱為保護模式 Protect Mode,同一時候為執行 16 位的程序提供了一個虛擬 x86 模式 Virtual 8086 Mode 簡稱 V86,這種模式通過虛擬機技術實現,能有效地利用 80386 的多任務特性來執行多個實模式程序。
在這之前是沒有 eax 這種 32 位的寄存器,可是由向下兼容的要求,以上指令的操作碼在 16 位機上具有同樣的作用。雖然如此,由於后來的 CPU 指令集在增長,在不同的模式下,同樣的操作碼會出現的不同的功能。比如: 1C8D:0100 66B800000000 mov eax, 00 在 16 位機上,將會解釋為: 1C8D:0100 66 DB 66 1C8D:0101 B80000 mov ax, 0 1C8D:0104 0000 mov [bx+si], al 可見,CPU 的執行模式對操作的解碼非常重要。為了深入操作碼的內部。那么僅僅有找 CPU 廠商了,x86 當然就是找 Intel 了,它共享公布有開發者手冊共為3卷7個PDF。到 Intel® 64 架構公布后,其相應的手冊已經整理成了全集組成一個 PDF 文檔。
這套手冊能夠說是低層學習的必需文檔,它詳盡地記錄了開發者須要了解的 CPU 信息,第二卷全三冊指令系統作為開發者最主要的要求,自然也就是重點。
前面說了這些就是為了熱身,在 x86 架構上,一個指令即操作碼由6個部分構造而成: Prefixes 指令代碼的前綴,每指令最多能夠有4個/種前綴修飾。
有操作數大小前綴。如前面提到的 66。它指定 32-bit 操作數大小,FE 前綴則表示 8-bit 操作數。按 16 位機上的傳統,默認的操作為 16 位,不使用前綴。有反復類型前綴,如最常見的 F3 表示 REP、REPE、REPZ 反復前綴。還有 F2 表示 REPNE、REPNZ 前綴。有段超越前綴,2E 相應 CS、36 相應 SS、3E 相應 DS、26 相應 ES。64、65 則相應 FS、GS,段超越是可內存尋址有關的內容。還有尋址地址大小前綴,67 表示 32-bit 內存尋址。以及官方手冊中提及的一些特別功能的前綴。這些前綴能夠按隨意的順序與指令碼組合; Code 指令本體,且稱指令碼,是唯一必需的部分,如前面 B80000 中的 B8 就是一個 Code。然而,指令碼還可能有一個額外的 3-bit 存放在 ModR/M 處; ModR/M,雖然這部分可能包括指令碼的一額外字節,但它主要功能是用來標識操作數的內存尋址信息。就此稱作尋址模式。顯得恰當。它被細分為三個區域,高 2-bit 為 mod 區、低 3-bit 為 R/M 區。當中 Mod 和 R/M 共 5-bit 有32 種可能值,剛好用來表示 8 個通用寄存器和 24 種尋址模式。
R/M 還能夠用來指定寄存器作為操作數,它是 Register 和 Memory 的縮寫。中間 3-bit 為 Reg/Opcode 區。中間斜杠表示或的意思。它表明此區域可表示寄存器操作數或作為指令碼的額外的 3-bit 來使用。具體用途要應指令來決定。
對於特定的比例變址尋址模式。才須要用到操作碼的 SIB 部分。具體內容見下面的 16-bit 或 32-bit 尋址模式圖表。 SIB,它是 Scale Index Base 的縮寫,是尋址模式的內容補充,是對照例變址尋址方式的具體說明。它也被細分為三個區域,高 2-bit 作為比例因子 Scale 使用,在《深入x86的內存尋址》也提到這種尋址方式的比例因此僅僅能是 1、2、4、8 就和 Scale 所占的 2-bit 有關。它僅僅能表示這四種可能值 。
中間 3-bit 為 Index。用來指定一個變址寄存器。正如《深入x86的內存尋址》所描寫敘述。除 ESP 外的七個通用寄存器都能夠用作比例變址地址,在下面的 SIB 尋址圖表中能夠看到第一欄的 none 代替了 ESP。低 3-bit 為 Base,用來指定基址寄存器。 Displacement 偏移量。可選,可為 1、2、4 字節。在《深入x86的內存尋址》中提到。 Immediate 馬上數。可選。可為 1、2、4 字節。樣例 B80000 指令中的 0000 就是馬上數,即包括在操作碼的數據。
總之,一條指令最長也不會超過 16 個字節。
深入指令構造 有了上面的基礎后,就能夠理解下面圖表了。這些圖表能夠理解為三維圖表,分別使用 ModR/M 或者 SIB 的三個區域來查找用於構造指令操作碼的二進制數值。16-bit 或 32-bit 尋址模式圖表的差別就是前者的是 16-bit 的地址,后者則為 32-bit 地址。要進行反匯編前,須要先了解手冊中的指令集內容怎樣閱讀。以 add 指令為例,手冊給出定義表格: Opcode Instruction Clocks Description 04 ib ADD AL,imm8 2 Add immediate byte to AL 05 iw ADD AX,imm16 2 Add immediate word to AX 05 id ADD EAX,imm32 2 Add immediate dword to EAX 80 /0 ib ADD r/m8,imm8 2/7 Add immediate byte to r/m byte 81 /0 iw ADD r/m16,imm16 2/7 Add immediate word to r/m word 81 /0 id ADD r/m32,imm32 2/7 Add immediate dword to r/m dword 00 /r ADD r/m8,r8 2/7 Add byte register to r/m byte ...... 第一列指明了指令碼的取值。能夠看到同一條指令它具有不同的表達形式。
同一時候,對於不同指令還可含有其他特定信息: /digit 這里的 digit 指 0-7 的數值,在尋址模式表中有相應的值。表示指令操作數僅僅使用 R/M 部分。這里的數值同一時候也作為指令碼的額外 3-bit 來使用。 /r 表明指令的尋址模式中指定了寄存器間接尋址操作數和 R/M 操作數。 cb cw cd cp 分別表示代碼偏移值即指令長度為 1-Byte、2-Byte、4-Byte、6Byte,也可能是新的段寄存值。
ib iw id 分別表示馬上數為 1-Byte、2-Byte、4-Byte,指令碼能夠確認它的符號。 +rb +rw +rd 分別表示寄存器代碼,+號表示將寄存器代碼與前面的 16 進制指令碼相相加形成一個字節的指令碼。
寄存器代碼例如以下所看到的: rb rw rd AL = 0 AX = 0 EAX = 0 CL = 1 CX = 1 ECX = 1 DL = 2 DX = 2 EDX = 2 BL = 3 BX = 3 EBX = 3 AH = 4 SP = 4 ESP = 4 CH = 5 BP = 5 EBP = 5 DH = 6 SI = 6 ESI = 6 BH = 7 DI = 7 EDI = 7 第二列指明了指令的偽匯編代碼的解釋。這部分和匯編語言有相似的地方,比較easy理解。主要是在操作數的解釋上有些須要羅列的內容: rel8: 表示操作數是一個 8-bit 相對尋址,范圍在同樣的代碼段。當前指令末端的前 128-Byte 到后 127-Byte。rel16 和 rel32 作用類似,僅僅是操作數的大小不同。范圍也加大了。
ptr16:16, ptr16:32: 遠代碼指針,操作數包括了代碼段地址和偏移,用於代碼尋址。CPU 會按指針的代碼段地址來設置 CS 寄存器以實現遠跳轉。 r8: 表示一個 8-bit 通用寄存器 AL, CL, DL, BL, AH, CH, DH, BH。
r16: 表示一個 16-bit 通用寄存器 AX, CX, DX, BX, SP, BP, SI, DI。
r32: 表示一個 32-bit 通用寄存器 EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI。 imm8: 表示一個 8-bit 馬上數。帶符號,取值為 [-128~+127]。imm16 和 imm32 也類似,僅僅是取值范圍增大。 r/m8: 表示操作數能夠為 r8 或者 8-bit 的內存數據。
r/m16 則相應一個 r16 或者 16-bit 的內存數據,r/m32 同理。
m8: 表示8-bit 的內存數據,由 DS:SI 或 ES:DI 尋址得到。專用於字符串指令。m16 和 m32 同理。 m16:16, m16:32: 表示操作數為遠指針尋址。冒號前的值為段基址,后面的值為相應偏移。
m16 & 32, m16 & 16, m32 & 32:a memory operand consisting of data item pairs whose sizes are indicated on the left and the right side of the ampersand. All memory addressing modes are allowed. m16 & 16 and m32 & 32 operands are used by the BOUND instruction to provide an operand containing an upper and lower bounds for array indices. m16 & 32 is used by LIDT and LGDT to provide a word with which to load the limit field, and a doubleword with which to load the base field of the corresponding Global and Interrupt Descriptor Table Registers. moffs8, moffs16, moffs32: 表示一個內存偏移值。對於不含 ModR/M 的指令,將在指令碼中包括內存偏移信息,它同一時候決定了地址方式。 Sreg: 表示一個段寄存器,ES=0, CS=1, SS=2, DS=3, FS=4, GS=5。 第三列、第四列則分別說明了指令所消耗的時鍾周期和指令的功能。文檔中還有其他具體的內容,如 Operation 使用偽代碼描寫敘述了 CPU 內部執行指令的過程,Flags Affected 描寫敘述了標志寄存器受指令影響的標志位,以及指令是否觸發 CPU 異常等等信息。 以 dec 指令為例,dec 主要的指令碼為 48+rw 或 FE /1,后者表明指令附加了 ModR/M 部分並且 Reg/Opcode 區域指定為指令碼的額外的 3-bit,取值為 1。當 dec 結合不同的寄存器時,它的指令碼就會產生微小的變化: 1C8D:0100 48 dec ax 1C8D:0101 FEC8 dec al 1C8D:0103 4B dec bx 1C8D:0104 FECB dec bl 1C8D:0106 49 dec cx 1C8D:0107 FEC9 dec cl 又以匯編代碼為例: 1C8D:0100 67668B00 mov eax,[eax] 1C8D:0104 678B00 mov ax, [eax] 1C8D:0107 678A00 mov al, [eax] 1C8D:010A 67668900 mov [eax], eax 1C8D:010E 678900 mov [eax], ax 1C8D:0111 678800 mov [eax], al 1C8D:0114 66A3 mov eax, eax 先來分解第一條指令,首先。通過 [eax] 這種寄存器間接尋址方式能夠確定 CPU 是 32-bit 尋址的。當然了反匯編是不會事先得知指令的細節的。可是從操作碼中還是能夠找到線索,67 表明它是指令前綴,指定了內存地址為 32-bit 模式;其次 66 是也表明是一個指令前綴,指定操作數為 32-bit。再次,依據手冊定義得到 8B 是 mov 的指令碼。這就能夠確定它的操作數僅僅為寄存器。並且源操作數是內存或寄存器尋址;接下來的 00 就為尋址模式,而不是其他什么馬上數什么的啦。那么就將 00 按 ModR/M 的三個區域展開。得到 Mod=0,Reg/Opcode=0,R/M=0。在相應的 32-bit 尋址模式表中找到 Mod=0的行。這里指令確定了這個區域指定一個寄存器而不是指令碼的 3-bit,因此找到 REG=0 的例。再找到 R/M=0 的行,即紅、綠、藍線框依次圈到的內容,最會找到的是 00 這個值。就是前面分解的操作碼。再次說明,Mod 和 R/M 確定了源操作數是通過 [EAX] 的寄存器間接尋址,然后前綴 66、指令碼 8B 和 Reg/Opcode 確定了目標操作數是 EAX 寄存器。接下來兩條代碼也如此一番分析。即能夠得到反匯編指令,這就是反匯編的原理所在。 第四條指令和第一條指令僅僅在操作數的位置上調換了一下,按前的方法推演,能夠 匯編實踐 有了以上信息,就能夠匯編及反匯編指令了,這些內容是個基礎。比如。匯編最簡單的一條指令: mov eax, [edx] 首先。能夠確定的信息就包括 32-bit 的內存尋址模式,因此代碼前綴有 67。使用 32-bit 寄存尋址。因此代碼前綴有 66。接下來須要依據手冊定義得到到 mov 的指令碼。8B,即下面這條指令形式: Opcode Instruction Clocks Description 8B /r MOV r32,r/m32 2/4 Move r/m dword to dword register 再依據源操作數的[edx]間接寄存器尋址定位到尋址模式表格的數據: +-Effective Address-+ +Mod R/M+ +---------ModR/M Values in Hexadecimal-------+ [EDX] 00 010 02 0A 12 1A 22 2A 32 3A 然后。再依據目標操作數 eax 定位到 REG=000 這列得到 02。
終於得到的操作碼就為 67 66 8B 02。通過 DEBUG32 能夠驗證結果。 將上面的匯編指令的操作數的尋址方式反轉來試試: mov [eax], edx 同樣,前綴 67 66 已經能夠從匯編指令中得出,依據手冊定義的 mov 的相應尋址模式的指令碼則為 89。 再依據目標操作數的[eax]間接寄存器尋址定位到尋址模式表格的數據: +-Effective Address-+ +Mod R/M+ +---------ModR/M Values in Hexadecimal-------+ [EAX] 00 000 00 08 10 18 20 28 30 38 再找到源操作數 edx 相應的 REG=002 這一列,得到 10 這個值,終於操作碼為 67 66 89 10。
注意,這個過程有點古怪。為了更全面理解這個過程。再來嘗試匯編不含內存尋址的指令: mov edx, eax // 668BD0 mov eax, edx // 668BC2 這條指令,比較有爭議。因此手冊中有種形式是符合這些指令表達的: Opcode Instruction Clocks Description 89 /r MOV r/m32,r32 2/2 Move dword register to r/m dword 8B /r MOV r32,r/m32 2/4 Move r/m dword to dword register 那么到底哪條指令適用呢?不是直接來看注解后所指出的指令碼,假設指令系統通過第一條指令的源操作數 eax 定位到所在行: +-Effective Address-+ +Mod R/M+ +---------ModR/M Values in Hexadecimal-------+ EAX/AX/AL 11 000 C0 C8 D0 D8 E0 E8 F0 F8 然后通過目標操作數 edx 定位到了 D0 這個值。對於第二條指令。也如此定位到了 C2 這個值,終於得到相應的指令碼。為了驗證這一規則,通過 DEBUG32 工具反匯編兩條指令碼得到下面內容。發現指令系統並非按這種規則構造指令碼的: mov eax, edx // 6689D0 mov edx, eax // 6689C2 而是通過 r/m32 指定的操作數來定位所在行,通過 r32 定位所在的列,這樣才干夠解釋這里8條指令的構造。 1C8D:0100 6689D0 mov eax, edx 1C8D:0103 6689C2 mov edx, eax 1C8D:0106 668BD0 mov edx, eax 1C8D:0109 668BC2 mov eax, edx 1C8D:010C 67668B02 mov eax, [edx] 1C8D:0110 67668910 mov [eax],edx 1C8D:0114 67668902 mov [edx],eax 1C8D:0118 67668B10 mov edx, [eax] 最后通過一條 div ecx 指令來驗證一下這個過程。div 指令有三種形式,當中 EAX/AX/AL 是隱含的,作為被除數的低位數據。不須要在匯編指令中指出它。 Opcode Instruction Clocks Description F6 /6 DIV AL,r/m8 14/17 Unsigned divide AX by r/m byte(AL=Quo, AH=Rem) F7 /6 DIV AX,r/m16 22/25 Unsigned divide DX:AX by r/m word (AX=Quo, DX=Rem) F7 /6 DIV EAX,r/m32 38/41 Unsigned divide EDX:EAX by r/m dword (EAX=Quo, EDX=Rem) 指令中顯式給的信息就有 ecx 這個 32-bit 的寄存器。因此能夠得到 32-bit 操作數前綴 66 和指令碼 F7,通過 /6 這條信息又能夠優先定位到 Opcode=6 所在的列。再由 r/m32 指定的 ecx 所在的行得到,F1。將各部結構起來就得到了完整的操作碼 66 F7 F1,這個過程就是完整的匯編。
第一次手工反匯編 有了匯編的基礎后。再來反匯編顯得更easy。
比如。在 AutoNeoGrub.mbr 引導程序中提取的部分數據: 00000000 EB 5E 80 00 20 39 FF FF 00 00 00 00 00 00 00 00 .^.. 9.......... 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ~ Duplicate Lines 00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060 FA 31 DB 8E D3 BC 80 05 E8 00 00 5B 81 EB 6B 00 .1.........[..k. 00000070 C1 EB 04 8C C8 01 C3 8E DB 53 6A 7D CB 68 00 20 .........Sj}.h. CPU 在執行程序代碼時。會先讀入 EB,通過定義得到 EB 為一條短跳轉 jmp 指令。通過手冊也能夠查找到這條指令的定義,它的操作數為 cb 占一個字節。
因此能夠知道拉下來的 5E 這個字節是個地址偏移。整條指令占 2 個字節。短跳轉的實際偏移量為 5E+2=60。為了方便查找,Sang Cho 制作了一份非常實用的 32-bit 尋址的 Pentium 指令集數據表供參考,也能夠在 Intel 80386 Reference Programmer's Manual 的 CHM文檔中方便地查找到,鏈接見附錄。
接下來。程序跳轉到 00000060 這里開始執行。同樣讀入一個字節 FA。它是一條 CLI 指令。 接着讀入 31,它是一條 XOR r/m32,r32 或者 XOR r/m16, r16 指令,由於 COM 程序是在 DOS 平台下定義的 16 位實模式程序,最重要的是這條指令沒有操作數大小前綴。因此它特指后者,表示執行在實模式下的指令。可是 x86 架構的 CPU 是向下兼容的,全部實模式指令也能夠在保護模式下執行。能緊接着的 ModR/M 這個字節 DB。二進制值為 11011011。能夠得到各個區域的取值與尋址模式表的映射關系。當中 Mod=11。表示了操作數為寄存器尋址,Reg/Opcode=011 表示 BX 寄存器所在的列,R/M=011 表示 BX 所在的行,因此終於得到匯編指令為 xor bx, bx。 跟着的一個字節 8E 為一條 MOV Sreg,r/m16 指令,由於可知接下來一字節 D3 為 ModR/M 數據,分區域得到 Mod=11,R/M=011,能夠定位到寄存器操作數 bx。Reg/Opcode=010 表示了段寄存器 SS。因此這條占兩個字節的指令為 mov ss, bx。
再跟着的 BC 這里比較麻煩,其實它是一條 mov 指令,可是不能在手冊上直接查找到。
回到前面講過的內容,指令碼能夠像 B8+cw 這種表達。正是由於指令碼中增長了操作數信息,而使用得指令主要的編碼發生了改變。因此手冊上不能直接查找到,僅僅能通過對指令集的整理來實現。由於是 16-bit 數據模式,這里 BC-B8=4 能夠推導出這個隱含的寄存器為 SP,那么跟着指令碼的兩個字節就是 16-bit 的馬上數。因此終於的匯編指令為 mov sp, 580h。
然后一個字節是 E8。即 CALL rel16 指令,由此可知整個指令占 3 個字節,后面的兩個字節為跳轉偏移值。終於構造得到匯編指令 call $+3。
這里的 CALL 指令僅僅是將程序的一條指令的地址入棧,程序還是繼續執行下一條指令。 經過上一條假 CALL 跳轉后,程序執行到 5B 這里。這條指令也不能在手冊中直接搜索到,但能夠變通地搜索 58 這條指令,即 58 + rw POP r16。從 5B-58=3 這時能夠得隱含的寄存器為 BX,即匯編指令為 pop bx。
然后是 81,這個字節十分具有迷惑性。通過搜索手冊,競有 16 條相似的指令: 81 /6 iw XOR r/m16,imm16 81 /7 iw CMP r/m16,imm16 81 /6 id XOR r/m32,imm32 81 /7 id CMP r/m32,imm32 81 /2 iw ADC r/m16,imm16 81 /1 iw OR r/m16,imm16 81 /2 id ADC r/m32,imm32 81 /1 id OR r/m32,imm32 81 /0 iw ADD r/m16,imm16 81 /3 iw SBB r/m16,imm16 81 /0 id ADD r/m32,imm32 81 /3 id SBB r/m32,imm32 81 /4 iw AND r/m16,imm16 81 /5 iw SUB r/m16,imm16 81 /4 id AND r/m32,imm32 81 /5 id SUB r/m32,imm32 那么會是那一條呢?當然 CPU 是肯定知道了。首先,通過 16-bit 操作數就能夠排除掉了一半。有 32 字樣的指令都不在目標內。然而余下的 8 條指令形式除了在指令碼的額外 3-bit 數值上不同外,其他內容形式都是同樣的。對我來說,這更像個奇觀!通過分析接下來的 ModR/M 這個字節 EB,Mod=11,Reg/Opcode=101,R/M=011,這下就清晰了。滿足 Reg/Opcode=101 即 /5 的指令僅僅有一條,那就是... SUB!
並且。目標寄存器操作數為 BX,源操作數 imm16 為后而緊跟的兩個字節 0x006B。終於得到的匯編指令為 sub bx, 6bh。
至於 AutoNeoGrub.mbr 引導程序余下的代碼還有非常多,不一而足。讀者能夠自行應用前面的理論進行手工反匯編工作。到此代碼片斷的匯編形式就能夠按下面 COM 程序的形式來組織: cs:0100 ; +-------------------------------------------------------------------------+ cs:0100 ; File Name : AutoNeoGrubA.com cs:0100 ; Format : MS-DOS COM-file cs:0100 ; Base Address: 1000h cs:0100 ; +-------------------------------------------------------------------------+ cs:0100 cs:0100 .686p cs:0100 .mmx cs:0100 .model tiny cs:0100 cs:0100 ; =========================================================================== cs:0100 cs:0100 seg000 segment byte public 'CODE' use16 cs:0100 assume cs:seg000 cs:0100 org 100h cs:0100 assume es:nothing, ss:nothing, ds:seg000 cs:0100 cs:0100 jmp short start cs:0100 ; --------------------------------------------------------------------------- cs:0102 db 80h, 0, 20h, 39h, 2 dup(0FFh), 58h dup(0) cs:0160 ; --------------------------------------------------------------------------- cs:0160 cs:0160 start: cs:0160 cli cs:0161 xor bx, bx cs:0163 mov ss, bx cs:0165 mov sp, 580h cs:0168 call $+3 cs:016B pop bx cs:016C sub bx, 6Bh 總結 匯編。它是個底層基礎,是高級語言的基本結構。
它們不同點在於,匯編更貼近了 CPU 的硬件執行機理。
而高級語言則更注重在算法、程序便利性和性能上的設計。
如主要的流程語句的設計,它須要怎么走。怎樣能讓開發者更有效率地編寫代碼,怎樣能以更快的速度得到終於的程序。怎樣能讓程序的執行效率更接近匯編的層次,等等都是高語言關注的核心問題。
幾天前。在構思這篇文章時。認為會特別耗時。那里,剛完畢了《深入x86的內在尋址》《深入BIOS與中斷》等文章的寫作。消耗了不少時間與精力。但這篇文章所涉及的內容之重要,又使我不得不緊接着前兩篇的腳步走下去。
寫到這時,匯編與反匯編的知識點也基本都講透了。又認為是不是還要加入一些內容進來!
這幾篇文章所涉及的內容都是按進階來進行的。同一時候又起到相互說明的作用,因此我認為,假設時機成熟,能夠將這系列文章組織好再發表成冊。這樣就滿足我小小的虛榮吧。
參考 IA-32 Intel® Architecture Software Developer's Manual Volume 2: Instruction Set Reference Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html Intel 80386 Reference Programmer's Manual http://download.csdn.net/detail/winsenjiansbomber/7281997 The art of disassembly http://bbs.pediy.com/showthread.php?
t=75094 X86 Opcode and Instruction Reference http://ref.x86asm.net/index.html CALL指令有多少種寫法 (有些 bit 誤寫成字節) http://blog.ftofficer.com/2010/04/n-forms-of-call-instructions/ x86 Disassembler Librarys http://bastard.sourceforge.net/libdisasm.html The Complete Pentium Instruction Set Table (32 Bit Addressing Mode Only) http://bbs.pediy.com/showthread.php?t=54706
以上黑帶為文章的文字內容。下面為相應正文圖片版式。