一、温故知新
1、跳转指令
b{cond} <target_label>
相当于C语言中的goto
<target_label>是跳转地址,±32M
{cond}是条件码,先决条件
根据CPSR寄存器的NZCV位来决定是跳转还是不跳转
bl
l:带链接状态,将PC寄存器的值保存到LR寄存器中
BL跳转的范围是±32M
bx
ARM有两种工作状态,ARM与Thumb
x可以进行状态的切换
2、数据传输指令
mov 数据传输指令
mov{cond}{S} <Rd>, <OP2>
{cond}条件码,是先决条件
{S}决定是否影响CPSR寄存器的NZCV位
<Rd>一定是通用寄存器
<OP2>有三种表现形式:立即数\寄存器\寄存器移位之后的值
mvn 数据取反传输指令
mvn{cond}{S} <Rd>, <OP2>
{cond}条件码,是先决条件
{S}决定是否影响CPSR寄存器的NZCV位
<Rd>一定是通用寄存器
<OP2>有三种表现形式:立即数\寄存器\寄存器移位之后的值
3、数据处理指令
1)移位操作
LSL逻辑左移,空位补0
LSR逻辑右移,空位补0
ASR算术右移,最高位补符号位
ROR循环右移,被移出的位位将重新插入最高位
RRX带扩展的循环右移,新的最高位由CPSR寄存器的C位补充,拿最低位更新C位
2)算术运算指令
add加法指令
add{cond}{S} <Rd>, <Rn>, <OP2>
{cond}条件码,是先决条件
{S}决定是否影响CPSR寄存器的NZCV位
<Rd>目标寄存器
<Rn>操作寄存器
<OP2>有三种表现形式:立即数\寄存器\寄存器移位之后的值
adc带进位的加法指令
adc{cond}{S} <Rd>, <Rn>, <OP2>
{cond}条件码,是先决条件
{S}决定是否影响CPSR寄存器的NZCV位
<Rd>目标寄存器
<Rn>操作寄存器
<OP2>有三种表现形式:立即数\寄存器\寄存器移位之后的值
adc r0, r1, r2 @r0 = r1 + r2 + CPSR.C位
sub减法指令
sub{cond}{S} <Rd>, <Rn>, <OP2>
{cond}条件码,是先决条件
{S}决定是否影响CPSR寄存器的NZCV位
<Rd>目标寄存器
<Rn>操作寄存器
<OP2>有三种表现形式:立即数\寄存器\寄存器移位之后的值
sbc带借位的减法指令
sbc{cond}{S} <Rd>, <Rn>, <OP2>
{cond}条件码,是先决条件
{S}决定是否影响CPSR寄存器的NZCV位
<Rd>目标寄存器
<Rn>操作寄存器
<OP2>有三种表现形式:立即数\寄存器\寄存器移位之后的值
sbc r0, r1, r2 @r0 = r1 - r2 - NOT(CPSR.C位)
rsb逆向减法指令
rsb{cond}{S} <Rd>, <Rn>, <OP2>
{cond}条件码,是先决条件
{S}决定是否影响CPSR寄存器的NZCV位
<Rd>目标寄存器
<Rn>操作寄存器
<OP2>有三种表现形式:立即数\寄存器\寄存器移位之后的值
rsb r0, #0x08, r1 @语法错误
4、位运算指令
and &
orr |
eor ^
bic 清除
5、比较测试指令
cmp 比较指令(内部其实做了一个相减的操作)
tst 位测试指令
teq 相等测试指令
注意:比较测试指令即使不加S,默认也会影响CPSR寄存器的NZCV位
二、ARM汇编-加载存储指令
图示过程
1、单寄存器加载指令
ldr指令用于从存储器(内存)中加载数据到寄存器中
ldr{cond} Rd, addr @用于将一个字(32bit)从内存中加载到寄存器中
{cond}条件码
Rd寄存器
addr地址
加载多少个字节可以根据ldr后面的符号来指定 | |
ldrb | 用于将一个无符号单字节(8bit)从内存中加载到寄存器中 |
ldrh | 用于将一个无符号半字(16bit)从内存中加载到寄存器中 |
ldrsb | 用于将一个有符号单字节(8bit)从内存中加载到寄存器中 |
ldrsh | 用于将一个有符号半字(16bit)从内存中加载到寄存器中 |
ldrt | 用于将一个字(32bit)从内存中加载到寄存器中,并保证加载过程不中断 |
ldrbt | 用于将一个无符号单字节(8bit)从内存中加载到寄存器中,并保证加载过程不中断 |
ldrd | 用于将一个双字(64bit)从内存中加载到寄存器中 |
2、单寄存器字/无符号字节加载地址模式
地址模式 = 基地址 + 偏移地址
基地址 = 任意的通用寄存器
偏移地址 = 立即数\寄存器\寄存器移位
3、ldr示例
[ ]是把内容取出来 | |
ldr r0, [r1] | 将存储器地址为r1的字数据读到寄存器r0 |
ldr r0, [r1, #0x8] | 将存储器地址为r1 + 0x8的字数据读到寄存器r0 |
ldr r0, [r1, #-0x20] | 将存储器地址为r1 - 0x20的字数据读到寄存器r0 |
ldr r0, [r1, r2] | 将存储器地址为r1 + r2的字数据读到寄存器r0 |
ldr r0, [r1, -r2] | 将存储器地址为r1 - r2的字数据读到寄存器r0 |
ldr r0, [r1, r2, lsl #3] | 将存储器地址为r1 + r2 * 8的字数据读到寄存器r0 |
ldr r0, [r1, #0x8]! | 将存储器地址为r1 + 0x8的字数据读取到r0,并且将r1 + 0x8的值存入到r1 |
ldr r0, [r1, r2]! | 将存储器地址为r1 + r2的字数据读到寄存器r0,并且将r1 + r2的值存入到r1 |
ldr r0, [r1, r2, lsl #2] | 将存储器地址为r1 + r2 * 4的字数据读到寄存器r0,并且将r1 + r2 * 4的值存入到r1 |
注意:加“!”表示要更新地址 | |
ldr r0, [r1, #2] | 将存储器地址为r1 + 2的字数据读到寄存器r0 |
ldr r0, [r1], #2 | 将存储器地址为r1的字数据读到寄存器r0,并将r1 + 2的值存入到r1 |
ldr r0, [r1], r2 | 将存储器地址为r1的字数据读到寄存器r0,并将r1 + r2的值存入到r1 |
ldr r0, [r1], r2, lsl #3 | 将存储器地址为r1的字数据读到寄存器r0,并将r1 + r2 * 8的值存入到r1 |
注意:先索引(前变址),后索引(后变址) |
4、ldr和mov的区别
ldr指令用于从内存中加载数据到寄存器
ldr指令的应用场景
【1】从内存中加载变量或者常量到寄存器中
【2】加载数组元素到寄存器中
【3】进行数据的读取和赋值操作
mov指令用于在寄存器之间传递数据,或将立即数存储到寄存器中
mov指令的应用场景
【1】在寄存器之间传递数据
【2】将立即数加载到寄存器中
【3】进行数据的赋值操作
5、单寄存器存储指令
str指令用于将寄存器中的数据写入到内存中
str{cond} Rd, addr @从寄存器中将一个32bit的字数据传送到存储器中
{cond}条件码
Rd寄存器
addr地址
strb | 把寄存器中低8bit数据写入到存储器中 |
strh | 把寄存器中低16bit数据写入到存储器中 |
strt | 把寄存器中低8bit数据写入到存储器中,并且不会被中断打断 |
6、单寄存器字/无符号字节存储地址模式
地址模式 = 基地址 + 偏移地址
基地址 = 任意的通用寄存器
偏移地址 = 立即数\寄存器\寄存器移位
7、str示例
str r0, [r1] | 将r0寄存器中的数据写入到地址值为r1的存储器中 |
str r0, [r1, #0x08] | 将r0寄存器中的数据写入到地址值为r1 + 0x08的存储器中 |
str r0, [r1, r2] | 将r0寄存器中的数据写入到地址值为r1 + r2的存储器中 |
str r0, [r1, r2, lsl #3] | 将r0寄存器中的数据写入到地址值为r1 + r2 * 8的存储器中 |
str r0, [r1, #0x08]! | 将r0寄存器中的数据写入到地址值为r1 + 0x08的存储器中, 并将r1 + 0x08的值存入到r1 |
str r0, [r1, r2]! | 将r0寄存器中的数据写入到地址值为r1 + r2的存储器中, 并将r1 + r2的值存入到r1 |
str r0, [r1, r2, lsl #3]! | 将r0寄存器中的数据写入到地址值为r1 + r2 * 8的存储器中, 并将r1 + r2 * 8的值存入到r1 |
str r0, [r1], #0x08 | 将r0寄存器中的数据写入到地址值为r1的存储器中, 并将r1 + 0x08的值存入到r1 |
str r0, [r1], r2 | 将r0寄存器中的数据写入到地址值为r1的存储器中, 并将r1 + r2的值存入到r1 |
三、栈操作指令
栈是一种数据结构:FILO(first in last out)
1、ATPCS
ATPCS(ARM-Thumb Produces Call Standard),是ARM程序和Thumb程序中调用的基本规则,目的是为了使单独编译的C语言程序和汇编程序之间能够相互调用(内嵌汇编)
2、ATPCS中各寄存器的名称
【1】R0~R3 可以记作A1~A4,用于函数间传递参数
【2】R4~R11 可以记作V1~V8,用于存储函数的局部变量
【a】R7 在Thumb工作状态下记作wr(work register)
【b】R9 可以记作sb(static base)
可以在与位置无关的代码中保存内存的静态地址,在Uboot中R9用来保存.global data的起始地址
【c】R10 可以记作sl(stack limit[栈限制])
用来检测stack是否overflow,可以把stack的最低端写入sl寄存器中,这样处理后,如果sp指向的地址低于sl寄存器里面的值时,就会触发栈溢出的错误。
【d】R11 可以记作fp(frame pointer[帧指针])
在函数的调用过程中,通常使用栈帧结构,也就是sp指针总是指向程序的顶端,同时设置fp来保存当前函数在栈上的基地址
-------------------------------------------------------------------------
补充:
在多进程并发程序中,每一个进程都有自己的栈
在同一进程中调用函数和被调用函数使用的是同一个栈,我们该如何进行区分?
我们可以使用桟帧(stack frame)来区分,桟帧就是一个函数所使用的stack的一部分,所有函数的桟帧穿起来就是一个完整的栈,桟帧的两个边界分别是由fp和sp来限定
-------------------------------------------------------------------------
【3】R12 可以记作ip(内部程序调用暂存器)
在子程序中R13(sp)不能作其他的用途,寄存器R13在进入子程序和退出子程序时的值必须相等
【4】R13 可以记作sp(stack pointer[栈指针])
【5】R14 可以记作lr(link register[链接寄存器])
用于保存调用函数的返回地址
【6】R15 可以记作pc(program counter[程序计数器])
不能作为其他的用途来使用
3、数据栈的使用规则
对于ARM来说,支持4中栈,分别是:
【1】FD 满递减栈
【2】FA 满递增栈
【3】ED 空递减栈
【4】EA 空递增栈
---------------------------------------------------------
F:Full 满栈SP指向最后一个数据
E:Empty 空栈SP指向与最后一个数据相邻的下一个可写单元
D:Descend 递减,代表栈的增长方向
A:Ascend 递增,代表栈的增长方向
满栈:栈顶本身有数据(SP寄存器指向位置本来有数据)
空栈:栈顶本身没有数据(SP寄存器指向位置本来没有数据)
注意:我们的S5P6818的内核ARM-Cortex-A53的栈所使用的结构就是fd满递增栈
---------------------------------------------------------
4、指令
ldr:从存储器加载数据到寄存器
str:从寄存器存储数据到存储器
ldm:(load much)多数据加载,将地址上的值加载到寄存器上(相当于出栈)
stm:(store much)多数据存储,将寄存器上的值存储到地址上(相当于入栈)
1)执行格式
ldm{cond} mode Rn{!}, register{^}
stm{cond} mode Rn{!}, register{^}
Rn基址寄存器,存储传送数据的起始地址(Rn不能是R15)
!表示最后的地址写回到Rn中
register可以包含多个寄存器,用“,”隔开
如:{r1, r2, r3, r6-r9} 寄存器由小到大顺序排列
^不允许在用户模式(user)和系统模式(system)下运行
2)stm示例(入栈操作)
注意:在ARM的指令系统中,
对于递减的栈,
入栈操作的顺序是从右到左依次入栈
对于递增的栈,
入栈操作的顺序是从左到右依次入栈
3)ldm示例(出栈操作)
注意:在ARM的指令系统中,
对于递减的栈,
出栈操作的顺序是从左到右依次出栈
对于递增的栈,
出栈操作的顺序是从右到左依次出栈
4)反汇编代码
arm-cortex_a9-linux-gnueabi-gcc xxx.c -o xxx
arm-cortex_a9-linux-gnueabi-objdump -S xxx > xxx.s
注意:
xxx:文件名
objdump:反汇编器