反汇编OC代码看函数调用及内存管理

Qinz
创建一个对象是我们再熟悉不过的了,那么它转换为汇编代码又是怎么执行的呢?接下来我们就通过最常见的创建对象入手,详细分析对象创建和销毁的汇编,从汇编中还原函数调用逻辑。
1. 首先我们来看下面一行代码,这里就只是创建一个P对象:
- (void)viewDidLoad {
    [super viewDidLoad];
    Person* p = [[Person alloc]init];
}
  • 1.1 上面是我们最熟悉的对象创建,我们知道,alloc和init都是发送消息,接下来我们断点程序,查看到汇编代码如下:
 CS`-[ViewController viewDidLoad]:
    0x10064a764 <+0>:   sub    sp, sp, #0x40             ; =0x40 
    0x10064a768 <+4>:   stp    x29, x30, [sp, #0x30]
    0x10064a76c <+8>:   add    x29, sp, #0x30            ; =0x30 
    0x10064a770 <+12>:  add    x8, sp, #0x10             ; =0x10 
    0x10064a774 <+16>:  adrp   x9, 2
    0x10064a778 <+20>:  add    x9, x9, #0xd00            ; =0xd00 
    0x10064a77c <+24>:  adrp   x10, 2
    0x10064a780 <+28>:  add    x10, x10, #0xd30          ; =0xd30 
    0x10064a784 <+32>:  stur   x0, [x29, #-0x8]
    0x10064a788 <+36>:  stur   x1, [x29, #-0x10]
->  0x10064a78c <+40>:  ldur   x0, [x29, #-0x8]
    0x10064a790 <+44>:  str    x0, [sp, #0x10]
    0x10064a794 <+48>:  ldr    x10, [x10]
    0x10064a798 <+52>:  str    x10, [sp, #0x18]
    0x10064a79c <+56>:  ldr    x1, [x9]
    0x10064a7a0 <+60>:  mov    x0, x8
    0x10064a7a4 <+64>:  bl     0x10064ab9c               ; symbol stub for: objc_msgSendSuper2
    0x10064a7a8 <+68>:  adrp   x8, 2
    0x10064a7ac <+72>:  add    x8, x8, #0xd08            ; =0xd08 
    0x10064a7b0 <+76>:  adrp   x9, 2
    0x10064a7b4 <+80>:  add    x9, x9, #0xd20            ; =0xd20 
    0x10064a7b8 <+84>:  ldr    x9, [x9]
    0x10064a7bc <+88>:  ldr    x1, [x8]
    0x10064a7c0 <+92>:  mov    x0, x9
    0x10064a7c4 <+96>:  bl     0x10064ab90               ; symbol stub for: objc_msgSend
    0x10064a7c8 <+100>: adrp   x8, 2
    0x10064a7cc <+104>: add    x8, x8, #0xd10            ; =0xd10 
    0x10064a7d0 <+108>: ldr    x1, [x8]
    0x10064a7d4 <+112>: bl     0x10064ab90               ; symbol stub for: objc_msgSend
    0x10064a7d8 <+116>: mov    x8, #0x0
    0x10064a7dc <+120>: add    x9, sp, #0x8              ; =0x8 
    0x10064a7e0 <+124>: str    x0, [sp, #0x8]
    0x10064a7e4 <+128>: mov    x0, x9
    0x10064a7e8 <+132>: mov    x1, x8
    0x10064a7ec <+136>: bl     0x10064abc0               ; symbol stub for: objc_storeStrong
    0x10064a7f0 <+140>: ldp    x29, x30, [sp, #0x30]
    0x10064a7f4 <+144>: add    sp, sp, #0x40             ; =0x40 
    0x10064a7f8 <+148>: ret    
  • 1.2 可以看到上面简单的一句代码转换为了很多条汇编指令,汇编指令是与机器码一一对应的,每执行一条汇编指令就是一个通电放电的过程,所以每条汇编执行执行的时间几乎相等。上面是在arm64架构下的汇编代码,不同的指令集对应的汇编代码会有所不同。接下来会分段剖析该汇编指令。
2. 首先分析前4条指令
    //拉伸64字节栈控件,sp为指向栈底的寄存器
    0x104802764 <+0>:   sub    sp, sp, #0x40             ; =0x40 
   // x29和x30寄存器入栈
    0x104802768 <+4>:   stp    x29, x30, [sp, #0x30]
   //sp指向48字节处位置,存入x29寄存器
    0x10480276c <+8>:   add    x29, sp, #0x30            ; =0x30 
  //sp从#0x30处偏移#0x10,即指向16直接处
    0x104802770 <+12>:  add    x8, sp, #0x10             ; =0x10 
  • 2.1 关于寄存器,这里简单说下,如下图:
    寄存器
  • 2.2 如上图,iOS中主要有异常处理寄存器、浮点寄存器以及通用寄存器。通用寄存器也称数据地址寄存器,通常用来做数据计算的临时存储、累加、计数、地址保存等功能,定义这些寄存器的作用主要是用于在CPU指令中保存操作数,在CPU中当做一些常规变量来使用。ARM64拥有有32个64位的通用寄存器 x0 到 x30,以及XZR(零寄存器),这些通用寄存器有时也有特定用途,w0 到 w28 这些是32位的. 因为64位CPU可以兼容32位.所以可以只使用64位寄存器的低32位。而在XCode中并没有看到X29和X30寄存器,如下图:
    通用寄存器
  • 2.3 其实这里的fp就是x29,lr就是x30寄存器,只不过被苹果重新进行了命名。x29在某些时刻会保存栈顶的地址,sp会一直保存栈底的地址,lr(x30)保存下一条指令执行的地址,pc指向当前指令的地址,cpsr为状态寄存器。如下图:
    寄存器
  • 2.4 为了更形象理解上面4条指令,绘制栈空间分配图如下:
    拉伸栈空间
3. 接下来继续往下看,x9寄存器放置ViewDidLoad方法的地址
  //将地址 0x104802774 左移三位 即 0x104802000 ,让后加2,即0x104804000
   0x104802774 <+16>:  adrp   x9, 2
 //将后三位偏移加上0xd00 ,得到地址0x104804d00
   0x104802778 <+20>:  add    x9, x9, #0xd00            ; =0xd00 
  • 3.1 通过算出该地址,我们即可以得到该方法名,如下图:
    x9寄存器
  • 3.2 当然这里读取x1的值也是该方法,因为最后x9会被读到x1中。默认函数的调用者被放在x0寄存器,方法地址被放在x1寄存器。
    x1寄存器
  • 3.3 通过相同的方法,我们分析接下来的两句指令:
 0x10064a77c <+24>:  adrp   x10, 2
 0x10064a780 <+28>:  add    x10, x10, #0xd30          ; =0xd30 
  • 3.3 寄存器 x10放置self对象的地址:
    x10寄存器
4. 将x0和x1寄存器入栈:
 0x10064a784 <+32>:  stur   x0, [x29, #-0x8]
 0x10064a788 <+36>:  stur   x1, [x29, #-0x10]
  • 4.1 x0和x1入栈图:
    x0和x1入栈
5. 将x29的值读到x0中,x0入栈:
->  0x10064a78c <+40>:  ldur   x0, [x29, #-0x8]
    0x10064a790 <+44>:  str    x0, [sp, #0x10]
6. 从栈区取出X10,也就是上面的self,然后再将x10拉伸24字节,我们可以看到,后面没有对X10这个寄存器的操作呢,说明对x10进行入栈保护,即当前控制器的地址在页面没销毁的情况下是一直强持有的。
    0x10064a794 <+48>:  ldr    x10, [x10]
    0x10064a798 <+52>:  str    x10, [sp, #0x18]
7. 将X9读到x1寄存器中,也就是上面的ViewDidLoad。然后将X8移动到X0寄存器,这样就将X8和X9两个寄存器给空出来,后面的函数进来就可以利用这一块空间了。
    0x10064a79c <+56>:  ldr    x1, [x9]
    0x10064a7a0 <+60>:  mov    x0, x8
8. 接下来可以看到objc_msgSendSuper2,就是调用 [super viewDidLoad]这个方法了,bl指令会调到方法内部,并且该命令会保存上一条指令。
   0x10064a7a4 <+64>:  bl     0x10064ab9c               ; symbol stub for: objc_msgSendSuper2
9. 接着x8寄存器就存储alloc方法的地址了,如下图:
x8被再次利用
10. 然后x9就用来存储对象的地址了,如下图:
x9被再次利用
11. 接着调用objc_msgSend发送alloc消息:
12. 当alloc调用完毕后,x8寄存器又被重复利用到存储init方法的地址:
x8再次被利用
13. 以上重点分析的是通过汇编看函数调用,接下来下面这6句汇编指令,就是和内存管理相关的:
   //将x8赋值为0,即x8 = nil
   0x10064a7d8 <+116>: mov    x8, #0x0
   //x9 = x9 + sp(#0x8)
    0x10064a7dc <+120>: add    x9, sp, #0x8              ; =0x8 
   //x0入栈
    0x10064a7e0 <+124>: str    x0, [sp, #0x8]
    //x0 = x9,x1 = x8作为参数传入objc_storeStrong函数
->  0x10064a7e4 <+128>: mov    x0, x9
    0x10064a7e8 <+132>: mov    x1, x8
    0x10064a7ec <+136>: bl     0x10064abc0               ; symbol stub for: objc_storeStrong
14. 通过查看x9寄存器的地址,可以得出x9寄存器保存的是person对象的指针:
保存指针的地址
15. 所以这里的两个参数x0和x1分别为&p和nil,伪代码如下:
 func(&p,nil);
16. 为了了解这个objc_storeStrong函数在做什么,我们就要去objc源码去查看,该函数的源码如下:
void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}
17. 所以将p对象传入方法后就等价于下面的代码了:
void
objc_storeStrong(&p, nil)
{
    id p = *&p;
    if (obj == nil) {
        return;
    }
    objc_retain(nil);
    &p = nil;
    objc_release(p);
}

@end
  • 17.1 内存管理如下图:
    内存管理
18. 最后三句汇编指令代表该函数的结束,即平衡栈空间:
    0x10064a7f0 <+140>: ldp    x29, x30, [sp, #0x30]
    0x10064a7f4 <+144>: add    sp, sp, #0x40             ; =0x40 
    0x10064a7f8 <+148>: ret   
  • 18.1 函数调用的栈空间变化图:
    栈空间变化
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270