Loading

嵌入式笔记2.2 ARM-GUN汇编简介

​ 人们利用助记符代替机器指令的操作码,用标号代替指令及操作数的地址,这就形成了汇编语言( 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. .syntax 伪指令:用于指定汇编器应该使用的语法格式

    在 ARM 汇编中,.syntax 伪指令通常与参数一起使用,以指定使用的语法格式。

    常见的语法格式包括:

    • .syntax unified:指定使用统一语法格式。这是 ARM 汇编的推荐语法格式,提供了更简洁和一致的语法。
    • .syntax divided:指定使用分离语法格式。在这种格式下,ARM 和 Thumb 指令使用不同的语法规则。

    使用 .syntax 伪指令可以在汇编代码中明确地指定所需的语法格式,确保代码的可读性和一致性。

  6. .thumb 是伪指令:用于指定所编写的汇编代码应该使用的指令集

    在 ARM 架构中,有两种主要的指令集:ARM 和 Thumb。

    • ARM 指令集:通常用于编写较为复杂的代码,指令长度为 32 位,提供了更多的功能和灵活性。
    • Thumb 指令集:通常用于编写较小和高效的代码,指令长度为 16 位,适用于资源受限的环境,如嵌入式系统。

    使用 .thumb 伪指令可以指示汇编器使用 Thumb 指令集来编译代码。示例使用方法如下:

    .thumb
    

    这将告诉汇编器接下来的代码应该使用 Thumb 指令集编译。在这种模式下,编写的指令将会被解释为 Thumb 指令,而不是 ARM 指令。

  7. .arm 伪指令:用于指定所编写的汇编代码应该使用的指令集

    当你使用 .arm 伪指令时,汇编器会被告知接下来的代码应该使用 ARM 指令集来编译。

    ARM 指令集通常用于编写较为复杂的代码,指令长度为 32 位,提供了更多的功能和灵活性。这种指令集适用于各种应用场景,包括嵌入式系统、移动设备和服务器等。

    示例使用方法如下:

    .arm
    

    这将告诉汇编器接下来的代码应该使用 ARM 指令集编译。在这种模式下,编写的指令将会被解释为 ARM 指令,而不是 Thumb 指令。

  8. .type 伪指令:用于指定符号的类型

    在汇编语言中,符号通常用于标识函数、变量或其他代码标签。.type 指令允许您明确地为这些符号指定类型。

    一般格式如下:

    .type symbol, type
    
    • symbol:符号的名称。
    • type:符号的类型,通常是以下之一:
      • function:用于表示符号是一个函数。
      • object:用于表示符号是一个对象或变量。
      • @function:用于表示符号是一个函数,但是不要求在当前文件中定义。
      • @object:用于表示符号是一个对象或变量,但是不要求在当前文件中定义。

    示例用法:

    .type my_function, function
    

    这会将 my_function 标记为一个函数类型的符号。这对于链接器来说很重要,因为它可以帮助链接器正确地处理符号,并在链接时找到正确的函数或变量。

  9. .end 伪指令:声明汇编文件的结束

posted @ 2024-03-30 18:53  一只心耳  阅读(43)  评论(0编辑  收藏  举报