Objective-C runtime 拾遗 (二)——Log message send

缘由

最近在考虑对App中全部的message进行Log,资料很多,前人也有一些实现,作些记录。html

对Objc_msgSend进行Hook

OC的Messaging都是经过改函数的调用的。如[foo bar],会被转化成:Objc_msgSend(foo,@selector(bar))。这个你们应该都知道,很少说。具体参见Apple文档:这里这里ios

若是可以对其进行Hook,那就能够进行Log了。Facebook出品了fishhook原理介绍不少,在载入符号表时进行替换,很少说。但最后问题是,Hook以后怎么将原来的参数传递给原来的实现呢?好像也不是很straightforward,这个后面再写一篇文章来讨论这个问题。但目前想要简单的经过HookObjc_msgSend来对message进行Log并不方便git

系统提供的方法

由于其实最开始并不想作Hook,因此先看看系统是否提供Log全部Message的方法。果真,Simulator上是有方法的。github

开启instrumentObjcMessageSends

方法并不惟一objective-c

在lldb中

(lldb)p (void)instrumentObjcMessageSends(YES)

开启以后Log日志放在/private/tmp/msgSends-%d%d是进程pidsegmentfault

在code中

instrumentObjcMessageSends(YES);

要注意的是,使用前记得声明一下:app

extern void instrumentObjcMessageSends(BOOL);

固然能够稍微geek一点来调用:ide

经过dlsym

typedef void functype(BOOL);
void *libobjc = dlopen("/usr/lib/libobjc.dylib", RTLD_LAZY);
functype *instrumentObjcMessageSends = dlsym(libobjc, "instrumentObjcMessageSends");
instrumentObjcMessageSends(YES);

日志仍是放在一样的地方。函数

logMessageSend

上述只是开启instrumentObjcMessageSends,可是,具体Log的实现是系统的。若是开发者想要对Log的message进行过滤,彷佛还要一些手段(固然能够经过处理msgSends-%d文件来实现,不过那样就不动态了)。
看看最新的源码,发现一段代码:ui

bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    ...
}

这个就是系统实现Log方法,具体不展开解释了。

那么只须要对logMessageSend进行Hook一下就行了,如今能够愉快的使用fishhook了。

#import "fishhook.h"
#import <dlfcn.h>

void (*orig_logMessageSend)(bool,const char *,const char *,SEL);
void my_logMessageSend(bool,const char *,const char *,SEL);

void my_logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    //实现本身的Log
    ......
    //也能够不调用
    orig_logMessageSend(isClassMethod,objectsClass,implementingClass,selector);
}

rebind_symbols((struct rebinding[1]){{"logMessageSend", my_logMessageSend, (void *)&orig_logMessageSend}}, 1);

使用DTrace

sudo dtrace -q -n 'objc1234:::entry { printf("%s %s\n", probemod, probefunc); }'

没试过,参考:1 2

总述&讨论

  • 上述方法只能在模拟器上,不支持iOS device。能够参见源码。

  • 该方法只能Log,其余也不能干什么。彷佛仍是得打objc_msgSend的主意。

已经有人这样干过了,Mike Ash 翻译在此。这是个很好的参考。不过稍微有点年久失修,下次再讲讲原理和实践。

  • 在上面进行logMessageSendHook时还有点其余东西。本来有工程师发现之前的源码是支持从外部注入Log函数的。这是系统的实现,源码

typedef int    (*ObjCLogProc)(BOOL, const char *, const char *, SEL);
__private_extern__ void    logObjcMessageSends      (ObjCLogProc    logProc)
{
    if (logProc)
    {
        objcMsgLogProc = logProc;
        objcMsgLogEnabled = 1;
    }
    else
    {
        objcMsgLogProc = logProc;
        objcMsgLogEnabled = 0;
    }

    if (objcMsgLogFD != (-1))
        fsync (objcMsgLogFD);
}

只要找到logObjcMessageSends符号,调用便可。虽然被staic隐藏了,但不妨碍。除了上述dlsym。还有下面一种方法:

#import <mach-o/nlist.h>
typedef int    (*ObjCLogProc)(BOOL, const char *, const char *, SEL);
typedef int (*LogObjcMessageSendsFunc)(ObjCLogProc);
extern bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                           SEL selector);
{
    LogObjcMessageSendsFunc fcn;
    struct nlist nl[2];
    bzero(&nl, sizeof(struct nlist) * 2);
    nl[0].n_un.n_name = "_instrumentObjcMessageSends";
    nl[1].n_un.n_name = "_logObjcMessageSends";

    typedef int (*LogObjcMessageSendsFunc)(ObjCLogProc);
    fcn = (LogObjcMessageSendsFunc)( (long) (&instrumentObjcMessageSends) + (nl[1].n_value-nl[0].n_value));
    fcn(&my_logMessageSend);
}

不太实用。由于新的实现已经没有这个方法。只是以为比较有趣,记录一下。

原做写于segmentfault 连接

相关文章
相关标签/搜索