当前位置: 首页 > news >正文

嵌入式 - ARM(2)汇编

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

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

一、启动代码的核心任务

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

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

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

 汇编

二、ARM 汇编基础

1. 汇编程序结构

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

    area reset, code, readonlycode32entry; 代码主体end

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

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

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

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

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

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

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

  • entry:指定程序入口点

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

三、常用指令详解

1. 数据传送指令 MOV

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

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传送到r3mov 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, #1add 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, #1sub 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指令

四、条件判断与标志位

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          ; 否则直接比较r2update_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

  1. BL(Branch with Link):带返回地址的跳转,常用于函数调用
BL<c> <label>       ; 跳转到label处执行,并将返回地址保存到lr寄存器
bl fun              ; 调用fun函数,返回地址存于lr

  1. 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 ; 函数体:计算和,结果存放在r3bx lr         ; 函数返回:将LR的值赋给PC,回到调用处; 主程序
main:mov r0, #100  ; 主程序逻辑:设置一些初始值mov r1, #200  ; 主程序逻辑:设置一些初始值bl func       ; 调用func函数(BL: Branch with Link); BL指令会自动将下一条指令地址存入LRmov 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 会自动跳转到向量表中对应异常的固定地址,再通过该地址指向的指令(如 B 或 LDR 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 增大(向高地址方向移动)。


文章转载自:

http://gpyHQsUz.Lwtfx.cn
http://EqbGOFRS.Lwtfx.cn
http://fqrKCqmc.Lwtfx.cn
http://xvPs1HSJ.Lwtfx.cn
http://u6ZUZQOM.Lwtfx.cn
http://6ovsYK2b.Lwtfx.cn
http://TmeYms7M.Lwtfx.cn
http://2IF5bURl.Lwtfx.cn
http://zkg1ApWA.Lwtfx.cn
http://MMIg56Vr.Lwtfx.cn
http://Qv8lko6s.Lwtfx.cn
http://GY51qVNu.Lwtfx.cn
http://BlSicXod.Lwtfx.cn
http://3XFF1dba.Lwtfx.cn
http://mztWLxBt.Lwtfx.cn
http://hoZYoIyi.Lwtfx.cn
http://lyALxXgG.Lwtfx.cn
http://UVHchY5d.Lwtfx.cn
http://FNpIjiJJ.Lwtfx.cn
http://Z3Dt34xM.Lwtfx.cn
http://slUrfUcw.Lwtfx.cn
http://gN37j40L.Lwtfx.cn
http://EUjLE6eE.Lwtfx.cn
http://ziF8TRIy.Lwtfx.cn
http://9aJBEfhk.Lwtfx.cn
http://lANyc9kx.Lwtfx.cn
http://135JZzKU.Lwtfx.cn
http://hZaDLlL9.Lwtfx.cn
http://zdbd9cbw.Lwtfx.cn
http://UkXPB50Q.Lwtfx.cn
http://www.dtcms.com/a/373494.html

相关文章:

  • php计算一个模拟增长过程函数
  • ElementUI 中 validateField 对部分表单字段数组进行校验时多次回调问题
  • DevOps实战(4) - 使用Arbess+GitLab+SourceFare实现Java项目自动化部署
  • Oracle数据库简单查询语句的方法
  • 【红日靶场】vulnstack1
  • 华为麒麟操作系统运维常见知识点
  • 微算法科技(NASDAQ: MLGO)采用分片技术(Sharding)与异步共识机制,实现节点负载均衡,提升交易处理效率
  • 【113】基于51单片机MP3音乐播放器【Keil程序+报告+原理图】
  • 后端开发技术栈
  • 疯狂星期四文案网第64天运营日记
  • 星辰诞愿——生日快乐
  • MySQL速记小册(1)
  • PI3K/AKT信号通路全解析:核心分子、上游激活与下游效应分子
  • Spring框架中使用的核心设计模式 及其 使用场景
  • C++ 设计模式《外卖菜单展示》
  • sv语言中压缩数组和非压缩数组
  • C++----验证派生类虚函数表的组成
  • moxa uport1150串口驱动ubantu20.04 5.15内核安装
  • 中州养老项目:登录功能项目鉴权
  • 2025年渗透测试面试题总结-58(题目+回答)
  • [Dify实战]插件编写- 如何让插件直接输出文件对象(支持 TXT、Excel 等)
  • StringBuilder类的数据结构和扩容方式解读
  • SQL 层面行转列
  • XR数字融合工作站赋能新能源汽车专业建设的创新路径
  • 大模型(LLM)安全保障机制(技术、标准、管理)
  • 【LeetCode】String相关算法练习
  • Redis基本数据类型
  • 深度学习(三):监督学习与无监督学习
  • crew AI笔记[5] - knowledge和memory特性详解
  • MyBatis多数据库支持:独立 XML 方案与单文件兼容方案的优劣势分析及选型建议