汇编讲解(上)--逆向开发
今天进入逆向开发的另一个部分--汇编知识的讲解,分为上下篇,希望通过两篇博客的讲解让大家对汇编知识有个大致的了解!!!
汇编语言发展
机器语言
机器语言:有0和1共同组成的机器语言
- 加: 0100 0000
- 减: 0100 1000
- 乘: 1111 0111 1110 0000
- 除: 1111 0111 1111 0000
汇编语言(assembly language)
汇编语言:使用助记符来代替机器语言
- 加:INC EAX 通过编译器 0100 0000
- 减:DEC EAX 通过编译器 0100 1000
- 乘:MUL EAX 通过编译器 1111 0111 1110 0000
- 除:DIV EAX 通过编译器 1111 0111 1111 0000
高级语言(High-level programming language)
高级语言:比如Java,Objective-C
- 加:A+B 通过编译器 0100 0000
- 减:A-B 通过编译器 0100 1000
- 乘:A*B 通过编译器 1111 0111 1110 0000
- 除:A/B 通过编译器 1111 0111 1111 0000
代码在手机终端上运行过程
- 汇编语言与机器语言是一一对应,每条机器指令都是有相对应的汇编指令
- 汇编语言是可以通过编译得到机器语言,机器语言也是可以通过反汇编来得到汇编语言
- 高级语言是可以通过编译得到汇编语言和机器语言;但是汇编语言,机器语言几乎是不可能还原成高级语言
汇编语言的特点与语言种类
特点
- 不区分大小写,DIV和div是一样的
- 汇编指令是机器指令的助记符,是和机器指令是一一对应的,每一种的CPU都是有自己的机器指令集/汇编指令集
- 目标代码短、占位内存少以及执行速度快
- 能够不受编译器的控制,对生成的二进制代码是完全控制的
- 能够直接访问,控制住硬件设备。
语言种类
iPhone使用的是ARM汇编,但是不同的手机设备以及架构是不同的
APP/程序执行过程
如果想深入了解汇编,下面将讲解几个插曲。
硬件最重要就是CPU和内存
插曲
总线
总线是一根根导线的集合
总线分为地址总线、数据总线、控制总线三种
- 地址总线
地址总线宽度决定了CPU的寻址能力
8086地址总线是20,其寻址能力是2^20 = 1M
- 数据总线
数据总线宽度决定了CPU单次传递的数据量,换句话说也是数据的传送速度
8086的数据宽度为16,单次最大传送数据量为2个字节
- 控制总线
控制总线宽度决定了CPU对其他控件的控制能力
数据宽度
- 位(bit):就是二进制位,就是0或者1
- 字节(Byte):1个字节是由8个bit(8位)组成。内存中最小的存储单元为Byte
- 字(word):1个字由两个字节Byte组成,两个字节是高字节和低字节
- 双字:1个双字由2个字组成
在存储数据时,分为有符号和无符号数
寄存器
内部部件之间是由总线来相连
CPU最重要的部件是寄存器,日常工作中,通过改变寄存器的值来实现对CPU的值。
通用寄存器
ARM64有31个64位通用寄存器X0-X30,通常用来存储一般性的数据,有时候也会有特定用途,称之为通用寄存器。
W0-W28是32位,是X0-X32的低28位,而64位CPU是兼容32位的CPU,所以使用64位的低32位。W0就是X0。
CPU一般先将内存的数据存储到通用寄存器中,然后再对此进行运算。
pc寄存器
- 指向了CPU当前要执行的指令地址
- 在内存或者磁盘上,指令和数据无区别,都是二进制信息
- CPU将pc指向的内存单元的内容看做指令,如果内存中的某段内容曾经被执行过,其内存单元必被pc指向过。
CPU的补充
CPU由计算器,控制器以及寄存器组成,其中,寄存器的作用将数据进行临行存储起来.
CPU的运行速度非常快, 为了性能的提高, CPU会在内部开辟小块的临时存储区域, 并在运算时先将数据从内存复制到这小块临时存储区域中, 在这小块区域进行运算,这小块临时区域就称作寄存器.
对于ARM64架构的CPU, 以 X 开头的就是64位的寄存器, 以 W 开头的就是32位的寄存器, 其中32位寄存器就是64位寄存器的低32位部分
系统中没有提供16位 和 8位的寄存器供以访问(注意: 查看时要使用真机调试, 模拟器使用的X86的架构)
高速缓存
iPhoneX搭载的ARM64处理器A11, 它的一级缓存容量是64KB, 二级缓存容量是8M (现在普遍也只有二级缓存)
寄存器的补充
数据地址存储器
数据地址存储器通常作为数据计算的临时存储, 做累加, 计数地址保存等功能. 这些寄存器的作用主要用于在CPU指令中保存操作数, 在CPU中当做常规变量使用.- 64位: X0 - X30 , XZR(零寄存器)
- 32位: W0 - W30 , WZR(零寄存器)
注意: 8086汇编中有一种寄存器, 段寄存器: CS, ES, DS, SS四个寄存器用来保存段的基地址, 这属于Intel CPU中, 在ARM中没有.
浮点和向量寄存器
现在CPU支持向量运算(向量运算在图形处理中使用比较广泛), 为支持向量运算, 故提供了更多向量寄存器.
- 向量寄存器: 128位 v0 - v128
因为浮点数以及其运算的特殊性, CPU专门提供浮点寄存器来计算浮点数
sp, fp寄存器
- sp寄存器会在任意时刻保存栈顶的地址
- fp寄存器也成为x29寄存器属于通用寄存器, 但是在某些时刻我们会用它保存栈底的地址
注意: ARM64开始取消32位的LDM, STM, PUSH, POP指令. 与之替代的是 ldr/ ldp, str/ stp
ARM64里面 对栈的操作都是16进制对齐的!!!
内存的读写指令
内存的读写指令
str(store register)写入指令
将数据从寄存器中读出来, 写入到内存中
ldr(load register)读取指令
将数据从内存中读取出来, 存到寄存器中
ldp/stp 是 ldr/str 的衍生, 可以同时读/写两个寄存器, ldr/str只能读写一个
sub sp, sp, #0x20 ; 拉伸栈空间32(20 = 2*16)个字节 stp x0 , x1, [sp, #0x10] ; sp往上加16(10 = 1 * 16)个字节,存放x0 和 x1 ldp x1 , x0, [sp, #0x10] ; 将sp偏移16个字节的值取出来,放入x1 和 x0
注意: 拉伸栈空间是往低地址拉伸, 拉伸的字节数只能是16的倍数, 否则会崩溃(对照上面 16进制对齐理解 )
bl 和 ret 指令
bl: 跳转指令 1. 将下一条指令的地址存放到 lr(x30) 寄存器中 2. 跳转到对应函数中执行指令
ret: 返回指令 默认使用 lr(x30) 寄存器的值, 通过底层指令提示CPU这是下一条指令的地址. ARM64架构中的特色指令, 面向硬件做了优化处理
lr (x30) 寄存器
- x30寄存器存放的是函数返回的地址, 当ret执行指令时, 会寻找这个寄存器中保存的地址
注意: 在函数嵌套调用中, 需要将x30入栈, 否则可能会造成死循环.
状态寄存器
CPSR和其他寄存器不一样, 其他寄存器都是用来存放数据的, 一个寄存器对应一个含义 ; 而CPSR寄存器是按位起作用的, 每一位都有专门的含义,记录特定的信息.
注意: CPSR 寄存器是32位的
CPSR的低8位(包括 I , F, T 和 M[4-0])称为控制位, 程序无法修改, 除非CPU运行于特权模式下, 才能修改控制位.
N Z C V均为条件码标志位, 它们的内容可被算术或逻辑运算的结果所改变, 并且决定某条指令是否被执行, 意义重
N (negative) 标志
CPSR 第31位是N, 符号标志位. 它记录相关指令执行后, 其结果是否为负, 如果为负,则 N 为1; 如果非负则N为0.
Z (zero) 标志
CPSR 第30位是Z, 0标志位. 它记录相关指令执行后, 其结果是否为0, 如果为0,则Z为1; 如果非0则Z为0.
C (carry) 标志
CPSR 第29位是C, 进位标志位. 一般情况下, 进行无符号的运算. 加法运算: 当运算结果产生了进位时(无符号位溢出), 则 C = 1, 否则C=0. 减法运算(包括CMP): 当运算结果产生了借位时(无符号数溢出), 则C=0,否则C=1.
对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N - 1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位。如下图所示:
进位
mov w0,#0xaaaaaaaa;0xa 的二进制是 1010 adds w0,w0,w0; 执行后 相当于 1010 << 1 进位1(无符号溢出) 所以C标记 为 1 adds w0,w0,w0; 执行后 相当于 0101 << 1 进位0(无符号没溢出) 所以C标记 为 0 adds w0,w0,w0; 重复上面操作 adds w0,w0,w0
借位
mov w0,#0x0 subs w0,w0,#0xff ; subs w0,w0,#0xff subs w0,w0,#0xff
V(overflow)溢出标志
CPSR的第28位, 溢出标志位. 在进行有符号数运算的时候, 如果超出了机器所能识别的范围, 称为溢出.- 正数 + 正数 为 负数, 溢出
- 负数 + 负数 为 正数, 溢出
- 正数 + 负数 不可能溢出
以上就是关于汇编知识点内容,希望对大家有所帮助,进入了解技术语言的底层知识!!!