开发MemoryGraph工具的可行性探索
大家好,我是脑洞TF,目前就职于阿里巴巴。从事iOS开发多年。这是我刚开的公众号。未来我会持续不定期更新,欢迎大家关注我。一起学习,一起探讨,一起成长。
老铁,关注我不迷路,咱也双击一个666~
本篇文字有点多,大家耐心看,有些东西大家自己去实验一下,收获的会更多。 话不多说,下面进入正题~~~(由于内容需要脱敏,导致有些东西不能说的太透,原谅我~有兴趣的同学可以私信我)
一、前言: 在Xcode里关于malloc stack logging 这个工具可以看到2个选项,如下图。
二、业内方案:
以上方案思路均没有什么问题,但是实际实现中有2个问题:
1、性能问题,卡顿。
2、需要自定义mallocZone,否则会出现自身逻辑开辟的内存也会被统计上,影响真实数据。
三、那么MallocStackLogging是如何实现的? 那有没有什么办法能够利用系统的一些能力,然后达到我们拿到内存行为日志的呢?最开始的思路就是看看系统的MallocStackLogging是如何实现的。
1、我始终坚信MallocStackLogging工具是有写文件的,猜测一定是把内存行为日志以及堆栈记录下来了(虽然后面发现不是完全正确哈)。 就着这个思路,通过逆向系统的工具和一些动态库,也是几经周折终于找到了日志存放的路径。虽然找到文件的路径,但是总不能所有打的包都打开MallocStackLogging这个配置项吧,而且日志文件非常大。所以还是要想想办法看看有没有代码的方式控制是否输出日志。(日志存放在tmp目录下)
2、如何用代码控制日志开关? 通过看libmalloc的源码找到2个比较可疑的方法:
一尝试果真是控制内存行为日志的开关。一打开日志发现里面存的都是2进制。如何解析呢?
3、如何解析日志?
先看看二进制文件的规律吧,如下图:
从上图可以很规律的看出,每64位表示一个数,感觉每4个64位像是一组数。 改了下解析二进制的脚本将数据解析成四元素数据结构,这次感觉应该没啥问题了。数据如下:
但是通过数据结构发现并没有栈信息,有的只是一个stackid。有这玩意没有用啊,那如何能获取到frames。
4、如何将stackid转为frames: 结合系统源码,找到了一个可以将stackid转为frames的方式。 到了这一步感觉好像这条路可以走通了。 到目前为止: 有内存行为日志>日志可以解析>获取到栈信息 。有了以上数据,核心链路就通了。
方案工程化验证: 日志体量 :1小时 6G左右 解析耗时 :6G日志stackid转成frames耗时也大概1小时 解析后日志体量 :大约30G左右 结论: 将stackid转成frames存在手机上,磁盘会爆炸。这个方式不行,必须想办法进行离线日志解析。
5、如何离线解析日志和获取栈信息 感觉一定是有一个map存储这stackid和frames的映射关系。只要把map持久化到本地,就可以做离线解析了。 看了下相关源码,发现原来系统并不是仅仅对stackid和frames做了一个map,而是用一个树来存储栈。并且是放在内存中的。这样一来是大大地减少了内存的占用,二来是通过stackid和树之间建立一种映射关系,查找速度也非常快!简直绝了! 然后对这棵树进行序列化和反序列化,验证了下,真是一点毛病没有啊!如此离线解析日志就做到了。而且解析6G数据耗时大概7min左右。
6、数据验证: 同一份数据,通过上面提到过的 运行时解析 和 离线解析 两种解析方式进行数据对比,数据结果是一致的。(在这个过程中type_flags内存分配类型这里也有点小问题,就不展开了) 如此,基本的路已经通了,那么自动化分析内存工具是可行的。剩下的工作就是根据地址将alloc和free成对的数据过滤掉。然后符号化再做数据挖掘了。 其实在方案工程化的过程中,会遇到各种各样的问题,每走一步都比较难,这一篇就不展开介绍了。
四、方案优缺点 优点:
五、整体总结 调研过程比上述过程要坎坷,不过大概了解了memgraph的原理和instrument的原理。在逆向很多系统工具的时候,发现如果设备和Xcode或者instrument工具建立的socket通道,数据会通过通道发送到Mac上。 Memgraph的Live Allocations Only 不是拿的内存行为日志,应该是遍历的所有的内存节点,再通过stack tree获取到的每个内存节点的栈信息。
Instrument工具应该是对内存行为日志做了包装,二者的核心都是类似的。
这就是本片的全部内容,如果有什么不对的地方,欢迎各位大佬私信我。
六、写在最后 最后回答一下标题的问题,能不能自己做一个memGraph工具? 答案是能!
本篇文字有点多,大家耐心看,有些东西大家自己去实验一下,收获的会更多。 话不多说,下面进入正题~~~(由于内容需要脱敏,导致有些东西不能说的太透,原谅我~有兴趣的同学可以私信我)
一、前言: 在Xcode里关于malloc stack logging 这个工具可以看到2个选项,如下图。
二、业内方案:
|
方案 |
流程 |
优点 |
缺点 |
| hook malloc 方法 | hook 内存分配释放的方法 -> 栈回溯 -> 记录内存行为日志 -> 分配释放成对数据过滤 -> 符号化 -> 数据挖掘 |
难度较高、成本较高 |
性能较差,且覆盖 case 不全。 |
|
实现系统的 malloc logger 的勾子 |
实现 malloc_logger 方法 -> 栈回溯 -> 记录内存行为日志 -> 分配释放成对数据过滤 -> 符号化 -> 数据挖掘 |
难度较高、覆盖 case 全 |
性能较差 |
| 遍历内存所有 live 的节点 |
遍历所有节点 -> 符号化 |
难度较低 |
性能高、无法抓堆栈。 |
三、那么MallocStackLogging是如何实现的? 那有没有什么办法能够利用系统的一些能力,然后达到我们拿到内存行为日志的呢?最开始的思路就是看看系统的MallocStackLogging是如何实现的。
1、我始终坚信MallocStackLogging工具是有写文件的,猜测一定是把内存行为日志以及堆栈记录下来了(虽然后面发现不是完全正确哈)。 就着这个思路,通过逆向系统的工具和一些动态库,也是几经周折终于找到了日志存放的路径。虽然找到文件的路径,但是总不能所有打的包都打开MallocStackLogging这个配置项吧,而且日志文件非常大。所以还是要想想办法看看有没有代码的方式控制是否输出日志。(日志存放在tmp目录下)
2、如何用代码控制日志开关? 通过看libmalloc的源码找到2个比较可疑的方法:
extern boolean_t turn_on_stack_logging(stack_logging_mode_type mode);extern void turn_off_stack_logging(void);
一尝试果真是控制内存行为日志的开关。一打开日志发现里面存的都是2进制。如何解析呢?
3、如何解析日志?
从上图可以很规律的看出,每64位表示一个数,感觉每4个64位像是一组数。 改了下解析二进制的脚本将数据解析成四元素数据结构,这次感觉应该没啥问题了。数据如下:
alloc size:296 stackid:0x00000000035944 add:0x08829C30free size:0 stackid:0x0000000001B793 add:0x08821270free size:0 stackid:0x00000000035D92 add:0x08821270alloc size:296 stackid:0x000000000157A2 add:0x08821270alloc size:24 stackid:0x00000000030A4C add:0x06DF6F88alloc size:296 stackid:0x00000000035944 add:0x08829080free size:0 stackid:0x0000000001B793 add:0x08821270free size:0 stackid:0x00000000035D92 add:0x08821270alloc size:28 stackid:0x000000000277EE add:0x08820AF0
但是通过数据结构发现并没有栈信息,有的只是一个stackid。有这玩意没有用啊,那如何能获取到frames。
4、如何将stackid转为frames: 结合系统源码,找到了一个可以将stackid转为frames的方式。 到了这一步感觉好像这条路可以走通了。 到目前为止: 有内存行为日志>日志可以解析>获取到栈信息 。有了以上数据,核心链路就通了。
方案工程化验证: 日志体量 :1小时 6G左右 解析耗时 :6G日志stackid转成frames耗时也大概1小时 解析后日志体量 :大约30G左右 结论: 将stackid转成frames存在手机上,磁盘会爆炸。这个方式不行,必须想办法进行离线日志解析。
5、如何离线解析日志和获取栈信息 感觉一定是有一个map存储这stackid和frames的映射关系。只要把map持久化到本地,就可以做离线解析了。 看了下相关源码,发现原来系统并不是仅仅对stackid和frames做了一个map,而是用一个树来存储栈。并且是放在内存中的。这样一来是大大地减少了内存的占用,二来是通过stackid和树之间建立一种映射关系,查找速度也非常快!简直绝了! 然后对这棵树进行序列化和反序列化,验证了下,真是一点毛病没有啊!如此离线解析日志就做到了。而且解析6G数据耗时大概7min左右。
6、数据验证: 同一份数据,通过上面提到过的 运行时解析 和 离线解析 两种解析方式进行数据对比,数据结果是一致的。(在这个过程中type_flags内存分配类型这里也有点小问题,就不展开了) 如此,基本的路已经通了,那么自动化分析内存工具是可行的。剩下的工作就是根据地址将alloc和free成对的数据过滤掉。然后符号化再做数据挖掘了。 其实在方案工程化的过程中,会遇到各种各样的问题,每走一步都比较难,这一篇就不展开介绍了。
四、方案优缺点 优点:
- 实现成本低
- 直接使用系统原始日志,自定义工具空间大,想怎么处理聚类都可以。
- 性能特别好,虽然打印了很多的日志,但是还是特别丝滑,毫无卡顿。
- 无需像MemGraph那样连接Mac设备,任何时间,任何环境都可以打开开关记录内存行为。
- 长时间记录内存行为日志,占用磁盘空间(不过我觉得这不叫事,这些外力都可以解决~)
五、整体总结 调研过程比上述过程要坎坷,不过大概了解了memgraph的原理和instrument的原理。在逆向很多系统工具的时候,发现如果设备和Xcode或者instrument工具建立的socket通道,数据会通过通道发送到Mac上。 Memgraph的Live Allocations Only 不是拿的内存行为日志,应该是遍历的所有的内存节点,再通过stack tree获取到的每个内存节点的栈信息。
Instrument工具应该是对内存行为日志做了包装,二者的核心都是类似的。
该方案
的应用
场景和想象空间还是非常大的,想象力往往是前进的源头和动力。
而且方案的灵活度也比较高,毕竟拿到的都是原始的日志和数据啥的,想怎么搞怎么搞,还不是任由咱们摆布,哈哈哈哈哈~
这就是本片的全部内容,如果有什么不对的地方,欢迎各位大佬私信我。
六、写在最后 最后回答一下标题的问题,能不能自己做一个memGraph工具? 答案是能!
那还有没有性能更好的方案 ?
答案是有。关注我,后面我会继续分享~
(下一篇打算分享一点逆向相关的内容。
先把下一篇的方向定好,然后写在文章里,这样能倒逼自己,一定要坚持~~)