逆向基礎(五)

wyzsk發表於2020-08-19
作者: reverse-engineering · 2014/05/29 17:16

Chapter 14


Division by 9

下面是一個非常簡單的函式

#!bash
int f(int a)
{
    return a/9;
};

14.1 x86

以一種十分容易預測的方式編譯的

#!bash
_a$ = 8             ; size = 4
_f   PROC
    push    ebp
    mov     ebp, esp
    mov     eax, DWORD PTR _a$[ebp]
    cdq             ; sign extend EAX to EDX:EAX
    mov     ecx, 9
    idiv    ecx
    pop     ebp
    ret     0
_f  ENDP

IDIV 有符號數除法指令 64位的被除數分存在兩個暫存器EDX:EAX,除數放在單個暫存器ECX中。運算結束後,商放在EAX,餘數放在EDX。f()函式的返回值將包含在eax暫存器中,也就是說,在進行除法運算之後,值不會再放到其他位置,它已經在合適的地方了。正因為IDIV指令要求被除數分存在EDX:EAX裡,所以需要在做除法前用CDQ指令將EAX中的值擴充套件成64位有符號數,就像MOVSX指令(13.1.1)所做的一樣。如果我們切換到最佳化模式(/0x),我們會得到

清單14.2:MSVC最佳化模式

#!bash
_a$ = 8                         ; size = 4
_f   PROC

    mov     ecx, DWORD PTR _a$[esp-4]
    mov     eax, 954437177      ; 38e38e39H
    imul    ecx
    sar     edx, 1
    mov     eax, edx
    shr     eax, 31             ; 0000001fH
    add     eax, edx
    ret     0
_f   ENDP

這裡將除法最佳化為乘法。乘法運算要快得多。使用這種技巧可以得到更高效的程式碼。

在編譯器最佳化中,這也稱為“strength reduction”

GCC4.4.1甚至在沒有開啟最佳化模式的情況下生成了和在MSVC下開啟最佳化模式的生成的幾乎一樣的程式碼。

清單14.3 GCC 4.4.1 非最佳化模式

#!bash
        public f
f       procnear
arg_0   = dword ptr 8

        push    ebp
        mov     ebp, esp
        mov     ecx, [ebp+arg_0]
        mov     edx, 954437177 ; 38E38E39h
        mov     eax, ecx
        imul    edx
        sar     edx, 1
        mov     eax, ecx
        sar     eax, 1Fh
        mov     ecx, edx
        sub     ecx, eax
        mov     eax, ecx
        pop     ebp
        retn
f       endp

14.2 ARM

ARM處理器,就像其他的“純”RISC處理器一樣,缺少除法指令,缺少32位常數乘法的單條指令。利用一個技巧,透過加法,減法,移位是可以實現除法的。 這裡有一個32位數被10(20,3.3常量除法)除的例子,輸出商和餘數。

#!bash
; takes argument in a1
; returns quotient in a1, remainder in a2
; cycles could be saved if only divide or remainder is required
    SUB     a2, a1, #10         ; keep (x-10) for later
    SUB     a1, a1, a1, lsr #2
    ADD     a1, a1, a1, lsr #4
    ADD     a1, a1, a1, lsr #8
    ADD     a1, a1, a1, lsr #16
    MOV     a1, a1, lsr #3
    ADD     a3, a1, a1, asl #2
    SUBS    a2, a2, a3, asl #1  ; calc (x-10) - (x/10)*10
    ADDPL   a1, a1, #1          ; fix-up quotient
    ADDMI   a2, a2, #10         ; fix-up remainder
    MOV     pc, lr

14.2.1 Xcode最佳化模式(LLVM)+ARM模式

#!bash
__text:00002C58 39 1E 08 E3 E3 18 43 E3     MOV     R1, 0x38E38E39
__text:00002C60 10 F1 50 E7                 SMMUL   R0, R0, R1
__text:00002C64 C0 10 A0 E1                 MOV     R1, R0,ASR#1
__text:00002C68 A0 0F 81 E0                 ADD     R0, R1, R0,LSR#31
__text:00002C6C 1E FF 2F E1                 BX      LR

執行原理

這裡的程式碼和最佳化模式的MSVC和GCC生成的基本相同。顯然,LLVM在產生常量上使用相同的演算法。

善於觀察的讀者可能會問,MOV指令是如何將32位數值寫入暫存器中的,因為這在ARM模式下是不可能的。實際上是可能的,但是,就像我們看到的,與標準指令每條有四個位元組不同的是,這裡的每條指令有8個位元組,其實這是兩條指令。第一條指令將值0x8E39裝入暫存器的低十六位,第二條指令是MOVT,它將0x383E裝入暫存器的高16位。IDA知道這些順序,並且為了精簡緊湊,將它精簡轉換成一條虛擬碼。

SMMUL (Signed Most Significant Word Multiply)實現兩個32位有符號數的乘法,並且將高32位的部分放在r0中,棄掉結果的低32位部分。

“MOV R1,R0,ASR#1“指令算數右移一位。
“ADD R0,R1,LSR#31” R0=R1+R0>>32

事實上,在ARM模式下,並沒有單獨的移位指令。相反,像(MOV,ADD,SUB,RSB)3 這樣的資料處理指令,第二個運算元需要被移位。ASR表示算數右移,LSR表示邏輯右移。

14.2.2 最佳化 Xcode(LLVM)+ths-

相關文章