1. 什么是立即数?怎么判断合法立即数
立即数就是直接写在指令里面的常数,前面带个#,数值直接编码在指令内部,不用去内存拿数据。 比如mov r0,#0x10,这里#0x10就是立即数。
这里很容易踩坑,ARM32的指令固定占32位,除去操作码、寄存器编号,留给立即数的位置不多。 合法立即数要求:
- 如果某个数的数值范围是0~0xFF之间,那么这个数一定是立即数;
- 把某个数展开成2进制,这个数的最高位1至最低位1之间的二进制数序列的位数不能超过8位;
- 这个数的二进制序列凑够8位之后的的右边必须为偶数个连续的 0
举个例子,#0xFF是没问题的;但像#0x104,拼不出来,就是非法立即数。 碰到非法立即数,就不能直接写#,得用LDR伪指令去加载。
2. ldr 和 str 是干啥的
CPU的通用寄存器不能直接访问内存,读写内存全靠这两条。
ldr:把内存的数据读到寄存器。
ldr r0, [r1]
r1里面存内存地址,把这个地址上的数据拿出来放到r0。
⚠️注意区分,还有一个ldr r0,=0x12345678,这个不是真正的内存访问指令,是伪指令,专门用来加载那些非法立即数。
str:反过来,把寄存器的数据写到内存里面。
str r0, [r1]
把r0的值,存进r1指向的内存地址。
简单记:ldr读,str写。
3. orr、bic指令
平时操作寄存器某几位的时候经常用。
orr,按位或,用来把某些bit置1。
orr r0, r0, #0x08
掩码哪一位是1,寄存器对应的bit就变成1,其他位不动。
bic,位清零,把指定bit清成0。
bic r0, r0, #0x08
掩码里面哪一位是1,就把寄存器对应bit清零,其余保持原样。
实操记一句:orr置1,bic清0。配置外设寄存器的时候用得巨多。
4. stmfd 和 ldmfd
ARM默认用满递减栈,这两条就是栈的入栈、出栈,批量操作多个寄存器。
stmfd sp!, {r0,r1,lr} 相当于入栈,把r0、r1、lr这些寄存器的值压到栈上保存现场,sp往低地址挪。
ldmfd sp!, {r0,r1,lr} 出栈,把栈上的数据拿回来,恢复到寄存器,sp往高地址走。
那个!千万不能丢!不加的话sp不会更新,栈直接乱掉,程序跑飞。
5. b、bl、bx三者区别
都是跳转,但用途差别很大。
b:单纯跳转,跳过去就完事,不会保存返回地址,跳走之后回不来。
b loop
bl:函数调用用的。跳转之前,会把下一条指令地址存到lr(r14)寄存器。函数执行完靠lr回到原来的地方。
bl func
bx:跳转到寄存器存的地址,还能切换ARM、Thumb指令集。我们写代码最常见就是bx lr,做函数返回。
总结:
- b:随便跳,不保存返回地址
- bl:调用函数,存返回地址到lr
- bx:按寄存器地址跳转,用来返回,切换指令集
6. mrs、msr
CPSR这个状态寄存器比较特殊,普通mov指令不能直接读写。
mrs:读。把cpsr的值读到通用寄存器。
mrs r0, cpsr
msr:写。把通用寄存器的值写回cpsr。
msr cpsr, r0
比如切换处理器模式,流程就是先用mrs读cpsr到r0,修改r0里面对应的bit,再msr写回cpsr。 有个坑:用户模式下改不了CPSR模式位,得在特权模式才可以改。
7. ARM栈一共几种?实际用哪种
一共4种,看sp指向哪、栈往哪个方向增长:
- FD 满递减栈:sp指向已经存好的有效数据;入栈sp向低地址减小
- FA 满递增栈:sp指向有效数据;入栈sp向高地址变大
- ED 空递减栈:sp指向下一个还没存东西的空位;入栈往低地址
- EA 空递增栈:sp指向下一个空位;入栈往高地址
名词简单理解: 满栈:sp指向的位置有有效数据;空栈:sp指向的地方是空的。 增栈:入栈往高地址;减栈:入栈往低地址。
我们裸机开发默认用 FD满递减栈。
简单复盘
今天这些都是常考的基础点,过一遍:
- 立即数是指令内部的常量,ARM有严格的编码约束,拼不出来就用LDR伪指令;
- ldr读内存,str写内存;
- orr置位,bic清零;
- stmfd入栈保存现场,ldmfd出栈恢复,
!不能漏; - b直接跳转,bl做函数调用存lr,bx用来返回;
- mrs读cpsr,msr写cpsr;
- 4种栈,裸机默认满递减栈FD。