什麼是彙編語言(一) 彙編底層原理,指令字節碼

作者:zenglong

添加時間:2013/8/30 19:12:30   瀏覽次數:979
學習彙編語言首先要理解什麼是彙編語言,不像其他的編程語言,不同的彙編程序有不同的語法格式,許多剛接觸彙編的程序員就陷入了這種困境,不知道該學哪種好。所以,學習彙編的第一步就是選擇一種適合你的開發環境的彙編語言類型,一旦你確定下來了,學習彙編將不再是一件困惑的事情...
    本文由zengl.com站長對 http://pan.baidu.com/share/link?shareid=3717576860&uk=940392313 彙編教程英文版相應章節進行翻譯得來。
    另外在附加一個因特爾英文手冊的共享鏈接地址:http://pan.baidu.com/share/link?shareid=2345340326&uk=940392313 (在某些例子中會用到)

    學習彙編語言首先要理解什麼是彙編語言,不像其他的編程語言,不同的彙編程序有不同的語法格式,許多剛接觸彙編的程序員就陷入了這種困境,不知道該學哪種好。

    所以,學習彙編的第一步就是選擇一種適合你的開發環境的彙編語言類型,一旦你確定下來了,學習彙編將不再是一件困惑的事情。

    本章將先介紹彙編語言的起源,以及爲什麼要使用匯編語言。要理解彙編語言,首先要理解處理器指令集的概念,接着要理解高級語言是如何轉爲原始的處理器指令集的,在理解了這些之後,你將明白如何使用匯編,以及如何與高級語言配合使用。

    從最底層的操作來看,所有的電腦處理器(包括微型計算機,小型計算機,大型計算機)處理數據都是通過廠家生產的處理器內部定義的二進制指令來完成的。這些指令定義了處理器可以執行的功能,以及如何處理程序員提供的數據,這些預先定義好的指令被稱作指令集。不同類型的處理器包含不同類型的指令集,處理器芯片的類型也通常是通過它們所支持的指令集類型和數量來劃分的。

    儘管不同處理器可能包含不同的指令集,但是它們處理這些指令集的方式卻很相似,下面將描述處理器如何處理這些指令集,以及指令集在示例芯片中的大概樣子。

指令的處理:

    當處理器芯片運行時,它從內存中讀取指令,每條指令可以包含一個或多個字節的信息,這些信息告訴處理器去執行不同的任務,由於每條指令都是從內存中讀取出來的,所以指令執行時所需要的任何數據也都是存儲在內存中的,而存儲指令的內存和存儲數據的內存是沒什麼不同的,所以就需要一個方法讓處理器能分辨出哪些內存中存儲的是指令,哪些內存中存儲的是指令操作數。

    通過兩種特殊的指針可以追蹤內存中的指令和數據。如圖1-1所示:
圖1-1
   
    圖1-1中的Instruction Pointer即指令指針用於幫助處理器判斷那些指令已經執行過了,以及哪條指令是下一次將要執行的。當然,還存在一些特殊的指令可以修改Instruction Pointer(指令指針)的位置,例如程序中的跳轉指令。

    類似的Data Pointer(數據指針)用於幫助處理器判斷數據區域在內存中的起始位置。這個數據區域被稱作"棧(stack)"。當有新的數據存放到棧中時,數據指針就會向下移,即從內存的高字節地址向低字節地址移動,也就是常說的"壓棧"。當數據從棧中讀取出來時,數據指針就會反方向往上移,即從內存的低字節地址向高字節地址移動,也就是常說的“彈出棧”

    每條指令可以包含一個或多個字節的信息以供處理器來處理,例如,下面這條指令的字節碼(以十六進制格式顯示):

    C7 45 FC 01 00 00 00

    這條指令會告訴因特爾IA-32系列的處理器去加載十進制1這個值到處理器寄存器定義的內存偏移的內存中。這條指令中包含的字節信息將在下面的Opcode操作碼中做解釋。另外,這條指令明確的定義了處理器將要執行的功能,爲了讓處理器能按順序正確的執行程序,所有的指令都必須以適當的方式和順序存放在內存中。

    每條指令中都必須至少包含一個字節,在這個字節中存放着operation code(操作碼,縮寫爲opcode),這個opcode操作碼定義了處理器可以執行的功能,每個處理器家族都有自己預定義好的操作碼,這些操作碼定義了所有可用的功能。下面就介紹英特爾IA-32家族的微處理器中操作碼的使用,這些操作碼將用在後面的所有例子中。

處理器的指令格式:

    在現代IBM微型計算機中所用到的所有微處理器的當前類型都包括在英特爾IA-32家族的微處理器系列中(在後面的章節中,被稱作“IA-32平臺”),流行的Pentium(奔騰)系列的處理器也包括在裏面。IA-32家族的微處理器使用一種特定的指令格式,理解這種格式將有助於你編寫彙編程序。這種指令格式主要由四個部分組成:
  • Optional Instruction prefix (可選的指令前綴)
  • Operational code (opcode) 前面提到過的操作碼
  • Optional modifier (可選的修飾字節)
  • Optional data element (可選的數據元素)
    下面的圖1-2顯示了這種IA-32指令格式的佈局:
圖1-2
   
    每個部分都定義了指令的相關功能,下面就一一介紹這幾個部分。

    小提示:並不是只有奔騰的處理器家族才採用這種IA-32的指令格式,AMD公司也生成了一批完全兼容IA-32指令格式的處理器芯片。

Opcode(操作碼):

    由圖1-2可知,IA-32指令格式中必須至少包含一個Opcode操作碼部分,這個操作碼定義了處理器可以執行的基本功能或任務。

    操作碼由1到3個字節組成,它唯一的定義了處理器可以執行的功能。例如,兩個字節的操作碼:"OF A2"對應IA-32的CPUID指令,當處理器執行這條指令時,它會在微處理器的不同寄存器中存放一些特定的信息,程序員就可以通過其他指令從這些寄存器中提取出信息,通過這些信息就可以檢測出當前程序所運行的微處理器的類型和型號。

    小提示:寄存器是處理器芯片中用於暫存數據的組件,在後面的章節"IA-32平臺"中會進行詳細的介紹。

Instruction Prefix(指令前綴):
 
    指令前綴由1到4個字節組成,用以修改操作碼的行爲。這些前綴根據功能的不同,被劃分爲四個不同的組,每個組中同一時間只能有一個前綴用於修改操作碼(因此指令前綴部分最多爲4個字節),這四個組能以任何順序進行前後排列。這四個組分別是(下面涉及到的一些彙編代碼和CS段之類的東東將在以後進行說明,這裏先留個印象,非原著裏的內容,而是從英特爾手冊裏找到的):
  • 組1Lock(鎖)和repeat(重複)前綴,在某些彙編代碼中可以看到LOCK前綴,LOCK會被編碼爲F0H(十六進制F0),表明任何共享內存區域都將只能被當前指令使用,lock會鎖FSB,前端串行總線,front serial bus,這個FSB是處理器和RAM之間的總線,鎖住了它,就能阻止其他處理器或者core從RAM獲取數據。當然這種操作是比較昂貴的,只能操作小的內存可以這樣做,在多處理器和超線程系統中,尤其是在原子性操作時,這個前綴就很有用了。repeat(重複)前綴,主要用於在進行字符串操作時或IO指令中,可以進行一些重複性的工作,例如彙編中常見的REPNE/REPNZ(會被編碼爲F2H),REP,REPE,REPZ(會被編碼爲F3H)等彙編前綴,有了這些前綴就可以通過單一的指令用數據填充一片連續的內存等。這裏提到的編碼如F2H,F3H在某些指令中屬於Opcode的強制前綴,在這些指令中F2H,F3H就不再具備repeat的含義了。
  • 組2Segment Override prefixs(段覆蓋前綴):
    • 2EH ---- CS段覆蓋 (在任何分支指令如JZ之類的跳轉指令中使用時將被保留)
    • 36H ---- SS段覆蓋前綴 (在任何分支指令中使用時將被保留)
    • 3EH ---- DS段覆蓋前綴 (在任何分支指令中使用時將被保留)
    • 26H ---- ES段覆蓋前綴 (在任何分支指令中使用時將被保留)
    • 64H ---- FS段覆蓋前綴 (在任何分支指令中使用時將被保留)
    • 65H ---- GS段覆蓋前綴 (在任何分支指令中使用時將被保留)
         Branch hints(分支暗示前綴)
    • 2EH ---- 不採用分支 (僅用於Jcc之類的條件轉移指令,例如JNZ之類的指令)
    • 3EH ---- 採用分支 (僅用於Jcc之類的條件轉移指令,例如JZ之類的指令)
  • 組3Operand-size override prefix(操作數大小覆蓋前綴),該前綴對應編碼爲66H(66H在某些指令中屬於強制前綴)。該前綴允許程序在16位和32位操作數大小之間進行切換,這兩種大小都有可能是當前執行環境的默認大小,通過使用此前綴,程序就能選擇非默認的另一種大小。
  • 組4Address-size override prefix(地址大小覆蓋前綴),對應編碼:67H,該前綴允許程序在16位和32位尋址中切換。每種大小都可能是默認大小,通過使用此前綴,程序就能選擇非默認的另一種大小來尋址。
Modifiers(修飾字節):

    一些Opcode(操作碼)需要某些額外的修飾字節來確定指令中的操作數存放在哪個寄存器中或者該操作數所在的內存位置,這些修飾字節主要包含在三個分開的值中:
  • addressing-form specifier (ModR/M) byte 尋址格式字節
  • Scale-Index-Base (SIB) byte 內存尋址時的三個計算因子,只在32位指令中存在,用以增加尋址的靈活性
  • one , two , or four address displacement bytes 一個,或兩個,或四個字節的displacement位移字節
The ModR/M byte (尋址格式字節):

    ModR/M字節由三個字段組成,如下面的圖1-3所示:



圖1-3

    上圖中Mod字段主要和r/m字段配合來定義出指令中要使用的寄存器和尋址模式。2位的Mod和3位的r/m一共有32種可能的組合,其中有8種組合用於定義要使用的寄存器,剩下24種組合用於定義尋址模式。

    中間的reg/opcode字段可以有兩種用途:前面提到過指令的Opcode操作碼由1到3個字節組成,有的指令的Opcode操作碼還會在reg/opcode字段中再定義3位,用於定義操作碼的子功能,reg/opcode字段的另一種用途就是定義一個指令要用的寄存器,通常是用作指令的目標操作數。

    r/m字段前面提到過,是和Mod字段配合來定義指令要使用的寄存器(和reg/opcode一樣,它也可以單獨定義一個寄存器),或者和Mod配合來定義一種需要的尋址模式。

    上面只是個概述,真正用的時候是需要通過在因特爾手冊上查表來確定這些字段的用途的,下面譯者會舉例說明,原著中對這段信息只有個概述,沒有很詳細的例子。

The SIB byte (32位指令中內存尋址的三個計算因子,用以增加尋址的靈活性):

    SIB字節也由三個字段組成,如下圖1-4所示:



圖1-4

    原著對SIB講的過於簡單,譯者就不採用原著的內容,在SIB字節中index和base字段都對應三位的寄存器代碼(同樣的index,base對應的寄存器代碼也可以通過因特爾手冊查表得到,後面會進行說明),scale字段是一個2位的數字。要計算SIB的值,處理器會使用下面的公式:(index * 2^scale) + base (其中2^scale表示2的scale次方,顯然,處理器會使用位移的方法來計算2的次方),處理器得到SIB的值後,就會代替掉ModR/M表中的計算因子(ModR/M表在後面會提到),從而得到最終的尋址值。(所以這些都是需要通過查表才能進行計算的)。

The address displacement byte (尋址位移字節):

    這個位移字節就是一個內存偏移值,和上面提到的東東組合在一起就可以得到最終的指令操作內存地址(組合的模式也在因特爾的手冊表裏,下面會提到表的位置)。

Data element (數據元素部分):

    有的彙編指令中會用到一些立即數,所謂立即數就是一些常量數字,例如 MOV CL 12H 這條彙編裏的12H(12的十六進制)就是立即數,這條彙編的意思就是將12H這個數字傳遞到CL寄存器中。這些立即數轉爲處理器指令時就會放在Data element(數據元素)部分,這樣CPU就可以直接從指令中得到操作數,不需要再內存尋址去獲取操作數,Data element(數據元素)部分根據數據的大小可以由1,2或4個字節組成。

下面的部分就由譯者從wikibooks上找到的資料,先舉例說明8086下16位指令的格式(32位的和16位的結構大致差不多,只不過多了SIB字節,ModR/M之類的組合也有些差別,wikibooks上的原文地址是 http://en.wikibooks.org/wiki/X86_Assembly/Machine_Language_Conversion ),如果覺得太難,就留個印象好了:

This is the general instruction form for the 8086 sequentially in main memory:
下面是8086指令在內存中的通用格式:

Prefixes (optional)
Opcode (first byte) D W  
Opcode 2 (occasional second byte)  
MOD Reg R/M
Displacement or data (occasional: 1, 2 or 4 bytes)
Prefixes
Optional prefixes which change the operation of the instruction
可選的前綴,可以改變指令的操作
D
(1 bit) Direction. 1 = Register is Destination, 0 = Register is source.
1位的方向位,1表示寄存器是目標操作數,0表示寄存器是源操作數
W
(1 bit) Operation size. 1 = Word, 0 = byte.
1位的操作數大小,1表示操作數是word 16位大小,0表示操作數是byte 1字節8位大小
Opcode
the opcode is a 6 bit quantity that determines what instruction family the code is
操作碼是一個6位的數據,用以決定指令的功能。
MOD (Mod)
(2 bits) Register mode.
2位的寄存器模式
Reg
(3 bits) Register. Each register has an identifier.
3位的寄存器值,每個寄存器都有一個標識。
R/M (r/m)
(3 bits) Register/Memory operand
3位的寄存器或內存操作數

Not all instructions have W or D bits; in some cases, the width of the operation is either irrelevant or implicit, and for other operations the data direction is irrelevant.
不是所有指令都有W或D位,在某些情況下,操作數的寬度要麼是無關緊要的要麼是隱式聲明的,並且對於指令的其他操作數,數據的方向是無關緊要的。

Notice that Intel instruction format is little-endian, which means that the lowest-significance bytes are closest to absolute address 0. Thus, words are stored low-byte first; the value 1234H is stored in memory as 34H 12H. By convention, most-significant bits are always shown to the left within the byte, so 34H would be 00110100B.
注意因特爾的指令格式是小字節序或叫低字節序,意思就是指令或數據中的最低字節是最靠近內存的絕對地址0的。因此,內存中首先出現的是低字節部分,例如,值1234H在內存中是以34H 12H的方式進行儲存的,按照慣例,字節內最高位通常顯示在字節的左側,所以34H對應的二進制就是00110100

After the initial 2 bytes, each instruction can have many additional addressing/immediate data bytes.
在頭兩個字節後,每條指令可以有許多額外的尋址或立即數的數據字節。

Mod / Reg / R/M tables

Mod Displacement
00 If r/m is 110, Displacement (16 bits) is address; otherwise, no displacement
01 Eight-bit displacement, sign-extended to 16 bits
10 16-bit displacement (example: MOV [BX + SI]+ displacement,al)
11 r/m is treated as a second "reg" field
Reg W = 0 W = 1 double word  
000 AL AX EAX
001 CL CX ECX
010 DL DX EDX
011 BL BX EBX
100 AH SP ESP
101 CH BP EBP
110 DH SI ESI
111 BH DI EDI
r/m Operand address
000 (BX) + (SI) + displacement (0, 1 or 2 bytes long)
001 (BX) + (DI) + displacement (0, 1 or 2 bytes long)
010 (BP) + (SI) + displacement (0, 1 or 2 bytes long)
011 (BP) + (DI) + displacement (0, 1 or 2 bytes long)
100 (SI) + displacement (0, 1 or 2 bytes long)
101 (DI) + displacement (0, 1 or 2 bytes long)
110 (BP) + displacement unless mod = 00 (see mod table)
111 (BX) + displacement (0, 1 or 2 bytes long)

(因特爾手冊上有關16位的ModR/M表則是以關係表的形式出現,在開頭的鏈接地址裏可以下載到因特爾英文手冊,可以查看該手冊的427頁,該頁有張“Table 2-1. 16-Bit Addressing Forms with the ModR/M Byte”的表)

Note the special meaning of MOD 00, r/m 110. Normally, this would be expected to be the operand [BP]. However, instead the 16-bit displacement is treated as the absolute address. To encode the value [BP], you would use mod = 01, r/m = 110, 8-bit displacement = 0.
注意表中一個特殊的組合,MOD爲00,r/m爲110時,如果按照常規,操作數應該是[BP],然而,因特爾卻武斷的將這種情況定義爲直接使用16位的displacement部分作爲內存的絕對地址(在32位下,也有這種情況,只不過32位的組合情況可能不同),如果需要使用[BP],那麼你將需要使用mod = 01,r/m=110,8位displacement=0的組合,所以很多彙編程序在遇到例如 MOV CL [BP] 這樣的彙編語句時,會自動將其轉爲 MOV CL [BP + 0]的形式。

Example: Absolute addressing (例子:絕對尋址)

Let's translate the following instruction into bytecode:
讓我們將下面的彙編語句轉爲指令字節碼:

XOR CL, [12H]

Note that this is XORing CL with the contents of address 12H – the square brackets are a common indirection indicator. The opcode for XOR is "001100dw". D is 1 because the CL register is the destination. W is 0 because we have a byte of data. Our first byte therefore is "00110010".
這條彙編的意思是將12H位置處的內存值讀取出來,再和CL進行異或運算 ---- 中括號是一種通用的尋址符號,XOR異或的指令操作碼是“001100dw”。D位是1,這是因爲CL寄存器是目標操作數。W位是0,這是因爲我們的操作數是一個字節的大小,所以我們的第一個字節就是“00110010”

Now, we know that the code for CL is 001. Reg thus has the value 001. The address is specified as a simple displacement, so the MOD value is 00 and the R/M is 110. Byte 2 is thus (00 001 110b).
從前面的表可知,CL寄存器的代碼是001 。另外,因爲絕對地址是一個單一的dispacement偏移即12H,所以MOD值爲00,R/M值爲110 。所以我們的第二個字節就是 (00 001 110b ,b表示這串數是二進制數)

Byte 3 and 4 contain the effective address, low-order byte first, 0012H as 12H 00H, or (00010010b) (00000000b)
字節3和字節4對應的就是16位的displacement偏移,此處就是有效地址,低字節放在前面,所以0012H對應爲12H 00H ,或者 (00010010b) (00000000) 因爲是16位,所以高位字節全爲0

All together,
所有的組合在一起就是:

XOR CL, [12H] = 00110010 00001110 00010010 00000000 = 32H 0EH 12H 00H

    wikibooks上還舉了個立即數的例子,因篇幅就不放在這裏了,可以根據前面的鏈接地址,到wikibooks上查看。

    在彙編語言英文原著中,有關Data element這一節的最後部分還舉了個32位指令的例子(這個例子在前面也提到過):

C7 45 FC 01 00 00 00

    上面的指令字節碼開頭的C7是opcode操作碼,在因特爾英文手冊PDF電子檔的第1693頁,有張表“Table A-2. One-byte Opcode Map: (00H — F7H)”,這張表裏是有關一個字節的操作碼和彙編指令的映射圖,從這張表中可知,C7對應的是MOV指令,如下圖1-5所示:


圖1-5

    對於C7後面的45可以查詢手冊第428頁的表“Table 2-2. 32-Bit Addressing Forms with the ModR/M Byte” ,如下圖1-6所示:
 


圖1-6

    所以45對應就是[EBP]的值加上後面的FC(FC就是表中的disp8偏移值),最後面的01 00 00 00爲立即數1,所以上面的字節碼對應的彙編就是

C7 45 FC 01 00 00 00 => MOV dword ptr [EBP + FCH],01H

    在因特爾手冊的第429頁是關於SIB的組合表,有需要的可以查看。

    所以如果你精通了這些指令字節碼,甚至可以用0101這種二進制來編程,有點hacker的味道了,呵呵。

    OK,到這裏,休息,休息一下 o(∩_∩)o~~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章