ARM匯編關鍵知識點總結(轉)


1.
LDR R1, =COUNT 意思是將 COUNT 變量的地址放到 R1中
LDR R1, COUNT 意思是將 COUNT 變量地址里面的內容賦給 R1

2.

Load-Store 結構——這個應該是 RISC設計中比較有特點的一部分。在 RISC 中,CPU 並不會對內存中的數據進行操作, 所有的計算都要求在寄存器中完成。 而寄存器和內存的通信則由單獨的指令來完成。而在 CSIC中,CPU是可以直接對內存進行操作的,這也是一個比較特別的地方。所以,在 ARM中,cpu只能通過寄存器來對內存的數據進行訪問和更改。

LDR Rd,(地址)
STR Rd, (地址)
LDMIA Rn!, regist
STMIA Rn!, regist

注意上面 LDR/STR 和 LDMIA/STMIA 的區別,LDR/STR 命令使用時,寄存器在前,地址在后。 而在 LDMIA/STMIA 使用時, 地址在前, 寄存器在后。 這就決定了 LDR 和 LDM 同為加載命令, 但操作順序是不同的, 同理 STR/STM。 但有一點他們是相同的, 即加載 LDR/LDM的意思是把內存的數據 (即上面的地址) 加載到寄存器; 存儲 STR/STM 的意思是把寄存器的內容存儲到內存(即上面的地址) 。這樣比較之后也就全明白了,只需明白哪部分是寄存器,哪部分是地址(內存) ,然后區別是加載還是存儲,就可以知道操作方向。

LDM/STM指令主要用於現場保護,數據復制,參數傳送等

3.

LDM/STM
IA/IB,DA,DB 數據塊傳輸
FD/ED,EA/FA 堆棧操作
LDMIA Rn!, regList
STMIA Rn!, regList

其中 Rn 加載/存儲的起始地址寄存器,Rn 必須為 R0~R7

RegList 加載/存儲的起始寄存器列表,寄存器必須為 R0~R7

4.
在匯編程序中 !的使用,意思是回寫,比如:

ldr r1,[sp, #S_PSR]
ldr lr, [sp, #S_PC]! 其中 ! 用來控制基址變址尋址的的最終新地址是否進行回寫操作

此條語句的意思是 執行 ldr 之后 sp 被回寫成 sp+#S_PC 基址變址尋址的新地址。

5.

ARM 堆棧的組織結構是滿棧降的形式,滿棧即 sp 是要停留在最后一個進棧元素;降,就是堆棧的增長方向是從高地址向低地址發展。
ARM 對於堆棧的操作一般采用 LDMFS(pop)和 STMFD(push)兩個命令。
難點在於 STMFD 命令對於操作數是按照什么順序壓棧的。
比如:STMFD sp! {R0-R5,LR}進棧順序是:

高地址
LR #先進棧 R5 R4 ........... R0 <-SP
低地址

ARM 指令
多寄存器尋址:

LDMIA R0!,{R1-R4}
;R1<----[R0]
;R2<----[R0+4]
;R3<----[R0+8]
;R4<----[R0+12]

堆棧尋址:

STMFD 入棧指令,相當於 STMDB

STMFD SP!,{R2-R4}
;[SP-4]<---R4
;[SP-8]<---R3
;[SP-12]<---R2

LDMFD 出棧指令,相當於 LDMIA

LDMFD SP!,{R6-R8}
;R6<----[SP]
;R7<----[SP+4]
;R8<----[SP+8]

6.
匯編語句 LDMFD SP!, {R0-R12, LR, PC }^ 程序后面的^ ,表示什么意思?
'^'是一個后綴標志,不能在 User 模式和 Sys 系統模式下使用該標志.該標志有兩個存在目的:
1) 對於 LDM 操作,程序會自動的將 spsr 的值拷貝到 cpsr 中。

比如:在 IRQ 中斷返回代碼中

ldmfd sp!, {r4} //讀取 sp 中保存的的 spsr 值到 r4中
msr spsr_cxsf, r4 //對 spsr 的所有控制為進行寫操作,將 r4的值全部注入 spsr
ldmfd sp! {r0-r12,lr,pc}^ //當指令執行完畢,pc 跳轉之前,將 spsr 的值自動拷貝到 cpsr 中

2)數據的送入,送出發生在 User 用戶模式下的寄存器,而非當前模式寄存器

如 ldmdb sp, {r0-lr}^;表示 sp 棧中的數據回復到 User 分組寄存器 r0-lr 中,而不是恢復到當前模式寄存器 r0-lr, 當然對於 User, System, IRQ,SVC,Abort, Undefined這6種模式來說 r0-r12是共用的,只是 r13和 r14為分別獨有,對於 FIQ 模式,僅僅 r0-r7是和前6種模式的 r0-r7共用,r8-r14都是 FIQ 模式下專有。

7. 關於 ldr/str 幾條指令使用的區別

ldr ip,[sp],#4 將 sp 中內容存入ip,之后 sp=sp+4;
ldr ip,[sp,#4] 將 sp+4這個新地址下的內容存入ip,之后 sp 值保持不變
ldr ip,[sp,#4]! 將 sp+4這個新地址下的內容存入ip,之后 sp=sp+4將新地址值賦給 sp
str ip,[sp],#4 將ip存入 sp 地址處,之后 sp=sp+4
str ip,[sp,#4] 將ip存入 sp+4這個新地址,之后 sp 值保持不變
str ip,[sp,#4]! 將ip存入 sp+4這個新地址,之后 sp=sp+4將新地址值賦給sp

8.
movs r1,#3; movs 將導致 ALU 被更改,因為 r1賦值非0,即操作結果 r1非0,所以 ALU 的 Z 標志清0
N,Z,C,V 稱為 ALU(算術邏輯單元)狀態標志。N:如果結果是負數則置位;Z:如果結果是零則置位;C:如果發生進位則置位;V:如果發生進位則置位。
9.
teq r1,#0 //r1-0,將結果送入狀態標志,如果 r1和0相減的結果為0,那么 ALU 的Z 置位,否則 Z 清0
bne reschedule//ne 表示 Z 非0,即:不等,那么執行 reschedule 函數
10。
.使用 tst 來檢查是否設置了特定的位
tst r1,#0x80   //按位 and 操作,檢測 r1的0x1<<7,即第7位是否置1,按位與之后結果為0,那么 ALU 的 Z 置位
beq reset   //如果 Z 置位,即:以上按位與操作結果是0,那么跳轉到 reset 標號執行

11.
  PC 和 LR 寄存器中在異常發生時,或在系統運行時其 PC 和 LR 寄存器值為多少?

下圖為用戶模式下 ARM 處理器體系結構:

  從圖1中我們看到, 在 user 模式下, ARM CPU 有16個數據寄存器, 被命名為 r0~r15(這個要比 x86的多一些)。r13~r15有特殊用途,其中:
◆ r13 - 指向當前棧頂,相當於 x86的 esp,這個東西在匯編指令中要用 sp 表示
◆ r14 - 稱作鏈接寄存器,指向函數的返回地址。用 lr 表示,這和 x86將返回地址保存在棧中是不同的
◆ r15 - 類似於 x86的 eip, 其值等於當前正在執行的指令的地址+8(因為在取址和執行之間多了一個譯碼的階段),這個用 pc 表示。

另外, ARM 處理器還有一個名為 cspr 的寄存器, 用來監視和控制內部操作, 這點和x86 的狀態寄存器是類似的。具體的內容就用到再說了。
總結:在系統正常運行時,PC 值等於當前正在執行的指令的地址+8,(因為在取址和執行之間多了一個譯碼的階段)。

寄存器 R14(LR 寄存器)有兩種特殊功能:
1)在任何一種處理器模式下,該模式對應的 R14寄存器用來保存子程序的返回地址
當執行 BL 或 BLX 指令進行子程序調用時,子程序的返回地址被放置在 R14中。這樣,只要把 R14內容拷貝到 PC 中,就實現了子程序的返回。
2)當某異常發生時,相應異常模式下的 R14被設置成異常返回的地址(對於某些異常,可能是一個偏移量,一個較小的常量)。異常返回類似於子程序返回,但有小小的不同。

總結:
  所謂的子程序的返回地址, 實際就是調用指令的下一條指令的地址, 也就是 BL 或 BLX指令的下一條指令的地址。所謂的異常的返回的地址,就是異常發生前,CPU 執行的最后一條指令的下一條指令的地址。
例如:(子程序返回地址示例)

指令                      指令所在地址
ADD R2,R1,R3  ;0x300000           
BL subC               ;0x300004
MOV R1,#2          ;0x300008

BL 指令執行后,R14中保存的子程序 subC 的返回地址是0x300008。

再例如:(異常返回地址示例)

指令                      指令所在地址
ADD R2,R1,R3  ;0x300000
SWI 0x98            ;0x300004
MOV R1,#2          ;0x300008

SWI 指令執行后,進入 SWI 異常處理程序,此時 R14中保存的返回地址為0x300008。

總結:在系統正常運行時,PC 的值存儲的是當前正在執行的指令地址的后兩條地址(即+8),而 LR 是在子程序返回或異常返回時才使用,其值為當前正在執行的指令的后一條指令地址(即+4)。
12.
  由於上面 LR 和 PC 寄存器值的特點:我們可以解釋軟中斷實現原理進行解釋。

  SWI,即 software interrupt 軟件中斷。該指令產生一個 SWI 異常。意思就是把處理器模式改變為超級用戶模式,CPSR 寄存器保存到超級用戶模式下的 SPSR 寄存器,並且跳到 SWI 向量。其 ARM 指令格式如下:

SWI{cond} immed_24

  Cond 域:是可選的條件碼 (參見 ARM 匯編指令條件執行詳解).

  immed_24域:范圍從 0 到 224-1 的表達式,(即0-16777215)。用戶程序可以使用該常數來進入不同的處理流程。
一、方法1:獲取 immed_24操作數。
  為了能實現根據指令中 immed_24操作數的不同,跳轉到不同的處理程序,所以我們往往需要在 SWI 異常處理子程序中去獲得 immed_24操作數的實際內容。獲得該操作數內容的方法是在異常處理函數中使用下面指令

LDR R0,[LR,#-4]

  該指令將鏈接寄存器 LR 的內容減去4后所獲得的值作為一個地址,然后把該地址的內容裝載進 R0。此時再使用下面指令,immed_24操作數的內容就保存到了 R0:

BIC R0,R0,#0xFF000000

;Rd,  Rn, Oprand2
;BIC(位清除)指令對 Rn 中的值 和 Operand2 值的反碼按位進行邏輯“與”運算

 

  該指令將 R0的高8位(綠色表示的)清零,並把結果保存到 R0,意思就是取 R0的低24位。

  所以,在 SWI 異常處理子程序中執行 LDR R0,[LR,#-4]語句,實際就是把產生本次 SWI異常的 SWI 指令的內容(如:SWI 0x98)裝進 R0寄存器。又因為 SWI 指令的低24位保存了指令的操作數(如: 0x98), 所以再執行 BIC R0, R0, #0xFF000000語句, 就可以獲得 immed_24操作數的實際內容。


二、方法2:使用參數寄存器。

  實際上,在 SWI 異常處理子程序的實現時,還可以繞開 immed_24操作數的獲取操作,這就是說,我們可以不去獲取 immed_24操作數的實際內容,也能實現 SWI 異常的分支處理。這就需要使用 R0-R4寄存器,其中 R0-R4可任意選擇其中一個,一般選擇R0,遵從 ATPCS 原則。
  具體方法就是, 在執行 SWI 指令之前, 給 R0賦予某個數值, 然后在 SWI 異常處理子程序中根據 R0值實現不同的分支處理。例如:

指令                                             指令所在地址
MOV R0,#1                               ; #1給 R0
SWI 0x98                                  ; 產生 SWI 中斷,執行異常處理程序
SoftwareInterrupt
ADD R2,R1,R3 ;
;SWI 異常處理子程序如下
SoftwareInterrupt
CMP R0, #6                               ; if R0 < 6
LDRLO PC, [PC, R0, LSL #2]       ; if R0 < 6,PC = PC + R0*4,else next
MOVS PC, LR
SwiFunction
DCD function0                            ;0
DCD function1                            ;1
DCD function2                            ;2
DCD function3                            ;3
DCD function4                            ;4
DCD function5                            ;5
Function0
異常處理分支0代碼
Function1
異常處理分支1代碼
function2
異常處理分支2代碼
function3
異常處理分支3代碼
function4
異常處理分支4代碼
function5
異常處理分支5代碼

  在 ARM 體系結構中,當正確讀取了 PC 的值時,該值為當前指令地址值加8字節,也就是說,對於 ARM 指令集來說,讀出的 PC 值指向當前指令的下兩條指令的地址,本例中就是指向SwiFunction 表頭 DCD function0 這個地址,在該地址中保存了異常處理子分支 function0的入口地址。 所以, 當進入 SWI 異常處理子程序 SoftwareInterrupt 時, 如果 R0=0, 執行 LDRLO PC,[PC, R0, LSL #2]語句后, PC 的內容即為 function0的入口地址, 即程序跳轉到了 function0執行。

  在本例中, 因為 R0=1, 所以, 實際程序是跳轉到了 function1執行。 R0左移2位 (LDRLO PC,[PC, R0, LSL #2]) ,即 R0*4, 是因為 ARM 指令是字(4個字節)對齊的 DCD function0等偽指令也是按4字節對齊的。

  在本方法的實現中,實際指令中的24位立即數(immed_24域)被忽略了, 就是說immed_24域可以為任意合法的值。 如在本例中, 不一定使用 SWI 0x98, 還可以為 SWI 0x00或者 SWI 0x01等等,程序還是會進入 SWI 異常處理子程序 SoftwareInterrupt,然后根據 R0的內容跳轉到相應的子分支。
13.
在 ARM 中棧底和棧頂的標識如下:滿遞減棧,棧底在上,棧頂在下是 SP。如下圖所示:

14.

  下面就兩個具體的例子談談 ARM 匯編。第一個是使用跳轉表解決分支轉移問題的例程,源代碼如下(保存的時候請將文件后綴名改為 s):

AREA JumpTest,CODE,READONLY
CODE32
num EQU 4
ENTRY
start
MOV r0, #4
MOV r1, #3
MOV r2, #2
MOV r3, #0
CMP r0, #num
BHS stop
ADR r4, JumpTable
CMP r0, #2
MOVEQ r3, #0
LDREQ pc, [r4,r3,LSL #2]
CMP r0, #3
MOVEQ r3, #1

LDREQ pc, [r4,r3,LSL #2]
CMP r0, #4
MOVEQ r3, #2
LDREQ pc, [r4,r3,LSL #2]
CMP r0, #1
MOVEQ r3, #3
LDREQ pc, [r4,r3,LSL #2]
DEFAULT
MOVEQ r0, #0
SWITCHEND
stop
MOV r0, #0x18
LDR r1, =0x20026
SWI 0x123456
JumpTable
DCD CASE1
DCD CASE2
DCD CASE3
DCD CASE4

DCD DEFAULT
CASE1
ADD r0, r1, r2
B SWITCHEND
CASE2
SUB r0, r1, r2
B SWITCHEND
CASE3
ORR r0, r1, r2
B SWITCHEND
CASE4
AND r0, r1, r2
B SWITCHEND
END

  程序其實很簡單,可見我有多愚笨!還是簡要介紹一下這段代碼吧。首先用 AREA 偽代碼加上 CODE, 表明下面引出的將是一個代碼段 (於此相對的還有數據段 DATA) , ENTRY 和 END成對出現,說明他們之間的代碼是程序的主體。start 段給寄存器初始化。ADR r4, JumpTable一句是將相當於數組的 JumpTable 的地址付給 r4這個寄存器。

  stop 一段是用來是程序退出的,第一個語句“MOV r0,#0x18”將 r0賦值為0x18,這個立即數對應於宏 angel_SWIreason_ReportException。表示 r1中存放的執行狀態。語句“LDR r1,=0x20026”將 r1的值設置成 ADP_Stopped_ApplicationExit,該宏表示程序正常退出。然后使用SWI,語句“SWI 0x123456”結束程序,將 CPU 的控制權交回調試器手中。

  在 JumpTable 表中, DCD 類型的數組包含四個字, 所以, 當實現 CASE 跳轉的時候, 需要將給出的索引乘上4,才是真正前進的地址數。
在語句:

CMP r0,#num
BHS stop

  書上意思是: 如果 r0寄存器中的值比 num 大的話, 程序就跳轉到 stop 標記的行。 但是,實際測試的時候,我發現如果 r0和 num 相等也能跳轉到 stop 標記的行,也就是說只要 r0小於num 才不會跳轉。


免責聲明!

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



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