<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/2046

            第11章


            選擇結構switch()/case/default

            11.1 一些例子

            #!bash
            void f (int a)
            {
                switch (a)
                {
                    case 0: printf ("zero
            "); break;
                    case 1: printf ("one
            "); break;
                    case 2: printf ("two
            "); break;
                    default: printf ("something unknown
            "); break;
                };
            };
            

            11.1.1 X86

            反匯編結果如下(MSVC 2010):

            清單11.1: MSVC 2010

            #!bash
            tv64 = -4       ; size = 4
            _a$ = 8         ; size = 4
            _f  PROC
                push    ebp
                mov     ebp, esp
                push    ecx
                mov     eax, DWORD PTR _a$[ebp]
                mov     DWORD PTR tv64[ebp], eax
                cmp     DWORD PTR tv64[ebp], 0
                je      SHORT [email protected]
                cmp     DWORD PTR tv64[ebp], 1
                je      SHORT [email protected]
                cmp     DWORD PTR tv64[ebp], 2
                je      SHORT [email protected]
                jmp     SHORT [email protected]
            [email protected]:
                push    OFFSET $SG739 ; ’zero’, 0aH, 00H
                call    _printf
                add     esp, 4
                jmp     SHORT [email protected]
            [email protected]:
                push    OFFSET $SG741 ; ’one’, 0aH, 00H
                call    _printf
                add     esp, 4
                jmp     SHORT [email protected]
            [email protected]:
                push    OFFSET $SG743 ; ’two’, 0aH, 00H
                call    _printf
                add     esp, 4
                jmp     SHORT [email protected]
            [email protected]:
                push    OFFSET $SG745 ; ’something unknown’, 0aH, 00H
                call    _printf
                add     esp, 4
            [email protected]:
                mov     esp, ebp
                pop     ebp
                ret     0
            _f    ENDP
            

            輸出函數的switch中有一些case選擇分支,事實上,它是和下面這個形式等價的:

            #!cpp
            void f (int a)
            {
                if (a==0)
                    printf ("zero
            ");
                else if (a==1)
                    printf ("one
            ");
                else if (a==2)
                    printf ("two
            ");
                else
                    printf ("something unknown
            ");
            };
            

            當switch()中有一些case分支時,我們可以看到此類代碼,雖然不能確定,但是,事實上switch()在機器碼級別上就是對if()的封裝。這也就是說,switch()其實只是對有一大堆類似條件判斷的if()的一個語法糖。

            在生成代碼時,除了編譯器把輸入變量移動到一個臨時本地變量tv64中之外,這塊代碼對我們來說并無新意。

            如果是在GCC 4.4.1下編譯同樣的代碼,我們得到的結果也幾乎一樣,即使你打開了最高優化(-O3)也是如此。

            讓我們在微軟VC編譯器中打開/Ox優化選項: cl 1.c /Fa1.asm /Ox

            清單11.2: MSVC

            #!bash
            _a$ = 8                 ; size = 4
            _f  PROC
                mov     eax, DWORD PTR _a$[esp-4]
                sub     eax, 0
                je      SHORT [email protected]
                sub     eax, 1
                je      SHORT [email protected]
                sub     eax, 1
                je      SHORT [email protected]
                mov     DWORD PTR _a$[esp-4], OFFSET $SG791 ; ’something unknown’, 0aH, 00H
                jmp     _printf
            [email protected]:
                mov     DWORD PTR _a$[esp-4], OFFSET $SG789 ; ’two’, 0aH, 00H
                jmp     _printf
            [email protected]:
                mov     DWORD PTR _a$[esp-4], OFFSET $SG787 ; ’one’, 0aH, 00H
                jmp     _printf
            [email protected]:
                mov     DWORD PTR _a$[esp-4], OFFSET $SG785 ; ’zero’, 0aH, 00H
                jmp     _printf
            _f ENDP
            

            我們可以看到瀏覽器做了更多的難以閱讀的優化(Dirty hacks)。

            首先,變量的值會被放入EAX,接著EAX減0。聽起來這很奇怪,但它之后是需要檢查先前EAX寄存器的值是否為0的,如果是,那么程序會設置上零標志位ZF(這也表示了減去0之后,結果依然是0),第一個條件跳轉語句JE(Jump if Equal 或者同義詞 JZ - Jump if Zero)會因此觸發跳轉。如果這個條件不滿足,JE沒有跳轉的話,輸入值將減去1,之后就和之前的一樣了,如果哪一次值是0,那么JE就會觸發,從而跳轉到對應的處理語句上。

            (譯注:SUB操作會重置零標志位ZF,但是MOV不會設置標志位,而JE將只有在ZF標志位設置之后才會跳轉。如果需要基于EAX的值來做JE跳轉的話,是需要用這個方法設置標志位的)。

            并且,如果沒有JE語句被觸發,最終,printf()函數將收到“something unknown”的參數。

            其次:我們看到了一些不尋常的東西——字符串指針被放在了變量里,然后printf()并沒有通過CALL,而是通過JMP來調用的。 這個可以很簡單的解釋清楚,調用者把參數壓棧,然后通過CALL調用函數。CALL通過把返回地址壓棧,然后做無條件跳轉來跳到我們的函數地址。我們的函數在執行時,不管在任何時候都有以下的棧結構(因為它沒有任何移動棧指針的語句):

            · ESP —— 指向返回地址
            · ESP+4 —— 指向變量a (也即參數)
            

            另一方面,當我們這兒調用printf()函數的時候,它也需要有與我們這個函數相同的棧結構,不同之處只在于printf()的第一個參數是指向一個字符串的。 這也就是你之前看到的我們的代碼所做的事情。

            我們的代碼把第一個參數的地址替換了,然后跳轉到printf(),就像第一個沒有調用我們的函數f()而是先調用了printf()一樣。 printf()把一串字符輸出到stdout 中,然后執行RET語句, 這一句會從棧上彈出返回地址,因此,此時控制流會返回到調用f()的函數上,而不是f()上。

            這一切之所以能發生,是因為printf()在f()的末尾。在一些情況下,這有些類似于longjmp()函數。當然,這一切只是為了提高執行速度。

            ARM編譯器也有類似的優化,請見5.3.2節“帶有多個參數的printf()函數調用”。

            11.1.2 ARM: 優化后的 Keil + ARM 模式

            #!bash
            .text:0000014C             f1
            .text:0000014C 00 00 50 E3          CMP R0, #0
            .text:00000150 13 0E 8F 02          ADREQ R0, aZero     ; "zero
            "
            .text:00000154 05 00 00 0A          BEQ loc_170
            .text:00000158 01 00 50 E3          CMP R0, #1
            .text:0000015C 4B 0F 8F 02          ADREQ R0, aOne      ; "one
            "
            .text:00000160 02 00 00 0A          BEQ loc_170
            .text:00000164 02 00 50 E3          CMP R0, #2
            .text:00000168 4A 0F 8F 12          ADRNE R0, aSomethingUnkno ; "something unknown
            "
            .text:0000016C 4E 0F 8F 02          ADREQ R0, aTwo      ; "two
            "
            .text:00000170
            .text:00000170                      loc_170             ; CODE XREF: f1+8
            .text:00000170                                          ; f1+14
            .text:00000170 78 18 00 EA          B __2printf
            

            我們再一次看看這個代碼,我們不能確定的說這就是源代碼里面的switch()或者說它是if()的封裝。

            但是,我們可以看到這里它也在試圖預測指令(像是ADREQ(相等)),這里它會在R0=0的情況下觸發,并且字符串“zero

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线