加法的優化相對來說比較簡單,只有3種優化方案,下面我們就以一個簡單的例子來說明這三個問題,先看源碼:
int _tmain(int argc, _TCHAR* argv[])
{
int nNum, nA = 8;
nNum = argc + nA; // 形式1
printf("%d/r/n",nNum);
nNum = argc + 9; // 形式2
printf("%d/r/n",nNum);
nNum = nNum + 1; // 形式3
printf("%d/r/n",nNum);
return 0;
}
Debug版反彙編代碼:
.text:0041301E mov [ebp+nA], 8 ; nA = 8
.text:00413025 mov eax, [ebp+argc] ; eax=argc
.text:00413028 add eax, [ebp+nA] ; eax=eax+nA <-- !!
.text:0041302B mov [ebp+nNum], eax ; nNum=eax
.text:00413030 mov eax, [ebp+nNum] ; eax=nNum=argc+nA
.text:00413033 push eax
.text:00413034 push offset Format ; "%d/r/n"
.text:00413039 call ds:__imp__printf
.text:0041303F add esp, 8
.text:00413049 mov eax, [ebp+argc]
.text:0041304C add eax, 9 ; eax=argc+9 <-- !!
.text:0041304F mov [ebp+nNum], eax
.text:00413054 mov eax, [ebp+nNum]
.text:00413057 push eax
.text:00413058 push offset Format ; "%d/r/n"
.text:0041305D call ds:__imp__printf
.text:00413063 add esp, 8
.text:0041306D mov eax, [ebp+nNum]
.text:00413070 add eax, 1 ; eax=nNum+1 <-- !!
.text:00413073 mov [ebp+nNum], eax
.text:00413078 mov eax, [ebp+nNum]
.text:0041307B push eax
.text:0041307C push offset Format ; "%d/r/n"
.text:00413081 call ds:__imp__printf
.text:00413087 add esp, 8
通過上面的反彙編代碼我們可以看到三個在普通不過的加法計算,下面我們再看看Release版的反彙編代碼:
.text:00401000 _main proc near ; CODE XREF: __tmainCRTStartup+10Ap
.text:00401000
.text:00401000 argc= dword ptr 4
.text:00401000 argv= dword ptr 8
.text:00401000 envp= dword ptr 0Ch
.text:00401000
.text:00401000 push esi
.text:00401001 mov esi, ds:__imp__printf
.text:00401007 push edi
.text:00401008 mov edi, [esp+8+argc]
.text:0040100C lea eax, [edi+8] ; 優化後的加法
.text:0040100F push eax
.text:00401010 push offset Format ; "%d/r/n"
.text:00401015 call esi ; __imp__printf
.text:00401017 add edi, 9 ; 此處並沒有什麼優化
.text:0040101A push edi
.text:0040101B push offset Format ; "%d/r/n"
.text:00401020 call esi ; __imp__printf
.text:00401022 inc edi ; 優化後的加法
.text:00401023 push edi
.text:00401024 push offset Format ; "%d/r/n"
.text:00401029 call esi ; __imp__printf
.text:0040102B add esp, 18h
.text:0040102E pop edi
.text:0040102F xor eax, eax
.text:00401031 pop esi
.text:00401032 retn
.text:00401032 _main endp
由上面兩段反彙編代碼我們可以總結出加法計算的以下優化方案:
變量+變量 = lea Exx,[變量+變量]
變量+常量 = add 變量+常量
變量+1 = inc 變量
1.7.2、減法的識別與優化技巧
減法的優化與加法大同小異,基本相同,因此與加法相同的地方筆者在本文中將不再多說,我們先看源代碼:
int _tmain(int argc, _TCHAR* argv[])
{
int nNum, nA = 8;
nNum = argc - nA; // 形式1
printf("%d/r/n",nNum);
nNum = argc - 9; // 形式2
printf("%d/r/n",nNum);
nNum = nNum - 1; // 形式3
printf("%d/r/n",nNum);
return 0;
}
這次我們直接看Release版反彙編代碼:
.text:00401000 _main proc near ; CODE XREF: __tmainCRTStartup+10Ap
.text:00401000
.text:00401000 argc = dword ptr 4
.text:00401000 argv = dword ptr 8
.text:00401000 envp = dword ptr 0Ch
.text:00401000
.text:00401000 push esi
.text:00401001 mov esi, ds:__imp__printf
.text:00401007 push edi
.text:00401008 mov edi, [esp+8+argc]
.text:0040100C lea eax, [edi-8] ; 減法優化
.text:0040100F push eax
.text:00401010 push offset Format ; "%d/r/n"
.text:00401015 call esi ; __imp__printf
.text:00401017 add edi, 0FFFFFFF7h ; 減法優化 <--!
.text:0040101A push edi
.text:0040101B push offset Format ; "%d/r/n"
.text:00401020 call esi ; __imp__printf
.text:00401022 dec edi ; 減法優化
.text:00401023 push edi
.text:00401024 push offset Format ; "%d/r/n"
.text:00401029 call esi ; __imp__printf
.text:0040102B add esp, 18h
.text:0040102E pop edi
.text:0040102F xor eax, eax
.text:00401031 pop esi
.text:00401032 retn
.text:00401032 _main endp
通過以上代碼我們不難發現減法的優化方案與加法基本相同,唯一不同的就是在“形式2”上的體現,我們可以發現編譯器將其優化成了一個加法,那麼這究竟是爲什麼,而其原理又是怎樣的呢?
僅從指令週期上來講,老版本的CPU其加法指令週期要比減法短一些,因此這種優化也就這樣一直沿襲了下來。而僅從本條指令上的優化來說,編譯器將原本的減法轉換成了加法,並將常量轉成了其原本的補碼,我們都知道減一個數與加這個數的補碼所得到的結果都是一樣的,因此編譯器就利用了這個特性。
總結:
變量-變量 = lea Exx,[變量-變量]
變量-常量 = add 變量+補碼(常量)
變量-1 = dec 變量
代碼逆向(六)——加法與減法的識別與優化原理
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.