成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

如何實現(xiàn)一個IOS網(wǎng)絡監(jiān)控組件

xbynet / 2981人閱讀

摘要:此文由作者朱志強授權網(wǎng)易云社區(qū)發(fā)布。代碼演示核心代碼將原方法放在中聲明函數(shù)指針實現(xiàn)函數(shù)使用系統(tǒng)方法的函數(shù)指針完成系統(tǒng)的實現(xiàn)在這里獲取到了系統(tǒng)方法調用的時機在程序啟動后調用對委托模型的監(jiān)控替換方法時需要指定類名,而的的類并不確定。

此文由作者朱志強授權網(wǎng)易云社區(qū)發(fā)布。

Mobile Application Monitor IOS組件設計技術分享

背景
應用程序性能管理Application Performance Management(APM)是近年來比較火的互聯(lián)網(wǎng)產(chǎn)業(yè), Mobile Application Monitor(MAM)是其核心功能之一。 APM主要指對企業(yè)的關鍵業(yè)務應用進行監(jiān)測、優(yōu)化,它可以提高企業(yè)應用的可靠性和質量,保證用戶得到良好的服務,降低IT總擁有成本(TCO)。 一個企業(yè)的關鍵業(yè)務應用的性能強大,可以提高競爭力,并取得商業(yè)成功,因此,加強應用性能管理可以產(chǎn)生巨大商業(yè)利益。 目前成熟的產(chǎn)品有:

目標
iOS客戶端的網(wǎng)絡統(tǒng)計組件,用于統(tǒng)計iOS app的http請求的數(shù)據(jù),如請求時間,數(shù)據(jù),錯誤

設計一個可復用的框架,方便后續(xù)添加幀率、用戶體驗等監(jiān)測內(nèi)容

對應用的影響盡可能小,使用方便

設計模型
處理數(shù)據(jù)分4步:
數(shù)據(jù)收集,數(shù)據(jù)組裝,數(shù)據(jù)持久化,數(shù)據(jù)發(fā)送
線程模型:
數(shù)據(jù)收集負責初始化MAMDataBuilder,在持久化層隊列完成數(shù)據(jù)組裝和數(shù)據(jù)庫插入操作。
滿足發(fā)送數(shù)據(jù)條件時,首先持久化層隊列從數(shù)據(jù)庫查找數(shù)據(jù),然后在發(fā)送層隊列中發(fā)送數(shù)據(jù),發(fā)送結束后在持久化層隊列刪除該條數(shù)據(jù),再處理下一個數(shù)據(jù)。
下圖使用圖形演示了程序執(zhí)行過程,灰色矩形代表API接口

本文主要針對常用網(wǎng)絡技術的攔截技術做全面細致的講解和分析。

數(shù)據(jù)收集Hooker
針對IOS主要的網(wǎng)絡技術:NSURLConnection和CFNetwork的HTTP請求做數(shù)據(jù)收集

NSURLConnection的hook
對Objective-C對象發(fā)送消息的攔截

技術背景

Runtime
Objective-C是一門運行時語言,它會盡可能地把代碼執(zhí)行的決策從編譯和鏈接的時候,推遲到運行時。 這樣對寫代碼帶來很大的靈活性,比如說可以把消息轉發(fā)給你想要的對象,或者隨意交換一個方法的實現(xiàn)。 Method Swizzling正是使用交換方法實現(xiàn)的方式來達到hook的目的。

動態(tài)綁定
在編譯的時候,我們不知道最終會執(zhí)行哪一些代碼,只有在執(zhí)行的時候,通過selector去查詢,我們才能確定具體的執(zhí)行代碼。
Objective-C的方法類型是SEL(selector)。實例對象performSelector時,會在各自的消息選標(selector)/實現(xiàn)地址(address) 方法鏈表中根據(jù) selector 去查找具體的方法實現(xiàn)(IMP), 然后用這個方法實現(xiàn)去執(zhí)行具體的實現(xiàn)代碼。

IMP類型
IMP 是消息最終調用的執(zhí)行代碼的函數(shù)指針,可以理解為Objective-C的每個方法都會在編譯時被轉換成C函數(shù),IMP就是這個C函數(shù)的函數(shù)指針,下面會演示調用這個IMP和調用Objective-C方法是等效的。 一個Objective-C方法:

-(void)setFilled:(BOOL)arg;
它的Objective-C調用方式會是:

[aObject setFilled:YES];
調用基類NSObject的方法- (IMP)methodForSelector:(SEL)aSelector得到IMP

void (*setter)(id, SEL, BOOL);
setter = (void (*)(id, SEL, BOOL))[self methodForSelector:@selector(setFilled:)];
等價的C調用是對IMP(函數(shù)指針)的調用:

setter(self, @selector(setFilled:), YES)
Method Swizzling
正常情況,我們無法知道系統(tǒng)方法在何時被調用,但替換掉系統(tǒng)方法的代碼實現(xiàn),就可以獲取系統(tǒng)方法的調用時機,這就是Method Swizzling!
如下圖,修改selector對應的IMP為保存原IMP的函數(shù),這樣就實現(xiàn)了對系統(tǒng)調用的hook。

代碼演示
Method Swizzling核心代碼:

BOOL HTSwizzleMethodAndStore(Class class, BOOL isClassMethod, SEL original, IMP replacement, IMP* store) {
IMP imp = NULL;
Method method ; if (isClassMethod) {

  method= class_getClassMethod(class, original);

}else{

  method= class_getInstanceMethod(class, original);

} if (method) {

  imp = method_setImplementation(method,(IMP)replacement);      if (!imp) {
      imp = method_getImplementation(method);
  }

}else{

  MAMLog(@"%@:not found%@!!!!!!!!",NSStringFromClass(class),NSStringFromSelector(original));

} if (imp && store) { *store = imp; }//將原方法放在store中
return (imp != NULL);
}
聲明函數(shù)指針I(yè)MP store,實現(xiàn)函數(shù)MAM IMP

static NSURLConnection (Original_connectionWithRequest)(id self,

                                                SEL _cmd,                                                    NSURLRequest *request,                                                    id delegate);static NSURLConnection * MAM_connectionWithRequest(id self,
                                                  SEL _cmd,                                                      NSURLRequest *request,                                                      id delegate){  //使用系統(tǒng)方法的函數(shù)指針完成系統(tǒng)的實現(xiàn)

id result = Original_connectionWithRequest(self,

                                    _cmd,
                                    request,
                                    hookDelegate);//在這里獲取到了系統(tǒng)方法調用的時機

return result;
}
在程序啟動后調用Swizzling

HTSwizzleMethodAndStore(NSClassFromString(@"NSURLConnection"),

                      YES,                          @selector(connectionWithRequest:delegate:),
                      (IMP)MAM_connectionWithRequest,
                      (IMP *)&Original_connectionWithRequest);

對委托模型的監(jiān)控
Runtime替換方法時需要指定類名,而NSURLConnection的delegate的類并不確定。如果還是使用Method Swizzling攔截delegate的消息,每多一個使用NSURLConnectionDelegate的類都需要動態(tài)聲明一次IMP store和MAM IMP,效率太低。
解決辦法是使用proxy delegate替換NSURLConnection原來的delegate。只要保證proxy delegate將所有接收到的網(wǎng)絡回調,轉發(fā)給原來的delegate就好了。

CFNetwork的hook
對C函數(shù)調用的攔截

技術背景

使用Dynamic Loader hook 庫函數(shù) ---- fishhook
Dynamic Loader (dyld)通過更新Mach-O文件中保存的指針的方法來綁定符號。借用它,可以在運行時修改C函數(shù)調用的函數(shù)指針!
fishhook查找函數(shù)符號名的過程見下圖

上圖中,1061是間接符號表(Indirect Symbol Table)的偏移量,存放的符號表(Symbol Table)偏移量16343。
符號表中包含了字符表(String Table)偏移量,然后找到中真實符號名(Actual Symbol Name),fishhook對間接符號表的偏移量做了修改,這樣就修改了字符表偏移量,指向字符表中的真實符號名發(fā)生了變化,最終,通過修改真實符號名修改了真實調用函數(shù)的指針,達到hook的目的。

Stream的read size和Toll-Free Bridge
CFNetwork使用CFReadStreamRef做數(shù)據(jù)傳遞,其接收服務器響應的方式是使用回調函數(shù)。獲取服務器數(shù)據(jù)的方式是,當回調函數(shù)收到流中有數(shù)據(jù)的通知后,從流中讀取數(shù)據(jù),保存在客戶端內(nèi)存中。
對流的讀取不適合使用修改字符串表的方式,這樣做需要hook 系統(tǒng)也在使用的read函數(shù),而系統(tǒng)的read函數(shù)不僅僅被網(wǎng)絡請求的stream調用,還有所有的文件處理,并且hook一個頻繁調用的函數(shù)也是不可取的!
但是怎么才能只針對網(wǎng)絡請求的stream做處理呢,對一個C類型真的是很難,但是倘若對一個對象而言,我們有很多辦法可以用,能不能轉換呢?
能,用Toll-Free Bridge!有了它,就可以將CFReadStreamRef類型直接轉換成NSInputStream對象??!
Toll-Free Bridge可以將Cocoa對象轉換為CoreFoundation類型,查看CFReadStreamRead源碼:

CFIndex CFReadStreamRead(CFReadStreamRef readStream, UInt8 *buffer, CFIndex bufferLength) {
CF_OBJC_FUNCDISPATCH2(__kCFReadStreamTypeID, CFIndex, readStream, "read:maxLength:", buffer, bufferLength);
函數(shù)的第一行調用的是Cocoa的方法read:maxLength:,這就確認了Toll-Free Bridge的實現(xiàn)機制——用Objective-C實現(xiàn)了一個可以用純C調用的類庫。
最后,這樣設計被監(jiān)控的stream:
這樣就成功地將hook一個C函數(shù)的問題轉變成了hook一個Objective-C方法的問題,但是,NSInputStream仍然是一個底層的公共類,仍然需要對系統(tǒng)的read方法做hook,能不能只針對某個stream對象進行hook呢?
能,用Trampoline!

Objective-C消息轉發(fā)機制和Trampoline ---- 對指定對象的hook
當某個實例對象接收到一個消息,但是沒有找到這個消息的實現(xiàn)時,會調用下面的兩個方法,給開發(fā)者提供了轉發(fā)消息的選擇

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
-(void)forwardInvocation:(NSInvocation *)anInvocation;
借用轉發(fā)機制,可以實現(xiàn)對指定對象的hook:
設計一個繼承自NSObject的Proxy類,持有一個NSInputStream,記為OriginalStream。
使用上面的方法中將發(fā)向Proxy的消息轉發(fā)給OriginalStream。這樣一來,所有發(fā)向Proxy的消息的都由OriginalStream處理了。再重寫NSInputStream read方法就可以獲取到stream的size了。這種修改程序執(zhí)行方向的設計就稱為Trampoline,它的本意是蹦床,象征著將方法反彈給真正的接收對象。
MAMNSStreamProxy的核心代碼:

-(instancetype)initWithClient:(id*)stream
{if (self = ![super init])
{
_stream = ![stream retain];
}return self;
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{return ![_stream methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
![anInvocation invokeWithTarget:_stream];
}
-(NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len
{
NSInteger rv = [_stream read:buffer maxLength:len];//在這里記錄sizereturn rv;
}
代碼演示
和Method Swizzling類似,需要聲明函數(shù)指針和函數(shù)的實現(xiàn):

static CFReadStreamRef(*original_CFReadStreamCreateForHTTPRequest)(CFAllocatorRef alloc,

                                       CFHTTPMessageRef request);/**

MAMNSInputStreamProxy持有original CFReadStreamRef,轉發(fā)消息到original CFReadStreamRef,在方法 read 中獲取數(shù)據(jù)大小。

以original CFReadStreamRef為鍵,保存CFHTTPMessageRef request

*/static CFReadStreamRefMAM_CFReadStreamCreateForHTTPRequest(CFAllocatorRef alloc,

                               CFHTTPMessageRef request){ //使用系統(tǒng)方法的函數(shù)指針完成系統(tǒng)的實現(xiàn)

CFReadStreamRef originalCFStream = original_CFReadStreamCreateForHTTPRequest(alloc,

                                                                          request); //將CFReadStreamRef轉換成NSInputStream,保存在MAMNSInputStreamProxy中,返回的時候再轉換成CFReadStreamRef

NSInputStream stream = (__bridge NSInputStream)originalCFStream;
MAMNSInputStreamProxy outReadStream = ![![MAMNSInputStreamProxy alloc] initWithStream:stream]; /內(nèi)存管理, create的CF stream ref轉成NS stream proxy,CF不再引用,使用結束后release掉*/
CFRelease(originalCFStream); /內(nèi)存管理,ARC轉交引用管理給CF/
CFReadStreamRef result = (__bridge_retained CFReadStreamRef)((id)outReadStream); return result;
}
使用fishhook替換函數(shù)地址

save_original_symbols();int bFishHookWork = rebind_symbols((struct rebinding![1])
{{"CFReadStreamCreateForHTTPRequest", MAM_CFReadStreamCreateForHTTPRequest},},1);
void save_original_symbols(){
original_CFReadStreamCreateForHTTPRequest = dlsym(RTLD_DEFAULT, "CFReadStreamCreateForHTTPRequest");
}
數(shù)據(jù)攔截模型
根據(jù)CFNetwork API 的調用方式,使用fishhook和proxyStream獲取C函數(shù)的設計模型如下:

更多網(wǎng)易技術、產(chǎn)品、運營經(jīng)驗分享請訪問網(wǎng)易云社區(qū)。

文章來源: 網(wǎng)易云社區(qū)

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://systransis.cn/yun/25330.html

相關文章

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<