基础汇编知识总结一

背景

嵌入式项目中,在处理linux崩溃问题时,有时候需要反汇编可执行文件或者动态库定位崩溃函数(见<Linux崩溃问题定位办法总结一>),这时就需要掌握一些基础的汇编知识,才可以大概弄明白汇编代码和源码的对应关系,方便定位问题,而且懂汇编也方便我们后续优化代码性能。

概述

汇编语言是一种低级编程语言,与机器指令直接对应,常用于嵌入式系统、操作系统内核开发等场景。理解汇编需要掌握寄存器、指令集、内存访问等核心概念。

注意

由于嵌入式领域架构繁多(ARM, RISC-V, AVR, MSP430等),这里以最常见的ARM Cortex-M系列为例,讲解核心概念。其汇编语法通常为UAL(统一汇编语言)。

寄存器介绍

寄存器是CPU内部的高速存储单元,用于存储指令、数据或地址。常见的寄存器包括:

  • PC(Program Counter):程序计数器,存储下一条要执行的指令地址。每执行一条指令,PC会自动递增(或根据跳转指令修改)。
  • LR(Link Register):链接寄存器,用于存储函数调用时的返回地址(如ARM架构中BL指令会将返回地址存入LR)。
  • SP(Stack Pointer):栈指针,指向当前栈顶地址。Cortex-M有两个SP:MSP(主堆栈指针,用于内核和异常)和PSP(进程堆栈指针,用于任务)。
  • 通用寄存器:如AXBX(x86),R0-R12(ARM),用于数据操作。

常用汇编指令分类

数据传送指令
  • MOV:将数据从源操作数复制到目标操作数。
    MOV R0, #5     ; 将立即数5存入R0
    MOV R1, R0     ; 将R0的值复制到R1
    
  • LDR/STR(ARM):加载/存储指令。
    LDR R0, [R1]   ; 从R1指向的内存地址加载数据到R0
    STR R0, [R1]   ; 将R0的值存储到R1指向的内存地址
    
算术运算指令
  • ADD/SUB:加法/减法。
    ADD R0, R1, R2 ; R0 = R1 + R2
    SUB R0, R1, #3 ; R0 = R1 - 3
    
  • MUL/DIV:乘法/除法(部分架构需特殊支持)。
逻辑运算指令
  • AND/ORR/EOR:按位与、或、异或。
    AND R0, R1, R2 ; R0 = R1 & R2
    EOR R0, R0, #0xFF ; R0 = R0 ^ 0xFF
    
控制流指令
  • B/BL(ARM):跳转/带链接跳转。
    B label      ; 无条件跳转到label
    BL func      ; 调用函数func,并将返回地址存入LR
    
  • CMP + Bcond:条件跳转。
    CMP R0, R1   ; 比较R0和R1
    BGT label    ; 若R0 > R1则跳转
    
栈操作指令
  • PUSH/POP:压栈/出栈。
    PUSH {R0, LR} ; 将R0和LR压入栈
    POP {R0, PC}  ; 出栈并恢复R0和PC(用于函数返回)
    

PC和LR的详细作用

  • PC
    程序计数器指向当前执行指令的下一条指令地址。执行顺序指令时,PC自动增加;跳转指令(如B)会直接修改PC的值。

  • LR
    在函数调用时(如BL指令),LR会自动保存返回地址。函数结束时可通过MOV PC, LRPOP {PC}返回到调用点。
    注意:若函数内嵌套调用,需提前保存LR到栈中,否则会被覆盖。

示例:函数调用与返回

main:
    BL func      ; 调用func,LR=下条指令地址
    ...

func:
    PUSH {LR}    ; 保存LR到栈
    ...          ; 函数体
    POP {PC}     ; 恢复LR到PC,实现返回

寻址模式介绍

寻址模式是汇编语言中指令获取操作数的方式,它决定了程序如何访问数据,直接影响代码的效率和灵活性,下面这个表格汇总了主要的寻址模式及其特点。

寻址模式

操作数位置

特点与应用

示例指令 (ARM)

立即寻址

指令本身

用于加载常数,执行速度最快

MOV R0, #0x55

寄存器寻址

CPU内部寄存器

寄存器间操作,无需访问内存,速度快

MOV R1, R2

直接内存寻址

内存地址

通过变量名或直接地址访问内存数据

LDR R0, [0x2000]

寄存器间接寻址

寄存器指向的内存

寄存器作为指针访问内存,适用于动态地址

LDR R0, [R1]

寄存器相对寻址

基址+固定偏移

访问数组元素或结构体成员

LDR R0, [R1, #4]

基址变址寻址

基址+变址寄存器

适用于二维数组或复杂数据结构

LDR R0, [R1, R2]

相对基址变址寻址

基址+变址+偏移

最灵活的寻址方式,用于复杂数据结构

LDR R0, [R1, R2, LSL #2]

多寄存器寻址

连续内存块

批量加载/存储寄存器,高效处理数据块

LDMIA R0!, {R1-R4}

堆栈寻址

堆栈区

专门用于堆栈操作,如函数调用保存现场

PUSH {R0, R1, LR}

相对寻址

PC + 偏移量

用于跳转指令(B, BL)和位置无关代码

B LOOP

下面具体介绍下每个寻址方式:

1.立即数寻址

特点​:操作数直接包含在指令中(即常数)。

用途​:用于加载常量或进行快速算术运算。

示例:

MOV R0, #0x55      ; R0 = 0x55(将立即数0x55存入R0)
ADD R1, R2, #10    ; R1 = R2 + 10(R2加立即数10,结果存入R1)

说明​:

  • #表示立即数。

  • 在C语言中类似:int a = 10;

2.寄存器寻址

特点​:操作数是CPU寄存器。

用途​:用于寄存器之间的数据传递或运算。

示例:

MOV R0, R1      ; R0 = R1(将R1的值复制到R0)
ADD R2, R3, R4  ; R2 = R3 + R4(R3和R4相加,结果存入R2)

说明​:

  • 这是最快的操作方式,因为寄存器在CPU内部,无需访问内存。

  • C语言类比:int a = b;b是变量,可能存储在寄存器中)。

3.寄存器间接寻址

特点​:操作数的地址存储在寄存器中,指令通过该寄存器访问内存。

用途​:用于访问数组、指针操作、外设寄存器等。

示例:

LDR R0, [R1]    ; R0 = *R1(从R1指向的内存地址加载数据到R0)
STR R2, [R3]    ; *R3 = R2(将R2的值存储到R3指向的内存地址)

说明​:

  • [ ]表示间接寻址,类似于C语言的指针解引用(*ptr)。

  • 在嵌入式开发中,常用于访问外设寄存器:

  • LDR R0, =0x40021000  ; 假设这是GPIOA的基地址
    LDR R1, [R0]         ; 读取GPIOA的输入数据寄存器
4.基址变址寻址

特点​:操作数的地址 = 基址寄存器 + 偏移量(立即数或寄存器)。

用途​:用于访问数组、结构体成员、外设寄存器组等。

示例:

LDR R0, [R1, #4]    ; R0 = *(R1 + 4)(基址R1 + 偏移4)
STR R2, [R3, R4]    ; *(R3 + R4) = R2(基址R3 + 偏移R4)

​在C语言中类似:

int arr[10];
int val = arr[2];  // 相当于基址arr + 偏移2*sizeof(int)

5.前变址寻址

特点​:先计算地址(基址 + 偏移),再访问内存,并且可以更新基址寄存器。

用途​:用于循环遍历数组或缓冲区。

示例:

LDR R0, [R1, #4]!   ; R0 = *(R1 + 4),然后 R1 = R1 + 4

说明​:

  • !表示更新基址寄存器(R1 += 4)。

  • C语言类比:

    int *ptr = &arr[0];
    int val = *(ptr += 2);  // ptr移动2个位置后取值

6. 后变址寻址

特点​:先使用基址寄存器访问内存,然后再更新基址寄存器(基址 += 偏移)。

用途​:适用于读取数据后移动指针的场景(如FIFO缓冲区)。

示例:

LDR R0, [R1], #4    ; R0 = *R1,然后 R1 = R1 + 4

C语言类比:

int val = *ptr++;  // 先取值,再移动指针

7.相对寻址

特点​:操作数的地址 = PC(程序计数器) + 偏移量。

用途​:用于跳转指令(B, BL)和加载常量(LDR)。

示例:

B label       ; 跳转到label(PC + 偏移量)
LDR R0, =0x12345678  ; 编译器会转换为PC相对寻址加载

在C语言中类比:

goto label;  // 编译器计算相对偏移

8. 多寄存器寻址

特点​:一条指令可以加载/存储多个寄存器。

用途​:用于函数调用时的寄存器保存(压栈/出栈)。

示例:

STMIA R0!, {R1-R4}   ; 将R1-R4的值存储到R0指向的地址,并递增R0
LDMDB R1!, {R2-R5}   ; 从R1指向的地址加载数据到R2-R5,并递减R1

说明​:

  • STM(Store Multiple)和 LDM(Load Multiple)常用于堆栈操作:

    PUSH {R0-R3, LR}   ; 保存寄存器到堆栈(STMDB SP!, {R0-R3, LR})
    POP {R0-R3, PC}    ; 从堆栈恢复寄存器(LDMIA SP!, {R0-R3, PC})

总结:不同寻址模式的应用场景

寻址模式

典型用途

C语言类比

立即数寻址

加载常量

int a = 10;

寄存器寻址

寄存器间运算

a = b + c;

寄存器间接寻址

指针操作、访问外设

int val = *ptr;

基址变址寻址

数组访问、结构体成员访问

arr[i]

前变址寻址

遍历数组(先计算地址再访问)

*(ptr += 2)

后变址寻址

FIFO缓冲区(先访问再移动指针)

*ptr++

PC相对寻址

跳转指令、常量池访问

goto label;

多寄存器寻址

函数调用时的寄存器保存(压栈/出栈)

save_registers()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值