基于stm32mp157 linux開發(fā)板ARM裸機(jī)開發(fā)教程6:ARM 匯編語言程序設(shè)計(jì)(連載中)
前言:
目前針對(duì)ARM Cortex-A7裸機(jī)開發(fā)文檔及視頻進(jìn)行了二次升級(jí)持續(xù)更新中,使其內(nèi)容更加豐富,講解更加細(xì)致,全文所使用的開發(fā)平臺(tái)均為華清遠(yuǎn)見FS-MP1A開發(fā)板(STM32MP157開發(fā)板)
針對(duì)對(duì)FS-MP1A開發(fā)板,除了Cortex-A7裸機(jī)開發(fā)篇外,還包括其他多系列教程,包括Cortex-M4開發(fā)篇、FreeRTOS篇、Linux基礎(chǔ)及應(yīng)用開發(fā)篇、Linux系統(tǒng)移植篇、Linux驅(qū)動(dòng)開發(fā)篇、硬件設(shè)計(jì)篇、人工智能機(jī)器視覺篇、Qt應(yīng)用編程篇、Qt綜合項(xiàng)目實(shí)戰(zhàn)篇等。除此之外計(jì)劃針對(duì)Linux系統(tǒng)移植篇、Linux驅(qū)動(dòng)開發(fā)篇均會(huì)進(jìn)行文檔及視頻的二次升級(jí)更新敬請(qǐng)關(guān)注!
開發(fā)板更多資料可關(guān)注華清遠(yuǎn)見在線實(shí)驗(yàn)室(微信號(hào):hqyjlab)領(lǐng)取``
ARM 匯編語言程序設(shè)計(jì)
GNU ARM 匯編器支持的偽操作
偽操作概述
在 ARM 匯編語言程序中,有一些特殊指令助記符,這些助記符與指令系統(tǒng)的助記符不同,沒有相對(duì)應(yīng)的操作碼,通常稱這些特殊指令助記符為偽操作標(biāo)識(shí)符(directive),它們所完成的操作稱為偽操作。偽操作在源程序中的作用是為了完成匯編程序做各種準(zhǔn)備工作的,這些偽操作僅在匯編過程中起作用,一旦匯編結(jié)束,偽操作的使命就完成。
在 ARM 的匯編程序中,偽操作主要有符號(hào)定義偽操作、數(shù)據(jù)定義偽操作、匯編控制偽操作及其雜項(xiàng)偽操作等。
數(shù)據(jù)定義(Data Definition)偽操作
數(shù)據(jù)定義偽操作一般用于為特定的數(shù)據(jù)分配存儲(chǔ)單元,同時(shí)可完成已分配存儲(chǔ)單元的初始化。常見的數(shù)據(jù)定義偽操作有.byte、.short、.long、.quad、.float、.string、.asciz、.ascii 和.rept。數(shù)據(jù)定義偽操作如下。

匯編控制偽操作用于控制匯編程序的執(zhí)行流程,常用的匯編控制偽操作包括以下幾條。
1、 .if、.else、.endif
語法格式
.if、.else、.endif 偽操作能根據(jù)條件的成立與否決定是否執(zhí)行某個(gè)指令序列。當(dāng).if 后面的邏輯表達(dá)式為真,則執(zhí)行.if 后的指令序列,否則執(zhí)行.else 后的指令序列。其中,.else 及其后指令序列可以沒有,此時(shí),當(dāng).if 后面的邏輯表達(dá)式為真,則執(zhí)行指令序列,否則繼續(xù)執(zhí)行后面的指令。
提示:
.if、.else、.endif 偽指令可以嵌套使用。
語法格式如下:
示例代碼 46-1 使用示例
用于決定指令執(zhí)行流程的邏輯表達(dá)式。
當(dāng)程序中有一段指令需要在滿足一定條件時(shí)執(zhí)行,使用該指令。該操作還有另一種形式。
示例代碼 46-2 使用示例
該形式避免了 if-else 形式的嵌套,使程序結(jié)構(gòu)更加清晰、易讀。
2、 .macro、.endm
語法格式
.macro 偽操作可以將一段代碼定義為一個(gè)整體,稱為宏指令,然后就可以在程序中通過宏指令多次調(diào)用該段代碼。其中,$標(biāo)號(hào)在宏指令被展開時(shí),標(biāo)號(hào)會(huì)被替換為用戶定義的符號(hào)。
宏操作可以使用一個(gè)或多個(gè)參數(shù),當(dāng)宏操作被展開時(shí),這些參數(shù)被相應(yīng)的值替換。
宏操作的使用方式和功能與子程序有些相似,子程序可以提供模塊化的程序設(shè)計(jì)、節(jié)省存儲(chǔ)空間并提高運(yùn)行速度。但在使用子程序結(jié)構(gòu)時(shí)需要保護(hù)現(xiàn)場(chǎng),從而增加了系統(tǒng)的開銷,因此,在代碼較短且需要傳遞的參數(shù)較多時(shí),可以使用宏操作代替子程序。
包含在.macro 和.endm 之間的指令序列稱為宏定義體,在宏定義體的第一行應(yīng)聲明宏的原型(包含宏名、所需的參數(shù)),然后就可以在匯編程序中通過宏名來調(diào)用該指令序列。在源程序被編譯時(shí),匯編器將宏調(diào)用展開,用宏定義中的指令序列代替程序中的宏調(diào)用,并將實(shí)際參數(shù)的值傳遞給宏定義中的形式參數(shù)。
提示:
.macro、.endm 偽操作可以嵌套使用。
語法格式如下:
示例代碼 46-3 使用示例
參數(shù)說明
{$label}:$標(biāo)號(hào)在宏指令被展開時(shí),標(biāo)號(hào)會(huì)被替換為用戶定義的符號(hào)。通常,在一個(gè)符號(hào)前使用
“$”表示該符號(hào)被匯編器編譯時(shí),使用相應(yīng)的值代替該符號(hào)。
{macroname}:所定義的宏的名稱。
{parameter}:宏指令的參數(shù)。當(dāng)宏指令被展開時(shí)將被替換成相應(yīng)的值,類似于函數(shù)中的參數(shù)。
示例如下:
示例代碼 46-4 使用舉例
.exitm 用于從宏定義中跳轉(zhuǎn)出去,只需要在宏定義的代碼中插入該指令即可。
在子程序代碼比較短,而需要傳遞的參數(shù)比較多的情況下可以使用宏匯編技術(shù)。
首先通過.macro 和.endm 偽操作定義宏,包括宏定義體代碼。在.macro 偽操作之后的第一行聲明宏的原型,其中包含該宏定義的名稱及需要的參數(shù)。在匯編中可以通過該宏定義的名稱來調(diào)用它。當(dāng)源程序被編譯時(shí),匯編器將展開每個(gè)宏調(diào)用,用宏定義體代替源程序中宏定義的名稱,并用實(shí)際參數(shù)值代替宏定義時(shí)的形式參數(shù)。
3、 .雜項(xiàng)偽操作
ARM 匯編中還有一些其他的偽操作,在匯編程序中經(jīng)常會(huì)被使用,包括以下幾條。

ARM 匯編器支持的偽指令
ARM 匯編器支持 ARM 偽指令,這些偽指令在匯編階段被翻譯成 ARM 或者 Thumb(或 Thumb-2)指令(或指令序列)。ARM 偽指令包含 ADR、ADRL、LDR 等。
ADR 偽指令
語法格式
ADR 偽指令為小范圍地址讀取偽指令。ADR 偽指令將基于 PC 相對(duì)偏移地址或基于寄存器相對(duì)偏移地址值讀取到寄存器中,當(dāng)?shù)刂分凳亲止?jié)對(duì)齊時(shí),取值范圍為?255~255,當(dāng)?shù)刂分凳亲謱?duì)齊時(shí),取值范圍為?1020~1020。當(dāng)?shù)刂分凳?16 字節(jié)對(duì)齊時(shí)其取值范圍更大。
語法格式如下:
ADR{c}{.W} register,label
{c}:可選的指令執(zhí)行條件。
{.W}:可選項(xiàng)。指定指令寬度(Thumb-2 指令集支持)。
{register}:目標(biāo)寄存器。
{label}:基于 PC 或具有寄存器的表達(dá)式。
使用說明
ADR 偽指令被匯編器編譯成一條指令。匯編器通常使用 ADD 指令或 SUB 指令來實(shí)現(xiàn)偽操作的地址裝載功能。如果不能用一條指令來實(shí)現(xiàn) ADR 偽指令的功能,匯編器將報(bào)告錯(cuò)誤。
示例
示例代碼 46-5 使用舉例
LDR 偽指令
語法格式
LDR 偽指令裝載一個(gè) 32 位的常數(shù)和一個(gè)地址到寄存器。
語法格式如下:
LDR{cond}{.W} register,=[expr|label-expr]
{c}:可選的指令執(zhí)行條件。
{.W}指定指令寬度(Thumb-2 指令集支持)。
{register}:目標(biāo)寄存器。
{expr}:32 位常量表達(dá)式。匯編器根據(jù) expr 的取值情況,對(duì) LDR 偽指令做如下處理。
① 當(dāng) expr 表示的地址值沒有超過 MOV 指令或 MVN 指令的地址取值范圍時(shí),匯編器用一對(duì) MOV 和MVN 指令代替 LDR 指令。
② 當(dāng) expr 表示的指令地址值超過了 MOV 指令或 MVN 指令的地址范圍時(shí),匯編器將常數(shù)放入數(shù)據(jù)緩存池,同時(shí)用一條基于 PC 的 LDR 指令讀取該常數(shù)。
{label-expr}
一個(gè)程序相關(guān)或聲明為外部的表達(dá)式。匯編器將 label-expr 表達(dá)式的值放入數(shù)據(jù)緩存池,使用一條程序相關(guān) LDR 指令將該值取出放入寄存器。
當(dāng) label-expr 被聲明為外部的表達(dá)式時(shí),匯編器將在目標(biāo)文件中插入鏈接重定位偽操作,由鏈接器在鏈接時(shí)生成該地址。
使用說明
當(dāng)要裝載的常量超出了 MOV 指令或 MVN 指令的范圍時(shí),使用 LDR 指令。
由 LDR 指令裝載的地址是絕對(duì)地址,即 PC 相關(guān)地址。
當(dāng)要裝載的數(shù)據(jù)不能由 MOV 指令或 MVN 指令直接裝載時(shí),該值要先放入數(shù)據(jù)緩存池,此時(shí) LDR 偽指令處的 PC 值到數(shù)據(jù)緩存池中目標(biāo)數(shù)據(jù)所在地址的偏移量有一定限制。ARM 或 32 位的 Thumb-2 指令中該范圍是?4~4KB,Thumb 或 16 位的 Thumb-2 指令中該范圍是 0~1KB。將常數(shù) 0xff0 讀到 r1 中
示例代碼 46-6 使用舉例
將常數(shù) 0xfff 讀到 R1 中
示例代碼 46-7 使用舉例
?將 place 標(biāo)號(hào)地址讀入 R1 中
?
示例代碼 46-8 使用舉例
ARM 匯編語言的程序結(jié)構(gòu)
匯編語言的程序格式
在 ARM(Thumb)匯編語言程序中可以使用.section 來進(jìn)行分段,其中每一個(gè)段用段名或者文件結(jié)尾為結(jié)束,這些段使用默認(rèn)的標(biāo)志,如 a 為允許段,w 為可寫段,x 為執(zhí)行段。
在一個(gè)段中,我們可以定義.text、.data、.bss 子段。由此我們可知道,段可以分為代碼段、數(shù)據(jù)段及其他存儲(chǔ)用的段,.text(正文段)包含程序的指令代碼;.data(數(shù)據(jù)段)包含固定的數(shù)據(jù),如常量、字符串;.bss(未初始化數(shù)據(jù)段)包含未初始化的變量、數(shù)組等,當(dāng)程序較長(zhǎng)時(shí),可以分割為多個(gè)代碼段和數(shù)據(jù)段,多個(gè)段在程序編譯鏈接時(shí)最終形成一個(gè)可執(zhí)行的映像文件。
示例代碼 46-9 使用舉例
過程調(diào)用標(biāo)準(zhǔn) AAPCS
為了使不同編譯器編譯的程序之間能夠相互調(diào)用,必須為子程序間的調(diào)用規(guī)定一定的規(guī)則。AAPCS 就是這樣一個(gè)標(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)告訴編譯器將源代碼編譯成符號(hào) AAPCS 調(diào)用標(biāo)準(zhǔn)的目標(biāo)代碼。
注意:
使用“--apcs”選項(xiàng)并不影響代碼的產(chǎn)生,編譯器只是在各段中放置相應(yīng)的屬性,標(biāo)識(shí)用戶選定的 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:編譯過程中對(duì)輸入文件使用堆棧檢測(cè)。
/noswstackcheck:編譯過程中對(duì)輸入文件不使用堆棧檢測(cè)。這是編譯器默認(rèn)選項(xiàng)。
/swstna:如果匯編程序?qū)τ谑欠襁M(jìn)行數(shù)據(jù)棧檢查無所謂,而與該匯編程序連接的其他程序指定了選項(xiàng)/swst 或選項(xiàng)/noswst,這時(shí)該匯編程序使用選項(xiàng)/swstna。
2、 ARM 寄存器使用規(guī)則
AAPCS 中定義了 ARM 寄存器使用規(guī)則如下:
子程序間通過寄存器 R0、R1、R2、 R3 來傳遞參數(shù)。如果參數(shù)多于 4 個(gè),則多出的部分用堆棧傳遞。被調(diào)用的子程序在返回前無須恢復(fù)寄存器 R0-R3 的內(nèi)容。
在子程序中,使用寄存器 R4-R11 來保存局部變量。如果在子程序中使用到了寄存器 R4-R11 中的某些寄存器,子程序進(jìn)入時(shí)必須保存這些寄存器的值,在返回前必須恢復(fù)這些寄存器的值;對(duì)于子程序中沒有用到的寄存器則不必進(jìn)行這些操作。在 Thumb 程序中,通常只能使用寄存器 R4-R7 來保存局部變量。
寄存器 R12 用做子程序間 scratch 寄存器(用于保存 SP,在函數(shù)返回時(shí)使用該寄存器出棧),記作 ip。在子程序間的連接代碼段中常有這種使用規(guī)則。
寄存器 R13 用做數(shù)據(jù)棧指針,記作 sp。在子程序中寄存器 R13 不能用做其他用途。寄存器 sp 在進(jìn)入子程序時(shí)的值和退出子程序時(shí)的值必須相等。
寄存器 R14 稱為連接寄存器,記作 lr。它用于保存子程序的返回地址。如果在子程序中保存了返回地址,寄存器 R14 則可以用做其他用途。
寄存器 R15 是程序計(jì)數(shù)器,記作 pc。它不能用做其他用途。
ARM 寄存器在函數(shù)調(diào)用過程中的保護(hù)規(guī)則,如圖所示。

3、 AAPCS 使用舉例
編寫一個(gè)簡(jiǎn)單的 c 程序,通過反匯編+單步調(diào)試,驗(yàn)證學(xué)習(xí) AAPCS 規(guī)則??梢源蜷_工程 c_AAPCS 工程,其中 main.c 內(nèi)容如下:
示例代碼 46-10 AAPCS 使用舉例
通過返回匯編窗口,可以看到,在 main 函數(shù)中,在調(diào)用 add 子函數(shù)前,將 R0=1,R1=2,R2=3,R3=4,并將 R0、R1、R2、R3 作為參數(shù)傳給了子函數(shù) add。

根據(jù) AAPCS 規(guī)則,add 子函數(shù),直接使用 R0,R1,R2,R3,并且不需要保護(hù)這幾個(gè)寄存器。但因?yàn)槭褂玫搅?R11,根據(jù)規(guī)則,需要保護(hù),所以對(duì) R11 進(jìn)行了入棧保護(hù)。最終的返回值通過 R0 傳輸。

思考:編程測(cè)試如果是 5 個(gè)參數(shù)的情況。
ARM 偽指令實(shí)驗(yàn)
實(shí)驗(yàn)?zāi)康?/span>
掌握 ARM 匯編語言的基本使用和一些偽指令的使用;
熟悉 eclipse 開發(fā)工具建立匯編工程和仿真;
?實(shí)驗(yàn)原理
根據(jù)上面闡述 RAM 匯編語言的使用語法和功能,編寫匯編程序,實(shí)現(xiàn)將存放在兩個(gè)內(nèi)存中的數(shù)據(jù)相加的操作。
實(shí)驗(yàn)內(nèi)容
匯編程序設(shè)計(jì)如下:
示例代碼 46-11 偽指令案例
實(shí)驗(yàn)步驟
導(dǎo)入工程源碼
請(qǐng)參考導(dǎo)入一個(gè)已有工程章的導(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】
?
實(shí)驗(yàn)現(xiàn)象
單擊“?如下圖標(biāo)”單步,查看 Rn 寄存器的變化。


三個(gè)數(shù)據(jù)的和保存在 R1 中,最終 R1 的數(shù)值為 42。
ARM 內(nèi)聯(lián)匯編實(shí)驗(yàn)
實(shí)驗(yàn)?zāi)康?/span>
掌握 ARM 匯編語言的基本使用和內(nèi)聯(lián)匯編用法;
熟悉 eclipse 開發(fā)工具建立匯編工程和仿真;
實(shí)驗(yàn)原理
GCC 內(nèi)聯(lián)匯編的一般格式:
asm(
代碼列表
:輸出運(yùn)算符列表
:輸入運(yùn)算符列表
:被更改資源列表
);
在代碼列表中,每個(gè)匯編語句都要用" "括起來。
?
示例代碼 46-12 內(nèi)聯(lián)匯編舉例
?案例詳解
在 C 代碼中嵌入?yún)R編需要使用 asm 關(guān)鍵字,用法 asm();
?" " 引號(hào)內(nèi)部包含的部分是指令部分
?: 參數(shù)輸出部分,函數(shù)的返回值
?: 參數(shù)輸入部分,函數(shù)的形參
?: 修飾列表,內(nèi)聯(lián)匯編的聲明部分,要被更改的資源
?"r" 用寄存器來保存參數(shù)
?"i" 是立即數(shù)
?"m" 一個(gè)有效的內(nèi)存地址
?"x" 只能做輸入
?+ 表示參數(shù)的可讀可寫
?不寫 表示參數(shù)只讀
?= 表示只寫
?& 只能做輸出
?%0 輸出列表和輸入列表的第 1 個(gè)成員
?%1 輸出列表和輸入列表的第 2 個(gè)成員
?%2 輸出列表和輸入列表的第 3 個(gè)成員
根據(jù)上面闡述ARM 匯編語言的使用語法和功能,在 C 語言中編寫內(nèi)聯(lián)匯編代碼,實(shí)現(xiàn)得到兩個(gè)參數(shù)的最小公倍數(shù)。
實(shí)驗(yàn)內(nèi)容
實(shí)驗(yàn)程序設(shè)計(jì)如下:
示例代碼 46-13 字節(jié)交換
實(shí)驗(yàn)步驟
導(dǎo)入工程源碼
相關(guān)內(nèi)容請(qǐng)參考導(dǎo)入一個(gè)已有工程章節(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】
實(shí)驗(yàn)結(jié)果
點(diǎn)擊“ 如下圖標(biāo)”單步,查看 result 結(jié)果。

