1. 立即数的概念

在ARM汇编中,立即数(Immediate)是指直接编码在指令中的常数,而不是存储在寄存器或内存中的值。例如,MOV R0, #0xFF 中的 0xFF 就是一个立即数。

  1. 如果某个数的数值范围是0~0xFF之间,那么这个数一定是立即数;
  2. 把某个数展开成2进制,这个数的最高位1至最低位1之间的二进制数序列的位数不能超过8位;
  3. 这个数的二进制序列凑够8位之后的的右边必须为偶数个连续的 0

在这里插入图片描述
例如:0x234 = 0000 0000 0000 0000 0000 0010 0011 0100
最高位1至最低位1之间的二进制数序列:1000 1101没有超过8位
末尾1的右边有2个0,所以0x234是立即数

0x3f4 = 0000 0000 0000 0000 0000 0011 1111 0100
最高位1至最低位1之间的二进制数序列:1111 1101 从第一个1开始到最后一个1之间没有超过8位
末尾1的右边有2个0,所以0x3f4是立即数

0x132 = 0000 0000 0000 0000 0000 0001 0011 0010
最高位1至最低位1之间的二进制数序列:1001 1001 从第一个1开始到最后一个1之间没有超过8位
末尾1的右边有1个0,不满足第二条,所以0x132不是立即数

2. b、bl、bx指令的区别

这三条指令都用于实现程序跳转,但行为有所不同:

指令 功能 是否保存返回地址
b (Branch) 无条件跳转到指定地址 否,不保存返回地址到LR寄存器
bl (Branch with Link) 跳转到指定地址,并将返回地址保存到LR寄存器 是,自动将下一条指令地址存入LR
bx (Branch and Exchange) 跳转到寄存器中指定的地址,并可切换指令集(ARM/Thumb) 否,但可通过寄存器间接跳转

使用场景

  • b:用于简单的循环、条件分支,不需要返回。
  • bl:用于函数调用,因为调用结束后可以通过 MOV PC, LR 返回。
  • bx:用于函数返回或切换到Thumb模式,例如 BX LR 从函数返回。

3. 栈的四种类型

ARM架构中,栈(Stack)根据指针移动方向和压栈/出栈方式,可以分为四种类型:

栈类型 描述
满增栈 (Full Ascending, FA) SP指向最后压入的数据,压栈时SP先增加,再存入数据
满减栈 (Full Descending, FD) SP指向最后压入的数据,压栈时SP先减少,再存入数据
空增栈 (Empty Ascending, EA) SP指向下一个空闲位置,压栈时先存入数据,SP再增加
空减栈 (Empty Descending, ED) SP指向下一个空闲位置,压栈时先存入数据,SP再减少

4. 空栈、满栈、增栈、减栈的定义

  • 满栈 (Full Stack):栈指针(SP)指向最后一个压入的数据。压栈时,先移动指针,再写入数据(*--SP = data)。
  • 空栈 (Empty Stack):栈指针(SP)指向下一个空闲位置。压栈时,先写入数据,再移动指针(*SP++ = data*SP-- = data)。
  • 增栈 (Ascending Stack):压栈时,栈指针向高地址方向移动(SP增加)。
  • 减栈 (Descending Stack):压栈时,栈指针向低地址方向移动(SP减少)。

5. ARM采用的栈类型

ARM处理器默认采用满减栈 (Full Descending, FD)

这意味着:

  • 栈指针(SP)指向栈中最后一个有效数据。
  • 压栈时,SP先自减(向低地址移动),然后将数据存入新的SP位置。
  • 出栈时,先从SP位置取出数据,然后SP自增。

ARM的 LDMFDSTMFD 指令就是专门为满减栈设计的批量数据加载/存储指令。

6. ldr和str指令的作用

LDRSTR 是ARM汇编中最基本的内存访问指令,用于在寄存器和内存之间传输数据。

  • LDR (Load Register):从内存地址加载数据到通用寄存器。

    • 格式:LDR Rd, [Rn, #offset]
    • 示例:LDR R0, [R1, #0x10] 将地址 R1+0x10 处的4字节数据加载到R0。
    • 作用:读取RAM、外设寄存器、Nor Flash等存储空间中的数据。
  • STR (Store Register):将通用寄存器的值写入到内存地址。

    • 格式:STR Rd, [Rn, #offset]
    • 示例:STR R0, [R1] 将R0的值写入到R1指向的地址。
    • 作用:将计算结果写回RAM或外设寄存器。

C语言对应关系

int i = 2;      // 对应 STR R0, [R1]  (假设R0=2, R1=&i)
int j = 3;      // 对应 STR R0, [R1]  (假设R0=3, R1=&j)
int sum = 0;    // 对应 STR R0, [R1]  (假设R0=0, R1=&sum)
sum = i + j;    // 对应 LDR R0, [R1]; LDR R2, [R3]; ADD R0, R0, R2; STR R0, [R4]

7. ldmfd和stmfd指令的作用

LDMFDSTMFD 是用于批量加载/存储多个寄存器的指令,专门配合ARM的满减栈(FD)使用。

  • STMFD SP!, {register list}:将寄存器列表中的值压入栈中(入栈)。

    • 示例:STMFD SP!, {R0-R3, LR} 将R0-R3和LR寄存器的值保存到栈中。
    • 作用:在函数调用开始时,保存现场(被调用者需要使用的寄存器和返回地址)。
  • LDMFD SP!, {register list}:从栈中弹出数据到寄存器列表(出栈)。

    • 示例:LDMFD SP!, {R0-R3, PC} 从栈中恢复R0-R3的值,并将返回地址加载到PC(程序计数器)。
    • 作用:在函数返回时,恢复现场并跳转回调用者。

典型函数调用模板

my_function:
    STMFD SP!, {R0-R3, LR}   ; 保存现场
    ; ... 函数体 ...
    LDMFD SP!, {R0-R3, PC}   ; 恢复现场并返回

8. bic和orr指令的作用

BICORR 是ARM汇编中的位操作指令,用于对寄存器中的特定位进行清零或置1。

  • BIC (Bit Clear):将操作数中为1的位清零,结果写回目标寄存器。

    • 格式:BIC Rd, Rn, Operand2
    • 示例:BIC R0, R0, #0xF0 将R0的第4-7位清零。
    • 作用:用于屏蔽或清除特定位。
  • ORR (OR):将操作数中为1的位进行逻辑或运算,结果写回目标寄存器。

    • 格式:ORR Rd, Rn, Operand2
    • 示例:ORR R0, R0, #0x0F 将R0的低4位置1。
    • 作用:用于设置或合并特定位。

应用场景:修改外设寄存器的特定位,而不影响其他位。例如,设置GPIO引脚为输出模式:

LDR R0, =GPIO_BASE
LDR R1, [R0, #GPIO_DIR]    ; 读取当前方向寄存器
ORR R1, R1, #(1 << 10)     ; 将第10位置1(设置为输出)
STR R1, [R0, #GPIO_DIR]    ; 写回

9. mrs和msr指令的作用

MRSMSR 是ARM汇编中用于访问特殊寄存器(主要是CPSR和SPSR)的指令。

  • MRS (Move to Register from Special register):将特殊寄存器的值读取到通用寄存器。

    • 格式:MRS Rd, CPSRMRS Rd, SPSR
    • 示例:MRS R0, CPSR 将当前程序状态寄存器(CPSR)的值读入R0。
    • 作用:读取处理器状态标志、中断屏蔽位、处理器模式等。
  • MSR (Move to Special register from Register):将通用寄存器的值写入到特殊寄存器。

    • 格式:MSR CPSR, RmMSR SPSR, Rm
    • 示例:MSR CPSR, R0 将R0的值写入CPSR。
    • 作用:修改处理器模式、开关中断等。

典型应用:修改处理器工作模式为USER模式

MRS R0, CPSR       ; 1. 读取CPSR到通用寄存器
BIC R0, R0, #0x1F  ; 2. 将低5位(模式位)清零
ORR R0, R0, #0x10  ; 3. 设置为USER模式(10000)
MSR CPSR, R0       ; 4. 写回CPSR

10. 汇编向C传参的注意事项

当汇编代码调用C函数时,参数传递遵循ARM过程调用标准(AAPCS):

  1. 参数个数 ≤ 4个:通过通用寄存器 R0 - R3 传递,依次对应第1到第4个参数。

    MOV R0, #3      ; 第一个参数 a = 3
    MOV R1, #5      ; 第二个参数 b = 5
    MOV R2, #7      ; 第三个参数 c = 7
    BL  max_of_three ; 调用C函数 int max_of_three(int a, int b, int c)
    ; 返回值保存在R0中
    
  2. 参数个数 > 4个:前4个参数仍通过R0-R3传递,第5个及之后的参数必须通过栈传递。调用者需要先将多余参数压栈,再调用函数。

    ; 假设调用 func(a, b, c, d, e, f)
    MOV R0, #a      ; 第1个参数
    MOV R1, #b      ; 第2个参数
    MOV R2, #c      ; 第3个参数
    MOV R3, #d      ; 第4个参数
    MOV R4, #e      ; 第5个参数
    MOV R5, #f      ; 第6个参数
    STMFD SP!, {R4, R5} ; 将第5、6个参数压栈
    BL  func
    ADD SP, SP, #8  ; 调用结束后,恢复栈指针
    
  3. 返回值:C函数的返回值通过 R0 寄存器返回给汇编调用者。

  4. 寄存器保存规则

    • 被调用者保存R4-R11LR,如果被调用函数使用了这些寄存器,必须在使用前保存,返回前恢复。
    • 调用者保存R0-R3R12,调用者如果需要在函数调用后继续使用这些寄存器的值,应自行保存。
  5. 栈对齐:ARM要求函数调用时栈指针(SP)必须8字节对齐,这是AAPCS规范的要求。

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐