第三章 ARM汇编语言程序设计——ARM
1.ARM匯編的語句格式
匯編語言都具有一些相同的基本特征。
-
一條指令一行。
-
使用標號(label)給內存單元提供名稱,從第1列開始書寫。
-
指令必須從第2列或能區分標號的地方開始書寫。
-
注釋跟在指定的注釋字符后面(ARM使用的是“;”),一直書寫到行尾。
{symbol} {instruction |directive | pseudo-instruction} {;comment} 符號 指令、偽指令或偽操作 [; 注釋]
ARM匯編語言基本的的語句格式如下:
(1)符號命名規則:
- 符號由大小寫字母、數字及下畫線組成,符號不能用數字開頭。
- 符號區分大小寫,同名的大、小寫符號會被編譯器認為是兩個不同的符號。
- 符號在其作用范圍內必須唯一。
- 自定義的符號名不能與系統的保留字相同。
- 符號名不應與指令或偽指令同名。
(2)偽操作
偽操作(Directive)是ARM匯編語言程序里的一些特殊的指令助記符,其作用主要是為完成匯編程序做各種準備工作,對源程序運行匯編程序處理,而不是在計算機運行期間由處理器執行。
常用偽操作列表:
介紹:
-
ENTRY 和 END
- 定義程序入口點偽指令 ENTRY用于指定匯編程序的入口點
- 匯編結束偽指令 END 用于通知編譯器匯編工作到此結束,不再往下匯編了
-
EXPORT(或GLOBAL)和 IMPORT(或EXTERN)
格式:EXPORT 符號 {[WEAK]}符號在程序中區分大小寫[WEAK] 選項聲明其他的同名符號優先于該符號被引用
外部可引用符號聲明偽指令EXPORT(或GLOBAL)可以聲明一個其他源文件可引用的符號,這種符號也叫做外部可引用符號。
當在一個源文件中需要使用另外一個源文件的外部可引用符號時,在被引用的符號前面必須使用偽指令 IMPORT 對其進行聲明
格式:IMPORT 符號 {[WEAK]} [WEAK] 選項表示當前所有的源文件都沒有定義這樣一個符號時,編譯器也不報錯,并在多數情況下將該符號置為0如果源文件聲明了一個引用符號,則無論當前源文件中程序是否真正地使用了該符號,該符號均會被加入到當前源文件的符號表中
(3)偽指令
偽指令是ARM處理器支持的匯編語言程序里的特殊助記符,它不在處理器運行期間由機器執行,只是在匯編時將被合適的機器指令代替成ARM或Thumb指令,從而實現真正的指令操作。
①段定義偽指令
②符號定義偽指令
符號的命名由編程者決定,但有以下約定:
符號區分大小寫,同名的大、小寫符號會被編譯器認為是兩個不同的符號 符號在其作用范圍內必須唯一 自定義的符號不能與系統保留字相同 符號不應與指令或偽指令同名-
全局變量偽指令(GBLA、GBLL、GBLS):
例如:GBLA Test1 ;定義一個全局數字變量,變量名為Test1,默認初值為0GBLL Test2 ;定義一個全局邏輯變量,變量名為Test2,默認初值為FGBLS Test3 ;定義一個全局字符串變量,變量名為Test3,默認初值為空
GBLA、GBLL 和 GBLS 偽指令用于定義一個ARM程序中的全局變量,并將其初始化。全局變量的變量名在整個程序范圍內必須具有唯一性。
-
局部變量偽指令(LCLA、LCLL、LCLS )
例如:LCLA Test4 ;定義一個局部數字變量,變量名為Test4,默認初值為0LCLL Test5 ;定義一個局部邏輯變量,變量名為Test5,默認初值為FLCLS Test6 ;定義一個局部字符串變量,變量名為Test6,默認初值為空
LCLA、LCLL 和 LCLS 偽指令用于定義一個ARM程序中的局部變量,并將其初始化
局部變量的變量名在變量作用范圍內必須具有唯一性。默認情況下,局部變量只在定義該變量的程序段內有效
-
變量賦值偽指令(SETA、SETL、SETS)
例如: Test1 SETA 0xAA ;將Test1變量賦值為0xAATest2 SETL {TRUE} ;將Test2變量賦值為真Test3 SETS "Testing" ;將Test3變量賦值為“Testing”
SETA、SETL 和 SETS 偽指令用于給一個已經定義的全局變量或局部變量進行賦值 -
定義寄存器列表偽指令 RLIST
格式: <name> RLIST <{list}>name :為表名稱{list} :為寄存器列表,列表中的寄存器訪問次序根據寄存器的編號由低到高,而與列表中的寄存器排列次序無關例如:RegList RLIST {R0-R5,R8,R10} ;將寄存器列表名稱定義為RegList
指令 LDM/STM 需要使用一個比較長的寄存器列表,使用偽指令 RLIST 可對一個列表定義一個統一的名稱
③程序中的標號
在匯編語言中用來表示地址的符號就叫做標號
根據用途不同標號主要有以下2種:
- 目標地址標號:寫在一條指令前面的標號
- 數據或數據區首地址標號:寫在數據或數據區定義偽指令前面的標號
④數據定義偽指令
該指令的功能就是為指定的數據分配存儲單元,以及用該數據對已分配存儲單元進行初始化。
-
DCB(可用“=”代替)
格式: {<label>} DCB <expr> label 為標號,為存儲區域的首地址(可選)expr 為表達式,為從標號開始存放的數據。該表達式可以為0~255的數字或字符串
用于分配一片連續的以字節為單位的存儲區域(操作數可以為-128~255的數值或字符串),并用指定的表達式對其進行初始化。 -
DCW(或DCWU)
格式:<label> DCW(或DCWU) <expr>表達式可以為程序標號或數字表達式
用于為數據分配一片連續的半字存儲單元(操作數是16位二進制數),并用表達式對其進行初始化。用 DCW 分配的半字存儲單元是嚴格按半字對齊的,而用DCWU 分配的半字存儲單元并不嚴格按半字對齊。
-
DCD(或DCDU)(可用“&”代替)
格式:<label> DCD(或DCDU) <expr>表達式可以為程序標號或數字表達式
用于分配一片連續的字存儲單元(操作數可以是32位的數字表達式),并用偽指令中指定的表達式初始化。用 DCD 分配的字存儲單元是字對齊的,而用 DCDU 分配的字存儲單元并不嚴格要求字對齊。
-
DCFD(或DCFDU)
格式:<label> DCFD(或DCFDU) <expr>
用于為雙精度的浮點數分配一片連續的字存儲單元,并用偽指令中指定的表達式初始化每個雙精度的浮點數占據兩個字單元用 DCFD 分配的字存儲單元是字對齊的而用 DCFDU 分配的字存儲單元并不嚴格字對齊
-
DCFS(或DCFSU)
格式:<label> DCFS(或DCFSU) <expr>
用于為單精度的浮點數分配一片連續的字存儲單元,并用偽指令中指定的表達式初始化每個單精度浮點數占據一個字單元用 DCFS 分配的字存儲單元是字對齊的而用 DCFSU 分配的字存儲單元并不嚴格字對齊
-
DCQ(或DCQU)
格式:label> DCQ(或DCQU) <expr>
用于分配一片以8字節為單位的連續存儲區域,并用偽指令中指定的表達式初始化用 DCQ 分配的存儲單元是字對齊的,而用 DCQU 分配的字存儲單元并不嚴格字對齊
-
SPACE(可用“%”代替)
格式:<label> SPACE <expr>
用于分配一片連續的存儲區域并初始化為0 -
LTORG
例如:AREA example, CODE, READONLYStart BL Func1…Func1 LDR R1,=0x800MOV PC,LRLTORG ;定義數據緩沖池的開始位置Date SPACE 40 ;數據緩沖池有40個被初始化為0的字節 END
來說用明某個存儲區域為一個用來暫存數據的數據緩沖區,也叫文字池或數據緩沖池。
大的代碼段也可以使用多個數據緩沖池。當程序中使用 LDR 之類的指令訪問數據緩沖池時,為防止越界發生,通常把數據緩沖池放在代碼段的最后面,或放在無條件轉移指令或子程序返回指令之后,這樣處理器就不會錯誤地將數據緩沖池中的數據當作指令來執行。
-
MAP 和 FIELD
-
MAP 用于定義一個結構化的內存表的首地址
格式:MAP <expr> {,<baseregister>}expr 為結構化表首地址,可以為標號或數字表達式baseregister 為基址寄存器(可選項)基址寄存器的值與 expr 的值之和就是表首地址
-
FIELD 偽指令用于定義一個結構化內存表中的數據域
格式:<label> FIELD <expr> label 為標號(為數據域指定一個標號供其他指令引用)expr 為表達式,它的值為數據域所占的字節數FIELD 偽指令與 MAP 偽指令配合使用來定義結構化的內存表。
只用于定義數據結構,不分配存儲單元。
-
⑤匯編控制偽指令
-
IF、ELSE 和 ENDIF
例如:GBLL Test ;聲明一個全局邏輯變量Test...IF Test = TRUE程序段1 ELSE程序段2ENDIF
根據條件的成立與否決定是否執行某個程序段
IF、ELSE、ENDIF 偽指令可以嵌套使用 -
WHILE 和 WEND
例如:GBLA Counter ;聲明一個全局數字變量CounterCounter SETA 3 ;賦值...WHILE Counter < 10程序段WEND
根據條件的成立與否決定是否重復匯編一個程序段
若 WHILE 后面的邏輯表達式為真,則重復匯編該程序段,直到邏輯表達式為假
WHILE 和 WEND 偽指令可以嵌套使用
⑥其他常用的偽指令
偽指令列表偽指令 語法格式 功能ADR ADR{cond} register,=expression 它將基于PC相對偏移的地址值或基于寄存器相對偏移的地址值讀取到寄存器中ADRL ADRL{cond} register,=expression 它將基于PC相對偏移的地址值或基于寄存器相對偏移的地址值讀取到寄存器中LDR LDR{cond} register,=expression 將一個32位的常數或者一個地址值讀取到寄存器中,可以看做是加載寄存器的內容NOP NOP 是空操作偽指令,在匯編時將會被替代成ARM中的空操作-
ADR偽指令————小范圍的地址讀取
當地址值是字節對齊時,其取指范圍為?255B~255B;當地址值是字對齊時,其取指范圍為?1020B~1020B。
在匯編編譯器編譯源程序時,ADR偽指令被編譯器替換成一條合適的指令。通常,編譯器用一條ADD指令或SUB指令來實現該ADR偽指令的功能,若不能用一條指令實現,則產生錯誤,編譯失敗。ADR偽指令中的地址是基于PC或寄存器的,當ADR偽指令中的地址是基于PC時,該地址與ADR偽指令必須在同一個代碼段中。
地址表達式expr的取值范圍如下: -
ADRL偽指令————中等范圍的地址讀取
當地址值是字節對齊時,其取指范圍為?64KB~64KB;當地址值是字對齊時,其取指范圍為?256KB~256KB。
ADRL比ADR偽指令可以讀取更大范圍的地址。在匯編編譯器編譯源程序時,ADRL偽指令被編譯器替換成兩條合適的指令。若不能用兩條指令實現,則產生錯誤,編譯失敗。
地址表達式expr的取值范圍如下: -
LDR偽指令————大范圍的地址讀取
格式:LDR{cond} reg,={expr | label - expr}reg:目標寄存器名稱expr:32位常數label – expr:為基于PC地址表達式程序經常用這條指令把一個地址傳遞到寄存器 reg 中
在匯編編譯源程序時,LDR偽指令被編譯器替換成一條合適的指令。若加載的常數未超出MOV或MVN的范圍,則使用MOV或MVN指令代替該LDR偽指令,否則匯編器將常量放入文字池,并使用一條程序相對偏移的LDR指令從文字池讀出常量。匯編器在對這種指令進行匯編時,會根據指令中 expr 的值的大小來把這條指令替換為合適的指令
- 當expr的值未超過MOV或MVN指令所限定的取值范圍時,匯編器用ARM的MOV或MVN指令來取代宏指令LDR
- 當expr的值超過MOV或MVN指令所限定的取值范圍時,匯編器將常數expr放在由LTORG定義的文字緩沖池,同時用一條ARM的裝載指令LDR來取代宏指令LDR,而這條裝載LDR指令則用PC加偏移量的方法到文字緩沖池中把該常數讀取到指令指定的寄存器。
2.ARM匯編的程序結構
在 ARM ( Thumb )匯編語言程序中,以程序段為單位組織代碼。段是相對獨立的指令或數據序列,具有特定的名稱。
段可以分為代碼段和數據段,代碼段的內容為執行代碼,數據段存放代碼運行時需要用到的數據。
一個匯編程序至少應該有一個代碼段,當程序較長時,可以分割為多個代碼段和數據段,多個段在程序編譯鏈接時最終形成一個可執行的映象文件。
可執行映象文件通常由以下幾部分構成:
- 一個或多個代碼段,代碼段的屬性為只讀。
- 零個或多個包含初始化數據的數據段,數據段的屬性為可讀寫。
- 零個或多個不包含初始化數據的數據段,數據段的屬性為可讀寫。
鏈接器根據系統默認或用戶設定的規則,將各個段安排在存儲器中的相應位置。因此源程序中段之間的相對位置與可執行的映象文件中段的相對位置一般不會相同。
(1)順序程序設計
沒有分支、循環等架構的程序,會順序執行匯編指令;
AREA EXAMPLE,CODE,READONLY ; 定義段的名稱和屬性,表示了一個段的開始ENTRY ; 標識程序的入口點 start ; 以下為具體指令MOV R0,#10 MOV R1,#3ADD R0,R0,R1END ; 標識源文件的結束(2)分支程序設計
ARM匯編中大部分的指令都支持條件執行,類似C語言中的if-else分支。
ADD指令可以根據已執行代碼對狀態寄存器的影響來決定是否執行,從而構成簡單的分支結構。
B、BL可以條件執行,從而構成復雜的分支架構。
例如: CMP R1,#3 ; 比較R1和#3BHI END ; if R1>3 then ENDADD R0,R0,#3 ; R0=R0+3END(3)循環程序設計
用預先設定的行標與B、BL結合可以設計各種循環結構。
例如: LOOP ADD R0,R0,R1 ; R0=R0+R1CMP R0,#3 ; 比較R0和#3 BLS LOOP ; if R0<3 then 跳轉到LOOP 循環END(4)子程序
在ARM匯編語言程序中,子程序的調用一般是通過BL指令來實現的。
在程序中,使用指令:
BL子程序名,即可完成子程序的調用。
該指令在執行時完成如下操作:
將子程序的返回地址存放在連接寄存器LR中,同時將程序計數器PC指向子程序的入口點,當子程序執行完畢需要返回調用處時,只需要將存放在LR中的返回地址重新復制給程序計數器PC即可。在調用子程序的同時,也可以完成參數的傳遞和從子程序返回運算的結果,通常可以使用寄存器R0~R3完成。
以下是使用BL指令調用子程序的匯編語言源程序的基本結構:
3.ATPCS[ARM-Thumb Procedure Call Standard(PCS,Procedure Call Standard(過程調用規范))]
(1)寄存器的使用規則
ATPCS中定義的寄存器寄存器 R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15sysnonym a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 special WR SB SL FP IP SP LR PC 其中:R0~R3:用于傳參,r0用于返回值。R4~R11:通用變量寄存器。 R12:用作過程調用中間臨時過渡寄存器IP。R13:堆棧指針。R14:連接寄存器。R15:PC。另外,R9、R10和R11還有一個特殊作用,分別記為:靜態基址寄存器SB,數據棧限制指針SL和楨指針FP。
- 子程序通過寄存器R0~R3來傳遞參數,這時寄存器可以記作A0~A3,被調用的子程序在返回前無須恢復寄存器R0~R3的內容。
- 在子程序中,使用R4~R11來保存局部變量,這時寄存器R4~R11可以記作V1~V8。
如果在子程序中使用到V1~V8的某些寄存器,子程序進入時必須保存這些寄存器的值,在返回前必須恢復這些寄存器的值,對于子程序中沒有用到的寄存器則不必執行這些操作。在Thumb程序中,通常只能使用寄存器R4~R7來保存局部變量。 - 寄存器R12用作子程序間臨時過渡寄存器,記作IP,在子程序的連接代碼段中經常會有這種使用規則。
- 寄存器R13用作數據棧指針,記做SP,在子程序中寄存器R13不能用作其他用途。寄存器SP在進入子程序時的值和退出子程序時的值必須相等。
- 寄存器R14用作連接寄存器,記作LR。它用于保存子程序的返回地址,如果在子程序中保存了返回地址,則R14可用作其他的用途。
- 寄存器R15是程序計數器,記作PC,它不能用作其他用途。
- ATPCS中的各寄存器在ARM編譯器和匯編器中都是預定義的。
(2)數據棧的使用規則
- 數據棧指針(stack pointer):指向最后一個寫入棧的數據的內存地址。
- 數據棧的基地址(stack base):指數據棧的最高地址。由于ATPCS中的數據棧是FD類型的,實際上數據棧中最早入棧數據占據的內存單元是基地址的下一個內存單元。
- 數據棧界限(stack limit):數據棧中可以使用的最低的內存單元地址。
- 已占用的數據棧(used stack):數據棧的基地址和數據棧棧指針之間的區域,其中包括數據棧棧指針對應的內存單元。
- 數據棧中的數據幀(stack frames):在數據棧中,為子程序分配的用來保存寄存器和局部變量的區域。
(3)參數的傳遞規則
- 參數個數可變的子程序參數傳遞規則
對于參數個數可變的子程序,當參數不超過4個時,可以使用寄存器R0~R3來進行參數傳遞;當參數超過4個時,還可以使用數據棧來傳遞參數。在參數傳遞時,將所有參數看做是存放在連續的內存單元中的字數據。然后,依次將各名字數據傳送到寄存器R0,R1,R2,R3;如果參數多于4個,將剩余的字數據傳送到數據棧中,入棧的順序與參數順序相反,即最后一個字數據先入棧。按照上面的規則,一個浮點數參數可以通過寄存器傳遞,也可以通過數據棧傳遞,也可能一半通過寄存器傳遞,另一半通過數據棧傳遞。 - 參數個數固定的子程序參數傳遞規則
對于參數個數固定的子程序,參數傳遞與參數個數可變的子程序參數傳遞規則不同,如果系統包含浮點運算的硬件部件,浮點參數將按照下面的規則傳遞:
各個浮點參數按順序處理;為每個浮點參數分配FP寄存器;分配的方法是,滿足該浮點參數需要的且編號最小的一組連續的FP寄存器。
第1個整數參數通過寄存器R0~R3來傳遞,其他參數通過數據棧傳遞。
(4)子程序結果返回規則
- 結果為一個32位的整數時,可以通過寄存器R0返回。
- 結果為一個64位整數時,可以通過R0和R1返回,依此類推。
- 結果為一個浮點數時,可以通過浮點運算部件的寄存器f0、d0或者s0來返回。
- 結果為一個復合的浮點數時,可以通過寄存器f0~fN或者d0~dN來返回。
- 對于位數更多的結果,需要通過調用內存來傳遞。
4.C語言及匯編語言混合編程
(1)在C / C++程序中使用內嵌的匯編指令
在ARM C語言程序中,使用關鍵字__asm來標識一段匯編指令程序。
程序格式如下所示:
其中一條指令占據多行的時候,要使用續行符號( \ )。必須小心使用物理寄存器,如R0 ~ R3、SP、LR和CPSR中的N、Z、C、V標志位,因為計算匯編代碼中的C表達式時,可能會使用這些物理寄存器,并修改N、Z、C、V標志位。
(2)從匯編程序中訪問C程序變量
在C程序中的聲明的全局變量可以被匯編程序通過地址間接訪問。
具體訪問方法如下:
-
使用IMPORT偽指令聲明這個全局變量。
-
使用LDR指令讀取該全局變量的內存地址,通常該全局變量的內存地址存放在程序的數據緩沖池中。
-
根據該數據類型,使用相應的LDR指令讀取該全局變量的值,使用相應的STR指令修改該全局變量的值。
AREA globals, CODE, READONPYEXPORT asmsubIMPORT glovbver ;聲明外部變量glovbverasmsubLDR R1, =glovbver ;裝載變量地址LDR R0, [R1] ;讀出數據ADDR R0, R0, #1 ;加1操作STR R0, [R1] ;保存變量值MOV PC, LREND
(3)C程序與匯編程序相互調用規則
-
寄存器的使用規則:
- 子程序間通過寄存器R0 ~ R3來傳遞參數。
- 在子程序中,使用寄存器R4 ~ R11來保存局部變量。
- 寄存器R12用于保存SP,在函數返回時使用該寄存器出棧,記作IP。
- 寄存器R13用于數據棧指針,記作SP,寄存器SP在進入子程序時的值和退出寄存器時的值必須相等。
- 寄存器R14稱為鏈接寄存器,記作LR,它用于保存子程序的返回地址。
- 寄存器R15是程序計數器,記作PC。
-
堆棧的使用規則:
堆棧采用滿遞減類型(FD,Full Descending),即堆棧通過減小存儲器地址而向下增長,堆棧指針指向內含有效數據項的最低地址。 -
參數的傳遞規則:
整數參數的前4個使用r0~r3傳遞,其他參數使用堆棧傳遞;
子程序的返回結果為一個32位整數時,通過r0返回;
返回結果為一個64位整數時,通過r0和r1返回;
依此類推。
總結
以上是生活随笔為你收集整理的第三章 ARM汇编语言程序设计——ARM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 24个笔画顺序表_小学一年级语文26个汉
- 下一篇: c51单片机矩阵键盘1602计算器_基于