[ARM][汇编] 01.基础概念
目录
1.全局标号
1.1.使用方法
1.1.1.声明全局标号
1.1.2.定义全局标号
1.1.3.引用全局标号
1.2.全局标号与局部标号的区别
1.3.注意事项
2.局部标号
2.1.使用方法
2.1.1.定义局部标号
2.1.2.跳转引用
2.2.局部标号与全局标号的对比
2.3.注意事项
3.符号定义伪指令
3.1.变量与数据定义
3.1.1.定义字节 - DCB
3.1.2.定义字 - DCW
3.1.3.定义双字 - DCD
3.2.内存分配与对齐
3.2.1.预留内存空间 - SPACE
3.2.2.内存对齐 - ALIGN
3.2.3.设置起始地址 - ORG
3.3.符号重定义与别名
3.3.1.定义常量 - EQU
3.3.2.定义变量 - SET
3.3.3.宏定义 - MACRO、MEND
3.4.代码段与数据段控制
3.4.1.定义段 - AREA
3.5.外部符号引用
3.5.1.声明外部符号 - IMPORT
3.5.2.导出符号 - EXPORT
3.6. 注意事项
4.程序控制伪指令
4.1.程序入口 - ENTRY
4.2.程序结束 - END
5.条件编译伪指令
1.全局标号
ARM 汇编中,全局标号是可在整个程序范围内被引用的标识符,代表了内存中的一个特定地址,能够指向代码中的某条指令,也可以指向数据段里的某个数据项,全局标号通过 .global 或 .globl 伪指令来声明,其作用如下:
-
跨文件引用:大型项目中,程序往往会被分割成多个源文件,全局标号能让不同源文件之间相互引用特定的代码或数据,从而实现模块化编程。
-
链接器识别:链接器在将多个目标文件链接成一个可执行文件时,会利用全局标号来确定不同模块之间的跳转地址和数据引用,保证程序的正确执行。
1.1.使用方法
1.1.1.声明全局标号
使用 .global 或 .globl 伪指令将一个标号 <label> 声明为全局标号,语法如下:
@ 方式一:
.global <label>@ 方式二:
.globl <label>
1.1.2.定义全局标号
全局标号声明后,就可以在代码中定义它,例如,以下代码定义了一个名为 _start 的全局标号,它通常作为程序的入口点:
@ 声明全局标号 _start全局标号
.global _start@ _start 定义
_start:MOV R0, #1MOV R7, #1SWI 0
1.1.3.引用全局标号
在其他文件或代码段中,可直接引用已经声明的全局标号,例如,在另一个文件中调用上述代码中的 _start 标号:
.extern _start @ 引用全局标号 _startB _start @ 跳转至该全局标号处执行
其中 .extern 伪指令用于声明外部全局标号,表示该标号在其他文件中定义
1.2.全局标号与局部标号的区别
-
作用域:
-
全局标号:作用域是整个程序,可以在任何文件或者代码段中被引用。
-
局部标号:作用域仅限于当前文件或代码段,不能在其他文件中被引用。
-
-
声明方式:
-
全局标号:需要使用 .global 或 .globl 伪指令进行声明。
-
局部标号:直接在代码中定义,无需额外的声明指令。
-
1.3.注意事项
-
命名冲突:使用全局标号时,要确保标号名称在整个程序中是唯一的,避免出现命名冲突,否则,链接器在链接过程中会报错。
-
标号类型:全局标号既可以指向代码,也可以指向数据,在引用标号时,要明确其类型,确保正确使用。
-
链接顺序:在链接多个目标文件时,链接器会按照指定的顺序处理这些文件,要保证引用全局标号的文件在链接时能够找到定义该标号的文件。
2.局部标号
ARM 汇编中,局部标号是一种仅在特定范围内有效的标号,与全局标号相对。局部标号仅能在当前文件或代码段内被引用,它代表内存中的一个特定地址,可指向代码中的某条指令,也可指向数据段里的某个数据项,局部标号有如下特点:
-
作用域局限:局部标号的作用范围仅限于定义它的文件或代码段,在其他文件中无法引用,这使得局部标号具有较好的封装性,能避免不同文件之间的标号命名冲突。
-
使用灵活:局部标号通常用于标记代码中的临时位置,比如循环的起始和结束位置、条件分支的跳转点等,方便代码的编写和维护。
-
无需额外声明:和全局标号不同,局部标号不需要使用 .global 或 .globl 伪指令声明,直接在代码中定义即可。
2.1.使用方法
2.1.1.定义局部标号
局部标号由标号名和一个 : 组成,以下代码定义了一个名为 fun_1 的局部标号:
fun_1:MOV R0, #10CMP R0, #20BLE fun_1b ; 跳转到标号 1 处(向后跳转)
在这个例子中,fun_1 就是一个局部标号,fun_1b 表示向后跳转到标号 fun_1 处。
2.1.2.跳转引用
局部标号主要用于跳转指令,通过指定标号和跳转方向(向前或向后)实现代码的跳转
跳转方向通过 f(forward,向前)或 b(backward,向后)指定,例如:
MOV R1, #0
fun_2:ADD R1, R1, #1CMP R1, #8BLT fun_2b ; 若 R1 < 8,向后跳转到标号 fun_2 处继续执行
上面的代码中,当 R1 中的值小于 8 时,向后跳转至标号 fun_2 处继续执行
MOV R0, #15CMP R0, #8BGT fun_1f ; 如果 R0 大于 8,向前跳转到标号 fun_3 处继续执行MOV R1, #20
fun_3:ADD R2, R0, R1MOV R7, #1SWI 0 ; 退出程序
上面的代码中,当 R0 中的值大于 8 时,向前跳转至标号 fun_3 处继续执行
2.2.局部标号与全局标号的对比
-
作用域:
-
全局标号:作用域是整个程序,可以在任何文件或代码段中被引用。
-
局部标号:作用域仅限于当前文件或者代码段,不能在其他文件中被引用。
-
-
声明方式:
-
全局标号:需要使用 .global 或 .globl 伪指令进行声明。
-
局部标号:直接在代码中定义,无需额外的声明指令。
-
-
使用场景:
-
全局标号:常用于定义程序的入口点、公共函数、全局变量等,方便不同文件之间的调用和引用。
-
局部标号:主要用于标记代码中的临时位置,如循环、分支等,方便代码的控制和跳转。
-
2.3.注意事项
-
标号重复使用:由于局部标号的作用域仅限于当前文件或代码段,同一个局部标号可以在不同的代码段中重复使用,但在同一个代码段中不能重复定义。
-
跳转方向:在使用局部标号进行跳转时,要明确指定跳转方向(f 或 b),否则可能会导致跳转错误。
3.符号定义伪指令
在汇编中,如果要定义变量,则需要用到特定的符号定义伪指令,同时,汇编中也提供了一些伪指令用于修改变量的值或者实现类似 C 语言中宏定义 #define 的功能
3.1.变量与数据定义
3.1.1.定义字节 - DCB
-
作用:在内存中定义一个或多个字节(8 位)数据,可用于存储字符、数值或二进制数据:
label: DCB value1, value2, ...
-
示例:
; 定义字符串(ASCII 码)
message: DCB 'H', 'e', 'l', 'l', 'o', 0; 定义数值数组
data_array: DCB 10, 20, 30, 40, 50; 定义二进制数据
flags: DCB 0x01, 0x02, 0x04, 0x08
3.1.2.定义字 - DCW
-
作用:定义 16 位数据(半字),常用于存储短整数或 16 位常量:
label: DCW value1, value2, ...
-
示例:
; 定义 16 位整数数组
short_nums: DCW 1000, 2000, 3000; 定义 16 位常量
max_value: DCW 0xFFFF
3.1.3.定义双字 - DCD
-
作用:定义 32 位数据(双字),常用于存储整数、地址或指针:
label: DCD value1, value2, ...
-
示例:
; 定义 32 位整数数组
int_array: DCD 1, 2, 3, 4, 5; 定义地址常量
device_addr: DCD 0x40000000; 定义函数指针
func_ptr: DCD my_function ; 指向 my_function 函数的地址
3.2.内存分配与对齐
3.2.1.预留内存空间 - SPACE
-
作用:在内存中预留指定字节数的未初始化空间,常用于创建缓冲区:
label: SPACE size_in_bytes
-
示例:
; 预留 100 字节的缓冲区
buffer: SPACE 100; 预留 4 字节空间(初始值未定义)
temp_var: SPACE 4
3.2.2.内存对齐 - ALIGN
-
作用:强制后续代码或数据按指定字节对齐(如 2、4、8 字节),优化内存访问效率:
.ALIGN alignment ; alignment 为 2 的幂(如 2、4、8)
-
示例:
; 按 4 字节对齐(地址为 4 的倍数)
.ALIGN 4
my_table: DCD 1, 2, 3, 4 ; 确保起始地址是 4 的倍数
3.2.3.设置起始地址 - ORG
-
作用:指定后续代码或数据在内存中的起始地址,用于定位特定区域:
ORG 0x10000 ; 从地址 0x10000 开始放置数据
config_data:DCB 0x01, 0x02 ; 配置数据将位于 0x10000
3.3.符号重定义与别名
3.3.1.定义常量 - EQU
-
作用:为常量或表达式定义符号名,类似 C 语言的 #define:
symbol EQU expression
-
示例:
; 定义数值常量
MAX_SIZE EQU 100
TIMEOUT EQU 5000; 定义寄存器别名
GPIO_BASE EQU 0x40020000
LED_PIN EQU 5; 使用表达式
DELAY_VAL EQU MAX_SIZE * 2
3.3.2.定义变量 - SET
-
作用:定义可修改的符号值,类似变量,但在汇编时确定具体值:
symbol SET expression
-
示例:
count SET 0 ; 初始值为 0
count SET count+1 ; 递增(汇编时计算)
3.3.3.宏定义 - MACRO、MEND
-
作用:定义可复用的代码片段,类似函数但在汇编时展开:
MACRO
$label macro_name $param1, $param2... ; 宏体(使用 $param1 等参数)
MEND
-
示例:
MACRO
$label DELAY $countMOV r0, #$count
delay_loop:SUBS r0, r0, #1BNE delay_loop
MEND; 使用宏
DELAY 100 ; 插入 100 次循环的延迟代码
3.4.代码段与数据段控制
3.4.1.定义段 - AREA
-
作用:将代码或数据分组到不同的内存区域(如 .text、.data、.bss):
AREA name, attributes
-
示例:
; 代码段(只读、可执行)
AREA my_code, CODE, READONLY
ENTRY ; 程序入口点
MOV r0, #1 ; 代码指令; 数据段(已初始化数据)
AREA my_data, DATA, READWRITE
my_variable DCD 100 ; 初始值为 100; BSS 段(未初始化数据,仅占位)
AREA my_bss, NOINIT, READWRITE
buffer SPACE 1024 ; 预留 1024 字节未初始化空间
3.5.外部符号引用
3.5.1.声明外部符号 - IMPORT
-
作用:声明当前文件中使用的、但定义在其他文件中的符号(如函数、变量):
IMPORT printf ; 声明 printf 函数在其他文件中定义...LDR r0, =messageBL printf ; 调用外部函数
3.5.2.导出符号 - EXPORT
-
作用:声明当前文件中定义的符号可被其他文件引用(类似 C 语言的 extern):
EXPORT my_function ; 导出函数供其他文件使用
my_function:...
3.6. 注意事项
-
符号作用域:在 AREA 内定义的符号仅在该区域可见,跨区域需使用全局声明(如 .global)。
-
对齐要求:某些架构(如 ARM)要求特定类型的访问(如 32 位数据)必须按 4 字节对齐,否则会触发异常。
-
汇编时计算:EQU 和 SET 的值在汇编时确定,无法在运行时修改。
4.程序控制伪指令
4.1.程序入口 - ENTRY
伪指令 ENTRY 标记程序的起始执行地址,通常位于 .text 段:
AREA MyCode, CODE, READONLY
ENTRY ; 程序从此处开始执行B main ; 跳转到主函数
4.2.程序结束 - END
程序结束伪指令 END 标记汇编程序结束,告诉汇编器停止处理后续代码:
END
5.条件编译伪指令
IF、ELSE、ENDIF 使程序可以根据条件选择性地包含或排除代码段,类似 C 语言的 #ifdef:
IF :DEF: DEBUG ; 如果定义了 DEBUG 符号MOV r0, #1 ; 执行这段代码
ELSEMOV r0, #0 ; 若程序中未定义 DEBUG 符号则执行这段代码
ENDIF