ARM指令學習筆記
參考----------<百度>"arm百度百科","NDS百度百科"
本文引用地址:http://www.biyoush.com/article/201611/316807.htm接下來就是對arm指令的學習。
因為有過前面8086指令的學習,并且也寫過像高精度計算這樣的匯編程序,看arm指
心里面老在比較這兩套指令。
arm7TDMI(-S)指令系統有兩套指令集,分別是32位的Arm指令集和16位的thumb指令集。簡單點說:arm支持arm內核的所有特點,具有高效、快速的特點;而thumb指令集靈活、小巧。二者可以互相調用,Thumb指令集可以看做是Arm的壓縮形式的子集,是針對代碼的密度問題而提出的,Thumb指令都有對應的Arm指令,但卻不是一個完整的系統,例如:Thumb指令集沒有協處理器指令,信號量指令以及訪問CPSR或SPSR的指令,沒有乘加指令及64位乘法指令等,且指令的第二操作數受到限制;除了跳轉指令B有條件執(zhí)行功能外,其它指令基本為無條件執(zhí)行.,等等。不一一敘述。而Arm指令集出了具有很多Thumb沒有的功能外,它最大的特點就是:高效。
Arm的寄存器是37個,包括
31generalregisters(Rxx)
6status registers(xPSR)
對這37個寄存器的詳細描述我們可以從nocash.emubase.de這個網站上得到。
學習arm指令,最先接觸的是尋址指令。
Arm尋址指令可分為九類:
Arm指令 80x86中有嗎?
1.寄存器尋址;
2.立即尋址;
3.寄存器移位尋址; 無
4.寄存器間接尋址;
5..基址尋址;
6.多寄存器尋址; 無
7.堆棧尋址; 無
8.塊拷貝尋址; 無
9.相對尋址。
可以看到arm尋址指令里面有個很大的特色是它的寄存器移位尋址,即第二個操作數在與第一個操作數結合之前可以選擇進行移位操作,例如:MOVR0,R2,LSL#3。而在80x86指令中這得要三步走:一次賦值(否則影響尋址變量的值)、一次移位、一次尋址。不僅帶來了視覺上的不便,而且給書寫帶來了麻煩(更容易出錯)。另外,Arm指令還可以進行多寄存器尋址,無疑簡化了操作(具體點講,就是少寫很多"LD""ST")。還可以看到我們在arm指令中只需加個"!"就可以決定中間值是否保留。更加方便的是我們可以自由選擇變址前后指針的變化,例如塊拷貝尋址中就有四種:STMIA,STMIB,STMDA,STMDB.連遞減還是遞增,先變址還是先復制,Arm都給你預先設計好了,不能不說它周全,這些都是80x86里沒有的,極大地方便了程序員的程序設計。
順帶說一下其另一個便捷之處,加載/裝填數據時,可以在命令后加H/B來表示對半字/字節(jié)的數據操作,默認情況下是字。并且ARM可以實現一組寄存器和一塊連續(xù)內存之間傳送數據,如LDMIA和STMIA指令。
看完尋址,接下來才是Arm指令的重點,
Arm指令的基本格式為:
————————arm的32偽指令二進制格式
其中<>內的項是必須的,而{}內的項是可選的。
Opcode:指令助記符
Cond:執(zhí)行條件
S:是否影響cpsr寄存器的值
Rd:目標寄存器
Rn:第一個操作數的寄存器
Oprand2:第二個操作數
ARM指令可以分為6大類
(1) 跳轉指令 B、BL、BLX、BX
(2)數據處理指令 數據傳送算術邏輯運算比較
(3)程序狀態(tài)寄存器傳輸指令
(4)load/store指令
(5)協處理指令
(6)異常中斷指令
可以看到
MOVE rcra
CMP rarb
JG NEXT
MOVE rcrb
NEXT
..............
而在arm中可以很方便地寫成
MOVE rcra
CMP rarb
MOVECC rcrb
靈活運用第二個操作數還能大大提高代碼的效率,例如我們要將r1寄存器中的數乘以9,可以在arm中很方便地寫成
ADD R1,R1,R1,LSL#3
換成80x86,那就是一堆步驟了
MOVE R2,R1
SHL R2,3
ADD R1,R2
就是說arm的指令更人性化,加上后來對arm偽指令的學習,我個人覺得arm指令集是匯編中的高級語言了。
在ARM中乘法操作可以用于任意兩個寄存器結果可以保存到任意寄存器,而80x86則需要先將被乘數保存于AL/AX中再做乘法。并且高位和地位還要分開保存至AX/AL和DX/AH中,麻煩!
ARM的中斷指令有:
(1)復位異常
(2)未定義指令異常
(3)軟件中斷異常
(4)預取指中斷異常
(5)數據中止異常
(6)中斷請求異常
(7)快速中斷(FIQ)請求異常
種類繁多學起來很頭疼,不像80x86從頭到尾就是INT中斷那樣清爽。中斷這塊本人不甚明白,還需繼續(xù)努力學習,
接下來的學習就到了偽指令。首先偽指令不是ARM指令集中的指令,只是為了編程方便編譯器定義了偽指令,使用時可以像其他ARM指令一樣使用,但在編譯時這些指令會被等效的ARM指令代替。雖說有些偽指令只是一些極其簡單的替換,但卻極大地方便我們編程。可以說它是以它是一種特殊的助記符。
對于偽指令的學習也只是概念性的,沒法深刻,很多指令雖然知道意思但完全不知道哪兒用的著。有待今后的時間吧。
總的來說,ARM指令有一下幾種
1.符號定義偽指令
全局變量聲明:GBLA、GBLL和GBLS。
局部變量聲明:LCLA、LCLL和LCLS。
變量賦值:SETA、SETL和SETS。
為一個通用寄存器列表定義名稱:RLIST。
為一個協處理器的寄存器定義名稱:CN。
為一個協處理定義名稱:CP。
為一個VFP寄存器定義名稱:DN和SN。
為一個FPA浮點寄存器定義名稱:FN。
2.數據定義偽指令
聲明一個文字池:LTORG。
定義一個結構化的內存表的首地址:MAP。
定義結構化內存表中的一個數據域:FIELD。
分配一塊內存空間,并用0初始化:SPACE。
分配一段字節(jié)的內存單元,并用指定的數據初始化:DCB。
分配一段字的內存單元,并用指令的數據初始化:DCD和DCDU。
分配一段字的內存單元,將每個單元的內容初始化為該單元相對于靜態(tài)基址寄存器的偏移量:DCDO。
分配一段雙字的內存單元,并用雙精度的浮點數據初始化:DCFD和DCFDU。
分配一段字的內存單元,并用單精度的浮點數據初始化:DCFS和DCFSU。
分配一段字的內存單元,并用單精度的浮點數據初始化,指定內存單元存放的是代碼,而不是數據:DCI。
分配一段雙字的內存單元,并用64位整數數據初始化:DCQ和DCQU。
分配一段半字的內存單元,并用指定的數據初始化:DCW和DCWU。
斷言錯誤:ASSERT。這個指令比較神奇,可以在程序首寫一些諸如ASSERTtop<>temp的斷言錯誤指令,在匯編編譯器對匯編程序的第二遍掃描中,如果其中
ASSERT條件不成立,ASSERT偽指令將報告該錯誤信息,從而減少錯誤。有點像C++中try和catch。
3.匯編控制偽指令/宏偽指令
匯編控制偽指令用于條件匯編、宏定義、重復匯編控制等。該類偽指令如下:
條件匯編控制:IF、ELSE和ENDIF
宏定義:MACRO和MEND
重復匯編:WHILE及WEND
這些就是它有點像高級語言的地方,可以用偽指令實現某些高級語句。其實高級語言不就是一個個匯編指令的打包嗎?大同小異。
值得提及的是MACRO和MEND,這個東西感覺就是C中的#define,很強大。
其偽指令格式:
MACRO
{$label}macroname{$parameter}{$parameter}…
其中:$label宏指令被展開時,label可被替換成相應的符號,通常為一個標號在一個 符號前使用$表示被匯編時將使用相應的值替代$后的符號。
macroname所定義的宏的名稱。
$parameter宏指令的參數。當宏指令被展開時將被替換成相應的值,類似于函數 中的形式參數。
可以實現參數的傳遞?。∵@種偽指令讓人看上去就有想去嘗試使用的沖動,以后一定會有機會的!
這里簡單假想下使用:
C語言中:#definebigger(a,b)(a>b)
可以寫成(可能會有錯,僅僅嘗試一下而已):
MACRO
$labelbigger$a,$b
$label
;GL1為一個定義的全局變量
CMP $a,$b
MOVEGT GL1,1
MOVELE GL1,1
MEND
調用testbiggera,b
然后在GL1中得到大小結果
當然我們可以直接比較,這里只是為了演示一下。
4.雜項偽指令
雜項偽指令在匯編編程設計較為常用,如段定義偽指令,入口點設置偽指令,包含 文件偽指令,標號導出或引入聲明等,該類偽指令如下:
邊界對齊:ALIGN。
段定義:AREA。
指令集定義:CODE16和CODE32。
匯編結束:END。
程序入口:ENTRY。
常量定義:EQU。
聲明一個符號可以被其它文件引用:EXPORT和GLORBAL。
聲明一個外部符號:IMPORT和EXTERN。
包含文件:GET和INCLUDE。
包含不被匯編的文件:INCBIN。
保留符號表中的局部符號:KEEP。
禁止浮點指令:NOFP。
指示兩段之間的依賴關系:REQUIRE。
堆棧8字節(jié)對準:PEQUIRE8和PRESERVE8。
給特定的寄存器命名:RN。
標記局部標號使用范圍的界限:ROUT。
最后一塊是C與匯編混合編程。
(1)c中內嵌匯編。曾今看過個故事:一個物理學家寫一個模擬天體運行的程序,分別從算法和指令兩方面經行優(yōu)化,用了一個月的時間將一個原本要幾年才能出結果的程序縮短到十幾分鐘。這其中有個很重要的一步,就是將某些重用性很大的高級語言程序塊用匯編語言直接書寫。大大縮短了程序運行的極限時間。曾今也好奇過,高級語言里面怎么去內嵌匯編?
_asm
{
指令[;指令]
...
[指令]
}
內嵌匯編程序對寄存器、常量、標號等有很多限制,就不多說了。
(2)匯編中內嵌c語言程序
(3)C與匯編互相調用
在學習ARM指令的過程中,遇到過很多問題,第一次碰到往往非常不解,還有些個該注意的地方,當然了,問題和需呀注意的地方遠不止這些。這些只是個人覺得一些比較典型的,寫在這里與諸位分享:
1.#immed_8r常數表達式時“該常數必須對應8位位圖,即常數是由一個8位的常數循環(huán)移位偶數位得到的。”
其意思是這樣:#immed_8r在芯片處理時表示一個32位數,但是它是由一個8位數(比如:01011010,即0x5A)通過循環(huán)移位偶數位得到(10000000000000000000000000010110,就是0x5A通過循環(huán)右移2位(偶數位)的到的)。
而10100000000000000000000000010110,就不符合這樣的規(guī)定,編譯時一定出錯。因為你可能通過將10110101循環(huán)右移位得到它,但是不可能通過循環(huán)移位偶數位得到。
10110000000000000000000000010110,也不符合這樣的規(guī)定,很明顯:101101011有9位。
2.什么叫帶符號擴展.
當從16位向32位賦值時,若選擇無符號擴展,那高位補零。選擇有符號擴展,那32中的16位按照16位最高位補齊。
例如1101010110101010------->11111111111111111101010110101010
0101010110101010------->000000000000000101010110101010
關于為什么這樣補,可以參照補碼定義,這里介紹一種簡單的補碼計算方法:
N位絕對值為k的數的補碼為:2^n-k.比那個取反加一得來得清爽一點。
3.我們說有四種類型的堆棧尋址方式,LDMFA,STMFA,LDMEA,STMEA。
注意F表示full,E表示empty,A表示after,B表示before。
我們假設:在C語言中stack[]為堆棧數組,top為堆的頂指針。為方便理解。我用c語言描述了一下。
堆棧是一種數據結構,按先進后出(FirstInLastOut,FILO)的方式工作,使用一個稱作堆棧指針的專用寄存器指示當前的操作位置,堆棧指針總是指向棧頂。
當堆棧指針指向最后壓入堆棧的數據時,稱為滿堆棧(FullStack),而當堆棧指針指向下一個將要放入數據的空位置時,稱為空堆棧(EmptyStack)。
同時,根據堆棧的生成方式,又可以分為遞增堆棧(AscendingStack)和遞減堆棧(DecendingStack),當堆棧由低地址向高地址生成時,稱為遞增堆棧,當堆棧由高地址向低地址生成時,稱為遞減堆棧。這樣就有四種類型的堆棧工作方式,ARM微處理器支持這四種類型的堆棧工作方式,即:
◎Fulldescending滿遞減堆棧
堆棧首部是高地址,堆棧向低地址增長。棧指針總是指向堆棧最后一個元素(最后一個元素是最后壓入的數據)。
ARM-Thumb過程調用標準和ARM、ThumbC/C++編譯器總是使用Fulldescending類型堆棧。
C語言表示:stack[--top]=value
◎Fullascending滿遞增堆棧
堆棧首部是低地址,堆棧向高地址增長。棧指針總是指向堆棧最后一個元素(最后一個元素是最后壓入的數據)。
C語言表示:stack[top--]=value
◎Emptydescending空遞減堆棧
堆棧首部是低地址,堆棧向高地址增長。棧指針總是指向下一個將要放入數據的空位置。
C語言表示:stack[++top]=value
◎Emptyascending空遞增堆棧
堆棧首部是高地址,堆棧向低地址增長。棧指針總是指向下一個將要放入數據的空位置。
操作堆棧的匯編指令
C語言表示:stack[top++]=value
4.算術位移/邏輯位移/循環(huán)位移
算術位移,邏輯位移邏輯右移最高位補0,最低位進入CF,相當于每移一位除以2,一般對于無符號數使用 如:133/8=16余5 MOVAL,10000101B MOVCL,03H SHRAL,CL AL=10H=16 算術右移最高位(即符號位)保持不變,而不是補0最低位進入CF.相當于每移一位除2,一般對于有符號數使用8/8 MOVAL,10000000B MOVCL,03H SARAL,CL AL=0F0H=-16
----------分別對應邏輯左移、邏輯右移、算術右移、循環(huán)右移
5.關于ARM的B,BL跳轉指令
假設跳轉指令處的地址是A,跳轉目標處的地址是B.
B,BL指令保存的是偏移地址,這個地址的計算方法是:
1.B-(A+8).A+8是因為ARM的流水線使得指令執(zhí)行到A處時,PC實際的值是A+8.2.第一步得到的值是4的倍數,因為ARM的指令是4對齊的,即最低兩位為00.于是將這個值右移兩位.
3.得到最終偏移
執(zhí)行時:
1.取出偏移
2.左移兩位
3.加入PC,這時PC的值剛好為目標處的地址值,即目標地址指令進入取指,流水線前兩級被清空
但是為什么是減去8呢?這因為ARM7是三級流水線。
那什么三級流水線是什么?
PC代表程序計數器,流水線使用三個階段,因此指令分為三個階段執(zhí)行:1.取指(從存儲器裝載一條指令);2.譯碼(識別將要被執(zhí)行的指令);3.執(zhí)行(處理指令并將結果寫回寄存器)。即執(zhí)行時取指已經提前兩個字了,即8個字節(jié)。
6.什么是軟中斷?
軟中斷是利用硬件中斷的概念,用軟件方式進行模擬,實現宏觀上的異步執(zhí)行效果。很多情況下,軟中斷和"信號"有些類似,同時,軟中斷又是和硬中斷相對應的,"硬中斷是外部設備對CPU的中斷","軟中斷通常是硬中斷服務程序對內核的中斷","信號則是由內核(或其他進程)對某個進程的中斷"(《Linux內核源代碼情景分析》第三章)。 軟中斷的一種典型應用就是所謂的"下半部"(bottomhalf),它的得名來自于將硬件中斷處理分離成"上半部"和"下半部"兩個階段的機制:上半部在屏蔽中斷的上下文中運行,用于完成關鍵性的處理動作;而下半部則相對來說并不是非常緊急的,通常還是比較耗時的,因此由系統自行安排運行時機,不在中斷服務上下文中執(zhí)行。bottomhalf的應用也是激勵內核發(fā)展出目前的軟中斷機制的原因。 軟中斷是linux系統原“底半處理”的升級,在原有的基礎上發(fā)展的新的處理方式,以適應多cpu、多線程的軟中斷處理?! ∫话銇碚f,軟中斷是由內核機制的觸發(fā)事件引起的(例如進程運行超時),但是不可忽視有大量的軟中斷也是由于和硬件有關的中斷引起的,例如當打印機端口產生一個硬件中斷時,會通知和硬件相關的硬中斷,硬中斷就會產生一個軟中斷并送到操作系統內核里,這樣內核就會根據這個軟中斷喚醒睡眠在打印機任務隊列中的處理進程?! ≡诰W絡編程中,軟中斷用來引發(fā)協議層代碼的執(zhí)行
7.關于程序狀態(tài)的切換
程序不能通過修改直接修改CPSR中的T控制位直接將程序狀態(tài)切換到Thumb狀態(tài),必須通過BX等指令完成程序狀態(tài)的切換
8.對于LDMIA指令,Rn的最終值是加載的值,而不是增加后的地址
評論