破解也不會,演算法也忘了,翻了點東西,算為OCG作點貢獻... (17千字)
32位程式碼優化常識
原作者: Benny/29A
翻譯改寫:hume/冷雨飄心
[注意:這不是鸚鵡學舌的翻譯,我儘量以我的理解傳達原文的本意]
關於程式碼優化的文章實在太多了,遺憾的是大部分我都沒有看,儘管他們就擺在我的床邊(每當我要看的時候就忍不住打哈欠...嘿嘿).這篇文章較短所以翻了一下.
程式碼優化的含義:
程式碼優化的目標當然是體積小和速度快,但是在通常的情況下二者就象魚和熊掌一樣不能得兼,我們通常尋找的是這二者的折中,究竟應該偏向何方,那就得具體看我們的實際需要.
但有些常識是我們應該牢記的,下面就結合我們最常遇到的具體情況來漫談一下:
1.暫存器清0
我絕對不想再看到下面的寫法:
1)
mov eax, 00000000h
;5 bytes
看起來上面的寫法很符合邏輯,但你應當意識到還有更加優化的寫法:
2)
sub eax, eax
;2 bytes
3) xor eax, eax
;2 bytes
看看後面的位元組數你就應該理解為什麼要這麼作了,除此之外,在速度上也沒有損失,他們一樣快,但你喜歡xor還是sub呢?我是比較喜歡xor,原因很簡單,因為我數學不好....
不過Microsoft比較喜歡sub....我們知道windows執行的慢....(呵呵,當然是玩笑這並不是真正原因X-D!)
2.測試暫存器是否為0
我也不希望看到下面的程式碼:
1) cmp eax, 00000000h
;5 bytes
je _label_
;2/6 bytes (short/near)
[* 注意很多指令針對eax作了優化,你要儘可能多地實用eax,比如CMP EAX,
12345678h (5 bytes)
如果你使用其他暫存器,就是6bytes *]
讓我們看看,簡單的比較指令居然要用7/11
bytes,No No No,試試下面的寫法:
2)
or eax, eax
;2 bytes
je _label_
;2/6 (short/near)
3) test eax, eax
;2 bytes
je _label_
;2/6 (short/near)
呵呵,只有4/8 bytes,看看我們可節省多少位元組啊3/4位元組...那麼接下來的問題是你喜歡OR還是TEST呢,就我個人而言,比較喜歡TEST,因為test不改變任何暫存器,並不向任何暫存器寫入內容,這通常能在pentium機上取得更快的執行速度.
別高興的太早,因為還有更值得我們高興的事情,假如你要判斷的的是eax暫存器,那麼看看下面的,是不是更有啟發?
4) xchg eax, ecx
;1 byte
jecxz _label_
;2
bytes
在短跳轉的情況下我們比2)和3)又節省了1位元組.oh....___...
3.測試暫存器是否為0FFFFFFFFh
一些API返回-1,因此如何測試這個值呢?看你可能又要這樣:
1) cmp eax, 0ffffffffh
;5 bytes
je _label_
;2/6 bytes
hey,不要這樣,寫程式碼的時候想一想,於是有了下面的寫法:
2) inc eax
;1 byte
je _label_
;2/6 bytes
dec eax
;1 byte
可以節省3
bytes並且執行速度會更快.
4.置暫存器為0FFFFFFFFh
看看假如你是Api的作者,如何返回-1?這樣嗎?
1) mov eax, 0ffffffffh
;5 bytes
看了上面的不會再這麼XXX了吧?看看下面的:
2)
xor eax, eax / sub eax, eax ;2
bytes
dec eax
;1 byte
節省一個字!還有寫法:
3) stc
;1 byte
sbb eax,
eax
;2 bytes
這有時還可以優化掉1 byte:
jnc _label_
sbb eax, eax
;2 bytes
only!
_label_: ...
我們為什麼用asm呢?這就是原因.
5.暫存器清0並移入低字數值
1)
xor eax, eax
;2 bytes
mov ax, word ptr [esi+xx] ;4 bytes
????--->不會吧,這可能是最多初學者的寫法了,我當然原來也是,看了benny的文章之後我決定改寫為:
2) movzx eax, word ptr [esi+xx]
;4 bytes
收穫2 bytes!
下面的
3)
xor eax, eax
;2 bytes
mov al, byte ptr [esi+xx]
;3 bytes
就相應改為:
4) movzx eax, byte ptr [esi+xx]
;4 bytes
我們應當儘可能利用movzx
5) xor eax, eax
;2
bytes
mov ax, bx
;3 bytes
因為執行速度不慢並通常能節省位元組...
6) movzx eax, bx
;3 bytes
6.關於push,下面是著重程式碼體積的優化,因為暫存器操作總要比記憶體操作要快.
1) mov eax, 50h
;5 bytes
這樣就小了1 word
2)
push 50h
;2 bytes
pop eax
;1 byte
當運算元只有1位元組時候,push只有2 bytes,否則就是5
bytes,記住!
下一個問題,向堆疊中壓入7個0
3) push 0
;2
bytes
push 0
;2 bytes
push 0
;2 bytes
push 0
;2 bytes
push 0
;2 bytes
push 0
;2 bytes
push 0
;2 bytes
佔用14位元組,顯然不能滿意,優化一下
4) xor
eax, eax
;2 bytes
push eax
;1 byte
push eax
;1 byte
push eax
;1 byte
push eax
;1 byte
push eax
;1 byte
push eax
;1 byte
push eax
;1
byte
可以更緊湊,但會慢一點的形式如下:
5) push 7
;2 bytes
pop ecx
;1 byte
_label_: push 0
;2 bytes
loop _label_
;2 bytes
可以節省7位元組....
有時候你可能會從將一個值從一個記憶體地址轉移到另外記憶體地址,並且要儲存所有暫存器:
6) push eax
;1 byte
mov
eax, [ebp + xxxx]
;6 bytes
mov [ebp
+ xxxx], eax ;6
bytes
pop eax
;1 byte
試試push,pop
7) push dword ptr [ebp
+ xxxx] ;6 bytes
pop dword ptr [ebp + xxxx]
;6 bytes
7.乘法
當eax已經放入被乘數,要乘28h,如何來寫?
1)
mov ecx, 28h
;5 bytes
mul ecx
;2 bytes
好一點的寫法如下:
2)
push 28h
;2 bytes
pop ecx
;1 byte
mul ecx
;2 bytes
哇這個更好::
3)
imul eax, eax, 28h
;3 bytes
intel在新CPU中提供新的指令並不是擺設,需要你的使用.
8.字串操作
你如何從記憶體取得一個位元組呢?
速度快的方案:
1)
mov al/ax/eax, [esi]
;2/3/2 bytes
inc esi
;1 byte
程式碼小的方案:
2) lodsb/w/d
;1 byte
我比較喜歡lod因為他小,雖然速度慢了點.
如何到達字串尾呢?
JQwerty's method:
9)
lea esi, [ebp + asciiz]
;6 bytes
s_check: lodsb
;1 byte
test al,
al
;2 bytes
jne s_check
;2 bytes
Super's method:
10) lea edi, [ebp + asciiz]
;6 bytes
xor al, al
;2 bytes
s_check: scasb
;1 byte
jne s_check
;2 byte
選擇哪一個?Super的在386以下的更快,JQwerty的在486以及pentium上更快,體積一樣,選擇由你.
9.複雜一點的...
假設你有一個DWORD表,ebx指向表的開始,ecx是指標,你想給每個doword加1,看看如何作:
1) pushad
;1 byte
imul
ecx, ecx, 4
;3 bytes
add
ebx, ecx
;2 bytes
inc dword ptr [ebx]
;2 bytes
popad
;1 byte
可以優化一點,但是好像沒人用:
2) inc dword ptr [ebx+4*ecx]
;3 bytes
一條指令就節省6位元組,而且速度更快,更易讀,但好像沒有什麼人用?...why?
還可以有立即數:
3) pushad
;1 byte
imul
ecx, ecx, 4
;3 bytes
add
ebx, ecx
;2 bytes
add ebx, 1000h
;6 bytes
inc dwor ptr [ebx]
;2 bytes
popad
;1 byte
優化為:
4) inc dword ptr [ebx+4*ecx+1000h]
;7 bytes
節省了8位元組!
看一下lea指令能為我們乾點什麼呢?
lea eax, [12345678h]
eax的最後結果是什麼呢?正確答案是12345678h.
假設 EBP = 1
lea eax, [ebp + 12345678h]
結果是123456789h....呵呵比較一下:
lea eax, [ebp + 12345678h]
;6 bytes
==========================
mov eax, 12345678h
;5 bytes
add eax, ebp
;2 bytes
5) 看看:
mov
eax, 12345678h
;5 bytes
add eax,
ebp
;2 bytes
imul ecx, 4
;3 bytes
add eax, ecx
;2 bytes
6)
用lea來進行一些計算我門將從體積上得到好處:
lea eax, [ebp+ecx*4+12345678h] ;7 bytes
速度上一條lea指令更快!不影響標誌位...記住下面的格式,在許多地方善用他們你可以節省時間和空間.
OPCODE <SIZE PTR>
[BASE + INDEX*SCALE + DISPLACEMENT]
10.下面是關於病毒重定位優化的,懼毒人士請繞行...
下面的程式碼你不應該陌生
1) call gdelta
gdelta: pop ebp
sub ebp, offset gdelta
在以後的程式碼中我們這樣使用delta來避免重定位問題
lea eax, [ebp + variable]
這樣的指令在應用記憶體資料的時候是不可避免的,如果能優化一下,我門將會得到數倍收益,開啟你的sice或者trw或者ollydbg等偵錯程式,看看:
3) lea eax, [ebp + 401000h]
;6 bytes
假如是下面這樣
4) lea eax, [ebp + 10h]
;3 bytes
也就是說如果ebp後面變數是1位元組的話,總的指令就只有3位元組
修改一下最初的格式變為:
5)
call gdelta
gdelta: pop ebp
在某些情況下我們的指令就只有3位元組了,可以節省3位元組,嘿嘿,讓我們看看:
6) lea eax, [ebp + variable - gdelta]
;3 bytes
和上面的是等效的,但是我們可以節省3位元組,看看CIH...
11.其他技巧:
如果EAX小於80000000h,edx清0:
--------------------------------------------------
1) xor edx, edx
;2 bytes,
but faster
2) cdq
;1 byte, but slower
我一直使用cdq,為什麼不呢?體積更小...
下面這種情況一般不要使用esp和ebp,使用其他暫存器.
-----------------------------------------------------------
1) mov eax, [ebp]
;3
bytes
2) mov eax, [esp]
;3
bytes
3) mov eax, [ebx]
;2
bytes
交換暫存器中4個位元組的順序?用bswap
---------------------------------------------------------
mov eax, 12345678h
;5 bytes
bswap eax
;2 bytes
;eax
= 78563412h now
Wanna
save some bytes replacin' CALL ?
---------------------------------------
1) call _label_
;5 bytes
ret
;1 byte
2)
jmp _label_
;2/5 (SHORT/NEAR)
如果僅僅是優化,並且不需要傳遞引數,請儘量用jmp代替call
比較 reg/mem 時如何節省時間:
------------------------------------------
1) cmp reg, [mem]
;slower
2) cmp [mem], reg
;1 cycle faster
乘2除2如何節省時間和空間?
------------------------------------------------------------
1) mov eax, 1000h
mov ecx, 4
;5
bytes
xor edx, edx
;2 bytes
div ecx
;2 bytes
2)
shr eax, 4
;3 bytes
3)
mov ecx, 4
;5 bytes
mul ecx
;2 bytes
4) shl eax, 4
;3 bytes
loop指令
------------------------
1) dec ecx
;1 byte
jne _label_
;2/6 bytes (SHORT/NEAR)
2)
loop _label_
;2 bytes
再看:
3) je $+5
;2 bytes
dec
ecx
;1 byte
jne _label_
;2 bytes
4) loopXX _label_ (XX = E, NE, Z or NZ) ;2 bytes
loop體積小,但486以上的cpu上執行速度會慢一點...
比較:
---------------------------------------------------------
1) push eax
;1 byte
push
ebx
;1 byte
pop eax
;1 byte
pop ebx
;1 byte
2)
xchg eax, ebx
;1 byte
3)
xchg ecx, edx
;2 bytes
如果僅僅是想移動數值,用mov,在pentium上會有較好的執行速度:
4) mov ecx, edx
;2
bytes
比較:
--------------------------------------------
1) 未優化:
lbl1: mov al, 5
;2 bytes
stosb
;1 byte
mov eax, [ebx]
;2 bytes
stosb
;1 byte
ret
;1 byte
lbl2: mov al, 6
;2 bytes
stosb
;1 byte
mov eax, [ebx]
;2 bytes
stosb
;1 byte
ret
;1 byte
---------
;14 bytes
2) 優化了:
lbl1: mov
al, 5
;2 bytes
lbl:
stosb
;1 byte
mov eax, [ebx]
;2 bytes
stosb
;1 byte
ret
;1 byte
lbl2: mov al, 6
;2
bytes
jmp lbl
;2 bytes
---------
;11 bytes
讀取常數變數,試試在指令中直接定義:
-----------------------------
...
mov [ebp + variable], eax ;6 bytes
...
...
variable dd 12345678h ;4 bytes
2) 優化為:
mov eax, 12345678h ;5 bytes
variable = dword ptr $ - 4
...
...
mov [ebp + variable], eax ;6 bytes
呵呵,好久沒看到這麼有趣的程式碼了,前提是編譯的時候支援程式碼段的寫入屬性要被設定.
最後介紹未公開指令SALC,現在的偵錯程式都支援...什麼含義呢:就是CF位置1的話就將al置為0xff
------------------------------------------------------------------
1) jc _lbl1 ;2 bytes
mov al, 0 ;2 bytes
jmp _end ;2 bytes
_lbl: mov al, 0ffh ;2 bytes
_end: ...
2) SALC db 0d6h ;1 byte ;)
------------------------------------------------------------------>over...
2002---hume
相關文章
- 獻給初學者(高手也點評點評!!)KoolMoves V1.33的破解!!
(10千字)2000-09-16
- 獻給初學者(高手也看看) 破解 Cpukiller 2.0 (1千字)2000-09-17
- 為什麼說區塊鏈也不是什麼好東西?2018-03-07區塊鏈
- 學會這幾點,你也能成為面試殺手!2018-08-07面試
- 筆試不會的東西2020-11-09筆試
- PHP之父安迪:谷歌支援PHP一點也不奇怪2013-05-20PHP谷歌
- 東數西算,網路為先2022-03-22
- IT人為什麼也需要懂點FMEA?2023-12-13
- Webpack4 那點兒東西2018-03-31Web
- ClockWise 3.22e註冊碼演算法分析 - OCG (17千字)2002-04-10演算法
- 零起點的開源社群貢獻指南2017-11-01
- 具體的破解過程來也! (10千字)2001-04-21
- 不會程式設計也可以製作ERP、CRM系統。2020-04-26程式設計
- 近來學習的一點東西2020-10-16
- AI語音:ChatTTS 真有點東西啊!2024-06-05AITTS
- oracle增加主鍵也不會了2019-04-09Oracle
- 從啥也不會的小白,如何才能成為合格的演算法工程師?2020-11-25演算法工程師
- 微軟是第 17 位 Linux 貢獻者2012-04-09微軟Linux
- 用Golang做點自動化的東西2019-03-25Golang
- 從蘋果BigSur官網學點東西2020-12-19蘋果
- noip模擬45[真是啥也不會]2021-08-22
- 方法到位,Linux也不難學會2021-11-18Linux
- 伺服器當機,我為什麼一點也不慌?2020-10-20伺服器
- 也談SAP系統優缺點2022-08-12
- 初學者的東西:Transoft's Server All 1.02破解
(3千字)2001-01-08Server
- 為開源社群做貢獻2013-12-08
- 如何為PHP貢獻程式碼2015-12-28PHP
- 如何為 PHP 貢獻程式碼2012-05-01PHP
- 2019 年第 9 周沸點看點:我為開源做貢獻(文末招聘專場)2019-02-25
- 我給Apache頂級專案貢獻了點原始碼。2021-03-09Apache原始碼
- service會不會因repository而變得也很薄2008-11-15
- 一點也不復雜!Nginx 可以輕鬆搞定跨域問題2024-02-29Nginx跨域
- 也談《傲世三國》的暴力破解法 (11千字)2001-01-10
- 一筆難算的賬——IT貢獻值2012-02-10
- 為什麼說會不會SQL,決定著你的工資?方向不對,努力也白費!2020-04-13SQL
- 為什麼要貢獻開源2018-12-28
- 隨意的一點東西,還是期望被人看到2024-03-07
- 就算是週末也不會忘記MySQL2020-10-31MySql