嵌入式ARM架构学习2——汇编

发布于:2025-09-10 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、处理器工作模式
ARM有7个基本工作模式:

  • User:非特权模式,大部分任务执行在这种模式
  • FIQ:当一个高优先级(fast)中断产生时将会进入这种模式
  • IRQ:当一个低优先级(normal)中断产生时将会进入这种模式
  • Supervisor:当复位或软中断指令执行时将会进入这种模式
  • Abort:当存取异常时将会进入这种模式
  • Undef:当执行未定义指令时会进入这种模式
  • System:便用和User模式相同寄存器集的特权模式

Cortex-A特有模式:

  • Monitor:是为了安全而扩展出的用于执行安全监控代码的模式;

也是一种特权模式

Part1

学习arm汇编的主要目的是为了编写arm启动代码,启动代码启动以后,引导程序到c语言环境下运行。换句话说启动代码的目的是为了在处理器复位以后搭建c语言最基本的需求。因此启动代码的主要任务有:
    1、初始化异常向量表;
    2、初始化各工作模式的栈指针寄存器;
    3、开启arm内核中断允许;
    4、将工作模式设置为user模式;
    5、完成上述工作后,引导程序进入c语言主函数执行;
    因此汇编指令的学习主要是围绕这几个目的展开,主要学习跟上述目的相关的指令。
    
1.格式
    伪操作:它们不是 ARM 处理器实际的指令(如 MOV, ADD 等),而是写给汇编器看的命令,用于指导汇编器如何工作
    area reset, code, readonly
    code32
    entry
    end    
    area: 这是最重要的一个伪操作,用于定义一个段。程序、数据、堆栈等都需要被组织在不同的段中。
    reset: 这是你为这个段起的名字。名字 reset 具有很强的暗示性,通常用于表示复位向量段,即CPU上电或复位后首先执行的第一段代码所在的位置。
    code: 指定该段的属性为代码,意味着这个段包含可执行的指令。
    readonly: 指定该段的属性为只读。对于代码段来说,这通常是默认且必须的。

    code32: 表示后续指令使用 32位的 ARM 指令集。
    thumb: 表示后续指令使用 16位的 Thumb 指令集。
2.指令    
    1.mov
        MOV{S}<c> <Rd>, #<const>
        MOV{S}<c> <Rd>, <Rm>

        MOV instruction                Canonical form
        MOV{S} <Rd>, <Rm>, ASR #<n> ASR{S} <Rd>, <Rm>, #<n>
        MOV{S} <Rd>, <Rm>, LSL #<n> LSL{S} <Rd>, <Rm>, #<n>
        MOV{S} <Rd>, <Rm>, LSR #<n> LSR{S} <Rd>, <Rm>, #<n>
        MOV{S} <Rd>, <Rm>, ROR #<n> ROR{S} <Rd>, <Rm>, #<n>
        MOV{S} <Rd>, <Rm>, ASR <Rs> ASR{S} <Rd>, <Rm>, <Rs>
        MOV{S} <Rd>, <Rm>, LSL <Rs> LSL{S} <Rd>, <Rm>, <Rs>
        MOV{S} <Rd>, <Rm>, LSR <Rs> LSR{S} <Rd>, <Rm>, <Rs>
        MOV{S} <Rd>, <Rm>, ROR <Rs> ROR{S} <Rd>, <Rm>, <Rs>
        MOV{S} <Rd>, <Rm>, RRX         RRX{S} <Rd>, <Rm>
        注意    (1)与C语言中的赋值运算对比(左值/右值),利于加深理解
            (2)#<n>/<Rs> 取值范围 (0 - 31)
            (3)RRX{S}:扩展右移 (不需要移位量)
            (4)在计算机中只识别二进制数据,计算机没有有无符号,浮动点等概念;
            
        mov r0, #0x8
        mov r1, r0
        mov r3, #31

        mov r0 #1
        mov r6, r0, lsl #31
        mov r7, r0, lsl r3
        
    2.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
        add r7, r0, r1
        add r7, r0, r1, lsl #1
        add r8, r0, r1, lsl r2
        注意    (1){, <shift>} 其中{}代表可选择,“,”表示在使用时需要在Rm后添加“,” shift 移位量(立即数)
            (2) add r0, #3, #2 :为什么没有这种形式,C语言int a = 1 + 2; 编译阶段计算, 不需要在机器指令中体现 
            
    3.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
        sub r7, r0, r1
        sub r7, r0, r1, lsl #1
        sub r8, r0, r1, lsl r2

        以上指令都有立即数作为第二操作数的情况,那么什么是立即数呢?
            准确的说这里所指的是12位立即数imm12。先说怎么判断某数是不是12位立即数,12位立即数的条件是:
        判断标准:把某个数展开成2进制,该数必须存在一种循环右移(偶数位),使得移位后高24位全0,低8位即为有效imm8;
        原因:
            0000 (rotate)0000 0000(imm8)
            N(0-15)        1000 0001
            2N(0-30)
    4.ldr(加载指令)
        LDR<c> <Rt>, <label>
    5.sdr(存放指令)

            
    6.MVN(按位取反移动指令):
        MVN{S}<c> <Rd>, #<const>
        MVN{S}<c> <Rd>, <Rm>{, <shift>}
        MVN{S}<c> <Rd>, <Rm>, <type> <Rs>

    7.bic(bit clear):指定位置清0
        BIC{S}<c> <Rd>, <Rn>, #<const>
        BIC{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
        BIC{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
        
        mov r0, #0xFFFFFFFF
        mov r1, #1
        bic r2, r0, r1, lsl    #31
        bic r3, r0, #(1 << 31)
        
    8.orr(or):指定位置1
        ORR{S}<c> <Rd>, <Rn>, #<const>
        ORR{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
        ORR{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
        
        mov r0, #0x00
        mov r1, #1
        mov r2, #31
        orrs r6, r0, #0x80000000
        orrs r7, r0, #(1 << 31)
        orrs r8, r0, r1, lsl #31
        orrs r9, r0, r1, lsl r2
        
    9.条件判断标志NZCV
        CPSR寄存器中条件判断标志位
        N: 符号标志位:上条指令执行结果最高位bit31为1,则 N = 1, 当结果作为有符号解释时为负值;
        Z: 零值标志位:上条指令执行结果为0(即bit0 - bit31 均为0),则 Z = 1;
        C: 进位标志位:进行无符号解读,如果在加法过程中进位或者减法时没有借位,则为 C = 1,否则 C = 0
        V: 溢出标志位:进行有符号解读,是否发生溢出 -2^31 - 2^31-1(两个正数加得负数,两个负数加得正数)
        条件码:eq ge gt le lt al(无条件执行)
        equal:等于
        not equal:不等于
        
    10.cmp(compare):比较指令
        CMP<c> <Rn>, #<const>
        CMP<c> <Rn>, <Rm>{, <shift>}
        CMP<c> <Rn>, <Rm>, <type> <Rs>
        
        cmp r0, r1 <==> subs r0, r1
        比较两个数中的最大值
        练习:比较获取三个数中最大值
        
    11.b bl bx :(跳转指令)
        B<c> <label>
        b fun <==> ldr pc, =fun
        
        BL<c> <label>
        bl fun
        
        BX<c> <Rm>
        bx lr <==> mov pc, lr

Part2

1.循环
    (1)循环三要素
        循环结束条件
        推动循环趋向终结的语句
        循环的循环体
    (2)do...while(C)
        int i = 0;
        int sum = 0;
        do{
            sum += i;
            i++;
        }while(i <= 100)
    
        mov r0, #0
        mov r1, #0
    loop
        add r1, r1, r0
        add r0, r0, #1
        cmp r0, #100
        ble loop


    (3)while(C) for()
        int i = 0;
        int sum = 0;
        while(i <= 100)
        {
            sum += i;
            i++;
        }
        
        int sum = 0;
        for(int i = 0; i <= 100; i++)
        {
        sum += i;
        }

        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

2.函数定义及调用
    (1)返回值 函数名(形参列表)
        {//函数体
            代码块
        }
    (2)pc => lr and lr =>pc
    
            b main              
        func
            mov r0, #1
            mov r1, #2
            add r3, r0, r1
            bx lr

        main
            mov r0, #100
            mov r1, #200
            bl func
            mov r3, #300
    (3)保护现场/恢复现场(压栈/弹栈)
        问题1:函数被调修改主调寄存器,问题二:函数嵌套时,无法正确返回。
        (1)栈类型空增 空减 满增 满减(ARM 2440))画图讲解
            空栈: *sp = xxxx        满栈: sp++
                  sp++                        *sp = xxxx 
            增栈: sp += 4           减栈: sp -= 4
        (2)栈顶指针寄存器初始化
            mov sp, #0x40001000  : 报错非立即数    
            ldr sp,    =0x40001000
            魔术棒 -> Target->IRAM1:#0x40000000 size:0x1000
        (3)保护现场 :stmfd (store(存储) multiple(多个) full(满) decrease(减少))
            STMDB<c> <Rn>{!}, <registers>
            <Rn>:栈顶指针寄存器
            {!},:入栈出栈后,栈顶指针寄存器自减自增
            <registers>:入栈出栈的寄存器列表
            
        (4)恢复现场 :ldmfd (load(加载) multiple(多个) full(满) decrease(减少))
            LDMFD<c> <Rn>{!}, <registers>
            GNU体系 保护现场、恢复现场由主调完成
                b main    
            func1
                mov r0, #10
                mov r1, #20
                cmp r0, r1
                movge r2, r0
                movlt r2, r1
                bx lr
                    
            func0
                mov r0, #1
                mov r1, #2
                add r3, r0, r1
                stmfd sp!, {r0-r12, lr}
                bl func1
                ldmfd sp!, {r0-r12, pc}
                ;bx lr

            main
                ldr sp, =0x40001000
                mov r0, #100
                mov r1, #200
                stmfd sp!, {r0-r12, lr}
                bl func0
                ldmfd sp!, {r0-r12, lr}
                mov r3, #300

            finish
                b finish
                end    
        《《《《《《《《《《《《《《《《《《《《《《《休息》》》》》》》》》》》》》》》》》》》》》》》
        
        (4)在汇编中调用C语言函数
            (1) 创建main.c
            (2) 声明 extern void c_add(void);
            (3) 导入 import c_add; (keil当中要求)
            (4) 保护现场 bl函数调用 恢复现场
            (5) 解决编译报错:asm.axf: Error: L6238E: start.o(reset) contains invalid call from '~PRES8 (The user did not require code to preserve 8-byte aligment of 8-byte data objects)' function to 'REQ8 (Code was permitted to depend on the 8-byte aligment of 8-byte data items)' function c_add.
                asm.axf: Finished: 0 information, 0 warning and 1 error messages.
                解决办法:栈对齐伪指令:preserve8 用于确保函数调用时栈指针保持 8 字节对齐
            (6) 创建工程自动添加了启动代码报错:
                (1)chong建覆盖工程
                (2)删除.sct文件
                (3)添加 start.s main.c 
                (4)重设软件配置
            (7) 魔术棒 -> Debug -> Use Simulator->Run to main(取消)
            (8) 魔术棒 -> Linker -> Use Memory Layout from Taget Dialog(勾选)
            (9) 魔术棒 -> Taget -> ROM1 -> Start: 0x0 Size:0x2000
            (10)函数传参:
                stmfd sp!, {r0-r12, lr}
                mov r0, #1
                mov r1, #2
                mov r2, #3
                mov r3, #4
                mov r4, #5
                stmfd sp!, {r4}
                bl c_add
                ldmfd sp!, {r4}
                ldmfd sp!, {r0-r12, lr}
        
        (5)在C语言中调用汇编    
            (1)导出 export func1;
            (2)声明 extern int func1(int a, int b);
        
        (6)修改工作模式
            CPSR M域修改
            MRS (read):    MRS<c> <Rd>, <spec_reg>
            MSR    (writ):    MSR<c> <spec_reg>, #<const>
                        MSR<c> <spec_reg>, <Rn>
            mrs r0, cpsr
            bic r0, r0, #(0x1FF << 0)
            orr r0, r0, #(0x10 << 0)
            mrs cpsr, r0
        (7)异常向量表    
            软中断异常:swi #7
        

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

ARM内核(主要指ARMv4-v7架构的经典A/R系列)通常有7种工作模式,主要用于处理异常和提供系统保护。模式的切换主要由硬件自动完成或通过软件指令触发。

模式 切换条件/触发场景
用户模式 (User) 正常程序执行的非特权模式。应用程序通常运行在此模式下。
系统模式 (System) 一种特权模式,与User模式共用寄存器。通过软件直接修改CPSR的模式位进入,用于运行需要特权的操作系统任务。
快速中断模式 (FIQ) 当处理器接受一个快速中断 (FIQ) 请求时自动进入,用于处理高速数据传输、紧急事件等。
普通中断模式 (IRQ) 当处理器接受一个普通中断 (IRQ) 请求时自动进入,用于处理一般的外设中断。
管理模式 (Supervisor) 复位后的默认模式。当执行软件中断指令 (SWI/SVC) 或进行软复位时自动进入。这是操作系统的内核模式。
中止模式 (Abort) 当发生内存访问失败时自动进入,例如:- 预取指中止:CPU试图从无效地址取指令。- 数据中止:指令试图访问无效的内存数据地址。
未定义模式 (Undefined) 当CPU遇到一条它无法识别(未定义)的指令时自动进入。

Cortex-A特有模式: 8.Monitor:是为了安全而扩展出的用于执行安全监控代码的模式:也是一种特权模式

9.HYP:测试

总结:除了用户模式系统模式,其他5种模式都与特定的异常一一对应。发生异常时,硬件自动切换模式;处理完异常后,通过特定的指令恢复回之前的模式。


11、异常向量表是什么?(同5)

异常向量表是内存中一块固定的、连续的区域,其中存放着各种异常处理程序的入口地址(或跳转到处理程序的指令)。

  • 工作原理:当异常发生时,ARM处理器会自动地、硬连线地根据异常类型,跳转到表中一个固定的偏移地址上。例如,发生复位异常,就跳转到0x00000000;发生IRQ中断,就跳转到0x00000018

  • 核心作用:它是连接硬件异常软件处理程序的桥梁。硬件通过固定机制自动定位处理程序,确保了异常响应的实时性和确定性

  • 典型布局(地址偏移量):

    • 0x00: 复位 (Reset)

    • 0x04: 未定义指令 (Undefined Instruction)

    • 0x08: 软件中断 (SWI/SVC)

    • 0x0C: 预取指中止 (Prefetch Abort)

    • 0x10: 数据中止 (Data Abort)

    • 0x14: 保留 (Reserved)

    • 0x18: IRQ

    • 0x1C: FIQ


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

  • 立即数:是指在指令编码本身中直接包含的操作数。执行时可以直接使用,无需从寄存器或内存中额外读取。

    • 例如:在指令 MOV R0, #0xFF 中,0xFF 就是一个立即数。

  • 判断12位立即数是否合法:ARM指令中的立即数并非完整的32位,而是由一个12位的编码字段(4位旋转值 + 8位立即数) 来表示。一个32位的常数是合法的12位立即数,当且仅当它可以通过以下方式生成:

    一个8位的数值(范围0-255),循环右移偶数位(0, 2, 4, ..., 30)后得到的32位常数。

    判断方法:您可以想象这个数能否被一个“字节+移位”的组合所表示。常见的非法立即数有:

    • 过大的数(如 0x12345678

    • 所有位模式中若出现连续 1 或连续 0 的“块”长度 > 8 位,基本就编不进去,(如 0x00000FFF 是合法的,因为它就是255循环右移0位;而 0x000001FF 很可能是非法的)


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

这三种都是分支(跳转)指令,但用途不同。

指令 全称 区别与用途
b Branch 单纯跳转。将程序计数器PC直接设置为目标地址。不保存返回地址,用于无需返回的跳转,如循环、条件分支。
bl Branch with Link 带链接的跳转。在跳转之前,自动将下一条指令的地址(返回地址)保存到链接寄存器LR(r14)。用于函数调用,因为函数结束后需要返回到调用处。
bx Branch and eXchange 跳转并切换指令集。跳转到目标寄存器中存储的地址。根据目标地址的最低位(bit[0])来决定后续执行ARM指令(T=0)还是Thumb指令(T=1)。用于从ARM代码跳转到Thumb代码,以及函数返回(bx lr)。

简单总结

  • b跳而不返(用于goto,循环)。

  • bl跳而必返(用于调用函数)。

  • bx跳并可换(用于状态切换和间接跳转)。


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

  • 硬件设计:ARM内核的压栈(PUSH)操作硬件上只支持向低地址增长(递减)

  • 栈指针特性:ARM的栈指针SP(r13)指向的是栈顶最后一个被压入的有效数据(满栈)。

因此,ARM内核采用的栈是 满递减栈 (Full Descending Stack, FD)

  • 满 (Full):SP指向栈顶最后一个有效数据。

  • 递减 (Descending):栈向内存低地址方向生长。压栈时SP减小,弹栈时SP增大。

补充:四种栈类型理论模型

栈的增长方向和栈指针(SP)的位置共同定义了栈的类型,共有四种组合:

类型 简称 增长方向 SP指向 压栈(PUSH)操作 弹栈(POP)操作
满递减 FD 向低地址 最后入栈的数据 先减SP,再存数据 先取数据,再加SP
空递减 ED 向低地址 下一个空位置 先存数据,再减SP 先加SP,再取数据
满递增 FA 向高地址 最后入栈的数据 先加SP,再存数据 先取数据,再减SP
空递增 EA 向高地址 下一个空位置 先存数据,再加SP 先减SP,再取数据


        

    
  


网站公告

今日签到

点亮在社区的每一天
去签到