<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>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # chapter 15 用FPU工作 FPU--是一個主cpu被設計用來處理浮點數的設備。 過去它被稱為協處理器,放在CPU旁邊,看起來像可編程的計算器,在學習FPU之前學習堆棧機或forth語言是值得的。 有趣的是,在過去(80486cpu之前),協處理器是一個單獨的芯片,并不總是安裝在母版上,單獨購買和安裝也是可以的。 但從80486 DX CPU開始,FPU就被安裝在里面了。 FWAIT指令可能提醒我們一個事實--它將CPU轉換成等待模式,因此它可以一直等待直到FPU完成工作。另外一點是FPU指令操作碼從所謂的escape操作碼(D8..DF)開始,進入了FPU。 FPU有可以容納8個80字節的寄存器棧容量,每一個寄存器可以存儲一個IEEE 754格式的數字。 C/C++語言提供至少兩種浮點數類型,float(單精度,32位),double類型(雙精度,64位)。 GCC也支持多精度類型(擴展精度,80位),但是MSVC不支持。 在32位環境中,浮點數要求和int類型的位數相同,但是數值的表示法完全不同。 數值包括符號位,尾數(也叫做分數)和指數。 參數列表中有float和double類型的函數通過棧來獲得值,如果函數返回float或者double類型的值,那么返回值將放在ST(0)寄存器中--在FPU的棧頂。 ## 15.1 簡單實例 下面我們來研究一個簡單的例子 ``` #!bash double f (double a, double b) { return a/3.14 + b*4.1; } ``` ### 15.1.1 x86 在msvc2010中編譯 ``` #!bash CONST SEGMENT __real@4010666666666666 DQ 04010666666666666r ; 4.1 CONST ENDS CONST SEGMENT __real@40091eb851eb851f DQ 040091eb851eb851fr ; 3.14 CONST ENDS _TEXT SEGMENT _a$ = 8 ; size = 8 _b$ = 16 ; size = 8 _f PROC push ebp mov ebp, esp fld QWORD PTR _a$[ebp] ; current stack state: ST(0) = _a fdiv QWORD PTR __real@40091eb851eb851f ; current stack state: ST(0) = result of _a divided by 3.13 fld QWORD PTR _b$[ebp] ; current stack state: ST(0) = _b; ST(1) = result of _a divided by 3.13 fmul QWORD PTR __real@4010666666666666 ; current stack state: ST(0) = result of _b * 4.1; ST(1) = result of _a divided by 3.13 faddp ST(1), ST(0) ; current stack state: ST(0) = result of addition pop ebp ret 0 _f ENDP ``` FLD從棧中取8個字節并將這個數字放入ST(0)寄存器中,自動將它轉換成內部80位格式的擴展操作數。 FDIV除存儲在ST(0)中地址指向的數值 __real@40091eb851eb851f —3.14 就放在那里。 匯編語法丟失浮點數,因此,我們這里看到的是64位IEEE754編碼的16進制表示的3.14。 執行FDIV執行后,ST(0)將保存除法的結果。 另外,這里也有FDIVP指令,用ST(0)除ST(1),從棧中將將這些值拋出來,然后將結果壓棧。如果你懂forth語言,你會很快意識到這是堆棧機。 FLD指令將b的值壓入棧中之后,商放入ST(1)寄存器中,ST(0)中保存b的值。 接下來FMUL指令將來自ST(0)的b值和在__real@4010666666666666 (4.1 的值在那里)相乘,然后將結果放入ST(0)中。 最后,FADDP指令將棧頂的兩個值相加,將結果存儲在ST(1)寄存器中,然后從ST(1)中彈出,再放入ST(0)中。 這個函數必須返回ST(0)寄存器中的值,因此,在執行FADDP命令后,沒有其他額外的的指令了需要執行了。 GCC 4.4.1(選項03)生成基本同樣的代碼,有小小的不同之處。 不同之處在于,首先,3.14被壓入棧中(進入ST(0)),然后arg_0的值除以ST(0)寄存器中的值 FDIVR 意味著逆向除法 被除數和除數交換。 因為乘法兩個乘數可交換,所以沒有這樣的指令,我們只有FMUL而沒有逆乘。 FADDP也是將兩個值相加,其中一個來自棧。然后ST(0)保存它們的和。 這段反編譯代碼的碎片是由IDA產生的,ST(0)簡稱為ST。 ### 15.1.2 ARM: Xcode優化模式(LLVM)+ARM 模式 直到ARM有標準化的浮點數支持后,幾家處理器廠商才將其加入到他們自己指令擴展中。然后,VFP(向量浮點運算單元)標準化了。 與x86相比,一個重要的不同是,在x86中使用fpu棧工作,而在ARM中,這里沒有棧,你只能使用寄存器。 ``` #!bash f VLDR D16, =3.14 VMOV D17, R0, R1 ; load a VMOV D18, R2, R3 ; load b VDIV.F64 D16, D17, D16 ; a/3.14 VLDR D17, =4.1 VMUL.F64 D17, D18, D17 ; b*4.1 VADD.F64 D16, D17, D16 ; + VMOV R0, R1, D16 BX LR dbl_2C98 DCFD 3.14 ; DATA XREF: f dbl_2CA0 DCFD 4.1 ; DATA XREF: f+10 ``` 可以看到,這里我們使用了新的寄存器,并以D開頭。這些是64位寄存器,有32個,他們既可以用作浮點數(double)運算也可以用作SIMD(在ARM中稱為NEON)。 它們同時也可以作為32個32位的S寄存器使用,它們被用于單精度操作浮點數(float)運算。 記住它們很容易:D系列寄存器用于雙精度數字,S寄存器用于單精度數字,記住Double和Single的首字母就可以了。 兩個常量(3.14和4.1)都是以IEEE 754的形式存儲在內存中。 VLDR和VMOV指令,容易推斷,類似LDR和MOV指令,但是它們使用D系列寄存器,需要注意的就是這些指令不就之后也會展現出,就像D系列寄存器一樣,不僅可以進行浮點數運算而且也可以用于SIMD(NEON)運算,參數傳遞的方式仍舊是通過R系列寄存器傳遞,但是每個具有雙精度的數值有64位,所以為了便于傳遞需要兩個寄存器。 ``` “VMOV D17,R0,R1”在最開始,將兩個來自R0和R1的32位的值組成一個64位的值并且將它保存在D17中。 “VMOV R0,R1,D16”是一個逆操作,D16中的值放回R0,R1中。 ``` VDIV,VMUL,VADD都是用于浮點數的處理計算的指令,分別為除法指令,乘法指令,加法指令。 thumb-2的代碼也是相同的。 ### 15.1.3 ARM:優化 keil+thumb 模式 ``` #!bash f PUSH {R3-R7,LR} MOVS R7, R2 MOVS R4, R3 MOVS R5, R0 MOVS R6, R1 LDR R2, =0x66666666 LDR R3, =0x40106666 MOVS R0, R7 MOVS R1, R4 BL __aeabi_dmul MOVS R7, R0 MOVS R4, R1 LDR R2, =0x51EB851F LDR R3, =0x40091EB8 MOVS R0, R5 MOVS R1, R6 BL __aeabi_ddiv MOVS R2, R7 MOVS R3, R4 BL __aeabi_dadd POP {R3-R7,PC} dword_364 DCD 0x66666666 ; DATA XREF: f+A dword_368 DCD 0x40106666 ; DATA XREF: f+C dword_36C DCD 0x51EB851F ; DATA XREF: f+1A dword_370 DCD 0x40091EB8 ; DATA XREF: f+1C ``` keil為處理器生成的代碼不支持FPU和NEON。因此,雙精度浮點數通過通用R寄存器來傳遞雙精度數字,與FPU指令不同的是,通過對庫函數調用(如__aeabi_dmul, __aeabi_ddiv, __aeabi_dadd)用來實現乘法,除法,浮點數加法。當然,這比FPU協處理器慢,但總比沒有強。 另外,在x86的世界中,當協處理器少而貴并且只安裝昂貴的計算機上時,在FPU模擬庫非常受歡迎。 在ARM的世界中,FPU處理器模擬稱為soft float 或者armel,用協處理器的FPU指令的稱為hard float和armhf。 舉個例子,樹莓派的linux內核用兩種變量編譯。如果是soft float,參數就會通過R系列寄存器編碼,hard float則會通過D系列寄存器。 這就是不讓你使用例子中來自armel編碼的armhf庫原因,反之亦然。那也是linux分區必須根據調用慣例編譯的原因。 ## 15.2 通過參數通過浮點數 ``` #!bash #include <math.h> #include <stdio.h> int main () { printf ("32.01 ^ 1.54 = %lf ", pow (32.01,1.54)); return 0; } ``` ### 15.2.1 x86 讓我們來看看在(msvc2010)中得到的東西 清單15.3 :MSVC 2010 ``` #!bash CONST SEGMENT __real@40400147ae147ae1 DQ 040400147ae147ae1r ; 32.01 __real@3ff8a3d70a3d70a4 DQ 03ff8a3d70a3d70a4r ; 1.54 CONST ENDS _main PROC push ebp mov ebp, esp sub esp, 8 ; allocate place for the first variable fld QWORD PTR __real@3ff8a3d70a3d70a4 fstp QWORD PTR [esp] sub esp, 8 ; allocate place for the second variable fld QWORD PTR __real@40400147ae147ae1 fstp QWORD PTR [esp] call _pow add esp, 8 ; "return back" place of one variable. ; in local stack here 8 bytes still reserved for us. ; result now in ST(0) fstp QWORD PTR [esp] ; move result from ST(0) to local stack for printf() push OFFSET $SG2651 call _printf add esp, 12 xor eax, eax pop ebp ret 0 _main ENDP ``` FLD和FSTP讀取FPU的棧中的變量。pow()從FPU棧中拿出兩個值然后將結果返回到ST(0)寄存器中。printf()函數從本地棧中取出8字節并且將他們翻譯為雙精度變量。 ### 15.2.2 ARM+Non-optimizing Xcode(LLVM)+thumb-2模式 ``` #!bash _main var_C = -0xC PUSH {R7,LR} MOV R7, SP SUB SP, SP, #4 VLDR D16, =32.01 VMOV R0, R1, D16 VLDR D16, =1.54 VMOV R2, R3, D16 BLX _pow VMOV D16, R0, R1 MOV R0, 0xFC1 ; "32.01 ^ 1.54 = %lf " ADD R0, PC VMOV R1, R2, D16 BLX _printf MOVS R1, 0 STR R0, [SP,#0xC+var_C] MOV R0, R1 ADD SP, SP, #4 POP {R7,PC} dbl_2F90 DCFD 32.01 ; DATA XREF: _main+6 dbl_2F98 DCFD 1.54 ; DATA XREF: _main+E ``` 就像我以前寫的一樣,64位的浮點數是成對傳遞給R系列寄存器的。這樣的代碼是冗陳的(當然是因為優化選項關掉了),因為,事實上直接從R系列寄存器傳遞值,不借助D系列寄存器是可能的。 因此我們可以看到,_pow 將第一個參數放入R0和R1中,第二個參數放入R2和R3中。函數結果放入R0和R1中。_pwn的結果先放入了D16中,然后再放入R1和R2中,然后printf函數將取走這個值。 ### 15.2.3 ARM+非優化模式keil+ARM模式 ``` #!bash _main STMFD SP!, {R4-R6,LR} LDR R2, =0xA3D70A4 ; y LDR R3, =0x3FF8A3D7 LDR R0, =0xAE147AE1 ; x LDR R1, =0x40400147 BL pow MOV R4, R0 MOV R2, R4 MOV R3, R1 ADR R0, a32_011_54Lf ; "32.01 ^ 1.54 = %lf " BL __2printf MOV R0, #0 LDMFD SP!, {R4-R6,PC} y DCD 0xA3D70A4 ; DATA XREF: _main+4 dword_520 DCD 0x3FF8A3D7 ; DATA XREF: _main+8 ; double x x DCD 0xAE147AE1 ; DATA XREF: _main+C dword_528 DCD 0x40400147 ; DATA XREF: _main+10 a32_011_54Lf DCB "32.01 ^ 1.54 = %lf",0xA,0 ; DATA XREF: _main+24 ``` D系列寄存器在這里不使用,只成對地使用R系列的寄存器 ## 15.3 對比實例 試試這個 ``` #!bash double d_max (double a, double b) { if (a>b) return a; return b; }; ``` ### 15.3.1 x86 盡管這個函數很簡單,但是理解它的工作原理并不容易。 MSVC 2010生成 ``` #!bash PUBLIC _d_max _TEXT SEGMENT _a$ = 8 ; size = 8 _b$ = 16 ; size = 8 _d_max PROC push ebp mov ebp, esp fld QWORD PTR _b$[ebp] ; current stack state: ST(0) = _b ; compare _b (ST(0)) and _a, and pop register fcomp QWORD PTR _a$[ebp] ; stack is empty here fnstsw ax test ah, 5 jp SHORT $LN1@d_max ; we are here only if a>b fld QWORD PTR _a$[ebp] jmp SHORT $LN2@d_max $LN1@d_max: fld QWORD PTR _b$[ebp] $LN2@d_max: pop ebp ret 0 _d_max ENDP ``` 因此,FLD將_b中的值裝入ST(0)寄存器中。 FCOMP對比ST(0)寄存器和_a值,設置FPU狀態字寄存器中的C3/C2/C0位,這是一個反應FPU當前狀態的16位寄存器。 C3/C2/C0位被設置后,不幸的是,IntelP6之前的CPU沒有任何檢查這些標志位的條件轉移指令。可能是歷史的原因(FPU曾經是單獨的一塊芯片)。從Intel P6開始,現在的CPU擁有FCOMI/FCOMIP/FUCOMI/FUCOMIP指令,這些指令功能相同,但會改變CPU的ZF/PF/CF標志位。 當標志位被設好后,FCOMP指令從棧中彈出一個變量。這就是和FCOM的不同之處,FCOM只對比值,讓棧保持同樣的狀態。 FNSTSW講FPU狀態字寄存器的內容拷貝到AX中,C3/C2/C0放置在14/10/8位中,它們會在AX寄存器中相應的位置上,并且都放在AX的高位部分—AH。 ``` 如果 b>a 在我們的例子中,C3/C2/C0位會被設置為:0,0,0 如果 a>b 標志位被設為:0,0,1 如果 a=b 標識位被設為:1,0,0 ``` 執行了 test sh,5 之后,C3和C1的標志位被設為0,但是第0位和第2位(在AH寄存器中)C0和C2位會保留。 下面我們談談奇偶位標志。Another notable epoch rudiment: 一個常見的原因是測試奇偶位標志事實上與奇偶沒有任何關系。FPU有4個條件標志(C0到C3),但是它們不能被直接測試,必須先拷貝到標志位寄存器中,在這個時候,C0放在進位標志中,C2放在奇偶位標志中,C3放在0標志位中。當例子中不可比較的浮點數(NaN或者其他不支持的格式)使用FUCOM指令進行比較的時候,會設置C2標志位。 如果一個數字是奇數這個標志就會被設置為1。如果是偶數就會被設置為0. 因此,PF標志會被設置為1如果C0和C2都被設置為0或者都被設置為1。然后jp跳轉就會實現。如果我們recall valuesof C3/C2/C0,我們將會發現條件跳轉jp可能會在兩種情況下觸發:b&gt;a或者a==b(C3位這里不再考慮,因為在執行test sh,5指令之后已經被清零了) 之后就簡單了。如果條件跳轉被觸發,FLD會將_b的值放入ST(0)寄存器中,如果沒有被觸發,_a變量的值會被加載 但是還沒有結束。 ### 15.3.2 下面我們用msvc2010優化模式來編譯它/0x ``` #!bash _a$ = 8 ; size = 8 _b$ = 16 ; size = 8 _d_max PROC fld QWORD PTR _b$[esp-4] fld QWORD PTR _a$[esp-4] ; current stack state: ST(0) = _a, ST(1) = _b fcom ST(1) ; compare _a and ST(1) = (_b) fnstsw ax test ah, 65 ; 00000041H jne SHORT $LN5@d_max fstp ST(1) ; copy ST(0) to ST(1) and pop register, leave (_a) on top ; current stack state: ST(0) = _a ret 0 $LN5@d_max: fstp ST(0) ; copy ST(0) to ST(0) and pop register, leave (_b) on top ; current stack state: ST(0) = _b ret 0 _d_max ENDP ``` FCOM區別于FCOMP在某種程度上是它只比較值然后并不改變FPU的狀態。和之前的例子不同的是,操作數是逆序的。這也是C3/C2/C0中的比較結果是不同的原因。 ``` 如果 a>b 在我們的例子中,C3/C3/C0會被設為0,0,0 如果 b>a 標志位被設為:0,0,1 如果 a=b 標志位被設為:1,0,0 ``` 可以這么說,test ah,65指令只保留兩位—C3和C0.如果a&gt;b那么兩者都被設為0:在那種情況下,JNE跳轉不會被觸發。 FSTP ST(1)接下來—這個指令會復制ST(0)中的值放入操作數中,然后從FPU棧中跑出一個值。 換句話說,這個這個指令將ST(0)中的值復制到ST(1)中。然后,_a的兩個值現在在棧定。之后,一個值被拋出。之后,ST(0)會包含_a然后函數執行完畢。 條件跳轉JNE在兩種情況下觸發:b&gt;a或者a==b。ST(0)中的值拷貝到ST(0)中,就像nop指令一樣,然后一個值從棧中拋出,然后棧頂(ST(0))會包含ST(1)之前的包含的內容(就是_b)。函數執行完畢。這條指令在這里使用的原因可能是FPU沒有從棧中拋出值的指令并且沒有地方存儲。 但是,還沒有結束。 ### 15.3.3 GCC 4.4.1 ``` #!bash d_max proc near b =qword ptr -10h a =qword ptr -8 a_first_half = dword ptr 8 a_second_half = dword ptr 0Ch b_first_half = dword ptr 10h b_second_half = dword ptr 14h push ebp mov ebp, esp sub esp, 10h ; put a and b to local stack: mov eax, [ebp+a_first_half] mov dword ptr [ebp+a], eax mov eax, [ebp+a_second_half] mov dword ptr [ebp+a+4], eax mov eax, [ebp+b_first_half] mov dword ptr [ebp+b], eax mov eax, [ebp+b_second_half] mov dword ptr [ebp+b+4], eax ; load a and b to FPU stack: fld [ebp+a] fld [ebp+b] ; current stack state: ST(0) - b; ST(1) - a fxch st(1) ; this instruction swapping ST(1) and ST(0) ; current stack state: ST(0) - a; ST(1) - b fucompp ; compare a and b and pop two values from stack, i.e., a and b fnstsw ax ; store FPU status to AX sahf ; load SF, ZF, AF, PF, and CF flags state from AH setnbe al ; store 1 to AL if CF=0 and ZF=0 test al, al ; AL==0 ? jz short loc_8048453 ; yes fld [ebp+a] jmp short locret_8048456 loc_8048453: fld [ebp+b] locret_8048456: leave retn d_max endp ``` FUCOMMP 類似FCOM指令,但是兩個值都從棧中取,并且處理NaN(非數)有一些不同之處。 更多關于”非數“的: FPU能夠處理特殊的值比如非數字或者NaNs。它們是無窮大的,除零的結果等等。NaN可以是“quiet”并且“signaling”的。但是如果進行任何有關“signaling”的操作將會產生異常。 FCOM會產生異常如果操作數中有NaN。FUCOM只在操作數有signaling NaN (SNaN)的情況下產生異常。 接下來的指令是SANF—這條指令很少用,它不使用FPU。AH的8位以這樣的順序放入CPU標志位的低8位中:SF:ZF:-:AF:-:PF:-:CF&lt;-AH。 FNSTSW將C3/C2/C0位放入AH寄存器的第6,2,0位中。 換句話說,fnstsw ax/sahf指令對是將C3/C2/C0移入CPU標志位ZF,PF,CF中。 現在我們來回顧一下,C3/C2/C0位會被設置成什么。 ``` 在我們的例子中,如果a比b大,那么C3/C2/C0位會被設為0,0,0 如果a比b小,這些位會被設為0,0,1 如果a=b,這些位會被設為1,0,0 ``` 換句話說,在 FUCOMPP/FNSTSW/SAHF指令后,我們的CPU標志位的狀態如下 ``` 如果a>b,CPU的標志位會被設為:ZF=0,PF=0,CF=0 如果a<b,CPU的標志位會被設為:ZF=0,PF=0,CF=1 如果a=b,CPU的標志位會被設為:ZF=1,PF=0,CF=0 ``` SETNBE指令怎樣給AL存儲0或1:取決于CPU標志位。幾乎是JNBE的計數器,利用設置cc碼產生的異常,來給AL寫入0或1,但是Jccbut Jcc do actual jump or not.SETNBE存儲1只在CF=0并且ZF=0的情況下。如果為假,將會存儲0。 cf和ZF都為0只存在于一種情況:a&gt;b 然后one將會被存入AL中,接下來JZ不會被觸發,函數將返回_a。在其他的情況下,返回的是_b。 ### 15.3.4 GCC 4.4.1-03優化選項turned開關 ``` #!bash public d_max d_max proc near arg_0 = qword ptr 8 arg_8 = qword ptr 10h push ebp mov ebp, esp fld [ebp+arg_0] ; _a fld [ebp+arg_8] ; _b ; stack state now: ST(0) = _b, ST(1) = _a fxch st(1) ; stack state now: ST(0) = _a, ST(1) = _b fucom st(1) ; compare _a and _b fnstsw ax sahf ja short loc_8048448 ; store ST(0) to ST(0) (idle operation), pop value at top of stack, leave _b at top fstp st jmp short loc_804844A loc_8048448: ; store _a to ST(0), pop value at top of stack, leave _a at top fstp st(1) loc_804844A: pop ebp retn d_max endp ``` 幾乎相同除了一種情況:JA替代了SAHF。事實上,條件跳轉指令(JA, JAE, JBE, JBE, JE/JZ, JNA, JNAE, JNB, JNBE, JNE/JNZ)檢查通過檢查CF和ZF標志來知曉兩個無符號數字的比較結果。C3/C2/C0位在比較之后被放入這些標志位中然后條件跳轉就會起效。JA會生效如果CF和ZF都為0。 因此,這里列出的條件跳轉指令可以在FNSTSW/SAHF指令對之后使用。 看上去,FPU C3/C2/C0狀態位故意放置在那里,傳遞給CPU而不需要額外的交換。 ### 15.3.5 ARM+優化Xcode(LLVM)+ARM模式 ``` #!bash VMOV D16, R2, R3 ; b VMOV D17, R0, R1 ; a VCMPE.F64 D17, D16 VMRS APSR_nzcv, FPSCR VMOVGT.F64 D16, D17 ; copy b to D16 VMOV R0, R1, D16 BX LR ``` 一個簡單例子。輸入值放在D17到D16寄存器中,然后借助VCMPE指令進行比較。就像x86協處理器一樣,ARM協處理器擁有自己的標志位寄存器(FPSCR),因為存儲協處理器的特殊標志需要存儲。 就像x86中一樣,在ARM中沒有條件跳轉指令,在協處理器狀態寄存器中檢查位,因此這里有VMRS指令,從協處理器狀態字復制4位(N,Z,C,V)放入通用狀態位(APSR寄存器) VMOVGT類似MOVGT指令,如果比較時一個操作數比其它的大,指令將會被執行。 如果被執行了,b值將會寫入D16,暫時被存儲在D17中。 如果沒有被執行,a的值將會保留在D16寄存器中。 倒數第二個指令VMOV將會通過R0和R1寄存器對準備D16寄存去中的值來返回。 ### 15.3.6 ARM+優化 Xcode(LLVM)+thumb-2 模式 ``` #!bash VMOV D16, R2, R3 ; b VMOV D17, R0, R1 ; a VCMPE.F64 D17, D16 VMRS APSR_nzcv, FPSCR IT GT VMOVGT.F64 D16, D17 VMOV R0, R1, D16 BX LR ``` 幾乎和前一個例子一樣,有一些小小的不同。事實上,許多ARM中的指令在ARM模式下根據條件判定,當條件為真則執行。 但是在thumb代碼中沒有這樣的事。在16位的指令中沒有空閑的4位來編碼條件。 但是,thumb-2為老的thumb指令進行擴展使得特殊判斷成為可能。 這里是IDA-生成的表單,我們可以看到VMOVGT指令,和在前一個例子中是相同的。 但事實上,常見的VMOV就這樣編碼,但是IDA加上了—GT后綴,因為以前會放置“IT GT”指令。 IT指令定義所謂的if-then塊。指令后面最多放置四條指令是可能的,判斷后綴會被加上。在我們的例子中,“IT GT”意味著下一條指令會被執行,如果GT(Greater Than)條件為真。 下面是一段更加復雜的代碼,來源于“憤怒的小鳥”(ios版) ``` #!bash ITE NE VMOVNE R2, R3, D16 VMOVEQ R2, R3, D17 ``` ITE意味著if-the-else并且它為接下來的兩條指令加上后綴。第一條指令將會執行如果ITE(NE,不相等)這時為真,為假則執行第二條指令。(與NE對立的就是EQ(equal)) 這段代碼也來自“憤怒的小鳥” ``` #!bash ITTTT EQ MOVEQ R0, R4 ADDEQ SP, SP, #0x20 POPEQ.W {R8,R10} POPEQ {R4-R7,PC} ``` 4個“T”符號在助記符中意味著接下來的4條指令將會被執行如果條件為真。這也是IDA在每條指令后面加上-EQ后綴的原因。 如果出現上面例子中ITEEE EQ(if-then-else-else-else),那么這些后綴將會被這樣設置。 ``` #!bash -EQ -NE -NE -NE ``` 另一段來自“憤怒的小鳥”的代碼。 ``` #!bash CMP.W R0, #0xFFFFFFFF ITTE LE SUBLE.W R10, R0, #1 NEGLE R0, R0 MOVGT R10, R0 ``` ITTE(if-then-then-else)意味著第一條第二條指令將會被執行,如果LE(Less or Equal)條件為真,反之第三條指令將會執行。 編譯器通常不生成所有的組合。舉個例子,在“憤怒的小鳥”中提到的(ios經典版)只有這些IT指令會被使用:IT,ITE,ITT,ITTE,ITTT,ITTTT.我們怎樣去學習它呢?在IDA中,產生這些列舉的文件是可能的,于是我這么做了,并且設置選項以4字節的格式現實操作碼。因為IT操作碼的高16位是0xBF,使用grep指令 ``` #!bash cat AngryBirdsClassic.lst | grep " BF" | grep "IT" > results.lst ``` 另外,對于thumb-2模式 ARM匯編語言的程序,通過附加的條件后綴,必要的時候匯編會自動加上IT指令和相應的標志。 ### 15.3.7 ARM+非優化模式 Xcode(LLVM)+ARM模式 ``` #!bash b =-0x20 a =-0x18 val_to_return = -0x10 saved_R7 = -4 STR R7, [SP,#saved_R7]! MOV R7, SP SUB SP, SP, #0x1C BIC SP, SP, #7 VMOV D16, R2, R3 VMOV D17, R0, R1 VSTR D17, [SP,#0x20+a] VSTR D16, [SP,#0x20+b] VLDR D16, [SP,#0x20+a] VLDR D17, [SP,#0x20+b] VCMPE.F64 D16, D17 VMRS APSR_nzcv, FPSCR BLE loc_2E08 VLDR D16, [SP,#0x20+a] VSTR D16, [SP,#0x20+val_to_return] B loc_2E10 loc_2E08 VLDR D16, [SP,#0x20+b] VSTR D16, [SP,#0x20+val_to_return] loc_2E10 VLDR D16, [SP,#0x20+val_to_return] VMOV R0, R1, D16 MOV SP, R7 LDR R7, [SP+0x20+b],#4 BX LR ``` 基本和我們看到的一樣,但是太多冗陳代碼,因為a和b的變量存儲在本地棧中,還有返回值 ### 15.3.8 ARM+優化模式keil+thumb模式 ``` #!bash PUSH {R3-R7,LR} MOVS R4, R2 MOVS R5, R3 MOVS R6, R0 MOVS R7, R1 BL __aeabi_cdrcmple BCS loc_1C0 MOVS R0, R6 MOVS R1, R7 POP {R3-R7,PC} loc_1C0 MOVS R0, R4 MOVS R1, R5 POP {R3-R7,PC} ``` keil 不為浮點數的比較生成特殊的指令,因為他不能依靠核心CPU的支持,它也不能直接按位比較。這里有一個外部函數用于比較:__aeabi_cdrcmple. N.B. 比較的結果用來設置標志,因此接下來的BCS(標志位設置 - 大于或等于)指令可能有效并且無需額外的代碼。 ## 15.4 x64 更多關于x86-64位浮點數的處理在這里
                  <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>

                              哎呀哎呀视频在线观看