偽代碼是給處理器的指令,它實際上是原始十六進制代碼的可讀版。因此,彙編是最低級的編程語言。彙編中的所有東西被直接翻譯為十六進制碼。換句話說,你沒有把高級語言翻譯為低級語言的編譯器上的煩惱,彙編器僅僅把彙編代碼轉化為原始數據。
下面將討論一些用來運算,位操作等的偽代碼。還有跳轉指令,比較等偽代碼。
1.一些基本的計算偽代碼(又稱指令)
(1)MOV
這條指令用來把一個地方移往(事實上是複製到)另一個地方。這個地方可以是寄存器,內存地址或是直接數值(當然只能作為源值)。Mov指令的語法是:
mov 目標,源
你可把一個寄存器移往另一個(注意指令是在複製那個值到目標中,儘管"mov"這個名字是移的意思)
mov edx, ecx
上面的這條指令把ecx的內容複製到了ecx中,源和目標的大小應該一致。例如這個指令是非法的:
mov al, ecx;非法
這條偽代碼試圖把一個DWORD(32位)值裝入一個字節(8位)的寄存器中。這不能個由mov指令來完成(有其他的指令幹這事)。但這些指令是允許的因為源和目標在大小上並沒有什麼不同:
mov al, blmov cl, dlmov cx, dxmov ecx, ebx
內存地址由offset指示(在win32中,前一章中有更多信息)你也能從地址的某一個地方獲得一個值並把它放入一個寄存器中。下面有一個例子:
每一個塊代表一個字節
offset的值這裡是用字節的形式表示的,但它事實上是32位的值,比如3A(這不是一個常見的offset的值,但如果不這樣簡寫表格裝不下),這也是一個32位的值:0000003Ah。只是為了節省空間,使用了一些不常見的低位offset。所有的值均為16進制。
看上表的offset 3A。那個offset的數據是25, 7A, 5E, 72, EF等。例如,要把這個位於3A的值用mov放入寄存器中:
mov eax, dword ptr[0000003Ah]
(h後綴表明這是一個十六進制值)
mov eax, dword ptr[0000003Ah]這條指令的意思是:把位於內存地址3A的DWORD大小的值放入eax寄存器。執行了這條指令後,eax包含了值725E7A25h。可能你注意到了這是在內存中時的反轉結果:25 7A 5E 72。這是因為存儲在內存中的值使用了little endian格式。這意味著越靠右的字節位數越高:字節順序被反轉了。我想一些例子可以使你把這個搞清楚。
十六進制dword(32位)值放在內存中時是這樣:40, 30, 20, 10(每個值佔一個字節(8位))
十六進制word(16位)值放在內存中時是這樣:50, 40
回到前面的例子。你也可以對其他大小的值這麼做:
mov cl, byte ptr [34h] ; cl得到值0Dh(參考上表)
mov dx, word ptr [3Eh] ; dx將得到值 7DEFh (看上表,記住反序)
大小有時不是必須的。
Mov eax,[00403045h]
因為eax是32位寄存器,編譯器假定(也只能這麼做)它應該從地址403045(十六進制)取個32位的值。
可以直接使用數值:
mov edx, 5006
這只是使得edx寄存器裝有值5006,綜括號[和]用來從括號間的內存地址處取值,沒有括號就只是這個值。寄存器和內存地址也可以(他應該是32位程序中的32位寄存器):
mov eax,403045h;使eax裝有值403045h(十六進制)
mov cx,[eax];把位於內存地址eax的word大小的值(403045)移入cx寄存器。
在mov cx, [eax]中,處理器會先查看eax裝有什麼值(=內存地址),然後在那個內存地址中有什麼值,並把這個word(16位,因為目標-cx-是個16位寄存器)移入cx。
(2)ADD, SUB, MUL, DIV
許多偽代碼做計算工作。你可以猜出它們中的大多數的名字:add(加),sub(減),mul(乘),div(除)等。
Add偽代碼有如下語法:
Add 目標,源
執行的運算是 目標=目標+源。下面的格式是允許的。
這條指令非常簡單。它只是把源值加到目標值中並把結果保存在目標中。其他的數學指令有:
sub 目標,源(目標=目標-源)mul 目標,源(目標=目標×源)div 源(eax=eax/源,edx=餘數)
減法和加法一樣做,乘法是目標=目標×源。除法有一點不同,因為寄存器是整數值(注意,繞回數不是浮點數)除法的結果被分為商和餘數。例如:
28/6->商=4,餘數=430/9->商=3,餘數=397/10->商=9,餘數=718/6->商=3,餘數=0
現在,取決於源的大小,商(一部分)被存在eax中,餘數(一部分)在edx:
*:例如,如果dx=2030h,而ax=0040h,dx:ax=20300040h。dx:ax是一個雙字值。其中高字代表dx,低字代表ax,Edx:eax是個四字值(64位)其高字是edx低字是eax。
Div偽代碼的源可以是
· 8位寄存器 (al, ah, cl,...)
· 16位寄存器 (ax, dx, ...)
· 32位寄存器 (eax, edx, ecx...)
· 8位內存數 (byte ptr [xxxx])
· 16位內存數(word ptr [xxxx])
· 32位內存數(dword ptr [xxxx])
源不可以是直接數值,因為處理器不能決定源參數的大小。
(3)位操作
這些指令都由源和目標,除了"NOT"指令。目標中的每位與源中的每位作比較,並看是那個指令,決定是0還是1放入目標位中。
如果源和目標均為1,AND把輸出位設為1。
如果源和目標中有一個為1,OR把輸出位設為1。
如果源和目標位不一樣,XOR把輸出位設為1。
NOT反轉源位
一個例子:
mov ax, 3406mov dx, 13EAhxor ax,dx
ax=3406(十六進制)是二進制的0000110101001110
dx=13EA(十六進制)是二進制的0001001111101010
對這些位進行xor操作:
新dx是0001111010100100 (十進制的7845, 十六進制的1EA4)
另一個例子:
mov ecx, FFFF0000hnot ecx
FFFF0000在二進制中是11111111111111110000000000000000(16個1,16個0)如果反轉每位會得到
00000000000000001111111111111111(16個0,16個1)在十六進制中是0000FFFF。因而執行NOT操作後,ecx是0000FFFFh。
(4)步增INC/步減DEC
有兩個很簡單的指令,DEC和INC。這些指令使內存地址和寄存器步增或步減,就是這樣:
inc reg -> reg = reg + 1
dec reg -> reg = reg – 1
inc dword ptr [103405] -> 位於103405的值步增
dec dword ptr [103405] -> 位於103405的值步減
(5)NOP
這條指令什麼都不幹。它僅僅佔用空間和時間。它用作填充或給代碼打補丁的目的。
(6)移位(Bit Rotation 和 shifiting)
注意:下面的大部分例子使用8位數,但這只是為了使目的清楚。
(7)Shifting函數
SHL 目標,計數(count)
SHR 目標,計數(count)
SHL和SHR在寄存器,內存地址中像左或向右移動一定數目(count)的位。
例如:
;這兒al=01011011(二進制)
shr al, 3
它的意思是:把al寄存器中的所有位向右移三個位置。因而al會變成為00001011。左邊的字節用0填充,而右邊的字節被移出。最後一個被移出的位保存在carry-flag中。Carry-flag是處理器標誌寄存器的一位,它不是像eax或ecx一樣的,你可以訪問的寄存器(雖然有偽代碼幹這活),但它的值決定於該指令的結構。它(carry-flag)會在後面解釋,你要記住的唯一一件事是carry是標誌寄存器的一位且它可以被打開或者關閉。這個位等於最後一個移出的位。
shl和shr一樣,只不過是向左移。
;這兒bl=11100101(二進制)
shl bl, 2
執行了指令後bl是10010100(二進制)。最後的兩個位是由0填充的,carry-flag是1,因為最後移出的位是1。
還有兩個偽代碼:
SAL 目標, 計數(算術左移)
SAR 目標, 計數(算術右移)
SAL和SHL一樣,但SAR不完全和SHR一樣。SAR不是用0來填充移出的位而是複製MSB(最高位)例如:
al = 10100110
sar al, 3
al = 11110100
sar al, 2
al = 11101001
bl = 00100110
sar bl, 3
bl = 00000100
(8)Rotation(循環移動) 函數
Rol 目標,計數;循環左移
Ror 目標,計數;循環右移
Rcl 目標,計數;通過carry循環左移
Rcr 目標,計數;通過carry循環右移
循環移動(Rotation)看上去就像移(Shifting),只是移出的位又到了另一邊。
例如:ror(循環右移)
如你在上圖所見,位循環了。注意,每個被推出的位又移到了另一邊。和Shifting一樣,carry位裝有最後被移出的位。Rcl和Rcr實際上和Rol,Rcr一樣。它們的名字暗示了它們用carry位來表明最後移出的位,但和Rol和Ror幹同樣的事情。它們沒有什麼不同。
(9)交換
XCHG指令也非常簡單。它同在兩個寄存器和內存地址之間交換:
eax = 237h
ecx = 978h
xchg eax, ecx
eax = 978h
ecx = 237h
2.條件跳轉
例如下面這段代碼:
mov eax, edx
sub eax, ecx
cmp eax, 2
jz loc1
xor eax, eax
jmp loc2
loc1:
xor eax, eax
inc eax
loc2:
(xor eax, eax意為:eax=0,eax清零)
讓我們來看看這些代碼:
mov eax, edx ;把edx放入eax中
sub eax, ecx ;eax-ecx
cmp eax, 2
這有一條新指令:cmp。Cmp意為compare(比較)。它能比較兩個值(或寄存器、或內存、或直接數值)並設置Z-flag(零標誌)。零標誌很像carry,也是內部標誌寄存器的一位。
Jz loc1
這也是條新的。它是條件跳轉指令。Jz=jump if zero(如果設置了零標誌就跳轉)。Loc1是一個標記指令"xor eax,eax|inc eax"內存開始處offset的標籤。因而jz loc1=如果設置了零標誌,跳往位於loc1的指令。
Cmp eax, 2;如果eax=2設置零標誌
Jz loc1;如果設置了零標誌就跳轉
= 如果eax等於2,跳往位於loc1的指令
然後有jmp loc2.這也好似一個跳轉,但是是一個無條件跳轉:它總是執行。上面的代碼,用c語言描述就是:
if ((edx-ecx)==2)
{
eax = 1;
}
Else
{
eax = 0;
}
或者,用Basic語言描述:
IF (edx-ecx)=2 THEN
EAX = 1
ELSE
EAX = 0
END IF
3.標誌寄存器
標誌寄存器有一套標誌。它們設不設置取決於計算或其他時間。我不會討論它們的全部。只揀幾個重要的說:
還有更多的標誌(Parity, Auxiliary, Trap, Interrupt, Direction, IOPL, Nested Task, Resume, & Virtual Mode)但因為我們不用它們,所以我不解釋。
4.跳轉系列
有一整套的條件跳轉,而且它們跳轉與否均取決於標誌的狀態。但由於大部分跳轉指令有明白的名字,你甚至無需知道哪個標誌要設置,例如:"如果大於等於就跳轉"(jge)和"符號標誌=溢出標誌"一樣,而"如果零就跳轉"和"如果零標誌=1就跳轉"一樣。死背住下面的表即可
在下表中,"意思"指的是什麼樣的計算結果該跳轉。"如果大於就跳轉"意為:
cmp x, y
jump 如果 x 比 y大 就跳
所有的跳轉指令後面要跟一個參數:要跳往的偏移地址(距離)offset。
閱讀更多 萃乎 的文章