學習ARM,就必須要學習ARM指令,ARM指令是CPU提供給我們的接口,是我們打開CPU這個潘多拉魔盒的鑰匙。
ARM指令有很多,為了讓大家能快速上手,一口君整理了一些對我們最有幫助的指令。keil軟件的操作,可以參考第一章。
讓我們開始吧!
0.指令分類
-
數據處理指令 數據處理指令可分為數據傳送指令、算術邏輯運算指令和比較指令等。
-
數據傳送指令用于在寄存器和存儲器之間進行數據的雙向傳輸。
-
算術邏輯運算指令完成常用的算術與邏輯的運算,該類指令不但將運算結果保存在目的寄存器中,同時更新CPSR中的相應條件標志位。
一、MOV指令
1、MOV
語法:
MOV{條件}{S} 目的寄存器,源操作數
功能:MOV指令完成從另一個寄存器、被移位的寄存器或將一個立即數加載到目的寄存器。其中S選項決定指令的操作是否影響CPSR中條件標志位的值,當沒有S時指令不更新CPSR中條件標志位的值。
指令示例:
MOV r0, #0x1 ;將立即數0x1傳送到寄存器R0
MOV R1,R0 ;將寄存器R0的值傳送到寄存器R1
MOV PC,R14 ;將寄存器R14的值傳送到PC,常用于子程序返回
MOV R1,R0,LSL #3 ;將寄存器R0的值左移3位后傳送到R1
【注:不區分大小寫】
思考,為什么以下賦值出錯?
MOV R0,#0xfff
錯誤log
要想搞懂這個問題,我們需要了解什么是立即數。
2. 什么是立即數?
立即數是由 0-255之間的數據循環右移偶數位生成。
判斷規則如下:
- 把數據轉換成二進制形式,從低位到高位寫成4位1組的形式,最高位一組不夠4位的,在最高位前面補0。
- 數1的個數,如果大于8個肯定不是立即數,如果小于等于8進行下面步驟。
- 如果數據中間有連續的大于等于24個0,循環左移2的倍數,使高位全為0。
- 找到最高位的1,去掉前面最大偶數個0。
- 找到最低位的1,去掉后面最大偶數個0。
- 數剩下的位數,如果小于等于8位,那么這個數就是立即數,反之就不是立即數。
而例子中的數是0xfff,我們來看下他的二進制:
0000 0000 0000 0000 0000 1111 1111 1111
按照上述規則,我們最終操作結果如下:
1111 1111 1111
可以看到剩余的位數大于8個,所以該數不是立即數。為什么立即數會有這么個限定?我們需要從MOV這條指令的機器碼來說起。
3. MOV機器碼
讓我們執行下面代碼:
AREA Example,CODE,READONLY ;聲明代碼段Example
ENTRY ;程序入口
Start
//測試代碼,添加在以下位置即可,后面不再貼完整代碼
mov r1,#0x80000001
OVER
END
然后點擊debug按鈕,查看對應的機器碼:
機器指令
得到mov r1,#0x80000001指令的機器碼是E3A01106
我們來分析這個機器碼。
MOV機器指令格式
用ARM指令助記符表示為:
<opcode> {<cond>} {S} <Rd>, <Rn>, <shift_op2>
每個域的含義如下:
1) {<cond>}:條件碼域
指令允許執行的條件編碼。花括號表示此項可缺省。
ARM指令的一個重要特點是可以條件執行,每條ARM指令的條件碼域包含4位條件碼,共16種。幾乎所有指令均根據CPSR中條件碼的狀態和指令條件碼域的設置有條件的執行。當指令執行條件滿足時,指令被執行,否則被忽略。指令條件碼及其助記符后綴表示參見下表。
指令的條件碼
每種條件碼可用兩個字符表示,這兩個字符可以作為后綴添加在指令助記符的后面和指令同時使用。
例如:跳轉指令B可以加上后綴EQ變為BEQ,表示“相等則跳轉”,即當CPSR中的Z標志置位時發生跳轉。
2) <opcode>:操作碼域
指令編碼的助記符;
3) {S} :條件碼設置域
這是一個可選項,當在指令中設置{S}域時,指令執行的結果將會影響程序狀態寄存器CPSR中相應的狀態標志。例如:
ADD R0,R1,R2;R1與R2的和存放到R0寄存器中,不影響狀態寄存器 ADDS R0,R1,R2; 執行加法的同時影響狀態寄存器
指令中比較特殊的是CMP指令,它不需要加S后綴就默認地根據計算結構更改程序狀態寄存器。
4) <Rd>:目的操作數
ARM指令中的目的操作數總是一個寄存器。如果與第一操作數寄存器相同,也必須要指明,不能缺省。
5) <Rn>:第一操作數
ARM指令中的第一操作數也必須是個寄存器。
6) <shift_op2>:第二操作數
在第二操作數中可以是寄存器、內存存儲單元或者立即數。
如果是立即數:
bit:[11-8]表示操作數向左移動的位數/2,bit:[7-0]表示最終的操作數
根據MOV指令格式,我們分析各個位域的值:
立即數0x80000001二進制為:
1000 0000 0000 0000 0000 0000 0000 0001
循環左移2位后得到以下結果:
00 0000 0000 0000 0000 0000 0000 0001 10
所以shifter的值為2/2=1,操作數的值為0000 0110。
二、移位操作
ARM微處理器支持數據的移位操作,移位操作在ARM指令集中不作為單獨的指令使用,它只能作為指令格式中是一個字段,在匯編語言中表示為指令中的選項。移位操作包括如下6種類型,ASL和LSL是等價的,可以自由互換:
1) LSL(或ASL)邏輯(算術)左移
尋址格式:
通用寄存器,LSL(或ASL) 操作數
完成對通用寄存器中的內容進行邏輯(或算術)的左移操作,按操作數所指定的數量向左移位,低位用零來填充。其中,操作數可以是通用寄存器,也可以是立即數(0~31)。如:
MOV R0, R1, LSL#2 ;將R1中的內容左移兩位后傳送到R0中。
2) LSR邏輯右移
尋址格式:
通用寄存器,LSR 操作數
完成對通用寄存器中的內容進行右移的操作,按操作數所指定的數量向右移位,左端用零來填充。其中,操作數可以是通用寄存器,也可以是立即數(0~31)。如:
MOV R0, R1, LSR #2 ;將R1中的內容右移兩位后傳送到R0中,左端用零來填充。
3) ASR算術右移
尋址格式:
通用寄存器,ASR 操作數
完成對通用寄存器中的內容進行右移的操作,按操作數所指定的數量向右移位,左端用第31位的值來填充。其中,操作數可以是通用寄存器,也可以是立即數(0~31)。如:
MOV R0, R1, ASR #2 ;將R1中的內容右移兩位后傳送到R0中,左端用第31位的值來填充。
4) ROR循環右移
尋址格式:
通用寄存器,ROR 操作數
完成對通用寄存器中的內容進行循環右移的操作,按操作數所指定的數量向右循環移位,左端用右端移出的位來填充。其中,操作數可以是通用寄存器,也可以是立即數(0~31)。顯然,當進行32位的循環右移操作時,通用寄存器中的值不改變。如:
MOV R0, R1, ROR #2 ;將R1中的內容循環右移兩位后傳送到R0中。
5) RRX帶擴展的循環右移
尋址格式:
通用寄存器,RRX 操作數
完成對通用寄存器中的內容進行帶擴展的循環右移的操作,按操作數所指定的數量向右循環移位,左端用進位標志位C來填充。其中,操作數可以是通用寄存器,也可以是立即數(0~31)。如:
MOV R0, R1, RRX #2 ;將R1中的內容進行帶擴展的循環右移兩位后傳送到R0中。
舉例
; 第二操作數 寄存器移位操作, 5種移位方式, 9種語法
;邏輯左移
mov r0, #0x1
mov r1, r0, lsl #1 ; 移位位數1-31肯定合法
mov r0, #0x2
mov r1, r0, lsr #1 ; 邏輯右移
mov r0, #0xffffffff
mov r1, r0, asr #1 ; 算術右移符號位不變, 次高位補符號位
mov r0, #0x7fffffff
mov r1, r0, asr #1
mov r0, #0x7fffffff
mov r1, r0, ror #1 ; 循環右移
mov r0, #0xffffffff
mov r1, r0, rrx ; 唯一不需要指定循環位數的移位方式
;帶擴展的循環右移
;C標志位進入最高位,最低位進入C 標志位
; 移位值可以是另一個寄存器的值低5bit, 寫法如下
mov r2, #1
mov r0, #0x1
mov r1, r0, lsl r2 ; 移位位數1-31肯定合法
mov r0, #0xffffffff
mov r1, r0, asr r2 ; 算術右移符號位不變, 次高位補符號位
mov r0, #0x7fffffff
mov r1, r0, asr r2
mov r0, #0x7fffffff
mov r1, r0, ror r2 ; 循環右移
上述結果不再截圖,讀者可以自行拷貝到keil中進行debug,查看寄存器中值以及符號位的變化。
三、CMP比較指令
語法
CMP{條件} 操作數1,操作數2
CMP指令用于把一個寄存器的內容和另一個寄存器的內容或立即數進行比較,同時更新CPSR中條件標志位的值。該指令進行一次減法運算,但不存儲結果,只更改條件標志位。cmp是做一次減法,并不保存結果,僅僅用來產生一個邏輯,體現在改變cpsr相應的condition位。
標志位表示的是操作數1與操作數2的關系(大、小、相等), 指令示例:
CMP R1,R0 ;將寄存器R1的值與寄存器R0的值相減,并根據結果設置CPSR的標志位CMP R1,#100 ;將寄存器R1的值與立即數100相減,并根據結果設置CPSR的標志位
四、TST條件指令
語法
TST{條件} 操作數1,操作數2
TST指令用于把一個寄存器的內容和另一個寄存器的內容或立即數進行按位的與運算,并根據運算結果更新CPSR中條件標志位的值。操作數1是要測試的數據,而操作數2是一個位掩碼,根據測試結果設置相應標志位。當位與結果為0時,EQ位被設置。 指令示例
TST R1,#%1 ;用于測試在寄存器R1中是否設置了最低位(%表示二進制數)。
比較指令和條件執行舉例
例1:找出三個寄存器中數據最大的數
mov r0, #3
mov r1, #4
mov r2, #5
cmp r1,r0
movgt r0,r1
cmp r2,r0
movgt r0,r2
例2:求兩個數的差的絕對值
mov r0,#9
mov r1,#15
cmp r0,r1
beq stop
subgt r0,r0,r1
sublt r1,r1,r0
帶條件碼的指令執行請參考本篇表格《指令的條件碼》
五、數據的處理指令
ADD
ADD{條件}{S} 目的寄存器,操作數1,操作數2
ADD指令用于把兩個操作數相加,并將結果存放到目的寄存器中。操作數1應是一個寄存器,操作數2可以是一個寄存器,被移位的寄存器,或一個立即數。指令示例:
ADD R0,R1,R2 ;R0 = R1 + R2ADD R0,R1,#256 ;R0 = R1 + 256ADD R0,R2,R3,LSL#1 ;R0 = R2 + (R3 << 1)
ADC
注意這個指令不是射手。。。。
除了正常做加法運算之外,還要加上CPSR中的C條件標志位,如果要影響CPSR中對應位,加后綴S。
SUB
SUB指令的格式為:
SUB{條件}{S} 目的寄存器,操作數1,操作數2
SUB指令用于把操作數1減去操作數2,并將結果存放到目的寄存器中。操作數1應是一個寄存器,操作數2可以是一個寄存器,被移位的寄存器,或一個立即數。該指令可用于有符號數或無符號數的減法運算。
如:
SUB R0,R1,R2 ;R0 = R1 - R2SUB R0,R1,#256 ;R0 = R1 - 256SUB R0,R2,R3,LSL#1 ;R0 = R2 - (R3 << 1)
SBC
除了正常做加法運算之外,還要再減去CPSR中C條件標志位的反碼 根據執行結果設置CPSR對應的標志位 AND指令的格式為:
AND{條件}{S} 目的寄存器,操作數1,操作數2
AND指令用于在兩個操作數上進行邏輯與運算,并把結果放置到目的寄存器中。操作數1應是一個寄存器,操作數2可以是一個寄存器,被移位的寄存器,或一個立即數。該指令常用于屏蔽操作數1的某些位。如:
AND R0,R0,#3 ; 該指令保持R0的0、1位,其余位清零。
ORR
ORR指令的格式為:
ORR{條件}{S} 目的寄存器,操作數1,操作數2
ORR指令用于在兩個操作數上進行邏輯或運算,并把結果放置到目的寄存器中。操作數1應是一個寄存器,操作數2可以是一個寄存器,被移位的寄存器,或一個立即數。該指令常用于設置操作數1的某些位。如:
ORR R0,R0,#3 ; 該指令設置R0的0、1位,其余位保持不變。
BIC
這是一個非常實用的指令,在實際寄存器操作經常要將某些位清零,但是又不想影響其他位的值,就可以使用該命令。
BIC指令的格式為:
BIC{條件}{S} 目的寄存器,操作數1,操作數2
BIC指令用于清除操作數1的某些位,并把結果放置到目的寄存器中。
操作數1應是一個寄存器,操作數2可以是一個寄存器,被移位的寄存器,或一個立即數。操作數2為32位的掩碼,如果在掩碼中設置了某一位,則清除這一位。未設置的掩碼位保持不變。
如:
BIC R0,R0,#%1011 ; 該指令清除 R0 中的位 0、1、和 3,其余的位保持不變。
數據處理指令舉例
- 加法運算
mov r0, #1
mov r1, #2
add r2, r0, r1 ; r2 = r0 + r1
add r2, r0, #4
add r2, r0, r1, lsl #2 ; r2 = r0 +R1<<2; (R0 + R1*4)
- adc,64位加法運算的實現
; 2. adc 64位加法 r0, r1 = r0, r1 + r2, r3
mov r0, #0
mov r1, #0xffffffff
mov r2, #0
mov r3, #0x1
adds r1, r1, r3 ; r1 = r1 + r3 必須加S后綴
adc r0, r0, r2 ; r0 = r0 + r2 + c ;add 帶 擴展的加法
可以對比下add和adds,沒有加s的話是不會影響條件位的。
- 減法
; 3. sub rd = rn - op2
mov r0, #1
sub r0, r0, #1 ; r0 = r0 - 1
- 64位減法
; 4. sbc 64位減法 r0, r1 = r0, r1 - r2, r3
; cpsr c 對于加法運算 C = 1 則代表有進位, C = 0 無進位
; 對于減法運算 C = 1 則代表無借位, C = 0 有借位
mov r0, #0
mov r1, #0x0
mov r2, #0
mov r3, #0x1
subs r1, r1, r3
sbc r0, r0, r2 ;sbc 帶擴展的減法
- 位清除
; 5. bic 位清除
mov r0, #0xffffffff
bic r0, r0, #0xff ; and r0, r0, #0xffffff00
執行結果
執行結果
六、跳轉指令
跳轉指令用于實現程序流程的跳轉,在ARM程序中有兩種方法可以實現程序流程的跳轉:
-
使用專門的跳轉指令;
-
直接向程序計數器PC寫入跳轉地址值,通過向程序計數器PC寫入跳轉地址值,可以實現在4GB的地址空間中的任意跳轉,在跳轉之前結合使用。
使用以下指令,可以保存將來的返回地址值,從而實現在4GB連續的線性地址空間的子程序調用。
MOV LR,PC
ARM指令集中的跳轉指令可以完成從當前指令向前或向后的32MB的地址空間的跳轉,包括以下4條指令:
B 跳轉指令
BL 帶返回的跳轉指令
BLX 帶返回和狀態切換的跳轉指令thumb指令
BX 帶狀態切換的跳轉指令thumb指令
1. B 指令
指令的格式為:
B{條件} 目標地址
B指令是最簡單的跳轉指令。一旦遇到一個 B 指令,ARM 處理器將立即跳轉到給定的目標地址,從那里繼續執行。
B label 程序無條件跳轉到標號label處執行
CMP R1 ,#0
BEQ label 當CPSR寄存器中的Z條件碼置位時,程序跳轉到標號Label處執行。
2. BL 指令
BL 指令的格式為:
BL{條件} 目標地址
BL是另一個跳轉指令,但跳轉之前,會在寄存器R14中保存PC當前值,因此,可以通過將R14 的內容重新加載到PC中,來返回到跳轉指令之后的那個指令處執行。該指令是實現子程序調用的一個基本但常用的手段。
BL label 當程序無條件跳轉到標號Label處執行時,同時將當前的PC值保存到R14中
子函數要返回執行以下指令即可:
MOV PC,LR
3. BL指令機器碼
語法:
Branch : B{<cond>} labelBranch with Link : BL{<cond>} subroutine_label
BL機器碼格式如下:
b指令機器碼
各域含義:
其中offset是24個bite,最高位包含一個符號位,1個單位表示偏移一條指令,所以可以尋址±2^23^條指令,即±8M條指令。
而一條指令是4個字節,所以最大尋址空間為±32MB的地址空間。
我們來看下以下代碼:
AREA Example,CODE,READONLY
ENTRY ;程序入口
Start
MOV R0,#0
MOV R1,#10
BL ADD_SUM
B OVER
ADD_SUM
ADD R0,R0,R1
MOV PC,LR
OVER
END
BL機器碼
由上圖所示:
- 第6行代碼BL ADD_SUM 會跳轉到第8行,即第9行的代碼
- 第6行的指令的機器碼是EB000000
根據BL的機器碼我們可以得到offset的值是0x000000,也就是說該指令跳轉本身,而根據我們的分析第6行代碼,應該是向前跳轉2條指令,按道理offset是應該是2,為什么是0呢?
因為是3級流水線,所以pc存儲指令地址與正在處理指令地址之間相差8個字節,pc的地址是預取指令地址,而不是正在執行的指令的地址。
4. 如何訪問全部32-bit地址空間?
可以手動設置LR寄存器,然后裝載到PC中。
MOV lr, pcLDR pc, =dest
在編譯項目過程中,ARM連接器(linker)會自動為長跳轉(超過32Mb范圍)。
ldr下一章會詳細詳細講解。
舉例
子函數多重嵌套調用,如何從子函數返回?
area first, code, readonly
code32
entry
main
; bl 指令, 子函數調用
mov r0,#1
bl child_func
mov r0,#2
stop
b stop
child_func
mov r1,r0
mov r2,lr
mov r0, #3 //<=== pc
bl child_func_2
mov r0,#4
mov r0,r1
mov lr,r2
mov pc, lr
child_func_2 ;葉子函數
mov r3,r0
mov r4,lr ; 保存直接父函數用到的所有寄存器
mov r0, #5
mov r0,r3
mov lr,r4 ;返回到直接父函數之前,把它用到的所有寄存器內容恢復
mov pc, lr
end
由上述例子所示,每調用一級子函數,我們都把返回地址存入到未分組寄存器中,但是未分組寄存器畢竟是有限的,像Linux內核函數的調用層次往往很深,通用寄存器根本不夠用,要想保存返回地址,就需要對數據進行壓棧,那我們就要為每個模式的棧設置空間,那如何設置棧空間呢?下一篇我們繼續討論。