精简指令集 RISC

ARM属于精简指令集,大约100条左右的指令,先比较于x86汇编,arm不能直接操作内存的加减,只能控制寄存器,所以需要将内存ldr到寄存器中进行操作后返回内存。

比x86(CISC)快

RISC指令集允许通过缩短时钟周期来加速代码执行

端序

arm3之前小端序,之后的架构支持大端序,可以手动设置,而x86和x64仅仅支持小端序。

ARM 基本特性

特权模式

1、不同于x86架构的r0内核模式和r3用户模式,arm提供了更多的特权模式(命名用的是英文的缩写)。

  • USR:用户模式 ---> R3:用户运行的层级
  • FIQ:快速中断请求模式
  • IRQ:中断请求模式
  • SVC:管理模式(supervisor) ---> R0:一般操作系统内核运行的层级
  • MON:监视模式
  • ABT:终止模式
  • UND:未定义指令模式
  • SYS:系统模式

thumb

都属于arm架构,只不过是指令的长度不同,一个arm程序中可以混用两种指令长度的模式,只需要在汇编代码后面标注就可以。

ARM指令为32位、Thumb为16或者32位。

执行状态选择

0、默认状态下是ARM模式,直到出现显示或者隐式的转换。

1、利用BX、BXL进行跳转的时候,目标寄存器最低位是1(奇数),会切换到thumb模式。

2、程序状态寄存器(CPSR)的T标志位被置起,则切换到thumb模式。

3、32位的thumb指令后面用.W进行标注。

版本问题

thumb-1 用于ARM v6及之前的体系结构,ARM v7只用thumb-2.

条件执行

arm支持 条件执行 :在指令中编码了算数条件,满足条件才会执行。

优点:

  • 提高了代码密度
  • 减少执行指令的数目

ARM 的每一个指令都支持 条件执行 ,但是默认都是无条件执行的。

桶式位移器

相当于指令的合并操作,在一个指令中包含多步操作。

MOV R1, R0, LSL #1    ;将r0寄存器左移一位后,传递给r1

运行ARM程序

运行

ubuntu1804环境,成功安装qemu,执行显示缺少ld-linux.so.3文件,通过命令

sudo find / -name "ld-linux.so.3"

找到对应的so文件,然后使用命令

qemu-arm -L /usr/arm-linux-gnueabihf/lib/ call_ret

运行arm文件。

或者编译的时候直接使用-static静态编译可以解决问题

避免每次运行的时候使用qemu-***的形式,可以下载

apt install binfmt*

调试

使用gdb远程调试的原理,先本机开一个运行端口,然后gdb远程访问。

数据类型 & 寄存器

ARM 的操作都是基于寄存器的操作,无法像x86那样可以直接操作内存中的内容。

换算

32bit = 4byte = 1word = 1/2dword。

16bit = 半字(half word),不知道为啥会单独出一个半字的数据类型。

16bit:扩展后缀为-h或者-sh对应着,

byte:-b或者-sb对应着字节

ldr = 加载字,宽度四字节
ldrh = 加载无符号的半字,宽度两字节
ldrsh = 加载有符号的半字,宽度两字节
ldrb = 加载无符号的字节
ldrsb = 加载有符号的字节
str = 存储字,宽度四字节
strh = 存储无符号的半字,宽度两字节
strsh = 存储有符号的半字,宽度两字节
strb = 存储无符号的字节
strsb = 存储有符号的字节

寄存器

定义了16个32bit通用寄存器(R0-R15)r0一般也用来放返回值,最后三个有特殊用处,0-12是通用的。

r11:栈帧寄存器

栈帧寄存器,相当于是rbp

r13:栈寄存器

栈指针寄存器,SP。相当于x86架构中的rsp寄存器,永远指向栈顶端。

r14:连接寄存器

连接寄存器,LR。在函数调用中保存返回地址。在BL的时候会把返回地址保存在这个寄存器中,x86会保存在栈顶。

r15:计数寄存器

程序计数寄存器,每次执行一个+8或者+4,类似于rip指令,但是他是支持arm程序直接读写的,并且会执行并跳转,也就是说他并不会一直指向下一条指令,

CPSR:程序状态寄存器

类似于x86里的EFLAG或者RFLAG寄存器,保存程序的一些状态。

E

大小端标志位,0-->小端

T

thumb标志位,thumb状态为1

M

模式标志位,确定当前特权模式。

系统级控制设置

arm利用 协处理器 来支持额外的指令和系统级设置。

以 MMU(内存 管理 单元) 为例。如果系统是支持的,那么就要要向内核或者启动代码提供对应的接口,在x86架构中,这些接口会放在CR0-CR4寄存器中。

在ARM中, 有16个协处理器,CP0-CP15(P0-P15),每个协处理器有16个寄存器和对应的8个操作码。

协处理器只能通过MRC读和MCR写这俩指令来访问。这俩指令的参数一般是编号的形式,例如:读出转换基址寄存器并保存到CR0中。读出p15中的c2和c0保存在r0中。

这些东西常用在固件中,或者嵌入式开发之类的。

MRC p15, 0, r0, c2, c0, 0

x86中的控制寄存器

CPU架构中共有CR0、CR1、CR2、CR3、CR4、CR8共6个控制寄存器,如下图。

image-20230323101414446

CR0:包含当前处理器运行的控制标志。

CR1:保留。

CR2:包含发生页面错误时的线性地址

CR3:页面目录表(Page Directory Table)的物理地址

CR4:包含处理器扩展功能的标志位。

CR8:提供对任务优先级寄存器(Task Priority Register)的读写(仅在64位模式下存在)。

对控制寄存器的读写是通过MOV CRn指令来实现。

ARM 指令集

1、连续横杠间断逗:

PUSH    {R11,LR}  ;将R11和LR寄存器的值push到栈中。
PUSH    {R11,R15} ;将R11 到 R15中的值push到栈中。

2、更新基址用叹号:

STM R1, {R3-R10}   ; R1 = 数组
STM R1!, {R3-R10}  ; R1 = 数组+1,将R1的地址更新为R10元素之后的一个

数据加载 & 保存

LDR & STR

ldr:从内存向寄存器中加载数据

str:从寄存器中向内存加载数据

从内存中加载和保存1,2,4字节的数据。

LDR R3, [R0]    ;取R0地址里的内容给R3寄存器指向的地址 -- R3 = *R0
STR R3, [R0]    ;取R3寄存器的值给R0指向的地址 -- R0 = *R3

这里也涉及到了ARM中的9种寻址方式。

  • 立即寻址
  • 寄存器寻址
  • 寄存器间接寻址
  • 寄存器移位寻址
  • 基址变址寻址
  • 相对寻址
  • 多寄存器寻址
  • 堆栈寻址(块拷贝寻址)

实际运用的时候不需要直到是什么方式寻址,能看懂就行了。

LDM & STM

这个和上面的指令一样,区别是操作的数据大小不同,r结尾的是1,2,4字节,m结尾的是操作数据块。

存在四种模式:

  • IA:后递增,写回最后一个地址+4字节地址的地址。
  • IB:前递增,把数据存储在基地址+4的地址,写回最后一个地址。
  • DA:后递减,最后的地址是基地址,写回最低地址-4。
  • DB:前递减:最后的地址是基地址-4,写回最低地址。

类似于x86架构下的rep和movs指令,比如c语言里的memcpy(有时会直接以内联汇编的形式将其附加到代码中,而不是显示的调用这个函数)的功能。

push & pop

这俩个x86架构下的没啥区别,就是x86一般一次一个,这个可以push很多一起进栈。一般用作函数边界的确定。

函数调用

函数调用涉及到了

  • 局部变量
  • 分支跳转
    • 每一次跳转都可以选择thumb和arm
  • 返回值
    • 默认使用r0寄存器
  • 返回地址
    • 可以用栈也可以用LR寄存器,但是用栈的话需要显示的将地址pop到PC寄存器中,否则默认LR寄存器。
  • 调用约定
    • 前4个32位参数放到r0-r3寄存器,其余的用栈。

这几个方面的内容。

跳转指令

B:branch

1、很少遇到的一种无条件跳转,类似于jmp指令。通常用于循环或者判断中,或者调用永不返回的函数

2、只能使用偏移量(当前地址)作为参数,不能跳转寄存器,b R0是非法的

BX:branch exchange

跳转并交换,可以在跳转的时候选择arm或者thumb模式(X结尾的指令),根据目标地址最低位是不是1确定。可以用来跳转寄存器BX R0是合法的。

BX LR 类似于 RET指令

跳转并连接指令,类似于call指令,在跳转之前先将返回地址保存到LR寄存器中。

作用和B指令一样,只能跳偏移,不能跳寄存器

BLX:branch linke exchange

如果目的地在32mb之内,使用BL指令较多,跳转地址未知或者不确定,使用BLX指令

arm下调用库函数使用BL

thumb下调用库函数使用BLX

计算

  • LSL , (LSR ASR):左移和右移
  • ROR , ROL:循环左右移
  • ADD , SUB:加减
  • MUL,:乘法,arm中没有原生除法。会截断结果为32位
  • AND , ORR , EOR:与或非

分支跳转 & 条件执行

主要依赖于一些标志寄存器的值,记不住,到时候现查就行。

一些后缀编码

EQ = Z==1
NE != Z==0
MI - N==1
PL +, 0 N==0
HI unsigned > C==1 && Z==1
LS unsigned < C==0 \ \ Z==1
GE >= N=V
LT < N!=V
GT > Z==0 && N=V
LE <= Z==1 \ \ N!=V

跳真不跳假。比如BLT这个指令:如果LT为真,则跳转。默认情况下不会更新标志位,除非使用了S后缀。

比较指令会自动更新标志位。

JIT和SMC

just-in-time:即时编译

SMC:自修改代码(逆向中的一种反调试技巧)。

arm支持这些玩意。

没有版权,随便复制,免费的知识应该共享 all right reserved,powered by Gitbook该文章修订时间: 2023-03-23 14:55:58

results matching ""

    No results matching ""