第二十四章:SB?Examples, Malloc?Logging

作為這一部分的最后一章, 你將會經(jīng)過同樣的步驟, 我自己理解當(dāng)一個對象被創(chuàng)建的到時候MallocStackLogging環(huán)境變量通常是怎樣得到堆棧記錄的.
從這里開始, 你將會創(chuàng)建一個可以給你一個對象在內(nèi)存中被創(chuàng)建或者銷毀的堆棧記錄的自定義的LLDB命令--甚至在堆棧記錄已經(jīng)離開調(diào)試器很久之后獲取到堆棧記錄.
知道在你的程序中一個對象是在哪里創(chuàng)建的堆棧記錄不僅有利于逆向工程, 而且在你日復(fù)一日的調(diào)試工作中也非常有幫助.當(dāng)一個進(jìn)程崩潰的時候, 在你的進(jìn)程結(jié)束之前知道那一塊內(nèi)存的歷史和發(fā)生的任何allocation 或者deallocation事件就顯得極為重要.
這是腳本使用與棧幀相關(guān)的邏輯的另外一個例子, 但是這一章將會聚焦于如何查找循環(huán)引用, 學(xué)習(xí), 然后實現(xiàn)一個非常強大的自定義命令.

設(shè)置腳本

在本章中你有一組腳本來使用(和實現(xiàn)). 讓我們挨個看一下你如何使用他們:
? msl.py: 這是在本章中你將要使用的一個(MallocStackLogging的縮寫)的命令腳本. 這里有一個邏輯的基本范圍.
? lookup.py: 等一下--你已經(jīng)創(chuàng)建了這個命令, 對吧?是的, 但是我將會給你我自己的在丑陋的代買中添加了一組額外的選項look up命令的版本. 你將會使用這些命令中的一個來過濾出一個進(jìn)程中你搜索的那個模塊.
? sbt.py: 這個命令將會追蹤無符號化的符號, 并且將它符號化.你在之前的章節(jié)中創(chuàng)建了這個腳本, 你將會在本章的末尾經(jīng)常用到. 如果你跳過了之前的章節(jié), 你可以在本章的資源文件夾里面找到它.
? search.py: 這個命令將會枚舉堆中的每一個對象并且搜索一個特定的子類. 這是快速抓取一個特定類的實例的引用的非常方便的命令.

注意:這些腳本來自[ https://github.com/DerekSelander/lldb]( https://github.com/DerekSelander/lldb).如果我需要一個我沒有的工具, 我將會構(gòu)建它, 并將它粘貼到上面的網(wǎng)站上. 為其他LLDB腳本相關(guān)的書籍下載這些腳本.

現(xiàn)在進(jìn)入通常的設(shè)置部分. 本章中所有的Python文件都可以在starter文件夾中找到然后將這個Python文件復(fù)制到~/lldb目錄下. 我假定你已經(jīng)設(shè)置過了lldbinit.py文件, 如果沒有你可以在第22章“SB Examples, ImprovedLookup.”中找到設(shè)置的方法.
在終端中啟動一個LLDB會話然后通過help命令確保每一個腳本都被成功加載了:

(lldb) help msl
(lldb) help lookup
(lldb) help sbt
(lldb) help search
MallocStackLogging 講解

如果你不熟悉MallocStackLogging環(huán)境變量, 我將會講解它并展示一下他的典型用法.
當(dāng)MallocStackLogging環(huán)境變量被設(shè)置為true并傳到一個進(jìn)程中去的時候, 它將會監(jiān)測堆中內(nèi)存的分配和釋放. 干凈漂亮!
包含在本章的starter文件夾中的50 Shades of Ray項目有一些額外邏輯.打開這個項目.
在你運行他之前, 為了達(dá)到你的目的你需要修改它的scheme.選中50 Shades of Ray scheme(確保scheme名字中沒有"Stripped"的字樣), 然后按下? + Shift + <來編輯這個scheme.
選擇Run, 然后選中Diagnostics, 然后選擇Malloc Stack, 然后選擇All Allocation andFree History.

圖片.png

在你啟用了這個環(huán)境變量之后, 構(gòu)建50 Shades of Ray程序然后在iPhone 7 Plus模擬器上運行.
如果MallocStackLogging環(huán)境變量被啟用了, 你會在LLD不控制臺中看到一些類似下面的輸出:

ShadesOfRay(12911,0x104e663c0) malloc: stack logs being written into /
tmp/stack-logs.12911.10d42a000.ShadesOfRay.gjehFY.index
ShadesOfRay(12911,0x104e663c0) malloc: recording malloc and VM allocation
stacks to disk using standard recorder
ShadesOfRay(12911,0x104e663c0) malloc: process 12673 no longer exists,
stack logs deleted from /tmp/stack-logs.
12673.11b51d000.ShadesOfRay.GVo3li.index

不要關(guān)心這個輸出的細(xì)節(jié); 只需要簡單的找到輸出中表明MallocStackLogging已經(jīng)生效的那部分輸出.
當(dāng)APP運行起來的時候, 點擊底部的Generate a Ray按鈕.

圖片.png

當(dāng)一個新的Ray被創(chuàng)建之后(也就是說, 你看到了一個新的英俊異常的Ray Wenderlich實例出現(xiàn)在了模擬器中), 執(zhí)行下面的步驟:

  1. 選擇Xcode中位于LLDB控制臺頂部的Debug Memory Graph.
  2. 選擇左側(cè)面板中的Show the Debug navigator.
  3. 在左側(cè)面板的底部選擇Show only content from workspace.
  4. 選擇RayView的引用.
  5. 在Xcode右側(cè)的面板中, 確保Show the Memory Inspector處于選中狀態(tài).

圖片.png

在你做完了上面的操作之后, 你將會通過Xcode中的Backtrace部分得到RayView實例是在哪里被創(chuàng)建的精確棧記錄. 多酷啊?!Xcode的作者(和其中的許多模塊)創(chuàng)建的的這些內(nèi)存調(diào)試功能讓我們的生活變得更簡單!

攻擊計劃

你知道了可以抓取一個對象初始化的棧記錄, 但是你將會做的比apple更好.
你的命令將能夠通過LLDB打開MallocStackLogging功能, 這就意味著你不再需要已來環(huán)境變量.這有一些額外的好處, 就是如果你在調(diào)試的時候忘記打開這個變量那么你不需要重啟你的進(jìn)程.
那么你怎樣才能弄清楚MallocStackLogging功能的工作原理呢?當(dāng)我對于瀏覽內(nèi)部代碼感到毫無頭緒的時候, 我會進(jìn)到相當(dāng)寬松的進(jìn)程下面然后修改查詢內(nèi)容, 根據(jù)查詢的方案或者輸出做決定:
? 我會在我附加到的將會被執(zhí)行的進(jìn)程中尋找我可以安全的假設(shè)某些我感興趣的的邏輯的阻塞點.如果我知道我可以復(fù)制一些我感興趣的東西, 我會強迫讓那些事件發(fā)生同時監(jiān)測他們.
? 在監(jiān)測我們感性的的代碼的時候, 我會使用不同的工具像LLDB或者DTrace(你將在下一章學(xué)到的東西)來找出我感興趣的代碼所在的模塊. 同樣, 這些模塊可能是一個動態(tài)庫, 一個框架, NSBundle或者從他們衍生出來的某些東西.
? 如果我發(fā)現(xiàn)了我感興趣的模塊, 我會提取出這個模塊中所有的代碼, 然后用我需要使用的不同的自定義腳本比如lookup.py做一下過濾.
? 如果我找到了看起來與我感興趣的內(nèi)容相關(guān)的函數(shù), 我首先會google一下這個函數(shù). 我經(jīng)常會在https://opensource.apple.com/上找到你一些揭示我如何使用我找到的東西的極其有用的建議.
? 通過apple的開源網(wǎng)站搜索, 我也能找到與我感興趣的那部分代碼相關(guān)的大量內(nèi)容. 有時候那些能夠為我如何構(gòu)建參數(shù)傳遞給函數(shù)提供主意的代碼在C/C++文件中, 或許我能在頭文件中找到這些代碼的描述或者這些代碼的目的.
? 如果在Google上找不到文檔, 我會在感興趣的代碼上設(shè)置一個斷點然后看一下我是否能夠自然的觸發(fā)這些代碼.一旦觸發(fā)了斷點, 我會同時瀏覽棧幀和寄存器來看一下傳入的參數(shù)的類型, 以及它所在的上下文.

捕獲getenv

MallocStackLogging是傳遞給進(jìn)程的一個環(huán)境變量.這就意味著很有可能用c函數(shù)getenv來檢查是否用了這個參數(shù)以及它是否會執(zhí)行額外的邏輯.
當(dāng)進(jìn)程啟動的時候你需要提取出用getenv查詢到的所有子項.你將會通過創(chuàng)建一個符號斷點來提取出getenv被調(diào)用時傳入的char*參數(shù)來執(zhí)行與第十五章“Hooking & Executing Codewith dlopen & dlsym”同樣的操作.
在Xcode中, 用下面的邏輯創(chuàng)建一個符號斷點:

?  Symbol: getenv
?  Action: po (char *)$arg1
?  Automatically continue after evaluating actions: 是!

圖片.png

MallocStackLogging變量仍然被勾選的情況下愛構(gòu)建并運行這個程序. 從輸出中, 你可以看到進(jìn)程啟動的地方, 下面是找到的存在MallocStackLogging的代碼.

圖片.png

將你的符號斷點修改為當(dāng)程序找到MallocStackLogging環(huán)境變量的時候只提取棧記錄:

?  Symbol: getenv
?  Condition: ((int)strcmp("MallocStackLogging", $arg1) == 0)
?  Action: bt
?  Automatically continue after evaluating actions: 是!
圖片.png

在你修改完符號斷點以后, 重新運行APP.
在控制臺中你將得到一些棧記錄的信息. 找出最靠前的棧幀的內(nèi)容:

 * frame #0: 0x0000000112b4da26 libsystem_c.dylib`getenv
    frame #1: 0x0000000112c7dd53
libsystem_malloc.dylib`_malloc_initialize + 466
    frame #2: 0x0000000112ddcac1 libsystem_platform.dylib`_os_once + 36
    frame #3: 0x0000000112c7d849
libsystem_malloc.dylib`default_zone_malloc + 77
    frame #4: 0x0000000112c7d259
libsystem_malloc.dylib`malloc_zone_malloc + 103
    frame #5: 0x0000000112c7f44a libsystem_malloc.dylib`malloc + 24
frame #6: 0x0000000112aa2947 libdyld.dylib`tlv_load_notification +
286
    frame #7: 0x000000010e0f68a9 dyld_sim`dyld::registerAddCallback(void
(*)(mach_header const*, long)) + 134
    frame #8: 0x0000000112aa1a0d
libdyld.dylib`_dyld_register_func_for_add_image + 61
    frame #9: 0x0000000112aa1be7 libdyld.dylib`_dyld_initializer + 47

很有趣...找出第一個棧幀:

 frame #1: 0x0000000112c7dd53 libsystem_malloc.dylib`_malloc_initialize +
466

如果我是一個apple的作者, 我很有可能會檢查一個有條件的環(huán)境變量看一下當(dāng)我的代碼初始化的時候是否可以正常運行.這些棧幀看起來做了同樣的事情, 加上模塊的名字, libsystem_malloc.dylib看起來是實現(xiàn)了malloc棧日志相關(guān)邏輯的庫.是這樣的, 對吧?也許是的. 值得我們查看一下?百分之百值得!
深入到模塊內(nèi)部然后看一下它提供給你了哪些內(nèi)容.
用你想到的, 新穎的, 改進(jìn)過的lookup命令瀏覽libsystem_malloc.dylib模塊中實現(xiàn)的所有可以在你的進(jìn)程中執(zhí)行的方法.
在調(diào)試器中暫停你的APP, 然后在LLDB控制臺中輸入下面命令:

(lldb) lookup . -m libsystem_malloc.dylib

在iOS 10.3上, 我找到了292和. 我本可以為所有的這些方法添加注釋, 但是作為一個調(diào)試者我越來越懶了. 我們還是值捕獲哪些包含單詞log(為了查找logging)的函數(shù)然后看一下我們會得到什么結(jié)果.在LLDB中輸入下面的內(nèi)容:

(lldb) lookup (?i)log -m libsystem_malloc.dylib

在libsystem_malloc.dylib 模塊中我搜索到了包含有單詞log并且區(qū)分大小寫的函數(shù)有46個.
在雜亂無章的內(nèi)容中這些數(shù)量是可以接受的.
這些函數(shù)中有看起來比較有趣的嗎?當(dāng)然.下面就是一個我覺得比較有趣的函數(shù):

create_log_file
open_log_file_from_directory
__mach_stack_logging_get_frames
turn_off_stack_logging
turn_on_stack_logging

在我選出的5個最感興趣的函數(shù)中, turn_on_stack_logging函數(shù)和__mach_stack_logging_get_frames函數(shù)看起來是最值得查看的.
你已經(jīng)找到了感興趣的模塊, 以及一些值得進(jìn)一步研究的函數(shù).是時候到Google中搜索一下并看看能找到哪些信息.

谷歌中的JIT函數(shù)

谷歌一下任何包含turn_on_stack_logging的內(nèi)容. 這個搜索看起來就是下面這個樣子:

圖片.png

在我寫這本書的時候, 我從Google中找到了三個結(jié)果.
這個函數(shù)并不出名而且離開了apple這個圈子之后并不會經(jīng)常被人討論. 事實上, 我相當(dāng)確定大部分apple的iOS開發(fā)者都不知道這個函數(shù), 因為在寫APP的過程中什么時候用的到呢?
這些資料屬于那些我們非常尊敬的apple底層的C開發(fā)者們.
從Google的搜索結(jié)果中, 從https://opensource.apple.com/source/libmalloc/libmalloc-116/private/stack_logging.h.auto.html網(wǎng)站中找到的頭文件中找出下面的代碼:

typedef enum {
  stack_logging_mode_none = 0,
  stack_logging_mode_all,
  stack_logging_mode_malloc,
  stack_logging_mode_vm,
  stack_logging_mode_lite
} stack_logging_mode_type;
extern boolean_t turn_on_stack_logging(stack_logging_mode_type mode);

這些是非常有用的信息. turn_on_stack_logging函數(shù)期望傳入一個int(C的枚舉型)型的參數(shù). stack_logging_mode_type的枚舉告訴你如果你想要stack_logging_mode_all選項, 它的值將會是1.
你將要運行一個關(guān)掉棧幀日志環(huán)境變量的項目來做一個實驗, 通過LLDB執(zhí)行上面的函數(shù), 然后看一下Xcode是否會為你記錄下調(diào)用turn_on_stack_logging函數(shù)后分配出對象的棧記錄.
在你做之前, 你首先需要瀏覽另外一個函數(shù)__mach_stack_logging_get_frames.

瀏覽__mach_stack_logging_get_frames

幸運的是, 因為你對探索的努力, __mach_stack_logging_get_frames 函數(shù)也可以在同一個頭文件里找到. 這個函數(shù)的聲明看起來是下面這個樣子:

extern kern_return_t __mach_stack_logging_get_frames(
                                        task_t task,
                          mach_vm_address_t address,
             mach_vm_address_t *stack_frames_buffer,
                          uint32_t max_stack_frames,
                                   uint32_t *count);
    /* Gets the last allocation record (malloc, realloc, or free) about
address */

這是一個很好的起始點, 但是如果這里有參數(shù)你還不能100%確定這些參數(shù)是什么如何獲得呢?例如, task_t task是什么?這是一個指明你想要讓這個函數(shù)在哪個進(jìn)程上執(zhí)行的最基本的參數(shù). 但是如果你不知道這些怎么辦呢?
使用Google并搜索包含__mach_stack_logging_get_frames的任何文件會有很大的幫助當(dāng)你不確定這些事情的時候.
在Google的搜索結(jié)果中, https://llvm.org/svn/llvm-project/lldb/trunk/examples/darwin/heap_find/heap/heap_find.cpp這個網(wǎng)站提供了這個函數(shù)第一個參數(shù)的釋義.
這個文件中包含下面的代碼:

task_t task = mach_task_self();
/* Omitted code.... */
    stack_entry->address = addr;
    stack_entry->type_flags = stack_logging_type_alloc;
    stack_entry->argument = 0;
    stack_entry->num_frames = 0;
    stack_entry->frames[0] = 0;
    err = __mach_stack_logging_get_frames(task,
                       (mach_vm_address_t)addr,
                           stack_entry->frames,
                                      MAX_FRAMES,
                      &stack_entry->num_frames);
    if (err == 0 && stack_entry->num_frames > 0) {
      // Terminate the frames with zero if there is room
      if (stack_entry->num_frames < MAX_FRAMES)
        stack_entry->frames[stack_entry->num_frames] = 0;
    } else {
      g_malloc_stack_history.clear();
    }
} }

位于libsystem_kernel.dylib庫中的mach_task_self函數(shù)中的task_t參數(shù)可以輕松的獲取到代表當(dāng)前進(jìn)程的任務(wù).

測試這些函數(shù)

為了防止你流下無聊的眼淚, 我已經(jīng)在APP中實現(xiàn)了__mach_stack_logging_get_frames的邏輯.

圖片.png

希望你的應(yīng)用程序還在運行. 如果沒有運行, 重新讓APP在MallocStackLogging仍然啟用的狀態(tài)下運行.
在Xcode中首先編譯證明你的概念的JIT代碼總是一個好主意, 并且如果它是可以運行的, 然后將它轉(zhuǎn)移到你的LLDB腳本中.如果你嘗試先直接在LLDB中寫POC JIT腳本你將會變得討厭你的生活.相信我.
在Xcode中, 找到stack_logger.cpp文件.__mach_stack_logging_get_frames是用C++寫的, 因此你需要用C++的代碼來執(zhí)行它.
這個文件中僅有的函數(shù)是trace_address:

void trace_address(mach_vm_address_t addr) {
  typedef struct LLDBStackAddress {
mach_vm_address_t *addresses;
  uint32_t count = 0;
} LLDBStackAddress;   // 1
LLDBStackAddress stackaddress; // 2
__unused mach_vm_address_t address = (mach_vm_address_t)addr;
__unused task_t task = mach_task_self_;  // 3
stackaddress.addresses = (mach_vm_address_t *)calloc(100,
                              sizeof(mach_vm_address_t)); // 4
__mach_stack_logging_get_frames(task,
                             address,
              stackaddress.addresses,
                                 100,
                  &stackaddress.count); // 5
  for (int i = 0; i < stackaddress.count; i++) {
    printf("[%d] %llu\n", i, stackaddress.addresses[i]);
  }
  free(stackaddress.addresses); // 7
}

拆解時間到了!

  1. 正如你所知, LLDB在執(zhí)行的時候僅讓你返回一個對象.但是, 作為你自己的一個有創(chuàng)造性的字符串理論版本, 可以創(chuàng)建包含任何你希望返回的類型的C結(jié)構(gòu)體.
  2. 用這個函數(shù)聲明一個上面說的結(jié)構(gòu)體的實例.
  3. 還記得前面說的mach_task_self引用的內(nèi)容嗎?全局變量mach_task_self_是調(diào)用mach_task_self時的返回值.
  4. 因為你在操作底層的東西, 所以沒有ARC幫助你在堆中分配項目. 你正在分配100個mach_vm_address_t的空間, 這些空間足以處理任何棧記錄.
  5. 然后執(zhí)行__mach_stack_logging_get_frames. 如果有任何可用的棧記錄信息, LLDBStackAddress結(jié)構(gòu)體的將會被添加到addresses數(shù)組中.
  6. 打印出我們發(fā)現(xiàn)的所有地址.
  7. 最后, 你創(chuàng)建的mach_vm_address_t被釋放掉了.
LLDB 測試

確保APP依然在運行, 然后點擊Generate a Ray!按鈕. 暫停執(zhí)行并將下面的命令輸入到LLDB中:

(lldb) search RayView -b

搜索腳本將會枚舉堆中包含的所有對象. 這個命令會捕獲當(dāng)前依然存在的所有的RayView實例.
-b選項將會給你--brief功能, 釋放類的description或者debugDescription方法. 你將會得到一個記錄Ray Wenderlich的連在你模擬器中出現(xiàn)的次數(shù)的變量.
在我的模擬器中有三個很有魔力的Ray Wenderlich的臉, 因此我的到了下面的輸出:

(lldb) search RayView -b
RayView * [0x00007fa838414330]
RayView * [0x00007fa8384125f0]
RayView * [0x00007fa83860c000]

抓取上面的任意一個地址然后在trace_address函數(shù)中執(zhí)行:

(lldb) po trace_address(0x00007fa838414330)

你的輸出看起來應(yīng)該是下面這個樣子:

[0] 4533269637
[1] 4460190625
[2] 4460232164
[3] 4454012240
[4] 4478307618
[5] 4482741703
[6] 4478307618
[7] 4479898204
[8] 4479898999
[9] 4479899371
...

這里是對象被創(chuàng)建時執(zhí)行的代碼的實際地址. 用image lookup命令驗證一下第一個地址在內(nèi)存中存儲的是代碼:

(lldb) image lookup -a 4533269637

你將會得到這個函數(shù)的詳細(xì)信息:

Address: libsystem_malloc.dylib[0x000000000000f485]
(libsystem_malloc.dylib.__TEXT.__text + 56217)
Summary: libsystem_malloc.dylib`calloc + 30

不只有一種方法可以查看內(nèi)存地址的內(nèi)容. 復(fù)制第三幀的地址容納后使用SBAddress取出這個地址里的信息:

(lldb) script print lldb.SBAddress(4454012240, lldb.target)

你將會得到棧中的第3幀, 想下面這樣:

ShadesOfRay`-[ViewController generateRayViewTapped:] + 64 atViewController.m:38
用lldb.value瀏覽C數(shù)組

你將會再一次使用lldb.value類來解析執(zhí)行這個函數(shù)時返回的這個C結(jié)構(gòu)體.
trace_address函數(shù)結(jié)束的地方設(shè)置一個GUI結(jié)構(gòu)體:

圖片.png

用LLDB執(zhí)行同樣的函數(shù), 但是尊重斷點, 記得用你的RayView實例的地址替換替換下面的地址:

(lldb) e -lobjc++ -O -i0 -- trace_address(0x00007fa838414330)

執(zhí)行將會在trace_address的最后一行停下來.你了解這個操作. 抓取LLDBStackAddress C結(jié)構(gòu)體的引用, stackaddress.

(lldb) script print lldb.frame.FindVariable('stackaddress')

如果成功了, 你將會得到stackaddress變量的組成格式:

(LLDBStackAddress) stackaddress = {
  addresses = 0x00007fa838515cd0
count = 25 }

lldb.value函數(shù)的返回值賦給a引用:

(lldb) script a = lldb.value(lldb.frame.FindVariable('stackaddress'))

確保a是一個有效值的:

(lldb) script print a

現(xiàn)在你可以很簡單的引用聲明在lldb.value函數(shù)中LLDBStackAddress結(jié)構(gòu)體上的變量. 在LLDB中輸入下面的內(nèi)容:

(lldb) script print a.count

你將會得到棧幀的數(shù)量:

(uint32_t) count = 25

LLDBStackAddress結(jié)構(gòu)體中addresses數(shù)組是什么樣的呢?

(lldb) script print a.addresses[0]

那是第一幀的內(nèi)存地址. 第三幀中的generateRayViewTapped:函數(shù)是什么樣的呢?

(lldb) script print a.addresses[3]

你將會得到一些類似下面的輸出:

(mach_vm_address_t) [3] = 4454012240

你看到了這些工具是如何組合在一起是用的? 從找到感興趣的阻塞點開始, 在模塊中瀏覽代碼, 在https://opensource.apple.com/網(wǎng)站重新搜索有用的相關(guān)信息, 在寫LLDB Python代碼之前先先在Xcode中驗證一下你實現(xiàn)的想法, 在蓋子下面有許多力量.
不要慢下來--是實現(xiàn)命令的時間了!

將數(shù)字傳入到棧幀中

在本章的starter目錄下的msl.py腳本是用來記錄malloc日志的.你已經(jīng)在前面的"Setting upthe scripts"章節(jié)中安裝了msl.py腳本.
不幸的是, 現(xiàn)在這個腳本能做的還很少, 因為它還不能產(chǎn)生任何輸出. 是時候來改變一下它了.
用你最喜歡的編輯器打開~/lldb/msl.py. 找到handle_command命令并添加下面的代碼:

command_args = shlex.split(command)
parser = generateOptionParser()
try:
    (options, args) = parser.parse_args(command_args)
except:
    result.SetError(parser.usage)
    return
cleanCommand = args[0]
process = debugger.GetSelectedTarget().GetProcess()
frame = process.GetSelectedThread().GetSelectedFrame()
target = debugger.GetSelectedTarget()

這些邏輯對你來說應(yīng)該并不陌生, 因為它是命令起始部分必要的序文. 唯一有趣的一點是你選擇忽略有時在shlex.spli中使用的posix=False參數(shù). 這里不需要提供這個參數(shù), 因為這個命令不會處理任何古怪的反斜杠和破折號字符. 這就意味著從optionsargs變量中剖析的輸出會更加清晰.
現(xiàn)在你已經(jīng)有了最基本的腳本, 你只需要按照下面的方式正確的完善剩余的腳本:

#1
script = generateScript(cleanCommand, options)
#2
sbval = frame.EvaluateExpression(script, generateOptions())
#3
if sbval.error.fail:
    result.AppendMessage(str(sbval.error))
    return
val = lldb.value(sbval)
addresses = []
#4
for i in range(val.count.sbvalue.unsigned):
    address = val.addresses[i].sbvalue.unsigned
    sbaddr = target.ResolveLoadAddress(address)
    loadAddr = sbaddr.GetLoadAddress(target)
    addresses.append(loadAddr)
#5
retString = processStackTraceStringFromAddresses(
                                        addresses,
                                           target)
#6
freeExpr = 'free('+str(val.addresses.sbvalue.unsigned)+')'
frame.EvaluateExpression(freeExpr, generateOptions())
result.AppendMessage(retString)

這里有幾個有趣的點:

  1. 我使用了generateScript函數(shù), 這個函數(shù)返回了一個跟trace_address函數(shù)一樣的同樣的包含著粗糙地代碼的字符串.
  2. 執(zhí)行這個代碼. 你知道浙江返回一個SBValue.
  3. 明智的檢查一下EvaluateExpression是否失敗. 如果失敗了, 提取出錯誤信息并盡早退出.
  4. 這個for循環(huán)會遍歷val對象中的內(nèi)存地址, 這會輸出script的代碼, 并將他們放到地址列表中.
  5. 現(xiàn)在地址已經(jīng)被放到了一個列表中, 你通過那個列表來預(yù)定義將要處理的函數(shù). 這將會返回將要輸出的棧記錄字符串.
  6. 最后, 你手動的分配內(nèi)存, 我相信你是一個好的內(nèi)存管理能收并且總是在后面清空分配的內(nèi)存. 你已經(jīng)寫完了內(nèi)存泄露腳本的大部分內(nèi)容, 但是現(xiàn)在你需要學(xué)習(xí)更高級的知識, 是時候做一些正確的事情并free之前分配的內(nèi)存.
    回到Xcode的LLDB控制臺中然后重新加載腳本:
(lldb) reload_script

假如你沒有遇到任何錯誤, 用LLDB的search命令抓取一個RayView的引用:

(lldb) search RayView -b

只是為了介紹一下, 這里還有另外一種實現(xiàn)方式搜索所有ShadesOfRay模塊中的子類:

(lldb) search UIView -m ShadesOfRay -b

如果你有了一個RayView的子類引用, 在這個引用上運行你最新創(chuàng)建的msl命令, 就像下面這樣:

(lldb) msl 0x00007fa838414330

在Xcode中你將會得到期望的輸出!

frame #0 : 0x11197d485 libsystem_malloc.dylib`calloc + 30
frame #1 : 0x10d3cbba1 libobjc.A.dylib`class_createInstance + 85
frame #2 : 0x10d3d5de4 libobjc.A.dylib`_objc_rootAlloc + 42
frame #3 : 0x10cde7550 ShadesOfRay`-[ViewController
generateRayViewTapped:] + 64
frame #4 : 0x10e512d22 UIKit`-[UIApplication
sendAction:to:from:forEvent:] + 83

歡呼吧!你已經(jīng)創(chuàng)建了一個可以追蹤一個對象的棧記錄的腳本.現(xiàn)在是時候給腳本一些很酷的選項將代碼升級一下了!

Swift對象的棧記錄

好了--我知道你想讓我聊一些swift的代碼.你同樣會學(xué)習(xí)一個swift的例子.
50 Shades of RayAPP 里面包含的是一個swift模塊, 名字叫做SomeSwiftModule. 在這個模塊中有一個SomeSwiftCode的類有一個獨特的靜態(tài)變量.
SomeSwiftCode.swift中的代碼非常簡單:

public final class SomeSwiftCode {
  private init() {}
  static let shared = SomeSwiftCode()
}

你將會用LLDB來調(diào)用這個方法并且檢查這個很熟被創(chuàng)建的位置的棧記錄.
首先, 你需要導(dǎo)入你的Swift模塊!在LLD不中輸入下面內(nèi)容:

(lldb) e -lswift -O -- import SomeSwiftModule

讓面的命令運行成功之后你不會得到任何輸出.
在LLDB中, 可以用下面的命令訪問這個靜態(tài)變量:

(lldb) e -lswift -O -- SomeSwiftCode.shared

你會得到這個對象的內(nèi)存地址:

<SomeSwiftCode: 0x600000033640>

現(xiàn)在你要將這個內(nèi)存地址傳給msl命令. 只需要從輸出中簡單的復(fù)制粘貼一下真是太簡單. 用search命令替代并搜索SwiftObject的子類:

(lldb) search SwiftObject

你將會得到一些類似下面的輸出:

<__NSArrayM 0x6000004578b0>(
SomeSwiftModule.SomeSwiftCode
)

再說一次, Swift嘗試隱藏description中的指針. 那是swift魔法的一部分.
search命令中的最后用--brief (-b)選項來抓取那個實例并且忽略對象的description方法.

(lldb) search SwiftObject -b

這將會抓取被打亂的名字, 但是在內(nèi)存中它們是同樣的引用.

_TtC15SomeSwiftModule13SomeSwiftCode * [0x0000600000033640]

將這個地址傳給msl命令:

(lldb) msl 0x0000600000033640

你將會得到期望的棧記錄.

圖片.png

標(biāo)記的那一幀清晰的指明了你在LLDB中調(diào)用這個靜態(tài)變量的位置. 你的與我的可能有所不同.
讓我們轉(zhuǎn)到我想簡單討論一下的最后一個話題: 當(dāng)你在LLDB腳本中創(chuàng)建功能的時候如何編譯這些腳本以便你"不需要重復(fù)你做過的事情".

只運行Python代碼

停止運行這個APP! 在scheme列表中, 選擇Stripped 50 Shades of Ray這個scheme.
確保MallocStackLogging環(huán)境變量在Stripped 50 Shades of Ray這個scheme中沒有勾選.

圖片.png

好. Ray贊同了.
是時候嘗試一下turn_on_stack_logging函數(shù)了. 構(gòu)建并運行應(yīng)用程序. 正如你在前幾章中找到的, "Stripped 50 Shades of Ray" scheme 會精簡掉可執(zhí)行文件中的內(nèi)容因此這里沒有可用的調(diào)試信息.當(dāng)你使用msl命令的時候記住那些描述.
如果應(yīng)用程序運行起來了, 點擊Generate a Ray!按鈕來創(chuàng)建一個新的RayView實例.因為MallocStackLogging是禁用的, 讓我們看一下會發(fā)生什么...
暫停執(zhí)行并在LLDB中輸入下面的內(nèi)容搜索所有的RayViews:

(lldb) search RayView -b

你將會得到一些類似下面的輸出:

RayView * [0x00007fc23eb00620]

看一下msl用這個地址是否生效:

(lldb) msl 0x00007fc23eb00620

什么都沒有. 與我們預(yù)料的結(jié)果一樣, 因為那個環(huán)境變量沒有被應(yīng)用到這個進(jìn)程上. 時候會回過頭來并看看turn_on_stack_logging做了什么事情. 在LLDB中輸入下面的內(nèi)容:

(lldb) po turn_on_stack_logging(1)

你將會得到一些與你將MallocStackLogging環(huán)境變量應(yīng)用到你的進(jìn)程上之后類似的輸出:

圖片.png

繼續(xù)執(zhí)行并點擊底部的按鈕創(chuàng)建另外一個RayView實例.
如果你已經(jīng)做完了上面的操作, 暫停執(zhí)行并再次搜索所有的RayView實例.
這一次你將會得到一個新的地址. 希望啟用了stack logging, 你將會得到這個記錄.
復(fù)制這個新地址并將它應(yīng)用到msl命令上:

(lldb) msl 0x00007f8250f0a170

這會給出棧記錄!

圖片.png

這是一個驚喜!你可以在將要監(jiān)測任何allocation或者deallocation事件之前啟用malloc日志而不需要重啟你的進(jìn)程.
等一下!稍等幾秒鐘....這里有一個被精簡過的符號.
圖片.png

Ray不喜歡沒有精簡過的函數(shù).
如果你能回想起前面的章節(jié), 你之前創(chuàng)建了一個可符號化棧記錄的sbt命令. 在sbt.py腳本中, 你創(chuàng)建了一個帶有一個數(shù)字?jǐn)?shù)組和SBTarget參數(shù)的processStackTraceStringFromAddresses函數(shù). 這個函數(shù)為棧記錄返回了一個有可符號化潛力的字符串.
你已經(jīng)完成了這個函數(shù)最難的部分, 所以為什么不將這個功能包含在msl.py腳本里可選的執(zhí)行呢?
轉(zhuǎn)到msl.py函數(shù)的最上面的位置, 并添加下面的導(dǎo)入語句:

import sbt

msl.py文件的handle_command函數(shù)中, 找到下面的代碼:

retString = sbt.processStackTraceStringFromAddresses(
                                            addresses,
                                            target)

用下面的代碼替換上面的代碼:

if options.resymbolicate:
    retString = sbt.processStackTraceStringFromAddresses(
                                                addresses,
                                                   target)
else:
    retString = processStackTraceStringFromAddresses(
                                        addresses,
                                           target)

你的條件是檢查options.resymbolicate選項.如果為true, 然后調(diào)用sbt模塊的邏輯來看一下它能否生成一個重新符號化后的函數(shù)的字符串.
因為你寫那個函數(shù)作為一個屬性并且處理一個Python的數(shù)字列表, 所以從你的msl腳本中可以輕松的通過這些信息.
在你測試這個代碼之前, 還有最后一段需要實現(xiàn).你需要創(chuàng)建一個快捷命令去啟用turn_on_stack_logging.
跳到__lldb_init_module函數(shù)中(這個函數(shù)仍然在msl.py)文件中然后添加下面一行代碼:

debugger.HandleCommand('command alias enable_logging expression -lobjc -O-- extern void turn_on_stack_logging(int); turn_on_stack_logging(1);')

這行命令聲明了一個快捷命令來打開malloc棧日志.
“耶!"完成了!回到Xcode中并重新加載你的代碼:

(lldb) reload_script

在前一個RayView上使用--resymbolicate選項來查看完全符號化的棧記錄.

(lldb) msl 0x00007f8250f0a170 -r
圖片.png

面對這個如此漂亮的棧記錄我高興的簡直要哭了!終于可以松口氣了!

我們?yōu)槭裁匆獙W(xué)這些?

希望, 這個注意的完整實現(xiàn), 重新搜索和已經(jīng)被驗證過的有用的實現(xiàn), 甚至可以鼓舞你去創(chuàng)造你自己的腳本. 在你的[i|mac|tv|watch]OS設(shè)備中的許多框架中還隱藏這許多強大的功能.
你所需要做的就是發(fā)現(xiàn)這些隱藏著的寶物并且并將他們開發(fā)成瘋狂的商業(yè)調(diào)試工具, 或者將他們引用到逆向工程中來更好的理解發(fā)生了什么事情.
這里有一個值得在你實際的iOS設(shè)備中去瀏覽的目錄列表:
? /Developer/
? /usr/lib/
? /System/Library/PrivateFrameworks/
繼續(xù)前行, 去實現(xiàn)我們的夢想吧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末落蝙,一起剝皮案震驚了整個濱河市倦挂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌畦攘,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徘六,死亡現(xiàn)場離奇詭異嘴脾,居然都是意外死亡,警方通過查閱死者的電腦和手機渠羞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來智哀,“玉大人次询,你說我怎么就攤上這事〈山校” “怎么了屯吊?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長摹菠。 經(jīng)常有香客問我盒卸,道長,這世上最難降的妖魔是什么次氨? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任蔽介,我火速辦了婚禮,結(jié)果婚禮上煮寡,老公的妹妹穿的比我還像新娘虹蓄。我一直安慰自己,他們只是感情好幸撕,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布薇组。 她就那樣靜靜地躺著,像睡著了一般杈帐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上专钉,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天挑童,我揣著相機與錄音,去河邊找鬼跃须。 笑死站叼,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的菇民。 我是一名探鬼主播尽楔,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼投储,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了阔馋?” 一聲冷哼從身側(cè)響起玛荞,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呕寝,沒想到半個月后勋眯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡下梢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年客蹋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孽江。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡讶坯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出岗屏,到底是詐尸還是另有隱情辆琅,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布担汤,位于F島的核電站涎跨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏崭歧。R本人自食惡果不足惜隅很,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望率碾。 院中可真熱鬧叔营,春花似錦、人聲如沸所宰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仔粥。三九已至婴谱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間躯泰,已是汗流浹背谭羔。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留麦向,地道東北人瘟裸。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像诵竭,于是被迫代替她去往敵國和親话告。 傳聞我的和親對象是個殘疾皇子兼搏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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