01-ARM架构汇编

一、基础概念铺垫

  1. RISC:ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:

    ① 对内存只有读、写指令

    ② 对于数据的运算是在CPU内部实现

    ③ 使用RISC指令的CPU复杂度小一点,易于设计
    image

    对于上图所示的乘法运算a = a * b,

    在RISC中要使用4条汇编指令:

    ① 读内存a

    ② 读内存b

    ③ 计算a*b

    ④ 把结果写入内存

  2. 寄存器:在CPU内部,使用寄存器保存下图a、b、a+b值,ARM架构常用 32 位寄存器(R0-R15),其中:

  • R0-R7:通用寄存器(函数调用时常用 R0-R3 传递参数)
  • R13(SP):栈指针寄存器(指向栈顶)
  • R14(LR):链接寄存器(保存函数返回地址)
  • R15(PC):程序计数器(指向当前执行指令的下一条)
    image
    image

xPSR 是一个 32 位寄存器,主要用于保存程序运行中的状态信息,包括运算结果的标志、当前执行状态、中断信息等。它由三个部分组成,可单独访问也可整体访问:

  • APSR(应用程序状态寄存器):保存算术 / 逻辑运算产生的标志位(与条件码直接相关),是 xPSR 中最常用的部分。关键标志位(32 位中的高 4 位,bit31-bit28):

    • N(Negative,位 31):负标志。运算结果为负数(最高位为 1)时置 1,否则置 0。
    • Z(Zero,位 30):零标志。运算结果为 0 时置 1,否则置 0。
    • C(Carry,位 29):进位 / 借位标志。
      • 加法时结果产生进位(无符号数溢出)置 1;
      • 减法时结果产生借位(无符号数不够减)置 1;
      • 移位指令中,最后移出的位会存入 C。
    • V(Overflow,位 28):溢出标志。有符号数运算时结果超出表示范围(溢出)置 1,否则置 0。

    例如:执行 ADD R0, R1, R2 后,APSR 的 N/Z/C/V 会根据 R1+R2 的结果自动更新。

  • IPSR(中断程序状态寄存器):保存当前正在服务的中断号(仅在中断处理时有效),用于标识当前响应的是哪个中断。

  • EPSR(执行程序状态寄存器):保存与指令执行相关的状态,例如:

    • Thumb 状态标志(Cortex-M 只支持 Thumb 指令集,此位固定为 1);
    • 异常返回地址的相关信息等。
  1. 条件码:ARM 指令可加条件后缀(如EQNE),根据 CPSR(当前程序状态寄存器)的标志位执行,实现条件判断(类似高级语言的if)。

二、数据处理指令

1. MOV:数据移动(寄存器→寄存器 / 立即数→寄存器)

  • 格式MOV{条件} 目标寄存器, 源数据

  • 功能:将源数据(立即数或寄存器值)复制到目标寄存器

  • 示例:

    MOV R0, #10      ; R0 = 10(立即数10存入R0,#表示立即数)
    MOV R1, R0       ; R1 = R0(将R0的值复制到R1)
    MOVNE R2, #0     ; 若条件为NE(不相等),则R2=0(带条件的移动)
    

2. ADD/SUB:加法 / 减法

  • 格式ADD{条件} 目标寄存器, 操作数1, 操作数2SUB格式相同)

  • 功能目标 = 操作数1 ± 操作数2(操作数可以是寄存器或立即数)

  • 示例:

    ADD R3, R1, R2   ; R3 = R1 + R2
    ADD R4, R0, #5   ; R4 = R0 + 5
    SUB R5, R3, #2   ; R5 = R3 - 2
    SUB R0, R0, #1   ; R0 = R0 - 1
    

3. CMP:比较(影响标志位,用于条件判断)

  • 格式CMP 操作数1, 操作数2

  • 功能:计算操作数1 - 操作数2,结果不保存,但会更新 CPSR (程序状态寄存器)的标志位(Z:零标志,N:负标志等),供后续条件指令使用。

  • 示例:

    CMP R0, R1       ; 比较R0和R1的值
    ; 若R0=R1,Z标志置1;若R0>R1,C标志置1;若R0<R1,N标志置1
    

三、跳转指令(控制程序流程)

1. B:无条件跳转(不保存返回地址)

  • 格式B{条件} 目标标签

  • 功能:直接跳转到目标标签处执行,不保存当前地址(无法返回)。

  • 示例:

    B loop           ; 无条件跳转到loop标签
    BEQ exit         ; 若CMP结果为相等(Z=1),则跳转到exit(带条件的跳转)
    

2. BL:带链接的跳转(保存返回地址,用于函数调用)

  • 格式BL{条件} 目标标签

  • 功能:跳转前将当前 PC(下一条指令地址)存入 LR(R14),再跳转到目标标签,用于函数调用(可通过BX LR返回)。

  • 示例:

    BL func          ; 调用func函数(返回地址存入LR)
    ...              ; func执行完后,通过BX LR返回这里继续执行
    

3. BX/BLX:跳转并切换指令集(Thumb/ARM)

  • 格式BX 寄存器BLX类似,同时保存返回地址)

  • 功能:跳转到寄存器指向的地址,若寄存器最低位为 1,切换到 Thumb 指令集;为 0 则保持 ARM 指令集。BX LR是函数返回的常用方式。

  • 示例:

    BX LR            ; 从函数返回(跳转到LR保存的地址)
    

四、内存访问指令(ARM 是 “加载 - 存储” 架构,只能通过这些指令访问内存)

1. LDR:从内存加载数据到寄存器

  • 格式LDR{条件} 目标寄存器, [源寄存器, 偏移量]

  • 功能:从源寄存器+偏移量指向的内存地址,读取数据到目标寄存器。

  • 示例:

    LDR R0, [R1]       ; R0 = 内存[R1](R1指向的内存数据加载到R0)
    LDR R2, [R3, #4]   ; R2 = 内存[R3+4](偏移4字节的内存数据加载到R2)
    LDR R4, [R5, R6]   ; R4 = 内存[R5+R6](寄存器偏移)
    

2. STR:将寄存器数据存储到内存

  • 格式STR{条件} 源寄存器, [目标寄存器, 偏移量]

  • 功能:将源寄存器的值,写入目标寄存器+偏移量指向的内存地址。

  • 示例:

    STR R0, [R1]       ; 内存[R1] = R0(R0的值存入R1指向的内存)
    STR R2, [SP, #-4]! ; 先SP=SP-4,再内存[SP] = R2(栈操作常用,!表示更新SP)
    

五、栈操作指令(函数调用时保存 / 恢复寄存器)

1. PUSH:将寄存器压入栈(SP 减小)

  • 格式PUSH {寄存器列表}

  • 功能:将列表中的寄存器值依次压入栈(栈是向下生长的,SP 自动减小)。

  • 示例:

    PUSH {R0, R1, LR}  ; 将R0、R1、LR的值压入栈(保存现场)
    

2. POP:从栈中弹出数据到寄存器(SP 增大)

  • 格式POP {寄存器列表}

  • 功能:从栈顶依次弹出数据到列表中的寄存器(SP 自动增大)。

  • 示例:

    POP {R0, R1, PC}   ; 从栈中恢复R0、R1的值,最后将栈顶值存入PC(实现返回)
    

六、常用条件码(指令后缀)

ARM 指令可加条件后缀,根据 CPSR 标志位决定是否执行,常用如下:

条件码 含义(标志位) 对应场景
EQ 相等(Z=1) if (a == b)
NE 不相等(Z=0) if (a != b)
GT 大于(N=0 且 Z=0) if (a > b)
LT 小于(N=1) if (a < b)
AL 总是执行(默认,可省略) 无条件执行

示例

CMP R0, #10    ; 比较R0和10
BEQ equal      ; 若相等(Z=1),跳转到equal
BNE not_equal  ; 若不相等(Z=0),跳转到not_equal
equal:
  MOV R1, #0
not_equal:
  MOV R1, #1

七、C函数的反汇编

C函数:

int add(volatile int a, volatile int b)
{
	volatile int sum;
    sum = a + b;
    return sum;
}

让Keil生成反汇编:
image

为例方便复制,制作反汇编的指令如下:

fromelf  --text  -a -c  --output=xxx.dis  xxx.axf

C函数add的反汇编代码如下:

    i.add
    add
        0x08002f34:    b503        ..      PUSH     {r0,r1,lr}
        0x08002f36:    b081        ..      SUB      sp,sp,#4
        0x08002f38:    e9dd0101    ....    LDRD     r0,r1,[sp,#4]
        0x08002f3c:    4408        .D      ADD      r0,r0,r1
        0x08002f3e:    9000        ..      STR      r0,[sp,#0]
        0x08002f40:    bd0e        ..      POP      {r1-r3,pc}
  • 第一列(地址):指令在单片机内存(通常是 Flash)中的存储地址(如0x08002f34),CPU 通过这个地址找到要执行的指令(PC 寄存器会指向这个地址)。
  • 第二列(机器码):二进制指令的十六进制形式(16 进制比二进制简短,方便显示),例如b503对应的二进制是10110101 00000011,这串二进制是 CPU 能直接解析的 “命令”。
  • 第三列(汇编码):机器码的助记符,用汇编(如PUSHADD)表示指令功能,让开发者能理解这条指令在做什么(比如PUSH {r0,r1,lr}表示 “将 r0、r1、lr 寄存器压入栈”)。
posted @ 2025-09-24 09:22  _KingRoc  阅读(115)  评论(0)    收藏  举报