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的ebparm中叫作FP
  • R13是栈顶,相当于x86的esparm中叫作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不为四字节对齐的话,会向下取整到四字节对齐后再读取

image-20230718115628492

指令

  • 内存操作数和立即数操作数不能同时出现
  • 内存操作数至多出现一次
  • 寄存器操作数总是在最前面

<opcode> {<cond>} {S} <Rd> , <Rn> { , }

  • opcode:指令
  • cond:条件码
  • S:可选后缀,加上“S”,在指令执行完毕后自动更新CPSR中的条件码标志位的值
  • Rd:目标寄存器
  • Rn:第一个操作数
  • op2:第二个操作数

条件后缀

image-20230714202416326

image-20230714211613500

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
.plt:00010308 10 CA 8C E2 ADD R12, R12, #0x10000
.plt:0001030C 04 FD BC E5 LDR PC, [R12,#(printf_ptr - 0x2030C)]! ; __imp_printf

objdump里

00010304 <printf@plt>:                                                                                                                                                                                             
10304: e28fc600 add ip, pc, #0, 12
10308: e28cca10 add ip, ip, #16, 20 ; 0x10000
1030c: e5bcfd04 ldr pc, [ip, #3332]! ; 0xd04

10304这里的add指令些许奇怪,最右边的12的意思是把0向右做20位循环移位

10308也是把16向右做20位循环移位

1030c最后还是从got表里拿了printf地址

https://reverseengineering.stackexchange.com/questions/16154/arm-add-instruction-with-shift#:~:text=Unlike%20a%20logical%20shift%2C%20the%20vacant%20bit%20positions,%2B%20%280%20%3C%3C%2012%29%20%3D%20PC%20%2B%200

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寄存器和代码段下面存放一些数据来赋值

#include <stdio.h>
#include <unistd.h>
int main() {
int x=0x12345678;
printf("hello,world");
printf("%x",x);
return 0;
}
.text:00010420                               ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00010420 EXPORT main
.text:00010420 main ; DATA XREF: _start+30↑o
.text:00010420 ; .got:main_ptr↓o
.text:00010420
.text:00010420 x= -8
.text:00010420
.text:00010420 00 48 2D E9 PUSH {R11,LR}
.text:00010424 04 B0 8D E2 ADD R11, SP, #4
.text:00010428 08 D0 4D E2 SUB SP, SP, #8
.text:0001042C 24 30 9F E5 LDR R3, =0x12345678
.text:00010430 08 30 0B E5 STR R3, [R11,#x]
.text:00010434 20 00 9F E5 LDR R0, =aHelloWorld ; "hello,world"
.text:00010438 B1 FF FF EB BL printf
.text:0001043C 08 10 1B E5 LDR R1, [R11,#x]
.text:00010440 18 00 9F E5 LDR R0, =aX ; "%x"
.text:00010444 AE FF FF EB BL printf
.text:00010448 00 30 A0 E3 MOV R3, #0
.text:0001044C 03 00 A0 E1 MOV R0, R3
.text:00010450 04 D0 4B E2 SUB SP, R11, #4
.text:00010454 00 88 BD E8 POP {R11,PC}
.text:00010454 ; End of function main
.text:00010454
.text:00010454 ; ---------------------------------------------------------------------------
.text:00010458 78 56 34 12 dword_10458 DCD 0x12345678 ; DATA XREF: main+C↑r
.text:0001045C ; char *const format
.text:0001045C 00 05 01 00 format DCD aHelloWorld ; DATA XREF: main+14↑r
.text:0001045C ; "hello,world"
.text:00010460 ; char *const off_10460
.text:00010460 0C 05 01 00 off_10460 DCD aX ; DATA XREF: main+20↑r
.text:00010460 ; .text ends ; "%x"
.text:00010460
.fini:00010464 ; ========================================================
.rodata:00010500 68 65 6C 6C 6F 2C 77 6F 72 6C+aHelloWorld DCB "hello,world",0 ; DATA XREF: main+14↑o

ida里反汇编出来的 LDR R3, =0x12345678这种带等号的一般都是ida解析好的按pc偏移来寻址的(ldr r3,[pc,#imm])、,ida里按q可以查看原始指令

image-20230719203353466