|
本帖最后由 51黑tt 于 2016-3-6 15:28 編輯
從ARM匯編指令機(jī)器碼解釋一些問題
為什么MOV R0, #0×12345678這條指令無法編譯,而MOV R0, #0x678卻可以編譯通過?
為什么芯片會有立即數(shù)尋址,寄存器尋址,間接尋址等多種尋址方式?一種尋址方式不可以么?
為什么B跳轉(zhuǎn)指令只能跳轉(zhuǎn)到±32MBytes的范圍內(nèi)?
下面,我們一起來看看ARM7的指令格式,從指令格式中找出這些問題的答案。
ARM7內(nèi)核采用的是RISC精簡指令集,所有的ARM指令都是32bits的,在這32bits里既包含了指令的指令碼,也包含了指令需要運(yùn)算的數(shù)據(jù),以MOV指令為例,通過MOV指令的32bits可以識別出這是一個MOV指令,又可以在這32bits里找到源寄存器和目的寄存器。我們來看一下MOV指令的機(jī)器碼格式:
0.png (5.91 KB, 下載次數(shù): 35)
下載附件
2016-3-6 15:28 上傳
圖 6 MOV指令的機(jī)器碼格式
28~31bits(cond)是條件碼,就是表明這條語句里是否有大于、等于、非零等的條件判斷,這4bits共有16種狀態(tài),分別為:
二進(jìn)制碼 | 指令符號 | 含義 | 二進(jìn)制碼 | 指令符號 | 含義 | 0000 | EQ | 相等 | 0001 | NE | 不等 | 0010 | CS/HS | 進(jìn)位/無符號數(shù)大于等于 | 0011 | CC/LO | 清進(jìn)位/無符號數(shù)小于 | 0100 | MI | 減/負(fù)數(shù) | 0101 | PL | 加/正數(shù)或0 | 0110 | VS | 溢出 | 0111 | VC | 沒溢出 | 1000 | HI | 無符號數(shù)大于 | 1001 | LS | 無符號數(shù)小于等于 | 1010 | GE | 有符號數(shù)大于等于 | 1011 | LT | 有符號數(shù)小于 | 1100 | GT | 有符號數(shù)大于 | 1101 | LE | 有符號數(shù)小于等于 | 1110 | AL | 任何條件 | 1111 | - | 未定義 | 表 1 匯編語言條件碼
指令與條件碼可以有多種組合,比如MOV指令可以有MOV、MOVEQ、MOVLT等多種形式。前面我們說過狀態(tài)寄存器里有NZCV的狀態(tài)標(biāo)志,當(dāng)執(zhí)行一條指令時,芯片就會將這條指令的條件碼與狀態(tài)寄存器中的狀態(tài)標(biāo)志做比較,如果狀態(tài)寄存器中的狀態(tài)標(biāo)志滿足這條指令的條件碼時,則執(zhí)行這條語句,如果不滿足則不執(zhí)行這條指令。狀態(tài)寄存器中的狀態(tài)標(biāo)志是受某些指令影響的,因此在使用有條件碼的指令進(jìn)行判斷前,必然會有其它指令配合使用,先修改狀態(tài)寄存器中的狀態(tài)標(biāo)志,例如:
CMP R1, #0
BEQ GETNEXTTASKSP
第一條指令“CMP”是一個“比較”指令,如果R1等于0,那么它就將狀態(tài)寄存器中的Z置為1,表示結(jié)果為真,否則,將狀態(tài)寄存器中的Z置為0,表示結(jié)果為假。第二條指令其實是一條“B”指令,是“跳轉(zhuǎn)”指令,B之后的“EQ”就是條件碼,從表1中可以知道,條件是“相等”時才執(zhí)行。
當(dāng)R1等于0時,CMP指令就將Z置為1,執(zhí)行BEQ時滿足條件,就執(zhí)行了跳轉(zhuǎn)。如果R1不等于0,CMP指令就將Z置為0,執(zhí)行BEQ時不滿足條件,就不執(zhí)行跳轉(zhuǎn)。
同理,只有當(dāng)狀態(tài)寄存器中的標(biāo)志為相等時,MOVEQ指令才會執(zhí)行,這時其功能與MOV指令相同。而MOVLT指令則是當(dāng)狀態(tài)寄存器中的標(biāo)志為有符號數(shù),并且處于小于狀態(tài)時才會執(zhí)行的MOV指令。MOV指令的條件碼是AL,因此MOV指令可以不管任何條件都去執(zhí)行。其它指令也可以與條件碼組合使用,具體情況請查閱參附錄中的參考文檔2。
25bit(I)是用來區(qū)別shifer_operand域是采用立即數(shù)尋址方式還是寄存器尋址方式,該bit為0表示寄存器尋址方式,為1表示立即數(shù)尋址方式,這就涉及到了指令的尋址方式。
尋址方式的出現(xiàn)不是為了使指令能有多種寫法,而是受指令長度限制被迫產(chǎn)生的產(chǎn)物。以MOV指令為例,如果采用立即數(shù)尋址,立即數(shù)的長度不可能超過shifer_operand域的長度(MOV指令可以采用移位的方式裝下部分更長的立即數(shù),這些不在討論之內(nèi)),因此我們就不可能使用
MOV R0, #0×12345678
這條指令。立即數(shù)#0×12345678是32bits數(shù)據(jù),已經(jīng)超過了shifer_operand域所能裝下的最長12bits數(shù)據(jù),如果把0×12345678全部被存到指令中,那么該指令中將無法存儲條件碼等其它指令信息,因此,這條指令在編譯時就會報錯。
為了解決這個問題,芯片設(shè)計人員就設(shè)計了寄存器尋址方式,在ARM7中每種模式有16個通用寄存器,2的4次方等于16,因此只需要用4bits就可以為每個寄存器分配一個編號,R0~R15寄存器分別對應(yīng)0~15的編號。4bits的寄存器編號完全可以存入shifer_operand域。采用寄存器尋址時,指令先查到寄存器的編號,然后再從寄存器中取出使用的數(shù)據(jù),這樣就解決了MOV指令受指令長度的限制而無法操作長立即數(shù)的問題。
從上述描述的過程來看采用寄存器尋址方式必須先將數(shù)據(jù)放入一個寄存器中,然后才能使用MOV指令采用寄存器尋址。對比立即數(shù)尋址方式,它增加了指令的執(zhí)行時間,也增加了代碼,還多用了一個寄存器,但它的優(yōu)點是可以操作長的數(shù)據(jù)。
除了上面這兩種尋址方式外,ARM7還有多種其它尋址方式,每種尋址方式都有其自身的特點,適用不同的場景,這里不介紹了。
21~24bits(opcode)是指令碼,用來表明這條指令是什么指令,例如,MOV指令的指令碼是0b1101,看到0b1101,芯片就將這條指令當(dāng)做MOV指令來解析。
20bit(S)就是指令中S標(biāo)志的體現(xiàn),該bits為0表示指令不帶S,為1表示指令帶S,功能見上述指令介紹。
16~19bits(SBZ)手冊中沒查到是什么意思,SBZ應(yīng)該是should be zero的意思,對比了幾條指令發(fā)現(xiàn)該域果然全是0,應(yīng)該是保留位。
12~15bits(Rd)是指令中的目的寄存器,存放寄存器的4bits編號。
0~11bits(shifter_operand),指令的操作數(shù)。
下面我找了4條指令,將MOV指令做一個對比:
指令 | 機(jī)器碼 | 指令格式 | cond | 00 | I | opcode | S | SBZ | Rd | shifer_operand | MOV R1, #0×64 | E3A01064 | 1110 | 00 | 1 | 1101 | 0 | 0000 | 0001 | 000001100100 | 條件碼為1110適用任何條件 | | 立即數(shù)方式 | MOV的指令碼 | 指令沒有S標(biāo)志 | | 目的寄存器為R1 | 源操作數(shù)為立即數(shù)0×64 | MOVS PC, R14 | E1B0F00E | 1110 | 00 | 0 | 1101 | 1 | 0000 | 1111 | 000000001110 | 條件碼為1110適用任何條件 | | 寄存器方式 | MOV的指令碼 | 指令有S標(biāo)志 | | 目的寄存器為R15 | 源操作數(shù)為寄存器R14 | MOVLT R3, #0×1 | B3A03001 | 1011 | 00 | 1 | 1101 | 0 | 0000 | 0011 | 000000000001 | LT的條件碼為1011 | | 立即數(shù)方式 | MOV的指令碼 | 指令沒有S標(biāo)志 | | 目的寄存器為R3 | 源操作數(shù)為立即數(shù)1 | MOVEQ R0, R1 | 01A00001 | 0000 | 00 | 0 | 1101 | 0 | 0000 | 0000 | 000000000001 | EQ的條件碼為0000 | | 寄存器方式 | MOV的指令碼 | 指令沒有S標(biāo)志 | | 目的寄存器為R0 | 源操作數(shù)為寄存器R1 | 表 2 MOV指令匯編格式對比
BL指令跳轉(zhuǎn)到目的地址,同時將BL指令的下條指令地址存入LR寄存器供程序返回時使用,調(diào)用函數(shù)時可以使用BL指令,如:
0×00080398 EB0002E2 BL 0x00080F28
0×00080398是BL指令所在的地址,EB0002E2是BL指令的機(jī)器碼,0x00080F28是BL指令要跳轉(zhuǎn)到的地址。從這個格式來看,BL指令好像是一個絕對跳轉(zhuǎn)指令,直接跳轉(zhuǎn)到0x00080F28這個地址,其實不然,BL指令是一個相對跳轉(zhuǎn)指令,格式如下:
1.png (4.89 KB, 下載次數(shù): 42)
下載附件
2016-3-6 15:28 上傳
BL指令的0~23bits存放的是要跳轉(zhuǎn)的相對地址,由于指令所在地址必須是4字節(jié)對齊的,因此跳轉(zhuǎn)的地址最低2bits必然是0,因此BL指令0~23bits保存的是省略這最低2bits的地址,如果補(bǔ)全了這2bits,BL指令就可以表示26bits的跳轉(zhuǎn)地址。在這26bits中需要使用1bit表示向前跳還是向后跳,那么剩下的25bits就可以表示32MByts的范圍了,225=32M,因此,我們在很多文檔上可以看到B跳轉(zhuǎn)指令只能跳轉(zhuǎn)到±32MBytes范圍內(nèi)的說明,就是這個原因。
上面這個BL指令要跳轉(zhuǎn)的相對地址是0x2E2(BL指令0~23bits),補(bǔ)充2個最低位后,跳轉(zhuǎn)的相對地址為0xB88,由于ARM7有2級流水線,所以跳轉(zhuǎn)到的指令需要多加8個字節(jié),BL要跳轉(zhuǎn)的實際地址為0×00080398+0xB88+8=0x00080F28。
這條BL指令執(zhí)行下面的操作:
LR = 0x0008039C
PC = 0x00080F28
在操作系統(tǒng)中我們沒有使用BL指令,就是因為我們不知道我們所調(diào)用的函數(shù)是否會超出BL指令的跳轉(zhuǎn)范圍,但我們可以看到編譯器編譯出的很多程序都是使用BL指令調(diào)用函數(shù)的,編譯器之所以不怕跳轉(zhuǎn)超出±32MBytes的范圍,是因為編譯器在編譯時就知道了程序所需要跳轉(zhuǎn)的范圍,它會為±32MBytes之內(nèi)的跳轉(zhuǎn)分配BL指令,保證BL指令不會超出范圍。在這里以BL指令為例,介紹一下B指令的相關(guān)知識。
|
|