- 一、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 語言中的陣列名。
對於標號有下列要求及說明。
- 如果一個語句有標號,則標號必須書寫在彙編語句的開頭部分;標號後必須帶冒號 “:”;標號是區別字母大小寫的,但指令不區分字母大小寫;一個標號在一個檔案(程式)中只能定義一次,否則其會被視為重複定義,不能透過彙編;一行語句只能有一個標號,代表當前指令的儲存地址。
- 可以組成標號的字元有英文大小寫字母、數字 0~9、下劃線 “_”、美元符號 “$”,但第一個符號不能為數字或 $。
- 標號長度基本上不受限制,但實際使用時最好不要超過 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 |
關於運算元,有以下幾點補充說明。
- 圓點 “.” 的用法。若圓點 “.” 單獨出現在語句的操作碼之後的運算元位置上,則代表當前程式計數器的值被放置在圓點的位置。例如:
b .
指令代表轉向本身,相當於永久迴圈,在除錯時若希望程式停留在某個地方,則可以新增這種語句,除錯之後應刪除。 - 運算元中常數的進製表示:十進位制(預設不需要字首標識)、十六進位制(字首標識 0x)、二進位制(字首標識 0b)。
- 立即數的表示方法:常數前新增 “#” 時表示一個立即數,不加 “#” 時表示一個地址;當立即數大於或等於 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 彙編還有其他常用的偽指令。
-
.section
偽指令:使用者可以透過.section
偽指令來自定義一個段。- 格式:
.section <段名>{,"<標誌>"}
其中,標誌可選
a
(允許段)、w
(可寫段)和x
(執行段)。.section .isr_vector,"a" //定義一個.isr_vector 段,"a"表示允許段
- 格式:
-
.global
偽指令:可以用來定義一個全域性符號。- 格式:
.global symbol
.global main //定義一個全域性符號main
- 格式:
-
.extern
偽指令:宣告一個外部函式,呼叫的時候可以遍訪所有檔案以找到該函式,並且使用它。- 格式:
.extern symbol
.extern main //宣告 main 為外部函式 bl main //呼叫 main 函式
- 格式:
-
.align
偽指令:可以透過新增填充位元組,使當前位置滿足一定的對齊方式。- 格式:
.align [exp[,fill]]
其中,
exp
的取值必須是 2 的冪指數,20~231 都是合法的, 表示下一條指令或資料對齊至exp
個位元組地址。若未指定,則將當前位置對齊到下一個字的位置,fill
指出為對齊而填充的位元組值,其可省略,預設為 0x00。.align 2 //確保下一條指令或資料對齊到2位元組地址
- 格式:
-
.end
偽指令:宣告彙編檔案的結束。