最近在考慮對App中全部的message進行Log,資料很多,前人也有一些實現,作些記錄。html
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
方法並不惟一objective-c
(lldb)p (void)instrumentObjcMessageSends(YES)
開啓以後Log日誌放在/private/tmp/msgSends-%d
,%d
是進程pid
。segmentfault
instrumentObjcMessageSends(YES);
要注意的是,使用前記得聲明一下:app
extern void instrumentObjcMessageSends(BOOL);
固然能夠稍微geek一點來調用:ide
typedef void functype(BOOL); void *libobjc = dlopen("/usr/lib/libobjc.dylib", RTLD_LAZY); functype *instrumentObjcMessageSends = dlsym(libobjc, "instrumentObjcMessageSends"); instrumentObjcMessageSends(YES);
日誌仍是放在一樣的地方。函數
上述只是開啓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);
sudo dtrace -q -n 'objc1234:::entry { printf("%s %s\n", probemod, probefunc); }'
上述方法只能在模擬器上,不支持iOS device。能夠參見源碼。
該方法只能Log,其餘也不能幹什麼。彷佛仍是得打objc_msgSend的主意。
在上面進行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); }
不太實用。由於新的實現已經沒有這個方法。只是以爲比較有趣,記錄一下。
原做寫於segmentfault 連接