一,ARM匯編語言立即數的表示方法
十六進制:前綴:0x
十進制:無前綴
二制:前綴:0b
二,常用的ARM指令(標准的ARM語法,GNU的ARM語法)
1、@M開頭系列
MOV R0, #12 @R0 = 0XFF。(1)、寄存器或寄存器之間的數據傳送 MVF = MOV
MOV R0, R0
MOV R0, R0, LSL#2 @OR =ASL#2, LSR/ASR/ROR/RRX,(2)、移位操作
MVN R0, #4 @數據取反傳送 R0 = -5
MVN R0, #0
@MOV R0, #0X88888888 這樣是不合法的,mov操作立即數范圍為0~512,可以通過使用LDR/STR的偽指令實現將大的立即數或內存寫入寄存器,如下
LDR R0, =0X88888888
@MSR CPSR, R0
MSR SPSR, R0
MSR CPSR_flg, R0
MSR CPSR_flg, #1<<28
MUL R0, R1, R2
@MOV PC, R14 @(3)、程序跳轉(默認模式為從程序第一句到當前這一句循環執行)
@MOVS PC, R14 @(4)、S->C 拷貝,實現從異常中斷返回
@MOVS PC, LR @這三句在這里是等價的
2、ADD、ADDS、ADC、ADCS
ADD指令將<shifter_operand>表示的數據加上寄存器<Rn>的值,將結果保存到目標寄存器<Rd>中,並根據指令的執行結果更新CPSR中相應的條件標志位。
@兩個64位數相加
LDR R1, =0X10101010
LDR R2, =0X10010010
LDR R3, =0X20202020
LDR R4, =0X20020020
ADDS R5, R1, R3 @加低位的字
ADCS R6, R2, R4 @加高位的字,帶進位
ADC指令常用於實現兩個數的相加,無進位時為C=0
3、SUB、SBC、RSB、RSC
SUB指令的作用是將寄存器<Rn>中的數值減去<shifter_operand>所表示的數值,將運算結果保存到目標寄存器<Rd>中,並根據指令的執行結果更新CPSR中的條件標志位。
SUB指令常用於實現兩個數的相減,若結果>=0,則C位為1,若結果為0,則Z位為1
SBC指令的作用是將寄存器<Rn>中的值減去<shifter_operand>表示的數值,再減去寄存器CPSR中C條件標志位的反碼[NOT (Carry Flag)],將結果保存到目標寄存器<Rd>中,並根據指令的執行結果更新CPSR中相應的條件標志位。
SBC指令常與SUB指令聯合使用,可以實現兩個64位數的減法。
@減法***************************
SUB R0, R1, R2 @ R0 = R1 - R2 不帶借位的減法
SUB R0, R1, #255 @ R0 = R1 - 256
SUB R0, R2, R3, LSL#1 @ R0 = R2 - (R3 << 1)
SBC R0, R1, R2 @ R0 = R1 - R2 -!CARRY 帶借位的解減法
RSB R0, R1, R2 @ R0 = R2 - R2 反向減法
RSC R3, R4, R5 @ R3 = R5 - R4 - !CARRY 帶借位的反向減法
4、 AND、EOR、ORR、BIC、
*AND指令將<shifter_operand>表示的數值與寄存器<Rd>的值按位做“與”運算,將將結果保存到目標寄存器<Rd>中,同時根據操作的結果更新CPSR寄存器中的條件標志位。
(1) 指令的語法格式
AND{<cond>}{S} <Rd>, <Rn>, <shifter_operand>
(2) 指令舉例
AND R0, R0, #0x03 ;R0寄存器中的0、1位不變,其它位清零
AND R2, R1, R3 ;R2 = R1 & R3
ANDS R0, R0, #0x1 ;R0 = R0 & 0x1
*EOR(Exclusive OR)指令將寄存器<Rn>中的值和<shifter_operand>表示的值按位進行“異或”操作,並將執行結果保存中目標寄存器<Rd>中,同時根據指令的執行結果更新CPSR中相應的條件標志位。
(1) 指令的語法格式
EOP{<cond>}{S} <Rd>, <Rn>, <shifter_operand>
(2) EOR指令舉例
EOR R0, R0, #3 ;反轉R0的第0位和第1位
EOR R1, R1, #0x0F ;將R1的低4位取反
EOR R2, R1, R0 ;R2 = R1 ^ R0
EORS R0, R5, #0x1 ;將R5和0x1進行邏輯異或,結果保存在R0中,並根據執行結果更新標志位。
*ORR(logical OR)為按位或指令,它將第2個源操作數<shifter_operand>表示的值與寄存器<Rn>表示的值按位做“或”操作,結果保存到<Rd>寄存器中。
(1) 指令的語法格式
ORR{條件}{S} <dest>, <op 1>, <op 2>
(2)指令的例子
ORR R0, R0, #3 ;將R0的第1、3位設置成1
ORR R0, R0, #0xFF ;將R0的低4位置1
*BIC(Bit Clear)位清零指令,將寄存器<Rn>的值與第2個源操作數<shifter_operand>的值的反碼按位做“邏輯與”運算,結果保存到寄存器<Rd>中
(1) 指令的語法格式
BIC{條件}{S} <Rd>, <op 1>, <op 2>
(2)指令例子
BIC R0, R0, #0x1011 ;清零R0中的第0、1、3位,其余位保持不變
BIC R1, R2, R3 ;將R3的反碼與R2進行邏輯與,結果保存到R1中
5、 CMP、CMN、TST、TEQ
*CMP(Compare)指令的實質是使用寄存器<Rn>的值減去<shifter_operand>表示的值,根據操作結果更新CPSR中相應的條件標志位,以便后面的指令根據相應的條件標志來判斷是否執行。
(1) 指令的語法格式
CMP{<cond>} <Rn>, <shifter_operand>
(2) CMP使用舉例
CMP指令允許把一個寄存器中的數值和另一個寄存器中的數值或立即數進行比較,比較結果將更新狀態寄存器CPSR中對應的標志,從而后續的指令可以根據狀態寄存器中的標志位有條件的執行。它的實質是進行了一次減法運算,但不保留結果,而是只更改CPSR中的條件標志位,標志位表示的是操作數1與操作數2比較的結果(其值可能大於,小於或等於),比如操作數1大於操作數2,則此后的帶有GT后續指令會被執行。
顯然,CMP不需要額外再添加S后綴來指示其更改狀態標志位。
CMP R1, #10 ;比較R1和立即數10並更新相關的狀態標志位
CMP R1, R2 ;比較寄存器R1和R2中的值並設置相關的標志位。
通過上面的例子可以 看出,CMP指令與SUBS指令的區別在於CMP指令並不保留運算結果,在進行兩個數大小的比較時,應常用CMP指令及相的條件標志位來確定后續的指令是否執行。
*CMN:(Compare Negative)比較取負的值
CMN{條件}{P} <op 1>, <op 2>
status = op_1 - (- op_2)
CMN 同於 CMP,但它允許你與小負值(操作數 2 的取負的值)進行比較,比如難於用其他方法實現的用於結束列表的 -1。這樣與 -1 比較將使用:
CMN R0, #1 ; 把 R0 與 -1 進行比較
*TST(Test)測試指令用於將一個寄存器的值和一個算術值進行邏輯“與”運算,條件標志位根據兩個操作數做“邏輯與”的結果設置
(1)TST指令的語法格式
TST {<cond>} <Rn>, <shifter_operand>
(2) TST 指令舉例 TST指令類似於CMP指令,也不會把結果保存到目標寄存器,而是在給出的兩個操作數上進行與運算,把結果反映在狀態寄存器的標志位上。使用TST指令來檢查是否設置了特定的位,操作數1是要測試的數據,而操作數2是一個位掩碼,經過測試后,如果匹配則設置Zero標志,否則清除它,與CMP指令一樣,該指令不需要S后綴。下面的指令用於測試R0中的最低位是否為0
TST R0, #1
*TEQ(Test Equivalence)指令用於將一個寄存器的值和一個算術值做比較,條件標志位根據兩個操作數做“邏輯異或”后的結果設置,以便后續的指令根據相應的條件標志位來判斷是否執行。比較兩個數是否相等不影響V位和C位。
(1) 指令的語法格式
TEQ {<cond>} <Rn>, <shifter_operand>
(2) 指令舉例
比較R0和R1是否相等,該指令不影響CPSR中的V位和C位。
TEQ R0, R1
使用TEQ進行相等測試,常與EQ和NE條件碼配合使用,當兩個數相等時,條件碼EQ有效,否則條件碼NE有效。
6、跳轉指令
跳轉指令B與BL都可以使程序跳轉到指定的地址執行程序。指令BL的作用是跳轉的同時將下一條指令的地址復制到R14(即返回地址連接寄存器LR)寄存器中。需要注意的是,這兩條指令和目標地址處的指令都要屬於ARM指令集。兩條指令都可以根據CPSR中的條件標志位的值決定指令是否執行。
MOVEQ PC, LR
B LAB1
(1)指令格式
B {L} {<cond>} <target_address>
(2)指令的例子
循環10次的例子
MOV R1, #0
BL
MOV R2, #1
......
LAB1:
ADD R1, R1, #1
CMP R1, #10
@帶連接的分支
load_new_format:
BL switch_screen_mode
BL get_screen_info
BL load_palette
new_loop:
MOV R1, R5
BL read_byte
CMP R0, #255
BLEQ read_loop
STRB R0, [R2, #1]!
7、Load/Store指令
*LDR指令
(1)指令語法格式
LDR指令用於從內存中將一個32位的字讀取到目標寄存器。
指令的編碼格式如圖所示。
LDR指令編碼格式
LDR{<cond>} <Rd>,<addr_mode>
(2)指令舉例
LDR r1,[r0,#0x12] ;將r0+12地址處的數據讀出,保存到r1中(r0的值不變)
LDR r1,[r0] ;將r0地址處的數據讀出,保存到r1中(零偏移)
LDR r1,[r0,r2] ;將r0+r2地址的數據讀出,保存到r1中(r0的值不變)
LDR r1,[r0,r2,LSL #2] ;將r0+r2×4地址處的數據讀出,保存到r1中(r0,r2的值不變)
LDR Rd,label ;label為程序標號,label必須是當前指令的±4KB范圍內
LDR Rd,[Rn],#0x04 ;Rn的值用作傳輸數據的存儲地址。在數據傳送后將偏移量0x04與
Rn相加,結果寫回到Rn中。Rn不允許是r15
注意:(1)地址對齊問題:大多數情況下,必須保證用於32位傳送的地址是32位對齊的。
(2)LDR有兩種形式,一種是指令,一種是偽指令,使用LDR的偽指令時,在第二個操作數前加"="
*STR指令用於將一個32位的字寫入到指令中指定的內存單元
(1) 指令的語法格式
STR {<cond>} <Rd>, <addr_mode>
(2) 指令舉例
LDR/STR指令用於對內存變量的訪問、內存緩沖區數據的訪問、查表、外圍部件的控制操作等。
① 變量訪問
NumCount EQU 0x40003000 ;定義變量NumCount
LDR R0,=NumCount ;使用LDR偽指令裝載NumCount的地址到R0
LDR R1,[R0] ;取出變量值
ADD R1,R1,#1 ;NumCount=NumCount+1
STR R1,[R0] ;保存變量
8、單數據交換指令
單數據交換指令是Load/Store指令的一種特例,它把一個內存單元中的內容與寄存器中的內容進行交換,交換指令是一個原子操作,也就是說,在連續的總線操作中讀/寫一個存儲單元,在操作期間阻止其他任何指令對該存儲單元的讀/寫。
SWP指令一般有兩種形式:
(1), SWP 字交換 tmp=mem32[Rn]; mem32[Rn] = Rm; Rd = tmp
指令的格式:
SWP {<cond>} <Rd>, <Rm>, [<Rn>]
SWP R1, R1, [R0] ;將R1的內容與R0指向的存儲單元內容進行交換。
(2), SWPB 字節交換
9、狀態寄存器傳輸指令
ARM指令集提供了兩條指令,用於讀寫程序狀態寄存器,MRS指令用於把CPSR或SPSR的值傳送到一個寄存器中;MSR相反,把一個寄存器的內容傳送到CPSR或SPSR中,這兩條指令結合起來,可用於對CPSR和SPSR進行讀/寫操作。
MRS 把程序狀態寄存器的值傳送給一個通用寄存器, Rd=SPSR
MSR 把通用寄存器的值傳送給程序狀態寄存器或把一個立即數傳送給程序狀態寄存器
(1)MRS指令
在ARM指令集中,只有MRS指令可以 將狀態寄存器中的值讀取到通用寄存器中。
格式:
MRS {<cond>} Rd, CPSR/SPSR
其中,Rd為目標寄存器,Rd不允許為程序計數器(R15)。
(2) MSR指令
在ARM指令集中,只有MSR指令可以直接設置 狀態寄存器的值
格式:
MSR {<cond>} SPSR/CPSR , #immed
Msr {<cond>} CPSR/SPSR , Rm
3,LDM和STM的配對規則
LDMFD--STMFD
LDMED--STMED
LDMFA--STMFA
LDMEA--STMEA
LDMIA--STMDB
LDMIB--STMDA
LDMDA--STMIB
LDMDB--STMIA
指令代碼如下:
.global _start
_start:
.if 0
@M開頭系列
MOV R0, #12 @R0 = 0XFF。(1)、寄存器或寄存器之間的數據傳送 MVF = MOV
MOV R0, R0
MOV R0, R0, LSL#2 @OR =ASL#2, LSR/ASR/ROR/RRX,(2)、移位操作
MVN R0, #4 @數據取反傳送 R0 = -5
MVN R0, #0
@MOV R0, #0X88888888 這樣是不合法的,mov操作立即數范圍為0~512,可以通過使用LDR/STR的偽指令實現將大的立即數或內存寫入寄存器,如下
LDR R0, =0X88888888
@MSR CPSR, R0
MSR SPSR, R0
MSR CPSR_flg, R0
MSR CPSR_flg, #1<<28
MUL R0, R1, R2
@MOV PC, R14 @(3)、程序跳轉(默認模式為從程序第一句到當前這一句循環執行)
@MOVS PC, R14 @(4)、S->C 拷貝,實現從異常中斷返回
@MOVS PC, LR @這三句在這里是等價的
.endif
@加減乘
@加法***************************
MOV R1, #24
ADD R0, R1, R2 @不帶進位的加法
ADD R0, R1, #256
ADD R0, R2, R1, LSL#1
ADC R0, R1, R3 @帶進位的加法
@兩個64位數相加
LDR R1, =0X10101010
LDR R2, =0X10010010
LDR R3, =0X20202020
LDR R4, =0X20020020
ADDS R5, R1, R3 @加低位的字
ADCS R6, R2, R4 @加高位的字,帶進位
@減法***************************
SUB R0, R1, R2 @ R0 = R1 - R2 不帶借位的減法
SUB R0, R1, #255 @ R0 = R1 - 256
SUB R0, R2, R3, LSL#1 @ R0 = R2 - (R3 << 1)
SBC R0, R1, R2 @ R0 = R1 - R2 -!CARRY 帶借位的解減法
RSB R0, R1, R2 @ R0 = R2 - R2 反向減法
RSC R3, R4, R5 @ R3 = R5 - R4 - !CARRY 帶借位的反向減法
@按位與、按位或、按位異或
@按位與*************************
AND R0, R0, #0X03 @R0 = 保持R0的位0和1不變,其余位歸零
AND R0, R1, R2 @R0 = R1 & R2
ANDS R0, R0, #0X03 @R0 = R0 & 0X03
@按位或*************************
LDR R0, =0X00000000
ORR R0, R0, #3 @將R0的第1、3位設置成1
LDR R0, =0X00000000
ORR R0, R0, #0xFF @將R0的低16位置1
@按位異或***********************
EOR R0, R0, #3 @反轉R0的第0位和第1位
EOR R1, R1, #0x0F @將R1的低4位取反
EOR R2, R1, R0 @R2 = R1 ^ R0
EORS R0, R5, #0x1 @將R5和0x1進行邏輯異或,結果保存在R0中,並根據執行結果更新標志位。
@位清除*************************
@BIC R0, R0, #%1011 @清除 R0 中的位 0、1、和 3。保持其余的不變。
BIC R0, R0, #11 @清零R0中的第0、1、3位,其余位保持不變
BIC R1, R2, R3 @將R3的反碼與R2進行邏輯與,結果保存到R1中
@比較、測試***************************
CMP R1, #10 @比較R1和立即數10並更新相關的狀態標志位,CMP不需要額外再添加S后綴來指示其更改狀態標志位。
CMP R1, R2 @比較寄存器R1和R2中的值並設置相關的標志位。常用CMP指令及相的條件標志位來確定后續的指令是否執行。
CMN R0, #1 @把 R0 與 -1 進行比較
TST R0, #1 @測試R0中的最低位是否為0
TEQ R0, R1 @比較R0和R1是否相等,該指令不影響CPSR中的V位和C位。
@分支與帶連接的分支B/BL***************
@循環10次
MOV R1, #0
MOV R2, #1
LAB1:
ADD R1, R1, #1
CMP R1, #10
MOVEQ PC, LR
B LAB1
@帶連接的分支
load_new_format:
BL switch_screen_mode
BL get_screen_info
BL load_palette
new_loop:
MOV R1, R5
BL read_byte
CMP R0, #255
BLEQ read_loop
STRB R0, [R2, #1]!
@LOAD/STORE
LDR r1, [r0, #0x12] @將r0+12地址處的數據讀出,保存到r1中(r0的值不變)
LDR r1, [r0] @將r0地址處的數據讀出,保存到r1中(零偏移)
LDR r1, [r0, r2] @將r0+r2地址的數據讀出,保存到r1中(r0的值不變)
LDR r1, [r0, r2, LSL #2] @將r0+r2×4地址處的數據讀出,保存到r1中(r0,r2的值不變)
LDR R1, label @label為程序標號,label必須是當前指令的±4KB范圍內
LDR R1, [R2], #0x04 @R2的值用作傳輸數據的存儲地址。在數據傳送后將偏移量0x04與R2相加,結果寫回到R2中。R2不允許是r15
abcd EQU 2 @定義abcd符號的值為2
abcd EQU label+16 @定義abcd符號的值為(label+16)
abcd EQU 0x1c,CODE32 @定義abcd符號的值為絕對地址值0x1c,而且此處為ARM指令
NumCount EQUD 0x40003000, code32 @定義變量NumCount
LDR R0, =NumCount @使用LDR偽指令裝載NumCount的地址到R0
LDR R1, [R0] @取出變量值
ADD R1, R1, #1 @NumCount=NumCount+1
STR R1, [R0] @保存變量
SWP R1, R1, [R0] @將R1的內容與R0指向的存儲單元內容進行交換
MRS R1, CPSR @其中,Rd為目標寄存器,Rd不允許為程序計數器(R15)。
MSR SPSR, #0X12
Msr CPSR, R2
.end
備注:
一,ARM中的注釋
1,"@"符號作為注釋可以放在語句的開始處
2,";"作為 流程只能放在語句的末尾
二,指令與偽指令
1,指令有對應的機器碼,CPU可以直接識別並執行。
2,偽指令,沒有對應的機器碼,它需要經過編譯器翻譯成指令才能被CPU識別和執行。
ldr r1, =0xfff //偽指令
.text
.global _start
_start:
@這是一條偽指令,沒有對應的機器碼,被編譯器翻譯為LDR R0,[PC,#0x0008]
LDR R0, =var ; int * R0 = var
@這是一條指令
LDR R1, [R0] ; int R1 = *R0
@指令有對應的機器碼,編譯器原樣執行
MOV R1, R0
NOP
NOP
.data
var: int * var; *var = 0x8;
.word 0x8
.end
GNU ARM匯編器的偽操作
一,符號定義偽操作
1,.global
用於聲明一個ARM程序中的全局變量,使得被聲明的符號在整個程序中可見,變成整個工程中都可以使用的全局變量。
用法舉例://聲明_start為一個全局的符號
.global _start
2,.local
用於聲明一個ARM程序中的局部變量,這樣它對外部是不可見的,作用域是在本文件范圍內
用法舉例://聲明_label為一個只可在本文件范圍內使用的符號
.local _label
2,.set
用於給一個全局變量或局部變量賦值
用法舉例:
.set _label, 0x10 //給變量_label賦值為0x10
3, .equ
用於給一個變量賦值
用法:
.equ _label, 0x10
二,數據定義偽操作
數據定義偽操作一般用於為特定的數據分配內存單元,同時對該內存單元中的數據進行初始化,覺的數據定義偽操作有:
1, .byte
在存儲器中分配 一個字節的內存單元,用指定的數據對該存儲單元進行初始化
_label:
.byte 0x1
在當前地址分配一個字節的存儲單元,將將其初始化為1,類似於C語言中的char _label = 1.
2, .short
在存儲器中分配2個字節的內存單元,並用指定的數據對該存儲單元進行初始化,用於與.byte類似
3,.word
在存儲器中分配4個字節的內存單元,並用指定的數據對該存儲單元進行初始化,用於與.byte類似
4, .long
與.word的功能相同
5, .quad
.quad的功能是在內存中分配8個字節的存儲單元,並用指定的數據對該存儲單元進行初始化。
6,.float
在存儲器中分配4個字節的存儲空間,並用指定的浮點數據對該空間進行初始化。
7, .space
.space偽操作用於分配一片連續的內存區域,並將其初始化為指定的值,如果后面的填充值省略不寫,則默認在后面填充0
8, .skip
等同與.space
9, .string, ascii, .asciz
這3條偽操作的功能都是定義一個字符串:
用法:
_label:
.string "Hello, World!"
10, .rept
.rept偽操作功能是 重復執行后面的指令,以.rept開始,並以.endr結束
用法:
.rept 3
add r1, r1, #1
.endr
三,匯編控制偽操作
1,
.if, .else, .endif
四,雜項操作偽指令
GNU匯編中還有一些其他的偽操作,在匯編程序中經常會使用到它們,包括而在這些:
1,.align
.align偽操作可通過添加填充字節的試,使當前位置滿足指定的對齊方式
舉例:
.align 2
.string "abcde"
聲明后面的字符串的對齊方式是4(2的2次方)字節對齊,這個字符串會占用8個字節的存儲空間。
2, .section
.section偽操作用於定義一個段,一個GNU的源程序至少需要一個段,大的程序可以包含多個代碼段和數據段。
可能用來定義自定義段
3, .data
.data偽操作用來定義一個數據段
4, .text
.text偽操作用來定義一個數據段
5, .include
.include 偽操作用來包含一個頭文件。
6, .extern
.extern用於聲明一個外部符號,即告訴編譯器當前符號不是在本源文件中定義的,而是在其它源文件中定義的,當前文件需要引用這個符號。
7, .weak
.weak用來聲明一個符號是弱符號,即如果這個符號沒有定義,編譯器就會忽略,不會報錯。
8, .end
.end代表程序的結束位置