分析字節(jié)跳動解決OOM的在線Memory Graph技術(shù)實現(xiàn)

之前看到字節(jié)團隊分享的 “iOS性能優(yōu)化實踐:頭條抖音如何實現(xiàn)OOM崩潰率下降50%+”這篇文章钧敞,對其實現(xiàn)比較感興趣驳阎,但是沒有開源栋操,所以覺得自己花時間探索一下具體實現(xiàn)柿冲。
什么是OOM,為什么會發(fā)生OOM以及其原因分析,大家去看原文就好了吧凉,本文主要分析APP內(nèi)存使用到達閾值后隧出,如何采集Memory Graph信息并分析。

什么是Memory Graph阀捅?

225849-81486a8ba4f85ccd.png

這個時候就進入了斷點模式胀瞪,可以查看issue面板,注意選擇右邊Runtime:

225849-5d7c2230bc3d4755.png

有很多嘆號說明就有問題了饲鄙∩屠看內(nèi)存中object的名字,有一條是Closure captures leaked傍妒。展開后點擊就可以看到這個issue對應的內(nèi)存圖形展示在中間的面板中。

225849-d6a25ca375131d85.png

點擊紫色的嘆號會出現(xiàn)Xcode分析出來的內(nèi)存引用圖形:

225849-2c8ba810774336fb.png

如果我們在線上App中也能采集到Memory Graph的相關(guān)信息摸柄,那么就可以在監(jiān)控平臺還原用戶當時內(nèi)存信息颤练,幫助開發(fā)人員分析和解決問題。

如何內(nèi)存快照采集

  • 虛擬內(nèi)存節(jié)點信息采集
  • OC/Swift/C++ 所有實例及其類名
  • 內(nèi)存節(jié)點之間的引用關(guān)系驱负,實例中成員變量類型和它引用其他內(nèi)存的關(guān)系

內(nèi)存節(jié)點的獲取

通過 mach 內(nèi)核的vm_region_recurse/vm_region_recurse64函數(shù)我們可以遍歷進程內(nèi)所有VM Region嗦玖,并通過vm_region_submap_info_64結(jié)構(gòu)體獲取以下信息:

  • 虛擬地址空間中的地址和大小。
  • Dirty 和 Swapped 內(nèi)存頁數(shù)跃脊,表示該VM Region的真實物理內(nèi)存使用宇挫。
  • 是否可交換,Text 段酪术、共享 mmap 等只讀或隨時可以被交換出去的內(nèi)存器瘪,無需關(guān)注。
  • user_tag绘雁,用戶標簽橡疼,用于提供該VM Region的用途的更準確信息。

獲取所有內(nèi)存節(jié)點信息

kern_return_t krc = KERN_SUCCESS;
vm_address_t address = 0;
vm_size_t size = 0;
uint32_t depth = 1;
pid_t pid = getpid();
char buf[PATH_MAX];
while (1) {
    struct vm_region_submap_info_64 info;
    mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
    krc = vm_region_recurse_64(mach_task_self(), &address, &size, &depth, (vm_region_info_64_t)&info, &count);
    if (krc == KERN_INVALID_ADDRESS){
        break;
    }
    if (info.is_submap){
        depth++;
    } else {
        //do stuff
        proc_regionfilename(pid, address, buf, sizeof(buf));
        printf("Found VM Region: %08x to %08x (depth=%d) user_tag:%s name:%s\n", (uint32_t)address, (uint32_t)(address+size), depth, [visualMemoryTypeString(info.user_tag) cStringUsingEncoding:NSUTF8StringEncoding], buf);
        address += size;
    }
}

輸出如下:

Found VM Region: 04c68000 to 04c80000 (depth=0) user_tag:(null) name:/private/var/containers/Bundle/Application/70EFA90E-D8EE-4FE9-9D5E-C708F6DB2FE6/OnlineMemoryGraphDemo.app/OnlineMemoryGraphDemo
Found VM Region: 04c80000 to 04c88000 (depth=0) user_tag:(null) name:/private/var/containers/Bundle/Application/70EFA90E-D8EE-4FE9-9D5E-C708F6DB2FE6/OnlineMemoryGraphDemo.app/OnlineMemoryGraphDemo
Found VM Region: 04c88000 to 04ca8000 (depth=0) user_tag:(null) name:/private/var/containers/Bundle/Application/70EFA90E-D8EE-4FE9-9D5E-C708F6DB2FE6/OnlineMemoryGraphDemo.app/OnlineMemoryGraphDemo
Found VM Region: 04ca8000 to 04cac000 (depth=0) user_tag:(null) name:/usr/lib/dyld
Found VM Region: 04cac000 to 04cb8000 (depth=0) user_tag:(null) name:/usr/lib/dyld
Found VM Region: 04cbc000 to 04d18000 (depth=0) user_tag:(null) name:/usr/lib/dyld
Found VM Region: 04d18000 to 04d20000 (depth=0) user_tag:(null) name:/usr/lib/dyld
Found VM Region: 04d24000 to 04d58000 (depth=0) user_tag:(null) name:/usr/lib/dyld
Found VM Region: 04d58000 to 04d94000 (depth=0) user_tag:(null) name:/usr/lib/dyld
Found VM Region: 04d98000 to 04da4000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libBacktraceRecording.dylib
Found VM Region: 04da4000 to 04da8000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libBacktraceRecording.dylib
Found VM Region: 04da8000 to 04dac000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libBacktraceRecording.dylib
Found VM Region: 04dac000 to 04db0000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libBacktraceRecording.dylib
Found VM Region: 04db0000 to 04db4000 (depth=0) user_tag:DYLIB name:/Developer/usr/lib/libMainThreadChecker.dylib
Found VM Region: 04db4000 to 04df8000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libMainThreadChecker.dylib
Found VM Region: 04df8000 to 04dfc000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libMainThreadChecker.dylib
Found VM Region: 04dfc000 to 04e00000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libMainThreadChecker.dylib
Found VM Region: 04e00000 to 04f00000 (depth=0) user_tag:DYLIB name:/Developer/usr/lib/libMainThreadChecker.dylib
Found VM Region: 04f00000 to 04f04000 (depth=0) user_tag:(null) name:/Developer/usr/lib/libMainThreadChecker.dylib
Found VM Region: 04f04000 to 04f08000 (depth=0) user_tag:DYLIB name:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
Found VM Region: 04f08000 to 04f40000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
Found VM Region: 04f40000 to 04f48000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
Found VM Region: 04f48000 to 04f5c000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
Found VM Region: 04f5c000 to 04f70000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
Found VM Region: 04f70000 to 04f74000 (depth=0) user_tag:DYLIB name:/usr/lib/system/introspection/libdispatch.dylib
Found VM Region: 04f74000 to 04fbc000 (depth=0) user_tag:(null) name:/usr/lib/system/introspection/libdispatch.dylib
Found VM Region: 04fd4000 to 04fd8000 (depth=0) user_tag:(null) name:/usr/lib/system/introspection/libdispatch.dylib
Found VM Region: 04fd8000 to 04fdc000 (depth=0) user_tag:(null) name:/usr/lib/system/introspection/libdispatch.dylib
Found VM Region: 04fdc000 to 05008000 (depth=0) user_tag:(null) name:/usr/lib/system/introspection/libdispatch.dylib
Found VM Region: 05008000 to 0500c000 (depth=0) user_tag:DYLIB name:/Developer/Library/PrivateFrameworks/DebugHierarchyFoundation.framework/DebugHierarchyFoundation
Found VM Region: 0500c000 to 05030000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DebugHierarchyFoundation.framework/DebugHierarchyFoundation
Found VM Region: 05030000 to 05034000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DebugHierarchyFoundation.framework/DebugHierarchyFoundation
Found VM Region: 05034000 to 0503c000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DebugHierarchyFoundation.framework/DebugHierarchyFoundation
Found VM Region: 0503c000 to 05050000 (depth=0) user_tag:(null) name:/Developer/Library/PrivateFrameworks/DebugHierarchyFoundation.framework/DebugHierarchyFoundation
Found VM Region: 05050000 to 05054000 (depth=0) user_tag:DYLIB name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05054000 to 0505c000 (depth=0) user_tag:OS_ALLOC_ONCE name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 0505c000 to 05060000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05060000 to 05064000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05064000 to 05068000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05068000 to 05070000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05070000 to 05074000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05074000 to 05078000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05080000 to 05084000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05088000 to 0508c000 (depth=0) user_tag:MALLOC name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 05090000 to 050d0000 (depth=0) user_tag:ANALYSIS_TOOL name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 050d8000 to 050dc000 (depth=0) user_tag:MALLOC_LARGE name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 050e0000 to 050e8000 (depth=0) user_tag:MALLOC_LARGE name:/private/var/preferences/Logging/.plist-cache.8czmnDXb
Found VM Region: 050f4000 to 050f8000 (depth=0) user_tag:ANALYSIS_TOOL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 050f8000 to 05100000 (depth=0) user_tag:ANALYSIS_TOOL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05100000 to 05200000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05200000 to 05300000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05300000 to 05340000 (depth=0) user_tag:GENEALOGY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05340000 to 05348000 (depth=0) user_tag:ANALYSIS_TOOL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05348000 to 0534c000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 0534c000 to 05350000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05350000 to 05354000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05354000 to 0535c000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 0535c000 to 05360000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05360000 to 053e0000 (depth=0) user_tag:MALLOC_LARGE name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 053e0000 to 053e4000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 053ec000 to 053f0000 (depth=0) user_tag:MALLOC name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 053f0000 to 053f4000 (depth=0) user_tag:LAYERKIT name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 053f4000 to 053f8000 (depth=0) user_tag:(null) name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 053f8000 to 05400000 (depth=0) user_tag:MALLOC_LARGE name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05400000 to 05500000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05500000 to 05600000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05600000 to 05700000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05700000 to 05800000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 05800000 to 06000000 (depth=0) user_tag:MALLOC_SMALL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 06000000 to 06800000 (depth=0) user_tag:MALLOC_SMALL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 06800000 to 07000000 (depth=0) user_tag:MALLOC_SMALL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 07000000 to 07800000 (depth=0) user_tag:MALLOC_SMALL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 07800000 to 08000000 (depth=0) user_tag:MALLOC_SMALL name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08000000 to 08100000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08100000 to 08200000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08200000 to 08300000 (depth=0) user_tag:MALLOC_TINY name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08300000 to 0830c000 (depth=0) user_tag:(null) name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 0830c000 to 08310000 (depth=0) user_tag:(null) name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08310000 to 08314000 (depth=0) user_tag:DYLIB name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08314000 to 0831c000 (depth=0) user_tag:MALLOC_LARGE name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 0831c000 to 08320000 (depth=0) user_tag:FOUNDATION name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 08320000 to 0832c000 (depth=0) user_tag:(null) name:/usr/lib/libobjc-trampolines.dylib
Found VM Region: 0832c000 to 08334000 (depth=0) user_tag:(null) name:/usr/share/icu/icudt66l.dat
Found VM Region: 08334000 to 08340000 (depth=0) user_tag:MALLOC_LARGE name:/usr/share/icu/icudt66l.dat
Found VM Region: 08340000 to 08344000 (depth=0) user_tag:(null) name:/usr/share/icu/icudt66l.dat
Found VM Region: 08344000 to 0834c000 (depth=0) user_tag:MALLOC_LARGE name:/usr/share/icu/icudt66l.dat
Found VM Region: 0834c000 to 08350000 (depth=0) user_tag:LAYERKIT name:/usr/share/icu/icudt66l.dat
Found VM Region: 08350000 to 08360000 (depth=0) user_tag:MALLOC_LARGE name:/usr/share/icu/icudt66l.dat
Found VM Region: 08400000 to 08500000 (depth=0) user_tag:MALLOC_TINY name:/usr/share/icu/icudt66l.dat
Found VM Region: 08800000 to 09000000 (depth=0) user_tag:MALLOC_SMALL name:/usr/share/icu/icudt66l.dat
Found VM Region: 09000000 to 09800000 (depth=0) user_tag:MALLOC_SMALL name:/usr/share/icu/icudt66l.dat
Found VM Region: 09800000 to 0b750000 (depth=0) user_tag:(null) name:/usr/share/icu/icudt66l.dat
Found VM Region: 0b750000 to 0bf38000 (depth=0) user_tag:(null) name:/System/Library/Fonts/CoreUI/SFUI.ttf
Found VM Region: 0bf38000 to 108cc000 (depth=0) user_tag:(null) name:/System/Library/Fonts/LanguageSupport/PingFang.ttc
Found VM Region: 6b098000 to 6b09c000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b09c000 to 6b198000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b198000 to 6b19c000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b19c000 to 6b224000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b3c8000 to 6b3cc000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b3cc000 to 6b454000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b454000 to 6b458000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b458000 to 6b4e0000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b56c000 to 6b570000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 6b570000 to 6b5f8000 (depth=0) user_tag:STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 8f018000 to 8f388000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 8f388000 to 8f38c000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 8f38c000 to 90000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 90000000 to 911bc000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 911bc000 to 911c0000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 911c0000 to 92000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 92000000 to 93130000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 93130000 to 93134000 (depth=0) user_tag:SHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 93134000 to 94000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 94000000 to a0000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a0000000 to a2ca4000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2ca4000 to a2ca8000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2ca8000 to a2cac000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2cac000 to a2cc0000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2cc0000 to a2cc4000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2cc4000 to a2cc8000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2ccc000 to a2da8000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2da8000 to a2dac000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: a2dac000 to b0000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: b0000000 to c0000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: c0000000 to d0000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: d0000000 to db010000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: dd010000 to de000000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: de000000 to e0000000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: e0000000 to e2000000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: e2000000 to e4000000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: e4000000 to e6000000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: e6000000 to e8000000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: e8000000 to e9da8000 (depth=0) user_tag:UNSHARED_PMAP name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: ebda8000 to f0000000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: f0000000 to f7150000 (depth=1) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 80000000 to a0000000 (depth=0) user_tag:MALLOC_NANO name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: c0000000 to 00000000 (depth=0) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
Found VM Region: 00000000 to 00000000 (depth=0) user_tag:(null) name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64

內(nèi)存節(jié)點大致分為這幾類:

  • App的二進制文件在內(nèi)存的映射(如OnlineMemoryGraphDemo)
  • 動態(tài)庫在內(nèi)存中的映射(如libBacktraceRecording.dylib庐舟,libdispatch.dylib等)
  • 系統(tǒng)或自定義字體等資源(SFUI.ttf欣除, PingFang.ttc)
  • 棧區(qū)(STACK name:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64)
  • Malloc Zone, Malloc Zone分為Nano和Scalable, Nano分配16B~256B(16B的整數(shù)倍)的內(nèi)存, Scalable分配256B以上的大內(nèi)存

Malloc內(nèi)存信息的獲取

Malloc內(nèi)就是我們平時開發(fā)接觸最多的了,我們iOS開發(fā)中語言挪略,C/C++历帚, OC, Swift等創(chuàng)建的內(nèi)存和對象都是在這里分配杠娱。

我們可以通過malloc_get_all_zones獲取libmalloc內(nèi)部所有的zone挽牢,并遍歷每個zone中管理的內(nèi)存節(jié)點,獲取 libmalloc 管理的存活的所有內(nèi)存節(jié)點的指針和大小墨辛。

獲取所有Malloc Zone:

vm_address_t *zones = NULL;
unsigned int zoneCount = 0;
kern_return_t result = malloc_get_all_zones(TASK_NULL, memory_reader_callback, &zones, &zoneCount);
if (result == KERN_SUCCESS) {    
    for (unsigned int i = 0; i < zoneCount; i++) {        
        malloc_zone_t *zone = (malloc_zone_t *)zones[i];               
        printf("Found zone name:%s\n", zone->zone_name);
    }
}

輸出如下:

Found zone name:DefaultMallocZone   // Nano Zone
Found zone name:MallocHelperZone.   // Scalable Zone
Found zone name:QuartzCore

獲取Zone內(nèi)所有分配的節(jié)點

malloc_introspection_t *introspection = zone->introspect;

if (!introspection) {
    continue;
}

void (*lock_zone)(malloc_zone_t *zone)   = introspection->force_lock;
void (*unlock_zone)(malloc_zone_t *zone) = introspection->force_unlock;

// Callback has to unlock the zone so we freely allocate memory inside the given block
malloc_object_enumeration_block_t callback = ^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
    unlock_zone(zone);
    block(object, actualClass);
    lock_zone(zone);
};

BOOL lockZoneValid = mallocPointerIsReadable((void *)lock_zone);
BOOL unlockZoneValid = mallocPointerIsReadable((void *)unlock_zone);

// There is little documentation on when and why
// any of these function pointers might be NULL
// or garbage, so we resort to checking for NULL
// and whether the pointer is readable
if (introspection->enumerator && lockZoneValid && unlockZoneValid) {
    lock_zone(zone);
    introspection->enumerator(TASK_NULL, (void *)&callback, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, memory_reader_callback, &vm_range_recorder_callback);
    unlock_zone(zone);
}

判斷內(nèi)存地址中是什么

這里遇到一個問題卓研,遍歷Malloc Zone分配的內(nèi)存節(jié)點,只會給一個地址和大小,怎么判斷這是一個OC/C++/Swift對象還是只是一個Malloc出來的Buffer呢奏赘?
判斷是OC/Swift對象
判斷是C++對象
一塊普通Buffer
如何判斷是不是一個OC/Swift對象

Swift在內(nèi)存布局上兼容了Objective-C寥闪,也有isa指針,objc相關(guān)方法可以作用于兩種語言的對象上磨淌。只要保證 isa 指針合法疲憋,對象實例大小滿足條件即可認為正確。

獲取所有OC/SwiftClass類型

CFMutableSetRef registeredClasses;

unsigned int updateRegisteredClasses() {
    if (!registeredClasses) {
        registeredClasses = CFSetCreateMutable(NULL, 0, NULL);
    } else {
        CFSetRemoveAllValues(registeredClasses);
    }
    unsigned int count = 0;
    Class *classes = objc_copyClassList(&count);
    for (unsigned int i = 0; i < count; i++) {
        CFSetAddValue(registeredClasses, (__bridge const void *)(classes[i]));
    }
    free(classes);
    return count;
}

判斷isa是否合法

typedef struct {
    Class isa;
} malloc_maybe_object_t;

void vm_range_recorder_callback(task_t task, void *context, unsigned type, vm_range_t *ranges, unsigned rangeCount) {
    if (!context) {
        return;
    }
    
    for (unsigned int i = 0; i < rangeCount; i++) {
        vm_range_t range = ranges[I];
        malloc_maybe_object_t *tryObject = (malloc_maybe_object_t *)range.address;
        Class tryClass = NULL;
#ifdef __arm64__
        // See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
        extern uint64_t objc_debug_isa_class_mask WEAK_IMPORT_ATTRIBUTE;
        tryClass = (__bridge Class)((void *)((uint64_t)tryObject->isa & objc_debug_isa_class_mask));
#else
        tryClass = tryObject->isa;
#endif
        // 1. 判斷是否為OC/SwiftObject
        if (CFSetContainsValue(registeredClasses, (__bridge const void *)(tryClass))) {
            (*(malloc_object_enumeration_block_t __unsafe_unretained *)context)((__bridge id)tryObject, tryClass);
        } 
        // 2. 判斷是否是一個保護type_info的C++對象
        else if ([CPPObjectUtil cppTypeInfoName:(void *)range.address] != NULL) {
            NSLog(@"Find a Cpp Object:%s!", [CPPObjectUtil cppTypeInfoName:(void *)range.address]);
        }
    }
}

如何判斷是不是一個C++對象

C++對象根據(jù)是否包含虛表可以分成兩類梁只。對于不包含虛表的對象缚柳,因為缺乏運行時數(shù)據(jù),無法進行處理搪锣。對于對于包含虛表的對象秋忙,在調(diào)研 mach-o 和 C++的 ABI 文檔后,可以通過 std::type_info 和以下幾個 section 的信息獲取對應的類型信息构舟。

C++實例以及 vtable灰追,type_info的引用關(guān)系示意圖.jpeg

分析MachO文件,我們發(fā)現(xiàn)在Dynamic Loader Info段中狗超,發(fā)現(xiàn)了C++Type_Info信息

獲取App二進制加載到內(nèi)存中起始地址弹澎,_dyld_register_func_for_add_image方法當App的二進制或者動態(tài)庫等MachO格式的文件映射到內(nèi)存后,啟動App時的回調(diào)努咐,我們可以通過這個拿到App執(zhí)行二進制的起始地址苦蒿,從而拿到段中的C++類型信息。

獲取App執(zhí)行二進制的起始地址

/*
 * The following functions allow you to install callbacks which will be called   
 * by dyld whenever an image is loaded or unloaded.  During a call to _dyld_register_func_for_add_image()
 * the callback func is called for every existing image.  Later, it is called as each new image
 * is loaded and bound (but initializers not yet run).  The callback registered with
 * _dyld_register_func_for_remove_image() is called after any terminators in an image are run
 * and before the image is un-memory-mapped.
 */
extern void _dyld_register_func_for_add_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide))    __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0);

獲取所有C++type_info

typedef std::vector<struct segment_command_64 const *>    Segment64Vector;
typedef std::set<uint64_t *>    CxxTypeInfoSet;

static Segment64Vector *segments_64 = NULL;
static CxxTypeInfoSet *cxxTypeInfoSet = NULL;

// 記錄Data Segment中__const段的有效最大最小地址渗稍,合法的C++ type_info地址不會超出這里
uint64_t dataConstMinAddress = NULL;
uint64_t dataConstMaxAddress = NULL;

static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide)
{
    // 這里只分析App的二進制
    if (mhp->filetype != MH_EXECUTE) {
        return;
    }
        
    segments_64 = new Segment64Vector();
    cxxTypeInfoSet = new CxxTypeInfoSet();
    size_t header_size = sizeof(struct mach_header_64);
    uint64_t *load_comandPtr = (uint64_t *)((unsigned char *)mhp + header_size);
    uint64_t address = (uint64_t)((uint64_t *)mhp);
    uint32_t ptrSize = sizeof(uint64_t);
    for (int i=0; i<mhp->ncmds; i++) {
        struct load_command *load_command = (struct load_command *)load_comandPtr;
        segments_64->push_back((struct segment_command_64 const *)load_command);
        NSString *cmdType = loadCommandMap[@(load_command->cmd)];
        NSLog(@"dyld_callback load_command cmd:%@", cmdType);
        // 分析 Data Segment中__const段佩迟,獲取有效最大最小地址
        if (load_command->cmd == LC_SEGMENT_64) {
            struct segment_command_64 *segment_64 = (struct segment_command_64 *)load_command;
            if (strcmp(segment_64->segname, "__DATA") == 0) {
                const struct section_64 *sec = (struct section_64 *)(segment_64 + 1);
                for (int j=0; j<segment_64->nsects; j++) {
                    if (strcmp(sec[j].sectname, "__const") == 0) {
                        dataConstMinAddress = (((uint64_t)(uint64_t *)mhp) + sec[j].offset);
                        dataConstMaxAddress = (((uint64_t)(uint64_t *)mhp) + sec[j].offset + sec[j].size);
                    }
                }
            }

        } 
        // 分析動態(tài)鏈接段的信息,獲取App內(nèi)C++ type_info的地址
        else if (load_command->cmd == LC_DYLD_INFO ||
                   load_command->cmd == LC_DYLD_INFO_ONLY) {
            struct dyld_info_command *dyldCommand = (struct dyld_info_command *)load_command;
            uint8_t *bytePtr = (uint8_t *)((uint8_t *)mhp + dyldCommand->bind_off); // Dynamic Loader Info Bind部分的起始地址
            uint64_t dyldMaxAddress = (((uint64_t)(uint64_t *)mhp) + dyldCommand->bind_off + dyldCommand->bind_size);
            uint64_t doBindLocation = *((uint64_t *)bytePtr);
            
            int32_t libOrdinal = 0;
            uint32_t type = 0;
            int64_t addend = 0;
            NSString * symbolName = nil;
            uint32_t symbolFlags = 0;
            BOOL isDone = NO;
            while (((uint64_t)(uint64_t *)bytePtr) < dyldMaxAddress) {
                uint8_t byte = read_int8(&bytePtr);
                uint8_t opcode = byte & BIND_OPCODE_MASK;
                uint8_t immediate = byte & BIND_IMMEDIATE_MASK;
                NSLog(@"dyld_callback load_command opcode:%d, immediate:%d", opcode, immediate);
                switch (opcode)
                {
                    case BIND_OPCODE_DONE:
                        // The lazy bindings have one of these at the end of each bind.
                        isDone = YES;
                        
                        doBindLocation = (*((uint64_t *)bytePtr) + 1);
                        
                        break;
                        
                    case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
                        libOrdinal = immediate;
                        break;
                        
                    case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
                        
                        libOrdinal = (uint32_t)read_uleb128(&bytePtr);
                        
                        break;
                        
                    case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
                    {
                        // Special means negative
                        if (immediate == 0)
                        {
                            libOrdinal = 0;
                        }
                        else
                        {
                            int8_t signExtended = immediate | BIND_OPCODE_MASK; // This sign extends the value
                            
                            libOrdinal = signExtended;
                        }
                    } break;
                        
                    case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
                        symbolFlags = immediate;
                        symbolName = read_string(&bytePtr);
                        break;
                        
                    case BIND_OPCODE_SET_TYPE_IMM:
                        type = immediate;
                        break;
                        
                    case BIND_OPCODE_SET_ADDEND_SLEB:
                        
                        addend = read_sleb128(&bytePtr);
                        break;
                        //
                    case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
                    {
                        uint32_t segmentIndex = immediate;
                        uint64_t val = read_uleb128(&bytePtr);
                        
                        if (segmentIndex < segments_64->size())
                        {
                            address += (*segments_64)[segmentIndex]->fileoff + val;
                        }
                    } break;
                        
                    case BIND_OPCODE_ADD_ADDR_ULEB:
                    {
                        uint64_t val = read_uleb128(&bytePtr);
                        address += val;
                    } break;
                        
                    case BIND_OPCODE_DO_BIND:
                    {
                        // 獲取C++ type_info地址
                        NSLog(@"dyld_callback Bind SymbolName:%@", symbolName);
                        if ([symbolName hasPrefix:@"__ZTVN10__cxxabi"]) {
                            std::type_info *type_info = (std::type_info *)address;
                            NSLog(@"std::type_info name:%s address:%p", type_info->name(), type_info);
                            cxxTypeInfoSet->insert((uint64_t *)address);
                        }
                        doBindLocation = *((uint64_t *)bytePtr);
                        
                        address += ptrSize;
                    } break;
                        
                    case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
                    {
                        uint64_t startNextBind = *((uint64_t *)bytePtr);
                        
                        uint64_t val = read_uleb128(&bytePtr);
                        doBindLocation = startNextBind;
                        
                        address += ptrSize + val;
                    } break;
                        
                    case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
                    {
                        uint32_t scale = immediate;
                        // 獲取C++ type_info地址
                        if ([symbolName hasPrefix:@"__ZTVN10__cxxabi"]) {
                            std::type_info *type_info = (std::type_info *)address;
                            NSLog(@"std::type_info name:%s address:%p", type_info->name(), type_info);
                            cxxTypeInfoSet->insert((uint64_t *)address);
                        }
                        
                        doBindLocation = *((uint64_t *)bytePtr);
                        
                        address += ptrSize + scale * ptrSize;
                    } break;
                        
                    case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
                    {
                        uint64_t startNextBind = *((uint64_t *)bytePtr);
                        
                        uint64_t count = read_uleb128(&bytePtr);
                        
                        uint64_t skip = read_uleb128(&bytePtr);
                        
                        for (uint64_t index = 0; index < count; index++)
                        {
                            doBindLocation = startNextBind;
                            
                            address += ptrSize + skip;
                        }
                    } break;
                        
                    default:
                        break;
                }
            }
        }
        load_comandPtr = (uint64_t *)((unsigned char *)load_comandPtr + load_command->cmdsize);
    }
}

輸出如下

2020-11-16 11:03:32.879783+0800 OnlineMemoryGraphDemo[9037:1777045] std::type_info name:4Base address:0x104c803c0
2020-11-16 11:03:32.880196+0800 OnlineMemoryGraphDemo[9037:1777045] std::type_info name:5Human address:0x104c803d0
2020-11-16 11:03:32.880761+0800 OnlineMemoryGraphDemo[9037:1777045] std::type_info name:P4Male address:0x104c803f8
2020-11-16 11:03:32.881309+0800 OnlineMemoryGraphDemo[9037:1777045] std::type_info name:4Male address:0x104c803e0

Demo App中關(guān)于C++類型的定義

class Human {
public:
    int age;
    int sex;
    
    void sayHello();
};

class Base
{
public:
 
    Base(int i) :baseI(i){};
  
    int getI(){ return baseI; }
 
    static void countI(){};
 
    virtual ~Base(){}

    virtual void basePrint(void){ printf("Base::print()");}
 
private:
 
    int baseI;
 
    static int baseS;
};

class Male : public Base {
public:
    Male(int i): Base(i){
        
    };
    void sayHello();
};

判斷一個地址是否為一個C++Object(有type_info的)

typedef std::set<uint64_t *>    CxxTypeInfoSet;
static CxxTypeInfoSet *cxxTypeInfoSet = NULL;

+ (const char *) cppTypeInfoName:(void *) ptr {
    uint64_t *typeInfoPtr = (uint64_t*)(*((uint64_t *)ptr) - 8);
    uint64_t typeInfoAddress = (uint64_t)typeInfoPtr;
    if (typeInfoAddress >= dataConstMinAddress && typeInfoAddress < dataConstMaxAddress) {
        uint64_t *typeInfo = (uint64_t *)(*typeInfoPtr);
        if (cxxTypeInfoSet->find(typeInfo) != cxxTypeInfoSet->end()) {
            const char *name = ((std::type_info *)typeInfo)->name();
            return name;
        }
    }
    return NULL;
}

輸出如下

2020-11-16 11:03:39.581065+0800 OnlineMemoryGraphDemo[9037:1777252] Find a Cpp Object:4Male!
2020-11-16 11:03:39.581137+0800 OnlineMemoryGraphDemo[9037:1777252] Find a Cpp Object:4Base!

數(shù)據(jù)上報和后臺分析

這個大家就根據(jù)自己情況自己去實現(xiàn)吧免胃。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末音五,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子羔沙,更是在濱河造成了極大的恐慌躺涝,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扼雏,死亡現(xiàn)場離奇詭異坚嗜,居然都是意外死亡,警方通過查閱死者的電腦和手機诗充,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門苍蔬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蝴蜓,你說我怎么就攤上這事碟绑“吃常” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵格仲,是天一觀的道長押袍。 經(jīng)常有香客問我,道長凯肋,這世上最難降的妖魔是什么谊惭? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮侮东,結(jié)果婚禮上圈盔,老公的妹妹穿的比我還像新娘。我一直安慰自己悄雅,他們只是感情好驱敲,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宽闲,像睡著了一般癌佩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上便锨,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機與錄音我碟,去河邊找鬼放案。 笑死,一個胖子當著我的面吹牛矫俺,可吹牛的內(nèi)容都是我干的吱殉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼厘托,長吁一口氣:“原來是場噩夢啊……” “哼友雳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起铅匹,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤押赊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后包斑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體流礁,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年罗丰,在試婚紗的時候發(fā)現(xiàn)自己被綠了神帅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡萌抵,死狀恐怖找御,靈堂內(nèi)的尸體忽然破棺而出元镀,到底是詐尸還是另有隱情,我是刑警寧澤霎桅,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布栖疑,位于F島的核電站,受9級特大地震影響哆档,放射性物質(zhì)發(fā)生泄漏蔽挠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一瓜浸、第九天 我趴在偏房一處隱蔽的房頂上張望澳淑。 院中可真熱鬧,春花似錦插佛、人聲如沸杠巡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽氢拥。三九已至,卻和暖如春锨侯,著一層夾襖步出監(jiān)牢的瞬間嫩海,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工囚痴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叁怪,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓深滚,卻偏偏與公主長得像奕谭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子痴荐,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容