在嵌入式開發中,匯編程序常常用於非常關鍵的地方,比如系統啟動時的初始化,中斷上下文的保存和恢復,對性能要求非常苛刻的函數等。
在3S3C2440的數據手冊中,對各種匯編指令的作用及使用方法都有詳細說明,這里只對一些常用的匯編指令進行介紹。
一、ARM寄存器介紹
1.1 32位體系
ARM 處理器在32位體系下主要由以下寄存器組成::
- 寄存器 0 到寄存器 7 是通用寄存器並可以用做任何目的。不像 80x86 處理器那樣要求特定寄存器被用做棧訪問,或者像 6502 那樣把數學計算的結果放置到一個累加器中,ARM 處理器在寄存器使用上是高度靈活的。
- 寄存器 8 到 12 是通用寄存器,但是在切換到 FIQ 模式的時候,使用它們的影子(shadow)寄存器。
- 寄存器 13 典型的用做 OS 棧指針,但可被用做一個通用寄存器。這是一個操作系統問題,不是一個處理器問題,所以如果你不使用棧,只要你以后恢復它,你可以在你的代碼中自由的占用(corrupt)它。每個處理器模式都有這個寄存器的影子寄存器。
- 寄存器 14 專職持有返回點的地址以便於寫子例程。當你執行帶連接的分支的時候,把返回地址存儲到 R14 中。同樣在程序第一次運行的時候,把退出地址保存在 R14 中。R14 的所有實例必須被保存到其他寄存器中(不是實際上有效)或一個棧中。這個寄存器在各個處理器模式下都有影子寄存器。一旦已經保存了連接地址,這個寄存器就可以用做通用寄存器了。
- 寄存器 15 是程序計數器。PC 是完全的 32 位寬,並只用做程序計數器。
-
兩個 PSR - CPSR 是當前的程序狀態寄存器(Current Program Status Register),而 SPSR 是保存的程序狀態寄存器(Saved Program Status Register)。每個有特權的模式都有自己的 SPSR,可獲得的 PSR 有:
CPSR_all - 當前的 SPSR_svc - 保存的,SVC(32) 模式 SPSR_irq - 保存的,IRQ(32) 模式 SPSR_abt - 保存的,ABT(32) 模式 SPSR_und - 保存的,UND(32) 模式 SPSR_fiq - 保存的,FIQ(32) 模式
你不能顯式的指定把 CPSR 保存到哪個SPSR 中,比如 SPSR_fiq。而是必須變更到 FIQ 模式並接着保存到 SPSR。換句話說,你只能在你所在的模式中改變這個模式的 SPSR。
為更清晰一些... 提供下列圖表(其中User26,...26是為了兼容26位體系):
User26 SVC26 IRQ26 FIQ26 User SVC IRQ ABT UND FIQ R0 ----- R0 ----- R0 ----- R0 -- -- R0 ----- R0 ----- R0 ----- R0 ----- R0 ----- R1 R1 ----- R1 ----- R1 ----- R1 -- -- R1 ----- R1 ----- R1 ----- R1 ----- R1 ----- R2 R2 ----- R2 ----- R2 ----- R2 -- -- R2 ----- R2 ----- R2 ----- R2 ----- R2 ----- R2 R3 ----- R3 ----- R3 ----- R3 -- -- R3 ----- R3 ----- R3 ----- R3 ----- R3 ----- R3 R4 ----- R4 ----- R4 ----- R4 -- -- R4 ----- R4 ----- R4 ----- R4 ----- R4 ----- R4 R5 ----- R5 ----- R5 ----- R5 -- -- R5 ----- R5 ----- R5 ----- R5 ----- R5 ----- R5 R6 ----- R6 ----- R6 ----- R6 -- -- R6 ----- R6 ----- R6 ----- R6 ----- R6 ----- R6 R7 ----- R7 ----- R7 ----- R7 -- -- R7 ----- R7 ----- R7 ----- R7 ----- R7 ----- R7 R8 ----- R8 ----- R8 R8_fiq R8 ----- R8 ----- R8 ----- R8 ----- R8 R8_fiq R9 ----- R9 ----- R9 R9_fiq R9 ----- R9 ----- R9 ----- R9 ----- R9 R9_fiq R10 ---- R10 ---- R10 R10_fiq R10 ---- R10 ---- R10 ---- R10 ---- R10 R10_fiq R11 ---- R11 ---- R11 R11_fiq R11 ---- R11 ---- R11 ---- R11 ---- R11 R11_fiq R12 ---- R12 ---- R12 R12_fiq R12 ---- R12 ---- R12 ---- R12 ---- R12 R12_fiq R13 R13_svc R13_irq R13_fiq R13 R13_svc R13_irq R13_abt R13_und R13_fiq R14 R14_svc R14_irq R14_fiq R14 R14_svc R14_irq R14_abt R14_und R14_fiq --------- R15 (PC / PSR) --------- --------------------- R15 (PC) --------------------- ----------------------- CPSR ----------------------- SPSR_svc SPSR_irq SPSR_abt SPSR_und SPSR_fiq
下面是你想知道的"模式",比如上面提及的"FIQ"模式。
- 用戶模式,運行應用程序的普通模式。限制你的內存訪問並且你不能直接讀取硬件設備。
- 超級用戶模式(SVC 模式),主要用於 SWI(軟件中斷)和 OS(操作系統)。這個模式有額外的特權,允許你進一步控制計算機。例如,你必須進入超級用戶模式來讀取一個插件(podule)。這不能在用戶模式下完成。
- 外部中斷模式(IRQ 模式),用來處理發起中斷的外設。這個模式也是有特權的。導致 IRQ 的設備有鍵盤、 VSync (在發生屏幕刷新的時候)、IOC 定時器、串行口、硬盤、軟盤、等等...
- 快速中斷模式(FIQ 模式),用來處理發起快速中斷的外設。這個模式是有特權的。導致 FIQ 的設備有處理數據的軟盤,串行端口(比如在 82C71x 機器上的 A5000) 和 Econet。
-
異常終止模式(ABT 模式): 在一個數據或指令預取異常終止(abort)的時候進入的模式。
-
未定義模式(UND 模式): 在執行了一個未定義的指令的時候進入的模式。
ARM處理器的工作模式可以通過軟件改變,也可以通過外部中斷或異常處理來改變處理器的工作模式。大多數的應用程序運行在用戶模式下,當處理器運行在用戶模式下時,某些被保護的系統資源是不能被訪問的。
除了軟件切換工作模式外,還有沒有其他方法呢?答案當然是有!通過異常中斷的方式也可以進入相應的工作模式。例如,當IRQ中斷發生時,處理器就進入外部中斷模式(IRQ模式)。
IRQ 和 FIQ 之間的區別是對於 FIQ 你必須盡快處理你事情並離開這個模式。IRQ 可以被 FIQ 所中斷但 IRQ 不能中斷 FIQ。FIQ 不能調用 SWI。FIQ 還必須禁用中斷。
當進行工作模式切換時,哪些寄存器的值需要保護呢?為什么快速中斷模式比外部中斷模式中斷響應的速度要快?
假設ARM處理器處於用戶模式執行程序,在程序執行過程中,發生了外部中斷,則處理器進入外部中斷模式(IRQ模式),從上圖可以看出,用戶模式和IRQ模式的寄存器R0-R12是公用的,也就是說,在進入IRQ模式之前,在用戶模式下可能使用到了寄存器R0-R12。如果在IRQ模式中也需要使用,那么寄存器R0~R12中的值將被更改,在IRQ模式執行完中斷處理程序,返回到用戶模式后,寄存器R0-R12中的值被破壞了。因此,在IRQ模式中,使用寄存器R0-R12之前需要將其中的值保存,當從IRQ模式返回到用戶模式時將原來的值恢復就可以避免上述問題。
從上圖還可以看出,用戶模式下的寄存器R13-R14和IRQ模式的寄存器R13_irq、R14_irq不是同一個寄存器,也就是說在每種工作模式中,這兩個寄存器是獨立的。因此,當從用戶模式切換到IRQ模式時,不需要保存這兩個寄存器的值。
此外,從上圖還可以看到,用戶模式和快速中斷模式公用的寄存器是R0-R7,在FIQ模式中,R8_fiq~R14_fiq是獨立的。因此,當從用戶模式切換到快速中斷模式(FIQ)時不需要保存這幾個寄存器的值,只需要保存R0-R7的值即可。因此發生快速中斷只需要保存R0-R7,共8個寄存器。但是,當發生外部中斷時需要保存R0~R12共13個寄存器,這里講的保存寄存器的值是通過將其值入棧實現的,入棧是需要時間的,因此快速中斷的響應時間要快一些。
1.2 CPSR 和 SPSR 寄存器
CPSR 寄存器(和保存它的 SPSR 寄存器)中的位分配如下::
31 30 29 28 --- 7 6 - 4 3 2 1 0 N Z C V I F M4 M3 M2 M1 M0
標志的意義:
- N Negative 如果結果是負數則置位
- Z Zero 如果結果是零則置位
- C Carry 如果發生進位則置位
- V Overflow 如果發生溢出則置位
- I IRQ 中斷禁用
- F FIQ 快速中斷禁用
- M4.. M0 是處理器模式標志:
0 0 0 0 0 User26 模式 0 0 0 0 1 FIQ26 模式 0 0 0 1 0 IRQ26 模式 0 0 0 1 1 SVC26 模式 1 0 0 0 0 User 模式 1 0 0 0 1 FIQ 模式 1 0 0 1 0 IRQ 模式 1 0 0 1 1 SVC 模式 1 0 1 1 1 ABT 模式 1 1 0 1 1 UND 模式
二、寄存器裝載和存儲
- LDM
- LDR
- STM
- STR
- SWP
它們可能是能獲得的最有用的指令。其他指令都操縱寄存器,所以必須把數據從內存裝載寄存器並把寄存器中的數據存儲到內存中。
2.1 傳送單一數據(STR 和 LDR)
ldr命令:把數據從內存加載到寄存器。
ldr{條件} r1, [r0] ; r1 = *r0 ldr{條件} r1, [r0, #4] ; r1 = *(r0+4) ldr{條件} r1, [r0, #4] ! ; r1 = *(r0+4);r0=r0+4; ldr{條件} r1, [r0], #4 ; r1 = *(r0);r0=r0+4;
str命令:把數據從寄存器保存到內存。
str{條件} r1, [r0] ; *r0 = r1 str{條件} r1, [r0, #4] ; *(r0+4) = r1 str{條件} r1, [r0, #4] ! ; *(r0+4) = r1;r0=r0+4; str{條件} r1, [r0], #4 ; *r0 = r1;r0=r0+4;
LDR(偽指令):
偽指令(並不存在的指令,最終被解析成真正的匯編指令)
例1:LDR偽指令可以在立即數前加上=,以表示把一個地址寫到某寄存器中。
COUNT EQU 0x56000054 LDR R1,=COUNT
COUNT是我們定義的一個變量,地址為0x56000054。該指令會在內存中開辟一個地址,該地址保存0x56000054。而LDR指令會被轉換為 LDR,R1,[PC,偏移量]。
例2:下面的例子是獲取代碼的絕對位置,這是位置有關碼,該地址的值是和程序的鏈接地址有關的:
LDR R1,=label label: ...
LDR會去讀取標號的地址,然后賦值給R1,由於標號的地址和鏈接地址有關,因此這是一個位置有關指令。
例3:LDR偽指令把數據從內存中某處讀取到寄存器中。
LDR R0, 0x12345678 ;就是把0x12345678這個地址中的值存放到r0中。
該指令會讀取0x12345678地址處的值,然后賦值給R0。
LDR偽指令和MOV是比較相似的。只不過MOV指令限制了立即數的長度為8位,也就是不能超過512。而LDR偽指令沒有這個限制。如果使用LDR偽指令時,后面跟的立即數沒有超過8位,那么在實際匯編的時候該LDR偽指令是被轉換為MOV指令的。
2.2 傳送多個數據(LDM和STM)
LDM:(load much)多數據加載,將地址上的值加載到寄存器上;
STM:(store much)多數據存儲,將寄存器的值存到地址上;
STM 的主要用途是把需要保存的寄存器的值復制到棧上。如我們以前見到過的:
STMFD R13!, {R0-R12, R14}
指令格式是:
xxM{條件}{類型} Rn{!}, <寄存器列表>{^}
‘xx’是 LD 表示裝載,或 ST 表示存儲。
再加 4 種‘類型’就變成了 8 個指令:
棧 其他
LDMED LDMIB 預先增加裝載
LDMFD LDMIA 過后增加裝載
LDMEA LDMDB 預先減少裝載
LDMFA LDMDA 過后減少裝載
STMFA STMIB 預先增加存儲
STMEA STMIA 過后增加存儲
STMFD STMDB 預先減少存儲
STMED STMDA 過后減少存儲
LDM例子:
Ldr R1,=0x10000000 #傳送數據的起始地址0x10000000 LDMIB R1!,{R0,R4-R6} #從左到右加載,相當於 LDR R0,10000004 LDR R4,10000008... ...
IB:(Increase Before)的含義每次傳送前地址加4, 傳送前地址加+4;
所以地址加4,R0=0x1000004地址里的內容;
地址加4,R4=0x10000008地址里的內容;
地址加4,R5=0x1000000C地址里的內容;
地址加4,R6=0x10000010 地址里的內容;
由於!, 最后的地址寫回到R1中,R1=0x10000010 ;
指令中寄存器列表和內存單元的對應關系為:編號低的寄存器對應內存中的低地址單元。編號搞得寄存器對應內存中的高地址單元。
2.3 單一數據交換(SWP)
指令格式:
SWP{條件}{B} <dest>, <op 1>, [<op 2>]
寄存器和存儲器交換指令。使用SWP 可實現信號量操作。
實列代碼如下:
SWP R1,R1,[R0] ; 取出r0地址中的數據,放在r1中,並把r1中的數據放在r0中。
SWP R1,R2,[R0] ; 將R0 指向的存儲單元內容讀取數據到R1 中 ; 並將R2 的內容寫入到該內存單元中 使用SWP 指令可以方便地進行信號量的操作:
三、分支指令
3.1 B分支
B跳轉指令,語法格式:
B{條件} <地址>
B 是最簡單的分支。一旦遇到一個 B 指令,ARM 處理器將立即跳轉到給定的地址,從那里繼續執行。注意存儲在分支指令中的實際的值是相對當前的 R15 的值的一個偏移量;而不是一個絕對地址。它的值由匯編器來計算,它是 24 位有符號數,左移兩位后有符號擴展為 32 位,表示的有效偏移為 26 位(+/- 32 M)。
3.2 BL帶連接的分支
BL語法格式:
BL{條件} <地址>
帶鏈接程序跳轉,也就是要帶返回地址。在發生跳轉前,將當前PC-4保存到R14中。 也就是返回地址存在R14中,所以可以在子程序返回時只要MOV PC, LR即可。
四、算數和邏輯指令
- ADC
- ADD
- AND
- BIC
- EOR
- MOV
- MVN
- ORR
- RSB
- RSC
- SBC
- SUB
4.1 ADC帶進位的加法
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 后綴來更改進位標志。
4.2 ADD加法
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)
加法可以在有符號和無符號數上進行。
4.3 AND : 邏輯與
AND{條件}{S} <dest>, <op 1>, <op 2>
dest = op_1 AND op_2
AND將在兩個操作數上進行邏輯與,把結果放置到目的寄存器中;對設置特定的位有用。操作數 1 是一個寄存器,操作數 2 可以是一個寄存器,被移位的寄存器,或一個立即值:
AND R0, R0, #0xFFFFFFE ; 清除R0 中位 0
AND真值表(二者中均為 1 則結果為 1):
Op_1 Op_2 結果
0 0 0
0 0 0
0 0 0
1 1 1
4.4 BIC位清除
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。
4.5 EOR邏輯異或
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
4.6 MOV傳送
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 體系)
4.7 MVN傳送取反的值
(Move Negative) MVN{條件}{S} <dest>, <op 1> dest = !op_1
MVN 從另一個寄存器、被移位的寄存器、或一個立即值裝載一個值到目的寄存器。不同之處是在傳送之前位被反轉了,所以把一個被取反的值傳送到一個寄存器中。這是邏輯非操作而不是算術操作,這個取反的值加 1 才是它的取負的值:
MVN R0, #4 ; R0 = -5
MVN R0, #0 ; R0 = -1
4.8 ORR : 邏輯或
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
4.9 RSB : 反向減法
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
反向減法可以在有符號或無符號數上進行。
4.10 RSC : 帶借位的反向減法
RSC{條件}{S} <dest>, <op 1>, <op 2> dest = op_2 - op_1 - !carry
同於 SBC,但倒換了兩個操作數的前后位置。
4.11 SBC : 帶借位的減法
SBC{條件}{S} <dest>, <op 1>, <op 2> dest = op_1 - op_2 - !carry
SBC 做兩個操作數的減法,把結果放置到目的寄存器中。它使用進位標志來表示借位,這樣就可以做大於 32 位的減法。SUB 和 SBC 生成進位標志的方式不同於常規,如果需要借位則清除進位標志。所以,指令要對進位標志進行一個非操作 - 在指令執行期間自動的反轉此位。
4.12 SUB : 減法
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)
減法可以在有符號和無符號數上進行。
五、程序狀態指令
5.1 MRS
MRS指令用於將程序狀態寄存器的內容傳送到通用寄存器中。
MRS{條件} 通用寄存器,程序狀態寄存器(CPSR或SPSR)
例子:
MRS R0, CPSR ; 復制 CPSR 到 R0 中
MRS R0, SPSR ; 復制 SPSR 到 R0 中
注意事項: MRS指令用於將程序狀態寄存器的內容傳送到通用寄存器中。該指令一般用在以下兩種情況:
- 當需要改變程序狀態寄存器的內容時,可用MRS將程序狀態寄存器的內容讀入通用寄存器,修改后再寫回程序狀態寄存器;
- 當在異常處理或進程切換時,需要保存程序狀態寄存器的值,可先用該指令讀出程序狀態寄存器的值,然后保存。
5.2 MSR
MSR指令用亍將操作數的內容傳送到程序狀態寄存器的特定域中。其中,操作數可以為通用寄存器或立即數。
MSR{條件} 程序狀態寄存器(CPSR或SPSR)_<域>,操作數
例子:
MSR CPSR, R0 ; 復制 R0 到 CPSR 中
MSR SPSR, R0 ; 復制 R0 到 SPSR 中
MSR CPSR_flg, R0 ; 復制 R0 的標志位到 CPSR 中
MSR CPSR_flg, #1<<28 ; 復制(立即值)標志位到 CPSR 中
注意事項:
<域>用於設置程序狀態寄存器中需要操作的位,32位的程序狀態寄存器可分為4個域:
位[31:24]為條件標志位域,用f表示;
位[23:16]為狀態位域,用s表示;
位[15:8]為擴展位域,用x表示;
位[7:0]為控制位域,用c表示;
該指令通常用於恢復或改變程序狀態寄存器的內容,在使用時,一般要在MSR指令中指明將要操作的域。
5.3 改變模式示例
使用 _flg
后綴允許你改變標志位而不影響控制位。 要設置 V 標志:
MSR CPSR_flg, #&10000000
要改變模式:
MRS R0, CPSR_all ; 復制 PSR
BIC R0, R0, #&1F ; 清除模式位
ORR R0, R0, #new_mode ; 把模式位設置為新模式
MSR CPSR_all, R0 ; 寫回 PSR,變更模式
六、比較指令
- CMN
- CMP
- TEQ
- TST
6.1 CMN:比較取負的值
CMN{條件}{P} <op 1>, <op 2> status = op_1 - (- op_2)
CMN 同於 CMP,但它允許你與負值(操作數 2 的取負的值)進行比較,比如難於用其他方法實現的用於結束列表的 -1。這樣與 -1 比較將使用:
CMN R0, #1 ; 把 R0 與 -1 進行比較
6.2 CMP : 比較
CMP{條件}{P} <op 1>, <op 2> status = op_1 - op_2
CMP 允許把一個寄存器的內容如另一個寄存器的內容或立即值進行比較,更改狀態標志來允許進行條件執行。它進行一次減法,但不存儲結果,而是正確的更改標志。標志表示的是操作數 1 比操作數 2 如何(大小等)。如果操作數 1 大於操作操作數 2,則此后的有 GT 后綴的指令將可以執行。
6.3 TEQ : 測試等價
TEQ{條件}{P} <op 1>, <op 2> Status = op_1 EOR op_2
TEQ 類似於 TST。區別是這里的概念上的計算是 EOR 而不是 AND。這提供了一種查看兩個操作數是否相同而又不影響進位標志(不象 CMP 那樣)的方法。加上 P 后綴的 TEQ 還可用於改變 R15 中的標志(在 26-bit 模式中)。
6.4 TST : 測試位
TST{條件}{P} <op 1>, <op 2> Status = op_1 AND op_2
TST 類似於 CMP,不產生放置到目的寄存器中的結果。而是在給出的兩個操作數上進行操作並把結果反映到狀態標志上。使用 TST 來檢查是否設置了特定的位。操作數 1 是要測試的數據字而操作數 2 是一個位掩碼。經過測試后,如果匹配則設置 Zero 標志,否則清除它。
TST R0, #%1 ; 測試在 R0 中是否設置了位 0。
七、移位指令
- LSL 邏輯左移
- ASL 算術左移
- LSR 邏輯右移
- ASR 算術右移
- ROR 循環右移
- RRX 帶擴展的循環右移
移位操作在 ARM 指令集中不作為單獨的指令使用,它是指令格式中是一個字段,在匯編語言中表示為指令中的選項。
如果數據處理指令的第二個操作數或者單一數據傳送指令中的變址是寄存器,則可以對它進行各種移位操作。如果數據處理指令的第二個操作數是立即值,在指令中用 8 位立即值和 4 位循環移位來表示它,所以對大於 255 的立即值,匯編器嘗試通過在指令中設置循環移位數量來表示它,如果不能表示則生成一個錯誤。在邏輯類指令中,邏輯運算指令由指令中 S 位的設置或清除來確定是否影響進位標志,而比較指令的 S 位總是設置的。在單一數據傳送指令中指定移位的數量只能用立即值而不能用寄存器。
7.1 邏輯或算術左移
Rx, LSL #n Rx, ASL #n Rx, LSL Rn 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
7.2 邏輯右移
Rx, LSR #n
Rx, LSR Rn
它在概念上與左移相對。把所有位向更低有效位方向移動。如果邏輯類指令中 S 位被設置了,則把最后被移出最右端的那位放置到進位標志中。它同於 BASIC 的 register = value >>> shift。
7.3 算術右移
Rx, ASR #n
Rx, ASR Rn
類似於 LSR,但使用要被移位的寄存器(Rx)的第 31 位的值來填充高位,用來保護補碼表示中的符號。如果邏輯類指令中 S 位被設置了,則把最后被移出最右端的那位放置到進位標志中。它同於 BASIC 的 register = value >> shift。
7.4 循環右移
Rx, ROR #n Rx, ROR Rn
循環右移類似於邏輯右移,但是把從右側移出去的位放置到左側,如果邏輯類指令中 S 位被設置了,則同時放置到進位標志中,這就是位的‘循環’。一個移位量為 32 的操作將導致輸出與輸入完全一致,因為所有位都被移位了 32 個位置,又回到了開始時的位置!
7.5 帶擴展的循環右移
Rx, RRX
這是一個 ROR#0 操作,它向右移動一個位置 - 不同之處是,它使用處理器的進位標志來提供一個要被移位的 33 位的數量。
八、偽指令
ARM 匯編偽指令主要包括ADR、ADRL、ALIGN、EQUx、.extern、.text等等,這里只介紹一部分。下面展示一部分代碼:
.extern main .text .global _start _start:
8.1 .extern
.extern定義一個外部符號(可以是變量也可以是函數),上面的代碼表示本文件中引用的main是一個外部函數。調用的時候可以遍訪所有文件找到main函數並且使用它。
8.2 .text
.text表示下面的語句都屬於代碼段。
8.3 .global
.global將程序中某個標號定義為全局的。.global _start 讓_start符號成為全局可見的標示符,這個符號可以被當前源文件以外的其他文件使用也可以被鏈接腳本(鏈接器)使用,_start僅僅是一個標號對應一個地址並不是C中的一個變量。
使用.global之后,這樣鏈接器就知道跳轉到程序中的什么地方並開始執行。一般我們在鏈接文件中會這樣使用_start:
/* s3c2440鏈接腳本 */ OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { ... }
此外,如果start.S匯編中想引入引入lowleverl_init.S文件中的函數,只需要在lowleverl_init.S中使用.global聲明即可;
.global lowlevel_init lowlevel_init: /* memory control configuration */ /* make r0 relative the current location so that it */ /* reads SMRDATA out of FLASH rather than memory ! */ ldr r0, =SMRDATA ldr r1, =CONFIG_SYS_TEXT_BASE sub r0, r0, r1 ldr r1, =BWSCON /* Bus Width Status Controller */ add r2, r0, #13*4 0: ldr r3, [r0], #4 str r3, [r1], #4 cmp r2, r0 bne 0b /* everything is fine now */ mov pc, lr
start.s中直接調用:
bl lowlevel_init
8.4 .DCx初始化數據存儲
DCx <值>
沒有 DCx 指令。‘x’表示一個可能的范圍。它們是:
DCB 預備一個字節(8 位值) DCW 預備一個半字(16 位值) DCD 預備一個字(32 位值) DCS 按給出的字符串的要求預備直到 255 個的字符
例如:
.start_counter DCB 1 .pointer DCD 0 .error_block DCD 17 DCS "Uh-oh! It all went wrong!" + CHR$0 ALIGN
8.5 EQUx : 初始化數據存儲
EQUx <值>
沒有 EQUx 指令,‘x’表示一個可能的范圍。它們是:
EQUB 預備一個字節(8 位值) EQUW 預備一個半字(16 位值) EQUD 預備一個字(32 位值) EQUS 按給出的字符串的要求預備直到 255 個的字符
簡單的理解,除了名字不同之外與DCx 完全一樣。
九、匯編指令的執行條件
大多數ARM指令都可以條件執行,即根據CPSR寄存器中的條件標志位決定是否執行該指令;如果條件不滿足,該指令相當於一條nop指令。
每天ARM指令包含4位的條件碼域,這表明可以定義16個執行條件。可以將這些執行條件的助記符附加在匯編指令后,比如MOVEQ、MOVGT。這16個條件碼和它們的助記符如下表所示:
條件碼 | 助記符 | 含義 | CPSR中條件標志位 |
0000 | EQ | 相等 | Z=1 |
0001 | NE | 不相等 | Z=0 |
0010 | CS/HS | 無符號數大於/等於 | C=1 |
0011 | CC/LO | 無符號數小於 | C=0 |
0100 | MI | 負數 | N=1 |
0101 | PL | 非負數 | N=0 |
0110 | VS | 上溢出 | V=1 |
0111 | VC | 沒有上溢出 | V=0 |
1000 | HI | 無符號數大於 | C=1且Z=0 |
1001 | LS | 無符號數小於等於 | C=0或Z=1 |
1010 | GE | 帶符號數大於等於 | N=1、V=1或N=0、V=0 |
1011 | LT | 帶符號數小於 | N=1、V=0或N=0、V=1 |
1100 | GT | 帶符號數大於 | Z=0且N=V |
1101 | LE | 帶符號數小於/等於 | Z=1或N!=V |
1110 | AL | 無條件執行 | - |
1111 | NV | 從不執行 | - |
標志的意義:
- N Negative 如果結果是負數則置位
- Z Zero 如果結果是零則置位
- C Carry 如果發生進位則置位
- V Overflow 如果發生溢出則置位
十、注釋
在GNU ARM嵌入式匯編源程序中的注釋方式有:
- /**/ 塊注釋,跟C語言的一樣;
- // 行注釋,跟C語言的一樣;
- @ 行注釋;
- # 行注釋;
另外,匯編指令大小寫也是可以混合的。
參考文章