汇编语言|常用伪指令(ARM 汇编入门)

前言 汇编语言分为指令语句 和伪指令语句。

  1. ++指令语句++:汇编后生成 CPU 可以直接执行的机器目标代码,由 CPU 运行。
  2. ++伪指令语句++ :不会产生任何机器码,CPU 不会执行伪指令。伪指令是给汇编器、链接器使用的控制命令,用来完成段划分、内存分配、符号别名定义、多文件之间互相调用等编译相关工作。

ARM 汇编书写硬性规范

  1. CPU 指令助记符,例如MOV、ADD、SUB、LDR、STR、BL、BX属于保留关键字,绝对不能作为自定义标号,就算加上冒号也会编译报错。
  2. **自定义标号后面必须加冒号 :**虽然 ARM 汇编语法允许省略冒号,但省略后极易把标号误认为指令,作业、考试一律书写冒号。
  3. CPU 指令、伪指令(AREA、ENTRY、EQU、DCD、SPACE等)后面不能加冒号。
  4. 尽量不要使用伪指令名称作为自定义标号,部分汇编器虽然可以编译通过,但可读性差,存在歧义风险。
  5. ARM 汇编符号区分大小写。

1. EQU 等值赋值伪指令

格式

复制代码
标号    EQU     操作数

功能 :给数字或者内存地址定义别名,本质是文本替换,不占用任何内存单元,整个源文件内该标号的值固定不变。

复制代码
;示例1:给内存地址0x1000设置别名
AREA_ADDR    EQU     1000H

;示例2:定义程序使用的数值常量
MAX_NUM     EQU     50
MASK        EQU     0x0F

;代码中直接使用别名,汇编器自动替换成对应数值
MOV  R0,#AREA_ADDR      ;等价于 MOV R0,#0x1000
MOV  R1,#MAX_NUM        ;等价于 MOV R1,#50

2. AREA 段定义伪指令(ARM 汇编专属)

格式

复制代码
AREA 段名,属性1,属性2......

功能:把程序划分成多个独立模块,称为 "段"。有的段存放执行代码,有的段存放变量数据;同时可以设置段的读写权限、内存对齐、是否初始化。

表格

属性 说明
CODE 存放 CPU 执行指令,默认属性为READONLY只读
DATA 存放变量与数据,默认属性为READWRITE可读可写
READONLY 内存只允许读取,不可修改
READWRITE 内存可读,允许修改写入
NOINIT 程序启动时,该段内存不做初始化
ALIGN=n 内存对齐字节 = \(2^n\);例如ALIGN=3代表 8 字节对齐
复制代码
;定义只读代码段,存放程序指令
AREA  CODE_SEG, CODE, READONLY
ENTRY
START:      MOV R0,#0

;定义可读可写数据段,存放运行时会修改的变量
AREA  DATA_SEG, DATA, READWRITE
DATA_BUF    SPACE  100

3. ENTRY 程序入口伪指令

格式

复制代码
ENTRY

功能:告诉链接器,CPU 上电复位后,从此标记位置开始执行程序。

注意:ENTRY 仅作为标记,不生成机器码;一个工程至少要有一个ENTRY,必须写在CODE代码段内部。

复制代码
AREA MY_CODE,CODE,READONLY
ENTRY                    ;伪指令,标记程序入口,无冒号
START:      MOV R1,#1
            ADD R2,R1,#2
END

4. ORG 地址定位伪指令(51、x86 汇编常用,ARM 很少使用)

格式

复制代码
ORG  绝对地址

功能 :修改汇编位置计数器,指定后续代码、数据存放的起始内存地址;直到遇见下一条ORG指令,地址才会重新改变。

复制代码
ORG  8000H         ;下面所有内容,从内存0x8000地址开始存放
ADD  R1,#1
MOV  R2,#2

ORG 0x2000         ;切换存储起始地址,后续数据存放在0x2000之后
DATA_BUF1: DB 0x11,0x22

5. DB / DW 数据定义伪指令

  • DB:定义1 字节(8bit) 数据,可以存放数字、ASCII 字符。 格式:[标号:] DB 字节常数 | 字符 | 表达式

  • DW:定义**16 位字(2 字节)**数据,用法与 DB 完全相同。

    ORG 8000H
    DATA1: DB 43H,09H,08H ;连续保存3个1字节数据
    DATA2: DB 07H

    MY_STR: DB 'A','B','C',0 ;定义ASCII字符串,末尾0作为字符串结束标记

    WORD_BUF: DW 0x1234,0x5678 ;DW,每一项占用2字节内存

⚠️注意:不要使用STR:作为标号,STR是 ARM 存储指令,属于保留关键字。

6. DCD / DCDU 32 位字分配伪指令(ARM 汇编)

格式

复制代码
标号    DCD / DCDU   表达式列表
  • DCD:分配连续 32 位(4 字节)内存单元,严格 4 字节字对齐。
  • DCDU:分配 32 位内存单元,不强制字对齐。

表达式既可以写数字,也可以写标号,用来保存标号对应的内存地址,中断向量表大量使用该伪指令。

复制代码
DATA_TEST: DCD 4,5,6        ;分配3个4字节空间,分别存入4、5、6
   
;向量表
VECTOR_TABLE:
    DCD 0x20000000
    DCD Reset_Handler       ;存储标号Reset_Handler的内存地址

UNALIGN_DATA: DCDU 0x1111,0x2222

7. SPACE 内存空间分配伪指令

格式

复制代码
标号    SPACE    字节数

简写符号 % 等价于 SPACE(单位:字节)

功能:划出一块连续内存区域,分配的内存全部初始化为 0;多用于缓冲区、栈空间。

复制代码
DATA_SPACE:  SPACE 1000    ;分配1000字节内存,全部清零
STACK_BUF:   SPACE 256     ;分配256字节栈内存

BUF2:        %  64         ;简写写法,分配64字节并清零

8. PRESERVE8 堆栈 8 字节对齐伪指令(ARM 汇编)

格式

复制代码
PRESERVE8 {FALSE}

功能:通知链接器当前文件堆栈是否保持 8 字节对齐。

汇编语言和 C 语言互相调用时,必须开启该伪指令;如果不满足 8 字节对齐,程序运行会发生异常崩溃。

复制代码
PRESERVE8           ;开启堆栈8字节对齐,C与汇编混编必备
;PRESERVE8 {FALSE}  ;关闭堆栈8字节对齐

9. EXPORT、EXTRN、IMPORT 多文件模块伪指令

应用场景

实际项目代码量很大,不会把全部代码写在一个汇编文件里。 举例:

  • 文件A.s:写了一个子程序_STARTUP
  • 文件B.s:需要调用A.s里面已经写好的_STARTUP子程序

两个.s文件是独立的,单独汇编的时候,互相看不到对方内部定义的标号。 需要依靠EXPORT、EXTRN/IMPORT,打通多个汇编文件之间的符号访问。

EXPORT:将本文件的标号对外公开

在 A.s 中,使用 EXPORT 把内部标号公开,其他汇编文件才可以访问调用该标号;如果不写 EXPORT,标号只在当前文件内部有效,外部不可见。
文件:A.s

复制代码
;A.s完整内容
AREA CODE,CODE,READONLY

EXPORT _STARTUP      ;对外公开标号_STARTUP,允许别的文件访问

_STARTUP:            ;A.s内部自定义的子程序入口标号
    MOV R0,#0
    BX LR            ;子程序执行完毕,返回调用处

EXTRN:声明标号来自外部其他文件

B.s 想要调用 A.s 的_STARTUP,但是 B.s 没有定义_STARTUP。

汇编编译 B.s 时,汇编器会报 "找不到符号" 错误。

使用EXTRN告诉汇编器:这个标号不是本文件定义的,定义在别的文件,等链接阶段再去找真实地址。
文件:B.s

复制代码
;B.s完整内容
AREA CODE,CODE,READONLY

EXTRN _STARTUP:CODE   ;声明:_STARTUP是外部的代码标号,不在本文件定义

ENTRY
START:
    BL _STARTUP       ;BL指令:调用子程序,调用A.s中的_STARTUP
    MOV R1,#2

END

完整工作步骤

  1. A.s:编写子程序_STARTUP:,使用EXPORT _STARTUP对外公开符号。
  2. B.s:使用EXTRN _STARTUP:CODE声明该标号属于外部。
  3. 汇编器分别编译 A.s、B.s,生成A.o、B.o两个目标文件。
  4. 链接器 把 A.o 与 B.o 合并。链接器发现 B 要调用_STARTUP,就到 A.o 里面找到_STARTUP真实内存地址,填入 B 的机器码中。

IMPORT 外部符号导入(ARM 常用)

IMPORT功能和 EXTRN 接近,ARM 汇编更习惯使用 IMPORT。 语法:IMPORT 标号 {[WEAK]}

  • [WEAK]弱符号:就算整个工程找不到这个标号的定义,链接器也不会报错。

    AREA INIT,CODE,READONLY
    IMPORT Main ;Main函数写在C语言文件,汇编中声明这是外部标号

    ENTRY
    START: B Main ;跳转到C语言的Main函数

    IMPORT Err_Handler [WEAK]
    B Err_Handler

10. CODE16 / CODE32 指令集声明伪指令

格式

复制代码
CODE16
CODE32
  • CODE32:告诉汇编器,后续代码编译为 32 位 ARM 指令。
  • CODE16:告诉汇编器,后续代码编译为 16 位 Thumb 指令。

⚠️重要提醒:该伪指令仅仅控制汇编器如何编译源代码,不能改变 CPU 硬件运行状态 ;真正切换 ARM/Thumb 处理器模式,需要使用BX指令。

复制代码
AREA MIX_CODE,CODE,READONLY

CODE32          ;后续编译32位ARM指令
ARM_FUNC:
    MOV R0,#1

CODE16          ;后续编译16位Thumb指令
THUMB_FUNC:
    MOVS R1,#2

11. PROC / ENDP 过程(子程序)伪指令

格式

复制代码
子程序名    PROC
    指令
    RET
    ENDP

功能 :用来定义汇编子程序;PROC标记子程序开始,ENDP标记子程序结束,二者必须成对出现 ;使用BL指令调用子程序。

复制代码
ADD_SUB PROC        ;子程序开始,子程序名字ADD_SUB
    ADD R0,R0,R1
    RET             ;RET:子程序返回
    ENDP            ;子程序结束

BL ADD_SUB          ;调用上面定义好的子程序

12. END 源程序结束伪指令

格式

复制代码
END

功能 :标记当前这一个汇编源文件编译到此结束。

注意:一个.s源文件,只能写一条 END,写在文件的末尾;END 之后书写的全部代码,汇编器直接忽略,不会参与编译。

复制代码
AREA TEST,CODE,READONLY
ENTRY
START:      MOV R0,#1

END    ;本汇编源文件到此结束

综合完整示例(ARM 简易启动框架)

复制代码
PRESERVE8
AREA DEMO_CODE,CODE,READONLY
EXPORT Reset_Handler
IMPORT Main
ENTRY

Reset_Handler PROC
    LDR SP,=0x20001000  ;设置栈指针
    B Main              ;跳转到C语言main函数
    ENDP

AREA DEMO_DATA,DATA,READWRITE
BUFF:        SPACE  64            ;分配64字节清零缓冲区
CONST_BYTES: DB 0x01,0x02,0x03

END

附录:标号使用小结

✅合法标号示例:START:、MY_STR:、DATA_BUF:

❌禁止作为标号:MOV:、LDR:、STR:(CPU 指令,属于保留关键字)

标号可以单独占一行,但是冒号不要省略冒号:

复制代码
START:
    MOV R0,#1

补充区分:

  • 51 单片机汇编没有LDR、STR指令,所以STR:作为标号是合法;
  • ARM32 汇编中 LDR、STR 是硬件指令,严禁用作标号,两套汇编规则不要混淆。
相关推荐
MSTcheng.1 个月前
【Linux学习】Linux学习第四弹——Linux基本指令3
linux·windows·学习·操作系统·指令
MSTcheng.2 个月前
【Linux学习】Linux学习第二弹——Linux基本指令1
linux·运维·服务器·学习·指令
凉、介2 个月前
ARMv8-A 指令学习
linux·笔记·学习·嵌入式·arm·指令
网络研究院4 个月前
人工智能行政命令为新的网络安全指令铺平道路
网络·人工智能·安全·指令·创新
无小道5 个月前
Redis——string类型相关指令
redis·指令·string
C.咖.5 个月前
Linux 基础指令详解 —— 从入门到熟练
linux·服务器·指令·linux指令
李日灐6 个月前
<3>Linux 基础指令:从时间、查找、文本过滤到 .zip/.tgz 压缩解压与常用热键
linux·运维·服务器·开发语言·后端·面试·指令