入門級ARM匯編指令
基于RISC 的ARM CPU
ARM是一種RISC體系結(jié)構(gòu)的處理器芯片。和傳統(tǒng)的CISC體系結(jié)構(gòu)不同,RISC 有以下的幾個(gè)特點(diǎn):
◆ 簡潔的指令集——為了保證CPU可以在高時(shí)鐘頻率下單周期執(zhí)行指令,RISC指令集只提供很有限的操作(例如add,sub,mul等),而復(fù)雜的操作都需要由這些簡單的指令來組合進(jìn)行模擬。并且,每一條指令不僅執(zhí)行時(shí)間固定,其指令長度也是固定的,這樣,在譯碼階段就可以對下一條指令進(jìn)行預(yù)取。
◆ Load-Store 結(jié)構(gòu)——這個(gè)應(yīng)該是RISC 設(shè)計(jì)中比較有特點(diǎn)的一部分。在RISC 中,CPU并不會對內(nèi)存中的數(shù)據(jù)進(jìn)行操作,所有的計(jì)算都要求在寄存器中完成。而寄存器和內(nèi)存的通信則由單獨(dú)的指令來完成。而在CSIC中,CPU是可以直接對內(nèi)存進(jìn)行操作的,這也是一個(gè)比較特別的地方。
◆ 更多的寄存器——和CISC 相比,基于RISC的處理器有更多的通用寄存器可以使用,且每個(gè)寄存器都可以進(jìn)行數(shù)據(jù)存儲或者尋址。
當(dāng)然,作為RISC 領(lǐng)域最成功的處理器,ARM也遵從上面的特點(diǎn)。這里,我們不妨來看一看在user 模式下,ARM處理器的體系結(jié)構(gòu),這對于我們了解其匯編語言是有好處的。而其它模式下只是有一些寄存器分組略有不同,大家可以在ARM的手冊上查到。這里要說明的是,盡管ARM處理器也支持16位指令,不過在下文中,我們都假定ARM處理器在32 位模式下工作。
圖1:user模式下ARM處理器體系結(jié)構(gòu)
從圖1中我們看到,在user 模式下,ARM CPU 有16個(gè)數(shù)據(jù)寄存器,被命名為r0~r15(這個(gè)要比x86的多一些)。r13~r15有特殊用途,其中:
◆ r13 - 指向當(dāng)前棧頂,相當(dāng)于x86的esp,這個(gè)東西在匯編指令中要用sp 表示
◆ r14 - 稱作鏈接寄存器,指向函數(shù)的返回地址。用lr表示,這和x86將返回地址保存在棧中是不同的
◆ r15 - 類似于x86的eip,其值等于當(dāng)前正在執(zhí)行的指令的地址+8(因?yàn)樵谌≈泛蛨?zhí)行之間多了一個(gè)譯碼的階段),這個(gè)用pc表示
另外,ARM處理器還有一個(gè)名為cspr的寄存器,用來監(jiān)視和控制內(nèi)部操作,這點(diǎn)和x86 的狀態(tài)寄存器是類似的。具體的內(nèi)容就用到再說了。
ARM 指令集
ARM處理器可以支持3種指令集——ARM,Thumb和Jazelle。
采用那種指令集,由cspr中的標(biāo)志位來決定。大體說來:
◆ ARM——這是ARM自身的32 位指令集
◆ Thumb ——這是一個(gè)全16 位的指令集,在16 位外部數(shù)據(jù)總線寬度下,這個(gè)指令集的效率要比32 位的ARM指令高一些。
◆ Jazelle ——這是一個(gè)8位指令集,用來加速Java字節(jié)碼的執(zhí)行
整個(gè)ARM指令集由數(shù)據(jù)處理指令、分支指令、Load-Store指令、程序中斷指令和一些系統(tǒng)控制指令構(gòu)成,除了Load-Store指令外,其他部分和x86指令集是比較類似的。但和x86相比,ARM指令最顯著的特點(diǎn)它們都是32-bit 定長的。另外,由于arm是基于RISC指令集的,所以CPU只處理在寄存器中的數(shù)據(jù)并通過獨(dú)立的load-store指令在內(nèi)存和寄存器之間進(jìn)行數(shù)據(jù)的傳遞。
在使用方面,ARM指令的格式也要比Intel的復(fù)雜些。一般說來,一條ARM指令有如下的形式:
{S} [Rd], [Rn], [Rm]
其中:
* {S} —— 加上這個(gè)后綴的指令會更新cpsr 寄存器
* [Rd] —— 目的寄存器
* [Rn]/[Rm] —— 源寄存器
一般來說,arm 指令有3個(gè)操作數(shù),其中Rm寄存器在執(zhí)行指令前可以進(jìn)入桶形移位器進(jìn)行移位操作,而Rn則會直接進(jìn)入ALU 單元。如果一條arm 指令只有2 個(gè)操作數(shù),那么源寄存器按照Rm 來處理。例如,一條加法指令:
add r0, r1, #1
就會把r1+1的結(jié)果存放到r0中。
在熟悉了基本的匯編格式后,讀者就可以自行去查詢基本的ARM匯編指令了,下面,我們找出ARM中比較有特色部分——Load-Store指令結(jié)構(gòu),它是CPU 和內(nèi)存進(jìn)行通信的一個(gè)重要媒介。
Load-Store 指令體系
由于ARM CPU并不直接處理內(nèi)存中的數(shù)據(jù),這個(gè)指令體系就擔(dān)起了在寄存器和內(nèi)存之間交換數(shù)據(jù)的重要媒介。它要比x86 的內(nèi)存訪問機(jī)制復(fù)雜一些。該指令體系分成3 類:
◆ 單寄存器傳輸(這是與x86 最為相像的)
◆ 多寄存器傳輸
◆ 交換指令
單寄存器傳輸
先看第一個(gè),很簡單:把單一的數(shù)據(jù)傳入(LDR) 或傳出(STR)寄存器,對內(nèi)存的訪問可以是DWORD(32-bit), WORD(16-bit)和BYTE(8-bit)。指令的格式如下:
DWORD:
Rd, addressing1
WORD:
H Rd, addressing2 無符號版
SH Rd, addressing2 有符號版
BYTE:
B Rd, addressing1 無符號版
SB Rd, addressing2 有符號版
addressing1 和addressing2 的分類下面再說,現(xiàn)在理解成某種尋址方式就可以了。
在單寄存器傳輸方面,還有以下三種變址模式,他們是:
◆ preindex
這種變址方式和x86的尋址機(jī)制是很類似的,先對寄存器進(jìn)行運(yùn)算,然后尋址,但是在尋之后,基址寄存器的內(nèi)容并不發(fā)生改變,例如:
ldr r0, [r1, #4]
的含義就是把r1+4 這個(gè)地址處的DOWRD 加載到r0,而尋址后,r1 的內(nèi)容并不改變。
◆ preindex with writeback
這種變址方式有點(diǎn)類似于++i的含義,尋址前先對基地址寄存器進(jìn)行運(yùn)算,然后尋址. 其基本的語法是在尋址符[]后面加上一個(gè)"!" 來表示.例如:
ldr r0, [r1, #4]!
就可以分解成:
add r1, r1, #4
ldr r0, [r1, #0]
◆ postindex
自然這種變址方式和i++的方式就很類似了,先利用基址寄存器進(jìn)行尋址,然后對基址寄存器進(jìn)行運(yùn)算,其基本語法是把offset 部分放到[]外面,例如:
ldr r0, [r1], #4
就可以分解成:
ldr r0, [r1, #0]
add r1, r1, #4
如果你還記得x86 的SIB 操作的話,那么你一定想ARM是否也有,答案是有也沒有。在ss上面提到的addressing1 和addressing2的區(qū)別就是比例寄存器的使用,addressing1可以使用[base, scale, 桶形移位器]來實(shí)現(xiàn)SB 的效果,或者通過[base,offset](這里的offset 可以是立即數(shù)或者寄存器)來實(shí)現(xiàn)SI 的效果,而addressing2則只能用后者了。于是每一種變址方式最多可以有3 種尋址方式,這樣一來,最多可以有9種用來尋址的指令形式。例如:
ldr r0, [r1, r2, LSR #0x04]!
ldr r0, [r1, -#0x04]
ldr r0, [r1], LSR #0x04
每樣找了一種,大概就是這個(gè)意思。到此,單寄存器傳輸就結(jié)束了,掌握這些足夠應(yīng)付差事了。下面來看看多寄存器傳輸吧。
多寄存器傳輸
說得很明白,意思就是通過一條指令同時(shí)把多個(gè)寄存器的內(nèi)容寫到內(nèi)存或者從內(nèi)存把數(shù)據(jù)寫到寄存器中,效率高的代價(jià)是會增加系統(tǒng)的延遲,所以armcc 提供了一個(gè)編譯器選項(xiàng)來控制寄存器的個(gè)數(shù)。指令的格式有些復(fù)雜:
<尋址模式> Rn{!}, {r^}
我們先來搞明白尋址模式,多寄存器傳輸模式有4 種:
也就是說以A開頭的都是在Rn的原地開始操作,而B開頭的都是以Rn的下一個(gè)位置開始操作。如果你仍然感到困惑,我們不妨看個(gè)例子。
所有的示例指令執(zhí)行前:
mem32[0x1000C] = 0x04
mem32[0x10008] = 0x03
mem32[0x10004] = 0x02
mem32[0x10000] = 0x01
r0 = 0x00010010
r1 = 0x00000000
r3 = 0x00000000
r4 = 0x00000000
1) ldmia r0!, {r1-r3} 2) ldmib r0!, {r1-r3}
執(zhí)行后: 執(zhí)行后:
r0 = 0x0010001C r0 = 0x0010001C
r1 = 0x01 r1 = 0x02
r2 = 0x02 r2 = 0x03
r3 = 0x03 r3 = 0x04
至于DA 和DB 的模式,和IA / IB 是類似的,不多說了。
最后要說的是,使用ldm 和stm指令對進(jìn)行寄存器組的保護(hù)是很常見和有效的功能。配對方案:
stmia / ldmdb
stmib / ldmda
stmda / ldmib
stmdb / ldmia
繼續(xù)來看兩個(gè)例子:
執(zhí)行前:
r0 = 0x00001000
r1 = 0x00000003
r2 = 0x00000002
r3 = 0x00000001
執(zhí)行的指令:
stmib r0!, {r1-r3}
mov r1, #1 ; These regs have been modified
mov r2, #2
mov r3, #3
當(dāng)前寄存器狀態(tài):
r0 = 0x0000100C
r1 = 0x00000001
r2 = 0x00000002
r3 = 0x00000003
ldmia r0!, {r1-r3}
最后的結(jié)果:
r0 = 0x00001000
r1 = 0x00000003
r2 = 0x00000002
r3 = 0x00000001
另外,我們還可以利用這個(gè)指令對完成內(nèi)存塊的高效copy:
loop
ldmia r9!, {r0-r7}
stmia r10!, {r0-r7}
cmp r9, r11
bne loop
說到這里,讀者應(yīng)該對RISC的Load-Store體系結(jié)構(gòu)有一個(gè)大概的了解了,能夠正確配對使用指令,是很重要的。
評論