ARM裸機(jī)開發(fā)篇3:ARM匯編語言程序設(shè)計(jì)
寫在前面:
本文章為《ARM Cortex-A7裸機(jī)開發(fā)篇》系列中的一篇,全系列總計(jì)11篇。筆者使用的開發(fā)平臺為華清遠(yuǎn)見FS-MP1A開發(fā)板(STM32MP157開發(fā)板)。
針對FS-MP1A開發(fā)板,除了Cortex-A7裸機(jī)開發(fā)篇外,還包括其他多系列教程,包括Cortex-M4開發(fā)篇、FreeRTOS篇、Linux基礎(chǔ)及應(yīng)用開發(fā)篇、Linux系統(tǒng)移植篇、Linux驅(qū)動開發(fā)篇、硬件設(shè)計(jì)篇、人工智能機(jī)器視覺篇、Qt應(yīng)用編程篇、Qt綜合項(xiàng)目實(shí)戰(zhàn)篇等。歡迎關(guān)注,更多stm32mp157開發(fā)教程及視頻,可加技術(shù)交流Q群483143191,感謝關(guān)注。
FS-MP1A開發(fā)板詳情介紹:https://item.taobao.com/item.htm?id=622457259672
1.1?GNU ARM匯編器支持的偽操作
1.1.1?偽操作概述
在ARM匯編語言程序中,有一些特殊指令助記符,這些助記符與指令系統(tǒng)的助記符不同,沒有相對應(yīng)的操作碼,通常稱這些特殊指令助記符為偽操作標(biāo)識符(directive),它們所完成的操作稱為偽操作。偽操作在源程序中的作用是為了完成匯編程序做各種準(zhǔn)備工作的,這些偽操作僅在匯編過程中起作用,一旦匯編結(jié)束,偽操作的使命就完成。
在ARM的匯編程序中,偽操作主要有符號定義偽操作、數(shù)據(jù)定義偽操作、匯編控制偽操作及其雜項(xiàng)偽操作等。
1.1.2?數(shù)據(jù)定義(Data Definition)偽操作
數(shù)據(jù)定義偽操作一般用于為特定的數(shù)據(jù)分配存儲單元,同時可完成已分配存儲單元的初始化。常見的數(shù)據(jù)定義偽操作有.byte、.short、.long、.quad、.float、.string、.asciz、.ascii和.rept。數(shù)據(jù)定義偽操作如下。
(1) 偽指令名:
.byte
用途:單字節(jié)定義。
用法:
.byte 1,2,0b01,0x34,072,'s' ;
(2) 偽指令名:
.short
用途:定義雙字節(jié)數(shù)據(jù),
用法:
.short 0x1234,60000 ;
(3) 偽指令名:
.long
用途: 定義4字節(jié)數(shù)據(jù)。
用法:
.long 0x12345678,23876565
(4) 偽指令名:
.quad:
用途:定義8字節(jié)。
用法:
.quad 0x1234567890abcd
(5) 偽指令名:
.float
用途:定義浮點(diǎn)數(shù)。
用法:
.float 0f311971.693993751E-40
(6) 偽指令名:
.string/.asciz/.ascii:
用途:定義多個字符串。
用法:
.string "abcd", "efgh", "hello!"
.asciz "qwer", "sun", "world!"
.ascii "welcome\0"(需要注意的是:.ascii偽操作定義的字符串需要在每行末尾添加結(jié)尾字符'\0')
(7) 偽指令名:
.rept/.endr
用途:重復(fù)定義偽操作。
用法:
.rept 3
.byte 0x23
.endr
(8) 偽指令名:
.equ/.set
用途:賦值語句,.equ(.set) 變量名,表達(dá)式。
用法:
.equ abc 3 @ abc=3
1.1.3?匯編控制偽操作
匯編控制偽操作用于控制匯編程序的執(zhí)行流程,常用的匯編控制偽操作包括以下幾條。
1、 .if、.else、.endif
a) 語法格式
.if、.else、.endif偽操作能根據(jù)條件的成立與否決定是否執(zhí)行某個指令序列。當(dāng).if后面的邏輯表達(dá)式為真,則執(zhí)行.if后的指令序列,否則執(zhí)行.else后的指令序列。其中,.else及其后指令序列可以沒有,此時,當(dāng).if后面的邏輯表達(dá)式為真,則執(zhí)行指令序列,否則繼續(xù)執(zhí)行后面的指令。
提示:
.if、.else、.endif偽指令可以嵌套使用。
語法格式如下:
.if logical-expressing
…
{.else
…}
.endif logical-expression:
用于決定指令執(zhí)行流程的邏輯表達(dá)式。
b) 使用說明
當(dāng)程序中有一段指令需要在滿足一定條件時執(zhí)行,使用該指令。該操作還有另一種形式。
.if logical-expression
Instruction
.elseif logical-expression2
Instructions
.endif
該形式避免了if-else形式的嵌套,使程序結(jié)構(gòu)更加清晰、易讀。
2、 .macro、.endm
a) 語法格式
.macro偽操作可以將一段代碼定義為一個整體,稱為宏指令,然后就可以在程序中通過宏指令多次調(diào)用該段代碼。其中,$標(biāo)號在宏指令被展開時,標(biāo)號會被替換為用戶定義的符號。
宏操作可以使用一個或多個參數(shù),當(dāng)宏操作被展開時,這些參數(shù)被相應(yīng)的值替換。
宏操作的使用方式和功能與子程序有些相似,子程序可以提供模塊化的程序設(shè)計(jì)、節(jié)省存儲空間并提高運(yùn)行速度。但在使用子程序結(jié)構(gòu)時需要保護(hù)現(xiàn)場,從而增加了系統(tǒng)的開銷,因此,在代碼較短且需要傳遞的參數(shù)較多時,可以使用宏操作代替子程序。
包含在.macro和.endm之間的指令序列稱為宏定義體,在宏定義體的第一行應(yīng)聲明宏的原型(包含宏名、所需的參數(shù)),然后就可以在匯編程序中通過宏名來調(diào)用該指令序列。在源程序被編譯時,匯編器將宏調(diào)用展開,用宏定義中的指令序列代替程序中的宏調(diào)用,并將實(shí)際參數(shù)的值傳遞給宏定義中的形式參數(shù)。
提示:
.macro、.endm偽操作可以嵌套使用。
語法格式如下:
.macro
{$label} macroname {$parameter{,$parameter}…}
;code
.endm
(1) {$label}。
(9) $標(biāo)號在宏指令被展開時,標(biāo)號會被替換為用戶定義的符號。通常,在一個符號前使用“$”表示該符號被匯編器編譯時,使用相應(yīng)的值代替該符號。
(10) Macroname:所定義的宏的名稱。
(11) Parameter:宏指令的參數(shù)。當(dāng)宏指令被展開時將被替換成相應(yīng)的值,類似于函數(shù)中的參數(shù)。
b) 使用說明
在子程序代碼比較短,而需要傳遞的參數(shù)比較多的情況下可以使用宏匯編技術(shù)。
首先通過.macro和.endm偽操作定義宏,包括宏定義體代碼。在.macro偽操作之后的第一行聲明宏的原型,其中包含該宏定義的名稱及需要的參數(shù)。在匯編中可以通過該宏定義的名稱來調(diào)用它。當(dāng)源程序被編譯時,匯編器將展開每個宏調(diào)用,用宏定義體代替源程序中宏定義的名稱,并用實(shí)際參數(shù)值代替宏定義時的形式參數(shù)。
c) 示例
示例如下:
.macro SHIFTLEFT a, b
.if \b < 0
MOV \a, \a, ASR #-\b
.exitm
.endif
MOV \a, \a, LSL #\b
.endm
3、 .mexit
a) 語法格式
.mexit用于從宏定義中跳轉(zhuǎn)出去。
b) 用法
只需要在宏定義的代碼中插入該指令即可。
.macro SHIFTLEFT a, b
.if \b < 0
mov \a, \a, ASR #-\b
.exitm
.endif
mov \a, \a, LSL #\b
.endm
1.1.4?雜項(xiàng)偽操作
ARM匯編中還有一些其他的偽操作,在匯編程序中經(jīng)常會被使用,包括以下幾條。
.arm .arm @ 定義以下代碼使用ARM指令集編譯
.code 32 .code 32 @作用同.arm
.code 16 .code 16 @作用同.thumb
.thumb .thumb @定義以下代碼使用Thumb指令集編譯
.section .section expr @定義域中包含的段。expr可以使.text,.data.,.bss
.text .text {subsection} @將定義符開始的代碼編譯到代碼段或代碼子段(subsection)
.data .data {subsection} @將定義符開始的代碼編譯到數(shù)據(jù)段或數(shù)據(jù)子段(subsection)
.bss .bss {subsection} @將變量存放到.bss段或.bss的子段(subsection)
.align .align{alignment}{,fill}{,max} @通過用零或指定的數(shù)據(jù)進(jìn)行填充來使當(dāng)前位置與指定邊界對齊
.org .org offset{,expr} @指定從當(dāng)前地址加上offset開始存放代碼,并且從當(dāng)前地址到當(dāng)前地址加上offset之間的內(nèi)存單元,用零或指定的數(shù)據(jù)進(jìn)行填充
1.2?ARM匯編器支持的偽指令
ARM匯編器支持ARM偽指令,這些偽指令在匯編階段被翻譯成ARM或者Thumb(或Thumb-2)指令(或指令序列)。ARM偽指令包含ADR、ADRL、ldr等。
1.2.1?ADR偽指令
1、 語法格式
ADR偽指令為小范圍地址讀取偽指令。ADR偽指令將基于PC相對偏移地址或基于寄存器相對偏移地址值讀取到寄存器中,當(dāng)?shù)刂分凳亲止?jié)對齊時,取值范圍為?255~255,當(dāng)?shù)刂分凳亲謱R時,取值范圍為?1020~1020。當(dāng)?shù)刂分凳?6字節(jié)對齊時其取值范圍更大。
語法格式如下:
ADR{cond}{.W} register,label
a) cond
可選的指令執(zhí)行條件。
b) .W
可選項(xiàng)。指定指令寬度(Thumb-2指令集支持)。
c) register
目標(biāo)寄存器。
d) label
基于PC或具有寄存器的表達(dá)式。
2、 使用說明
ADR偽指令被匯編器編譯成一條指令。匯編器通常使用ADD指令或SUB指令來實(shí)現(xiàn)偽操作的地址裝載功能。如果不能用一條指令來實(shí)現(xiàn)ADR偽指令的功能,匯編器將報告錯誤。
3、 示例
示例如下。
ldr R4,=data+4*n ;n是匯編時產(chǎn)生的變量
; code
MOV pc,lr
data .word value0
; n-1條 DCD 偽操作
.word valuen ;所要裝載入R4的值
;更多 DCD 偽操作
1.2.2?ADRL偽指令
1、 語法格式
ADRL偽指令為中等范圍地址讀取偽指令。ADRL偽指令將基于PC相對偏移的地址或基于寄存器相對偏移的地址值讀取到寄存器中,當(dāng)?shù)刂分凳亲止?jié)對齊時,取值范圍為?64~64KB;當(dāng)?shù)刂分凳亲謱R時,取值范圍為?256~256KB。當(dāng)?shù)刂分凳?6字節(jié)對齊時,其取值范圍更大。在32位的Thumb-2指令中,地址取值范圍到達(dá)?1~1MB。
語法格式如下:
ADRL{cond} register,label
a) cond
可選的指令執(zhí)行條件。
b) register
目標(biāo)寄存器。
c) label
基于PC或具體寄存器的表達(dá)式。
2、 使用說明
ADRL偽指令與ADR偽指令相似,用于將基于PC相對偏移的地址或基于寄存器相對偏移的地址值讀取到寄存器中。所不同的是,ADRL偽指令比ADR偽指令可以讀取更大范圍的地址。這是因?yàn)樵诰幾g階段,ADRL偽指令被編譯器換成兩條指令。即使一條指令可以完成該操作,編譯器也將產(chǎn)生兩條指令,其中一條為多余指令。如果匯編器不能在兩條指令內(nèi)完成操作,將報告錯誤,中止編譯。
1.2.3?ldr偽指令
1、 語法格式
ldr偽指令裝載一個32位的常數(shù)和一個地址到寄存器。
語法格式如下:
ldr{cond}{.W} register,=[expr|label-expr]
(12) Cond
可選的指令執(zhí)行條件。
(13) .W
可選項(xiàng)。指定指令寬度(Thumb-2指令集支持)。
(14) register
目標(biāo)寄存器。
(15) expr
32位常量表達(dá)式。匯編器根據(jù)expr的取值情況,對ldr偽指令做如下處理。
① 當(dāng)expr表示的地址值沒有超過MOV指令或MVN指令的地址取值范圍時,匯編器用一對MOV和MVN指令代替ldr指令。
② 當(dāng)expr表示的指令地址值超過了MOV指令或MVN指令的地址范圍時,匯編器將常數(shù)放入數(shù)據(jù)緩存池,同時用一條基于PC的ldr指令讀取該常數(shù)。
(16) label-expr
一個程序相關(guān)或聲明為外部的表達(dá)式。匯編器將label-expr表達(dá)式的值放入數(shù)據(jù)緩存池,使用一條程序相關(guān)ldr指令將該值取出放入寄存器。
當(dāng)label-expr被聲明為外部的表達(dá)式時,匯編器將在目標(biāo)文件中插入鏈接重定位偽操作,由鏈接器在鏈接時生成該地址。
2、 使用說明
當(dāng)要裝載的常量超出了MOV指令或MVN指令的范圍時,使用ldr指令。
由ldr指令裝載的地址是絕對地址,即PC相關(guān)地址。
當(dāng)要裝載的數(shù)據(jù)不能由MOV指令或MVN指令直接裝載時,該值要先放入數(shù)據(jù)緩存池,此時ldr偽指令處的PC值到數(shù)據(jù)緩存池中目標(biāo)數(shù)據(jù)所在地址的偏移量有一定限制。ARM或32位的Thumb-2指令中該范圍是?4~4KB,Thumb或16位的Thumb-2指令中該范圍是0~1KB。
3、 示例
(1)將常數(shù)0xff0讀到R1中。
ldr R3,=0xff0 ;
相當(dāng)于下面的ARM指令:
MOV R3,#0xff0
(2)將常數(shù)0xfff讀到R1中。
ldr R1,=0xfff ;
相當(dāng)于下面的ARM指令:
ldr R1,[pc,offset_to_litpool]
…
litpool .word 0xfff
(3)將place標(biāo)號地址讀入R1中。
ldr R2,=place ;
相當(dāng)于下面的ARM指令:
ldr R2,[pc,offset_to_litpool]
…
litpool .word place
1.3?GNU ARM 匯編語言的語句格式
在匯編語言程序設(shè)計(jì)中,經(jīng)常使用各種符號代替地址(addresses)、變量(variables)和常量(constants)等,以增加程序的靈活性和可讀性。盡管符號的命名由編程者決定,但并不是任意的,必須遵循以下的約定。
(1) 符號區(qū)分大小寫,同名的大、小寫符號會被編譯器認(rèn)為是兩個不同的符號。
(2) 符號在其作用范圍內(nèi)必須唯一。
(3) 自定義的符號名不能與系統(tǒng)的保留字相同。其中保留字包括系統(tǒng)內(nèi)部變量(built in variable)和系統(tǒng)預(yù)定義(predefined symbol)的符號。
(4) 符號名不應(yīng)與指令或偽指令同名。如果要使用和指令或偽指令同名的符號要用雙斜杠“//”將其括起來,如“//SSERT//”。
注意:雖然符號被雙斜杠括起來,但雙斜杠并非符號名的一部分。
(5) 局部標(biāo)號以數(shù)字開頭,其他的符號都不能以數(shù)字開頭。
1、 變量(variable)
程序中的變量是指其值在程序的運(yùn)行過程中可以改變的量。ARM(Thumb)匯編程序所支持的變量有3種。
? 數(shù)字變量(numeric)。
? 邏輯變量(logical)。
? 字符串變量(string)。
數(shù)字變量用于在程序的運(yùn)行中保存數(shù)字值,但注意數(shù)字值的大小不應(yīng)超出數(shù)字變量所能表示的范圍。
邏輯變量用于在程序的運(yùn)行中保存邏輯值,邏輯值只有兩種取值情況:真({TURE})和假({FALSE})。
字符串變量用于在程序的運(yùn)行中保存一個字符串,注意字符串的長度不應(yīng)超出字符串變量所能表示的范圍。
在ARM(Thumb)匯編語言程序設(shè)計(jì)中,可使用GBLA、GBLL、GBLS偽指令聲明全局變量,使用LCLA、LCLL、LCLS偽指令聲明局部變量,可使用SETA、SETL和SETS對其進(jìn)行初始化。
2、 常量(constants)
程序中的常量是指其值在程序的運(yùn)行過程中不能被改變的量。ARM(Thumb)匯編程序所支持的常量有數(shù)字常量、邏輯常量和字符串常量。
數(shù)字常量一般為32位的整數(shù),當(dāng)作為無符號數(shù)時,其取值范圍為0~232?1,當(dāng)作為有符號數(shù)時,其取值范圍為?231~231?1。匯編器認(rèn)為?n和232?n是相等的。對于關(guān)系操作,如比較兩個數(shù)的大小,匯編器將其操作數(shù)看做無符號的數(shù),也就是說“0>?1”,對匯編器來說取值為“假({FLASE})”。
邏輯常量只有兩種取值情況,真或假。
字符串常量為一個固定的字符串,一般用于程序運(yùn)行時的信息提示。
3、 程序中的變量代換
匯編語言中的變量可以作為一整行出現(xiàn)在匯編程序中,也可以作為行的一部分使用。如果在數(shù)字變量前面有一個代換操作符“$”,編譯器會將該數(shù)字變量的值轉(zhuǎn)換為十六進(jìn)制的字符串,并將該十六進(jìn)制的字符串代換“$”后的數(shù)字變量。
如果在邏輯變量前面有一個代換操作符“$”,編譯器會將該邏輯變量代換為它的取值(真或假)。如果在字符串變量前面有一個代換操作符“$”,編譯器會將該字符串變量的值代換“$”后的字符串變量。如果程序中需要字符“$”,則可以用“$$”來表示。匯編器將不進(jìn)行變量替換,而是將“$$”作為“$”。
4、 程序標(biāo)號(label)
在ARM匯編中,標(biāo)號代表一個地址,段內(nèi)標(biāo)號的地址在匯編時確定,而段外標(biāo)號地址值在鏈接時確定。根據(jù)標(biāo)號的生成方式,程序標(biāo)號分為以下3種。
? 程序相關(guān)標(biāo)號(Program-relative labels)。
? 寄存器相關(guān)標(biāo)號(Register-relative labels)。
? 絕對地址(Absolute address)。
程序相關(guān)標(biāo)號
程序相關(guān)標(biāo)號指位于目標(biāo)指令前的標(biāo)號或程序中的數(shù)據(jù)定義偽操作前的標(biāo)號。這種標(biāo)號在匯編時將被處理成PC值加上或減去一個數(shù)字常量。它常用于表示跳轉(zhuǎn)指令的目標(biāo)地址或代碼段中所嵌入的少量數(shù)據(jù)。
寄存器相關(guān)地址
這種標(biāo)號在匯編時將被處理成寄存器的值加上或減去一個數(shù)字常量。它常被用于訪問數(shù)據(jù)段中的數(shù)據(jù)。這種基于寄存器的標(biāo)號通常用MAP和FIELD偽操作定義,也可以用EQU偽操作定義。
絕對地址
絕對地址是一個32位的數(shù)字量,使用它可以直接尋址整個內(nèi)存空間。
5、 局部標(biāo)號
局部標(biāo)號是一個0~99之間的十進(jìn)制數(shù)字,可重復(fù)定義。局部標(biāo)號后面可以緊接一個通常表示該局部變量作用范圍的符號。局部變量的作用范圍為當(dāng)前段,也可以用偽操作ROUT來定義局部標(biāo)號的作用范圍。
局部標(biāo)號在子程序或程序循環(huán)中常被用到,也可以配合宏定義偽操作(.MACRO和.MEND)來使程序結(jié)構(gòu)更加合理。在同一個段中,可以使用相同的數(shù)字命名不同的局部變量。默認(rèn)情況下,匯編器會尋址最近的變量。也可以通過匯編器命令選項(xiàng)來改變搜索順序。
1.4?ARM 匯編語言的程序結(jié)構(gòu)
1.4.1?匯編語言的程序格式
在ARM(Thumb)匯編語言程序中可以使用.section來進(jìn)行分段,其中每一個段用段名或者文件結(jié)尾為結(jié)束,這些段使用默認(rèn)的標(biāo)志,如a為允許段,w為可寫段,x為執(zhí)行段。
在一個段中,我們可以定義下列的子段:
? .text
? .data
? .bss
? .sdata
? .sbss
由此我們可知道,段可以分為代碼段、數(shù)據(jù)段及其他存儲用的段,.text(正文段)包含程序的指令代碼;.data(數(shù)據(jù)段)包含固定的數(shù)據(jù),如常量、字符串;.bss(未初始化數(shù)據(jù)段)包含未初始化的變量、數(shù)組等,當(dāng)程序較長時,可以分割為多個代碼段和數(shù)據(jù)段,多個段在程序編譯鏈接時最終形成一個可執(zhí)行的映像文件。
.section.data
< initialized data here>
.section .bss
< uninitialized data here>
.section .text
.globl _start
_start:
<instruction code goes here>
1.4.2?匯編語言子程序調(diào)用
在ARM匯編語言程序中,子程序的調(diào)用一般是通過BL指令來實(shí)現(xiàn)的。在程序中,使用指令“BL子程序”名即可完成子程序的調(diào)用。
該指令在執(zhí)行時完成如下操作:將子程序的返回地址存放在連接寄存器LR中,同時將程序計(jì)數(shù)器PC指向子程序的入口點(diǎn)。當(dāng)子程序執(zhí)行完畢需要返回調(diào)用處時,只需要將存放在LR中的返回地址重新復(fù)制給程序計(jì)數(shù)器PC即可。在調(diào)用子程序的同時,也可以完成參數(shù)的傳遞和從子程序返回運(yùn)算的結(jié)果,通??梢允褂眉拇嫫鱎0~R3完成。
注意:
同編譯器編譯的代碼間的相互調(diào)用,要遵循AAPCS(ARM Architecture)。詳見ARM編譯工具手冊。
以下是使用BL指令調(diào)用子程序的匯編語言源程序的基本結(jié)構(gòu):
.text
.global _start
_start:
ldr R0,=0x3FF5000
ldr R1,0xFF
STR R1,[R0]
ldr R0,=0x3FF5008
ldr R1,0x01
STR R1,[R0]
BL PRINT_TEXT
…
PRINT_TEXT:
…
MOV PC,BL
…
1.4.3?過程調(diào)用標(biāo)準(zhǔn)AAPCS
為了使不同編譯器編譯的程序之間能夠相互調(diào)用,必須為子程序間的調(diào)用規(guī)定一定的規(guī)則。AAPCS就是這樣一個標(biāo)準(zhǔn)。所謂AAPCS,其英文全稱為Procedure Call Standard for the ARM Architecture(AAPCS),即ARM體系結(jié)構(gòu)過程調(diào)用標(biāo)準(zhǔn)。它是ABI(Application Binary Interface(ABI)for the ARM Architecture (base standard) [BSABI])標(biāo)準(zhǔn)的一部分。
可以使用“--apcs”選項(xiàng)告訴編譯器將源代碼編譯成符號AAPCS調(diào)用標(biāo)準(zhǔn)的目標(biāo)代碼。
注意:
使用“--apcs”選項(xiàng)并不影響代碼的產(chǎn)生,編譯器只是在各段中放置相應(yīng)的屬性,標(biāo)識用戶選定的AAPCS屬性。
1、 AAPCS相關(guān)的編譯/匯編選項(xiàng)
? none:指定輸入文件不使用AAPCS規(guī)則。
? /interwork:指定輸入文件符合ARM/Thumb交互標(biāo)準(zhǔn)。
? /nointerwork:指定輸入文件不能使用ARM/Thumb交互。這是編譯器默認(rèn)選項(xiàng)。
? /ropi:指定輸入文件是位置無關(guān)只讀文件。
? /noropi:指定輸入文件是非位置無關(guān)只讀文件。這是編譯器默認(rèn)選項(xiàng)。
? /pic:同/ropi。
? /nopic:同/noropi。
? /rwpi:指定輸入文件是位置無關(guān)可讀可寫文件。
? /norwpi:指定輸入文件是非位置無關(guān)可讀可寫文件。
? /pid:同/rwpi。
? /nopid:同/norwpi。
? /fpic:指定輸入文件編譯成位置無關(guān)只讀代碼。代碼中地址是FPIC地址。
? /swstackcheck:編譯過程中對輸入文件使用堆棧檢測。
? /noswstackcheck:編譯過程中對輸入文件不使用堆棧檢測。這是編譯器默認(rèn)選項(xiàng)。
? /swstna:如果匯編程序?qū)τ谑欠襁M(jìn)行數(shù)據(jù)棧檢查無所謂,而與該匯編程序連接的其他程序指定了選項(xiàng)/swst或選項(xiàng)/noswst,這時該匯編程序使用選項(xiàng)/swstna。
2、 ARM寄存器使用規(guī)則
AAPCS中定義了ARM寄存器使用規(guī)則如下:
子程序間通過寄存器R0、R1、R2、 R3來傳遞參數(shù)。如果參數(shù)多于4個,則多出的部分用堆棧傳遞。被調(diào)用的子程序在返回前無須恢復(fù)寄存器R0-R3的內(nèi)容。
在子程序中,使用寄存器R4-R11來保存局部變量。如果在子程序中使用到了寄存器R4-R11中的某些寄存器,子程序進(jìn)入時必須保存這些寄存器的值,在返回前必須恢復(fù)這些寄存器的值;對于子程序中沒有用到的寄存器則不必進(jìn)行這些操作。在Thumb程序中,通常只能使用寄存器R4-R7來保存局部變量。
寄存器R12用做子程序間scratch寄存器(用于保存SP,在函數(shù)返回時使用該寄存器出棧),記作ip。在子程序間的連接代碼段中常有這種使用規(guī)則。
寄存器R13用做數(shù)據(jù)棧指針,記作sp。在子程序中寄存器R13不能用做其他用途。寄存器sp在進(jìn)入子程序時的值和退出子程序時的值必須相等。
寄存器R14稱為連接寄存器,記作lr。它用于保存子程序的返回地址。如果在子程序中保存了返回地址,寄存器R14則可以用做其他用途。
寄存器R15是程序計(jì)數(shù)器,記作pc。它不能用做其他用途。
ARM寄存器在函數(shù)調(diào)用過程中的保護(hù)規(guī)則,如圖所示。

1.4.4?匯編語言程序設(shè)計(jì)舉例
通過組合使用條件執(zhí)行和條件標(biāo)志設(shè)置,可簡單地實(shí)現(xiàn)分支語句,不需要任何分支指令。這樣可以改善性能,因?yàn)榉种е噶顣加幂^多的周期數(shù);同時這樣做也可以減小代碼尺寸,提高代碼密度。
下面是一段C語言程序,該程序?qū)崿F(xiàn)了著名的Euclid最大公約數(shù)算法:
int gcd(int a, int b)
{
while (a != b)
{
if (a > b)
a = a - b;
else
b = b - a;
}
return a;
}
用ARM匯編語言重寫來重寫這個例子,如下所示。
Code1:
gcd:
CMP r0, r1
BEQ end
BLT less
SUB r0, r0, r1
B gcd
Less:
SUB r1, r1, r0
B gcd
充分地利用條件執(zhí)行修改上面的例子,得到Code2。
Code2:
gcd:
CMP r0, r1
SUBGT r0, r0, r1
SUBLT r1, r1, r0
BNE gcd
兩段代碼的比較如下。
? Code1:僅使用了分支指令。
? Code2:充分利用了ARM指令條件執(zhí)行的特點(diǎn),僅使用了4條指令就完成了全部算法。這對提供程序的代碼密度和執(zhí)行速度十分有幫助。
事實(shí)上,分支指令十分影響處理器的速度。每次執(zhí)行分支指令,處理器都會排空流水線,重新裝載指令。
1.5?ARM 偽指令實(shí)驗(yàn)
1.5.1?實(shí)驗(yàn)?zāi)康?/h1>
? 掌握 ARM匯編語言的基本使用和一些偽指令的使用;
? 熟悉 eclipse 開發(fā)工具建立匯編工程和仿真;
1.5.2?實(shí)驗(yàn)原理
根據(jù)上面闡述RAM 匯編語言的使用語法和功能,編寫匯編程序,實(shí)現(xiàn)將存放在兩個內(nèi)存中的數(shù)據(jù)相加的操作。
1.5.3?實(shí)驗(yàn)內(nèi)容
匯編程序設(shè)計(jì)如下
.text
.global _start
_start:
.code 32
mov r0, #0x9
nop
mov r1, #0
ldr r2,=myarray
loop:
ldr r3,[r2],#4
add r1,r1,r3
cmp r3,#0
bne loop
stop:
b stop
myarray:
.word 6
.word 24
.word 12
.word 0
.end
1.5.4?實(shí)驗(yàn)步驟
(1) 導(dǎo)入工程源碼
請參考第37章的導(dǎo)入已有工程章節(jié)。
光盤實(shí)驗(yàn)源碼路徑:【資料光盤\華清遠(yuǎn)見-FS-MP1A開發(fā)資料-2020-11-06\02-程序源碼\03-ARM體系結(jié)構(gòu)與接口技術(shù)\Cortex-A7\h_test】
1.5.5?實(shí)驗(yàn)現(xiàn)象
1、 單擊“

”單步,查看Rn寄存器的變化。

三個數(shù)據(jù)的和保存在R1中,最終R1的數(shù)值為42。
1.6?ARM 內(nèi)聯(lián)匯編實(shí)驗(yàn)
1.6.1?實(shí)驗(yàn)?zāi)康?/h1>
? 掌握 ARM匯編語言的基本使用和內(nèi)聯(lián)匯編用法;
? 熟悉 eclipse 開發(fā)工具建立匯編工程和仿真;
1.6.2?實(shí)驗(yàn)原理
根據(jù)上面闡述RAM 匯編語言的使用語法和功能,在C語言中編寫內(nèi)聯(lián)匯編代碼,實(shí)現(xiàn)得到兩個參數(shù)的最小公倍數(shù)。
1.6.3?實(shí)驗(yàn)內(nèi)容
實(shí)驗(yàn)程序設(shè)計(jì)如下
unsigned int gcd(unsigned int data1,unsigned int data2)
{
int arg1,arg2,result;
arg1 = data1;
arg2 = data2;
asm volatile
(
"mov r0, #0\n\t"
"mul r1,%1,%2\n\t"
"loop: \n\t"
"cmp %1,%2\n\t"
"subgt %1,%1,%2\n\t"
"sublt %2,%2,%1\n\t"
"bne loop\n\t"
"mov r2,%0\n\t"
"loop1: \n\t"
"cmp r1, #0\n\t"
"subgt r1, r1,r2\n\t"
"addgt r0, r0,#1\n\t"
"bne loop1\n\t"
"mov %0, r0\n\t"
: "=r" (result) //輸出
: "r"(arg1),"r"(arg2) //輸入
// : "r3"
);
return result;
}
int main(void)
{
unsigned long result;
unsigned int a = 6,b = 8;
result = gcd(a,b);
printf("Result:%d\r\n", result);
return 0;
}
1.6.4?實(shí)驗(yàn)步驟
(1) 導(dǎo)入工程源碼
相關(guān)內(nèi)容請參考Cortex-A7開發(fā)環(huán)境搭建章節(jié)導(dǎo)入已有工程。
光盤實(shí)驗(yàn)源碼路徑:【資料光盤\華清遠(yuǎn)見-FS-MP1A開發(fā)資料-2020-11-06\02-程序源碼\03-ARM體系結(jié)構(gòu)與接口技術(shù)\Cortex-A7\h_inline】
1.6.5?實(shí)驗(yàn)結(jié)果
1、 點(diǎn)擊“

”單步,查看result結(jié)果。

如圖所示,6和8的最小公倍數(shù)為24,結(jié)果正確。
硬件平臺:華清遠(yuǎn)見FS-MP1A開發(fā)板(STM32MP157)
部分開發(fā)教程下載:加QQ群483143191,群文件里有。
部分視頻課程收看:華清遠(yuǎn)見研發(fā)中心的個人空間_嗶哩嗶哩_Bilibili
淘寶購買鏈接:https://item.taobao.com/item.htm?id=622457259672
手機(jī)淘寶分享碼:復(fù)制本行文字打開手淘?T4FPXn3YYJ2?