一、ARM汇编指令概要
学习arm汇编的主要目的是为了编写arm启动代码,启动代码启动以后,引导程序到c语言环境下运行。换句话说启动代码的目的是为了在处理器复位以后搭建c语言最基本的需求。因此启动代码的主要任务有:
- 初始化异常向量表;
- 初始化各工作模式的栈指针寄存器;
- 开启arm内核中断允许;
- 将工作模式设置为user模式;
二、相关指令学习
1.mov指令:
加载12位立即数到寄存器或转移一个寄存器的值到另外一个寄存器mov r0, #2 ;加载立即数2到寄存器r0,MOV{S}<c> <Rd>, #<const>
mov r1, r0
将r0寄存器的值加载到r1,MOV{S}<c> <Rd>, <Rm>
(1) 大多数指令的格式为opcode rd, rn ,rm,其中,rd是目标寄存器,rn是第一操作数寄存器。
2.add指令常用的两种方式
ADD{s}<c><Rd>,<Rn>,#<const>
ADD{s}<c><Rd>,<Rn>,<Rm>{shift}
3.sub指令
SUB{S}<c> <Rd>, <Rn>, #<const>
SUB{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
4.立即数
以上四条指令都有立即数作为第二操作数的情况,那么是什么立即数呢?准确的说这里所指的是12位立即数imm12。先说怎么判断某数是不是12位立即数,12位立即数的条件是:
1)如果某个数的数值范围是0~0xFF之间,那么这个数一定是立即数;
2)把某个数展开成2进制,这个数的最高位1至最低位1之间的二进制数序列的位数不能超过8位;
3)这个数的二进制序列凑够8位之后的的右边必须为偶数个连续的 0
5.ldr寄存器加载指令:
LDR{<c>}{<q>} <Rt>, <label> ;如ldr r0, =0x2FAB4
ldr指令多用于从ram中将一个32位的字数据传送到目的寄存器中
LDR<c> <Rt>, [<Rn>{, #+/-<imm12>}]
如:
LDR R0,[R1,#4] ;将内存地址为R1+4的字数据读入寄存器R0,这里的#8作为12位立即数是可以省略的
LDR<c> <Rt>, [<Rn>], #+/-<imm12>
如:
ldr r0, [r1], #8 ;将内存地址R1的字数据读入r0,之后r1+8
LDR<c> <Rt>, [<Rn>, #+/-<imm12>]!
如:
LDR R0,[R1,#8] ! ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。
6.bic指定位清零指令:
bic{S}<c> <Rd>, <Rn>, #<const>;
将rn中的字数据const为1的比特清零,把结果放入rd
7.orr指定位置位指令:
orr{S}<c> <Rd>,<Rn>,<Rn>,#<const>
8.s后缀
汇编指令的s后缀,几乎所有的汇编指令都可以在指令后面加上s后缀,s后缀的含义是在指令执行过程中会更新cpsr寄存器的N,V,C,Z位
N:在结果是有符号的二进制补码情况下,如果结果为负数,则N=1;如果结果为非负数,则N=0
Z:如果结果为0,则Z=1;如果结果为非零,否则Z=0
C:是针对无符号数最高有效位向更高位进位时C=1;减法中运算结果的最高有效位从更高位借位时C=0
V:该位是针对有符号数的操作,会在下面两种情形变为1,两个最高有效位均为0的数相加,得到的结果最高有效位为1;两个最高有效位均为1的数相加,得到的结果最高有效位为0;除了这两种情况以外V位为0
9.在指令增加条件
10.CMP指令
CMP比较指令用于比较两个寄存器的值或者比较一个寄存器和立即数的值,其原理是对待比较的两个数求差,看结果是否为0,这个指令会无条件修改N,V,C,Z位。
11.跳转指令
(1)b指令类似c语言的goto语句,能够实现无条件跳转。跳转时需要一个lable,表示要跳转到什么地方去
(2)bl指令: bl指令本质上就是把待跳转那行的地址装入pc寄存器,但是函数在调用完毕之后要回到调用处的下一行指令处执行,为了能够回到调用的下一行,需要使用bl指令。
区别
bl和b之间的区别就在于bl会在lr寄存器中保存回来的地址。
(3)bx指令:把lr中的地址装回pc中,跳转回跳转前的下一条语句;
三、栈
1.栈的实现类型:
(1)2440实现保护和恢复现场使用的栈是数组栈,即用一段连续的内存空间为栈提供空间。从数组栈的具体实现来看入栈的方式有四种做法:
- 空增:先写入数据,再让栈指针自增;
- 空减:先写入数据,再让栈指针自减;
- 满增:先让栈指针自增,再写入数据;
- 满减:先让栈指针自减,再写入数据。
(2)arm体系采用的方案是满减,但是在进行操作之前,我们必须告诉2440栈底的位置,这里我们把栈底设置为0x40001000,从地址0x40000000开始的0x1000这段内存空间对应的是2440内部的一段ram,总共4k。实际能够使用的内存空间为[0x40000000~0x40000FFF],设置栈底指针寄存器: ldr sp =0x40001000
2.入栈保护指令stmfd(STMDB)
STMFD<c> <Rn>{!}, <registers>
其中Rn表示栈底指针寄存器,< registers >表示需要入栈保护的寄存器,!表示入栈之后sp自动自减。如:
stmfd sp!, {r0, r1, r2, r3-r12, lr}
3.出栈恢复指令ldmfd(LDM/LDMIA/)
LDMFD<c> <Rn>{!}, <registers>
- 中Rn表示栈底指针寄存器,< registers >表示需要入栈保护的寄存器,!表示出栈之后sp自动自增。如:
ldmfd sp!,{r0-r12,lr}
保护现场和恢复现场的工作必须由主调函数;
在切换回之前的模式时,需要注意,恢复现场也要恢复模式:
四、c语言
1.在汇编中调用c语言编写的函数
设有c语言定义的函数void func_c(void);在汇编代码中调用该函数,只需用import声明函数名即可,之后就可以使用bl指令调用该函数,注意,既然是调函数,就一定要保护现场
2.向c函数传参
向c函数传参的方法很简单,如果参数个数小于等于4个,就直接用r0~r3传参,c函数返回值通过r0寄存器返回:
设有c函数:
int add_c(int a, int b, int c, int d)
{
return a + b + c + d;
}
如果参数个数大于4个,从第五个参数开始就需要通过栈来传参
注:在c语言中调用汇编编写的函数类似,不过在汇编中用export声明函数,同时需要在c语言中用extern声明函数,按照标准,调用者负责保护现场和恢复现场
3.切换arm内核的工作模式
切换工作方式的思路很简单,由于内核的工作模式是由cpsr寄存器的低5位来设置的,那么就可以先把cpsr读出来,更改低5位之后再设置进去。这里读取cpsr使用mrs指令,写cpsr寄存器用msr指令,需要注意的是在keil环境下写cpsr需要写成: msr cpsr_c r0;将r0的值写入到cpsr寄存器