1. 程式人生 > >ARM指令集詳解(超詳細!帶例項!)

ARM指令集詳解(超詳細!帶例項!)

算術和邏輯指令

ADC : 帶進位的加法

(Addition with Carry)

ADC{條件}{S}  <dest>, <op 1>, <op 2>

dest = op_1 + op_2 + carry

ADC將把兩個運算元加起來,並把結果放置到目的暫存器中。它使用一個進位標誌位,這樣就可以做比 32 位大的加法。下列例子將加兩個 128 位的數。
128
位結果: 暫存器 012、和 3
第一個 128 位數: 暫存器 456、和 7
第二個 128 位數: 暫存器 8910、和 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 是一個暫存器,運算元

2 可以是一個暫存器,被移位的暫存器,或一個立即值:

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

將在兩個運算元上進行邏輯與,把結果放置到目的暫存器中;對遮蔽你要在上面工作的位很有用。運算元 1 是一個暫存器,運算元 2 可以是一個暫存器,被移位的暫存器,或一個立即值:

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 位的減法。SUBSBC生成進位標誌的方式不同於常規,如果需要借位則清除進位標誌。所以,指令要對進位標誌進行一個操作 - 在指令執行期間自動的反轉此位。

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 處理器組建了可以與資料處理指令(ADCADDANDBICCMNCMPEORMOVMVNORRRSBSBCSUBTEQTST)一起使用的桶式移位器(barrel shifter)。你還可以使用桶式移位器影響在 LDR/STR 操作中的變址值。

譯註:移位操作在 ARM 指令集中不作為單獨的指令使用,它是指令格式中是一個欄位,在組合語言中表示為指令中的選項。如果資料處理指令的第二個運算元或者單一資料傳送指令中的變址是暫存器,則可以對它進行各種移位操作。如果資料處理指令的第二個運算元是立即值,在指令中用 8 位立即值和 4 位迴圈移位來表示它,所以對大於 255 的立即值,彙編器嘗試通過在指令中設定迴圈移位數量來表示它,如果不能表示則生成一個錯誤。在邏輯類指令中,邏輯運算指令由指令中 S 位的設定或清除來確定是否影響進位標誌,而比較指令的 S 位總是設定的。在單一資料傳送指令中指定移位的數量只能用立即值而不能用暫存器。

下面是給不同的移位型別的六個助記符:

LSL  邏輯左移

ASL  算術左移

LSR  邏輯右移

ASR  算術右移

ROR  迴圈右移

RRX  帶擴充套件的迴圈右移

ASLLSL是等同的,可以自由互換。

你可以用一個立即值( 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 標誌,