嵌入式 - ARM(2)汇编

在嵌入式开发中,ARM 处理器的启动代码是连接硬件与操作系统或应用程序的桥梁。

学习arm汇编的主要目的是为了编写arm启动代码,启动代码启动以后,引导程序到c语言环境下运行。换句话说启动代码的目的是为了在处理器复位以后搭建c语言最基本的需求。因此启动代码的主要任务有:

一、启动代码的核心任务

启动代码的主要目的 是在处理器复位后,为 C 语言程序运行搭建最基本的环境,具体包括:

  1. 初始化异常向量表
  2. 初始化各工作模式的栈指针寄存器
  3. 开启 ARM 内核中断允许
  4. 将工作模式设置为 User 模式
  5. 引导程序进入 C 语言主函数执行

接下来,我们将学习实现这些任务所需的 ARM 汇编基础和关键指令。

汇编

二、ARM 汇编基础

1. 汇编程序结构

一个基本的 ARM 汇编程序结构如下:

复制代码
    area reset, code, readonly
    code32
    entry

    ; 代码主体

    end

2. 关键- 伪操作: 它们不是 ARM 处理器实际的指令(如 MOV, ADD 等),而是写给汇编器看的命令,用于指导汇编器如何工作

  • area:定义一个段,是最重要的伪操作。程序、数据、堆栈等都需要组织在不同的段中

  • reset : 这是你为这个段起的名字。名字 reset 具有很强的暗示性,通常用于表示复位向量段,即CPU上电或复位后首先执行的第一段代码所在的位置

  • code: 指定该段的属性为代码,意味着这个段包含可执行的指令。

  • readonly: 指定该段的属性为只读。对于代码段来说,这通常是默认且必须的。

  • code32:表示后续指令使用 32 位的 ARM 指令集

  • thumb:表示后续指令使用 16 位的 Thumb 指令集

  • entry:指定程序入口点

  • end:标记汇编程序的结束

三、常用指令详解

1. 数据传送指令 MOV

MOV 指令用于在寄存器之间寄存器与立即数之间传送数据,基本格式:

cpp 复制代码
MOV{S}<c> <Rd>, #<const>        ; 立即数传送到寄存器
MOV{S}<c> <Rd>, <Rm>            ; 寄存器传送到寄存器

MOV 指令还可以配合移位操作:

复制代码
; 移位操作的规范形式
ASR{S} <Rd>, <Rm>, #<n>         ; 算术右移
LSL{S} <Rd>, <Rm>, #<n>         ; 逻辑左移
LSR{S} <Rd>, <Rm>, #<n>         ; 逻辑右移
ROR{S} <Rd>, <Rm>, #<n>         ; 循环右移
RRX{S} <Rd>, <Rm>               ; 带扩展的循环右移

算数右移

为了进行除法运算

示例:

复制代码
mov r0, #0x8        ; 将立即数0x8传送到r0
mov r1, r0          ; 将r0的值传送到r1
mov r3, #31         ; 将立即数31传送到r3

mov r0, #1
mov r6, r0, lsl #31 ; 将r0的值左移31位后传送到r6
mov r7, r0, lsl r3  ; 将r0的值左移r3中指定的位数(31位)后传送到r7

注意

  • 移位量#<n>或寄存器<Rs>的取值范围是 0-31
  • RRX{S}:扩展右移 (不需要移位量)
  • 计算机中数据以二进制形式存在,没有符号、浮点等概念,这些是编程时的解读方式
  • 与C语言中的赋值运算对比(左值/右值),利于加深理解

2. 加法指令 ADD

ADD 指令用于执行加法操作,基本格式:

;立即数作为第二操作数
ADD{S}<c> <Rd>, <Rn>, #<const>

; 寄存器作为第二操作数
ADD{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}

; 寄存器作为第二操作数移位量
ADD{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>

示例:

复制代码
mov r0, #0x0F
mov r1, #0xF0
mov r2, #1

add r6, r0, #0xF0        ; r6 = r0 + 0xF0
add r7, r0, r1           ; r7 = r0 + r1
add r7, r0, r1, lsl #1   ; r7 = r0 + (r1 << 1)
add r8, r0, r1, lsl r2   ; r8 = r0 + (r1 << r2)

注意

  • (1){, <shift>} 其中{}代表可选择,","表示在使用时需要在Rm后添加"," shift 移位量(立即数)
  • (2) add r0, #3, #2 :为什么没有这种形式,C语言int a = 1 + 2; 编译阶段计算, 不需要在机器指令中体现

3. 减法指令 SUB

SUB 指令用于执行减法操作,基本格式:

; 立即数作为第二操作数
SUB{S}<c> <Rd>, <Rn>, #<const>

; 寄存器作为第二操作数
SUB{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}

; 寄存器作为第二操作数移位量
SUB{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>

示例:

复制代码
mov r0, #0xFF
mov r1, #0xF0
mov r2, #1

sub r6, r0, #0xF0        ; r6 = r0 - 0xF0
sub r7, r0, r1           ; r7 = r0 - r1
sub r7, r0, r1, lsl #1   ; r7 = r0 - (r1 << 1)
sub r8, r0, r1, lsl r2   ; r8 = r0 - (r1 << r2)

4. 立即数的概念

在 ARM 指令中,立即数特指 12 位立即数(imm12)。判断一个数是否为有效的 12 位立即数的标准是:

将该数展开为二进制形式后,必须存在一种偶数位的循环右移方式,使得移位后高 24 位全为 0,低 8 位为有效的 imm8。

这是因为 ARM 指令中,立即数的编码方式是:

8 位数据(imm8)加上 4 位循环右移量(rotate),实际值为 imm8 循环右移 (2×rotate) 位。

5. 加载与存储指令 LDR/STR

LDR(加载)和 STR(存储)指令用于在内存和寄存器之间传送数据:

LDR<c> <Rt>, <label> ; 从label指向的内存地址加载数据到Rt寄存器

STR<c> <Rt>, <label> ; 将Rt寄存器的数据存储到label指向的内存地址

这些指令在初始化内存、设置异常向量表等操作中非常重要。

6. 按位取反 移动指令 MVN

MVN 指令将源操作数按位取反后传送到目标寄存器:

MVN{S}<c> <Rd>, #<const> ; 立即数取反后传送到Rd

MVN{S}<c> <Rd>, <Rm>{, <shift>} ; 寄存器值取反后传送到Rd

MVN{S}<c> <Rd>, <Rm>, <type> <Rs> ; 带移位的寄存器值取反后传送到Rd

MVN 指令常用于生成特定的掩码,特别是需要设置某些位为 0 而其他位为 1 的情况。

7. 位清除指令 BIC

BIC 指令用于将目标寄存器中指定的位清 0:

BIC{S}<c> <Rd>, <Rn>, #<const> ; 用立即数掩码清除Rn中的位,结果存到Rd

BIC{S}<c> <Rd>, <Rn>, <Rm>{, <shift>} ; 用寄存器掩码清除Rn中的位,结果存到Rd

BIC{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs> ; 用带移位的寄存器掩码清除Rn中的位

示例:

复制代码
mov r0, #0xFFFFFFFF    ; r0 = 0xFFFFFFFF
mov r1, #1             ; r1 = 1
bic r2, r0, r1, lsl #31 ; 清除r0的第31位,结果存到r2
bic r3, r0, #(1 << 31)  ; 同上,使用立即数掩码

8. 按位或指令 ORR

ORR 指令用于执行按位或操作,常用于设置寄存器中的特定位:

ORR{S}<c> <Rd>, <Rn>, #<const> ; 寄存器与立即数按位或

ORR{S}<c> <Rd>, <Rn>, <Rm>{, <shift>} ; 寄存器与寄存器按位或

ORR{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs> ; 寄存器与带移位的寄存器按位或

示例:

复制代码
mov r0, #0x00         ; r0 = 0x00
mov r1, #1            ; r1 = 1
mov r2, #31           ; r2 = 31

; 以下指令均为设置r0的第31位为1
orrs r6, r0, #0x80000000
orrs r7, r0, #(1 << 31)
orrs r8, r0, r1, lsl #31
orrs r9, r0, r1, lsl r2

在启动代码中,ORR 指令常用于设置 CPSR 寄存器中的特定标志位,如开启中断。

9.带s指令

四、条件判断与标志位NZCV

1、CPSR 寄存器中的条件标志位

ARM 处理器的当前程序状态寄存器(CPSR)中包含四个条件标志位,用于表示指令执行结果的状态:

  • N(Negative):符号标志位。当指令执行结果的最高位(bit31)为 1 时,N=1,表示结果作为有符号数时为负值。
  • Z(Zero):零值标志位:上条指令执行结果为0(即bit0 - bit31 均为0),则 Z = 1;
  • C(Carry) :进位标志位。进行无符号解读,如果在加法过程中进位或者减法时没有借位, 则为**C = 1,**否则 C = 0
  • V(Overflow):进行有符号解读,是否发生溢出 -2^31 - 2^31-1(两个正数加得负数,两个负数加得正数),对于有符号运算,当运算结果超出有符号数的表示范围时,V=1。

2、常用条件码

  • eq:等于(Z=1)
  • ne:不等于(Z=0)
  • ge:大于或等于(N=V)
  • gt:大于(Z=0 且 N=V)
  • le:小于或等于(Z=1 或 N≠V)
  • lt:小于(N≠V)
  • al:无条件执行(默认)

3、比较指令 CMP

CMP 指令用于比较两个操作数,本质上是执行减法操作但不保存结果,只更新标志位:

CMP<c> <Rn>, #<const> ; 比较寄存器与立即数

CMP<c> <Rn>, <Rm>{, <shift>} ; 比较两个寄存器

CMP<c> <Rn>, <Rm>, <type> <Rs> ; 比较寄存器与带移位的寄存器
cmp r0, r1 等价于 subs r0, r1(带 S 后缀的减法指令,会更新标志位但不保存结果)。

示例:比较获取三个数中的最大值

复制代码
; 假设r0, r1, r2中存放三个待比较的数,结果存放在r3中
mov r3, r0          ; 先假设r0是最大值
cmp r3, r1          ; 比较r3和r1
blt update_max1     ; 如果r3 < r1,则更新最大值
b check_r2          ; 否则直接比较r2

update_max1:
mov r3, r1          ; 将r1设为当前最大值

check_r2:
cmp r3, r2          ; 比较当前最大值和r2
blt update_max2     ; 如果当前最大值 < r2,则更新最大值
b end_compare       ; 否则结束比较

update_max2:
mov r3, r2          ; 将r2设为最大值

end_compare:
; 此时r3中存放的是三个数中的最大值

4、跳转指令

ARM 汇编提供了多种跳转指令,用于控制程序流程:

  1. B(Branch):无条件跳转

    B<c> <label> ; 跳转到label处执行
    b fun ; 等价于 ldr pc, =fun

  2. BL(Branch with Link):带返回地址的跳转,常用于函数调用

    BL<c> <label> ; 跳转到label处执行,并将返回地址保存到lr寄存器
    bl fun ; 调用fun函数,返回地址存于lr

  3. BX(Branch and Exchange):跳转并切换指令集

    BX<c> <Rm> ; 跳转到Rm寄存器指定的地址,可切换ARM/Thumb模式
    bx lr ; 从子程序返回,等价于 mov pc, lr

在启动代码中,这些跳转指令用于实现异常处理、函数调用和程序流程控制。

五、分支及循环结构

循环是程序设计中最基本的控制结构之一,无论是 C 语言还是汇编语言,都需要围绕 "循环三要素" 来实现:

  • 循环结束条件
  • 推动循环趋向终结的语句
  • 循环体(重复执行的代码块)

1.分支结构

1. do...while 循环的实现

C 语言中的 do...while 循环先执行循环体,再判断循环条件:

复制代码
int i = 0;
int sum = 0;
do{
    sum += i;  // 循环体:累加
    i++;       // 推动循环终结:计数器递增
}while(i <= 100);  // 循环结束条件

对应的 ARM 汇编实现:

复制代码
mov r0, #0    ; 初始化i=0,使用r0寄存器存储i
mov r1, #0    ; 初始化sum=0,使用r1寄存器存储sum
loop:         ; 循环标签(相当于循环体开始)
add r1, r1, r0 ; 循环体:sum += i (r1 = r1 + r0)
add r0, r0, #1 ; 推动循环终结:i++ (r0自增1)
cmp r0, #100   ; 比较i与100(设置条件码)
ble loop       ; 若i <= 100 (BLE: Branch if Less than or Equal),跳回loop继续循环

汇编实现中,使用cmp指令进行条件比较,ble条件跳转指令控制循环是否继续,这是汇编实现循环的核心机制。

2. while 与 for 循环的实现

C 语言中的 while 循环先判断条件,再执行循环体:

复制代码
int i = 0;
int sum = 0;
while(i <= 100)  // 先判断循环条件
{
    sum += i;    // 循环体
    i++;         // 推动循环终结
}

for 循环是 while 循环的,将初始化、条件判断和迭代语句整合在一起:

复制代码
int sum = 0;
for(int i = 0; i <= 100; i++)  // 初始化;条件;迭代
{
    sum += i;  // 循环体
}

对应的 ARM 汇编实现(while 和 for 在汇编层面实现相同):

复制代码
mov r0, #0    ; 初始化i=0
mov r1, #0    ; 初始化sum=0
loop:         ; 循环标签
cmp r0, #100  ; 先判断条件:比较i与100
bgt finish    ; 若i > 100 (BGT: Branch if Greater Than),跳转到循环结束
add r1, r1, r0 ; 循环体:sum += i
add r0, r0, #1 ; 迭代:i++
b loop        ; 无条件跳回loop继续循环
finish:       ; 循环结束标签
b finish      ; 死循环(通常在嵌入式中表示程序结束)

与 do...while 的汇编实现相比,while/for 的实现将条件判断移到了循环体之前,这正是两种循环结构的本质区别。

六、函数定义及调用机制

函数是代码复用和模块化的基础,ARM 汇编通过特殊寄存器实现函数调用与返回,理解这一机制对掌握汇编编程至关重要。

1. 函数的基本结构

C 语言中函数的定义格式:

复制代码
返回值类型 函数名(形参列表) {
    // 函数体
    代码块;
    return 返回值;
}

在 ARM 汇编中,函数的实现依赖两个关键寄存器:

  • PC(Program Counter):程序计数器,存储下一条要执行的指令地址
  • LR(Link Register):链接寄存器,存储函数调用后的返回地址

函数调用的核心机制是:

  1. 调用函数时,将当前 PC 值(返回地址)保存到 LR
  2. 函数执行完毕后,将 LR 的值恢复到 PC,实现返回

2. ARM 汇编函数调用示例

下面通过一个简单的函数调用来理解这一机制:

复制代码
; 函数定义:计算两个数的和
func:
    mov r0, #1    ; 函数体:设置第一个参数
    mov r1, #2    ; 函数体:设置第二个参数
    add r3, r0, r1 ; 函数体:计算和,结果存放在r3
    bx lr         ; 函数返回:将LR的值赋给PC,回到调用处

; 主程序
main:
    mov r0, #100  ; 主程序逻辑:设置一些初始值
    mov r1, #200  ; 主程序逻辑:设置一些初始值
    bl func       ; 调用func函数(BL: Branch with Link)
                  ; BL指令会自动将下一条指令地址存入LR
    mov r3, #300  ; func返回后继续执行的指令

    b main        ; 主程序循环

函数调用过程解析

  1. 执行bl func指令时,硬件自动完成:
    • 将下一条指令(mov r3, #300)的地址存入 LR 寄存器
    • 将 PC 设置为 func 函数的入口地址,开始执行函数
  2. 函数执行到bx lr时:
    • 将 LR 中保存的返回地址(mov r3, #300的地址)赋给 PC
    • 程序跳回主程序继续执行

在 ARM 汇编中,BL(Branch with Link)指令是实现函数调用的关键,它自动完成了返回地址的保存;而BX LR(Branch and eXchange)指令则实现了函数返回。

作业:

1、ARM 内核工作模式有哪些,分别是在什么情况下被切换?

ARM 基本工作模式(7 种)

1、User(用户模式):非特权模式,大部分任务执行在此模式。

普通程序运行模式,权限最低,无法直接访问硬件资源或切换模式。

切换场景:程序正常执行时默认处于该模式,仅能通过异常进入其他模式。

2、IRQ(普通中断模式):低优先级(normal)中断产生时进入此模式。

特权模式,用于处理通用中断请求。

切换场景:外部设备触发 IRQ 中断时自动进入。

3、FIQ(快速中断模式):高优先级(fast)中断产生时进入此模式。

特权模式,用于处理高优先级、低延迟的中断(如定时器、高速外设)。

切换场景:外部设备触发 FIQ 中断时自动进入(优先级高于 IRQ)。

4、Supervisor(管理模式):当复位或软中断指令执行时将会进入这种模式

特权模式,用于操作系统内核管理,是复位后的默认模式。

切换场景:复位(Reset)、执行 SWI(软件中断)指令时进入

5、Abort(中止模式):存取异常时进入此模式。

特权模式,用于处理内存访问错误(如非法地址、权限不足)。

切换场景:发生预取指中止(指令读取失败)或数据中止(数据读写失败)时进入。

6、Undef(未定义模式):执行未定义指令时进入此模式。

特权模式,用于处理未识别的指令。

切换场景:CPU 执行未定义指令时自动进入。

7、System(系统模式):使用与 User 模式相同寄存器集的特权模式。

特权模式,使用用户模式的寄存器集,用于运行操作系统核心代码。

切换场景:仅通过软件修改 CPSR 的模式位(M [4:0])主动切换。

Cortex - A 特有模式

Monitor(监控模式):为安全扩展而来,用于执行安全监控代码;属于特权模式。

2、异常向量表是什么?

异常向量表是 ARM 内核 内存中一组固定地址的集合,每个地址对应一种异常(如复位、中断等),用于存储该异常的处理程序入口地址(或跳转指令)。

当异常发生时,CPU 会自动跳转到向量表中对应异常的固定地址,再通过该地址指向的指令(如 BLDR PC,=handler)跳转到具体的异常处理逻辑。

以 ARM 小端模式为例,典型异常向量表地址分布:

  • 0x00000000:复位(Reset)
  • 0x00000004:未定义指令
  • 0x00000008:软件中断(SWI)
  • 0x0000000C:预取指中止
  • 0x00000010:数据中止
  • 0x00000014:保留
  • 0x00000018:IRQ 中断
  • 0x0000001C:FIQ 中断

3、什么是立即数?如何判断某数是非法是12位立即数?

实现压缩数据

1、立即数 :指在指令中直接包含的常数(无需从内存读取),ARM 指令中立即数通过 12 位编码 表示(8 位数值 + 4 位旋转值)。

编码规则:立即数 = 8 位数值(imm8)经过 4 位旋转值(rotate)右移(rotate×2)位得到,即 immediate = imm8 >> (rotate×2)(或左移补 0,等效于循环右移)。

2、判断非法 12 位立即数:把某个数展开成2进制,该数必须存在一种循环右移(偶数位),使得移位后高24位全0,低8位即为有效imm8;

4、b,bl,bx指令的区别是什么?

1、b :无条件跳转指令,仅修改 PC 寄存器(程序计数器),不保存返回地址。

用途:用于普通跳转(如循环、条件分支),无返回需求场景。

示例:b loop(跳转到 loop 标签)。

2、bl :带链接的跳转指令,跳转时自动将下一条指令地址存入 LR(链接寄存器),再修改 PC。

用途:用于函数调用(需返回调用处),函数返回时通过 bx lr 恢复执行。

示例:bl func(调用 func 函数,返回地址存入 LR)。

3、bx:跳转并切换指令集(ARM ↔ Thumb),跳转地址的最低位(bit0)决定目标指令集:

  • 若 bit0 = 1:切换到 Thumb 模式(16 位指令集);
  • 若 bit0 = 0:保持 ARM 模式(32 位指令集)。
    用途:在 ARM 和 Thumb 指令集间切换执行,常配合 LR 实现函数返回(bx lr)。

5、ARM内核采用的栈是哪种栈?

ARM 内核默认采用 满递减栈,其特点:

满栈:栈指针(SP)指向最后一个已压入栈的元素(而非下一个空闲位置)。

递减栈:入栈时 SP 减小(向低地址方向增长),出栈时 SP 增大(向高地址方向移动)。

相关推荐
瑞禧生物ruixibio5 小时前
4-ARM-PEG-COOH(2),多功能羧基PEG的结构特性与反应特点
arm开发
gfanbei18 小时前
ARM V8 Cortex R52 上电运行在什么状态?— Deepseek 解答
linux·arm开发·嵌入式硬件
YeGop1 天前
51单片机定时器函数分享(8051汇编)
汇编·嵌入式硬件·51单片机
isaki1371 天前
arm day1
linux·运维·arm开发
YeGop1 天前
51单片机汇编实现DHT11读取温湿度
汇编·嵌入式硬件·51单片机
资料,小偿2 天前
8086汇编语言定制,8086仿真proteus8086定制,emu8086汇编语言
汇编
花阴偷移2 天前
逆向基础--汇编基础(段的分类) (07)
汇编
YeGop2 天前
51单片机数码管显示函数分享(8051汇编)
汇编·嵌入式硬件·51单片机
花阴偷移2 天前
逆向基础--汇编基础(DOS安装与介绍) (06)
汇编
GilgameshJSS3 天前
STM32H743-ARM例程41-FMC_INDEP
arm开发·stm32·单片机·嵌入式硬件