ARM匯編基礎知識


1、前言

匯編語言是一種低級編程語言,通常是一對一的匯編語言指令(助記符)與由核心執行的實際二進制操作碼之間的關系,在高度優化的情況下,匯編代碼可能會很有用,在編寫編譯器或者無法直接使用底層功能的情況下,在C中添加匯編代碼是必需的,部分SoC的啟動代碼、設備驅動程序或者操作系統開發也可能需要匯編代碼,在進行嵌入式Linux開發的時候需要掌握一定的ARM匯編知識,對於ARM Cortex-A架構的芯片,系統上電后,C語言運行環境還沒有設置好,因此肯定是不能直接運行C代碼的,所以必須先用匯編語言設置好C運行環境,初始化好SP指針等,使用匯編語言設置好C運行環境后,才能開始運行C語言代碼。

 

2、GNU匯編語法

對於ARM架構下的匯編語言,編譯使用的是gcc交叉編譯工具鏈,匯編代碼要符合GNU匯編語法,GNU匯編語法適用於所有的架構,並不是ARM獨享的,GNU匯編由一系列的語句組成,每行一條語句,每條語句有3個可選部分,如下所示:

label:    instruction    @comment

label:label就是標號,表示地址的位置,有一些指令的前面可能會存在標號,然后通過這個標號就可以得到指令的地址,標號也可以用來表示數據的地址,任何以冒號":"結尾的標識符都會被認為是一個標號label;

instruction:匯編指令或者偽指令;

@:表示后面的是注釋,和C語言的注釋一樣,同樣也可以使用"/**/"進行注釋;

comment:要注釋的內容。

例如,下面的ARM匯編代碼:

Code:
    MOVS    R0,#0x14    @設置R0=0x14

上面的舉例代碼中,"Code:"就是標號,"MOVS  R0,#0x14"就是指令,"@"后面就是注釋的內容。

需要注意的是,ARM中的指令、偽指令、偽操作、寄存器名等可以全部使用大寫,也可以全部使用小寫,但是不能大小寫混合使用。

另外,用戶還可以使用".section"偽操作來定義一個段,匯編系統中預定義了一些段名,如下:

.text:表示代碼段;

.data:表示初始化的數據段;

.bss:表示未初始化的數據段;

.rodata:表示只讀數據段。

用戶可以使用".section"來定義一個段,每個段以段名開始,以下一個段名或者文件結束,例如:

.section    .mysection    @定義一個.mysection段

匯編程序的默認入口標號是"_start",不過也可以在鏈接腳本中使用ENTRY來指明其它的入口點,下面的代碼就是使用"_start"來作為入口標號:

.global _start
_start:
    MOVS    R0,#0X14    @R0=0x14

在上面的代碼中,".global"就是偽操作,表示"_start"是一個全局標號,類似C語言定義的全局變量一樣,ARM匯編中常用的偽操作有如下:

.byte:定義單字節數據,例如:.byte 0x14;

.short:定義雙字節數據,例如:.short 0x1000;

.long:定義四字節數據,例如:.long 0x10001000;

.equ:賦值語句,其格式為:.equ 變量名,表達式,例如:.equ cnt,0x14,表示cnt=0x14;

.align:表示數據字節對齊,例如:.align 4,表示4字節對齊;

.end:表示匯編源文件結束;

.global:定義一個全局標號。

GNU匯編同樣也支持函數,函數的格式如下所示:

函數名:
    函數體
    返回語句

GNU匯編函數中的返回語句並不是必須的。

 

3、ARM v7-A常用匯編指令

接下來,總結一些ARM v7-A架構中常用的匯編指令,如下:

(1)處理器內部數據傳輸

在處理器內部來回傳遞數據,常見的操作有:

  • 數據從一個寄存器傳輸到另一個寄存器
  • 數據傳輸到特殊寄存器,例如CPSR寄存器
  • 將立即數傳輸到寄存器

常用的數據傳輸指令有3個,分別是MOV、MRS和MSR,這3個指令的用法如下:

指令 目的 作用
MOV R0 R1 將R1里面的數據賦值到R0中
MRS R0 CPSR 將CPSR里面的數據賦值到R0中
MSR CPSR R1 將R1的數據賦值到CPSR中

(1.1)MOV指令

MOV指令用於將數據從一個寄存器賦值到另一個寄存器,或將一個立即數賦值到寄存器里面,使用示例如下:

MOV    R0,R1      @將R1中的數據傳遞到R0,也就是R0=R1
MOV    R0,#0x14    @將立即數0x14傳遞到R0,也就是R0=0x14

(1.2)MRS指令

MRS指令用於將特殊寄存器,例如CPSR或SPSR中的數據傳遞到通用寄存器,使用如下:

MRS    R0,CPSR    @將CPSR中的數據傳遞到R0,也就是R0=CPSR

(1.3)MSR指令

MSR指令用來將通用寄存器中的數據傳遞到特殊寄存器,例如CPSR和CPSR,使用如下:

MSR    CPSR,R0    @將R0的數據傳遞到CPSR中,也就是CPSR=R0

(2)存儲器訪問指令

ARM架構不能直接去訪問存儲器,例如RAM中的數據,I.MX6UL中的寄存器就是RAM類型的,當我們使用匯編指令來配置SoC寄存器的時候就需要借助存儲器訪問指令,一般的步驟為,先將要配置的寄存器值寫入到Rx寄存器中,然后使用存儲器訪問指令將Rx中的數據寫入到SoC的寄存器中,讀取SoC寄存器的值類似,常用的存儲器訪問指令有LDR和STR,用法如下:

指令 作用
LDR Rd,[Rn,#offset] 將存儲器Rn+offset位置的數據讀取到Rd中
STR Rd,[Rn,#offset] 將Rd中的數據寫入到存儲器Rn+offset位置中

(2.1)LDR指令

在嵌入式ARM中,LDR指令用於從存儲器中加載數據到通用寄存器Rx中,另外,LDR指令也能將一個立即數加載到寄存器Rx中,但是要使用"=",而不是"#",在ARM開發中,LDR最常用的就是讀取SoC的寄存器值,例如,I.MX6UL有一個寄存器GPIO1_GDIR,該寄存器地址為0x0209C004,如果想要讀取該寄存器中的數值,可以使用下面匯編代碼:

LDR    R0,=0x0209C004    @將寄存器地址0x0209C004加載到R0中
LDR    R1,[R0]    @讀取寄存器地址0x0209C004中的數據到R1中

上述示例代碼中,沒有使用到offset,也就是offset為0。

(2.2)STR指令

LDR指令可以用來讀取存儲器的數據都Rx寄存器中,STR指令可以將數據寫入到存儲器中,例如,I.MX6UL有一個寄存器GPIO1_GDIR,該寄存器地址為0x0209C004,如果想要往該寄存器中寫入數值,可以使用下面匯編代碼:

LDR    R0,=0x0209C004    @將寄存器地址0x0209C004加載到R0中
LDR    R1,=0x00000001    @將0x00000001加載到R1中
STR    R1,[R0]    @將R1中的值寫入到寄存器地址0x0209C004中

另外,需要注意的是,LDR指令和STR指令都是按照字進行讀取和寫入的,也就是直接操作32bit數據,如果要按照字節或者半字操作的話,可以在LDR指令和STR指令后面加上"B"或"H",例如按字節操作的指令為LDRB和STRB。

(3)壓棧和出棧指令

在編寫代碼的時候,通常會在A函數中調用B函數,當B函數執行完以后需要再回到A函數處執行,如果想要跳回到A函數繼續正常執行,那就必須在跳到B函數之前將當前處理器的狀態保存起來(保存R0~R15寄存器的值),當B函數執行完后,需要將前面保存的寄存器值恢復到R0~R15,保存寄存器的值操作就是現場保護,恢復寄存器的值操作就是恢復現場,在進行現場保護需要使用PUSH指令進行壓棧操作,恢復現場就需要使用POP指令進行出棧操作,這些指令一次可以操作多個寄存器數據,利用當前的棧指針SP來生成地址,用法如下:

指令 作用
PUSH <reg list> 將寄存器列表壓入棧中
POP <reg list> 將寄存器列表出棧

例如,現在需要將R0~R3寄存器和R12這5個寄存器壓棧,當前的SP指針指向0x80000000,處理器的堆棧向下增長,ARM匯編代碼如下:

PUSH    {R0~R3,R12}    @將R0~R3和R12進行壓棧操作

代碼執行后,堆棧如下所示:

此時的堆棧指針SP指向0x7FFFFFEC,假如現在需要將LR寄存器進行壓棧,ARM匯編代碼如下:

PUSH    {LR}    @將LR寄存器進行壓棧操作

代碼執行后,堆棧生長如下所示:

想要將寄存器出棧的話,使用下面的ARM匯編代碼:

POP    {LR}    @先將LR寄存器出棧
POP    {R0~R3,R12}    @將R0~R3,R12寄存器出棧

棧是一種先進后出的模型,出棧是從棧頂先開始,地址依次減小來提取堆棧中的數據恢復到寄存器列表中。

(4)跳轉指令

 在ARM匯編中,有多種跳轉操作,例如:

  • 使用B、BL或BX指令直接跳轉
  • 直接向PC寄存器里面寫入數據

在上面列出的操作中,都可以完成跳轉操作,但是一般常用的還是使用跳轉指令B、BL或者BX,指令用法如下:

指令 作用
B <label> 跳轉到label
BX <Rm> 間接跳轉,跳轉到存放在Rm的地址處,並切換指令集
BL <label> 跳轉到label,並將返回地址保存到LR中
BLX <label> 跳轉到Rm指定的地址,並將返回地址保存到LR中,切換指令集

(4.1)B指令

B指令會將PC寄存器的值設置為跳轉的目標地址,一旦執行B指令后,ARM處理器將會立即跳到指定的目標地址,如果想調用的函數不會再返回到原來的地方執行,就可以使用B指令,使用示例如下:

_start:
    ldr  sp,=0x80200000    @設置堆棧指針SP
    b    main        @跳轉到main函數處執行

示例代碼就是在匯編中初始化C運行環境,然后跳轉到C文件的main函數處執行,main函數調用后,將不會返回到原來的位置處執行。

(4.2)BL指令

BL指令在跳轉之前會將當前PC寄存器的值保存到LR寄存器中,通過將LR寄存器中的值重新加載到PC中,就可以繼續從跳轉之前的代碼處執行,這是子程序調用的一個基本常用手段,例如,ARM處理器的irq中斷服務函數使用匯編編寫,主要是使用匯編來實現現場保護和現場恢復、獲取中斷號等,具體的中斷處理過程使用C函數,所以存在在匯編中調用C函數的問題,C函數的處理過程完成以后,需要返回到匯編的中斷服務函數,一般是恢復現場,這時候就要使用BL指令進行跳轉了,典型代碼如下:

push    {r0, r1}    @將r0和r1進行保存
cps    #0x13    @處理器進入SVC模式

bl    system_irqhandler    @跳轉到C的中斷處理函數

cps    #0x12    @處理器進入IRQ模式
pop    {r0, r1}    @恢復r0和r1寄存器
str    r0, {r1, #0x10 }    @中斷執行完成,寫EOIR

上面代碼中,使用BL指令跳轉到C版本的中斷處理函數system_irqhandler,函數執行完后,需要返回到原來的位置繼續執行下面的匯編代碼。    

(5)算術運算指令

ARM匯編中也可以進行算術運算,例如加減乘除操作,使用對應的運算指令即可,常用的運算指令用法如下:

指令 計算公式 作用
ADD Rd, Rn, Rm Rd = Rn + Rm 加法運算
ADD Rd, Rn, #immed Rd = Rn + #immed
ADC Rd, Rn, Rm Rd = Rn + Rm + 進位 帶進位的加法運算
ADC Rd, Rn, #immed Rd = Rn + #immed + 進位
SUB Rd, Rn, Rm Rd = Rn - Rm 減法運算
SUB Rd, Rn, #immed Rd = Rn - #immed
SBC Rd, Rn, Rm Rd = Rn - Rm - 借位 帶借位的減法
SBC Rd, Rn, #immed Rd = Rn - #immed -借位
MUL Rd, Rn, Rm Rd = Rn * Rm 乘法運算
UDIV Rd, Rn, Rm Rd = Rn / Rm 無符號除法運算
SDIV Rd, Rn, Rm Rd = Rn / Rm 有符號除法運算

(6)邏輯運算指令

 ARM中還有一些常用的邏輯運算指令,用法如下:

指令 計算公式 作用
AND Rd, Rn Rd = Rd & Rn 按位與
AND Rd, Rn, #immed Rd = Rn & #immed
AND Rd, Rn, Rm Rd = Rn & Rm
ORR Rd, Rn Rd = Rd | Rn 按位或
ORR Rd, Rn, #immed Rd = Rn | #immed
ORR Rd, Rn, Rm Rd = Rn | Rm
BIC Rd, Rn Rd = Rd & (~Rn) 位清除
BIC Rd, Rn, #immed Rd = Rn & (~#immed)
BIC Rd, Rn, Rm Rd = Rn & (~Rm)

對於更多更詳細的ARM匯編指令,可以參考ARM官網的相關文檔。

 

4、小結

本文主要記錄了ARM中一些匯編基礎知識和一些常用的ARM匯編指令。


免責聲明!

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



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