注册

iOS-汇编-指针、OC

编译器优化

局部变量&全局变量

int global = 10;

int main(int argc, char * argv[]) {
int a = 20;
int b = global + 1;
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

在不进行优化的情况下:

8fe7cbbc2ba37c003993c7f80bac69f8.png

改成Fastest、Smallest模式,ab都被优化掉了。


75213ab04931ebaf4c616d5c46f79f7f.png


局部变量和全局变量会被优化掉。

函数

int func(int a,int b) {
return a + b;
}

int main(int argc, char * argv[]) {
int value = func(10, 20);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

d9bafa72be485995c22c64f6dec0adb6.png

func函数也会被优化掉,因为对程序的执行结果没有影响。
修改下:


int func(int a,int b) {
return a + b;
}

int main(int argc, char * argv[]) {
int value = func(10, 20);
NSLog(@"%d",value);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}


60ccdaff97e84716ece3e060939a7ce5.png



可以看到直接将0x1e结果入栈。一样也会被优化。

编译配置(Optimization Level)


编译器(从c汇编的编译过程)的优化配置(指定被编译代码的执行速度和二进制文件大小的优化程度)。优化后的代码效率比较高,但是可读性比较差,且编译时间更长。
优化等级配置在Build Settings -> Apple Clang - Code Generation -> Optimization Level 中:

547c54c7b178045e242044134de2d309.png


None [-O0]:不优化。
编译器的目标是降低编译消耗,保证调试时输出期望的结果。程序的语句之间是独立的:如果在程序的停在某一行的断点出,我们可以给任何变量赋新值抑或是将程序计数器指向方法中的任何一个语句,并且能得到一个和源码完全一致的运行结果。Fast [-O, O1]: 大函数所需的编译时间和内存消耗都会稍微增加。
在这种设置下,编译器会尝试减小代码文件的大小,减少执行时间,但并不执行需要大量编译时间的优化。在苹果的编译器中,在优化过程中,严格别名,块重排和块间的调度都会被默认禁止掉。此优化级别提供了良好的调试体验,堆栈使用率也提高,并且代码质量优于None[-O0]。Faster [-O2]:编译器执行所有不涉及时间空间交换的所有的支持的优化选项。
更高的性能优化Fast[-O1]。在这种设置下,编译器不会进行循环展开、函数内联或寄存器重命名。和‘Fast[-O1]’项相比,此设置会增加编译时间和生成代码的性能。Fastest [-O3]:在开启Fast[-O1]项支持的所有优化项的同时,开启函数内联和寄存器重命名选项
是更高的性能优化Faster[-O2],指示编译器优化所生成代码的性能,而忽略所生成代码的大小,有可能会导致二进制文件变大。还会降低调试体验。Fastest, Smallest [-Os]:在不显着增加代码大小的情况下尽量提供高性能
这个设置开启了Fast[-O1]项中的所有不增加代码大小的优化选项,并会进一步的执行可以减小代码大小的优化。增加的代码大小小于Fastest[-O3]。与Fast[-O1]相比,它还会降低调试体验。Fastest, Aggressive Optimizations [-Ofast]:与Fastest, Smallest[-Os]相比该级别还执行其他更激进的优化
这个设置开启了Fastest[-O3]中的所有优化选项,同时也开启了可能会打破严格编译标准的积极优化,但并不会影响运行良好的代码。该级别会降低调试体验,并可能导致代码大小增加。Smallest, Aggressive Size Optimizations [-Oz]:不使用LTO的情况下减小代码大小
与-Os相似,指示编译器仅针对代码大小进行优化,而忽略性能优化,这可能会导致代码变慢。

dacaf269d0459412d71d498de7ca3b5c.png

XcodeDebug模式默认为None[-O0]Release默认为Fastest, Smallest[-Os]

指针

指针在汇编中只是地址, 在底层来说就是数据。

指针基本常识

指针的宽度为8字节。

void func() {
//指针的宽度8字节
int *a;
printf("%lu",sizeof(a));
}

int main(int argc, char * argv[]) {
func();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
5e3ebd0c3c0736d2186877b35c4791bb.png

  • sizeof:是个符号,操作符。这里验证了是常量8

指针的运算

指针++

int型指针++:

int *a;
a = (int *)100;
a++;

运算结果:104int4字节。

char型指针++:

char *a;
a = (char *)100;
a++;

运算结果:101char1字节。

指向指针的指针++:

int **a;
a = (int **)100;
a++;

运算结果:108,指针占8字节

指针+

int **a;
a = (int **)100;
a = a + 1;

运算结果:108。与a++++、--与编译器有关)等价。

指针-

int *a;
a = (int *)100;
int *b;
b = (int *)200;
int x = a - b; // a/4 - b/4 = -25


运算结果:-25。(a/4 - b/4 = -25

  • 指针的运算与指向的数据类型宽度(步长)有关。
  • 指针的运算单位是执行的数据类型的宽度。
  • 结构体和基本类型不能强制转换,普通类型可以通过&

指针的反汇编

void func() {
int* a;
int b = 10;
a = &b;
}

对应的汇编:

TestDemo`func:
0x10098a1c4 <+0>: sub sp, sp, #0x10 ; =0x10
//sp+0x4 x8
0x10098a1c8 <+4>: add x8, sp, #0x4 ; =0x4
//1O给w9
0x10098a1cc <+8>: mov w9, #0xa
//w9 入栈
-> 0x10098a1d0 <+12>: str w9, [sp, #0x4]
//x8 指向 sp+0x8。相当于x8指向sp,也就是指向10的地址
0x10098a1d4 <+16>: str x8, [sp, #0x8]

0x10098a1d8 <+20>: add sp, sp, #0x10 ; =0x10
0x10098a1dc <+24>: ret

[sp, #0x8]是个指针变量。从0x8~0x10保存的就是指针。

数组和指针

void func() {
int arr[5] = {1,2,3,4,5};
//int *a == &arr[0] == arr
int *a = arr;
for (int i = 0; i < 5; i++) {
printf("%d\n",arr[i]);
printf("%d\n",*(arr + i));
// printf("%d\n",*(arr++));
printf("%d\n",*(a++));
}
}

*(arr++) 会报错。int *a = arr; 之后 a++就没问题了、

  • 数组名和指针变量是一样的,唯一的区别是一个是常量,一个是变量。
    int *a == &arr[0] == arr

指针的基本用法

void func() {
char *p1;
char c = *p1;
printf("%c",c);
}


b324fdcc193792d640908797bbc38fcd.png

p1由于是个指针,没有初始化编译不会报错,运行会报错。在iOS中默认是0,运行会直接野指针。

指向char的指针+0

void func() {
char *p1;
char c = *p1;
char d = *(p1 + 0);
}


对应汇编

TestDemo`func:
0x104b661bc <+0>: sub sp, sp, #0x10 ; =0x10
//p1 -> 0x0 x8指向p1
0x104b661c0 <+4>: ldr x8, [sp, #0x8]
//c = [x8] 给到 w9
-> 0x104b661c4 <+8>: ldrb w9, [x8]
0x104b661c8 <+12>: strb w9, [sp, #0x7]
0x104b661cc <+16>: ldr x8, [sp, #0x8]
//d = [x8] 给到 w9
0x104b661d0 <+20>: ldrb w9, [x8]
0x104b661d4 <+24>: strb w9, [sp, #0x6]

0x104b661d8 <+28>: add sp, sp, #0x10 ; =0x10
0x104b661dc <+32>: ret

指向char的指针+1

void func() {
char *p1;//指针 -> x8 0x0
char c = *p1;// [x8]
char d = *(p1 + 1);//[x8, #0x1]
}

对应汇编:

TestDemo`func:
0x1041f21bc <+0>: sub sp, sp, #0x10 ; =0x10
//p1
0x1041f21c0 <+4>: ldr x8, [sp, #0x8]
//c
-> 0x1041f21c4 <+8>: ldrb w9, [x8]
0x1041f21c8 <+12>: strb w9, [sp, #0x7]
0x1041f21cc <+16>: ldr x8, [sp, #0x8]
//d
0x1041f21d0 <+20>: ldrb w9, [x8, #0x1]
0x1041f21d4 <+24>: strb w9, [sp, #0x6]
0x1041f21d8 <+28>: add sp, sp, #0x10 ; =0x10
0x1041f21dc <+32>: ret

指向int的指针+1

void func() {
int *p1;//指针 -> x8 0x0
int c = *p1;// [x8]
int d = *(p1 + 1);//[x8, #0x4]
}
TestDemo`func:
0x1040e61bc <+0>: sub sp, sp, #0x10 ; =0x10
//p1 [x8]
0x1040e61c0 <+4>: ldr x8, [sp, #0x8]
//c
-> 0x1040e61c4 <+8>: ldr w9, [x8]
0x1040e61c8 <+12>: str w9, [sp, #0x4]
0x1040e61cc <+16>: ldr x8, [sp, #0x8]
//d
0x1040e61d0 <+20>: ldr w9, [x8, #0x4]
0x1040e61d4 <+24>: str w9, [sp]
0x1040e61d8 <+28>: add sp, sp, #0x10 ; =0x10
0x1040e61dc <+32>: ret

指向int的指针的指针+1

void func() {
int **p1;//指针 -> x8 0x0
int *c = *p1;// [x8]
int *d = *(p1 + 1);//[x8, #0x8]
}
TestDemo`func:
0x1041821b8 <+0>: sub sp, sp, #0x20 ; =0x20
//p1 [x8]
0x1041821bc <+4>: ldr x8, [sp, #0x18]
//c
-> 0x1041821c0 <+8>: ldr x8, [x8]
0x1041821c4 <+12>: str x8, [sp, #0x10]
0x1041821c8 <+16>: ldr x8, [sp, #0x18]
//d
0x1041821cc <+20>: ldr x8, [x8, #0x8]
0x1041821d0 <+24>: str x8, [sp, #0x8]
0x1041821d4 <+28>: add sp, sp, #0x20 ; =0x20
0x1041821d8 <+32>: ret

这里拉伸了#0x2016字节对齐。

指向指针的指针

void func() {
char **p1;
char c = **p1;
}

取地址的地址在汇编中:

TestDemo`func:
0x102cf61c4 <+0>: sub sp, sp, #0x10 ; =0x10
//初始值
0x102cf61c8 <+4>: ldr x8, [sp, #0x8]
//两次ldr,二级指针在寻址
-> 0x102cf61cc <+8>: ldr x8, [x8]
0x102cf61d0 <+12>: ldrb w9, [x8]

0x102cf61d4 <+16>: strb w9, [sp, #0x7]
0x102cf61d8 <+20>: add sp, sp, #0x10 ; =0x10
0x102cf61dc <+24>: ret

两次ldr,二级指针在寻址。


指针的指针&指针混合偏移

void func() {
char **p1;
char c = *(*(p1 + 2) + 2); // [0x10 + 0x2]
}

p1偏移 (2 * 指针) +(2 * char)


void func() {
char **p1;
char c = *(*(p1 + 2) + 2); // [0x10 + 0x2]
char c2 = p1[1][2]; // [0x8 + 0x2]
}

p1[1][2]等价于*(*(p1 + 1) + 2)


OC反汇编

创建一个简单的Hotpot类:

//Hotpot.h
@interface Hotpot : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

+ (instancetype)hotpot;

@end

//Hotpot.m
#import "Hotpot.h"

@implementation Hotpot

+ (instancetype)hotpot {
return [[self alloc] init];
}

@end

main.m中调用:

#import "Hotpot.h"

int main(int argc, char * argv[]) {
Hotpot *hp = [Hotpot hotpot];
return 0;
}

对应的汇编代码:


81b065cad1f600e1592bf75c1cd7d938.png


我们都知道OC方法objc_msgSend默认有两个参数self cmd,分别是idSEL类型。
验证下:

(lldb) x 0x1027c95b0
0x1027c95b0: f8 95 7c 02 01 00 00 00 20 96 7c 02 01 00 00 00 ..|..... .|.....
0x1027c95c0: 08 00 00 00 10 00 00 00 08 00 00 00 00 00 00 00 ................
(lldb) po 0x01027c95f8
Hotpot

(lldb) x 0x1027c95a0
0x1027c95a0: bd 65 7c 02 01 00 00 00 a8 af b3 df 01 00 00 00 .e|.............
0x1027c95b0: f8 95 7c 02 01 00 00 00 20 96 7c 02 01 00 00 00 ..|..... .|.....
(lldb) po (SEL)0x01027c65bd
"hotpot"

(lldb) register read x0
x0 = 0x00000001027c95f8 (void *)0x00000001027c95d0: Hotpot
(lldb) register read x1
x1 = 0x00000001027c65bd "hotpot"
(lldb)

接着进入hotpot方法中,对应汇编如下:

55b921f20a94656f30871d95edeee2c8.png


发现没有走objc_msgSend方法,直接走了objc_alloc_init方法。

⚠️:这块和支持的最低版本有关。
iOS9中为objc_msgSend 和 objc_msgSend对应allocinit
iOS11中为objc_alloc 和 objc_msgSend,这里优化了alloc直接调用了objc_alloc,没有调用objc_msgSend
iOS13中为objc_alloc_init,这里同时优化了allocinit

hotpot方法执行完毕后会返回实例对象:

3257f3b8dd3e15c361a5ce9cacbc548a.png


在下面调用了一个objc_storeStrong函数(OC中用strong修饰的函数都会调用这个函数,例子中hp局部变量默认就是__strong)。objc_storeStrong调用后如果被外部引用引用计数+1,否则就销毁。
objc4-818.2源码中objc_storeStrong源码(在NSObject.mm中):

void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}

这个函数有两个参数 id* 和 id,函数的目的为对strong修饰的对象retain + 1,对旧对象release

    //x8指向 sp + 0x8 地址
0x1022421a0 <+60>: add x8, sp, #0x8 ; =0x8
//x8 就是指向x0的地址
0x1022421a4 <+64>: str x0, [sp, #0x8]
0x1022421a8 <+68>: stur wzr, [x29, #-0x4]
0x1022421ac <+72>: mov x0, x8
0x1022421b0 <+76>: mov x8, #0x0
0x1022421b4 <+80>: mov x1, x8
//objc_storeStrong 第一个参数 &hp,第二个参数 0x0
-> 0x1022421b8 <+84>: bl 0x102242520 ; symbol stub for: objc_storeStrong
0x1022421bc <+88>: ldur w0, [x29, #-0x4]
0x1022421c0 <+92>: ldp x29, x30, [sp, #0x20]
0x1022421c4 <+96>: add sp, sp, #0x30 ; =0x30
0x1022421c8 <+100>: ret

调用objc_storeStrong的过程就相当于:

//分别传入 &hp  和 0x0
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;//id prev = *hp
if (obj == prev) {
return;
}
objc_retain(obj);// nil
*location = obj;// hp 指向第二个对象 hp = nil
objc_release(prev);//释放老对象 release hp 释放堆空间
}
所以这里objc_storeStrong调用为了释放对象。
objc_storeStrong断点前后验证:

(lldb) p hp
(Hotpot *) $3 = 0x000000028014bf60
(lldb) ni
(lldb) p hp
(Hotpot *) $4 = nil
(lldb)

单步执行后hp变成了nil

工具反汇编

由于大部分情况下OC代码都比较复杂,自己分析起来比较麻烦。我们一般都借助工具来协助反汇编,一般会用到MachoViewHopper,IDA
将刚才的代码稍作修改:

#import "Hotpot.h"

int main(int argc, char * argv[]) {
Hotpot *hp = [Hotpot hotpot];
hp.name = @"cat";
hp.age = 1;
return 0;
}

通过hopper打开macho文件

3a8dde27c636600ff31ef7c7889b908c.png
可以看到已经自动解析出了方法名和参数,那么编译器是怎么做到呢?
双击objc_cls_ref_Hotpot会跳转到对应的地址:

bec7691d447f9d7720b91a3d187b77d2.pngc5a24d6070ed9515544d6b0bef995a51.pngfb243b3e7966d4ed2f7d931d494d76d6.png


可以看到所有方法都在这块。
所以在分析汇编代码的时候编译器就能根据工具找到这些字符串。这也就是能还原的原因。

Block反汇编

在平时开发中经常会用到block,那么block汇编是什么样子呢?

int main(int argc, char * argv[]) {
void(^block)(void) = ^() {
NSLog(@"block test");
};
block();
return 0;
}

一般在反汇编的时候我们希望定位到block的实现(invoke
对应汇编如下:

ea751743b395ce15fc531c7f6d7527fc.png

invoke0x102c4e160

block源码定义如下(Block_private.h):

struct Block_layout {
void *isa; //8字节
volatile int32_t flags; // contains ref count //4字节
int32_t reserved;//4字节
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};

也就是isa往下16字节就是invoke

e651682227d57dbfe68d8d3b2bf606ea.png

hopper中:

3316bd501ca4ec0387b272b7a4d88ca6.pngfa5892282a087404ce6d08ed5b1bf432.png


StackBlock

int main(int argc, char * argv[]) {
int a = 10;
void(^block)(void) = ^() {
NSLog(@"block test:%d",a);
};
block();
return 0;
}


7d066a6310077264351161e95eb9c8ef.png


验证isainvoke

(lldb) po 0x100a8c000
<__NSStackBlock__: 0x100a8c000>
signature: "<unknown signature>"

(lldb) x 0x100a8c000
0x100a8c000: 30 88 ae df 01 00 00 00 94 3f c5 89 01 00 00 00 0........?......
0x100a8c010: 00 00 00 00 00 00 00 00 24 00 00 00 00 00 00 00 ........$.......
(lldb) po 0x01dfae8830
__NSStackBlock__

(lldb) dis -s 0x100a8a140
TestOC&BlockASM`__main_block_invoke:
0x100a8a140 <+0>: sub sp, sp, #0x30 ; =0x30
0x100a8a144 <+4>: stp x29, x30, [sp, #0x20]
0x100a8a148 <+8>: add x29, sp, #0x20 ; =0x20
0x100a8a14c <+12>: stur x0, [x29, #-0x8]
0x100a8a150 <+16>: str x0, [sp, #0x10]
0x100a8a154 <+20>: ldr w8, [x0, #0x20]
0x100a8a158 <+24>: mov x0, x8
0x100a8a15c <+28>: adrp x9, 2

invokeimp实现通过dis -s查看汇编实现。

hopper中:


39f5f3701705ad034f60af26a7995b1d.png80cf435f2dd81b1e5a4ba7765348659f.png


global blockblockdescriptor是在一起的,stack block并不在一起。




作者:HotPotCat
链接:https://www.jianshu.com/p/e3351311efa8


0 个评论

要回复文章请先登录注册