【優化(Optimization)】
~~~~~~~~~~~~~~~~~~~~
有兩種型別的優化:結構優化和區域性優化。在這一小章裡我們將討論這兩種型別。但是首先你必須懂得一件事:在它不影響功能的條件下優化你的程式碼。如果你作了不起作用的優化,有很多使得它不能工作,你需要修正它,那麼你就會犯越來越多的錯誤...一個永不休止的惡性迴圈:)
%結構優化%
~~~~~~~~~~
這是最有效的,也是很難做到和理解的。這種型別的優化通過使用一張紙把你的病毒的程式碼寫在上面後就能夠被很容易的理解了。我們這裡沒有紙,所以讓我們想象一種情形...假設你,在你的病毒裡,以只讀開啟檔案,關閉,再以讀/寫方式開啟檔案,再關閉,這是位元組的浪費。對於這種型別的優化,你必須對你能改變和儲存哪些位元組考慮。解決方法肯定因你的問題不同而不同。
%區域性優化%
~~~~~~~~~~
儘管它能節約很多位元組,這個是最簡單的方法。它在於單獨地改變一些程式碼行,做相同的事情而能使用更少的位元組。
給暫存器清0:
mov bx,0000h ; 3 bytes
xor bx,bx ; 2 bytes
sub bx,bx ; 2 bytes
所以,永遠不要使用第一個,要選擇其它的方式。有一個暫存器可以用其它的方法清零:DX。讓我們來看:
mov dx,0000h ; 3 bytes
xor dx,dx ; 2 bytes
sub dx,dx ; 2 bytes
cwd ; Convert word to dword ( 1 byte )
CWD將在AX比8000h小的時候工作。有一種只用一個位元組就可以給AH清零的方法:如果AL<80h,你可以使用CBW指令。
比較:
我們都知道一個很著名的方法,那就是對下面的指令使用特殊的指令:CMP。在比較兩個暫存器時,你可以使用下面能得到相同結果的方法,沒有任何節約:
cmp ax,bx ; 2 bytes
xor ax,bx ; 2 bytes
但是如果我們僅僅想知道兩個值是否相等,我們可以在任何情況下使用XOR,如果我們在比較一個暫存器和一個值的時候使用XOR而不使用CMP就可以節約位元組:
cmp ax,0666h ; 3 bytes
xor ax,0666h ; 2 bytes
但是,由於XOR指令的自然屬性,我們可以用它來判斷一個暫存器是否為0。但是下面可以用OR來節約位元組...
cmp ax,0000h ; 3 bytes
or ax,ax ; 2 bytes
暫存器優化 - AX:
你可以利用它來作比較:
cmp bx,0666h ; 4 bytes
cmp ax,0666h ; 3 bytes
而且你還可以用一種非常優化的方法把AX的值賦給另外一個暫存器:
mov bx,ax ; 2 bytes
xchg ax,bx ; 1 byte
你做這個的時候,AX和BX的值是否改變都不重要。在一個檔案開啟後確實很好,因為檔案控制程式碼在BX中更好。
字串操作:
每一個字串操作(MOVS,STOS,SCAS...)都是優化形式。讓我們看看你利用它來做什麼:
- MOVS:從DS:[SI]到ES:[DI]的移動
les di,ds:[si] ; 3 bytes
movsb ; If we want a byte ( 1 byte )
movsw ; If we want a word ( 1 byte )
movsd ; If we want a dword ( 2 bytes ) 386+
- LODS: 把位置DS:[SI]的值放進累加器
mov ax,ds:[si] ; 2 bytes
lodsb ; If we want a byte ( 1 byte )
lodsw ; If we want a word ( 1 byte )
lodsd ; If we want a dword ( 2 bytes ) 386+
- STOS: 把位置ES:[SI]的值放進累加器
les di,al ; Can't do this!
les di,ax ; Can't do this!
stosb ; If we want a byte ( 1 byte )
stosw ; If we want a word ( 1 byte )
stosd ; If we want a dword ( 2 bytes ) 386+
- CMPS:比較DS:[SI]和ES:[DI]裡的值
cmp ds:[si],es:[di] ; Can't have 2 segment overrides!
cmpsb ; If we want a byte ( 1 byte )
cmpsw ; If we want a word ( 1 byte )
cmpsd ; If we want a dword ( 2 bytes ) 386+
- SCAS:比較累加器的值和ES:[DI]
cmp ax,es:[di] ; 3 bytes
scasb ; If we want a byte ( 1 byte )
scasw ; If we want a word ( 1 byte )
scasd ; If we want a dword ( 2 bytes ) 386+
16位暫存器:
通常,使用16位暫存器比8位暫存器更優化。讓我們一個MOV指令的例子:
mov ah,06h ; 2 bytes
mov al,66h ; 2 bytes ( 4 bytes total )
mov ax,0666h ; 3 bytes
加/減任何16位暫存器也更優化:
inc al ; 2 bytes
inc ax ; 1 byte
dec al ; 2 bytes
dec ax ; 1 byte
基和段:
從一個段到另一個段的移動不能直接進行,所以我們必須對它處理:
mov es,ds ; Can't do this!
mov ax,ds ; 2 bytes
mov es,ax ; 2 bytes ( 4 bytes total )
push ds ; 1 byte
pop es ; 1 byte ( 2 bytes total )
使用DI/SI比使用BP更先進。
mov ax,ds:[bp] ; 4 bytes
mov ax,ds:[si] ; 3 bytes
過程:
如果你使用一個例程很多次,你必須考慮編寫一個過程的可能性。這個能優化你的程式碼。無論如何,對過程的使用不當會使我們的需要相反:程式碼將會增長。所以,如果你想知道一個例程到過程的轉變能否節約位元組,你可以使用下面的公式:
X = [rout. size - (CALL size + RET size)] * number of calls - rout. size
CALL size+ RET size表示4個位元組。X使我們將會節約的位元組。讓我們看看節約一些位元組的經典函式,檔案指標的移動:
fpend: mov ax,4202h ; 3 bytes
fpmov: xor cx,cx ; 2 bytes
cwd ; 1 byte
int 21h ; 2 bytes
ret ; 1 byte
我們得到8位元組+CALL 大小...11位元組。讓我們看看這個是否能優化我們的程式碼:
X = [ 7 - ( 3 + 1 ) ] * 3 - 7
X = 2 bytes saved
毫無疑問,這是一個創造性的計算。你呼叫這個例程可以多於3次(或更少),使它的大小不同,和更多的東西。
區域性優化的最後幾點:
- 使用SFT。在這個結構裡你已經有很多有用的資訊了,並且你可以沒有任何問題地操作它。
- 使你的編譯器至少掃描你的程式碼三遍來排除無用的NOP和其它的東西。
- 使用堆疊。
- 使用MOV offset的LEA指令更優化。