1. 程式人生 > >ARM指令集簡介

ARM指令集簡介

指令和偽指令概念

指令
指令指的是CPU機器指令的助記符,是由CPU的指令集提供的,經過編譯之後,會以二進位制機器碼的形式由CPU讀取執行

偽指令
偽指令本質上不是指令,和CPU的機器指令沒有任何關係,只是和指令一起寫在程式碼中而已,是由編譯器環境提供的,其目的是用於指導編譯過程,偽指令經過編譯後不會生成二進位制機器碼,僅僅在編譯階段有效果

指令程式設計風格
ARM官方風格
官方風格指令一般使用大寫,例如:LDR R0,[R1],Windows中常使用這種風格

GUN Linux風格
指令一般使用小寫字母,例如:ldr r0,[r1],Linux環境中常用這種風格

ARM彙編特點

LDR/STR架構
採用RISC架構,CPU本身不能直接讀取記憶體,而需要把記憶體中的資料載入到CPU的通用暫存器中,才能被CPU處理
ldr(load register)將記憶體中的資料載入到通用暫存器
str(store register)將暫存器內容存入記憶體空間
ldr和str組合,可以實現ARM CPU和記憶體的資料交換

8種定址方式
暫存器定址:move r1,r2:把r2的值賦值到r1暫存器中

立即定址:move r0,#0xFF00:把立即數0xFF00賦值給r0暫存器

暫存器移位定址:move r0,r1,lsl #3:把r1左移三位(*8)之後的值賦值給r0暫存器

暫存器間接定址:ldr r1,[r2]:暫存器有中括號,表示記憶體地址對應的資料,所以這裡r2表示一個記憶體地址,[]表示取r2指標對應的資料,這句程式碼的意思是把r2對應的記憶體中的資料賦值給r1

基址變址定址:ldr r1,[r2,#4]:將指標r2的值(記憶體地址)+4之後指向的資料賦值給r1

多暫存器定址:ldmia r1!,{r2 - r7,r12}:這種情況下,r1是一個指標,裡邊存放的記憶體地址,然後以r1裡邊的記憶體地址為基地址,向後以此加1得到{}裡的暫存器數量個記憶體地址,然後將剛才得到的這些記憶體地址指向的變數的值賦值給{}裡的對應位置的暫存器,類似從記憶體中讀取陣列,然後把陣列的元素依次賦值給這些暫存器

堆疊定址:stmfd sp!,{r2 - r7,lr}:和多暫存器類似,區別是將棧SP中連續訪問{}數量個位元組,然後依次賦值給{}裡的暫存器

相對定址:beq flag::flag:標號用於標記標號後面那句指令的地址,常用來表示入口點,函式名就是一個標號,C語言中的goto就可以跳轉到一個標號,在ARM彙編中用指令b flag:就可以跳轉到flag:對應的標號處執行,和beq flag:是一樣的,其原理是相對於PC程式位置暫存器做一個偏移

指令字尾

ARM中的指令可以帶字尾,從而豐富該指令的功能,這種形式叫做指令族,常用的字尾有:
B(byte):功能不變,操作長度變為8位(依賴CPU位數,以下相同)
H(Halfword):功能不變,操作長度變為16位
S(signed):功能不變,運算元變為有符號數
S(S標識):影響CPSR裡的NZCV標識位,

舉例:
ldr指令族:ldrb,ldrh,ldrsb ldrsh,從記憶體中載入指定長度的資料
mov指令族:movs r0,#0,結果是0,賦值會影響CPSR的NZCV標識,將Z位置為1
條件執行字尾
條件執行字尾用於限制該執行執行的,只有在符合條件之後才能夠執行該指令

舉例:moveq r0,r1,如果eq成立,執行mov r0,r1,不成立則該條不執行,和C語言中的條件判斷類似
條件字尾成立與否,不是取決於本條指令,而是取決於之前指令執行後的結果
條件字尾決定了本條指令是否執行,不會影響之前和之後指令
條件字尾和CPSR的NZCV位相關,例如,如果上一句程式碼執行的結果將Z置為1,下一句帶有eq條件字尾的語句就會被執行

多級指令流水線

多級流水線用於增加處理器處理指令的速度,
允許CPU同時非同步的執行多條指令,而非上一條指令全部執行完畢之後才會執行下一條指令
多級可以簡單那理解為把一條指令分為多個步驟來非同步執行,例如:
CPU把一條指令分為[取址,解碼,執行]3個步驟,則為3級指令流水線
第一條指令進行取值操作
第一條指令取值完畢,進入解碼操作,第二條指令緊隨其後就開始執行取值操作
第一條指令解碼完畢,進入執行操作,第二條指令緊接著進入解碼操作,同時第三條指令進入取值操作
第一條指令執行完畢,第二條指令進入執行操作,第三條指令進入解碼操作,第四條指令進入取值操作,依次類推
可見,多級流水線可以提高同時執行指令的數量,從而加速指令執行
需要注意的是,PC指向的是正在取值的指令,而非正在執行的指令,之間的差值就是流水線級數和單位元組長度的乘積,在中斷返回到PC的時候需要注意這個問題
ARM指令
資料處理指令
資料傳輸指令
mov:move,在兩個暫存器之間或者立即數和暫存器之間傳遞資料,將後一個暫存器上的值或者立即數賦值給前一個暫存器
例如:mov r1,r0
mov r1,#0xFF:將立即數0xFF賦值給暫存器r1
mvn:和mov用法一致,區別是mvn會把後一個暫存器的值或者立即數按位取反後賦值給前一個暫存器
例如:mvn r0,#0xFF,則r0的值為0xffffff00(32位資料)
算術運算指令
add:加法運算
sub:減法運算
rsb:反減運算
adc: 帶進位的加法運算
sbc: 帶進位的減法運算
rsc:帶進位的反減指令
邏輯指令
and:與操作
orr:或操作
eor:異或操作
bic:位清除操作
比較指令
cmp:比較大小
cmn:取反比較
tst:按位與運算
teq:按位異或運算
乘法指令
mvl: mla: umull: umlal: smull: smlal:
前導0計數
clz:統計一個數的二進位制位前面有幾個0
CPSR訪問指令
mrs
用於讀取CPSR和SPSR

msr
用於寫CPSR和SPSR

CPSR和SPSR
CPSR是程式狀態暫存器,整個Soc只有一個
SPSR在五種異常模式下各有一個,用於從普通模式進入異常模式的時候,儲存普通模式下的CPSR,在返回普通模式時可以恢復原來的CPSR
跳轉分支指令
b指令: 無條件直接跳轉,沒打算返回
bl指令:跳轉前把返回地址放入lr中,以便返回,常用在函式中
bx指令:跳轉同時切換到ARM模式,用於異常處理的跳轉
記憶體訪問指令
ldr:載入指定記憶體地址的資料到暫存器,按照位元組訪問
str:載入指定暫存器資料到記憶體地址中,按照位元組訪問
ldm:和ldr功能一樣,一次多位元組多暫存器訪問
stm:和str功能一樣,一次多位元組多暫存器訪問
swp:記憶體和暫存器互換指令,一邊讀一邊寫,例如:
swp r1,r2,[r0]:讀取指標r0的資料到r1中,同時把r2的資料賦值給r0指標指向的變數
軟中斷指令
swi(software interrupt),在軟體層模擬產生一箇中斷,這個中斷會傳送給CPU,常用於實現系統呼叫

立即數
非法與合法
ARM指令都是32為,除了指令標記和操作標記外,只能附帶少位數的立即數,所以有非法與合法之分

非法立即數:
合法立即數:經過任意位數的移位後,非0部分可以用8位表示就是合法立即數

協處理器與指令

協處理器
協處理器屬於Soc中另外一顆核心,用於協助主CPU實現某些功能,被主CPU呼叫來執行任務,協處理器和MMU,Cache,TLB有功能和管理上的聯絡
ARM設計可以支援多達16個協處理器,但是一般只實現其中的CP15

協處理器指令
mrc:讀取CP15中的暫存器
mcr:向CP15中的暫存器寫資料
指令用法:mcr{<”cond”>} p15,<”opcode_1”>,<”Rd”>,<”Crn”>,<”Crm”>,{<”opcode_2”>}
opcode_1:對於CP15永遠為0
Rd:ARM通用暫存器
Crn:CP15暫存器,取值範圍c0~c15
Crm:CP15暫存器,一般為c0
opcode_2:省略或者為0
ldm,stm和棧
ldm,stm
ldr與str只能訪問4個位元組,當資料較大的時候,就會明顯的降低效率,這時就需要使用到ldm和stm,ldm與stm是大量的從暫存器與記憶體交換資料的方式,常用於在記憶體和暫存器之間大量讀取和寫入資料:

stmia sp {r0 - r12}:stm表示進行批量資料操作,ia的意思是將r0存入SP的記憶體地址處,然後SP記憶體地址+4(32位),將r1存入該地址,記憶體地址再+4,存入r2,依次存到r12,
這就是一個暫存器和記憶體交換大量資料的示例,在一個週期內完成了多個記憶體地址和多個暫存器的操作。
除了ia字尾,還有多個不同功能的字尾:
ia:increase after,後增加,表示每個操作的時候,先傳輸資料,後增加記憶體地址,
ib:increase before,先增加,表示在每個操作的時候,先增加記憶體地址,再進行資料傳輸
da:decrease after:和ia一樣,差別在於減少地址
db:decrease before:和ib一樣,差別在於減少地址
fd:full decrease:滿遞減堆疊,檢視棧的描述
ed:empty decrease:空遞減堆疊
fa:滿遞增堆疊
ea:空遞增堆疊
操作棧時使用相同的字尾就不會出錯
堆疊(棧)
空棧:棧指標指向空位,每次可以直接存入,然後棧指標(SP)遞增或者遞減1格,取的時候要遞增或者遞減1格才能取出
滿棧:SP指向棧最後1格資料,存入的時候需要先移動1格才能存入,取的時候可以直接取出
增棧:SP向地址增加的方向移動
減棧:SP向地址減少的方向移動
組合起來就可以成為空增/減棧,滿增/減棧。四種形式

彙編中的符號

!號的作用
在彙編中常見”!”符號,究竟是用來幹嘛的呢?
例如:

ldmia r0,{r2 - r3}:資料傳輸完畢之後指標r0的記憶體地址值還是第一次操作之前的值
ldmia r0!,{r2 - r3}:資料傳輸完畢之後指標r0的記憶體地址值是最後一次操作後的值
^號的作用
那麼^號又是什麼作用呢?
例如:

ldmfd sp!,{r0 - r6,pc}
ldmfd sp!,{r0 - r6,pc}^
作用:當目標暫存器中有pc時,會同時將SPSR寫入到CPSR,一般用於從異常返回。

偽指令
偽指令的作用用於指導編譯過程,偽指令並不是指令,編譯後並不會生成二進位制機器碼,偽指令是和具體的編譯環境有關的,我們使用的GNU編譯工具鏈,所以需要使用GNU下的彙編偽指令。

GNU彙編符號
[@,#,//,/~/]:註釋,和C語言的//是一樣的
::冒號,在彙編中以冒號結尾的是標號,標號標記標號後面的指令的地址,
.:點號,代表當前指令的地址,例如:[b .]指令會進入死迴圈
#:#或者$號後面跟著數值,代表一個立即數(不區分進位制)
偽指令
.global _start:給_start外部可連結屬性,可以在外部檔案中訪問_start
.section .text:指定當前段為程式碼段
.ascii .byte .short .long .word .quad .float .string:定義各種型別的資料
.align 4:以16位元組對齊
.balignl 16 0x3C:b表示填充,align表示對齊,l表示long,以4位元組為單位填充,16表示以16位元組對齊,0x3C是用來填充的原料
.equ:巨集定義
.end:表示一個檔案的結束
.include:用於包含標頭檔案
.arm / .code32:宣告以下的程式碼是arm指令
.thumb / .code16:宣告以下的程式碼是thumb指令
比較重要的偽指令
nop:空操作,什麼也不執行
ldr:大範圍地址載入指令,把記憶體地址載入到暫存器中,和ldr指令是有區別的
ldr指令附帶立即數要考慮合法性問題,只能帶合法立即數,帶的立即數字首#號
ldr偽指令,不用考慮立即數合法問題,帶的立即數前面是一個=號,藉助了編譯器環境文字池的幫助,幫忙載入非法立即數
ldr可以載入的地址比較廣,載入的地址和連結時給的地址有關,由連線指令碼決定,在連結時確定,編譯時會被mov或者以文字池方式處理
adr:小範圍地址載入指令,可載入的範圍比較小
adr:總是以P C為基準來表示地址,則該地址為相對地址,要在執行的時候才能確定,因此該指令和執行地址有關,可以用於檢測程式當前執行地址在哪裡,編譯時會被sub或者add替代
adrl:中等範圍地址載入指令