原因
最近在考慮對App中所有的message進行Log,資料不少,前人也有一些實現,做些記錄。
對Objc_msgSend進行Hook
OC的Messaging都是通過改函式的呼叫的。如[foo bar]
,會被轉化成:Objc_msgSend(foo,@selector(bar))
。這個大家應該都知道,不多說。具體參見Apple文件:這裡和這裡
如果能夠對其進行Hook,那就可以進行Log了。Facebook出品了fishhook,原理介紹很多,在載入符號表時進行替換,不多說。但最後問題是,Hook之後怎麼將原來的引數傳遞給原來的實現呢?好像也不是很straightforward,這個後面再寫一篇文章來討論這個問題。但目前想要簡單的通過HookObjc_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
", 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 連結