ARM ASM
ARM ASM
手册 https://developer.arm.com/documentation/ddi0487/fc/
寄存器
Reg | APCS | mean |
---|---|---|
R0 | A1 | 工作寄存器 |
R1 | A2 | |
R2 | A3 | |
R3 | A4 | |
R4 | V1 | 变量寄存器(易变寄存器) |
R5 | V2 | |
R6 | V3 | |
R7 | V4 | 常用来存放系统调用号 |
R8 | V5 | |
R9 | V6/SB | 静态基址寄存器 |
R10 | V7/SL | 堆栈限制寄存器 |
R11 | V8/FP | 帧指针,保存栈帧,相当于x86 ebp |
R12 | IP | 指令指针(临时存放sp) |
R13 | SP | 栈顶指针,相当于x86 esp |
R14 | LR | Link Register链接寄存器,保存函数返回地址 |
R15 | PC | 程序计数器 |
CPSR | Current Program Status Register 程序状态寄存器 | |
SPSR | Saved Program Status Register 中断或异常时保存CPSR |
APCS :ARM Procedure Call Standard
R0 ~ R3
是用来依次传递参数的R0
还被用于存储函数的返回值R7
常用来存放系统调用号R11
是栈帧,相当于x86的ebp
,arm
中叫作FP
R13
是栈顶,相当于x86的esp
,arm
中叫作SP
R14(LP)
用来存放函数的返回地址的
关于pc寄存器是可以直接赋值的如mov pc,r0
如果是arm状态,读pc是会读取到当前指令往后两条指令的位置,如ldr r0,[pc],以mov r0,pc为例,一条指令四个字节的话 r0=pc+8
thumb状态下就是+4,thumb状态下,指令是变长的,即使当前指令下面两条指令是一个四字节一个二字节,依旧是读出来依旧是+4
ldr r0,[pc] 如果pc+4不为四字节对齐的话,会向下取整到四字节对齐后再读取
指令
- 内存操作数和立即数操作数不能同时出现
- 内存操作数至多出现一次
- 寄存器操作数总是在最前面
<opcode> {<cond>} {S} <Rd> , <Rn> { ,
- opcode:指令
- cond:条件码
- S:可选后缀,加上“S”,在指令执行完毕后自动更新CPSR中的条件码标志位的值
- Rd:目标寄存器
- Rn:第一个操作数
- op2:第二个操作数
条件后缀
mov
arm里mov
指令,通常用于寄存器与寄存器间的数据传输,也可以传递立即数
mov定长指令的话就不能操作一些特别大的立即数
mov r0,0x4567 写r0低16位(会对r0高16位做0拓展)
movt r0,0x1234 写r0高16位
mov r1, #0x10:r1 = 0x10
mov r1, r2:r1 = r2
mov r1, r2, LSL#2:r1 = r2 << 2
基本运算
add adds cmn 有s的既要结果又设置状态寄存器的标志位,不带s的只要运算结果
add r1, r2, #2
: r1 = r2 + 2
;
add r1,r2,r3
: r1=r2+r3
add r1, r2, LSL#2
: r1 = r2 << 2
sub subs cmp
sub r1, r2, r3
: r1 = r2 - r3
rsb
rsb r0,r1,#8
: r0=8-r1
and
与,只影响标志位的与指令tst
and and r0,r1,r2
: r0 = r1&r2
bic
bic r0,r1,r2
:r0=(~r1)&(~r2)
oor 或
eor
异或,只影响标志位的异或指令teq
实现not取反指令是通过与一个全1的数进行异或
lsl(logic shift left)
逻辑左移
lsr(logic shift right)
逻辑右移
asr(arithmatic shift right)
算数右移,左端用第31位的值来填充
ROR
循环右移
mvn(Move Not)
MVN r1,r2
: r1=~r2
访存指令
LDR
LDR {cond} Rd, <addr>
:加载指定地址(addr
)上的数据(字),放入到Rd
寄存器中。
STR
STR {cond} Rd, <addr>
:将Rd
寄存器中的数据(字)存储到指定地址(addr
)中。
ADR
ADR register,exper 一条小范围的地址读取伪指令,它将基于PC的相对偏移的地址值读到目标寄存器中
汇编器首先计算当前PC值(当前指令位置)到exper的距离,然后用一条ADD或者SUB指令替换这条伪指令,
这个adr在ida帮我们解析好了生成原本的adr伪指令,实际汇编里是add
ida里
.plt:00010304 00 C6 8F E2 ADR R12, 0x1030C |
objdump里
00010304 <printf@plt>: |
10304这里的add指令些许奇怪,最右边的12的意思是把0向右做20位循环移位
10308也是把16向右做20位循环移位
1030c最后还是从got表里拿了printf地址
https://stackoverflow.com/questions/16207865/weird-gas-arm-syntax
记忆技巧:数据在寄存器里处理且寄存器cpu可以直接访问,所以load肯定时从内存加载到寄存器,store时从寄存器存到内存
str r2, [r1, #2]
:寄存器r2
中的值被存放到寄存器r1
中的地址加2
处的地址中,r1
寄存器中的值不变;
str r2, [r1, #2]!
:与上一条一样,不过最后r1 += 2
,这里的{!}
是可选后缀,若选用该后缀,则表示请求回写,也就是当数据传送完毕之后,将最后的地址写入到基址寄存器r1(Rn
)中;
ldr r2, [r1], #-2
:将r1
寄存器里地址中的值给r2
寄存器,最后r1 -= 2
;
str r2, [r1, r3, LSL#2]
:将寄存器r2
中的值存储到寄存器r1
中的地址加上r3
寄存器中的值左移两位后的值所指向的地址中;
ldr r2, [r1], r3, LSL#2
:将r1
寄存器里地址中的值给r2
寄存器,最后r1 += r3 << 2
.
str r0,[sp,#-4]!
: 相当于push r0
ldr r0,[sp],#4
:相当于pop r0
块访存
https://blog.csdn.net/stephenbruce/article/details/51151147
ldm 依旧是从内存load到寄存器
stm从寄存器store到内存
用于内存描述
IA —-> Increment After 每次传送后地址加4
IB —-> Increment Before 每次传送前地址加4
DA —-> Decrement After 每次传送后地址减4
DB —-> Decrement Before 每次传送前地址减4
用于堆栈描述 如果递增,STM将向上,LDM向下
FA —-> Full Ascending 满递增堆栈 这里full的意思就是sp指向的栈地址里的数据是有用数据
FD —-> Full Descending 满递减堆栈
EA —-> Empty Ascending 空递增堆栈 这里Empty 的意思就是sp指向的栈地址里的是垃圾数据的 sp+4才是有用的
ED —-> Empty Descending 空递减堆栈
所以ldmfd=ldmia,stmfd=stmdb
STMFD SP!, {R4-R11,LR} 相当于push R4-R11 push LR
有!的话指令执行完SP值会变,否则不变
LDMFD SP!, {R4-R11,LR} 就是pop出栈
分支跳转
B imm 相当于x86 jmp
BL imm 跳过去 且把指令的下一条地址和cpsr寄存器的t位(t为1表示thumb模式)或运算后写入lr寄存器,
BLX imm blx+立即数的话一定有模式切换 call
BX reg 把reg最低一个bit写入cpsr的t位,然后把reg最低一bit置0,jmp到reg
BLX reg 比bx+寄存器行为多了一个把指令的下一条地址和cpsr寄存器的t位(t为1表示thumb模式)或运算后写入lr寄存器,不一定有模式切换
mov pc,reg是不会模式切换的
BEQ #不等于 想到于 jne
BCC #进位清除,应该就是 cmp后,进位为0就跳 感觉就像是 <
BGT # 大于
BLT #小于
BCS #进位设置,应该就是cmp后,有进位 感觉就像是 >=
调用约定和栈帧
前四个参数r0-r3,多余参数从右向左压栈
非易变寄存器 r4-r11
r12导入表寻址
实际看下来栈帧和x86差不多,只是有的经过了一层寄存器,而且因为定长指令的关系导致可以操作的立即数大小有限(有些情况可以利用循环移位来操作较大的立即数),所以程序经常可以看到利用pc寄存器和代码段下面存放一些数据来赋值
|
.text:00010420 ; int __cdecl main(int argc, const char **argv, const char **envp) |
ida里反汇编出来的 LDR R3, =0x12345678
这种带等号的一般都是ida解析好的按pc偏移来寻址的(ldr r3,[pc,#imm])、,ida里按q可以查看原始指令