<pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
    <pre id="vvttv"></pre>

      <p id="vvttv"></p>

          <p id="vvttv"></p>

                <p id="vvttv"></p>

                <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                  <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                    <p id="vvttv"></p>

                    原文地址: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

                      <pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
                      <pre id="vvttv"></pre>

                        <p id="vvttv"></p>

                            <p id="vvttv"></p>

                                  <p id="vvttv"></p>

                                  <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                                    <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                                      <p id="vvttv"></p>

                                      这里只有精品视频