ARM指令集詳解(超詳細!帶例項!)
算術和邏輯指令
ADC : 帶進位的加法
(Addition with Carry)
ADC{條件}{S} <dest>, <op 1>, <op 2>
dest = op_1 + op_2 + carry
ADC將把兩個運算元加起來,並把結果放置到目的暫存器中。它使用一個進位標誌位,這樣就可以做比 32
位大的加法。下列例子將加兩個 128
位的數。
128 位結果:
暫存器 0、1、2、和
3
第一個 128
位數: 暫存器 4、5、6、和
7
第二個 128
位數: 暫存器 8、9、10、和
11。
ADDS R0, R4, R8 ; 加低端的字
ADCS R1, R5, R9 ; 加下一個字,帶進位
ADCS R2, R6, R10 ; 加第三個字,帶進位
ADCS R3, R7, R11 ; 加高階的字,帶進位
如果如果要做這樣的加法,不要忘記設定 S 字尾來更改進位標誌。
ADD : 加法
(Addition)
ADD{條件}{S} <dest>, <op 1>, <op 2>
dest = op_1 + op_2
ADD將把兩個運算元加起來,把結果放置到目的暫存器中。運算元 1 是一個暫存器,運算元
ADD R0, R1, R2 ; R0 = R1 + R2
ADD R0, R1, #256 ; R0 = R1 + 256
ADD R0, R2, R3,LSL#1 ; R0 = R2 + (R3 << 1)
加法可以在有符號和無符號數上進行。
AND : 邏輯與
(logical AND)
AND{條件}{S} <dest>, <op 1>, <op 2>
dest = op_1 AND op_2
AND
AND R0, R0, #3 ; R0 = 保持 R0 的位 0 和 1,丟棄其餘的位。
AND 的真值表(二者都是 1 則結果為 1):
Op_1 Op_2 結果
0 0 0
0 1 0
1 0 0
1 1 1
BIC : 位清除
(Bit Clear)
BIC{條件}{S} <dest>, <op 1>, <op 2>
dest = op_1 AND (!op_2)
BIC是在一個字中清除位的一種方法,與 OR 位設定是相反的操作。運算元 2 是一個 32 位位掩碼(mask)。如果如果在掩碼中設定了某一位,則清除這一位。未設定的掩碼位指示此位保持不變。
BIC R0, R0, #%1011 ; 清除 R0 中的位 0、1、和 3。保持其餘的不變。
BIC 真值表 :
Op_1 Op_2 結果
0 0 0
0 1 0
1 0 1
1 1 0
譯註:邏輯表示式為 Op_1 AND NOT Op_2
EOR : 邏輯異或
(logical Exclusive OR)
EOR{條件}{S} <dest>, <op 1>, <op 2>
dest = op_1 EOR op_2
EOR將在兩個運算元上進行邏輯異或,把結果放置到目的暫存器中;對反轉特定的位有用。運算元 1 是一個暫存器,運算元 2 可以是一個暫存器,被移位的暫存器,或一個立即值:
EOR R0, R0, #3 ; 反轉 R0 中的位 0 和 1
EOR 真值表(二者不同則結果為 1):
Op_1 Op_2 結果
0 0 0
0 1 1
1 0 1
1 1 0
MOV : 傳送
(Move)
MOV{條件}{S} <dest>, <op 1>
dest = op_1
MOV從另一個暫存器、被移位的暫存器、或一個立即值裝載一個值到目的暫存器。你可以指定相同的暫存器來實現 NOP 指令的效果,你還可以專門移位一個暫存器:
MOV R0, R0 ; R0 = R0... NOP 指令
MOV R0, R0, LSL#3 ; R0 = R0 * 8
如果 R15 是目的暫存器,將修改程式計數器或標誌。這用於返回到呼叫程式碼,方法是把連線暫存器的內容傳送到 R15:
MOV PC, R14 ; 退出到呼叫者
MOVS PC, R14 ; 退出到呼叫者並恢復標誌位
(不遵從 32-bit 體系)
MVN : 傳送取反的值
(MoveNegative)
MVN{條件}{S} <dest>, <op 1>
dest = !op_1
MVN從另一個暫存器、被移位的暫存器、或一個立即值裝載一個值到目的暫存器。不同之處是在傳送之前位被反轉了,所以把一個被取反的值傳送到一個暫存器中。這是邏輯非操作而不是算術操作,這個取反的值加 1 才是它的取負的值:
MVN R0, #4 ; R0 = -5
MVN R0, #0 ; R0 = -1
ORR : 邏輯或
(logical OR)
ORR{條件}{S} <dest>, <op 1>, <op 2>
dest = op_1 OR op_2
OR將在兩個運算元上進行邏輯或,把結果放置到目的暫存器中;對設定特定的位有用。運算元 1 是一個暫存器,運算元 2 可以是一個暫存器,被移位的暫存器,或一個立即值:
ORR R0, R0, #3 ; 設定 R0 中位 0 和 1
OR 真值表(二者中存在 1 則結果為 1):
Op_1 Op_2 結果
0 0 0
0 1 1
1 0 1
1 1 1
RSB : 反向減法
(Reverse Subtraction)
RSB{條件}{S} <dest>, <op 1>, <op 2>
dest = op_2 - op_1
SUB用運算元two 減去運算元one,把結果放置到目的暫存器中。運算元 1 是一個暫存器,運算元 2 可以是一個暫存器,被移位的暫存器,或一個立即值:
RSB R0, R1, R2 ; R0 = R2 - R1
RSB R0, R1, #256 ; R0 = 256 - R1
RSB R0, R2, R3,LSL#1 ; R0 = (R3 << 1) - R2
反向減法可以在有符號或無符號數上進行。
RSC : 帶借位的反向減法
(Reverse Subtraction with Carry)
RSC{條件}{S} <dest>, <op 1>, <op 2>
dest = op_2 - op_1 - !carry
同於SBC,但倒換了兩個運算元的前後位置。
SBC : 帶借位的減法
(Subtraction with Carry)
SBC{條件}{S} <dest>, <op 1>, <op 2>
dest = op_1 - op_2 - !carry
SBC做兩個運算元的減法,把結果放置到目的暫存器中。它使用進位標誌來表示借位,這樣就可以做大於 32 位的減法。SUB和SBC生成進位標誌的方式不同於常規,如果需要借位則清除進位標誌。所以,指令要對進位標誌進行一個非操作 - 在指令執行期間自動的反轉此位。
SUB : 減法
(Subtraction)
SUB{條件}{S} <dest>, <op 1>, <op 2>
dest = op_1 - op_2
SUB用運算元one 減去運算元 two,把結果放置到目的暫存器中。運算元 1 是一個暫存器,運算元 2 可以是一個暫存器,被移位的暫存器,或一個立即值:
SUB R0, R1, R2 ; R0 = R1 - R2
SUB R0, R1, #256 ; R0 = R1 - 256
SUB R0, R2, R3,LSL#1 ; R0 = R2 - (R3 << 1)
減法可以在有符號和無符號數上進行。
移位指令
ARM 處理器組建了可以與資料處理指令(ADC、ADD、AND、BIC、CMN、CMP、EOR、MOV、MVN、ORR、RSB、SBC、SUB、TEQ、TST)一起使用的桶式移位器(barrel shifter)。你還可以使用桶式移位器影響在 LDR/STR 操作中的變址值。
譯註:移位操作在 ARM 指令集中不作為單獨的指令使用,它是指令格式中是一個欄位,在組合語言中表示為指令中的選項。如果資料處理指令的第二個運算元或者單一資料傳送指令中的變址是暫存器,則可以對它進行各種移位操作。如果資料處理指令的第二個運算元是立即值,在指令中用 8 位立即值和 4 位迴圈移位來表示它,所以對大於 255 的立即值,彙編器嘗試通過在指令中設定迴圈移位數量來表示它,如果不能表示則生成一個錯誤。在邏輯類指令中,邏輯運算指令由指令中 S 位的設定或清除來確定是否影響進位標誌,而比較指令的 S 位總是設定的。在單一資料傳送指令中指定移位的數量只能用立即值而不能用暫存器。
下面是給不同的移位型別的六個助記符:
LSL 邏輯左移
ASL 算術左移
LSR 邏輯右移
ASR 算術右移
ROR 迴圈右移
RRX 帶擴充套件的迴圈右移
ASL和LSL是等同的,可以自由互換。
你可以用一個立即值(從 0 到 31)指定移位數量,或用包含在 0 和 31 之間的一個值的暫存器指定移位數量。
邏輯或算術左移
(Logical or Arithmetic Shift Left)
Rx, LSL #n or
Rx, ASL #n or
Rx, LSL Rn or
Rx, ASL Rn
接受 Rx 的內容並按用‘n’或在暫存器 Rn 中指定的數量向高有效位方向移位。最低有效位用零來填充。除了概念上的第 33 位(就是被移出的最小的那位)之外丟棄移出最左端的高位,如果邏輯類指令中 S 位被設定了,則此位將成為從桶式移位器退出時進位標誌的值。
考慮下列:
MOV R1, #12
MOV R0, R1, LSL#2
在退出時,R0 是 48。這些指令形成的總和是R0 = #12, LSL#2等同於 BASIC 的R0 = 12 << 2
邏輯右移
(Logical Shift Right)
Rx, LSR #n or
Rx, LSR Rn
它在概念上與左移相對。把所有位向更低有效位方向移動。如果邏輯類指令中 S 位被設定了,則把最後被移出最右端的那位放置到進位標誌中。它同於 BASIC 的register = value >>> shift。
算術右移
(Arithmetic Shift Right)
Rx, ASR #n or
Rx, ASR Rn
類似於 LSR,但使用要被移位的暫存器(Rx)的第 31 位的值來填充高位,用來保護補碼錶示中的符號。如果邏輯類指令中 S 位被設定了,則把最後被移出最右端的那位放置到進位標誌中。它同於 BASIC 的register = value >> shift。
迴圈右移
(Rotate Right)
Rx, ROR #n or
Rx, ROR Rn
迴圈右移類似於邏輯右移,但是把從右側移出去的位放置到左側,如果邏輯類指令中 S 位被設定了,則同時放置到進位標誌中,這就是位的‘迴圈’。一個移位量為 32 的操作將導致輸出與輸入完全一致,因為所有位都被移位了 32 個位置,又回到了開始時的位置!
帶擴充套件的迴圈右移
(Rotate Right with extend)
Rx, RRX
這是一個 ROR#0 操作,它向右移動一個位置 - 不同之處是,它使用處理器的進位標誌來提供一個要被移位的 33 位的數量。
乘法指令 |
這兩個指令與普通算術指令在對運算元的限制上有所不同:
1. 給出的所有運算元、和目的暫存器必須為簡單的暫存器。
2. 你不能對運算元 2 使用立即值或被移位的暫存器。
3. 目的暫存器和運算元 1 必須是不同的暫存器。
4. 最後,你不能指定 R15 為目的暫存器。
MLA : 帶累加的乘法
(Multiplication with Accumulate)
MLA{條件}{S} <dest>, <op 1>, <op 2>, <op 3>
dest = (op_1 * op_2) + op_3
MLA的行為同於MUL,但它把運算元 3 的值加到結果上。這在求總和時有用。
MUL : 乘法
(Multiplication)
MUL{條件}{S} <dest>, <op 1>, <op 2>
dest = op_1 * op_2
MUL提供 32 位整數乘法。如果運算元是有符號的,可以假定結果也是有符號的。
比較指令 |
譯註:CMP 和 CMP 是算術指令,TEQ 和 TST 是邏輯指令。把它們歸入一類的原因是它們的 S 位總是設定的,就是說,它們總是影響標誌位。
CMN : 比較取負的值
(Compare Negative)
CMN{條件}{P} <op 1>, <op 2>
status = op_1 - (- op_2)
CMN同於CMP,但它允許你與小負值(運算元 2 的取負的值)進行比較,比如難於用其他方法實現的用於結束列表的 -1。這樣與 -1 比較將使用:
CMN R0, #1 ; 把 R0 與 -1 進行比較
詳情參照CMP指令。
CMP : 比較
(Compare)
CMP{條件}{P} <op 1>, <op 2>
status = op_1 - op_2
CMP允許把一個暫存器的內容如另一個暫存器的內容或立即值進行比較,更改狀態標誌來允許進行條件執行。它進行一次減法,但不儲存結果,而是正確的更改標誌。標誌表示的是運算元 1 比運算元 2 如何(大小等)。如果運算元 1 大於操作運算元 2,則此後的有 GT 字尾的指令將可以執行。明顯的,你不需要顯式的指定S字尾來更改狀態標誌... 如果你指定了它則被忽略。
TEQ : 測試等價
(Test Equivalence)
TEQ{條件}{P} <op 1>, <op 2>
Status = op_1 EOR op_2
TEQ類似於TST。區別是這裡的概念上的計算是 EOR 而不是 AND。這提供了一種檢視兩個運算元是否相同而又不影響進位標誌(不象CMP那樣)的方法。加上P字尾的TEQ還可用於改變 R15 中的標誌(在 26-bit 模式中)。詳情請參照,在 32-bit 模式下如何做請參見這裡。
TST : 測試位
(Test bits)
TST{條件}{P} <op 1>, <op 2>
Status = op_1 AND op_2
TST類似於CMP,不產生放置到目的暫存器中的結果。而是在給出的兩個運算元上進行操作並把結果反映到狀態標誌上。使用TST來檢查是否設定了特定的位。運算元 1 是要測試的資料字而運算元 2 是一個位掩碼。經過測試後,如果匹配則設定 Zero 標誌,否則清除它。象CMP 那樣,你不需要指定S字尾。
TST R0, #%1 ; 測試在 R0 中是否設定了位 0。
分支指令
B : 分支
(Branch)
B{條件} <地址>
B是最簡單的分支。一旦遇到一個 B指令,ARM 處理器將立即跳轉到給定的地址,從那裡繼續執行。
注意儲存在分支指令中的實際的值是相對當前的 R15 的值的一個偏移量;而不是一個絕對地址。
它的值由彙編器來計算,它是 24 位有符號數,左移兩位後有符號擴充套件為 32 位,表示的有效偏移為 26 位(+/- 32 M)。
在其他處理器上,你可能經常見到這樣的指令:
OPT 1
LDA &70
CMP #0
BEQ Zero
STA &72
.Zero RTS
(取自 Acorn Electron User Guide issue 1 page 213)
在 ARM 處理器上,它們將變成下面這些東西:
OPT 1
ADR R1, #&70
LDR R0, [R1]
CMP #0
BEQ Zero
STR R0, [R1, #2]
.Zero
MOV PC, R14
這不是一個很好的例子,但你可以構想如何更好的去條件執行而不是分支。另一方面,如果你有大段的程式碼或者你的程式碼使用狀態標誌,那麼你可以使用條件執行來實現各類分支: 這樣一個單一的簡單條件執行指令可以替代在其他處理器中存在的所有這些分支和跳轉指令。
OPT 1
ADR R1, #&70
LDR R0, [R1]
CMP R0, #0
STRNE R0, [R1, #2]
MOV PC, R14
BL : 帶連線的分支
(Branch with Link)
BL{條件} <地址>
BL是另一個分支指令。就在分支之前,在暫存器 14 中裝載上 R15 的內容。你可以重新裝載 R14 到 R15 中來返回到在這個分支之後的那個指令,
它是子例程的一個基本但強力的實現。它的作用在螢幕裝載器 2 (例子 4)中得以很好的展現...
.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]!
...在這裡我們見到在裝載器迴圈之前呼叫了三個子例程。接著,一旦滿足了條件執行就在迴圈中呼叫了 read_byte 子例程。
條件執行 |
ARM 處理器的一個非常特殊的特徵是它的條件執行。我們指的不是基本的如果進位則分支,ARM 使這個邏輯階段進一步深化為如果進位則 XXX- 這裡的 XXX 是任何東西。
為了舉例,下面是 Intel 8086 處理器分支指令的一個列表:
JA Jump if Above
JAE Jump if Above or Equal
JB Jump if Below
JBE Jump if Below or Equal
JC Jump if Carry
JCXZ Jump if CX Zero (CX is a register that can be used for loop counts)
JE Jump if Equal
JG Jump if Greater than
JGE Jump if Greater than or Equal
JL Jump if Less than
JLE Jump if Less Than or Equal
JMP JuMP
JNA Jump if Not Above
JNAE Jump if Not Above or Equal
JNB Jump if Not Below
JNBE Jump if Not Below or Equal
JNC Jump if No Carry
JNE Jump if Not Equal
JNG Jump if Not Greater than
JNGE Jump if Not Greater than or Equal
JNL Jump if Not Less than
JNLE Jump if Not Less than or Equal
JNO Jump if Not Overflow
JNP Jump if Not Parity
JNS Jump if Not Sign
JNZ Jump if Not Zero
JO Jump if Overflow
JP Jump if Parity
JPE Jump if Parity Even
JPO Jump if Parity Odd
JS Jump if Sign
JZ Jump if Zero
80386 添加了:
JECXZ Jump if ECX Zero
作為對比,ARM 處理器只提供了:
B 分支
BL 帶連線的分支
但 ARM 提供了條件執行,你可以不受這個表面上不靈活的方式的限制:
BEQ Branch if EQual
BNE Branch if Not Equal
BVS Branch if oVerflow Set
BVC Branch if oVerflow Clear
BHI Branch if HIgher
BLS Branch if Lower or the Same
BPL Branch if PLus
BMI Branch if MInus
BCS Branch if Carry Set
BCC Branch if Carry Clear
BGE Branch if Greater than or Equal
BGT Branch if Greater Than
BLE Branch if Less than or Equal
BLT Branch if Less Than
BLEQ Branch with Link if EQual
....
BLLT Branch with Link if Less Than
還有兩個程式碼,
· AL - ALways,預設條件所以不須指定
· NV - NeVer,不是非常有用。你無論如何不要使用這個程式碼...
當你發現所有 Bxx 指令實際上是同一個指令的時候,緊要關頭就到了。
接著你會想,如果你可以在一個分支指令上加上所有這些條件,那麼對一個暫存器裝載指令能否加上它們? 答案是可以。
下面是可獲得的條件程式碼的列表:
EQ : 等於
如果一次比較之後設定了 Z 標誌。
NE : 不等於
如果一次比較之後清除了 Z 標誌。
VS : 溢位設定
如果在一次算術操作之後設定了 V 標誌,計算的結果不適合放入一個 32bit 目標暫存器中。
VC : 溢位清除
如果清除了 V 標誌,與 VS 相反。
HI : 高於(無符號)
如果一次比較之後設定了 C 標誌並清除了 Z 標誌。
LS : 低於或同於(無符號)
如果一次比較操作之後清除了 C 標誌或設定了 Z 標誌。
PL : 正號
如果一次算術操作之後清除了 N。出於定義‘正號’的目的,零是正數的原因是它不是負數...
MI : 負號
如果一次算術操作之後設定了 N 標誌。
CS : 進位設定
如果一次算術操作或移位操作之後設定了 C 標誌,