<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # Chapter 10 條件跳轉 現在我們來了解條件跳轉。 ``` #!cpp #include <stdio.h> void f_signed (int a, int b) { if (a>b) printf ("a>b "); if (a==b) printf ("a==b "); if (a<b) printf ("a<b "); }; void f_unsigned (unsigned int a, unsigned int b) { if (a>b) printf ("a>b "); if (a==b) printf ("a==b "); if (a<b) printf ("a<b "); }; int main() { f_signed(1, 2); f_unsigned(1, 2); return 0; }; ``` ## 10.1 x86 ### 10.1.1 x86 + MSVC f_signed() 函數: Listing 10.1: 非優化MSVC 2010 ``` #!bash _a$ = 8 _b$ = 12 _f_signed PROC push ebp mov ebp, esp mov eax, DWORD PTR _a$[ebp] cmp eax, DWORD PTR _b$[ebp] jle SHORT $LN3@f_signed push OFFSET $SG737 ; ’a>b’ call _printf add esp, 4 $LN3@f_signed: mov ecx, DWORD PTR _a$[ebp] cmp ecx, DWORD PTR _b$[ebp] jne SHORT $LN2@f_signed push OFFSET $SG739 ; ’a==b’ call _printf add esp, 4 $LN2@f_signed: mov edx, DWORD PTR _a$[ebp] cmp edx, DWORD PTR _b$[ebp] jge SHORT $LN4@f_signed push OFFSET $SG741 ; ’a<b’ call _printf add esp, 4 $LN4@f_signed: pop ebp ret 0 _f_signed ENDP ``` 第一個指令JLE意味如果小于等于則跳轉。換句話說,第二個操作數大于或者等于第一個操作數,控制流將傳遞到指定地址或者標簽。否則(第二個操作數小于第一個操作數)第一個printf()將被調用。第二個檢測JNE:如果不相等則跳轉。如果兩個操作數相等控制流則不變。第三個檢測JGE:大于等于跳轉,當第一個操作數大于或者等于第二個操作數時跳轉。如果三種情況都沒有發生則無printf()被調用,事實上,如果沒有特殊干預,這種情況幾乎不會發生。 f_unsigned()函數類似,只是JBE和JAE替代了JLE和JGE,我們來看f_unsigned()函數 Listing 10.2: GCC ``` #!bash _a$ = 8 ; size = 4 _b$ = 12 ; size = 4 _f_unsigned PROC push ebp mov ebp, esp mov eax, DWORD PTR _a$[ebp] cmp eax, DWORD PTR _b$[ebp] jbe SHORT $LN3@f_unsigned push OFFSET $SG2761 ; ’a>b’ call _printf add esp, 4 $LN3@f_unsigned: mov ecx, DWORD PTR _a$[ebp] cmp ecx, DWORD PTR _b$[ebp] jne SHORT $LN2@f_unsigned push OFFSET $SG2763 ; ’a==b’ call _printf add esp, 4 $LN2@f_unsigned: mov edx, DWORD PTR _a$[ebp] cmp edx, DWORD PTR _b$[ebp] jae SHORT $LN4@f_unsigned push OFFSET $SG2765 ; ’a<b’ call _printf add esp, 4 $LN4@f_unsigned: pop ebp ret 0 _f_unsigned ENDP ``` 幾乎是相同的,不同的是:JBE-小于等于跳轉和JAE-大于等于跳轉。這些指令(JA/JAE/JBE/JBE)不同于JG/JGE/JL/JLE,它們使用無符號值。 我們也可以看到有符號值的表示(35)。因此我們看JG/JL代替JA/JBE的用法或者相反,我們幾乎可以確定變量的有符號或者無符號類型。 main()函數沒有什么新的內容: Listing 10.3: main() ``` #!bash _main PROC push ebp mov ebp, esp push 2 push 1 call _f_signed add esp, 8 push 2 push 1 call _f_unsigned add esp, 8 xor eax, eax pop ebp ret 0 _main ENDP ``` ### 10.1.2 x86 + MSVC + OllyDbg 我們在OD里允許例子來查看標志寄存器。我們從f_unsigned()函數開始。CMP執行了三次,每次的參數都相同,所以標志位也相同。 第一次比較的結果:fig. 10.1.標志位:C=1, P=1, A=1, Z=0, S=1, T=0, D=0, O=0.標志位名稱為OD對其的簡稱。 當CF=1 or ZF=1時JBE將被觸發,此時將跳轉。 接下來的條件跳轉:fig. 10.2.當ZF=0(zero flag)時JNZ則被觸發 第三個條件跳轉:fig. 10.3.我們可以發現[14](img/2014101711184580126.png)當CF=0 (carry flag)時,JNB將被觸發。在該例中條件不為真,所以第三個printf()將被執行。 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec35db496.png) Figure 10.1: OllyDbg: f_unsigned(): 第一個條件跳轉 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec35f3cda.png) Figure 10.2: OllyDbg: f_unsigned(): 第二個條件跳轉 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec361adb8.png) Figure 10.3: OllyDbg: f_unsigned(): 第三個條件跳轉 現在我們在OD中看f_signed()函數使用有符號值。 可以看到標志寄存器:C=1, P=1, A=1, Z=0, S=1, T=0, D=0, O=0. 第一種條件跳轉JLE將被觸發fig. 10.4.我們可以發現[14](img/2014101711184580126.png),當ZF=1 or SF≠OF。該例中SF≠OF,所以跳轉將被觸發。 下一個條件跳轉將被觸發:如果ZF=0 (zero flag): fig. 10.5. 第三個條件跳轉將不會被觸發,因為僅有SF=OF,該例中不為真: fig. 10.6. ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec3635582.png) Figure 10.4: OllyDbg: f_signed(): 第一個條件跳轉 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec3651c94.png) Figure 10.5: OllyDbg: f_signed(): 第二個條件跳轉 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec366ce9d.png) Figure 10.6: OllyDbg: f_signed(): 第三個條件跳轉 ### 10.1.3 x86 + MSVC + Hiew 我們可以修改這個可執行文件,使其無論輸入的什么值f_unsigned()函數都會打印“a==b”。 在Hiew中查看:fig. 10.7. 我們要完成以下3個任務: ``` 1\. 使第一個跳轉一直被觸發; 2\. 使第二個跳轉從不被觸發; 3\. 使第三個跳轉一直被觸發。 ``` 我們需要使代碼流進入第二個printf(),這樣才一直打印“a==b”。 三個指令(或字節)應該被修改: ``` 1\. 第一個跳轉修改為JMP,但跳轉偏移值不變。 2\. 第二個跳轉有時可能被觸發,我們修改跳轉偏移值為0后,無論何種情況,程序總是跳向下一條指令。跳轉地址等于跳轉偏移值加上下一條指令地址,當跳轉偏移值為0時,跳轉地址就為下一條指令地址,所以無論如何下一條指令總被執行。 3\. 第三個跳轉我們也修改為JMP,這樣跳轉總被觸發。 ``` 修改后:fig. 10.8. 如果忘了這些跳轉,printf()可能會被多次調用,這種行為可能是我們不需要的。 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec368356b.png) Figure 10.7: Hiew: f_unsigned() 函數 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec36a5de4.png) Figure 10.8: Hiew:我們修改 f_unsigned() 函數 ### 10.1.4 Non-optimizing GCC GCC 4.4.1非優化狀態產生的代碼幾乎一樣,只是用puts() (2.3.3) 替代 printf()。 ### 10.1.5 Optimizing GCC 細心的讀者可能會問,為什么要多次執行CMP,如果標志寄存器每次都相同呢?可能MSVC不會做這樣的優化,但是GCC 4.8.1可以做這樣的深度優化: Listing 10.4: GCC 4.8.1 f_signed() ``` #!bash f_signed: mov eax, DWORD PTR [esp+8] cmp DWORD PTR [esp+4], eax jg .L6 je .L7 jge .L1 mov DWORD PTR [esp+4], OFFSET FLAT:.LC2 ; "a<b" jmp puts .L6: mov DWORD PTR [esp+4], OFFSET FLAT:.LC0 ; "a>b" jmp puts .L1: rep ret .L7: mov DWORD PTR [esp+4], OFFSET FLAT:.LC1 ; "a==b" jmp puts ``` 我們可以看到JMP puts替代了CALL puts/RETN。稍后我們介紹這種情況11.1.1.。 不用說,這種類型的x86代碼是很少見的。MSVC2012似乎不會這樣做。其他情況下,匯編程序能意識到此類使用。如果你在其它地方看到此類代碼,更可能是手工構造的。 f_unsigned()函數代碼: Listing 10.5: GCC 4.8.1 f_unsigned() ``` #!bash f_unsigned: push esi push ebx sub esp, 20 mov esi, DWORD PTR [esp+32] mov ebx, DWORD PTR [esp+36] cmp esi, ebx ja .L13 cmp esi, ebx ; instruction may be removed je .L14 .L10: jb .L15 add esp, 20 pop ebx pop esi ret .L15: mov DWORD PTR [esp+32], OFFSET FLAT:.LC2 ; "a<b" add esp, 20 pop ebx pop esi jmp puts .L13: mov DWORD PTR [esp], OFFSET FLAT:.LC0 ; "a>b" call puts cmp esi, ebx jne .L10 .L14: mov DWORD PTR [esp+32], OFFSET FLAT:.LC1 ; "a==b" add esp, 20 pop ebx pop esi jmp puts ``` 因此,GCC 4.8.1的優化算法并不總是完美的。 ## 10.2 ARM ### 10.2.1 Keil + ARM mode優化后 Listing 10.6: Optimizing Keil + ARM mode ``` #!bash .text:000000B8 EXPORT f_signed .text:000000B8 f_signed ; CODE XREF: main+C .text:000000B8 70 40 2D E9 STMFD SP!, {R4-R6,LR} .text:000000BC 01 40 A0 E1 MOV R4, R1 .text:000000C0 04 00 50 E1 CMP R0, R4 .text:000000C4 00 50 A0 E1 MOV R5, R0 .text:000000C8 1A 0E 8F C2 ADRGT R0, aAB ; "a>b " .text:000000CC A1 18 00 CB BLGT __2printf .text:000000D0 04 00 55 E1 CMP R5, R4 .text:000000D4 67 0F 8F 02 ADREQ R0, aAB_0 ; "a==b " .text:000000D8 9E 18 00 0B BLEQ __2printf .text:000000DC 04 00 55 E1 CMP R5, R4 .text:000000E0 70 80 BD A8 LDMGEFD SP!, {R4-R6,PC} .text:000000E4 70 40 BD E8 LDMFD SP!, {R4-R6,LR} .text:000000E8 19 0E 8F E2 ADR R0, aAB_1 ; "a<b " .text:000000EC 99 18 00 EA B __2printf .text:000000EC ; End of function f_signed ``` ARM下很多指令只有某些標志位被設置時才會被執行。比如做數值比較時。 舉個例子,ADD實施上是ADDAL,這里的AL是Always,即總被執行。判定謂詞是32位ARM指令的高4位(條件域)。無條件跳轉的B指令其實是有條件的,就行其它任何條件跳轉一樣,只是條件域為AL,這意味著總是被執行,忽略標志位。 ADRGT指令就像和ADR一樣,只是該指令前面為CMP指令,并且只有前面數值大于另一個數值時(Greater Than)時才被執行。 接下來的BLGT行為和BL一樣,只有比較結果符合條件才能出發(Greater Than)。ADRGT把字符串“a&gt;b ”的地址寫入R0,然后BLGT調用printf()。因此,這些指令都帶有GT后綴,只有當R0(a值)大于R4(b值)時指令才會被執行。 然后我們看ADREQ和BLEQ,這些指令動作和ADR及BL一樣,只有當兩個操作數對比后相等時才會被執行。這些指令前面是CMP(因為printf()調用可能會修改狀態標識)。 然后我們看LDMGEFD,該指令行為和LDMFD指令一樣1,僅僅當第一個值大于等于另一個值時(Greater Than),指令才會被執行。 “LDMGEFD SP!, {R4-R6,PC}”恢復寄存器并返回,只是當a&gt;=b時才被觸發,這樣之后函數才執行完成。但是如果a&lt;b,觸發條件不成立是將執行下一條指令LDMFD SP!, {R4-R6,LR},該指令保存R4-R6寄存器,使用LR而不是PC,函數并不返回。最后兩條指令是執行printf()(5.3.2)。 f_unsigned與此一樣只是使用對應的指令為ADRHI, BLHI及LDMCSFD,判斷謂詞(HI = Unsigned higher, CS = Carry Set (greater than or equal))請類比之前的說明,另外就是函數內部使用無符號數值。 我們來看一下main()函數: Listing 10.7: main() ``` #!bash .text:00000128 EXPORT main .text:00000128 main .text:00000128 10 40 2D E9 STMFD SP!, {R4,LR} .text:0000012C 02 10 A0 E3 MOV R1, #2 .text:00000130 01 00 A0 E3 MOV R0, #1 .text:00000134 DF FF FF EB BL f_signed .text:00000138 02 10 A0 E3 MOV R1, #2 .text:0000013C 01 00 A0 E3 MOV R0, #1 .text:00000140 EA FF FF EB BL f_unsigned .text:00000144 00 00 A0 E3 MOV R0, #0 .text:00000148 10 80 BD E8 LDMFD SP!, {R4,PC} .text:00000148 ; End of function main ``` 這就是ARM模式如何避免使用條件跳轉。 這樣做有什么好處呢?因為ARM使用精簡指令集(RISC)。簡言之,處理器流水線技術受到跳轉的影響,這也是分支預測重要的原因。程序使用的條件或者無條件跳轉越少越好,使用斷言指令可以減少條件跳轉的使用次數。 x86沒有這也的功能,通過使用CMP設置相應的標志位來觸發指令。 ### 10.2.2 Optimizing Keil + thumb mode Listing 10.8: Optimizing Keil + thumb mode ``` #!bash .text:00000072 f_signed ; CODE XREF: main+6 .text:00000072 70 B5 PUSH {R4-R6,LR} .text:00000074 0C 00 MOVS R4, R1 .text:00000076 05 00 MOVS R5, R0 .text:00000078 A0 42 CMP R0, R4 .text:0000007A 02 DD BLE loc_82 .text:0000007C A4 A0 ADR R0, aAB ; "a>b " .text:0000007E 06 F0 B7 F8 BL __2printf .text:00000082 .text:00000082 loc_82 ; CODE XREF: f_signed+8 .text:00000082 A5 42 CMP R5, R4 .text:00000084 02 D1 BNE loc_8C .text:00000086 A4 A0 ADR R0, aAB_0 ; "a==b " .text:00000088 06 F0 B2 F8 BL __2printf .text:0000008C .text:0000008C loc_8C ; CODE XREF: f_signed+12 .text:0000008C A5 42 CMP R5, R4 .text:0000008E 02 DA BGE locret_96 .text:00000090 A3 A0 ADR R0, aAB_1 ; "a<b " .text:00000092 06 F0 AD F8 BL __2printf .text:00000096 .text:00000096 locret_96 ; CODE XREF: f_signed+1C .text:00000096 70 BD POP {R4-R6,PC} .text:00000096 ; End of function f_signed ``` 僅僅Thumb模式下的B指令可能需要條件代碼輔助,所以thumb代碼看起來更普通一些。 BLE通常是條件跳轉小于或等于(Less than or Equal),BNE—不等于(Not Equal),BGE—大于或等于(Greater than or Equal)。 f_unsigned函數是同樣的,只是使用的指令用來處理無符號數值:BLS (Unsigned lower or same) 和BCS (Carry Set (Greater than or equal)).
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看