Intel CPU中的條件轉移指令
概述
毫無疑問,Intel CPU功能越來越強大,指令越來越復雜。
從Intel官網(wǎng)可以免費下載其全套CPU手冊。這套手冊是Intel匯編最權威的資料,而且可讀性非常高,對于學習匯編語言,了解CPU工作原理來說,都是最有價值的資料,沒有之一。
本文以《Intel 64 and IA-32 Architectures Software Developers Manual Volume 2 - Instruction Set Reference》為底稿,整理總結一下各種條件轉移指令。
定義
條件轉移指令,常見的有je,jnz,jcxz,jle等,是滿足條件時進行跳轉的指令。
手冊中列出的條件轉移指令總計有95條,記憶很不方便。
CPU的無記憶特性
CPU是沒有記憶的,它只知道按指令功能嚴格執(zhí)行完當前指令,然后繼續(xù)執(zhí)行下一條指令。假設某一種簡單CPU只有四條指令,分別是向上、向下、向左、向右。那么下圖左側的程序運行起來以后,便可以得到右側的運行結果。生成一條從A到B的路徑。但任何一個時刻,你問CPU上一步從哪里來,上一條指令是什么?上一步寄存器內容是多少?它全然不知。

條件判斷依據(jù)?
典型的條件轉移指令上下文如下(紅色為標號,可以忽略):
L1:? mov eax, 0xFF
L2:? cmp eax, dword ptr [ebp-20h]
L3:? jne?error
L4:? jmp continue
error: return
continue: ......
上面的程序中,L1處的指令將0xFF傳送到寄存器eax,L2處的指令對eax的值和[epb-20]所指向內存中的雙子進行比較,L3處的指令jne意思是:如果不相等,就跳轉到error標號處(jump if not equal)。不過,問題來了:
前面說過,CPU沒有歷史記憶,CPU在執(zhí)行條件轉移指令時,并不知道其上一條指令到底是相減、相加還是相乘或按位與操作,也不知道eax中到底是有符號數(shù)還是無符號數(shù),比如,把cmp換成test或者add,程序依舊可以順利通過編譯并順利運行:
test eax, dword ptr [ebp-20h]
add eax, dword ptr [ebp-20h]
但這種情況下,我們再將jne理解為兩數(shù)不等,就完全不合乎實際程序邏輯了。
事實上,條件轉移指令執(zhí)行時,并不依賴于上一條指令,僅依據(jù)當前EFLAGS寄存器的值進行是否轉移的判定。
關于EFLAGS寄存器
對于IA-32架構CPU,其內部的EFLAGS寄存器布局如下圖所示:

影響EFLAGS寄存器的因素有很多,可以通過指令,如:LAHF, SAHF, PUSHF, PUSHFD, POPF, POPFD, CLI, STD等,任務切換時EFLAGS被保存在TSS中,任務恢復時EFLAGS也被恢復;中斷和異常發(fā)生時,EFLAGS也需要保存和恢復。當然,這些聽不懂也沒關系。
和條件跳轉指令緊密相關的是被稱為狀態(tài)標志的6個標志,分別是CF/PF/AF/ZF/SF/OF

這些標志位都會受到數(shù)學運算指令的影響,如加減乘除、移位、位運算等等。其中最難區(qū)分的是CF和OF,兩個標志都和溢出有關。
從上面的英文畫線部分看,CF標志是針對unsigned?integer運算,而OF是針對signed運算,不過實際上遠沒有這么簡單。比如mul是無符號乘法指令,但其結果會同時影響OF和CF兩個標志,具體細節(jié)是:

比如,兩個十六位數(shù)進行MUL乘法運算,結果保存到dx:ax寄存器對中。如果dx寄存器是0,則OF和CF均為0,否則都為1,而ZF,AF,PF則屬于未定義。針對IMUL有符號數(shù)乘法,影響還要更復雜些。所以具體情況一定要查手冊才能確定。
不過,條件轉移指令的前一條指令,最常見的就是減法SUB,比較CMP和測試TEST,其中cmp和sub的唯一區(qū)別在于:cmp相減的結果不保存,只影響標志位。另外,cmp和sub均同時測試有符號數(shù)和無符號數(shù),因為如果部考慮最高位的溢出,有符號和無符號相減運算的結果是相同的。舉例:
假設:ax = 0x80F0,bx = 0x8F00,那么sub ax, bx對EFLAGS的影響就是:
CF = 1, ZF = 0, SF = 1, OF=0,具體分析如下:
1)假設ax和bx中存儲的都是無符號數(shù),則有:0x80F0可轉換成十進制33008,0x8F00轉換成十進制是36608,那么無符號減法 33008-36608 = -3600,轉換成十六進制為F1F0,做減法運算過程中需要最高位借位。
2)?假設ax和bx中存儲的都是有符號數(shù),那么0x80F0對應十進制-32528,而0x8F00對應十進制-28928,則-32528 - (-289282) = -3600,而且數(shù)據(jù)不超過十六位二進制可以容納的范圍,未發(fā)生溢出,所以OF = 0
通過以上分析我們知道,對于CPU來說,SUB指令無需區(qū)分操作數(shù)是否有符號,只需按無符號數(shù)相減,如果最高位有借位,就置位CY,否則CY=0。然后再按有符號計算一下結果是否溢出,相應置位或復位OF。sub和cmp運算,根據(jù)結果會影響OF, SF, ZF, AF, PF, CF標志。
對于test指令來說,它使兩個操作數(shù)按位進行與運算,影響SF, ZF, PF標志位,且OF=0, CF=0,運算結果不存儲。
Intel CPU的條件轉移指令速記
95個有條件轉移指令,想要記住很難,即便自己不寫,讀別人程序總歸需要理解正確才行。
其實,很多條件轉移指令可以望文生義的,如:
jnz = jump not ZF(如果ZF不為1則轉移)
jle = jump if less or equal (小于或等于轉移)
jb = jump if below (小于則轉移)
不過正如前文所述,CPU是沒有記憶能力的,這里的所謂“等于”,“小于”等,都是基于前一條影響標志位的指令是sub或cmp的假設,如果不是,則根本就風馬牛不相及了。何況below和less是否有區(qū)別也不清楚。
正因為Jxx類條件轉移指令只根據(jù)EFLAGS當前值做出是否轉移的決定,實際上還是需要熟記je是判斷那個標志位,jl是判斷哪個標志位。根據(jù)Intel手冊,有如下主要規(guī)律:

這里,SF與運算結果最高位相同,對于有符號數(shù)來說,SF=1則是負數(shù),否則是正數(shù);而OF則代表是否存在溢出,也就是目的操作數(shù)能否裝下完整運算結果。結合這兩個因素仔細一想,上面通過SF和OF是否相等來判斷兩個有符號數(shù)大小的方法真的很巧妙。
只有明白了每條指令對標志位的影響,再明白了每一條條件轉移指令到底是根據(jù)哪個標志位或標志位組合來判斷,才算是真正搞懂了條件轉移。
最后,將所有條件轉移指令列表如下:



