1. 程式人生 > 其它 >前期準備——2.基本彙編語法

前期準備——2.基本彙編語法

在做裸機開發前,我們要掌握一些基礎的ARM彙編語法,因為即使後面我們用C去寫驅動,也要用匯編去執行配置指標、中斷、清除session等操作。我們使用的晶片是I.MX6UL,這是款Cortex-A7的核心晶片,所以使用的就是Cortex-A的彙編指令,這裡有兩份資料可以參考點選下載(提取碼:l1rg)。還好我們主要目的就是進行系統的初始化,用到的都是比較簡單的指令,也不會涉及到複雜的程式碼結構。

GNU彙編語法

語法結構

GNU彙編語法是適用於所有的架構,並不是ARM獨享的,GNU彙編由一系列的語句組成,每條語句結構如下:

label: instruction @ comment

label:標號,用來表示地址位置,有些指令前面可能會有標號,這樣可以通過標號來找到指令的地址注意標號後面有個冒號,在彙報中,任何以冒號結尾的表示符都會被識別為標號

instruction:指令,包括彙編指令或偽指令

@:註釋符,也可以用/**/來包裹註釋內容

comment:註釋內容

比如我們在第一個用匯編點亮LED的試驗中的一段彙編程式碼

第二行的_start就是就是標號,第6行的

ldr r0,=0x020c4068

就是指令。注意指令中的指令、偽指令、暫存器、暫存器名可以全部使用大寫,也可以全部使用小寫,但是切記不能大小寫混用

後面的@CCGR0就是表明註釋內容。

常用的偽操作

在定義標號時,要知道有些標號是常用的偽操作

  • .byte  定義單位元組資料
  • .short  定義雙位元組資料
  • .long  定義4位元組資料
  • .equ   賦值語句 比如.equ num,0x12,表示num=0x12
  • .align  位元組對齊,比如.align 4 表示4位元組對齊,這個在後面定義session會經常用到
  • .end  表示原始檔結束
  • .global 定義全域性符號,比如上面的.global _start。

並且在在使用.session偽操作定義段的時候,彙編已經預定義了一些段名:

  • .text——程式碼段
  • .data——初始化的資料段
  • .bss——未初始化的資料段
  • .rodata——只讀資料段

函式

GNU也是支援函式的,函式的格式如下:

函式名:
    函式體
    返回語句

可以發現,函式宣告的方法和前面的基礎語法一樣,就是把label換成了函式名。返回值在函式裡不是必要的。比如下面這段程式碼就是我們在講中斷時定義的函式

/*復位中斷服務函式 */
Reset_Handler:
    ldr r0,=Reset_Handler
    bx r0

/*未定義指令中斷服務函式 */
Undefined_Handler:
    ldr r0,=Undefined_Handler
    bx r0

/*SVC中斷服務函式 */
SVC_Handler:
    ldr r0,=SVC_Handler
    bx r0

常用匯編語句

下面看看我們常用的彙編語句

處理器內部資料傳輸指令

處理器做的最多的事情就是在處理器內部來回傳遞資料,這些資料操作基本為

  • 通用暫存器之間資料相互傳遞
  • 通用暫存器與特殊暫存器(如CPSR、SPSR等)之間資料相互傳遞
  • 暫存器存入立即數

資料傳輸常用的指令有三個:MOV、MRS和MSR,這三個指令的用法如下:

指令 目的 資料來源 說明
MOV R0 R1 將暫存器R1內資料複製到暫存器R0中
MRS R0 CPSR 將特殊暫存器CPSR內的資料複製到R0中
MSR CPSR R0 將R0內資料複製到特殊暫存器CPSR內

下面分別介紹下3個指令的具體用法

MOV指令

MOV指令用來將一個暫存器裡的資料拷貝到另一個暫存器內,也可以將一個立即數拷貝到暫存器內,用法如下

MOV R0,R1       @將R1內的資料傳遞給R0
MOV R0,#0xFF    @將立即數0xFFC傳遞給R0

MRS指令

MRS是將資料從特殊暫存器傳遞個通用暫存器,也就是用於讀取特殊暫存器的值,用法如下:

MRS R0,CPSR     @將CPSR的資料傳遞給R0

MSR指令

MSR指令是將資料從通用暫存器傳遞給特殊暫存器,也就是寫通用暫存器

MSR CPSR,R0    @將R0的資料傳遞給CPSR

MSR和MRS兩個指令非常容易混淆,有個簡單的方法記憶:因為指令後面兩個暫存器方向是固定的,前面的是目標,後面的是源,R可以記成通用暫存器,S剛好和特殊的頭文字相符,就記成特殊暫存器,MRS就是從特殊到通用,MSR就是從通用到特殊。

儲存器訪問指令

ARM處理器是不能直接訪問儲存器的,這裡的儲存器不是SD卡或者NANDFlash,而是類似RAM中的資料,它必須藉助核心暫存器組,也就是R0~R15來實現功能,我們需要用匯編來配置I.MX6UL暫存器的時候需要藉助儲存器訪問指令,一般就是將要配置的引數寫入到通用暫存器(R0~R12)中,然後藉助儲存器訪問指令將R儲存器中的資料寫入到I.MX6UL暫存器中,讀取暫存器也是一樣的,只是過程相反。常用的訪問儲存器的指令有兩種:LDR和STR:用法如下表

指令 說明
LDR Rd,[Rn,#offset]

從儲存器Rn+offset的位置讀取資料放到Rd中

STR Rd,[Rn,#offset] 將Rd裡的資料寫入到Rn+offset的位置

下面來看看這兩條指令是怎麼用的:

LDR指令

LDR用於從RAM裡讀取暫存器的值,比如我們要讀取地址為0x0209C004這個暫存器(GPIO1_GDIR,控制GPIO1輸入輸出功能的暫存器,點燈時候會用到)的值,就要這麼做

LDR R1,=0x0209C004
LDR R0,[R1]

就是現在R1中儲存要讀取暫存器的地址,再把R1地址中的資料傳給R0。注意這裡傳遞立即數的時候和MOV指令有區別,一個用的是#,一個用的是=

STR指令

STR指令和LDR相反,用來設定暫存器的值,比如我們要講GPIO1_GDIR的值設定為0x00000001,程式碼如下:

LDR R1,=0x0209C004
LDR R0,=0x00000001
STR R0,[R1]

LDR和STR是按照位元組進行讀取和寫入到,也就是操作得都是32位的資料,如果要按照位元組或板子姐進行操作得話可以來指令後加上B(Byte)或者H(Half)。同樣我們找個方便記憶的方法:LDR可以記憶成LosdR,也就是讀取暫存器,STR是setR,設定,也就是寫入暫存器。

壓棧和出棧

我們通常在中斷等操作中需要在A函式中呼叫B函式,當B函式執行完成後繼續執行A函式,要想在跳回A函式時程式碼能夠正常執行,就需要在跳轉B函式前講當前處理器狀態儲存(也就是儲存R0~R15的值),在B函式執行完畢後將儲存的值恢復值R0~R15即可。所以儲存R0~R15的過程就叫現場保護,恢復的過程就叫恢復現場。在現場保護的時候要進行壓棧操作,恢復現場就是進行出棧操作。我們有兩個指令來進行該擦操作:PUSH和POP

指令 說明
PUSH <reg list>

將暫存器列表存入棧中

POP <reg list>

從棧中恢復到暫存器列表

假如我們現在要把R0~R3和R12這5個暫存器壓棧,當前的SP指標指向0x80000000,處理器的堆疊為向下增長,使用的程式碼就是這樣的

PUSH {R0~R3, R12} @將 R0~R3 和 R12 壓棧

一定要注意堆疊指標的增長方向,入棧以後的堆疊如下圖所示

現在的指標就指向0x7FFFFFEC,假如此刻我們需要對LR暫存器入棧,那麼執行完下面程式碼

PUSH {LR}    @將LR壓棧

堆疊模型如下:

在這種情況下我們如果要出棧,就要執行下面的程式碼

POP {LR}      @先恢復 LR
POP {R0~R3,R12}  @在恢復 R0~R3,R12

出入棧的另一種方法

出棧就是從棧盯,也就是SP當前執行的開始位置,地址依次減小來提取堆疊中的資料到要恢復的暫存器列表中,PUSH和POP的另一種方法就是 STMFD SP!和LDMFD SP!所以上面的程式碼也可以寫成這樣:

STMFD SP!,{R0~R3, R12}     @R0~R3,R12 入棧
STMFD SP!,{LR}                    @LR 入棧
LDMFD SP!, {LR}                   @先恢復 LR
LDMFD SP!, {R0~R3, R12}    @再恢復 R0~R3, R12

STMFD可以被分解為兩部分:STM和FD,同樣,LDMFD也可以被分解成LDM和FD這個STM和LDM就跟前面我們講過LDR和STR,但是這兩個指令只能讀取儲存器中一個數據,而這兩個STM和LDM可以多儲存和多載入,可以操作儲存器內多個連續資料。FD是Full Descending的縮寫,即滿遞減的意思,根據ATPCS規則,ARM使用的FD型別的堆疊,SP指向最後一個入棧的數值,堆疊是由高地址向下增長的,所以用的是STMFD的指令。STM和LDM的指令暫存器列表中標號小的對應低地址,編號高度對應小地址。

跳轉指令

在程式碼中我們經常需要程式跳轉至別的程式碼段,這就是跳轉操作。有兩種方法我們比較常用:

  1. 使用跳轉指令:B、BL、BX
  2. 直接向PC暫存器寫入資料

第一種方法我們是最常用的,指令用法如下:

指令 說明
B <Label>

跳轉到label處,如果跳轉範圍超過了±2KB,可以指定B.W來使用32位版本的跳轉指令。

BX <Rm>

間接跳轉,Rm記憶體放資料為跳轉到目的地址,並且切換指令集

BL <Lable>

跳轉到Label處,並將返回地址儲存在LR(Link Register)裡,可以回到跳轉前的位置

BLX <Rm>

結合了BX和BL的特點,間接跳轉至Rm儲存的地址,並儲存返回地址再LR內,切換指令集。

B指令

最簡單的跳轉指令,跳轉後不考慮返回,一旦執行B指令,ARM處理器直接跳轉到指定目標地址

_start:

    ldr sp,=0x80200000  @設定堆疊指標
    b main              @跳轉到main函式

上面的程式碼就是用來初始化C語言的執行環境,然後跳轉到C底main函式處。這裡跳轉到main函式後不會再回到彙編,所以使用了B指令。

BL指令

BL指令和B指令相比,就是在LR(R14)中儲存PC(R15)的值,看下圖,處理器在各種模式下R0~R15各個核心暫存器作用,後面會大致提到。主要是這個R15,也就是PC,儲存了當前指令地址加8個位元組。也就是說R15記錄了當前程式的位置,我們需要回到跳轉前位置只需要讀取LR到值就可以了。這個後面有機會可以詳細講講。

上面就是我們常用的指令,除此之外還有算數運算指令、邏輯運算指令等, 需要用到話我們再來補充。