自己搞一个 MemoryGraph 工具行不行?
共 3577字,需浏览 8分钟
·
2021-11-28 09:40
一、前言:
二、业内方案:
方案 | 流程 | 优点 | 缺点 |
hook malloc方法 | hook 内存分配释放的方法 ->栈回溯 ->记录内存行为日志 ->分配释放成对数据过滤 ->符号化 ->数据挖掘 | 难度较高、成本较高 | 性能较差,且覆盖case不全。 |
实现系统的malloc logger的勾子 | 实现malloc_logger方法 ->栈回溯 ->记录内存行为日志 ->分配释放成对数据过滤 ->符号化 ->数据挖掘 | 难度较高、覆盖case全 | 性能较差 |
遍历内存所有live的节点 | 遍历所有节点 ->符号化 | 难度较低 | 性能高、无法抓堆栈。 |
以上方案思路均没有什么问题,但是实际实现中有2个问题:
1、性能问题,卡顿。
2、需要自定义 mallocZone,否则会出现自身逻辑开辟的内存也会被统计上,影响真实数据。
三、那么 Malloc Stack Logging 是如何实现的?
那有没有什么办法能够利用系统的一些能力,然后达到我们拿到内存行为日志的呢?最开始的思路就是看看系统的 Malloc Stack Logging 是如何实现的。
就着这个思路,通过逆向系统的工具和一些动态库,也是几经周折终于找到了日志存放的路径。虽然找到文件的路径,但是总不能所有打的包都打开 Malloc Stack Logging 这个配置项吧,而且日志文件非常大。所以还是要想想办法看看有没有代码的方式控制是否输出日志。(日志存放在 tmp 目录下)
2、如何用代码控制日志开关?
extern boolean_t turn_on_stack_logging(stack_logging_mode_type mode);
extern void turn_off_stack_logging(void);
3、如何解析日志?
改了下解析二进制的脚本将数据解析成四元素数据结构,这次感觉应该没啥问题了。数据如下:
alloc size:296 stackid:0x00000000035944 add:0x08829C30
free size:0 stackid:0x0000000001B793 add:0x08821270
free size:0 stackid:0x00000000035D92 add:0x08821270
alloc size:296 stackid:0x000000000157A2 add:0x08821270
alloc size:24 stackid:0x00000000030A4C add:0x06DF6F88
alloc size:296 stackid:0x00000000035944 add:0x08829080
free size:0 stackid:0x0000000001B793 add:0x08821270
free size:0 stackid:0x00000000035D92 add:0x08821270
alloc size:28 stackid:0x000000000277EE add:0x08820AF0
4、如何将 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 设备,任何时间,任何环境都可以打开开关记录内存行为。
缺点:
长时间记录内存行为日志,占用磁盘空间(不过我觉得这不叫事,这些外力都可以解决~)
五、整体总结
该方案的应用场景和想象空间还是非常大的,想象力往往是前进的源头和动力。而且方案的灵活度也比较高,毕竟拿到的都是原始的日志和数据啥的,想怎么搞怎么搞,还不是任由咱们摆布,哈哈哈哈哈~
最后回答一下标题的问题,能不能自己做一个 MemoryGraph 工具?
答案是能!
那还有没有性能更好的方案?
答案是有。
关注我,后面我会继续分享~
(下一篇打算分享一点逆向相关的内容。先把下一篇的方向定好,然后写在文章里,这样能倒逼自己,一定要坚持~~)