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 才不會跳轉。