ARM-GUN彙編簡介

一只心耳發表於2024-03-30

目錄
  • 一、GUN 彙編書寫格式
    • 1. 標號(Label)
    • 2. 操作碼(Opcodes)
    • 3. 運算元(Operands)
    • 4. 註釋(Comments)
  • 二、GUN 彙編常用偽指令
    • 1. 系統預定義的段
    • 2. 常量的定義
    • 3. 資料定義
    • 4. 條件偽指令
    • 5. 檔案包含偽指令
    • 6. 其他常用偽指令

​ 人們利用助記符代替機器指令的操作碼,用標號代替指令及運算元的地址,這就形成了組合語言( Assembly Language)。 它是介於機器語言與高階語言之間的計算機語言,被人們稱為第二代計算機語言。

​ 用匯編語言寫成的程式不能直接放入計算機內部的儲存器中去執行,必須先轉為機器語言。把用匯編語言寫成的源程式“翻譯”成機器語言的工具稱為彙編程式或彙編器( Assembler),以下統稱作彙編器。

​ 組合語言源程式可以用通用的文字編輯軟體編輯,以文字形式存檔。不同的彙編器中的組合語言源程式的格式有所區別。同時,彙編器除了識別計算機指令系統外,為了能夠正確地產生目的碼以及方便組合語言的編寫,還提供了一些在彙編時使用的命令和運算子號。在編寫彙編程式時,也必須正確使用它們。由於彙編器提供的指令僅是對把組合語言源程式“翻譯”成機器碼工作的輔助,並不產生執行階段執行的機器碼,因此這些指令被稱為偽指令( Pseudo Instruction)。

一、GUN 彙編書寫格式

組合語言源程式以行為單位進行設計,每一行最多可以包含以下 4 部分。

標號: 操作碼 運算元 註釋

1. 標號(Label)

​ 標號表示地址位置,是可選的。有些指令的前面可能會有標號,透過這個標號可以得到指令的地址,如在跳轉語句轉移的位置前就應該加標號。標號也可以用於表示資料地址,如在字串前可以放一個標號來表示該字串的首地址,這個標號有點類似 C 語言中的陣列名。

對於標號有下列要求及說明。

  1. 如果一個語句有標號,則標號必須書寫在彙編語句的開頭部分;標號後必須帶冒號 “:”;標號是區別字母大小寫的,但指令不區分字母大小寫;一個標號在一個檔案(程式)中只能定義一次,否則其會被視為重複定義,不能透過彙編;一行語句只能有一個標號,代表當前指令的儲存地址。
  2. 可以組成標號的字元有英文大小寫字母、數字 0~9、下劃線 “_”、美元符號 “$”,但第一個符號不能為數字或 $。
  3. 標號長度基本上不受限制,但實際使用時最好不要超過 20 個字元。若希望標號能被更多的彙編器識別,建議標號(或變數名)的長度小於 8 個字元。

2. 操作碼(Opcodes)

​ 操作碼可以是指令和偽指令, 其中偽指令是指 Arm-GUN 彙編器可以識別的偽指令。對於有標號的行,必須用至少一個空格或製表符(即 TAB 鍵)將標號與操作碼隔開。對於沒有標號的行,不能從第一列開始寫指令碼,應以空格或製表符開頭。彙編器不區分操作碼中字母的大小寫

3. 運算元(Operands)

​ 運算元可以是地址、標號或指令碼定義的常數,也可以是偽運算子構成的表示式。若一條指令或偽指令有運算元,則運算元與操作碼之間必須用空格隔開書寫。運算元多於一個時,運算元之間用英文逗號 “,” 分隔。運算元中一般都有一個存放結果的暫存器,這個暫存器位於運算元的最前面。

Arm-GUN 彙編器識別的偽運算子

運算子 功能 型別 例項
+ 加法 二元 mov r3,#30+40 等價於 mov r3,#70
- 減法 二元 mov r3,#40-30 等價於 mov r3,#10
* 乘法 二元 mov r3,#5*4 等價於 mov r3,#20
/ 除法 二元 mov r3,#20/4 等價於 mov r3,#5
% 取模 二元 mov r3,#20%7 等價於 mov r3,#6
|| 邏輯或 二元 `mov r3,#1
&& 邏輯與 二元 mov r3,#1&&0 等價於 mov r3,#0
<< 左移 二元 mov r3,#4<<2 等價於 mov r3,#16
>> 右移 二元 mov r3,#4>>2 等價於 mov r3,#1
^ 按位異或 二元 mov r3,#4^6 等價於 mov r3,#2
& 按位與 二元 mov r3,#4^2 等價於 mov r3,#0
| 按位或 二元 `mov r3,#4
== 等於 二元 mov r3,#1==0 等價於 mov r3,#0
!= 不等於 二元 mov r3,#1!=0 等價於 mov r3,#1
<= 小於等於 二元 mov r3,#1<=0 等價於 mov r3,#0
>= 大於等於 二元 mov r3,#1>=0 等價於 mov r3,#1
+ 正號 一元 mov r3,#+1 等價於 mov r3,#1
- 負號 一元 ldr r3,= -325 等價於 ldr r3,=0xfffffebb
~ 取反運算 一元 ldr r3,=~325 等價於 ldr r3,= 0xfffffeba
> 大於 一元 mov r3,#1>0 等價於 mov r3,#1
< 小於 一元 mov r3,#1<0 等價於 mov r3,#0

關於運算元,有以下幾點補充說明。

  1. 圓點 “.” 的用法。若圓點 “.” 單獨出現在語句的操作碼之後的運算元位置上,則代表當前程式計數器的值被放置在圓點的位置。例如:b . 指令代表轉向本身,相當於永久迴圈,在除錯時若希望程式停留在某個地方,則可以新增這種語句,除錯之後應刪除。
  2. 運算元中常數的進製表示:十進位制(預設不需要字首標識)、十六進位制(字首標識 0x)、二進位制(字首標識 0b)。
  3. 立即數的表示方法:常數前新增 “#” 時表示一個立即數,不加 “#” 時表示一個地址;當立即數大於或等於 256 時, 若使用 LDR 指令, 則立即數前應加 “=”。需要注意的是,初學時常常會將立即數前的 “#” 遺漏,彙編器識別不到這種錯誤,且只有在只能使用立即數的指令時,彙編器才會提示錯誤。
mov r3, 1 //給暫存器 r3 賦值為 1(這個語句是錯誤的)

彙編時會提示 “immediate expression requires a # prefix – ' mov r3,1'”,故其應該改為:

mov r3,#1 //暫存器 r3 賦值為 1(這個語句是正確的)

4. 註釋(Comments)

​ 註釋即說明文字,是對彙編指令的作用和功能進行解釋,有助於對指令的理解,可以採用單行邊註釋和整行註釋,建議使用 “//” 引導。以 “/*” 開始和 “*/” 結束的註釋用於保留除錯時遮蔽語句行。

二、GUN 彙編常用偽指令

​ 不同整合開發環境下的偽指令不同。 偽指令書寫格式與所使用的彙編器有關,讀者可參照具體的工程樣例“照葫蘆畫瓢”。

​ 偽指令主要有常量、宏的定義、條件判斷、檔案包含等,在 Arm-GUN 下,所有的偽指令都是以 “.” 開頭的。

1. 系統預定義的段

.data 	//已初始化的資料段
.bss	//未初始化的資料段
.text	//程式碼段

​ 組合語言程式在經過彙編和連結之後,最終生成可執行檔案。.elf 可執行檔案是以段為單位來組織檔案的,通常劃分為 .text.data.bss 等段。 其中,.text 是隻讀的程式碼段,是程式存放的地方,實際程式碼儲存在 flash 區;.data可讀可寫的資料段,而 .bss 則是可讀可寫且沒有初始化的資料段,啟動時會清零,兩者都是用來存放變數的,實際資料儲存在 RAM 區。這些段分別從哪個地址開始,這在連結檔案中會指明。

2. 常量的定義

.equ(或.set) 常量名,表示式

​ 在彙編程式中使用常量定義,能夠提高程式程式碼的可讀性,並且可使程式碼維護更加簡單。常量的定義可以使用 .equ.set 偽指令。

下面是 GNU 彙編器的一個常量定義的例子。

.equ _NVIC_ICER,0xE000E180	//定義常量名_NVIC_ICER=0xE000E180
LDR R0,=_NVIC_ICER 			//將常量名_NVIC_ICER的值0xE000E180放R0中
.set ROM_size,128*102 		//定義常量 ROM_size

3. 資料定義

資料定義偽指令

資料型別 長度 舉例 備註
.word 字(4 位元組) .word 0x12345678 定義多個資料時,資料間用 “,” 隔開
.hword 半字(2 位元組) .hword 0x1234 定義多個資料時,資料間用 “,” 隔開
.byte 位元組(1 位元組) .byte 0x12 定義多個資料時,資料間用 “,” 隔開
.ascii 字串 .ascii “hello\n\0” 定義的字串不以 “\0” 結尾,要自行新增 “\0”
.asciz.string 字串 .asciz “hello\n” 定義的字串以 “\0” 結尾

在定義一個資料型別之前,一般會給一個標號(相當於 C 語言中的變數名或陣列名),該標號表示這個資料的起始地址,然後利用這個標號透過直接定址方式就可以訪問到這個資料,具體用法如下。

LDR R3,=NUMNER 			//得到 NUMNER 的儲存地址
LDR R4,[R3] 			//將 0x123456789 讀到 R4 中
	……
	LDR R0,=HELLO_TEXT 	//得到 HELLO_TEXT 的起始地址
	BL PrintText 		//呼叫 PrintText 函式以顯示字串
	……
	ALIGN 4
NUMNER:
	.word 0x123456789
HELLO_TEXT:
	.asciz "hello\n" 	//以'\0'結束的字元

4. 條件偽指令

.ifdef 表示式 	  //當表示式為真時,執行程式碼 1
	程式碼 1
.else 			//否則,表示表示式為假,執行程式碼 2
	程式碼 2
.endif

.if 條件偽指令後面緊跟著一個恆定的表示式(即該表示式的值為真),並且最後要以 .endif 結尾。中間如果有其他條件,可以用 .else 填寫彙編語句。

5. 檔案包含偽指令

.include "filename"

​ 其中,filename 是一個檔名(以 .s 或 .inc 為副檔名),可以包含檔案的絕對路徑或相對路徑,建議同一工程的相關檔案統一放到同一個資料夾中,更多的時候使用相對路徑。

​ 類似高階語言中的檔案包含一樣,在組合語言中也可以使用 “.include” 進行檔案包含。 .include 是一個附加檔案的連結指示命令,利用它可以把另一個原始檔插入當前的原始檔一起彙編,成為一個完整的源程式。

6. 其他常用偽指令

除了上述的偽指令外,GNU 彙編還有其他常用的偽指令。

  1. .section 偽指令:使用者可以透過 .section 偽指令來自定義一個段

    • 格式:.section <段名>{,"<標誌>"}

    其中,標誌可選 a(允許段)、 w(可寫段)和 x(執行段)。

    .section .isr_vector,"a" 	//定義一個.isr_vector 段,"a"表示允許段
    
  2. .global 偽指令:可以用來定義一個全域性符號

    • 格式:.global symbol
    .global main 	//定義一個全域性符號main
    
  3. .extern 偽指令:宣告一個外部函式,呼叫的時候可以遍訪所有檔案以找到該函式,並且使用它。

    • 格式:.extern symbol
    .extern main 	//宣告 main 為外部函式
    bl main 		//呼叫 main 函式
    
  4. .align 偽指令:可以透過新增填充位元組,使當前位置滿足一定的對齊方式

    • 格式:.align [exp[,fill]]

    其中,exp 的取值必須是 2 的冪指數,20~231 都是合法的, 表示下一條指令或資料對齊至 exp 個位元組地址。若未指定,則將當前位置對齊到下一個字的位置,fill 指出為對齊而填充的位元組值,其可省略,預設為 0x00。

    .align 2 	//確保下一條指令或資料對齊到2位元組地址
    
  5. .end 偽指令:宣告彙編檔案的結束

相關文章