代碼逆向(六)——加法與減法的識別與優化原理

加法的優化相對來說比較簡單,只有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 變量

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章