1. ARM64中的寄存器
程序在计算机中执行时, 依赖最重要的是CPU中的寄存器, 不同的CPU架构体系中的寄存器个数和种类都不一样.
ARM64目前拥有31个 64位(8字节) 通用寄存器(General Purpose Registers), 可以用x0~x30
来表示. 其中有一些比较关键的寄存器有一些特殊的用途如下:
x29
-- fp(frame pointer
)寄存器, 用来记录函数的调用栈的, 栈底地址x30
-- lr (link register
)寄存器, 连接寄存器, 用来记录当前函数返回时, 执行的下一条代码指令的地址.x31
-- sp (stack pointer
)寄存器, 用来记录内存中, 栈顶地址, 寄存器在任意时刻会保存我们栈顶的地址.
另外还有两个比较特殊的寄存器:
pc (program counter) 程序计数寄存器
: 它指示了CPU当前要读取的指令的地址cpsr (current program status register)程序状态寄存器
: 用来记录CPU计算过程中的一些状态标志位xzr
(零寄存器)
w0~w30
是31个32位寄存器, 实际是用x0~x30的低32位表示的- 另外CPU还有其他的浮点寄存器, 向量寄存器等等其他的寄存器
2. 从CPU看程序执行的过程
程序在执行过程经过如下过程:
- CPU将 内存(堆/栈等) 中的数据存储到通用寄存器中
- 然后对通用寄存器中的数据进行运算
- CPU将运算结果存储回内存中
另外, 在内存或者磁盘中上的二进制, 指令与数据没有任何区别!!! CPU在执行时, 有时将二进制解释成指令, 有时将二进制看做数据, 例如 1110 0000 0000 0011 0000 1000 1010 1010
:
- 当做数据:
0xE003008AA
- 当做指令:
mov x0, x8
对CPU执行程序的理解可以有如下过程:
pc
寄存器指向的内容, CPU都会当做指令去解释并执行pc
执行完当前指令以后, 就将pc
寄存器指向当前指令的下一个位置, 继续重复- 我们可以通过改变pc的内容来控制CPU执行目标指令, ARM64中
bl
指令就是改变pc
寄存器的内容,从而实现函数跳转,bl
簇指令都称为转移指令
下面是一个简单的汇编代码, 描述从函数void A()
内部调用void B()
函数的示例:
_A:
mov x0,#0xa0
mov x1,#0x00
add x1, x0, #0x14
mov x0,x1
bl _B
mov x0,#0x0
ret
_B:
add x0, x0, #0x10
ret
复制代码
3. 从CPU的角度看函数
ARM64中, 操作栈是通过sub/add sp
指针来进行栈拉伸和栈平衡, 下面是一个实例, 操作栈内存
:
sub sp, sp, #0x40 ; 拉伸0x40(64字节)空间
stp x29, x30, [sp, #0x30] ; x29\x30 寄存器入栈保护
add x29, sp, #0x30 ; x29指向栈帧的底部
...
ldp x29, x30, [sp, #0x30] ; 恢复x29/x30 寄存器的值
add sp, sp, #0x40 ; 栈平衡
ret
复制代码
函数中的内存操作:
-
storage pair/register:
使用stp/str
来将寄存器内容存储到内存中, -
使用
ldp/ldr
来读取内存数据到指定寄存器
注意: ARM64的寄存器是64位(8字节), 因此
str/ldr
一次只能操作8字节数据, 使用stp/ldp
可以一次性操作16个字节数据.
两个与函数相关的ARM64指令与寄存器:
-
bl 标号
-
将下一条指令的地址放入lr(x30)寄存器
-
转到标号处执行指令
-
-
ret
- 默认使用lr(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址
-
x30 (lr) 寄存器
- x30寄存器存放的是函数的返回地址.当ret指令执行时刻,会寻找x30寄存器保存的地址值!
函数的参数与返回值:
- ARM64下, 函数的参数是存放在
x0
到x7
这8个寄存器里面的.如果超过8个参数,就会提前入栈内存. - 函数的返回值是放在
x0
寄存器里面的. - 函数的局部变量放在
栈内存
里面.
4. 从CPU的角度看多线程
从ARM64来看, 整个程序执行过程中CPU并不知道线程的存在, 操作系统内核会为每个线程维护独立的函数调用栈, 并且在系统切换线程时, 使用内核数据结构保存CPUInfo
, 包括各个寄存器的关键信息:
//这个结构是linux在arm32CPU上的线程上下文结构,代码来自于:http://elixir.free-electrons.com/linux/latest/source/arch/arm/include/asm/thread_info.h
//这里并没有保存所有的寄存器,是因为ABI中定义linux在arm上运行时所使用的寄存器并不是全体寄存器,所以只需要保存规定的寄存器的内容即可。这里并不是所有的CPU所保存的内容都是一致的,保存的内容会根据CPU架构的差异而不同。
//因为iOS的内核并未开源所以无法得到iOS定义的线程上下文结构。
//线程切换时要保存的CPU寄存器,
struct cpu_context_save {
__u32 r4;
__u32 r5;
__u32 r6;
__u32 r7;
__u32 r8;
__u32 r9;
__u32 sl;
__u32 fp;
__u32 sp;
__u32 pc;
__u32 extra[2]; /* Xscale 'acc' register, etc */
};
//线程上下文结构
struct thread_info {
unsigned long flags; /* low level flags */
int preempt_count; /* 0 => preemptable, <0 => bug */
mm_segment_t addr_limit; /* address limit */
struct task_struct *task; /* main task structure */
__u32 cpu; /* cpu */
__u32 cpu_domain; /* cpu domain */
struct cpu_context_save cpu_context; /* cpu context */
__u32 syscall; /* syscall number */
__u8 used_cp[16]; /* thread used copro */
unsigned long tp_value[2]; /* TLS registers */
#ifdef CONFIG_CRUNCH
struct crunch_state crunchstate;
#endif
union fp_state fpstate __attribute__((aligned(8))); /*浮点寄存器*/
union vfp_state vfpstate; /*向量浮点寄存器*/
#ifdef CONFIG_ARM_THUMBEE
unsigned long thumbee_state; /* ThumbEE Handler Base register */
#endif
};
作者:欧阳大哥2013
链接:https://juejin.cn/post/6844903560145010702
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
复制代码
5. objc_msgSend 汇编分析
网上很多, 可以参考 www.jianshu.com/p/62ecc3f31…
6. HOOK所有的OC方法, 统计每个方法耗时
可以参考一下两个实现: