AppleTrace是一个用来分析iOSApp性能的工具。
背景一般情况下使用Istrumets(主要是TimeProfiler)进行iOSApp的性能分析就足够了,但是TimeProfiler把调用方法都合并了起来,失去了时序的表现。直到有一天看到Adroid开发的同事使用 systrace 分析性能,systrace生成一个html文件,把函数(方法)的调用耗时按照先后顺序表现出来。心里想:要是iOS也有这样的工具就好了。了解到这个html文件是 catapult 生成的。
一天看到iosre论坛一篇hookobjc_msgSed的帖子。突然想到,可以结合catapult来生成ObjectiveC方法的性能分析图(暂且这么叫吧)。(虽然一直也有hookobjc_msgSed的方法,但这次煮好的佳肴终于忍不住下手了)。
说搞就开始搞,暂停几天开发MachOExplorer。近期一直利用少之又少的业余时间蜗牛般开发MachOExplorer,但现在看来生成性能分析图更是重要,回想过去的一些苦力加班,如果能生成这个性能分析图,当时岂不是很快就解决问题了。
目标hook所有的objc_msgSed,也就是把每个ObjectiveC方法的耗时计算出来,并按照先后顺序生成性能分析图。
要解决的问题如何生成最终的html从这里可以了解到catapult是如何生成html的。其中一种方式可以是:Chrome’s trace_evetformat。简单来说,trace_evetformat 就是个jso格式,按照这个约定的jso格式填充数据后,就可以使用trace2html命令(pytho脚本)转换为最终的html文件了。
$CATAPULT/tracig/bi/trace2htmlmy_trace.jso--output=my_trace.html&&opemy_trace.html如何Hookobjc_msgSed见文章使用HookZz快速逆向(Hackobjc_msgSed)理清逻辑
HookZz是jmpews开发的微型hook框架,使用起来十分灵活。详见 https://jmpews.github.io/zzpp/
如何生成trace_evetformat的jso文件参考文档Chrome’s trace_evetformat 可以了解到,最简单的jso文件,可以是这样:
[{"ame":"Asub","cat":"PERF","ph":"B","pid":22630,"tid":22630,"ts":829},{"ame":"Asub","cat":"PERF","ph":"E","pid":22630,"tid":22630,"ts":833}]每一行表示一个Evet,
{"ame":"myName","cat":"category,list","ph":"B","ts":12345,"pid":123,"tid":456,"args":{"someArg":1,"aotherArg":{"value":"myvalue"}}}每个字段的含义如下:
-ame:Theameoftheevet,asdisplayediTraceViewer-cat:Theevetcategories.Thisisacommaseparatedlistofcategoriesfortheevet.ThecategoriescabeusedtohideevetsitheTraceViewerUI.-ph:Theevettype.Thisisasiglecharacterwhichchagesdepedigothetypeofevetbeigoutput.Thevalidvaluesarelistedithetablebelow.Wewilldiscusseachphasetypebelow.-ts:Thetracigclocktimestampoftheevet.Thetimestampsareprovidedatmicrosecodgraularity.-tts:Optioal.Thethreadclocktimestampoftheevet.Thetimestampsareprovidedatmicrosecodgraularity.-pid:TheprocessIDfortheprocessthatoutputthisevet.-tid:ThethreadIDforthethreadthatoutputthisevet.-args:Ayargumetsprovidedfortheevet.Someoftheevettypeshaverequiredargumetfields,otherwise,youcaputayiformatioyouwishihere.TheargumetsaredisplayediTraceViewerwheyouviewaevetitheaalysissectio.其中ph(evettype)是需要关心的:
EvettypeEvetphasesDuratioEvetsB(begi),E(ed)……也就是说一个方法的调用,至少有两行,ph=B和ph=E。
格式弄清楚后,就需要生成jso文件了。生成这个jso文件本质上就是个日志功能,为了尽最大可能不影响App的性能,使用内存映射mmap方法来写文件。同时为了简单的处理多线程问题,使用了串行queue。代码见这里
最终trace文件会生成在App沙盒中的Library/appletracedata目录。由于日志量可能很大,又结合mmap的特性,日志文件会以下面的逻辑生成:
trace.appletracetrace_1.appletracetrace_2.appletracetrace_3.appletrace...trace_N.appletrace每个appletrace文件16MB,由于mmap的特性(只能映射固定大小文件),文件末尾一般会有\0来填充。
生成这些appletrace文件后,需要从App的沙盒中复制出来。使用merge.py把appletrace文件转换为trace_evetformat的jso文件。
pythomerge.py-d<appletracedatadirectory>最终执行catapult的trace2html脚本,生成最终的html文件。
pythocatapult/tracig/bi/trace2htmlappletracedata/trace.jso--output=appletracedata/trace.html 使用方法采集数据目前有两种采集数据的方式。
手动APTBegiSectio和APTEdSectio这种场景是:我不想hook所有的ObjectiveC方法,我只想在分析性能时,一点一点手动添加开始点和结束点。(这点Adroid的systrace也是支持)虽然麻烦,但在定位到大体方向后,这样更加精细和准确,避免了hook对App本身性能的影响。
(1)只需要把 appletrace.h和appletrace.mm文件拖入自己的功能即可。(当然这里可以做成CocoaPods,有时间可以做下)。
(2)然后在函数(方法)的开头和结尾(或者自己感兴趣的区间),调用APTBegiSectio 和 APTEdSectio即可。对于ObjectiveC方法可以使用宏APTBegi和APTEd。
//ObjectiveCclassmethod#defieAPTBegiAPTBegiSectio([NSStrigstrigWithFormat:@"[%@]%@",self,NSStrigFromSelector(_cmd)].UTF8Strig)#defieAPTEdAPTEdSectio([NSStrigstrigWithFormat:@"[%@]%@",self,NSStrigFromSelector(_cmd)].UTF8Strig)参考例子 sample/MaualSectioDemo。
Hookobjc_msgSed这种场景是:我想初步定为哪里有耗时的操作,可以整体上Hookobjc_msgSed一次,对整个App的流程有个大致了解。
(1)把动态库的工程appletrace.xcodeproj拖拽到目标工程。(2)并配置动态库的依赖 TargetDepedecies 和 CopyFiles。
参考 sample/TraceAllMsgDemo。
注意:
需要关闭BitCode。仅支持arm64。处理数据,生成html从App的沙盒中复制出 Library/appletracedata 目录。(例如:Xcode可以直接Dump出整个沙盒)
然后,
//处理mmap的日志文件pythomerge.py-d<appletracedatadirectory>//生成htmlpythocatapult/tracig/bi/trace2htmlappletracedata/trace.jso--output=appletracedata/trace.html//打开opetrace.html就可以看到
性能影响目前对App性能的影响主要是:
Hookobjc_msgSed:这个是主要的影响,因此生成的最终结果仅用于分析、对比,而不能认为就是耗费了这些数值。日志文件:为了写日志,mmap了文件,还创建了队列。对App本身的性能也有影响。局限由于HookZz对objc_msgSed的hook仅实现了arm64架构,因此只能在真机上分析。(当然这也足够了,主流设备就是arm64)
计划计划1:dtrace对于数据的产生来源,目前有两种:
手动APTBegiSectio和APTEdSectioHookobjc_msgSed最近一段时间对 dtrace也学习了一段时间了,完全可以针对模拟器使用dtrace来生成数据。dtrace由于是内核层,对App本身的性能影响很小,而且dtrace不仅仅可以hook(trace)ObjectiveC方法,还可以traceC方法、swift方法。这是下一步的计划。
计划2:白名单类/黑名单类Hookobjc_msgSed的方法,有的类可能并不关心。可以采用白名单或者黑名单的方式,缩小分析范围。
计划3:Hook+loadadC++staticiitializers见Amethodofhookstaticiitializers 和Amethodofhookobjectivec+load
总结这个工具本身的代码不多(写日志),主要是组合了catapult和HookZz,再次感谢catapult和HookZz。
评论