自己搞一個 MemoryGraph 工具行不行?

一、前言:

二、業(yè)內(nèi)方案:
方案 | 流程 | 優(yōu)點 | 缺點 |
hook malloc方法 | hook 內(nèi)存分配釋放的方法 ->?;厮?/p> ->記錄內(nèi)存行為日志 ->分配釋放成對數(shù)據(jù)過濾 ->符號化 ->數(shù)據(jù)挖掘 | 難度較高、成本較高 | 性能較差,且覆蓋case不全。 |
實現(xiàn)系統(tǒng)的malloc logger的勾子 | 實現(xiàn)malloc_logger方法 ->?;厮?/p> ->記錄內(nèi)存行為日志 ->分配釋放成對數(shù)據(jù)過濾 ->符號化 ->數(shù)據(jù)挖掘 | 難度較高、覆蓋case全 | 性能較差 |
遍歷內(nèi)存所有live的節(jié)點 | 遍歷所有節(jié)點 ->符號化 | 難度較低 | 性能高、無法抓堆棧。 |
以上方案思路均沒有什么問題,但是實際實現(xiàn)中有2個問題:
1、性能問題,卡頓。
2、需要自定義 mallocZone,否則會出現(xiàn)自身邏輯開辟的內(nèi)存也會被統(tǒng)計上,影響真實數(shù)據(jù)。
三、那么 Malloc Stack Logging 是如何實現(xiàn)的?
那有沒有什么辦法能夠利用系統(tǒng)的一些能力,然后達到我們拿到內(nèi)存行為日志的呢?最開始的思路就是看看系統(tǒng)的 Malloc Stack Logging 是如何實現(xiàn)的。
就著這個思路,通過逆向系統(tǒng)的工具和一些動態(tài)庫,也是幾經(jīng)周折終于找到了日志存放的路徑。雖然找到文件的路徑,但是總不能所有打的包都打開 Malloc Stack Logging 這個配置項吧,而且日志文件非常大。所以還是要想想辦法看看有沒有代碼的方式控制是否輸出日志。(日志存放在 tmp 目錄下)
2、如何用代碼控制日志開關(guān)?
extern boolean_t turn_on_stack_logging(stack_logging_mode_type mode);extern void turn_off_stack_logging(void);
3、如何解析日志?

改了下解析二進制的腳本將數(shù)據(jù)解析成四元素數(shù)據(jù)結(jié)構(gòu),這次感覺應(yīng)該沒啥問題了。數(shù)據(jù)如下:
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
4、如何將 stackid 轉(zhuǎn)為 frames:
方案工程化驗證:
日志體量:1 小時 6G 左右
解析耗時:6G 日志 stackid 轉(zhuǎn)成 frames 耗時也大概1小時
解析后日志體量:大約 30G 左右
結(jié)論:將 stackid 轉(zhuǎn)成 frames 存在手機上,磁盤會爆炸。這個方式不行,必須想辦法進行離線日志解析。
5、如何離線解析日志和獲取棧信息
感覺一定是有一個 map 存儲這個 stackid 和 frames 的映射關(guān)系。只要把 map 持久化到本地,就可以做離線解析了。
看了下相關(guān)源碼,發(fā)現(xiàn)原來系統(tǒng)并不是僅僅對 stackid 和 frames 做了一個 map,而是用一個樹來存儲棧。并且是放在內(nèi)存中的。這樣一來是大大地減少了內(nèi)存的占用,二來是通過 stackid 和樹之間建立一種映射關(guān)系,查找速度也非??欤『喼苯^了!
然后對這棵樹進行序列化和反序列化,驗證了下,真是一點毛病沒有??!如此離線解析日志就做到了。而且解析 6G 數(shù)據(jù)耗時大概 7min 左右。
6、數(shù)據(jù)驗證:
同一份數(shù)據(jù),通過上面提到過的運行時解析和離線解析兩種解析方式進行數(shù)據(jù)對比,數(shù)據(jù)結(jié)果是一致的。(在這個過程中 type_flags 內(nèi)存分配類型這里也有點小問題,就不展開了)
如此,基本的路已經(jīng)通了,那么自動化分析內(nèi)存工具是可行的。剩下的工作就是根據(jù)地址將 alloc 和 free 成對的數(shù)據(jù)過濾掉。然后符號化再做數(shù)據(jù)挖掘了。
其實在方案工程化的過程中,會遇到各種各樣的問題,每走一步都比較難,這一篇就不展開介紹了。
四、方案優(yōu)缺點
優(yōu)點:
實現(xiàn)成本低
直接使用系統(tǒng)原始日志,自定義工具空間大,想怎么處理聚類都可以。
性能特別好,雖然打印了很多的日志,但是還是特別絲滑,毫無卡頓。
無需像 MemGraph 那樣連接 Mac 設(shè)備,任何時間,任何環(huán)境都可以打開開關(guān)記錄內(nèi)存行為。
缺點:
長時間記錄內(nèi)存行為日志,占用磁盤空間(不過我覺得這不叫事,這些外力都可以解決~)
五、整體總結(jié)
該方案的應(yīng)用場景和想象空間還是非常大的,想象力往往是前進的源頭和動力。而且方案的靈活度也比較高,畢竟拿到的都是原始的日志和數(shù)據(jù)啥的,想怎么搞怎么搞,還不是任由咱們擺布,哈哈哈哈哈~
最后回答一下標(biāo)題的問題,能不能自己做一個 MemoryGraph 工具?
答案是能!
那還有沒有性能更好的方案?
答案是有。
關(guān)注我,后面我會繼續(xù)分享~
(下一篇打算分享一點逆向相關(guān)的內(nèi)容。先把下一篇的方向定好,然后寫在文章里,這樣能倒逼自己,一定要堅持~~)
