转载

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

原因

最近在考虑对App中所有的message进行Log,资料不少,前人也有一些实现,做些记录。

对Objc_msgSend进行Hook

OC的Messaging都是通过改函数的调用的。如 [foo bar] ,会被转化成: Objc_msgSend(foo,@selector(bar)) 。这个大家应该都知道,不多说。具体参见Apple文档: 这里 和 这里

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

系统提供的方法

因为其实最开始并不想做Hook,所以先看看系统是否提供Log所有Message的方法。果然,Simulator上是有方法的。

开启instrumentObjcMessageSends

方法并不唯一

在lldb中

(lldb)p (void)instrumentObjcMessageSends(YES)

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

在code中

instrumentObjcMessageSends(YES);

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

extern void instrumentObjcMessageSends(BOOL);

当然可以稍微geek一点来调用:

通过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 文件来实现,不过那样就不动态了)。

看看最新的 源码 ,发现一段代码:

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 翻译在此 。这是个很好的参考。不过稍微有点年久失修,下次再讲讲原理和实践。

  • 在上面进行 logMessageSend Hook时还有点其他东西。原本有工程师发现以前的源码是支持从外部注入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); }                           

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

正文到此结束
Loading...