汇编之浮点数处理(CrackMe003前置知识)
文章目錄
- 浮點數的二進制表示
- IEEE二進制浮點數的表示
- 1.符號位
- 2.有效數字
- 3.有效數字的精度
- 階碼
- 規格化二進制浮點數
- 新建IEEE表示
- 實數編碼
- 單精度數轉換為十進制
- 浮點單元
- FPU寄存器棧
- FPU寄存器
- 專用寄存器
- 舍入
- FPU控制字
- 浮點數異常
- 浮點數指令集
- 1.初始化(FINIT)
- 2.浮點數據類型
- 3.加載浮點數值 FLD
- FILD
- 加載常數
- 保存浮點數值(FST FSTP FIST)
- 算術運算指令
- FCHS和FABS
- FADD FADDP FIADD
- FSUB FSUBP FISUB
- FMUL FMULP FIMUL
- FDIV FDIVP FIDIV
- 比較浮點數值
- FCOM FCOMP FCOMPP
- 條件碼
- P6處理器的改進
- 讀寫浮點數值
- 異常同步
浮點數的二進制表示
十進制浮點數有三個部分組成:符號,有效數字和階碼。比如,在-1.23154*105中,符號為負,有效數字為1.23154,階碼為5
IEEE二進制浮點數的表示
x86處理器使用的三種浮點數二進制存儲格式都是由IEEE標準754-1985——二進制浮點運算一一所制定。下表列出了他們的特點
| 單精度 | 32位:1位符號位,8位階碼,23位為有效數字的小數部分。大致的規格化范圍:2-126—2127 也被稱為短實數 |
| 雙精度 | 64位:1位符號位,11位階碼,52位為有效數字的小數部分。大致的規格化范圍為:2-1022—21023 也被稱為長實數 |
| 擴展雙精度 | 80位:1位符號位,15位階碼,1位為整數部分,63位為有效數字的小數部分。大致的規格化范圍:2-16382—216383 也被稱為擴展實數 |
由于三種格式比較相似,因此本節將重點關注單精度格式。
1.符號位
如果符號位為1,則該數為負;如果符號位為0,則該數為正。
2.有效數字
浮點數的有效數字由小數點的左右的十進制數字構成。十進制的數123.154用加權位計數法可以表示為下面的累加和形式
123.154=(1x102)+(2x101)+(3x100)+(1x10-1)+(5x10-2)+(4x10-3)
小數點左邊的數字的階碼都位正,右邊的數字階碼都為負
小數點右邊數字還有一種表達方式,即把他們列為分數之和,其中分母為2的冪,例如:
.1011=1/2+0/4+1/8+1/16=11/16
3.有效數字的精度
用有限位數表示的任何浮點數都無法表示完整的連續的實數。例如:假設一個簡單的浮點數格式有5位有效數字,那么將無法表示范圍在1.1111-10.000之間的二進制數。比如,二進制數1.11111就需要更精確的有效數字。將這個思想擴展到IEEE雙精度格式,就會發現53位有效數字無法表示需要54位或更多二進制數值。
階碼
單精度數用8位無符號整數存放階碼,引入的偏差為127,因此必須在數的實際階碼上再加上127
規格化二進制浮點數
大多數二進制浮點數都以規格化格式存放,以便將有效數字的精度最大化。給定任意二進制浮點數,都可以進行規格化,方法是將小數點移位,直到小數點左邊只有一個1。階碼表示的是二進制小數點向左或向右移動的位數。示例如下:
| 1110.1 | 1.1101x23 |
| 000101 | 1.01x2-4 |
| 1010001 | 1.010001x2-6 |
反規格化數:規格化操作的逆操作是將二進制浮點數反規格化。移動二進制小數點,直到階碼為0。如果階碼為正數,則將小數點右移,如果階碼為負數,則將二進制小數點左移,并在需要的位置前填充導數0。
新建IEEE表示
實數編碼
一旦符號位 階碼和有效數字字段完成格式化和編碼后,生成一個完整的二進制IEEE段實數就很容易了。首先設置符號位,然后是階碼字段,最后是有效數字部分。例如:下面表示的是二進制1.101x20
- 符號位:0
- 階碼:01111111
- 小數部分:10100000000000000000000
偏移碼(01111111)是十進制數127的二進制形式。所有規格化有效數字在二進制小數點的左邊都有個1,因此,不需要對這一位進行顯示編碼。
? 單精度數位編碼示例
| -1.11 | 127 | 1 01111111 11000000000000000000000 |
| 1101.101 | 130 | 0 10000010 10110100000000000000000 |
IEEE規范包含了多鐘實數和非數字編碼
- 正零和負零
- 非規格化有限數
- 規格化有限數
- 正無窮和負無窮
- 非數字
- 不定數
規格化和非規格化: 規格化有限數是指所有非零有限值,這些數能被編碼為零到無窮之間的規格化實數。盡管看上去全部有限非零浮點數都應被規格化,但若數值接近于零,則無法規格化,當階碼范圍造成的限制使得FPU不能將二進制小數點移動到規格化位置時,就會發生這種情況。假設FPU計算結果為1.0101111x2-129,其階碼太小,無法用單精度數形式存放。此時產生一個下溢異常,數值則每次將二進制小數點左移一位逐步進行非規格化,直到階碼達到有效范圍
正無窮和負無窮:正無窮表示最大正實數,負無窮表示最大負實數。無窮可以和其他數值比較。負無窮小于正無窮,負無窮小于任意有限實數。任一無窮都可以表示浮點溢出條件。運算結果不能格式化的原因是,結果的階碼太大而無法用有效階碼的位數來表示。
NaN:NaN是不表示任何有效實數的位模式
特定編碼:在浮點運算中,常常會出現一些特定的數值編碼
單精度數轉換為十進制
IEEE單精度數轉換為十進制時,建議步驟如下:
示例IEEE(0 10000010 01011000000000000000000)轉換為十進制:
浮點單元
Inter8086處理器設計使之只能處理整數運算。這對于使用浮點運算的圖形和計算密集型軟件來說就變成了麻煩。盡管也可以純粹地通過軟件來模擬浮點運算,但這樣會帶來嚴重的性能損失
FPU寄存器棧
FPU不使用通用寄存器,反之,它有自己的一組寄存器,稱為寄存器棧。數值從內存加載到寄存器棧,然后執行計算,再將堆棧數值保存到內存。FPU指令用后綴形式計算算術表達式,這和惠普計算器的方法大致相同。比如,現有一個中綴表達式:(5*6)+4,其后綴表達式為:5 6 *4 +
中綴表達式(A+B)*C要用括號來覆蓋默認的優先規則,與之等效的后綴表達式則不需要括號:A B + C *
? 中綴轉為后綴的例子
| A+B | AB+ | (A+B)*(C+D) | AB+CD+* |
| (A-B)/D | AB-D/ | ((A+B)/C)*(E-F) | AB+C/EF-* |
表達式堆棧:在計算后綴表達式的過程中,用堆棧來保存中間結果
FPU寄存器
FPU有8個獨立的 可尋址的80位數據寄存器R0-R7,這些寄存器合稱為寄存器棧。FPU狀態字中名為TOP的一個3位字段給出了當前處于棧頂的寄存器編號。例如 當TOP=011時 表示棧頂為R3。在編寫浮點指令時,這個位置也稱為ST(0)。最后一個寄存器為ST(7)
如同想的一樣,入棧操作將top-1,并把操作數復制到標識為ST(0)的寄存器中,如果在入棧之前,TOP等于0,那么TOP就回繞到寄存器R7。出棧操作把ST(0)的數據復制到操作數,再將TOP+1。如果在出棧之前TOP=7,則TOP就回繞到寄存器R0。如果加載到堆棧的數值覆蓋了寄存器棧內的原有數據,就會產生一個浮點異常
盡管理解FPU如何利用一組有限數量的寄存器實現堆棧很有意思,但這里只需要關注ST(n),其中ST(0)總是表示棧頂。從這里開始,引用棧寄存器時將使用ST(0) ST(1),以此類推。指令操作數不能直接引用寄存器編號
寄存器中浮點數使用的是IEEE10字節擴展實數格式,也被稱為臨時實數。當FPU把算術運算結果存入內存時,它會把結果轉換成如下格式之一:整數 長整數 單精度 雙精度 或者壓縮二進制編碼的十進制數
專用寄存器
FPU有6個專用寄存器
- 操作碼寄存器:保存最后執行的非控制指令的操作碼
- 控制寄存器:執行運算時,控制精度以及FPU使用的舍入方法,還可以用這個寄存器來屏蔽單個浮點異常
- 狀態寄存器:包含棧頂指針 條件碼和異常警告
- 標識寄存器:指明FPU數據寄存器棧內每個寄存器的內容。其中每個寄存器都用兩位來表示該寄存器包含的是一個有效數 零 特殊數值還是為空
- 最后指令指針寄存器:保存指向最后執行的非控制指令的指針
- 最后數據(操作數)指針寄存器:保存指向數據操作數的指針,如果存在那么該數被最后執行的指令所使用
舍入
FPU嘗試從浮點運算中產生非常精確的運算結果,但是在很多情況下這是不可能的,因為目標操作數可能無法精確表示計算結果。FPU可以在四種舍入方法中進行選擇
FPU控制字
FPU控制字用兩位指明使用的舍入方法,這兩位被稱為RC字段。字段數值如下:
- 00:舍入到最接近的偶數(默認)
- 01:向負無窮舍入
- 10:向正無窮舍入
- 11:向0舍入
浮點數異常
每個程序都可能出錯,而FPU就需要處理這些結果。因而,它要識別并檢測6種類型的異常條件:無效操作 除零 非規格化操作數 數字上溢 數字下溢以及模糊精度。前三個在全部運算操作發生前進行檢測,后三個在操作發生后進行檢測。
每種異常都有對應的標志位和屏蔽位。當檢測到浮點異常時,處理器將與之匹配的標志位置1。每個被處理器標志的異常都有兩種可能的操作:
- 如果相應的屏蔽位置1 那么處理器自動處理異常并繼續執行程序
- 如果相應的屏蔽位清0,那么處理器將調用軟件異常處理程序
大多數程序普遍都可以接受處理器的屏蔽響應。如果應用程序需要特殊響應,那么可以使用自定義異常處理程序,一條指令能觸發多個異常,因此處理器要持續保存自上一次異常清零后所發生的全部異常。完成一系列計算后,可以檢測是否發生了異常。
浮點數指令集
FPU指令集有些復雜,因此本節嘗試對齊功能進行概述,并用具體例子給出編譯器通常會生成的代碼。此外,本節還將看到如何通過改變舍入模式來控制FPU。指令集包括如下基本指令類型:
- 數據傳送
- 基本算術運算
- 比較
- 超越函數
- 常數加載
- x87FPU控制
- x87FPU和SIMD狀態管理
浮點指令名用字母F開頭,以區別CPU指令,指令助記符的第二個字母(通常為B或I)指明如何解釋內存操作數:B表示BCD操作數,I表示二進制整數操作數。如果這兩個字母都沒有使用,則內存操作數被認為是實數。比如,FBLD操作對象為BCD數值,FILD操作對象為整數,而FLD操作對象為實數
操作數:浮點指令可以包含零操作數 單操作數和雙操作數。如果是雙操作數,那么其中一個必然為浮點寄存器。指令中沒有立即操作數,但是某些預定義常數可以加載到堆棧。通用寄存器EAX EBX…不能作為操作數。
整數操作數從內存加載到FPU,并自動轉換為浮點格式。同樣,將浮點數保存到整數內存操作數時,該數值也會被自動截斷或舍入為整數。
1.初始化(FINIT)
FINIT指令對FPU進行初始化。將FPU控制字設置為037Fh,即屏蔽了所有浮點異常,舍入模式設置為最近偶數,計算精度設置為64位。建議在程序開始時調用FINIT,這樣就可以了解處理器的其實狀態
2.浮點數據類型
MASM支持的浮點類型有:
- QWORD 64位整數
- TBYTE 80位整數
- REAL4 32位IEEE短實數
- REAL8 64位IEEE長實數
- REAL10 80位IEEE擴展實數
3.加載浮點數值 FLD
FLD指令將浮點操作數復制到FPU堆棧棧頂(ST(0))。操作數可以是32位 64位 80位的內存操作數或另一個FPU寄存器。FLD支持的內存操作數類型與MOV指令一樣
FILD
FILD指令將16位 32位或者64位有符號整數源操作數轉換為雙精度浮點數,并加載到ST(0)。源操作數符號保留。FILD支持的內存操作數類型和MOV一致
加載常數
下面的指令將特定常數加載到堆棧,這些指令沒有操作數
- FLD1指令將1.0壓入寄存器堆棧
- FLDL2T指令將log210壓入寄存器堆棧
- FLDL2E指令將log2e壓入寄存器堆棧
- FLDPI指令將π壓入寄存器堆棧
- FLDLG2指令將log102壓入寄存器堆棧
- FLDLN2指令將loge2壓入寄存器堆棧
- FLDZ(加載零)指令將0.0壓入FPU堆棧
保存浮點數值(FST FSTP FIST)
FST指令將浮點操作數從FPU棧頂復制到內存。FST支持的內存操作數類型和FLD一致。操作數可以為32位 64位 80位內存操作數或另外一個FPU寄存器
FSTP(保存浮點值并將其出棧)指令將ST(0)的值復制到內存并將ST(0)彈出堆棧
FIST(保存整數)指令將ST(0)的值轉換為有符號整數,并把結果保存到目標操作數。保存的值可以為字或者雙字。FIST支持的內存操作數類型與FST一致
算術運算指令
下表列出了基本算術運算操作。所有算術運算指令支持的內存操作數類型與FLD(加載)和FST(保存)一致,因此操作數可以是間接操作數 變址操作數和基址變址操作數等等
| FCHS | 修改符號 |
| FADD | 源操作數與目的操作數相加 |
| FSUB | 從目的操作數中減去源操作數 |
| FSUBR | 從源操作數中減去目的操作數 |
| FMUL | 源操作數和目的操作數相乘 |
| FDIV | 目的操作數除以源操作數 |
| FDIVR | 源操作數除以目的操作數 |
FCHS和FABS
FCHS(修改符號)指令將ST(0)中的浮點值的符號取反。FABS(絕對值)指令清除ST(0)中數值的符號,以得到它的絕對值,這兩條指令都沒有操作數
FADD FADDP FIADD
FADD(加法),如果FADD沒有操作數,則ST(0)與ST(1)相加,結果暫存在ST(1)。然后ST(0)彈出堆棧,把加法結果保留在棧頂。如果是寄存器操作數,從同樣的棧開始,將ST(0)加到ST(1)。如果是內存操作數,FADD將操作數與ST(0)相加
FADDP(相加并出棧)指令先執行加法操作,再將ST(0)彈出堆棧
FIADD(整數加法)指令先將源操作數轉換為擴展雙精度浮點數,再與ST(0)相加
FSUB FSUBP FISUB
FUSB指令從目的操作數中減去源操作數,并把結果保存到目的操作數。目的操作數總是一個FPU寄存器,源操作數可以是FPU寄存器或內存操作數。該指令操作數類型和FADD指令一致。
FUSB的操作與FADD相似,只不過它進行的是減法而不是加法。比如,無參數FUSB實現ST(1)-ST(0),結果暫存與ST(1)。然后ST(0)彈出堆棧,將減法結果留在棧頂。若FSUB使用內存操作數,則從ST(0)中減去內存操作數,且不再彈出堆棧
FSUBP(相減并出棧)指令先執行減法,再將ST(0)彈出堆棧
FISUB(整數減法)指令先把源操作數轉為擴展雙精度浮點數,再從ST(0)中減去該操作數
FMUL FMULP FIMUL
FMUL指令將源操作數與目的操作數相乘,乘積保存在目的操作數中。目的操作數總是一個FPU寄存器,源操作數可以為寄存器或者內存操作數。除了執行的是乘法不是加法外,FMUL的操作與FADD相同。比如,無參數FMUL將ST(0)與ST(1)相乘,乘積暫存于ST(1),然后將ST(0)彈出堆棧,將乘積留在棧頂。
FMULP(相乘并出棧)指令先執行乘法,再將ST(0)彈出堆棧
FIMUL與FIADD相同,只是它執行的是乘法不是加法
FDIV FDIVP FIDIV
FDIV指令執行目的操作數除以源操作數,被除數保存在目的操作數中。目的操作數總是一個寄存器,源操作數可以為寄存器或者內存操作數。其語法與FADD和FSUB相同。
除了執行的是除法不是加法外,FDIV的操作和FADD相同。比如,無參數FDIV執行ST(1)除以ST(0)。然后ST(0)彈出堆棧,將被除數留在棧頂。使用內存操作數的FDIV將ST(0)除以內存操作數。
若操作數為零 則產生除零異常。若源操作數為正 負無窮 零 或者NaN,則使用一些特殊情況
FIDIV指令先將整數源操作數轉換為擴展雙精度浮點數,再執行與ST(0)的除法
比較浮點數值
浮點數不能使用CMP進行比較,因為CMP是通過整數減法來執行比較的。取而代之,必須使用FCOM指令,執行FCOM。執行FCOM指令后,還需要采取特殊步驟,然后再使用JCC跳轉指令。由于所有的浮點數都為隱含的有符號數,因此FCOM執行的是有符號的比較。
FCOM FCOMP FCOMPP
FCOM(比較浮點數)指令將源操作數與ST(0)進行比較。源操作數可以為內存操作數或者FPU寄存器
FCOMP指令的操作數類型和執行的操作與FCOM指令相同,但是它要將ST(0)彈出堆棧
FCOMPP指令與FCOMP相同,但是它有兩次出棧操作
條件碼
FPU條件碼標識有三個:C3 C2和C0,用以說明浮點數的比較結果。C3 C2和C0的功能分別與零標志位(ZF) 奇偶標志位(PF)和進位標志位(CF)相同。
在比較了兩個數值并設置了FPU條件碼之后,遇到的主要挑戰就是怎樣根據條件分支到相應標號。這包括兩個步驟
- 用FNSTSW指令把FPU狀態字送入AX
- 用SAHF指令把AH復制到EFLAGS寄存器
條件碼送入EFLAGS之后,就可以根據ZF CF和PF進行條件跳轉
P6處理器的改進
浮點數比較的運行時開銷大于整數比較??紤]到這一點,InterP6系列引入了FCOMI指令。該指令比較浮點數值,并直接設置ZF PF CF
讀寫浮點數值
- ReadFloat:從鍵盤讀取一個浮點數,并將其壓入浮點堆棧
- WriteFloat:將ST(0)中的浮點數以階碼形式寫到控制臺窗口
異常同步
整數(CPU)和FPU是相互獨立的單元,因此,在執行整數和系統指令的同時可以執行浮點指令。這個功能被稱為并行性,當發生未屏蔽的浮點異常時,它可能是一個潛在的問題。反之,已屏蔽異常則不成問題,因為FPU總是可以完成當前操作并保存結果。
發生未屏蔽異常時,中斷當前的浮點指令,FPU發異常事件信號。當下一條浮點指令或者FWAIT指令將要被執行時,FPU檢查待處理的異常。如果發現有這樣的異常,FPU就調用浮點異常處理程序
如果引發異常的浮點指令后面跟的是整數或系統指令,則指令不會檢查待處理異?!鼈儠⒓磮绦?/p>
總結
以上是生活随笔為你收集整理的汇编之浮点数处理(CrackMe003前置知识)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 160个CrackMe002
- 下一篇: 160个Crackme003之4C大法详