嵌入式——ARM——汇编1

【ARM汇编学习笔记】ARMv7‑A汇编伪指令、常用指令、循环函数栈、C与汇编混合编程

📖今日学习:Keil4环境下ARM汇编,伪操作、数据处理指令、移位、比较跳转、循环、函数调用、栈操作、MRS/MSR读写CPSR,汇编和C语言互相调用。

🔍文章大纲

  1. ARM汇编学习目的(启动代码作用)
  2. 汇编伪操作(AREA、CODE32、ENTRY、END、PRESERVE8、IMPORT、EXPORT)
  3. 常用汇编指令详解
  4. CPSR状态寄存器 NZCV条件标志与条件码
  5. 汇编实现循环(do‑while / while / for)
  6. 函数调用、栈操作 STMFD / LDMFD(满递减栈)
  7. C与汇编互相调用(AAPCS调用规约)
  8. MRS / MSR读写程序状态寄存器,修改处理器工作模式
  9. ✨学习重点汇总
  10. ⚠️易错点踩坑记录
  11. 📝今日学习小结

1. ARM汇编学习目的

学习ARM汇编,主要是为了编写ARM启动代码

处理器复位之后,C语言环境还没有建立,CPU只能执行汇编指令。启动代码要完成如下工作:

  1. 初始化异常向量表
  2. 初始化各个处理器工作模式下的栈指针SP
  3. 设置内核中断开关
  4. 切换处理器工作模式为User用户模式
  5. 搭建好C运行环境,跳转执行C语言main函数

我们学习汇编指令,全部围绕启动代码的上述需求展开。开发环境:Keil4,参考手册armV7A_V7R_architecture_reference_manual

2. 汇编伪操作

伪操作不是CPU可执行指令,是给汇编器(编译器)看的命令,指导汇编器如何处理代码。

伪指令 作用说明
AREA reset,CODE,READONLY 定义段;段名reset,代码段,只读;复位代码放在该段
CODE32 后续代码编译为32位ARM指令集
THUMB 后续代码编译为16位Thumb指令集
ENTRY 标记程序入口点,复位从此处开始执行
END 汇编源文件结束标记,写在整个文件末尾
PRESERVE8 保持栈8字节对齐;汇编调用C函数必须加,解决L6238E链接报错
IMPORT 导入外部符号(外部C函数)
EXPORT 导出本汇编的符号,供C语言调用

示例段框架:

复制代码
PRESERVE8
AREA reset, CODE, READONLY
CODE32
ENTRY

;此处写汇编代码

END

3. 常用汇编指令

指令后缀说明:

{S}:写S代表运算后更新CPSR的NZCV标志位 ;不带S不更新标志。

{<c>}:条件执行后缀,如BEQBNE,满足条件才执行指令。

3.1 MOV 数据传送指令

复制代码
MOV{S}<c> <Rd>, #<const>        ;立即数传送到寄存器
MOV{S}<c> <Rd>, <Rm>           ;寄存器拷贝
MOV{S}<c> <Rd>,<Rm>,LSL #n     ;移位操作,移位0‑31位
;移位类型 LSL逻辑左移,LSR逻辑右移,ASR算术右移,ROR循环右移,RRX扩展循环右移

示例

复制代码
mov r0,#0x8
mov r1,r0
mov r6,r0,lsl #31

注意:mov的立即数是12位立即数(imm12) ,不是任意数字都可以直接用#

12位立即数判断规则

把数字二进制展开,该数字能够通过8位原始数据,偶数次循环右移 得到。不能满足则为非法立即数,不能直接用mov,改用LDR Rd,=立即数

3.2 ADD 加法指令

复制代码
ADD{S}<c> <Rd>,<Rn>,#<const>        ;立即数
ADD{S}<c> <Rd>,<Rn>,<Rm>{,shift}   ;寄存器+移位寄存器

示例

复制代码
mov r0,#0x0F
mov r1,#0xF0
add r6,r0,#0xF0
add r7,r0,r1
add r7,r0,r1,lsl #1

❌错误写法:add r0,#3,#2;ARM汇编不允许两个立即数相加,编译阶段由汇编器计算。

3.3 SUB 减法指令

复制代码
SUB{S}<c> <Rd>,<Rn>,#<const>
SUB{S}<c> <Rd>,<Rn>,<Rm>{,shift}

3.4 MVN 按位取反传送

把源操作数按位取反后送入目标寄存器。

复制代码
MVN{S}<c> <Rd>, #<const>
MVN{S}<c> <Rd>, <Rm>

3.5 BIC 位清除(把指定bit置0)

复制代码
BIC{S}<c> <Rd>,<Rn>,#<const>
BIC{S}<c> <Rd>,<Rn>,<Rm>{,shift}

示例,把r0的bit31清零

复制代码
mov r0,#0xFFFFFFFF
mov r1,#1
bic r2,r0,r1,lsl #31

3.6 ORR 按位或(指定bit置1)

复制代码
ORR{S}<c> <Rd>,<Rn>,#<const>
ORR{S}<c> <Rd>,<Rn>,<Rm>{,shift}

示例,把bit31置1

复制代码
mov r0,#0
mov r1,#1
orr r6,r0,r1,lsl #31

3.7 CMP 比较指令

CMP Rn,Rm 等价于执行SUBS Rn,Rm只更新CPSR标志,不保存运算结果

复制代码
CMP<c> <Rn>,#<const>
CMP<c> <Rn>,<Rm>{,shift}

3.8 跳转指令 B / BL / BX

指令 功能
B label 无条件跳转,不保存返回地址
BL label 带链接跳转;把返回地址存入LR(R14),用于函数调用
BX Rm 跳转到寄存器地址,可切换ARM/Thumb状态;BX LR实现函数返回

BL用于函数调用;BX LR用于函数返回。

3.9 LDR 伪加载指令

LDR Rd,=value,把任意32位立即数/标签地址加载到寄存器;解决mov立即数限制。

复制代码
ldr sp,=0x40001000  ;设置栈指针,不能写 mov sp,#0x40001000(非法立即数)

3.10 批量内存访问 STMFD / LDMFD(栈操作)

ARM7默认使用FD满递减栈Full‑Descending,栈向低地址方向增长,SP指向有效栈顶。

  • STMFD SP!,{r0‑r12,lr}:压栈,保存寄存器现场(!代表SP自动更新)

  • LDMFD SP!,{r0‑r12,pc}:弹栈,恢复现场,直接跳转返回

    ;保护现场
    stmfd sp!,{r0‑r12,lr}

    ;恢复现场
    ldmfd sp!,{r0‑r12,pc}

3.11 MRS / MSR 读写状态寄存器CPSR/SPSR

普通指令不能直接访问CPSR,需要专用指令

复制代码
MRS<c> Rd, spec_reg    ;读状态寄存器 CPSR/SPSR → Rd
MSR<c> spec_reg, Rn    ;把Rn写入状态寄存器

示例修改处理器模式位:

复制代码
mrs r0,cpsr
bic r0,r0,#0x1F
orr r0,r0,#0x10
msr cpsr,r0

4. CPSR条件标志位 NZCV

CPSR的高4位N Z C V,算术指令带S后缀会更新标志,条件跳转指令依靠标志判断。

标志 位置 置1条件
N(负号) bit31 运算结果为负数(有符号,结果最高位为1)
Z(零) bit30 运算结果等于0
C(进位) bit29 无符号加法产生进位;减法无借位;移位移出bit=1
V(溢出) bit28 有符号数运算溢出(正数相加变负数,负数相加变正数)

常用条件码后缀:

  • EQ:相等(Z=1)
  • NE:不相等(Z=0)
  • GT:有符号大于
  • GE:有符号大于等于
  • LT:有符号小于
  • LE:有符号小于等于
  • AL:无条件执行,指令默认

5. 汇编实现循环

do‑while循环(先执行后判断)

C语言

复制代码
int i=0,sum=0;
do{
    sum += i;
    i++;
}while(i <=100);

ARM汇编

复制代码
        mov r0,#0
        mov r1,#0
loop
        add r1,r1,r0
        add r0,r0,#1
        cmp r0,#100
        ble loop

while / for循环(先判断后执行)

C语言

复制代码
int i=0,sum=0;
while(i<=100){
    sum +=i;
    i++;
}

ARM汇编

复制代码
        mov r0,#0
        mov r1,#0
loop
        cmp r0,#100
        bgt finish
        add r1,r1,r0
        add r0,r0,#1
        b loop
finish
        b finish

6. 函数调用,栈帧与现场保护

  1. BL func调用函数,自动把返回地址保存到LR(R14)
  2. 如果函数内部还会调用别的子函数,LR会被覆盖,必须压栈保存LR
  3. STMFD压栈保护用到的通用寄存器,函数返回前LDMFD弹栈恢复现场。
  4. AAPCS调用规范:R0‑R3用作函数参数,R0存放返回值。

简单嵌套函数示例:

复制代码
func0
    stmfd sp!,{r0‑r12,lr}  ;保护现场
    bl func1                ;调用子函数
    ldmfd sp!,{r0‑r12,pc}  ;恢复现场,返回

func1
    mov r0,#10
    mov r1,#20
    bx lr                   ;返回

7. C语言和汇编互相调用

7.1 汇编调用C函数

  1. 汇编文件开头伪指令:PRESERVE8保证栈8字节对齐,解决L6238E链接报错

  2. IMPORT c_add导入外部C函数符号

  3. R0‑R3传递前4个参数;多于4个参数压入栈

  4. BL跳转调用C函数;调用前做好寄存器现场保护。

    PRESERVE8
    AREA reset,CODE,READONLY
    CODE32
    ENTRY
    IMPORT c_add

    start
    ldr sp,=0x40001000
    stmfd sp!,{r0‑r12,lr}
    mov r0,#1
    mov r1,#2
    mov r2,#3
    mov r3,#4
    mov r4,#5
    stmfd sp!,{r4} ;第5个参数压栈
    bl c_add
    ldmfd sp!,{r4}
    ldmfd sp!,{r0‑r12,pc}
    END

7.2 C调用汇编函数

  1. 汇编中使用EXPORT func_name导出函数名
  2. C语言中使用extern int func(int a,int b);声明外部汇编函数。

AAPCS调用约定

  • R0、R1、R2、R3传递前4个函数参数
  • 返回值存放在R0寄存器
  • 超过4个参数,参数从栈上传递。

Keil工程配置要点

  1. Target选项卡:ROM1起始地址0x0,Size0x2000;设置IRAM内存地址大小。
  2. Linker:勾选Use Memory Layout from Target Dialog,不使用scatter脚本。
  3. Debug:Use Simulator,取消Run to main

✨学习重点

  1. 汇编伪操作和指令区分:伪操作给汇编器看;指令是CPU执行。重点AREA、CODE32、ENTRY、END、PRESERVE8、IMPORT、EXPORT
  2. MOV立即数受12位imm12限制,非法立即数使用LDR Rd,=xxx不能直接mov sp,#立即数,要用ldr sp,=地址
  3. 移位指令LSL/LSR/ASR/ROR/RRX;S后缀指令会更新CPSR的NZCV标志位。
  4. CMP比较本质是SUBS,只改标志,不保存运算结果;B、BL、BX跳转指令区别,BL保存LR用于函数调用,BX LR函数返回。
  5. ARM7栈:满递减FD栈,SP向低地址增长 ;STMFD压栈、LDMFD弹栈,!表示自动更新SP。
  6. CPSR寄存器N/Z/C/V四个标志位含义;条件码EQ/NE/GT/GE/LT/LE。
  7. MRS读CPSR,MSR写CPSR,用来修改处理器工作模式。
  8. 汇编循环:do‑while先执行再判断;while先判断后执行。
  9. 函数调用:BL保存返回地址到LR;嵌套调用必须压栈保存LR防止被覆盖。
  10. AAPCS调用规范:R0‑R3传参,R0存返回值;汇编调用C必须写PRESERVE8保证栈8字节对齐。
  11. 汇编<‑>C互相调用:IMPORT导入C函数,EXPORT导出汇编函数。

⚠️易错点&踩坑记录

  1. ❌混淆伪操作和指令,把AREA、ENTRY当成CPU指令。
  2. ❌mov直接写大立即数,遇到非法12位立即数,编译报错;改用ldr rd,=立即数
  3. mov sp,#0x40001000;立即数非法,必须使用ldr sp,=0x40001000
  4. ❌忘记S后缀:不加S的ADD/SUB不会更新CPSR标志,后面条件跳转判断失效。
  5. ❌B、BL、BX混淆:B跳转不保存LR,不能用于函数调用返回;函数调用用BL,返回用BX LR。
  6. ❌栈操作忘记写!,STMFD/LDMFD不会自动更新SP,栈指针错乱。
  7. ❌汇编调用C函数不加PRESERVE8,报L6238E链接错误。
  8. ❌函数嵌套调用,没有压栈保护LR;子函数调用覆盖LR,函数返回直接跑飞。
  9. ❌AAPCS规约记错,函数参数不用R0‑R3,随便乱用寄存器传参,和C交互结果错乱。
  10. ❌MRS/MSR写错,直接mov cpsr,r0;CPSR不能用普通MOV读写,只能MRS/MSR。
  11. ❌汇编调用C函数,超过4个参数忘记压栈;C调用汇编忘记EXPORT导出符号。
  12. ❌Keil工程scatter脚本干扰,勾选Use Memory Layout from Target Dialog

📝今日学习小结

今天学习ARMv7‑A汇编,主要面向启动代码开发。掌握伪操作AREA、CODE32、ENTRY、PRESERVE8;掌握MOV、ADD、SUB、MVN、BIC、ORR、CMP、B/BL/BX等核心指令;理解CPSR的NZCV条件标志。

学会汇编实现do‑while、while循环;掌握满递减栈STMFD/LDMFD做函数现场保护;理解BL保存LR实现函数调用,BX‑LR返回;掌握MRS/MSR读写状态寄存器修改处理器模式。

重点掌握汇编与C语言互相调用,AAPCS调用规约,解决Keil链接报错。

练习方向:汇编实现1‑100求和;汇编写一个函数供C调用;汇编调用C函数。

后续学习:ARM异常向量表编写,完整启动代码。

相关推荐
ouynagda1 小时前
嵌入式与ARM内核基础体系梳理
arm开发
ly76891 小时前
磁盘 I/O 延迟突增:用 iostat、blktrace 与火焰图定位到具体调用栈
java·linux·前端·数据库·iostat·磁盘 i/o·blktrace
学linux的QQ蛋1 小时前
零基础吃透ARM体系架构与底层原理
arm
程序员-Benothing1 小时前
Linux查找文件命令:find、locate、which、whereis实战
linux·运维·服务器
j7~1 小时前
【Linux网络加餐】(篇七)网络版计算器(中):协议落地、报文分隔与完整链路
linux·c++·网络编程·tcp·报文分割·网络计算器·协议落地
团子股股东峥哥1 小时前
day39-RHEL-访问网络附加存储
linux·运维·服务器
不会写代码的小可爱&&1 小时前
深入 Linux 内核内存管理:slab/slub 分配器原理剖析
linux·硬件架构
PiaoKe___1 小时前
从端云解耦到可编程 Android 实例:云手机架构解析与 Python 批量管控实战
arm开发·python·架构·自动化
H_oRIZoN_2 小时前
Linux入门DAY44 ARM 入门 Day02|ARM 汇编指令、模式切换、栈操作、汇编 C 混合编程
linux·单片机·嵌入式硬件·arm·linux应用编程