嵌入式Linux之常用ARM匯編


在嵌入式開發中,匯編程序常常用於非常關鍵的地方,比如系統啟動時的初始化,中斷上下文的保存和恢復,對性能要求非常苛刻的函數等。

在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語言的一樣;
  • @ 行注釋;
  • # 行注釋;

另外,匯編指令大小寫也是可以混合的。

參考文章

[1] ARM匯編手冊


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM