iOS架构及设备
架构 | 设备 | 宽度 |
---|---|---|
armv6 | iPhone, iPhone2, iPhone3G, 第一代、第二代iPod Touch | 32 |
armv7 | iPhone3GS, iPhone4, iPhone4S, iPad, iPad2, iPad3(The New iPad), iPad mini, iPod Touch 3G, iPod Touch4 | 32 |
armv7s | iPhone5, iPhone5C, iPad4(iPad With Retina Display) | 32 |
arm64 | iPhone5s以及之后版本 | 64 |
ARM64寄存器
寄存器 | 描述 |
---|---|
x0-x30 | 通用寄存器, 如果有需要可以当做32bit(w0-w30)使用 |
FP(x29) | 保存栈帧地址(栈底指针) |
LR(30) | 通常称x30为程序链接寄存器, 保存子程序结束后需要执行的下一条指定的地址 |
SP | 保存栈指针, 使用SP/WSP来进行对SP寄存器的访问 |
PC | 程序计数器, 俗称PC指针, 总是指向即将执行的下一条指令, 在arm64中, 软件是不能改写PC寄存器的 |
SPSR | 状态寄存器 |
- x0-x7: 用于传递函数参数, 超出的参数将入栈. 假如在函数
funcA
中调用函数funcB
, 传给funcB
的参数超出8个的将保存在函数funcA
函数栈的栈顶. - x0 - x30 是31个通用整形寄存器。每个寄存器可以存取一个64位大小的数。 当使用 r0 - r30/x0 - x30 访问时,它就是一个64位的数。当使用 w0 - w30 访问时,访问的是这些寄存器的低32位.
寄存器概念补充
数据地址寄存器
数据地址寄存器通常用来做数据计算的临时存储、做累加、计数、地址保存等功能。定义这些寄存器的作用主要是用于在CPU指令中保存操作数,在CPU中当做一些常规变量来使用。
ARM64中
- 64位: X0-X30, XZR(零寄存器)
- 32位: W0-W30, WZR(零寄存器)
注意:
之前讲解8086汇编中有一种特殊的寄存器段寄存器:CS,DS,SS,ES四个寄存器来保存这些段的基地址,这个属于Intel架构CPU中.在ARM中并没有
浮点和向量寄存器
因为浮点数的存储以及其运算的特殊性,CPU中专门提供浮点数寄存器来处理浮点数
- 浮点寄存器 64位: D0 - D31 32位: S0 - S31
SP和FP寄存器
- sp寄存器在任意时刻都会保存我们栈顶的地址
- fp寄存器也称为x29寄存器属于通用寄存器, 但是在某些时刻我们利用它保存栈底地址
注意: ARM64开始, 取消32位的
LDM
,STM
,PUSH
,PHP
指令, 取而代之的是ldr/ldp
,str/stp
ARM64里面对栈的操作是16字节对齐的
关于内存读写指令
注意: 读/写数据都是往高地址读/写
str(store register)
将数据从寄存器存到栈中, 同时操作两个寄存器时, 使用stp
str x0,[sp,#0x10]
ldr(load register)
将数据从栈读到寄存器中, 同时操作两个寄存器时, 使用ldp
ldr x0,[sp,#0x10]
堆栈操作练习
将32个字节空间作为这段程序的栈空间,然后利用栈x0,x1的值进行交换
sub sp,sp,#0x20 ;拉伸栈空间
stp x0,x1,[sp,#0x10] ;将x0,x1存放在sp+0x10为地址的栈中
ldp x1,x0,[sp,#0x10] ;从sp+0x10为地址的栈中读取数据到x1,x0
add sp,sp,#0x20 ;平栈
bl和ret指令
bl 标号
- 将下一条指令的地址放入lr(x30)寄存器 (l)
- 跳转执行标号为地址所指向的指令 (b)
ret
- 默认使用lr(x30)寄存器的值, 通过底层指令提示CPU此处作为下一条指令的地址!
ARM64平台的特色指令, 它面向硬件做了优化处理
x30寄存器(lr)
- x30寄存器存放的是函数的返回地址. 当ret指令执行时, 会寻找x30寄存器保存的地址所指向的内存保存的值为下一条指令
注意: 在函数嵌套调用的时候. 需要将x30入栈!
ldp x29, x30, [sp, #0x10] add sp, sp, #0x20 ; =0x20 ret
函数的参数和返回值
- ARM64下, 函数的参数是存放在x0-x7(w0-w7)这8个寄存器里面的. 如果超过8个参数, 就会入栈.
- 函数的返回值放在x0寄存器里面
NZCV状态寄存器
- CPU内部的寄存器中, 有一种特殊的寄存器(对于不同处理器, 个数和结构可能不同). 这种寄存器在RAM中, 被称为CPSR(Current program status register)寄存器
- CPSR和其他寄存器不同, 其他寄存器是用来存放数据的, 都是整个寄存器具有一个含义. 而CPSR寄存器是按位起作用的, 它的每一位都有特定含义.
- CPSR的低八位(包括I、F、T、M[4:0]) 称为控制位, 程序无法修改, 除非CPU运行于特权模式下.
- N、Z、C、V均为条件标志位. 他们的内容可被算数或逻辑运算改变, 并且可以决定某条指令是否被执行(if的汇编实现..)
注: CPSR是32位寄存器. add/sub等指令不能影响CPSR寄存器, 如需要保存计算状态, 使用adds/subs等.
N(Negative)
CPSR的第31位(从零开始数...)是N, 符号标志位. 它记录相关指令执行后, 其结果是否为负. 如果结果为负, N=1. 反之N=0.
Z(Zero)
CPSR的第30位(...你们懂得)是Z, 0标志位,. 它记录相关指令执行后, 其结果是否为0, 如果是0, Z=1. 反之Z=0.
C(Carry)
CPSR的第29位(...)时C, 进位标志位. 一般情况下, 进行无符号数运算.
加法运算: 当运算产生进位时, C=1, 反之C=0.
减法运算(包括CMP): 当运算产生借位时, C=0, 反之C=1.
- 可理解为, 进位将进位的1保存到了C. 借位将C保存的1借走. 而实际上, 借位之前C为保存的不一定是1. 进位也类似(未仔细求证).
进位:
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位时V, 溢出标志位. 在进行有符号运算时, 如果超过了机器所能标识的范围, 视为溢出.
实际是无符号运算影响了符号位或者产生了进位或借位
以下情况都会造成溢出
- 正数 + 正数 = 负数
- 负数 + 负数 = 正数
正数 + 负数 不可能溢出.
指令条件码(逻辑对照)
竟然跟NZCV对应的值不匹配, 虽然理解, 但还是好难受.
比如不相等
, 就不能确定N
位的值. 猜测
编码 | 助记符 | 描述 | 标记 |
---|---|---|---|
0000 | EQ (equal) | 相等 | Z=1 |
0001 | NE(Not Equal) | 不相等 | Z=0 |
0010 | CS/(Carry Set/High or Same) | 无符号数大于/等于 | C = 1 |
0011 | CC/LO(Carry Clear/LOwer) | 无符号数小于 | C = 0 |
0100 | MI(MInus) | 负数 | N=1 |
0101 | PL(PLus) | 非负数 | N=0 |
0110 | VS(oVerflow set) | 上溢出 | V=1 |
0111 | VC(oVerflow clear) | 没有上溢出 | V=0 |
1000 | HI(HIgh) | 无符号数大于 | C=1 且 Z=0 |
1001 | LS(Lower or Same) | 无符号数小于/等于 | C=0 且 Z=1 |
1010 | GE(Greater or Equal) | 有符号数大于或等于 | N=V |
1011 | LT(Less Than) | 有符号数小于 | N!=V |
1100 | GT(Greater Than) | 有符号数大于 | Z=0,N=V |
1101 | LE(Less or Equal) | 有符号数小于等于 | Z=1,N!=V |
1110 | AL | 无条件执行 | 任何 |
1111 | NV | 从不执行 | 任何 |
全局变量/静态变量/常量
- 全局变量、静态变量保存在静态区
- 常量的话, 如果是字符串, 通常保存在常量区. 较大(二进制表示需要较大宽度)的数字类型保存在常量区. 如果是较小数字, 可以通过立即数的形式来保存.
- 较大的数字(ARM64中二进制表示大于
0xffff
, 即长度超过2字节), 因为ARM汇编命令是等长的(四字节), 比较大的数不能包含在命令中, ARM通过偏移的方式, 使用adrp
指令读取静态区.- 同理字符串也不能保存在代码区, 通过偏移的方式, 同样使用
adrp
指令读取.
下面代码是读取一个常量/静态变量到x8寄存器
adrp x0,1 ; 将当前PC寄存器指向的地址低12位清零, 加上 (`1`左移12位), 将结果保存到`x0`寄存器
add x0,#0xf28 ; `x0`保存的值加上偏移0xf28再次保存到`x0`寄存器.
ldr x8, [x0] ; 将x0存储的值作为地址, 读取地址指向的数据, 保存到x8寄存器
上面代码中, 最后x8寄存器保存的就是一个静态变量或者字符串常量的指针.
待续
// 知识有限, 随时修改..
// ## #### ######