ARM汇编
学习arm汇编的主要目的是为了编写arm启动代码,引导程序到c语言环境下运行。启动代码的目的是为了在处理器复位以后搭建c语言最基本的需求。
启动代码的主要任务:
- 初始化异常向量表;
- 初始化各工作模式的栈指针寄存器;
- 开启arm内核中断允许;
- 将工作模式设置为user模式;
- 引导程序进入c语言主函数执行;
格式
**伪操作:**不是 ARM 处理器实际的指令(如 MOV, ADD 等),而是汇编器的命令,用于指导汇编器如何工作
area reset, code, readonly
code32
entry
end
- area:用于定义一个段。程序、数据、堆栈等都需要被组织在不同的段中。
- reset: 段的名字。名字 reset 具有很强的暗示性,通常用于表示复位向量段,即CPU上电或复位后首先执行的第一段代码所在的位置。
- code: 指定该段的属性为代码,意味着这个段包含可执行的指令。
- readonly: 指定该段的属性为只读。对于代码段来说,通常是默认且必须的。
- code32: 表示后续指令使用 32位的 ARM 指令集。
标志位
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(相等) ne(不相等)
ge(大于等于) gt(大于) le(小于等于) lt(小于)
al(无条件执行)
指令
**手册目录:**指令(目的操作数,第一操作数,第二操作数)
immediate:立即数
register:寄存器
SP plus:栈指针
立即数: 12位立即数imm12的条件是:把某个数展开成2进制,该数必须存在一种循环右移(偶数位),使得移位后高24位全0,低8位即为有效imm8;
eg:0xA000 000A 是立即数
0xFFFF FFFF 不是立即数
mov指令
基本格式:
MOV{S}<c> <Rd>, #<const>
MOV{S}<c> <Rd>, <Rm>
MOV与位移指令的结合:

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

add指令
立即数作为第二操作数: ADD{S}<c> <Rd>, <Rn>, #<const>
寄存器作为第二操作数寄存器: ADD{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
寄存器作为第二操作数移位量: ADD{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
注意
(1){, <shift>} 其中{}代表可选择,","表示在使用时需要在Rm后添加"," shift 移位量(立即数)
(2) add r0, #3, #2 :没有这种形式,C语言int a = 1 + 2; 编译阶段计算, 不需要在机器指令中体现

sub指令
立即数作为第二操作数: SUB{S}<c> <Rd>, <Rn>, #<const>
寄存器作为第二操作数寄存器: SUB{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
寄存器作为第二操作数移位量: SUB{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>

ldr指令
向寄存器中加载一个非立即数,将RAM中的数据加载到通用寄存器

str指令
将通用寄存器中值写入到RAM空间

mvn指令
MVN{S}<c> <Rd>, #<const>
MVN{S}<c> <Rd>, <Rm>{, <shift>}
MVN{S}<c> <Rd>, <Rm>, <type> <Rs>

bic指令
BIC{S}<c> <Rd>, <Rn>, #<const>
BIC{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
BIC{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>

orr指令
ORR{S}<c> <Rd>, <Rn>, #<const>
ORR{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
ORR{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>

cmp指令
CMP<c> <Rn>, #<const>
CMP<c> <Rn>, <Rm>{, <shift>}
CMP<c> <Rn>, <Rm>, <type> <Rs>
比较并获取三个数中的最大值

b bl bx指令
B<c> <label>
b fun <==> ldr pc, =fun
直接跳转到目标地址,不保存返回地址
BL<c> <label>
bl fun
跳转前将返回地址存入LR寄存器
BX<c> <Rm>
bx lr <==> mov pc, lr
跳转到寄存器指定的地址,并可切换指令集,不保存返回地址

stmfd:将寄存器的值保存到栈区(入栈)
ldmfd:将寄存器的值从栈区取出(出栈)

while/dowhile
循环三要素
循环结束条件
推动循环趋向终结的语句
循环的循环体
dowhile实现0-100求和

while实现0-100求和


ARM栈
FD (满减栈)
栈指针(SP)指向最后压入的元素,即栈顶元素
操作方式:PUSH:SP先减少再存数据
POP:先取数据再增加SP
FA(满增栈)
栈指针(SP)指向最后压入的元素,即栈顶元素
操作方式:PUSH:SP先增加再存数据
POP:先取数据再减少SP
EA (空增栈)
栈指针(SP)指向下一个空闲位置
操作方式:PUSH:现存数据再增加SP
POP:SP先减少再取数据
ED (空减栈)
栈指针(SP)指向下一个空闲位置
操作方式:PUSH:现存数据再减少SP
POP:SP先增加再取数据
汇编与C语言函数的相互调用
汇编调用C函数
- 汇编->C函数传参:通过通用寄存器传参,将C语言函数返回值保存到R0寄存器
- 如果参数个数小于等于4个时,可以通过通用寄存器R0 - R3传参
- 当参数个数超过4个时,必须通过栈进行传参(入栈,出栈)
C语言调用汇编
- C -> 汇编传参:将C语言传递的参数保存到通用寄存器中
- 汇编程序结果通过R0寄存器返回
初始化supervisor模式栈
ldr sp, =0x40001000 // 初始化栈指针,复位后默认为supervisor模式
在进入主函数操作前,需要对当前默认的supervisor模式设置一个有效的栈空间,确保能够进行函数调用和临时数据存储,通过ldr命令将预先分配的地址(0x40001000)存入到栈指针
声明主函数
import main // 声明主函数
告知汇编器,符号main是在C语言文件中定义的,定义后可以调用
示例:在汇编中调用C语言函数

main.c

示例:在C语言函数中调用汇编函数
main.c
extern int _asm_min(int a,int b);


修改处理器工作模式为USER模式
步骤:
- 将cpsr寄存器中的值读到通用寄存器中
- 将通用寄存器中的低5位修改为:10000(USER)
- 将通用寄存器中的值写回到CPSR寄存器
mrs指令:读取某个特殊寄存器(cpsr)值到通用寄存器
msr指令:将通用寄存器的值写入到特殊寄存器(cpsr)
