首页
社区
课程
招聘
[原创]对objc_msgSend的分析
发表于: 2015-4-20 22:39 37360

[原创]对objc_msgSend的分析

2015-4-20 22:39
37360

对objc_msgSend的分析
        刚接触ios,每次调试程序,都能看到很多的objc_msgSend。看了函数说明,感觉有点类似windows的sendmessage,但还是理解不深刻。所以今天花了一下午的时间,分析了一下。
1、基础知识
参考:http://my.oschina.net/amoyai/blog/92191
objc_msgSend方法含两个必要参数:receiver、方法名(即:selector),如:
    [receiver message]; 将被转换为:objc_msgSend(receiver, selector);
objc_msgSend方法也能hold住message的参数,如:
    objc_msgSend(receiver, selector, arg1, arg2, …);
每个对象都有一个指向所属类的指针isa。通过该指针,对象可以找到它所属的类,也就找到了其全部父类,如下图所示:

    当向一个对象发送消息时,objc_msgSend方法根据对象的isa指针找到对象的类,然后在类的调度表(dispatch table)中查找selector。如果无法找到selector,objc_msgSend通过指向父类的指针找到父类,并在父类的调度表(dispatch table)中查找selector,以此类推直到NSObject类。一旦查找到selector,objc_msgSend方法根据调度表的内存地址调用该实现。 通过这种方式,message与方法的真正实现在执行阶段才绑定。
为了保证消息发送与执行的效率,系统会将全部selector和使用过的方法的内存地址缓存起来。每个类都有一个独立的缓存,缓存包含有当前类自己的 selector以及继承自父类的selector。查找调度表(dispatch table)前,消息发送系统首先检查receiver对象的缓存。
注意红色字体,经过下面的分析,红色字体都是珠玑!
2、objc_msgSend
        这个函数在libobjc.A.dylib中,在下面的目录中可以找到(我用的ios7.1.1系统)。

将它拖入ida中分析:(注意红线勾的地方)

3、整理
        经过调试,我对前面的图进行了整理。

4、例证
        分别对先后调用的Hello函数下断点,通过查看内存和寄存器来验证前面的结论。
- (IBAction)onBtn:(id)sender {
   
    //BOOL isSet = ( [[NSUserDefaults standardUserDefaults] valueForKey:@"KeyForCurrentPatternToUnlock"]) ? YES : NO;
   
    //CMyClass *pTest = [[CMyClass alloc] init1];
    CMyClass *pTest = [CMyClass alloc];
    [pTest Hello:4]; //断点1--第一次调用Hello
    pTest->m_nCount = 10;
    pTest->m_nLen = 15;
   
    //[CMyClass testClassFun];
    [pTest Hello:4]; //断点2--第二次调用Hello
    [pTest World:"Test" Num:6];
}

进入objc_msgSend后查看内存和寄存器,如下图:

4.1断点1--第一次调用Hello
(lldb) si
(lldb) re r
General Purpose Registers:
r0 = 0x16d3d610
r1 = 0x000760ea  "Hello:"
r2 = 0x00000004
r3 = 0x3aec5621  libobjc.A.dylib`objc_msgSend + 1
r4 = 0x00000004
r5 = 0x3aec5621  libobjc.A.dylib`objc_msgSend + 1
r6 = 0x0007770c  "Hello:"
r7 = 0x27d93508
r8 = 0x0000000f
r9 = 0x00000000
r10 = 0x00077724  test_msgSend`CMyClass.m_nLen
r11 = 0x0000000a
r12 = 0x3d422234  (void *)0x3b4e33c8: _platform_memset$VARIANT$CortexA9
sp = 0x27d934a0
lr = 0x00075cab  test_msgSend`-[ViewController onBtn:] + 235 at ViewController.m:36
pc = 0x3aec5620  libobjc.A.dylib`objc_msgSend
cpsr = 0x20000030

(lldb) memory read -s4 -fx -c16 0x16d3d610  // receiver指针(R0)
0x16d3d610: 0x00077748 0x00000000 0x00000000 0x00000000  // isa指针
0x16d3d620: 0x00000000 0x00000000 0x00000000 0x00000000
0x16d3d630: 0x00000000 0x16d2f3c0 0x00000000 0x00000000
0x16d3d640: 0x3b686010 0x01000480 0x00090003 0x00000001
(lldb) memory read -s4 -fx -c16 0x00077748  // isa指针
0x00077748: 0x00077734 0x3d3d1050 0x3aed02a8 0x00000000 // 函数结构体数组
0x00077758: 0x16d52a70 0x00077770 0x3bb0a1e4 0x17293c00
0x00077768: 0x004400ff 0x16e50fb0 0x3d3d1064 0x3bb0a1d0
0x00077778: 0x16e523f0 0x00060007 0x16e50e50 0x3d3d1064
(lldb) memory read -s4 -fx -c16 0x3aed02a8  // 第一次调用,还未缓存,所以为空
0x3aed02a8: 0x00000000 0xfffffffc 0x00000003 0x00000000
0x3aed02b8: 0x00000000 0x00000000 0x00000000 0x00000000
0x3aed02c8: 0x00000000 0x00000000 0x00000000 0x00000000
0x3aed02d8: 0x00000000 0x624f0000 0x7463656a 0x495f5f00

// 运行到_objc_msgSend里这一行
// __text:37D0963A                 TEQ.W           R12, R1 ; 与传入的函数名进行比较
// 此时在lldb中查看寄存器
(lldb) re read
General Purpose Registers:
r0 = 0x16d3d610
r1 = 0x000760ea  "Hello:"
r2 = 0x00000004
r3 = 0x3aec5621  libobjc.A.dylib`objc_msgSend + 1
r4 = 0x00000004
r5 = 0x3aec5621  libobjc.A.dylib`objc_msgSend + 1
r6 = 0x0007770c  "Hello:"
r7 = 0x27d93508
r8 = 0x0000000f
r9 = 0x3aed02a8  libobjc.A.dylib`_objc_empty_cache // 这里显示空缓存
r10 = 0x00077724  test_msgSend`CMyClass.m_nLen
r11 = 0x0000000a
r12 = 0x00000000
sp = 0x27d934a0
lr = 0x00075cab  test_msgSend`-[ViewController onBtn:] + 235 at ViewController.m:36
pc = 0x3aec563e  libobjc.A.dylib`objc_msgSend + 30
cpsr = 0x20000030
        从上面的可以看出,第一次调用,系统并未缓存。最后会进入函数__objc_msgSend_uncached,查找并缓存函数地址。
4.2、断点2--第二次调用Hello
(lldb) si
(lldb) re read
General Purpose Registers:
r0 = 0x16d3d610
r1 = 0x000760ea  "Hello:"
r2 = 0x00000004
r3 = 0x00077724  test_msgSend`CMyClass.m_nLen
r4 = 0x00000004
r5 = 0x3aec5621  libobjc.A.dylib`objc_msgSend + 1
r6 = 0x0007770c  "Hello:"
r7 = 0x27d93508
r8 = 0x0000000f
r9 = 0x0000000f
r10 = 0x00077724  test_msgSend`CMyClass.m_nLen
r11 = 0x0000000a
r12 = 0x0007770c  "Hello:"
sp = 0x27d934a0
lr = 0x00075cdb  test_msgSend`-[ViewController onBtn:] + 283 at ViewController.m:41
pc = 0x3aec5620  libobjc.A.dylib`objc_msgSend
cpsr = 0x60000030

(lldb) memory read -s4 -fx -c16 0x16d3d610 // receiver指针(R0)
0x16d3d610: 0x00077748 0x0000000f 0x0000000a 0x00000000 // isa指针
0x16d3d620: 0x00000000 0x00000000 0x00000000 0x00000000
0x16d3d630: 0x00000000 0x16d2f3c0 0x00000000 0x00000000
0x16d3d640: 0x3b686010 0x01000480 0x00090003 0x00000001
(lldb) memory read -s4 -fx -c16 0x00077748 // isa指针
0x00077748: 0x00077734 0x3d3d1050 0x16e47b20 0x00010003  // 函数结构体数组
0x00077758: 0x16d52a70 0x00077770 0x3bb0a1e4 0x17293c00
0x00077768: 0x004400ff 0x16e50fb0 0x3d3d1064 0x3bb0a1d0
0x00077778: 0x16e523f0 0x00060007 0x16e50e50 0x3d3d1064
(lldb) memory read -s4 -fx -c16 0x16e47b20  // 函数结构体数组
0x16e47b20: 0x00000000 0x00000000 0x00000000 0x00000000
0x16e47b30: 0x000760ea 0x00075aed 0x00000000 0x00000000 // 有数据了。这里我有点迷糊,因为有时
0x16e47b40: 0x00000001 0x16e47b18 0x00000000 0x00000000 // 候数据是在地址0x16e47b28,很奇怪!
0x16e47b50: 0x00000028 0x30814000 0x031bc000 0x00000000

(lldb) memory read 0x000760ea  // 显示函数名字
0x000760ea: 48 65 6c 6c 6f 3a 00 57 6f 72 6c 64 3a 4e 75 6d  Hello:.World:Num
0x000760fa: 3a 00 6d 5f 6e 4c 65 6e 00 6d 5f 6e 43 6f 75 6e  :.m_nLen.m_nCoun
(lldb) di -s 0x00075aed  // 显示函数地址
test_msgSend`-[CMyClass Hello:] + 1 at CMyClass.m:25:
0x75aed:  ldr    r5, [r6, #0x78]
0x75aef:  strh   r6, [r0, #0x1a]
        第二次调用,系统有缓存。就不会进入函数__objc_msgSend_uncached,而是直接取出函数地址并调用。

word:
对objc_msgSend的分析.rar

src:
src.rar


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 3
支持
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  CCkicker   +1.00 2017/05/22
最新回复 (25)
雪    币: 126
活跃值: (46)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
楼主赞~~
2015-4-21 00:10
0
雪    币: 8835
活跃值: (2404)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
3
楼主大赞~
2015-4-21 03:15
0
雪    币: 585
活跃值: (568)
能力值: ( LV13,RANK:290 )
在线值:
发帖
回帖
粉丝
4
// 现在搞清楚,更新下
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
  
    [CMyClass testClassFun ]; // +号函数

        // 后面都是-号函数
    CMyClass *pTest = [[CMyClass alloc] init1];
    [pTest Hello:4];
    [pTest World:"Test" Num:5];
}

(lldb) memory read -s4 -fx -c16 0x1668b600    // 0x1668b600为实例类指针 pTest
0x1668b600: 0x00097760 0x00000001 0x00000002 0x6c6c6568
0x1668b610: 0x0000006f 0x00000000 0x00000000 0x00000000
0x1668b620: 0x3348bd3c 0x3aecba4d 0x3348b284 0x3058e665
0x1668b630: 0x00000000 0x00000000 0x00000000 0x00000000
(lldb) memory read -s4 -fx -c16 0x00097760  // 0x00097760为isa指针
0x00097760: 0x0009774c 0x3d3d1050 0x1659c110 0x00010003  // 0x1659c110为实例类的函数结构体数组
0x00097770: 0x1659b3a0 0x00097788 0x3bb0a1e4 0x16c08200
0x00097780: 0x000e007f 0x16599490 0x3d3d1064 0x3bb0a1d0
0x00097790: 0x1659a760 0x00030007 0x16599330 0x3d3d1064
(lldb) memory read -s4 -fx -c16 0x0009774c  // 基类指针
0x0009774c: 0x3d3d1064 0x3d3d1064 0x1668bd80 0x00020003  // 0x1668bd80为基类的函数结构体数组
0x0009775c: 0x1659b290 0x0009774c 0x3d3d1050 0x1659c110
0x0009776c: 0x00010003 0x1659b3a0 0x00097788 0x3bb0a1e4
0x0009777c: 0x16c08200 0x000e007f 0x16599490 0x3d3d1064

(lldb) memory read -s4 -fx -c16 0x1668bd80  // 基类的函数结构体数组
0x1668bd80: 0x00000000 0x00000000 0x000960dd 0x00095b49 // 0x000960dd为函数名
0x1668bd90: 0x00000000 0x00000000 0x3348b603 0x3aecba95
0x1668bda0: 0x00000001 0x1668bd78 0x00000000 0x00000000
0x1668bdb0: 0x90000000 0x90000000 0x73730007 0x286e7546
(lldb) memory read -s4 -fx -c16 0x1659c110 // 实例类的函数结构体数组
0x1659c110: 0x00000000 0x00000000 0x00000000 0x00000000
0x1659c120: 0x000960ea 0x00095a5d 0x00000000 0x00000000 // 0x000960ea为函数名
0x1659c130: 0x00000001 0x1659c108 0x00000000 0x00000000
0x1659c140: 0x1659bfb0 0x00000000 0x1659bad0 0x00000000

(lldb) memory read 0x000960dd
0x000960dd: 74 65 73 74 43 6c 61 73 73 46 75 6e 00 69 6e 69  testClassFun.ini
0x000960ed: 74 31 00 48 65 6c 6c 6f 3a 00 57 6f 72 6c 64 3a  t1.Hello:.World:
(lldb) memory read 0x000960ea
0x000960ea: 69 6e 69 74 31 00 48 65 6c 6c 6f 3a 00 57 6f 72  init1.Hello:.Wor
0x000960fa: 6c 64 3a 4e 75 6d 3a 00 6d 5f 6e 4c 65 6e 00 6d  ld:Num:.m_nLen.m
2015-4-21 09:20
0
雪    币: 585
活跃值: (568)
能力值: ( LV13,RANK:290 )
在线值:
发帖
回帖
粉丝
5
(lldb) memory read -s4 -fx -c16 0x1668bd80  // 基类的函数结构体数组
0x1668bd80: 0x00000000 0x00000000 0x000960dd 0x00095b49 // 0x000960dd为函数名
0x1668bd90: 0x00000000 0x00000000 0x3348b603 0x3aecba95
0x1668bda0: 0x00000001 0x1668bd78 0x00000000 0x00000000
0x1668bdb0: 0x90000000 0x90000000 0x73730007 0x286e7546
(lldb) memory read -s4 -fx -c16 0x1659c110 // 实例类的函数结构体数组
0x1659c110: 0x00000000 0x00000000 0x00000000 0x00000000
0x1659c120: 0x000960ea 0x00095a5d 0x00000000 0x00000000 // 0x000960ea为函数名
0x1659c130: 0x00000001 0x1659c108 0x00000000 0x00000000
0x1659c140: 0x1659bfb0 0x00000000 0x1659bad0 0x00000000

     可以看见,基类和实例类的函数结构体数组位置不一样哦。
2015-4-21 09:21
0
雪    币: 585
活跃值: (568)
能力值: ( LV13,RANK:290 )
在线值:
发帖
回帖
粉丝
6
在oc里,+号函数和-号函数的区别:
-(void)是实例方法,只有定义了这个类的实例,才能用实例调用这个方法。
+(void)是类方法,用类名可以直接调用这个方法。

通过分析,+号函数是基类调用的,-号函数是实例类调用的。其函数结构体数组的位置也不一样。
2015-4-21 09:26
0
雪    币: 608
活跃值: (403)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
7
收益了。。。
2015-4-21 09:27
0
雪    币: 133
活跃值: (233)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
还可以直接看代码的:http://www.opensource.apple.com/source/objc4/
2015-4-21 09:28
0
雪    币: 2323
活跃值: (4113)
能力值: ( LV12,RANK:530 )
在线值:
发帖
回帖
粉丝
9
其实就是弄清楚objective-c runtime机制就可以了,Object-C 是通过 Dynamic Binding (动态绑定) 机制来实现消息传递的,对象对于详细的响应和处理都是在 runtime 运行时才能决定https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html
http://blog.jobbole.com/79545/
2015-4-21 09:54
0
雪    币: 585
活跃值: (568)
能力值: ( LV13,RANK:290 )
在线值:
发帖
回帖
粉丝
10
感谢楼上2位
2015-4-21 10:32
0
雪    币: 585
活跃值: (568)
能力值: ( LV13,RANK:290 )
在线值:
发帖
回帖
粉丝
11
在objc-msg-arm.s中找到源码,真是一模一样啊。哈哈哈

.macro CacheLookup
       
        ldrh        r12, [r9, #CACHE_MASK]        // r12 = mask
        ldr        r9, [r9, #CACHE]        // r9 = buckets
.if $0 == STRET  ||  $0 == SUPER_STRET
        and        r12, r12, r2                // r12 = index = SEL & mask
.else
        and        r12, r12, r1                // r12 = index = SEL & mask
.endif
        add        r9, r9, r12, LSL #3        // r9 = bucket = buckets+index*8
        ldr        r12, [r9]                // r12 = bucket->sel
2:
.if $0 == STRET  ||  $0 == SUPER_STRET
        teq        r12, r2
.else
        teq        r12, r1
.endif
        bne        1f
        CacheHit $0
1:       
        cmp        r12, #1
        blo        LCacheMiss_f                // if (bucket->sel == 0) cache miss
        it        eq                        // if (bucket->sel == 1) cache wrap
        ldreq        r9, [r9, #4]                // bucket->imp is before first bucket
        ldr        r12, [r9, #8]!                // r12 = (++bucket)->sel
        b        2b

.endmacro

/********************************************************************
*
* id objc_msgSend(id self, SEL        _cmd,...);
*
********************************************************************/

        ENTRY objc_msgSend
        MESSENGER_START
       
        cbz        r0, LNilReceiver_f

        ldr        r9, [r0]                // r9 = self->isa
        CacheLookup NORMAL
        // calls IMP or LCacheMiss

LCacheMiss:
        MESSENGER_END_SLOW
        ldr        r9, [r0, #ISA]                // class = receiver->isa
        b        __objc_msgSend_uncached

LNilReceiver:
        mov     r1, #0
        MESSENGER_END_NIL
        bx      lr       

LMsgSendExit:
        END_ENTRY objc_msgSend
2015-4-21 11:25
0
雪    币: 38
活跃值: (526)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
12
以前破解一个mac系统上的软件的时候遇到objc_msgSend ,当时没有找到任何中文资料。最后Google到国外一个博客中有讲解objc_msgSend 。
2015-4-21 11:41
0
雪    币: 38
活跃值: (526)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
13
总算找到了,一共4篇文章。
http://www.friday.com/bbum/2009/12/18/objc_msgsend-part-1-the-road-map/

http://cocoasamurai.blogspot.com/2010/01/understanding-objective-c-runtime.html
2015-4-21 11:47
0
雪    币: 573
活跃值: (1009)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
14
收益, 前排
2015-4-21 17:15
0
雪    币: 2359
活跃值: (288)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
第一次学习。
2015-4-21 21:21
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
非常感谢  学习了
2015-9-9 17:16
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
来赞赞,么么哒
2015-9-29 14:57
0
雪    币: 2044
活跃值: (237)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
18
Mark..
2015-11-21 13:52
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
图没了
2017-1-4 20:02
0
雪    币: 585
活跃值: (568)
能力值: ( LV13,RANK:290 )
在线值:
发帖
回帖
粉丝
20


x86分析,做个记录



2017-5-18 23:44
0
雪    币: 7006
活跃值: (4217)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
感谢分享
2017-5-18 23:51
0
雪    币: 510
活跃值: (299)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
22
2017-6-6 12:02
0
雪    币: 224
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
23
objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@%@%d"));请问大哥,后面的参数的形参或值从哪里分析找到
2019-7-22 21:05
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
24
砖叔 objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@%@%d"));请 ...
同问
  _objc_msgSend(&_OBJC_CLASS_$_NSString,"stringWithFormat:",&cf_%@);
  lVar7 = _objc_retainAutoreleasedReturnValue();
这个stringWithFormat后面的参数消失了,怎么解析呢?
2019-10-28 09:17
0
雪    币: 392
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
25
厉害了
2019-12-14 10:17
0
游客
登录 | 注册 方可回帖
返回
//