第四章 ARM偽指令及編程基礎
4.1 偽指令概述
4.1.1 偽指令定義
- 人們設計了一些專門用于指導匯編器進行匯編工作的指令,由于這些指令不形成機器碼指令,它們只是在匯編器進行匯編工作的過程中起作用,所以被叫做偽指令。
4.1.2 偽指令特征
- 偽指令是一條指令
- 偽指令沒有指令代碼。
4.1.3 偽指令作用
- 程序定位的作用
- 為非指令代碼進行定義
- 為程序完整性做標注
- 有條件的引導程序段
4.2 通用偽指令
- 在 ARM 匯編程序語言中,有如下幾種偽指令:
- 符號定義(Symbol Definition)偽指令
- 數據定義(Data Definition)偽指令
- 匯編控制(Assembly Control)偽指令
- 其它(Miscellaneous)偽指令
4.2.1 為變量定義或賦值的偽指令
- 符號的命名由編程者決定,但必須遵循以下約定:
- 符號區分大小寫,同名的大、小寫符號會被編譯器認為是兩個不同的符號
- 符號在其作用范圍內必須唯一
- 自定義的符號不能與系統保留字相同
- 符號不應與指令或偽指令同名
一、聲明全局變量偽指令GBLA、GBLL和GBLS
- GBLA、GBLL 和 GBLS 偽指令用于定義一個ARM 程序中的全局變量,并將其初始化。
- 指令格式:
GBLA(GBLL和GBLS) <variable>
;variable 為變量名稱 - 全局變量的變量名在整個程序范圍內必須具有唯一性
- 指令格式:
- GBLA 定義一個 全局數字變量,其默認初值為 0
- GBLL 定義一個 全局邏輯變量,其默認初值為 FALSE(假)
- GBLS 定義一個 全局字符串變量,其默認初值為 空 ;
二、聲明局部變量偽指令LCLA、LCLL和LCLS
- LCLA、LCLL和LCLS偽指令用于定義一個ARM程序中的局部變量,并將其初始化。
- 格式:
LCLA(LCLL和LCLS) <variable>
;variable 為變量名稱 - 局部變量 的變量名在變量作用范圍內必須具有唯一性
- 在默認情況下,局部變量只在定義該變量的程序段內有效
- 格式:
- LCLA 定義一個 局部數字變量,其默認初值為 0
- LCLL 定義一個 局部邏輯變量,其默認初值為 FALSE(假)
- LCLS 定義一個 局部字符串變量,其默認初值為 空
三、變量賦值偽指令 SETA、SETL和SETS
-
偽指令SETA、SETL和SETS 用于給一個已經定義的全局變量或局部變量進行賦值。 注:要頂格寫
- 指令格式:
變量名 SETA(SETL或SETS) 表達式
- 指令格式:
-
SETA偽指令用于給一個數字變量賦值
-
SETL偽指令用于給一個邏輯變量賦值
-
SETS偽指令用于給一個字符串變量賦值
-
示例
- Test1 SETA 0xAA ;將Test1變量賦值為0xAA
- Test2 SETL {TRUE} ;將Test2 變量賦值為真
- Test3 SETS “Testing” ;將Test3變量賦值為“Testing”
四、定義寄存器列表偽指令
- 指令 LDM/STM 需要使用一個比較長的寄存器列表,使用偽指令 RLIST 可對一個列表定義一個統一的名稱。
- 格式:
<name> RLIST <{list}>
- 格式:
- 例如:
- LoReg RLIST {R0-R7} ;定義寄存器列表{R0-R7}的名稱為LoReg
- STMFD SP!, LoReg ;堆棧操作使用寄存器列表
- RegList RLIST {R0-R5,R8,R10} ;將寄存器列表名稱定義為RegList,可在ARM指令LDM/STM中,通過該名稱訪問寄存器列表
4.2.2 數據定義偽指令
-
LTORG
-
用于聲明一個數據緩沖池(文字池)的開始。
- 語法格式:
LTORG
- 語法格式:
-
偽指令 LTORG 用來說明某個存儲區域為一個用來暫存數據的數據緩沖區,也叫文字池或數據緩沖池。
- 指令位置:
- 通常把數據緩沖池放在代碼段的最后面,或放在無條件轉移指令或子程序返回指令之后,這樣處理器就不會錯誤地將數據緩沖池中的數據當作指令來執行
- 指令位置:
-
大的代碼段也可以使用多個數據緩沖池。
-
示例:
AREA example, CODE, READONLY Start BL Func1… Func1 LDR R1,=0x800MOV PC,LRLTORG ;定義數據緩沖池的開始位置,;系統會自動設置數據緩沖池的大小… END
-
指令作用
- 防止在程序中使用LDR之類的指令訪問時,可能產生的越界
-
-
MAP和FIELD
-
在應用程序中經常使用一種如下圖所示的表:
-
MAP 用于定義一個結構化的內存表的首地址。MAP 可以用 “^” 代替。
- 語法格式:
MAP <expr> {,<base_register>}
- expr是數字表達式或程序中的標號。當指令中沒有base_register時,expr即為結構化內存表的首地址,可以為標號或數字表達式
- base_register為基址寄存器(可選項)。當指令中包含這一項時,結構化內存表的首地址為expr與base_register寄存器值的和
- 示例:
- MAP fun ; fun就是內存表的首地址
- MAP 0x100,R9 ;內存表的首地址為R9+0X100
- 語法格式:
-
MAP 通常和 FIELD 偽指令相配合來定義一個結構化的內存表。
- FIELD 偽指令用于定義一個結構化內存表中的數據域
- 指令格式:
{label} FIELD expr
- Label為域標號,要頂格寫
- Expr表示本數據域在內存表中所占用的字節數
- 指令格式:
- 功能:FIELD用于定義一個結構化內存表中的數據域,“#”與FIELD同義
- FIELD 偽指令用于定義一個結構化內存表中的數據域
-
示例
- MAP 0X100 ;定義結構化內存表首地址為 0X100
- A FIELD 16 ;定義A的長度為16字節,位置為 0X100
- B FIELD 32 ;定義B的長度為32字節,位置為 0X110
- S FIELD 256 ;定義S的長度為256字節,位置為 0X130
-
注意:MAP 和 FIELD 偽指令僅用于定義數據結構,并不實際分配存儲單元
-
-
SPACE
-
SPACE偽指令用于分配一片連續的存儲區域并初始化為 0。
-
指令格式:
{label} SPACE expr
-
label為內存塊起始地址標號
-
Expr為所要分配的內存字節數;
-
SPACE 也可用 “%” 代替。
-
示例
AREA DataRAM,DATA,READWRITE ;聲明一數據段,名為DataRAM DataSpace SPACE 100 ;分配連續的100字節的存儲單元,并初始化為 0。
-
-
-
DCB
-
分配內存單元并初始化
- 指令格式:
{label} DCB expr{,expr }{,expr }…
- label是存塊起始地址標號;
- expr可以為0至255的數值或字符串,內存分配的字節數由expr個數決定;
- 指令格式:
-
功能:DCB用于分配一段字節內存單元,并用偽指令中的expr初始化,一般可用來定義數據表格,或文字符串,“=”與DCB同義。
-
示例
DISPTAB DCB 0x43,0x33,0x76,0x12DCB 120,20,32,44 String DCB “send,data is error!”,0 ;構造字符串# 使用示例 LDR R1, =DISPTAB ;把DISPTAB的地址值送入R1 LDRB R2, [R1,#2] ;獲取地址為[R1+#2]字節單元的值 R2=0x76
-
-
DCD和DCDU分配存儲單元并初始化
-
指令格式:
{label} DCD expr{,expr }{,expr }…
{label} DCDU expr{,expr }{,expr }…
- label是內存塊起始地址標號
- expr為常數表達式或程序中標號,內存分配字節數由expr個數決定
-
功能:
- DCD用于分配一段字內存單元,并用偽指令中的expr初始化,字對齊,可定義數據表格或其它常數。“&”與DCD同義。
- DCDU用于分配一段字內存單元,并用偽指令中的expr初始化。DCDU偽指令分配的內存不需要字對齊,可定義數據表格或其它常數
-
示例
AREA blockcopy,CODE,READONLY ……LDR R1,=ftt LDR R2,=ftt2 LDR R3,[R1] ;R3=1LDR R4,[R2] ;R4=3LDR R5,[R1, #4] ;R5=2LDR R6,[R2, #4] ;R6=4…… Src DCD 1,2,3,4,5,6,7,8,MAP Src ftt FIELD 8 ftt2 FIELD 8END
- 因為FIELD 8所以,ftt在內存中占8個字節,所以ftt2指向3而不是1
- 該例說明了:MAP和FIELD偽指令不分配存儲空間,只是給相關存儲單元取個名稱(標號)。便于程序以結構的方式訪問對應的內存單元。
-
4.2.4 其他偽指令
-
定義對齊方式偽指令 ALIGN
-
指令格式:
ALIGN {表達式,{偏移量}}
-
ALIGN是邊界對齊偽指令,它可以通過添加填充字節的方式,使當前位置滿足一定的對齊方式。其中表達式用于指定對齊方式在不同場合有不同的定義
-
對于在代碼中單獨使用的ALIGN偽指令,表達式的值,就是對齊方式的值,且該值必須是2的冪次方
-
示例
ALIGN 4 ;4字節字對齊,ALIGN后面不能有等號。thumbcode ALIGN 4 CODE32ARMCODE
AREA OffsetExample, CODE... ss1 DCB 1 ;假設ss1在0x01000字節ALIGN 4,3 ;4字節對齊+3偏移量. ss2 DCB 1 ;使用“ALIGN 4,3”以后,;當前位置會轉到0x01003(0x01000+3)。;ss1和ss2之間會空2個字節。...
-
-
段定義偽指令AREA
-
AREA用于定義一個代碼段或數據段。
-
指令格式:
AREA sectionname {,attr} {,attr}…
-
sectionname是定義的代碼段或數據段的名稱。
- 若該名稱是以數據開頭的,則該名稱必須用“|”括起來,如|2_datasec|。
- 還有一些代碼段的名稱是專有名稱。
-
Attr表示代碼或數據段的屬性,多個屬性用短號分隔,常用的屬性如下:
屬性 含義 備注 CODE 代碼段 默認讀/寫屬性為READONLY DATA 數據段 默認讀/寫屬性為READWRITE NOINIT 數據段 指定此數據段僅僅保留了內存單元,而沒有將各初始值寫入內存單元。 READONLY 本段為只讀 READWRITE 本段為可讀可寫 ALIGN表達式 ELF 的代碼段和數據段為字對齊 COMMON 多源文件共享段
-
-
示例:
AREA Init, CODE,READONLY………… ;程序段
- 該偽指令定義了一個代碼段,段名為 Init ,屬性為只讀。
- 一個匯編語言程序 至少 要有一個段。
-
-
CODE16和CODE32
-
CODE16告訴匯編編譯器后面的指令序列為16位的Thumb指令。
-
CODE32告訴匯編編譯器后面的指令序列為32位的ARM指令。
- 語法格式:
CODE16
CODE32
- 語法格式:
-
注意:CODE16和CODE32只是告訴編譯器后面指令的類型,該偽操作本身不進行程序狀態的切換。
-
示例
AREA ChangeState, CODE, READONLYENTRYCODE32 ;下面為32位ARM指令LDR R0,=start+1 ;將跳轉地址放入寄存器R0BX R0 ;程序跳轉到新的位置執行…… ;并將處理器切換到Thumb工作狀態CODE16 ;下面為16位Thumb指令 start MOV R1,#10…….END
-
-
定義程序入口點偽指令 ENTRY
- 指定程序的入口點。
- 語法格式:
ENTRY
- 語法格式:
- 注意:一個程序(可包含多個源文件)中至少要有一個ENTRY(可以有多個ENTRY,當有多個ENTRY入口時,程序的真正入口點由鏈接器指定),但一個源文件中最多只能有一個ENTRY(可以沒有ENTRY)
- 指定程序的入口點。
-
匯編結束偽指令 END
- END 偽指令用于通知編譯器匯編工作到此結束,不再往下匯編了。
- 語法格式:
END
- 語法格式:
- 注意:每一個匯編源程序都必須包含END偽操作,以表明本源程序的結束
- END 偽指令用于通知編譯器匯編工作到此結束,不再往下匯編了。
-
外部可引用符號聲明偽指令 EXPORT(或GLOBAL)
-
聲明一個源文件中的符號,使此符號可以被其他源文件引用。
- 語法格式:
EXPORT/GLOBAL symbol {[weak]}
- symbol:聲明的符號的名稱。(區分大小寫)
- [weak]:聲明其他同名符號優先于本符號被引用
- 語法格式:
-
示例
AREA example,CODE,READONLYEXPORT DoAdd ;申明一個全局引用的標號DoAdd DoAdd ADD R0,R0,R1
-
-
IMPORT
- 當在一個源文件中需要使用另外一個源文件的外部可引用符號時,在被引用的符號前面,必須使用偽指令 IMPORT 對其進行聲明:聲明一個符號是在其他源文件中定義的。
- 如果源文件聲明了一個引用符號,則無論當前源文件中程序是否真正地使用了該符號,該符號均會被加入到當前源文件的符號表中。
- 語法格式:
IMPORT symbol{[weak]}
- symbol:聲明的符號的名稱
- [weak]:
- 當沒有指定此項時,如果symbol在所有的源文件中都沒有被定義,則連接器會報告錯誤。
- 當指定此項時,如果symbol在所有的源文件中都沒有被定義,則連接器不會報告錯誤,而是進行下面的操作:
- 如果該符號被B或者BL指令引用,則該符號被設置成下一條指令的地址,該B或BL指令相當于一條NOP指令。
- 其他情況下此符號被設置成0
- 語法格式:
-
EXTERN
- EXTERN 偽指令與 IMPORT 偽指令的功能基本相同
- 不同點:如果當前源文件中的程序實際并未使用該符號,則該符號不會加入到當前源文件的符號表中。
- 其它與 IMPORT 相同
-
等效偽指令 EQU
-
EQU 偽指令用于為程序中的常量、標號等定義一個等效的字符名字,其作用類似于C語言中的 #define。
- 語法格式:
name EQU expr{,type}
- name:為expr定義的字符名稱
- expr:基于寄存器的地址值、程序中的標號、32位的地址常量或者32位的常量。表達式,為常量。
- type:當expr為32位常量時,可以使用type指示expr的數據的類型。取值為:
- CODE32
- CODE16
- DATA
- 語法格式:
-
示例:
abcd EQU 2 ;定義abcd符號的值為2 abcd EQU label+16 ;定義abcd符號的值為(label+16) abcd EQU 0x1c, CODE32 ;定義abcd符號的值為絕對地址0x1c,而且此處為ARM指令
-
-
GET(或INCLUDE)
- GET 偽指令用于將一個源文件包含到當前的源文件中,并將被包含的源文件在當前位置進行匯編。
- 語法格式:
GET 文件名
- 可以使用 INCLUDE 代替 GET。
- 語法格式:
- GET 偽指令只能用于包含源文件,包含目標文件則需要使用 INCBIN 偽指令
- 源文件是指 可被匯編器直接處理的文本文件,通常是:
- 匯編代碼文件(
.s
、.asm
) - 宏定義文件(
.inc
、.h
) - 其他可被匯編器解析的文本文件。
- 匯編代碼文件(
- 目標文件是指 已編譯的二進制文件,通常是:
- 機器碼文件(
.o
、.obj
) - 二進制數據文件(
.bin
、.dat
) - 其他非文本格式的原始數據文件。
- 機器碼文件(
- 源文件是指 可被匯編器直接處理的文本文件,通常是:
4.3 與ARM指令相關的宏指令
-
MACRO 和 MEND
-
MACRO 和 MEND 偽指令可以為一個程序段定義一個名稱。
-
在匯編語言應用程序中,可以通過這個名稱來使用它所代表的程序段,即當程序做匯編時,該名稱將被替換為其所代表的程序段。
-
語法格式:
MACRO$標號 宏名 $參數1, $參數2,…..程序段(宏定義體) MEND
$標號
:為主標號,宏內的所有其它標號必須由主標號組成- 宏名:宏名稱,為宏在程序中的引用名;
$參數1,$參數2
:宏中可以使用的參數。- 宏中的所有標號必須在前面冠以符號“$”。
- MACRO、 MEND偽指令可以嵌套使用。
-
示例:
MACRO ;宏定義指令 $MDATA MAXNUM $NUM1,$NUM2 ;主標號,宏名,參數語句段 $MDATA.WAY1 ; 宏內標號,必須寫為“主標號.宏內標號”語句段 $MDATA.WAY2 ; 宏內標號語句段MEND ; 宏結束指令主程序中調用該宏: Lab1 MAXNUM 0x01, 0x02
-
-
MEXIT
- MEXIT 用于從宏定義中跳轉出去。
- 語法格式:MEXIT
4.3.2 宏指令
- 在ARM中,還有一種匯編器內置的無參數和標號的宏——宏指令。
- 在匯編時,這些宏指令被替換成一條或兩條真正的ARM或 Thumb 指令。
- ARM宏指令有四條,分別是:
- ADR:小范圍的地址讀取宏指令;
- ADRL:中等范圍的地址讀取宏指令;
- LDR:大范圍的地址讀取宏指令;
- NOP:空操作宏指令。
一、ADR
-
小范圍的地址讀取宏指令
-
ADR 指令用于將一個近地址值傳遞到一個寄存器中。
- 指令格式:
ADR{cond} <reg>, <expr>
- reg 為目標寄存器名稱;
- expr 為表達式。該表達式通常是程序中一個表示存儲位置的地址標號。
- 指令格式:
-
該宏指令的功能是把標號所表示的地址傳遞到目標寄存器中。
-
匯編器在匯編時,將把ADR宏指令替換成一條真正的ADD或SUB指令,以當前的PC值減去或加上expr與PC之間的偏移量得到標號的地址,并將其傳遞到目標寄存器。若不能用一條指令實現,則產生錯誤,編譯失敗。
-
示例:
start MOV R0,#10ADR R4,start
二、ADRL
-
中等范圍的地址讀取宏指令
-
類似于ADR,但可以把更遠的地址賦給目標寄存器。
- 指令格式:
ADRL{cond} <reg>, <expr>
- reg 為目標寄存器名稱
- Expr 為表達式,必須是64KB以內非字對齊地址,或256KB以內的字對齊地址
- 指令格式:
-
該指令只能在ARM狀態下使用,在Thumb狀態下不能使用。
-
匯編時,ADRL宏指令由匯編器替換成兩條合適的指令;如果匯編器找不到合適的兩條指令,將會報錯
-
示例:
- start MOV R0,#10
- ADRL R4,start + 60000
- 此時,start為PC-12,則start+60000=PC+59988(0XEA54)
- 其中 ADRL 將被替換為如下兩條指令:
- ADD R4,PC,#0XE800
- ADD R4,R4,#0X254
三、LDR
-
大范圍的地址讀取宏指令
-
指令格式:
LDR{cond} reg,={expr | label - expr}
- reg:目標寄存器名稱;
- expr:32位常數;
- label – expr:為地址表達式。
-
程序經常用這條指令把一個地址傳遞到寄存器reg中。
-
匯編器在對這種指令進行匯編時,會根據指令中expr的值的大小來把這條指令替換為合適的指令。
-
與ARM指令的LDR的區別:偽指令LDR的參數有“=”號
-
注意:
- 當 expr 的值未超過 MOV 或 MVN 指令所限定的取值范圍時,匯編器用 ARM 的 MOV 或 MVN 指令來取代宏指令 LDR;
- 當 expr 的值超過 MOV 或 MVN 指令所限定的取值范圍時,匯編器將常數 expr 放在由 LTORG 定義的文字緩沖池,同時用一條 ARM 的裝載指令 LDR 來取代宏指令 LDR ,而這條裝載 LDR 指令則用 PC 加偏移量 的方法到文字緩沖池中把該常數讀取到指令指定的寄存器。
- 由于這種指令可以傳遞一個 32 位地址,因此也被叫做全范圍地址讀取指令
-
示例:
四、NOP
-
空操作
-
匯編器對NOP指令進行匯編時,會將其轉換為:MOV R0,R0
-
指令格式:NOP
-
示例
例:延時子程序DelayNOP ;空操作NOPSUBS R1,R1,#1 ;循環次數減1BNE DelayMOV PC,LR
補充:ARM程序設計要點
AREA example,CODE,READONLY ;定義代碼段,一個ARM匯編程序至少有一個代碼段ENTRY ;定義程序入口點
……
……END ;匯編語言程序結束
4.4 匯編語言編程規范
4.4.1 匯編語言的語句格式
- ARM(Thumb)匯編語言的語句格式為:
{<標號>} <指令或偽指令> {;注釋}
- 在匯編語言程序設計中,每一條指令的助記符可以全部用大寫或全部用小寫 ,但不允許在一條指令中大小寫混用 。標號要頂格。
- 如果一條語句太長,則可將該長語句分成若干行來書寫,每行的末尾用“\”來表示下一行與本行為同一條語句。但是注意:在**”\”之后不能再有其他字符,包括空格和制表符**
4.4.2 匯編語言程序中常用的符號
-
在ARM匯編語言中,符號可以代表地址、變量和數字常量。
-
符號命名規則如下:
-
符號由大小寫字母、數字以及下劃線組成;
-
除局部標號以數字開頭,其他的符號都不能以數字開頭;
-
符號是區分大小寫的;
-
符號在其作用范圍內必須唯一;
-
程序中的符號不能與指令助記符、偽指令、宏指令同名。
-
-
這些規則主要涉及程序中的變量和常量
4.4.3 匯編語言的表達式和運算符
一、運算次序優先級:
- 優先級相同的雙目運算符運算順序為從左到右;
- 相鄰的單目運算符的運算順序為從右到左,且單目運算符的優先級高于其他運算符;
- 括號運算符的優先級最高
二、數字表達式及運算符
- +、-、*、/及MOD算術運算符
- X + Y 表示 X 與 Y 的和
- X - Y 表示X與Y的差
- X * Y 表示X與Y的乘積
- X / Y 表示X除以Y的商
- X :MOD: Y 表示X除以Y的余數
- ROL、ROR、SHL及SHR移位運算符
- X :ROL: Y 表示將X循環左移Y位
- X :ROR: Y 表示將X循環右移Y位
- X :SHL: Y 表示將X左移Y位
- X :SHR: Y 表示將X右移Y位
- AND、OR、NOT及EOR按位邏輯運算符
- X :AND: Y 表示將X和Y按位做邏輯“與”的操作
- X :OR: Y 表示將X和Y按位做邏輯“或”的操作
- :NOT: Y 表示將Y按位做邏輯“非”的操作
- X :EOR: Y 表示將X和Y按位做邏輯“異或”的操作
三、邏輯表達式及運算符
- =、>、<、>=、<=、/=、<>運算符
- X = Y 表示X等于Y
- X > Y 表示X大于Y
- X < Y 表示X小于Y
- X >= Y 表示X大于或等于Y
- X <= Y 表示X小于或等于Y
- X /= Y 表示X不等于Y
- X <> Y 表示X不等于Y
- LAND、LOR、LNOT及LEOR運算符
- X :LAND: Y 表示將X和Y做邏輯“與”的操作
- X :LOR: Y 表示將X和Y做邏輯“或”的操作
- :LNOT: Y 表示將Y做邏輯“非”的操作
- X :LEOR: Y 表示將X和Y做邏輯“異或”的操作
四、字符串表達式及運算符
- 編譯器所支持的字符串最大長度為512 字節。
-
LEN 運算符
-
LEN 運算符返回字符串的長度(字符數),以 X 表示字符串表達式。
-
語法格式:
:LEN: X
-
示例:
GBLS STR GBLA LEN STR SETS “AAA” LEN SETA :LEN:STR ;LEN=3
-
-
**CHR 運算符 **
- CHR 運算符將 0~255 之間的整數轉換為一個字符,以 M 表示一個整數,
- 語法格式:
:CHR: M
- 在內存單元中,其值還是沒有變。只是經過這種處理后,可以把它當做一個字符,用于字符串處理函數中。
-
STR 運算符
-
STR 運算符將一個數字表達式或邏輯表達式轉換為一個字符串。
- 對于數字表達式,STR 運算符將其轉換為一個以十六進制組成的字符串
- 對于邏輯表達式,STR 運算符將其轉換為 字符串"T"或"F" 。
-
語法格式:
:STR: X
;X為數字表達式或邏輯表達式 -
示例
AREA blockcopy,CODE,READONLY CODE32LDR R0,=TTQLDR R1,TTQLDR R4,[R0] TTQ DCB :STR: 0x12345678END
- 該程序執行后,R1的值為0x 34333231 (十六進制), 0x34是4對應的ASCII碼,0x31是1對應的ASCII碼。
- 因為ARM默認是小端模式–從低地址到高地址存儲
- 所以先把字符’1’對應的ascii碼放在R1的低字節中
- 因為寄存器只有32位,所以只給4個字節
- 該程序執行后,R1的值為0x 34333231 (十六進制), 0x34是4對應的ASCII碼,0x31是1對應的ASCII碼。
-
-
LEFT 運算符
-
LEFT 運算符返回某個字符串左端的一個 子集。
-
語法格式:
X :LEFT: Y
- X 為源字符串,Y 為一個整數,表示要返回的字符個數
-
示例:
GBLS STR1GBLS STR2 STR1 SETS “AAABBB” STR2 SETS STR1 :LEFT:3; 程序運行完后,STR2為"AAA"
-
-
RIGHT 運算符
-
RIGHT 運算符返回某個字符串右端的一個 子集。
-
語法格式:
X :RIGHT: Y
- X 為源字符串,Y 為一個整數,表示要返回的字符個數
-
示例
GBLS STR1GBLS STR2 STR1 SETS “AAABBB” STR2 SETS STR1 : RIGHT :3 ; 程序運行完后,STR2為”BBB”
-
-
CC 運算符
-
CC 運算符用于將兩個字符串連接成一個字符串。
-
語法格式:
X :CC: Y
- X為源字符串1,Y為源字符串2,CC運算符將Y連接到X的后面。
-
示例:
GBLS STR1;聲明字符串變量STR1GBLS STR2;聲明字符串變量STR2 STR1 SETS “AAACCC”;變量STR1賦值為”AAACCC” STR2 SETS “BBB”:CC:(STR1:LEFT:3) ; 程序運行完后,STR2為”BBBAAA”
-
五、程序中的變量代換
-
程序中的變量可通過代換操作取得一個常量。代換操作符為
$
。- 如果在數字變量前面有一個代換操作符
$
,則編譯器會將該數字變量的值轉換為十六進制的字符串,并將該十六進制的字符串代換$
后的數字變量。 - 如果在邏輯變量前面有一個代換操作符
$
,則編譯器會將該邏輯變量代換為它的取值(真或假)。 - 如果在字符串變量前面有一個代換操作符
$
,則編譯器會將該字符串變量的值代換$
后的字符串變量。
- 如果在數字變量前面有一個代換操作符
-
示例:
GBLS s1 ; 定義局部字符串變量S1和S2GBLS s2 s1 SETS “Test !” s2 SETS “This is a $s1”; 字符串變量S2的值為“This is a Test !”
GBLA Test1GBLL Test2GBLL Test3 Test1 SETA 0x66 Test2 SETL {TRUE} Test3 SETL {FALSE} StrMem DCB "12345$Test1$Test2$Test3" ;StrMem存放的內容為字符串:“1234500000066TF”對應的ASCII碼
- PPT中所有邏輯變量都轉換成T或者F,但是gpt說轉換成TRUE或者FALSE。。???
-
習題
AREA blockcopy,CODE,READONLY ENTRY LDR R1,=fttLDR R2,=ftt2 LDR R3,[R1] LDR R4,[R2] LDR R5,[R1, #4] LDR R6,[R2, #4] Src DCD 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4 StrMem DCB “ABCS”,0 MAP Src ftt FIELD 8 ftt2 FIELD 8 END ;該程序段執行后,R3=1 ,R4=3 ,R5=2 ,R6=4 。
AREA blockcopy,CODE,READONLY ENTRYARMLDR R0,=ss1LDR R1,=ss2LDR R4,[R0]ALIGN 4 ss1 DCB 1 ALIGN 4,3 ss2 DCB 3 END ;該段程序執行完成后,假如R0的值為0x10001000,則R1=0x ,R4=0x
- 這題有問題!
GBLL llllGBLS Str1GBLS Str2GBLS Str3GBLS Str4 Str1 SETS "AAAA" Str2 SETS "BCDEFG" llll SETL {False} Str3 SETS Str1 :CC:(Str2:LEFT:3):CC: :STR: llll Str4 SETS Str1 :CC:Str2:LEFT:3:CC: :STR: llll;該段程段中Str3和Str4對應的字符串分別為:;Str3對應的字符串為:AAAABCDF; Str4對應的字符串為:AAAF
4.5 ARM工程
-
由于C語言便于理解,有大量的支持庫,所以它是當前 ARM 程序設計所使用的主要編程語言。
-
對硬件系統的初始化、CPU狀態設定、中斷使能、主頻設定以及RAM控制參數初始化等C程序力所不能及的底層操作,還是要由匯編語言程序來完成。
-
用匯編語言或C/C++語言編寫的程序叫做源程序,對應的文件叫做源文件。
-
一個ARM工程應由多個文件組成,其中包括擴展名為** .S 的匯編語言源文件**、擴展名為** .C的C語言源文件**,擴展名為 .CPP的C++源文件、擴展名為**.H的頭文件**等。
-
ARM工程的各種源文件之間的關系,以及最后形成可執行文件的過程如下:
-
編譯器
-
編譯器負責生成目標文件,它是一種包含了調試信息的ELF格式文件。
- ELF(Executable and Linking Format):可執行連接格式。
- 可執行連接格式是UNIX系統實驗室(USL)作為應用程序二進制接口(Application Binary Interface(ABI)而開發和發布的。
-
編譯器還要生成列表文件等相關文件:
文件擴展名 說明 .h 頭文件 .o ELF 格式的目標文件 .s 匯編代碼文件 .lst 錯誤及警告信息列表文件
-
-
連接器
- 各種源文件先由編譯器和匯編器將它們分別編譯或匯編成匯編語言文件及目標文件。
- 連接器負責將所有目標文件連接成一個文件并確定各指令的確定地址,從而形成最終可執行文件。
- 連接器有三個功能:
- 根據程序員所指定的選項,為程序分配地址空間;
- 生成與地址相關的代碼,把所有文件連接成一個可執行文件;
- 給出連接信息,以說明連接過程和連接結果
4.6 ARM程序框架
-
在應用系統的程序設計中,若所有的編程任務均用匯編語言來完成,其工作量是可想而知的,這樣做也不利于系統升級或應用軟件移植。
-
通常匯編語言部分完成系統硬件的初始化;高級語言部分完成用戶的應用。
-
執行時,首先執行初始化部分,然后再跳轉到 C/C++ 部分。整個程序結構顯得清晰明了,容易理解。程序的基本結構如下:
4.6.1 初始化程序部分
-
由于在用于完成初始化任務的匯編語言程序中需要在特權模式下做一些諸如修改 CPSR 等特權操作,所以不能過早地進入用戶模式。
-
上電復位后進入SVC模式,除用戶模式外,其它模式中能任意切換工作模式
-
通常,初始化過程大致會經歷如下所示的一些模式變化
4.6.2 初始化部分與主應用程序部分的銜接
-
當所有的系統初始化工作完成之后,就需要把程序流程轉入主應用程序。
-
最簡單的方法是,在匯編語言程序末尾使用跳轉指令 B 或 BL 直接從啟動代碼轉移到 C/C++ 程序入口。
B main
;跳轉到C/C++程序- 同時在匯編文件中有如下代碼:
IMPORT main
-
完整匯編語言程序大致如下:
IMPORT mainAREA Init, CODE, READONLYENTRYLDR SP, =0X3EE1000B mainEND
-
c語言程序如下
void main(void){…..}
-
4.6.3 ARM開發環境提供的程序框架
-
為方便工程開發,ARM 公司的開發環境ARM ADS 為用戶提供了一個可以選用的應用程序框架。
-
該框架把為用戶程序做準備工作的程序分成了: 啟動代碼和應用程序初始化兩部分。
-
用于硬件初始化的匯編語言部分叫做啟動代碼;
-
用于應用程序初始化的C部分叫做初始化部分。
-
整個程序如下所示:
-
4.7 ARM匯編語言程序設計
4.7.1 段
- 匯編語言編寫的程序叫做匯編語言源程序,包含源程序的文件叫做匯編語言程序文件。
- 一個工程可以有多個源文件,匯編源文件的擴展名為 .S。
- 在ARM(Thumb)匯編語言程序中,通常以段為單位來組織代碼。
- 段是具有特定名稱且功能相對獨立的指令或數據序列。
- 根據段的內容,分為代碼段和數據段。
- 一個匯編程序至少應該有一個代碼段,當程序較長時,可以分割為多個代碼段和數據段。
4.7.2 分支程序設計
具有兩個或兩個以上可選執行路徑的程序叫做分支程序。
一、普通分支程序設計
-
使用帶有條件碼的指令可以很容易地實現分支程序。
-
示例:
-
編寫一個分支程序段,如果寄存器 R5 中的數據等于 10,就把 R5 中的數據存入寄存器 R1;否則把 R5 中的數據分別存入寄存器 R0 和 R1。
;用條件指令實現的分支程序段CMP R5,#10 MOVNE R0,R5MOV R1,R5;用條件轉移指令來實現分支程序段CMP R5,#10 BEQ doequalMOV R0,R5doequal MOV R1,R5
-
編寫一個程序段,當寄存器R1中的數據大于R2中數據時,將R2中的數據加10存入寄存器 R1;否則將R2中數據加 5 存入寄存器 R1
CMP R1,R2 ADDHI R1,R2,#10 ADDLS R1,R2,#5
- HI:無符號數大于,LS:無符號數小于或等于;
- GT:帶符號數大于,LE:帶符號數小于或等于。
-
二、多分支(散轉)程序設計
-
程序分支點上有多于兩個以上的執行路徑的程序叫做多分支程序。
-
利用條件測試指令或跳轉表可以實現多分支程序。
-
示例:
-
編寫一個程序段,判斷寄存器R1中數據是否為10、15、12、22。如果是,則將R0中的數據加1;否則將R0設置為 0XF
MOV R0,#0 TEQ R1,#10 TEQNE R1,#15 TEQNE R1,#12 TEQNE R1,#22 ADDEQ R0,R0,#1 MOVNE R0,#0XF
-
-
當多分支程序的每個分支所對應的是一個程序段時,常常把各個分支程序段的首地址依次存放在一個叫做跳轉地址表的存儲區域,然后在程序的分支點處使用一個可以將跳轉表中的目標地址傳送到PC的指令來實現分支
-
一個具有 3 個分支的跳轉地址表示意圖如下:
MOV R0,N ;N為表項序號0~2ADR R5,JPTABLDR PC,[R5,R0,LSL #2] JPTAB ;跳轉表 DCD FUN0DCD FUN1DCD FUN2 FUN0 ….. ;分支FUN0的程序段FUN1 ….. ;分支FUN1的程序段FUN2 ….. ;分支FUN2的程序段
-
三、帶ARM/Thumb狀態切換的分支程序設計
-
在ARM程序中經常需要在程序跳轉的同時還要進行處理器狀態的轉移,即從ARM指令程序段跳轉到Thumb指令程序段(或相反)
-
為了實現這個功能,系統提供了一條專用的、可以實現4GB空間范圍內的絕對跳轉交換指令BX
-
看PPT144-145的例子
4.7.3 循環程序設計
-
當條件滿足時,需要重復執行同一個程序段做同樣工作的程序叫做循環程序。
-
被重復執行的程序段叫做循環體,需要滿足的條件叫做循環條件。
-
循環程序有兩種結構:DO-WHILE結構 和 DO-UNTIL 結構
4.7.4 子程序及其調用
一、子程序的調用與返回
-
人們把可以多次反復調用的、能完成指定功能的程序段稱為“子程序”。把調用子程序的程序稱為“主程序”。
-
為進行識別,子程序的第1條指令之前必須賦予一個標號,以便其他程序可以用這個標號調用子程序。
-
ARM匯編語言程序中,主程序一般通過BL指令來調用子程序。
-
為使子程序執行完畢能返回主程序的調用處,子程序末尾處應有MOV、B、BX、LDMFD等指令,并在指令中將返回地址重新復制到 PC 中
-
示例:
一個使用 MOV 指令實現返回的子程序。relay …..MOV PC, LR 使用 B 指令實現返回的子程序。relay …..B LR ;(錯誤)--開發環境不認這個; 可以使用BX LR (正確)
-
二、子程序中堆棧的使用
relay STMFD R13!,{R0~R12,LR};壓入堆棧…… ;子程序代碼LDMFD R13!,{R0~R12,PC} ;彈出堆棧并返回
4.8 C/C++語言和匯編語言的混合編程
4.8.1 匯編程序訪問全局C變量
-
一般來說,匯編語言程序與C語言程序不在同一個文件上,所以實質上這是一個引用不同文件定義的變量問題。解決這個問題的辦法就是使用關鍵字IMPORT和EXPORT。
-
在C程序中聲明的全局變量可以被匯編程序通過地址間接訪問,具體訪問方法如下。
-
使用 IMPORT 偽指令聲明該全局變量;
-
使用LDR宏指令讀取該全局變量的內存地址,通常該全局變量的內存地址值存放在程序的數據緩沖池中;
-
根據該數據的類型,使用相應的LDR指令讀取該全局變量的值;使用相應的STR 指令修改該全局變量的值。
- 各數據類型及其對應的 LDR/STR 指令如下。
- 對于無符號的char類型的變量通過指令LDRB/STRB來讀寫;
- 對于無符號的short類型的變量通過指令LDRH/STRH 來讀寫;
- 對于 int 類型的變量通過指令LDR/STR 來讀寫;
- 對于有符號的char類型的變量通過指令LDRSB 來讀取;
- 對于有符號的char 類型的變量通過指令 STRB 來寫入;
- 對于有符號的short類型的變量通過指令 LDRSH 來讀取;
- 對于有符號的short類型的變量通過指令 STRH 來寫入;
- 對于小于8個字的結構型變量,可以通過一條 LDM/STM 指令來讀/寫整個變量;
- 對于結構型變量的數據成員,可以使用相應的 LDR/STR 指令來訪問,這時必須知道該數據成員相對于結構型變量開始地址的偏移量。
- 各數據類型及其對應的 LDR/STR 指令如下。
-
示例:
例、下面是一個匯編代碼的函數,它引用了一個在其他文件中定義的全局變量globvar,將其加 2 后寫回 globvar 。AREA globals, CODE, READONLYEXPORT asmsubrouttineIMPORT globvar asmsubrouttineLDR R1,=globvarLDR R0,[R1]ADD R0,R0,#2STR R0,[R1]MOV PC,LREND
-
4.8.2 C與匯編之間的函數調用
- 在ARM工程中,C程序調用匯編函數和匯編程序調用C 函數是經常發生的事情。為此人們制定了ARM-Thumb過程調用標準ATPCS(ARM-Thumb Procedure Call Standard)
一、ATPCS
-
為了使單獨編譯的C語言程序和匯編語言程序之間能夠相互調用,必須為子程序間的調用設置一定的規則。具體包括:
- 在子程序編寫時必須遵守相應的ATPCS規則。
- 數據棧的使用要遵守相應的ATPCS規則。
- 在匯編編譯器中使用選項
-
堆棧與寄存器在函數調用中的作用
- 函數是通過寄存器和堆棧來傳遞參數和返回值的
- ARM編譯器使用的函數調用規則就是ATPCS標準。ATPCS標準既是ARM編譯器的規則,也是設計可被C程序調用的匯編函數的編寫規則
-
ATPCS 關于堆棧和寄存器的使用規則
-
ATPCS規定,ARM的數據堆棧為FD型堆棧,即遞減滿堆棧。
-
ATPCS 標準規定
-
對于參數個數不多于4的函數,編譯器必須按參數在列表中的順序,自左向右為它們分配寄存器 R0~R3。其中函數返回時,R0還被用來存放函數的返回值
-
如果函數的參數多于4個,那么多余的參數則按自右向左的順序壓入數據堆棧,即參數入棧順序與參數順序相反。
-
下表列舉了ARM-Thumb過程調用標準規定的寄存器的名稱和使用方法
-
-
二、C調用匯編函數實例
-
下面是一個用匯編語言編寫的函數,該函數把R1指向的字符串復制到R0指向的存儲塊。
AREA tt, CODE, READONLYEXPORT strcopystrcopy LDRB R2,[R1],#1STRB R2,[R0],#1CMP R2,#0BNE strcopyMOV PC,LREND
- 根據ATPCS的C語言程序調用匯編函數的規則,參數由左向右依次傳遞給寄存器R0~R3,可知匯編函數strcopy 在 C 程序中原型應該為:
void strcopy(char *d,const char* s);
// 在 C 語言文件中,調用 strcopy 函數的方法如下:extern void strcopy(char *d,const char * s);int main(void){const char *src = “source”;char dest[10];…….strcopy(dest, src);……….}
- 根據ATPCS的C語言程序調用匯編函數的規則,參數由左向右依次傳遞給寄存器R0~R3,可知匯編函數strcopy 在 C 程序中原型應該為:
-
常量指針
- 表示指針所指向的地址的內容是不可修改的,但指針自身可變。
- const 類型 指針名*
- 表示指針所指向的地址的內容是不可修改的,但指針自身可變。
-
指針常量
- 指針常量表示指針自身不可變,但其指向的地址的內容是可以修改的
- 類型 const 指針名*
- 指針常量表示指針自身不可變,但其指向的地址的內容是可以修改的
三、匯編調用C函數
-
現有 C 函數 g() 如下:
int g(int a, int b, int c, int d, int e){return a+b+c+d+e;}
匯編函數f中調用C函數g(),以實現下面的功能:
int f(int i) {return –g(i, 2*i, 3*i, 4*i,5*i)}
;整個匯編函數 f 的代碼如下:EXPORT f AREA tt, CODE, READONLYIMPORT g ;聲名g為外部引用符號ENTRYf STR LR, [SP,#-4]! ;斷點存入堆棧,解決嵌套、返回ADD R1, R0, R0 ;(R1)= i*2ADD R2, R1, R0 ;(R2)= i*3ADD R3, R1, R2 ;(R3)= i*5STR R3, [SP, #-4]! ;將(R3)即第5個參數i*5存入堆棧ADD R3, R1, R1 ;(R3)= i*4BL g ;調用C函數g(),返回值在寄存器R0中ADD SP, SP, #4 ;清棧,第5個參數RSB R0, R0, #0 ;函數f的返回值(R0)=0-(R0)LDR PC, [SP],#4 ;恢復斷點并返回END
4.8.3 C和匯編的混合編程
- 除了上面介紹的函數調用方法之外,ARM編譯器 armcc 中含有的內嵌匯編器還允許在C程序中內聯或嵌入式匯編代碼,以提高程序的效率
一、內聯匯編
-
定義內聯匯編程序
-
所謂內聯匯編程序,就是在C程序中直接編寫匯編程序段而形成一個語句塊,這個語句塊可以使用除了 BX 和 BLX之外的全部ARM指令來編寫,從而可以使程序實現一些不能從C獲得的底層功能。
-
其格式為:
_ _asm { 匯編語句塊 }
- 匯編語句塊中,如果有兩條指令占據了同一行,那么必須用分號“ ;”將它們分隔。
- 如果一條指令需要占用多行,那么必須用反斜線符號“ \ ”作為續行符。
- 可以在內聯匯編語言塊內的任意位置使用 C/C++ 格式的注釋。
- 內聯匯編代碼中定義的標號可被用作跳轉或C/C++ goto 語句的目標,同樣,在C/C++代碼中定義的標號,也可被用作內聯匯編代碼跳轉指令的目標。
-
-
內聯匯編的限制
- 內聯匯編與真實匯編之間有很大區別,會受到很多限制。
- 它不支持 Thumb 指令;
- 除了程序狀態寄存器 CPSR 之外,不能直接訪問其他任何物理寄存器等;
- 如果在內聯匯編程序指令中出現了以某個寄存器名稱命名的操作數,那么它被叫做虛擬寄存器,而不是實際的物理寄存器。編譯器在生成和優化代碼的過程中,會給每個虛擬寄存器分配實際的物理寄存器,但這個物理寄存器可能與在指令中指定的不同。唯一的一個例外就是狀態寄存器PSR ,任何對PSR的引用總是執行指向物理 PSR;
- 在內聯匯編代碼中不能使用寄存器 PC(R15)、LR(R14)和SP(R13),任何試圖使用這些寄存器的操作都會導致出現錯誤消息;
- 鑒于上述情況,在內聯匯編語句塊中最好使用C或C++ 變量作為操作數;
- 雖然內聯匯編代碼可以更改處理器模式,但更改處理器模式會禁止使用C操作數或對已編譯C代碼的調用,直到將處理器模式恢復為原設置之后。
- 內聯匯編與真實匯編之間有很大區別,會受到很多限制。
二、嵌入式匯編
-
嵌入式匯編程序是一個編寫在C程序外的單獨匯編程序,該程序段可以像函數那樣被C程序調用。
-
與內聯匯編不同,嵌入式匯編具有真實匯編的所有特性,數據交換符合 ATPCS 標準,同時支持 ARM 和Thumb,所以它可以對目標處理器進行不受限制的低級訪問。但是不能直接引用 C/C++ 的變量。
-
用 _ _asm聲明的嵌入式匯編程序像C函數那樣可以有參數和返回值。定義一個嵌入式匯編函數的語法格式為:
_ _asm return–type function–name(parameter-list){匯編程序段} // return–type:函數返回值類型,C語言中的數據類型; // function–name:函數名; // parameter-list:函數參數列表
-
嵌入式匯編在形式上看起來就像使用關鍵字 _ _asm進行了聲明的函數
-
注:
-
ADS環境中不能使用嵌入式匯編。
-
參數名只允許使用在參數列表中,不能用在嵌入式匯編函數體內
-
如下面定義的嵌入式匯編程序是錯誤的
_ _asm int f(int i){ADD i, i, #1 //錯誤MOV PC, LR}// 按 ATPCS 規定,應該使用寄存器 R0 來代替 i。
-
-
-
在C程序中調用嵌入式匯編程序的方法與調用C函數的方法相同。
-
三、內聯匯編和嵌入式匯編之間的差異
-
內聯匯編代碼使用高級處理器抽象,并在代碼生成過程中與 C 和 C++代碼集成。因此編譯程序將 C 和 C++代碼與匯編代碼一起進行優化;
-
與內聯匯編代碼不同,嵌入式匯編代碼從 C 和 C++ 代碼中分離出來單獨進行匯編,產生與 C 和 C++ 源代碼編譯對象相結合的編譯對象;
-
可通過編譯程序來內聯匯編代碼,但無論是顯示還是隱式,都無法內聯嵌入式匯編代碼
-
主要差異:
項目 內聯匯編 嵌入式匯編 指令集 僅限于ARM ARM和Thumb ARM匯編程序命令 不支持 支持 C表達式 支持 僅支持常量表達式 優化代碼 支持 不支持 內聯 可能 從不 寄存器訪問 不使用物理寄存器 使用物理寄存器 返回指令 自動生成 顯示編寫