arm匯編指令整理
.align的作用在于對指令或者數據的存放地址進(jìn)行對齊,有些CPU架構要求固定的指令長(cháng)度并且存放地址相對于2的冪指數圓整,否則程序無(wú)法正常運行,比如ARM;有些系統卻不需要,如果不遵循地址的圓整規則,程序依然可以正確執行,只是降低了一些執行效率,比如i386。.align的作用范圍只限于緊跟它的那條指令或者數據,而接下來(lái)的指令或者數據的地址由上一條指令的地址和其長(cháng)度決定。
本文引用地址:http://dyxdggzs.com/article/201611/317877.htmARM匯編器并不直接使用.align提供的參數作為對齊目標,而是使用2^n的值,比如這里的參數為4,那么圓整對象為2^4 = 16。這也就是為什么在A(yíng)RM平臺的Uboot或者Linux內核匯編中會(huì )出現.align 5的根本原因。.align此時(shí)的取值范圍為0-15,當取值為0,2或者不提供參數時(shí)均圓整于4。如果嘗試使用大于15的值,將會(huì )得到編譯器的err。
在指令出現非對齊情況下,插入.align偽指令,對于32bit的ARM會(huì )進(jìn)行4byte的指令對齊。
2..rept
.rept和.endr之間的語(yǔ)句count次。
3..text
幾個(gè)常用的段代號,基本上與編譯器/處理器都沒(méi)有無(wú)關(guān)系(FLAT模式):
.text- 代碼段
.const - 只讀數據段(有些編譯器不使用此段,將只讀數據并入.data段)
.data- 讀寫(xiě)數據段
.bss - 堆
4..extern
".extern"定義一個(gè)外部符號(可以是變量也可以是函數。
5..global
".global"將本文件中的某個(gè)程序標號定義為全局的。
6..word
.word expression就是在當前位置放一個(gè)word型的值,這個(gè)值就是expression。
相當于用.word定義了一個(gè)16bit的數據。
舉例來(lái)說(shuō),
_rWTCON:
.word 0x15300000
就是在當前地址,即_rWTCON處放一個(gè)值0x15300000
7.更多偽指令
http://www.byywee.com/page/M0/S774/774183.html
8.條件碼表
條件碼助記符 | 標志 | 含義 |
EQ | Z=1 | 相等 |
NE | Z=0 | 不相等 |
CS/HS | C=1 | 無(wú)符號數大于或等于 |
CC/LO | C=0 | 無(wú)符號數小于 |
MI | N=1 | 負數 |
PL | N=0 | 正數 |
VS | V=1 | 溢出 |
VC | V=0 | 沒(méi)有溢出 |
HI | C=1,Z=0 | 無(wú)符號數大于 |
LS | C=0,Z=1 | 無(wú)符號數小于或等于 |
GE | N=V | 帶符號數大于或等于 |
LT | N!=V | 帶符號數小于 |
GT | Z=0,N=V | 帶符號數大于 |
LE | Z=1,N!=V | 帶符號數小于或等于 |
AL | 任何無(wú)條件執行(指令默認條件) |
9.ldr
偽指令LDR
大范圍的地址讀取偽指令.LDR偽指令用于加載32位的立即數或一個(gè)地址值到指定寄存器.在匯編編譯源程序時(shí),LDR偽指令被編譯器替換成一條合適的指令.若加載的常數未超出MOV或MVN的范圍,則使用MOV或MVN指令代替該LDR偽指令,否則匯編器將常量放入字池,并使用一條程序相對偏移的LDR指令從文字池讀出常量.LDR偽指令格式如下:
LDR{cond} register,=expr/label_expr
其中register加載的目標寄存器
expr 32位立即數.
label_expr基于PC的地址表達式或外部表達式.
LDR/STR指令用于對內存變量的訪(fǎng)問(wèn),內存緩沖區數據的訪(fǎng)問(wèn)、查表、外圍部件的控制操作等等,若使用LDR指令加載數據到PC寄存器,則實(shí)現程序跳轉功能,這樣也就實(shí)現了程序散轉。
ldr r1, [r2, #4] /*將地址為r2+4的內存單元數據讀取到r1中*/
ldr r1,[r2] /*將地址為r2的內存單元數據讀取到r1中*/
ldr r1,[r2], #4/*將地址為r2的內存單元數據讀取到r1中,然后r2=r2+4*/
str r1 ,[r2, #4]/*將r1的數據保存到地址為r2+4的內存單元中*/
str r1, [r2]/*將r1的數據保存到地址為r2的內存單元中。*/
str r1, [r2],#4/*將r1的數據保存到地址為r2的內存單元,然后r2= r2+4*/
ldrb:8bit=>1byte
ldrh:16bit=>2byte
LDR R0,LED_TAB
LDR R1, =LED_TAB
LED_TAB: .work 0x12345678
R0的值是0x12345678,R1的值是LED_TAB標號值,就是0x12345678在內存中存放的地址
10.adr
轉自:http://coon.blogbus.com/logs/2738861.html
ldr r0, _start
adr r0, _start
ldr r0, =_start
nop
mov pc, lr
_start:
nop
編譯的時(shí)候設置 R0 為 0x0c008000
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
0c008000 <_start-0x14>:
c008000: e59f000c ldr r0, [pc, #12] ; c008014 <_start>
c008004: e28f0008 add r0, pc, #8 ; 0x8
c008008: e59f0008 ldr r0, [pc, #8] ; c008018 <_start+0x4>
c00800c: e1a00000 nop (mov r0,r0)
c008010: e1a0f00e mov pc, lr
0c008014 <_start>:
c008014: e1a00000 nop (mov r0,r0)
c008018: 0c008014 stceq 0, cr8, [r0], -#80
分析:
ldr r0, _start
從內存地址 _start 的地方把值讀入。執行這個(gè)后,r0 = 0xe1a00000
adr r0, _start
取得 _start 的地址到 r0,但是請看反編譯的結果,它是與位置無(wú)關(guān)的。其實(shí)取得的時(shí)相對的位置。例如這段代碼在 0x0c008000 運行,那么 adr r0, _start 得到 r0 = 0x0c008014;如果在地址 0 運行,就是 0x00000014 了。
ldr r0, =_start
這個(gè)取得標號 _start 的絕對地址。這個(gè)絕對地址是在 link 的時(shí)候確定的??瓷先ミ@只是一個(gè)指令,但是它要占用 2 個(gè) 32bit 的空間,一條是指令,另一條是 _start 的數據(因為在編譯的時(shí)候不能確定 _start 的值,而且也不能用 mov 指令來(lái)給 r0 賦一個(gè) 32bit 的常量,所以需要多出一個(gè)空間存放 _start 的真正數據,在這里就是 0x0c008014)。
因此可以看出,這個(gè)是絕對的尋址,不管這段代碼在什么地方運行,它的結果都是 r0 = 0x0c008014
11.ldm
ldm和stm屬于批量?jì)却嬖L(fǎng)問(wèn)指令,只用一條指令就可以讀寫(xiě)多個(gè)數據。它們的格式如下:
ldm{cond}
stm{cond}
其中,
{cond}表示指令的執行條件,參見(jiàn)下面的指令條件碼。
表示地址變化模式,有以下幾種方式:
1)ia(increment after): 傳送后遞增方式;
2)ib(increment before): 傳送前遞增方式;
3)da(decrement after): 傳送后遞減方式;
4)db(decrement before):傳送前遞減方式;
5)fd(full descending): 滿(mǎn)遞減堆棧;
6)ed(empty descending):空遞減堆棧;
7)fa(full ascending): 滿(mǎn)遞增堆棧;
8)ea(empty ascending): 空遞增堆棧;
{^}有兩種含義:如果
指令中寄存器列表和內存單元的對應關(guān)系為:編號低的寄存器對應于內存中的低地址單元,編號高的寄存器對應于內存中高地址的單元。
ldmia r0!, {r3-r10} /*將基址寄存器r0開(kāi)始的連續8個(gè)地址單元的值分別賦給r3,r4,r5,r6,r7,r8,r9,r10,注意的是r0指定的地址每次賦一次r0會(huì )加1,指向下一個(gè)地址單元*/
stmia r1!, {r3-r10} /*跟上面指令功能相反,將寄存器r3到r10的值依次賦值給r1指定的地址單元,每次賦值一次r1就加1*/
堆棧尋址:堆棧是特定順序進(jìn)行存取的存儲區,堆棧尋址時(shí)隱含的使用一個(gè)專(zhuān)門(mén)的寄存器(堆棧指針),指向一塊存儲區域(堆棧),存儲器堆??煞譃閮煞N:
向上生長(cháng):向高地址方向生長(cháng),稱(chēng)為遞增堆棧。
向下生長(cháng):向低地址方向生長(cháng),稱(chēng)為遞減堆棧。
如此可結合出四種情況:
1、滿(mǎn)遞增:堆棧通過(guò)增大存儲器的地址向上增長(cháng),堆棧指針指向內含有效數據項的最高地址,棧指針總是指向最后一個(gè)元素(最后入棧的數據),指令如 LDMFA,STMFA。
2、空遞增:堆棧通過(guò)增大存儲器的地址向上增長(cháng),堆棧指針指向堆棧上的第一個(gè)空位置,棧指針總是指向下一個(gè)將要放入數據的空位置,指令如 LDMEA,STMEA。
3、滿(mǎn)遞減:堆棧通過(guò)減小存儲器的地址向下增長(cháng),堆棧指針指向內含有效數據項的最低地址,棧指針總是指向最后一個(gè)元素(最后入棧的數據),指令如 LDMFD,STMFD。
4、空遞減:堆棧通過(guò)減小存儲器的地址向下增長(cháng),堆棧指針指向堆棧下的第一個(gè)空位置,棧指針總是指向下一個(gè)將要放入數據的空位置,指令如 LDMED,STMED。
滿(mǎn):棧指針總是指向最后一個(gè)元素(最后入棧的數據)。
空:棧指針總是指向下一個(gè)將要放入數據的空位置。
增:棧首部是低地址,棧向高地址增長(cháng)。
減:棧首部是高地址,棧向低地址增長(cháng)。
STMFD SP!,{R1-R7,LR} ;將R1-R7,LR入棧,滿(mǎn)遞減堆棧
LDMFD SP!,{R1-R7,LR} ;數據出棧,放入R1-R7,LR寄存器,滿(mǎn)遞減堆棧
ARM-Thumb過(guò)程調用標準和ARM、Thumb C/C++ 編譯器總是使用Full descending 類(lèi)型堆棧。
以前困惑的就是STMFD 命令 對于操作數 是按照什么順序壓棧的
比如:STMFD sp!{R0-R5,LR} 進(jìn)棧順序是:
高地址(1方式) LR R5 R4 ``````` R0 <-sp 低地址
高地址(2方式) R0 R1 ``` R5 LR <-sp 低地址
尋址方式 | 說(shuō)明 | pop | =LDM | push | =STM |
FA | 遞增滿(mǎn) | LDMFA | LDMDA | STMFA | STMIB |
FD | 遞減滿(mǎn) | LDMFD | LDMIA | STMFD | STMDB |
EA | 遞增空 | LDMEA | LDMDB | STMEA | STMIA |
ED | 遞減空 | LDMED | LDMIB | STMED | STMDA |
按照圖表,可知 STMFD對應的是STMDB,根據arm指令手冊,可知STMDB入棧順序是1方式,而LDMFD對應的是LDMIA,這樣這兩個(gè)操作就可以成功配對。
在寄存器傳輸中,基地址可以在傳輸前或者傳輸后遞增或者遞減:STMIA r10,{r1,r3-r5,r8}
http://blog.csdn.net/xiaomt_rush/article/details/6501711
12.ldrex
在 include/asm-arm/spinlock.h 下有這麼一段
#if __LINUX_ARM_ARCH__ <6
#errorSMP not supported on pre-ARMv6 CPUs
#endif
好啦,前提就是:只有ARM core版本>=6才可以繼續:
all spin lock primitives 到最後都是使用下面這個(gè)基本型:
static inline void__raw_spin_lock(raw_spinlock_t *lock)
{
unsigned longtmp;
1 __asm____volatile__(
2"1: ldrex %0, [%1]n"
3" teq %0, #0n"
4" strexeq %0, %2, [%1]n"
5" teqeq %0, #0n"
6" bne 1b"
7 : "=&r" (tmp)
8 : "r" (&lock->lock), "r" (1)
9: "cc");
smp_mb();
}
[指令重點(diǎn)]:
ldrex 指令是 core 6 以後才有的,跟 strex 配成一對指令,可以請 bus 監控從 ldrex 到 strex 之間有無(wú)其他的 CPU 或 DMA 來(lái)存取這個(gè)位址 ,若有的話(huà),strex 會(huì )在第一個(gè) register 裡設定值為 1(non-exclusive by this CPU) 並且令 store 動(dòng)作失敗,若沒(méi)有,strex 會(huì )在第一個(gè) register 裡設定值為 0(exclusive access by this CPU) 並且令 store 動(dòng)作成功。
Code Trace Discussion:
Line 1: __volatile__ 告訴 compiler ,不要對這塊 assembly template 做最佳化動(dòng)作,因為我們裡面有 loop 讀取 memory 動(dòng)作,最佳化的結果可能導致 compiler 用一個(gè) register 來(lái) cache 它的值,不會(huì )老老實(shí)實(shí)的去讀 memory... ,這不是我們想要的動(dòng)作喔!
Line 2: 把 lock 讀到 tmp,並請 bus monitor 這個(gè) memory。
Line 3: 測試 lock 是否為 0,若非 0,表示 lock 已經(jīng)被別人取得了,則 Line 4,5 都不做了,然後 Line 6 一定 branch,做 spin 的動(dòng)作。若為 0,表示有機會(huì )取得 lock,繼續做 Line 4.5.。
Line 4:重點(diǎn)來(lái)了!,核對 bus monitor 的結果,若是exclusive access 則 tmp 設為 0 並且把 1 儲存到 lock,若是 non-exclusive access(有其他 CPU 來(lái)動(dòng)過(guò))則 tmp設為 1並且不做儲存 lock 的動(dòng)作。
Line 5: 測試 tmp。
Line 6: 若 tmp 為 0 表示剛剛對 lock 動(dòng)作是 exclusive,可以離開(kāi)迴圈,若 tmp 為 1,則做 spin 動(dòng)作。
Line 7: tmp 用 register 來(lái)操作,同時(shí)是 input 及 output 令它為 %0。
Line 8: &lock->lock 用 register 來(lái)操作 ,令它為 %1,值 1 用 register 來(lái)操作 ,令它為 %2。
Line 9: 此 template 會(huì )改到 condition code,加入 clobber list 以告訴 compiler 有這回事。
好了,終於看完了,真的很佩服那些 coding 的 kernel hackers ....
思考問(wèn)題: ARM v6 以前有個(gè) SWP 指令可以 lock bus and swap memory ,一樣可以用來(lái)完成 exclusive access ,但是比起 ldrex,strex 這對指令有什麼缺點(diǎn)呢?
SWP lock bus,其他 CPU 所有動(dòng)作都不能做,但是 ldrex,strex就不會(huì )有這種現象,使用 ldrex,strex 時(shí)若其他 CPU不來(lái) access 這個(gè)特定的 memory 就可以平行的做動(dòng)作,增加平行執行的 performance。
13.swi
SWI,即software interrupt軟件中斷。該指令產(chǎn)生一個(gè)SWI異常。意思就是處理器模式改變?yōu)槌売脩?hù)模式,CPSR寄存器保存到超級用戶(hù)模式下的SPSR寄存器,并且跳轉到SWI向量。其ARM指令格式如下:
SWI{cond} immed_24
Cond域:是可選的條件碼 (參見(jiàn) ARM匯編指令條件執行詳解).
immed_24域:范圍從 0 到 224-1 的表達式, (即0-16777215)。用戶(hù)程序可以使用該常數來(lái)進(jìn)入不同的處理流程。
一、方法1:獲取immed_24操作數。
為了能實(shí)現根據指令中immed_24操作數的不同,跳轉到不同的處理程序,所以我們往往需要在SWI異常處理子程序中去獲得immed_24操作數的實(shí)際內容。獲得該操作數內容的方法是在異常處理函數中使用下面指令:
LDR R0,[LR,#-4]
該指令將鏈接寄存器LR的內容減去4后所獲得的值作為一個(gè)地址,然后把該地址的內容裝載進(jìn)R0。此時(shí)再使用下面指令,immed_24操作數的內容就保存到了R0:
BIC R0,R0,#0xFF000000
該指令將R0的高8位清零,并把結果保存到R0,意思就是取R0的低24位。
可能還是有人會(huì )問(wèn):為什么在SWI異常處理子程序中執行這兩條指令后,immed_24操作數的內容就保存到了R0寄存器呢?之所以會(huì )有這樣的疑問(wèn),基本都是因為對LR寄存器的作用沒(méi)了解清楚。下面介紹一下鏈接寄存器LR(R14)的作用。
寄存器R14(LR寄存器)有兩種特殊功能:
·在任何一種處理器模式下,該模式對應的R14寄存器用來(lái)保存子程序的返回地址。當執行BL或BLX指令進(jìn)行子程序調用時(shí),子程序的返回地址被放置在R14中。這樣,只要把R14內容拷貝到PC中,就實(shí)現了子程序的返回(具體的子程序返回操作,這里不作詳細介紹)。
·當某異常發(fā)生時(shí),相應異常模式下的R14被設置成異常返回的地址(對于某些異常,可能是一個(gè)偏移量,一個(gè)較小的常量)。異常返回類(lèi)似于子程序返回,但有小小的不同(這里不作詳細介紹)。
所謂的子程序的返回地址,實(shí)際就是調用指令的下一條指令的地址,也就是BL或BLX指令的下一條指令的地址。所謂的異常的返回的地址,就是異常發(fā)生前,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指令執行后,進(jìn)入SWI異常處理程序,此時(shí)R14中保存的返回地址為0x300008。
所以,在SWI異常處理子程序中執行LDR R0,[LR,#-4]語(yǔ)句,實(shí)際就是把產(chǎn)生本次SWI異常的SWI指令的內容(如:SWI 0x98)裝進(jìn)R0寄存器。又因為SWI指令的低24位保存了指令的操作數(如:0x98),所以再執行BIC R0,R0,#0xFF000000語(yǔ)句,就可以獲得immed_24操作數的實(shí)際內容。
二、方法2:使用參數寄存器。
實(shí)際上,在SWI異常處理子程序的實(shí)現時(shí),還可以繞開(kāi)immed_24操作數的獲取操作,這就是說(shuō),我們可以不去獲取immed_24操作數的實(shí)際內容,也能實(shí)現SWI異常的分支處理。這就需要使用R0-R4寄存器,其中R0-R4可任意選擇其中一個(gè),一般選擇R0,遵從ATPCS原則。
具體方法就是,在執行SWI指令之前,給R0賦予某個(gè)數值,然后在SWI異常處理子程序中根據R0值實(shí)現不同的分支處理。例如:
指令 指令所在地址
MOV R0,#1 ; #1給R0
SWI 0x98 ; 產(chǎn)生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 //PC-8處的指令
MOVS PC, LR //PC-4處的指令
SwiFunction
DCD function0 ;0//PC處的指令
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代碼
在A(yíng)RM體系結構中,當正確讀取了PC的值時(shí),該值為當前指令地址值加8字節,也就是說(shuō),對于A(yíng)RM指令集來(lái)說(shuō),讀出的PC值指向當前指令的下兩條指令的地址,本例中就是指向SwiFunction 表頭DCD function0這個(gè)地址,在該地址中保存了異常處理子分支function0的入口地址。所以,當進(jìn)入SWI異常處理子程序SoftwareInterrupt時(shí),如果R0=0,執行LDRLO PC, [PC, R0, LSL #2]語(yǔ)句后,PC的內容即為function0的入口地址,即程序跳轉到了function0執行。在本例中,因為R0=1,所以,實(shí)際程序是跳轉到了function1執行。R0左移2位(LDRLO PC, [PC,R0, LSL #2]),即R0*4,是因為ARM指令是字(4個(gè)字節)對齊的DCD function0等偽指令也是按4字節對齊的。
在本方法的實(shí)現中,實(shí)際指令中的24位立即數(immed_24域)被忽略了, 就是說(shuō)immed_24域可以為任意合法的值。如在本例中,不一定使用SWI 0x98,還可以為SWI 0x00或者SWI 0x01等等,程序還是會(huì )進(jìn)入SWI異常處理子程序SoftwareInterrupt,然后根據R0的內容跳轉到相應的子分支。
ARM處理器使用流水線(xiàn)來(lái)增加處理器指令流的速度,這樣可使幾個(gè)操作同時(shí)進(jìn)行,并使處理與存儲器系統之間的操作更加流暢,連續,能提供0.9MIPS/MHZ的指令執行速度。 PC代表程序計數器,流水線(xiàn)使用三個(gè)階段,因此指令分為三個(gè)階段執行:
1.取指(從存儲器裝載一條指令);
2.譯碼(識別將要被執行的指令);
3.執行(處理指令并將結果寫(xiě)回寄存器)。
而R15(PC)總是指向“正在取指”的指令,而不是指向“正在執行”的指令或正在“譯碼”的指令。一般來(lái)說(shuō),人們習慣性約定將“正在執行的指令作為參考點(diǎn)”,稱(chēng)之為當前第一條指令,因此PC總是指向第三條指令。當ARM狀態(tài)時(shí),每條指令為4字節長(cháng),所以PC始終指向該指令地址加8字節的地址,即:PC值=當前程序執行位置+8;
周期1 周期2 周期3 周期4 周期5 周期6
PC-8取指 譯碼 執行
PC-4 取指 譯碼 執行
PC 取指 譯碼執行
14.cmp
CMP比較指令,用于把一個(gè)寄存器的內容和另一個(gè)寄存器的內容或一個(gè)立即數進(jìn)行比較,同時(shí)更新CPSR中條件標志位的值。指令將第一操作數減去第二操作數,但不存儲結果,只更改條件標志位。
CMP R1, R0 ;做R1-R0的操作。
CMP R1,#10 ;做R1-10的操作。
15.txt
TST位測試指令,用于把一個(gè)寄存器的內容和另一個(gè)寄存器的內容或立即數進(jìn)行按位的與運算,并根據運算結果更新CPSR中條件標志位的值。操作數1是要測試的數,而操作數2 是一個(gè)位掩碼,該指令一般用來(lái)檢測是否設置了特定的位。
TST {條件} 操作數1, 操作數2
例:TST R0, #0X0000 0040 ; 指令用來(lái)測試R0的位3是否為1。
TST指令通常和EQ、NE條件碼配合使用,當所有測試位為0時(shí),EQ有效,而只要有一個(gè)測試位不為0,則NE有效。
16.teq
TEQ相等測試指令,用于把一個(gè)寄存器的內容和另一個(gè)寄存器的內容或立即數進(jìn)行按位的異或運算,并根據運算結果更新CPSR中的條件標志位。指令用于比較兩個(gè)操作數是否相等。如果相等,則 Z = 1,否則Z = 0。指令通常和EQ、NE條件碼配合使用
例:TEQ R1, R2
TST R1,#%1;測試R1中是否設置了最低位(%表示二進(jìn)制數)
17.cmn
CMN -- 比較取負的值
CMN{條件}{P}
status = op1 - (-op2)
CMN R0, #1 @把R0與-1進(jìn)行比較
18.bic
BIC指令用于清除操作數1的某些位,并把結果放置到目的寄存器中,如果在掩碼中設置了某一位,則清除這一位。未設置的掩碼位保持不變。
例:BIC R1, R1, #0X0F ;將R1的低四位清零,其他位不變。
評論