学习arm汇编的主要目的是为了编写arm启动代码,启动代码启动以后,引导程序到c语言环境下运行。换句话说启动代码的目的是为了在处理器复位以后搭建c语言最基本的需求。因此启动代码的主要任务有:
1、初始化异常向量表;
2、初始化各工作模式的栈指针寄存器;
3、开启arm内核中断允许;
4、将工作模式设置为user模式;
5、完成上述工作后,引导程序进入c语言主函数执行;
因此汇编指令的学习主要是围绕这几个目的展开,主要学习跟上述目的相关的指令
格式
以下并非ARM处理器实际的指令(例如MOV,ADD等),而是写给汇编器看的命令,用于直到汇编器如何工作,属于伪操作
area reset, code, readonly
code32
entry
end

area:最重要的伪操作,用于定义一个段。程序、数据、堆栈等都需要被组织在不同的段中
reset:为段取得名字。reset具有很强的暗示性,通常用于复位向量段,即CPU上电或复位后首先执行的第一段代码所在的位置
code:指定该段的属性为代码,意味着这个段可包含可执行的指令
readonly:指定该段的属性为只读。对于代码来说,这些通常是默认且必须的
code32:表示后续指令使用32位的ARM指令集
thumb:表示后续指令使用16位的Thumb指令集
debug测试

编译后进行代码测试,以下图为例

注意:每行代码前必须加一个Tab,不然会报错,这是Keil的要求


左上角的部分按键,分别为重新启动,快速运行,暂停运行,运行一行。可以通过双击行号来设置断点,快速运行时会停止在断点行处
黄色箭头表示将要执行的代码,蓝色箭头表示选择的这行代码,左侧为代码内容的具体改变量
指令
MOV数据传输指令

immediate:立即数
register:寄存器 Thumb:16位短指令 ARM:32位长指令
shifted register:移位操作
用法
普通用法


MOV{S}<c> <Rd>, #<const>
MOV{S}<c> <Rd>, <Rm>
ARMv4...:适用范围
所有花括号及其内容都可以省略,尖括号不用写
mov:既可以大写MOV也可以小写mov
Rd:register destination目标寄存器
const:常量
Rm:register source源寄存器
S:条件标志位,如果不写则默认不影响条件标志位
c:条件码,如果不写则表示无条件执行

注:以分号';'作为注释标志(C语言中的//)
移位用法
ARS:Arithmetic Shift Right算数右移(保持符号位,高位补符号位)
LSL:Logical Shift Left逻辑左移(C语言中的<<)
LSR:Logical Shift Right逻辑右移(C语言中的>>)
ROR:Rotate Right循环右移(移出去的低位作为高位)例:1001→1100→0110
RRX:Rotate Right with Extend扩展右移
#<n>:立即数移位位数,取值范围0~32
<Rs>:Register Shift,用寄存器的值指定移位位数,取值范围0~31
在计算机中只识别二进制数据,计算机没有有无符号、浮点等概念


注意:以下例子R0、R1、R2固定为上图大小
ADD加法指令



立即数作为第二操作数: ADD{S}<c> <Rd>, <Rn>, #<const>
寄存器作为第二操作数寄存器: ADD{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
寄存器作为第二操作数移位量: ADD{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
shift:可选操作,是否进行移位操作,进行则在Rm后加逗号","并进行操作
type:寄存器移位操作(例lsl,ror)
Rn:源寄存器


注:汇编中没有类似于a=1+1这种写法,因为编译阶段计算不需要再机器指令中体现
SUB减法指令
立即数作为第二操作数: SUB{S}<c> <Rd>, <Rn>, #<const>
寄存器作为第二操作数寄存器: SUB{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
寄存器作为第二操作数移位量: SUB{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
格式和ADD相同,只需将ADD换成SUB即可


BIC指定位清零



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


作用:将第二操作数转换成二进制,其中为1的位置清零
ORR指定位置1
ORR{S}<c> <Rd>, <Rn>, #<const>
ORR{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}
ORR{S}<c> <Rd>, <Rn>, <Rm>, <type> <Rs>
用法与BIC相同,只需将BIC换成ORR
左移右移"<<"">>"也可以使用
条件判断标志NZCV
作为CRSR寄存器中条件判断标志位,四个符号各有其作用
|----------|----------------------------------------------------------------|
| 标志位 | 作用 |
| N(符号标志位) | 上条指令执行结果最高位为1,则N = 1,当结果作为有符号时解释为负值 |
| Z(零号标志位) | 上条指令执行结果为0,即bit0~bit31均为0,则Z= 1 |
| C(进位标志位) | 进行无符号解读,如果在加法过程中,最高位向更高位进位,或者减位时没有向更高位借位(大数减小数),则C = 1,否则C = 0 |
| V(溢出标志位) | 进行有符号解读,是否发生溢出(正数相加得负数,负数相加得正数) |


R0此时为0x80000000,最高位为1,且发生溢出


此时R1为0,执行结果为0,且减位时没有借位


此时R0为0,最高位进位,且结果为0
注意:指令后加"s"才可以对CPSR进行操作
条件码

指令后加条件码,会根据条件码是否为真来确认是否执行后续操作,可以理解为C语言中的条件if
cmp比较指令
CMP<c> <Rn>, #<const>
CMP<c> <Rn>, <Rm>{, <shift>}
CMP<c> <Rn>, <Rm>, <type> <Rs>
用来比较两个数的大小,并将结果存入条件判断标志位
三个数中找到最大值


B跳转指令

B<c> <label>
类比于C语言中的goto语句,跳转到指定行,通过更改R15来跳转指定行
BL指令
会将当前PC(R15)的值加上4备份到LR中,也就是指定行的下一行,由于ARM指令是四字节对齐,所以上下两个的地址会相差4
BX指令
将lr恢复到pc中
条件循环


标签不占空间
立即数
指在指令编码阶段就被固化到指令二进制流中的常数,它是指令操作数的一种直接形式,CPU 执行指令时无需从寄存器或内存中读取,可直接从指令字中解析获取该数值参与运算。简单来说,就是在汇编阶段能被汇编器直接编译进指令的字面常量

以mov为例,立即数就存放在imm12这里,表明立即数只分配12个字节,数据偏大则会失败


但是其取值并不是简单的0~4095,实际上,imm12分成了4 + 8两部分,其中4个字节作为Rotate旋转位,8个字节为imm8有效位
12位立即数判断标准
把某个数展开成2进制,该数必须存在一种循环右移(偶数次),使得移位后高24位全0,低8位即为有效imm8
例:
1,0~255全是立即数
2,1110 0110 0000 0000 0000 0000
3,1001 0000 0000 0000 0000 0011
经过右移后,低8位包含了所有的1,存入imm8中,而高4位旋转位存入循环右移的次数,至于为什么是偶数次,是因为四位最大数只是15,对于第三个例子来说不够用,所以一次右移就是移两位,将右移次数除以2便可存入旋转位中
特殊情况


该行代码编译成功,这是因为在编译器中对该行代码进行了有效替换,如上图所示,红线上方红色行代码为实际代码的位置及内容,黑色行为机器指令(MVN作用是取反)
MVN{S}<c> <Rd>, #<const>{, <shift>}
MVN{S}<c> <Rd>, <Rm>
MVN{S}<c> <Rd>, <Rm>, <type> <Rs>
非立即数写法ldr

LDR<c> <Rt>, <label>



实际上是数据单独放在32个bit位,指令单独放在32个bit位中
模仿C语言函数调用

类比C语言,运行main,运行twoMAX函数并保存返回地址→运行twoMAX函数并返回main中的执行部位的下一行
栈方式返回
然而PC和LR只有一个,对于多次调用函数,按照上例的方式明显不够返回地址,所以应该用栈的方式
类型
满减栈,满增栈,空减栈,空增栈,其中增减是由栈顶动态生长方向地址变化决定,满空根据栈顶指针是指向最后一个存入的数据或是第一个空闲位置决定,ARM为满减栈
空栈: *sp = xxxx 满栈: sp++
sp++ *sp = xxxx
增栈: sp += 4 减栈: sp -= 4
IRAM配置


位置挂在了0x40000000,一共0x1000大小(4K)

STMFD(STMFB)压栈指令
store memory full Descending,多寄存器存储(满减栈)

Rn:SP寄存器
!:是否让内核在压完栈后自动SP自加
registers:寄存器列表,可以填写多个寄存器
LDMFD(弹栈指令)
Load Multiple, Full Descending,多寄存器加载(满减栈

图例

和C语言不同的是,所谓全局变量可以通过函数更改,如果不想更改,可以在压栈时将对应变量,或者说是对应寄存器,写入{},也就是压入栈中,这样弹栈的时候便会和压栈时候有相同的值,但是弹栈的时候要一一对应地弹

