前言
最近看到公司同事的《iOS内存那些事》系列文章,其中的一篇文章讲了他在研究WebKit中内存管理的时候,发现可以用phys_footprint来衡量内存,其结果和xcode debug显示的值基本一致。文章通读下来,收获颇丰~回味之余,突然脑洞了一下,为啥不直接逆向一下Xcode,学习一下xcode debug app时它是怎么实现内存监控的?刚好最近在自学逆向知识,顺便也来练练手~
动手实践
准备一个小项目
运行一下,我们可以在debug面板看到memory report信息
{:height=”100%” width=”100%”}
lldb和hopper的使用
- 通过如下操作,我们可以直接attach Xcode调试
1 | ➜ ~ lldb -n Xcode |
- 来到Xcode debug面板,可以直接看到app运行时的内存信息。先小试一下那个内存信息栏能否响应点击操作。加个断点,尝试点击一下那个内存栏,bingo,顺利跑到断点处~
1 | (lldb) b -[NSResponder mouseUp:] |
因为Xcode肯定是x86_64架构编译的,所以通过po $rdi
,可以看到点击方法的对象是<NSTextField: 0x7fb7aed38280>
。 第一直觉告诉我,NSTextField是不是类似于UITextField,有text属性可以被赋值?查看了一下apple文档,它的父类NSControl
有个stringValue
属性可以设置,设断点,发现面板上内存变化时,断点触发了,bt
一下,可以看到如下信息(注意,要先确定触发断点的是展示内存的那个NSTextField)
1 | (lldb) bt |
从函数调用栈上,我们可以看出,NSTextField值的变化是通过kvo某个值实现的。image lookup -rn '\[DBGGaugeMemoryEditor\
,可以发现它位于/Applications/Xcode.app/Contents/PlugIns/DebuggerUI.ideplugin/Contents/MacOS/DebuggerUI
, 把二进制文件拖到hooper里看一下-[DBGGaugeMemoryEditor _setupTopSectionComponentViews]_block_invoke
的实现。
{:height=”100%” width=”100%”}
通过查看相应的实现,可以知道是它通过debugSession实例获取相关信息的,再结合调用栈信息,debugSession肯定就是DBGLLDBSession。同样的,通过lldb,我们可以找到DBGLLDBSession位于/Applications/Xcode.app/Contents/PlugIns/DebuggerLLDB.ideplugin/Contents/MacOS/DebuggerLLDB
,企图通过hooper看看它的实现,然而并没看出啥有用信息。只能继续尝试lldb断点。po $rdx
打印它的参数,似乎出了一串奇怪的字符串?!
1 | (lldb) b -[DBGLLDBSession processProfileDataString:] |
google大法,lldb源码查看
随便抽了一个关键字host_sys_ticks
,google了一下,发现这串字符,竟然来自lldb项目里的debugserver!先查看了一下本机的lldb版本(lldb-900.0.45),在apple open source的官网上没找到这个版本的lldb。无奈下只能去lldb官网clone了一份最新的代码,虽然不知道apple是基于哪个lldb版本开发的,但是看最新的实现总不会错~
通过查看debugserver源码,可以发现前面那串字符串是在std::string MachTask::GetProfileData(DNBProfileDataScanType scanType)
生成的,里面有各种profile信息,比如cpu,memory等。
大胆猜想一下,Xcode的内存监控正是定时通过获取debugserver的这个方法的信息来展示的!!!
另外,关于debugserver,可以看这里的介绍。简单来说,它是运行在ios上的一个可以接受lldb前端命令的『远程调试』服务器。在越狱设备上,可以通过它做很多trick,这里暂且不表。
验证
初步
扒出memory profile的代码实现如下:
1 | static void GetPurgeableAndAnonymous(task_t task, uint64_t &purgeable, |
再看看前面获取的anonymous的字节值,57823232对应的正是55.1445007MB,即debug内存面板展示的值!
这里再附上同事发现的WebKit内存计算公式,可以比较一下理解,具体参看这里,搜索一下phys_footprint
便知。
phys_footprint = (internal - alternate_accounting) + (internal_compressed - alternate_accounting_compressed) + iokit_mapped + purgeable_nonvolatile + purgeable_nonvolatile_compressed + page_table
具体
本来按理说是应该直接把上述代码拷出来具体执行进一步确认一下。但是意外的找到了一个偷懒的方法。既然Xcode是通过debugserver获取到相关信息,那么有没有办法直接和debugserver交互来获取信息呢?
继续翻看了一下lldb代码,lldb前端确实就存在相应的命令来触发debugserver执行。
通过代码可以发现std::string MachTask::GetProfileData(DNBProfileDataScanType scanType)
会在RNBRemote
接受到消息包qGetProfileData
时执行,而lldb原来可以直接通过process plugin packet send
命令来给debugserver发送包命令。
换句话说,也就是直接在Xcode终端直接执行命令验证
{:height=”100%” width=”100%”}
结合lldb脚本的使用,目测验证起来并不难。
当然,最终可能还是要直接拷出一下那段代码验证一下,这个后面有空再试试。
总结
- 虽然咋看下来全文一路顺畅,但是作为一名逆向新手,中间还是遇到了不少问题,不过收获也是很大滴~
- lldb和hopper确实很强大,深入学习一下lldb源码还是有必要的,其中还是有不少有趣的地方值得挖掘。
- 通过Xcode debug机制的原理探寻,我们可以学习它的profile实现并且自己撸一遍做一套性能监控.
参考文献
https://store.raywenderlich.com/products/advanced-apple-debugging-and-reverse-engineering