Advanced+Apple+Debugging(14)

來源:互聯網
上載者:User

標籤:lldb

作為這一部分的最後一章, 你將會經過同樣的步驟, 我自己理解當一個對象被建立的到時候MallocStackLogging環境變數通常是怎樣得到堆棧記錄的.
從這裡開始, 你將會建立一個可以給你一個對象在記憶體中被建立或者銷毀的堆棧記錄的自訂的LLDB命令--甚至在堆棧記錄已經離開調試器很久之後擷取到堆棧記錄.
知道在你的程式中一個對象是在哪裡建立的堆棧記錄不僅有利於逆向工程, 而且在你日複一日的調試工作中也非常有協助.當一個進程崩潰的時候, 在你的進程結束之前知道那一塊記憶體的曆史和發生的任何allocation 或者deallocation事件就顯得極為重要.
這是指令碼使用與棧幀相關的邏輯的另外一個例子, 但是這一章將會聚焦於如何尋找循環參考, 學習, 然後實現一個非常強大的自訂命令.

設定指令碼

在本章中你有一組指令碼來使用(和實現). 讓我們挨個看一下你如何使用他們:
? msl.py: 這是在本章中你將要使用的一個(MallocStackLogging的縮寫)的命令指令碼. 這裡有一個邏輯的基本範圍.
? lookup.py: 等一下--你已經建立了這個命令, 對吧?是的, 但是我將會給你我自己的在醜陋的代買中添加了一組額外的選項look up命令的版本. 你將會使用這些命令中的一個來過濾出一個進程中你搜尋的那個模組.
? sbt.py: 這個命令將會追蹤無符號化的符號, 並且將它符號化.你在之前的章節中建立了這個指令碼, 你將會在本章的末尾經常用到. 如果你跳過了之前的章節, 你可以在本章的資源檔夾裡面找到它.
? search.py: 這個命令將會枚舉堆中的每一個對象並且搜尋一個特定的子類. 這是快速抓取一個特定類的執行個體的引用的非常方便的命令.

注意:這些指令碼來自 https://github.com/DerekSelander/lldb.如果我需要一個我沒有的工具, 我將會構建它, 並將它粘貼到上面的網站上. 為其他LLDB指令碼相關的書籍下載這些指令碼.
現在進入通常的設定部分. 本章中所有的Python檔案都可以在starter檔案夾中找到然後將這個Python檔案複製到~/lldb目錄下. 我假定你已經設定過了lldbinit.py檔案, 如果沒有你可以在第22章“SB Examples, ImprovedLookup.”中找到設定的方法.
在終端中啟動一個LLDB會話然後通過help命令確保每一個指令碼都被成功載入了:

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

如果你不熟悉MallocStackLogging環境變數, 我將會講解它並展示一下他的典型用法.
當MallocStackLogging環境變數被設定為true並傳到一個進程中去的時候, 它將會監測堆中記憶體的分配和釋放. 乾淨漂亮!
包含在本章的starter檔案夾中的50 Shades of Ray項目有一些額外邏輯.開啟這個項目.
在你運行他之前, 為了達到你的目的你需要修改它的scheme.選中50 Shades of Ray scheme(確保scheme名字中沒有"Stripped"的字樣), 然後按下? + Shift + <來編輯這個scheme.
選擇Run, 然後選中Diagnostics, 然後選擇Malloc Stack, 然後選擇All Allocation andFree History.

圖片.png

在你啟用了這個環境變數之後, 構建50 Shades of Ray程式然後在iPhone 7 Plus模擬器上運行.
如果MallocStackLogging環境變數被啟用了, 你會在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
不要關心這個輸出的細節; 只需要簡單的找到輸出中表明MallocStackLogging已經生效的那部分輸出.
當APP運行起來的時候, 點擊底部的Generate a Ray按鈕.

圖片.png

當一個新的Ray被建立之後(也就是說, 你看到了一個新的英俊異常的Ray Wenderlich執行個體出現在了模擬器中), 執行下面的步驟:
選擇Xcode中位於LLDB控制台頂部的Debug Memory Graph.
選擇左側面板中的Show the Debug navigator.
在左側面板的底部選擇Show only content from workspace.
選擇RayView的引用.
在Xcode右側的面板中, 確保Show the Memory Inspector處於選中狀態.
圖片.png

在你做完了上面的操作之後, 你將會通過Xcode中的Backtrace部分得到RayView執行個體是在哪裡被建立的精確棧記錄. 多酷啊?!Xcode的作者(和其中的許多模組)建立的的這些記憶體調試功能讓我們的生活變得更簡單!
×××計劃

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

捕獲getenv

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

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

在MallocStackLogging變數仍然被勾選的情況下愛構建並運行這個程式. 從輸出中, 你可以看到進程啟動的地方, 下面是找到的存在MallocStackLogging的代碼.
圖片.png

將你的符號斷點修改為當程式找到MallocStackLogging環境變數的時候只提取棧記錄:
? Symbol: getenv
? Condition: ((int)strcmp("MallocStackLogging", $arg1) == 0)
? Action: bt
? Automatically continue after evaluating actions: 是!
圖片.png

在你修改完符號斷點以後, 重新運行APP.
在控制台中你將得到一些棧記錄的資訊. 找出最靠前的棧幀的內容:

  • frame #0: 0x0000000112b4da26 libsystem_c.dylibgetenv<br/>frame #1: 0x0000000112c7dd53<br/>libsystem_malloc.dylib_malloc_initialize + 466
    frame #2: 0x0000000112ddcac1 libsystem_platform.dylib_os_once + 36<br/>frame #3: 0x0000000112c7d849<br/>libsystem_malloc.dylibdefault_zone_malloc + 77
    frame #4: 0x0000000112c7d259
    libsystem_malloc.dylibmalloc_zone_malloc + 103<br/>frame #5: 0x0000000112c7f44a libsystem_malloc.dylibmalloc + 24
    frame #6: 0x0000000112aa2947 libdyld.dylibtlv_load_notification +<br/>286<br/>frame #7: 0x000000010e0f68a9 dyld_simdyld::registerAddCallback(void
    ()(mach_header const, long)) + 134
    frame #8: 0x0000000112aa1a0d
    libdyld.dylib_dyld_register_func_for_add_image + 61<br/>frame #9: 0x0000000112aa1be7 libdyld.dylib_dyld_initializer + 47
    很有趣...找出第一個棧幀:

    frame #1: 0x0000000112c7dd53 libsystem_malloc.dylib`_malloc_initialize +
    466
    如果我是一個apple的作者, 我很有可能會檢查一個有條件的環境變數看一下當My Code初始化的時候是否可以正常運行.這些棧幀看起來做了同樣的事情, 加上模組的名字, libsystem_malloc.dylib看起來是實現了malloc棧日誌相關邏輯的庫.是這樣的, 對吧?也許是的. 值得我們查看一下?百分之百值得!
    深入到模組內部然後看一下它提供給你了哪些內容.
    用你想到的, 新穎的, 改進過的lookup命令瀏覽libsystem_malloc.dylib模組中實現的所有可以在你的進程中執行的方法.
    在調試器中暫停你的APP, 然後在LLDB控制台中輸入下面命令:

(lldb) lookup . -m libsystem_malloc.dylib
在iOS 10.3上, 我找到了292和. 我本可以為所有的這些方法添加註釋, 但是作為一個調試者我越來越懶了. 我們還是值捕獲哪些包含單詞log(為了尋找logging)的函數然後看一下我們會得到什麼結果.在LLDB中輸入下面的內容:

(lldb) lookup (?i)log -m libsystem_malloc.dylib
在libsystem_malloc.dylib 模組中我搜尋到了包含有單詞log並且區分大小寫函數有46個.
在雜亂無章的內容中這些數量是可以接受的.
這些函數中有看起來比較有趣的嗎?當然.下面就是一個我覺得比較有趣的函數:

create_log_file
open_log_file_from_directory
__mach_stack_logging_get_frames
turn_off_stack_logging
turn_on_stack_logging
在我選出的5個最感興趣的函數中, turn_on_stack_logging函數和__mach_stack_logging_get_frames函數看起來是最值得查看的.
你已經找到了感興趣的模組, 以及一些值得進一步研究的函數.是時候到Google中搜尋一下並看看能找到哪些資訊.

Google中的JIT函數

Google一下任何包含turn_on_stack_logging的內容. 這個搜尋看起來就是下面這個樣子:

圖片.png
在我寫這本書的時候, 我從Google中找到了三個結果.
這個函數並不出名而且離開了apple這個圈子之後並不會經常被人討論. 事實上, 我相當確定大部分apple的iOS開發人員都不知道這個函數, 因為在寫APP的過程中什麼時候用的到呢?
這些資料屬於那些我們非常尊敬的apple底層的C開發人員們.
從Google的搜尋結果中, 從https://opensource.apple.com/source/libmalloc/libmalloc-116/private/stack_logging.h.auto.html網站中找到的標頭檔中找出下面的代碼:

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函數期望傳入一個int(C的枚舉型)型的參數. stack_logging_mode_type的枚舉告訴你如果你想要stack_logging_mode_all選項, 它的值將會是1.
你將要運行一個關掉棧幀日誌環境變數的項目來做一個實驗, 通過LLDB執行上面的函數, 然後看一下Xcode是否會為你記錄下調用turn_on_stack_logging函數後分配出對象的棧記錄.
在你做之前, 你首先需要瀏覽另外一個函數__mach_stack_logging_get_frames.

瀏覽__mach_stack_logging_get_frames

幸運的是, 因為你對探索的努力, __mach_stack_logging_get_frames 函數也可以在同一個標頭檔裡找到. 這個函數的聲明看起來是下面這個樣子:

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
/
這是一個很好的起始點, 但是如果這裡有參數你還不能100%確定這些參數是什麼如何獲得呢?例如, task_t task是什麼?這是一個指明你想要讓這個函數在哪個進程上執行的最基本的參數. 但是如果你不知道這些怎麼辦呢?
使用Google並搜尋包含
mach_stack_logging_get_frames的任何檔案會有很大的協助當你不確定這些事情的時候.
在Google的搜尋結果中, https://llvm.org/svn/llvm-project/lldb/trunk/examples/darwin/heap_find/heap/heap_find.cpp這個網站提供了這個函數第一個參數的釋義.
這個檔案中包含下面的代碼:

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函數中的task_t參數可以輕鬆的擷取到代表當前進程的任務.

測試這些函數

為了防止你流下無聊的眼淚, 我已經在APP中實現了__mach_stack_logging_get_frames的邏輯.

圖片.png

希望你的應用程式還在運行. 如果沒有運行, 重新讓APP在MallocStackLogging仍然啟用的狀態下運行.
在Xcode中首先編譯證明你的概念的JIT代碼總是一個好主意, 並且如果它是可以啟動並執行, 然後將它轉移到你的LLDB指令碼中.如果你嘗試先直接在LLDB中寫POC JIT指令碼你將會變得討厭你的生活.相信我.
在Xcode中, 找到stack_logger.cpp檔案.__mach_stack_logging_get_frames是用C++寫的, 因此你需要用C++的代碼來執行它.
這個檔案中僅有的函數是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_taskself; // 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
}
拆解時間到了!

正如你所知, LLDB在執行的時候僅讓你返回一個對象.但是, 作為你自己的一個有創造性的字串理論版本, 可以建立包含任何你希望返回的類型的C結構體.
用這個函式宣告一個上面說的結構體的執行個體.
還記得前面說的mach_task_self引用的內容嗎?全域變數mach_task_self_是調用mach_task_self時的傳回值.
因為你在操作底層的東西, 所以沒有ARC協助你在堆中分配項目. 你正在分配100個mach_vm_address_t的空間, 這些空間足以處理任何棧記錄.
然後執行__mach_stack_logging_get_frames. 如果有任何可用的棧記錄資訊, LLDBStackAddress結構體的將會被添加到addresses數組中.
列印出我們發現的所有地址.
最後, 你建立的mach_vm_address_t被釋放掉了.
LLDB 測試

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

(lldb) search RayView -b
搜尋指令碼將會枚舉堆中包含的所有對象. 這個命令會捕獲當前依然存在的所有的RayView執行個體.
-b選項將會給你--brief功能, 釋放類的description或者debugDescription方法. 你將會得到一個記錄Ray Wenderlich的連在你模擬器中出現的次數的變數.
在我的模擬器中有三個很有魔力的Ray Wenderlich的臉, 因此我的到了下面的輸出:

(lldb) search RayView -b
RayView [0x00007fa838414330]
RayView
[0x00007fa8384125f0]
RayView * [0x00007fa83860c000]
抓取上面的任意一個地址然後在trace_address函數中執行:

(lldb) po trace_address(0x00007fa838414330)
你的輸出看起來應該是下面這個樣子:

[0] 4533269637
[1] 4460190625
[2] 4460232164
[3] 4454012240
[4] 4478307618
[5] 4482741703
[6] 4478307618
[7] 4479898204
[8] 4479898999
[9] 4479899371
...
這裡是對象被建立時執行的代碼的實際地址. 用image lookup命令驗證一下第一個地址在記憶體中儲存的是代碼:

(lldb) image lookup -a 4533269637
你將會得到這個函數的詳細資料:

Address: libsystem_malloc.dylib[0x000000000000f485]
(libsystem_malloc.dylib.TEXT.text + 56217)
Summary: libsystem_malloc.dylib`calloc + 30
不只有一種方法可以查看記憶體位址的內容. 複製第三幀的地址容納後使用SBAddress取出這個地址裡的資訊:

(lldb) script print lldb.SBAddress(4454012240, lldb.target)
你將會得到棧中的第3幀, 想下面這樣:

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

你將會再一次使用lldb.value類來解析執行這個函數時返回的這個C結構體.
在trace_address函數結束的地方設定一個GUI結構體:

圖片.png

用LLDB執行同樣的函數, 但是尊重斷點, 記得用你的RayView執行個體的地址替換替換下面的地址:

(lldb) e -lobjc++ -O -i0 -- trace_address(0x00007fa838414330)
執行將會在trace_address的最後一行停下來.你瞭解這個操作. 抓取LLDBStackAddress C結構體的引用, stackaddress.

(lldb) script print lldb.frame.FindVariable(‘stackaddress‘)
如果成功了, 你將會得到stackaddress變數的組成格式:

(LLDBStackAddress) stackaddress = {
addresses = 0x00007fa838515cd0
count = 25 }
將lldb.value函數的傳回值賦給a引用:

(lldb) script a = lldb.value(lldb.frame.FindVariable(‘stackaddress‘))
確保a是一個有效值的:

(lldb) script print a
現在你可以很簡單的引用聲明在lldb.value函數中LLDBStackAddress結構體上的變數. 在LLDB中輸入下面的內容:

(lldb) script print a.count
你將會得到棧幀的數量:

(uint32_t) count = 25
LLDBStackAddress結構體中addresses數組是什麼樣的呢?

(lldb) script print a.addresses[0]
那是第一幀的記憶體位址. 第三幀中的generateRayViewTapped:函數是什麼樣的呢?

(lldb) script print a.addresses[3]
你將會得到一些類似下面的輸出:

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

將數字傳入到棧幀中

在本章的starter目錄下的msl.py指令碼是用來記錄malloc日誌的.你已經在前面的"Setting upthe scripts"章節中安裝了msl.py指令碼.
不幸的是, 現在這個指令碼能做的還很少, 因為它還不能產生任何輸出. 是時候來改變一下它了.
用你最喜歡的編輯器開啟~/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()
這些邏輯對你來說應該並不陌生, 因為它是命令起始部分必要的序文. 唯一有趣的一點是你選擇忽略有時在shlex.spli中使用的posix=False參數. 這裡不需要提供這個參數, 因為這個命令不會處理任何古怪的反斜線和破折號字元. 這就意味著從options和args變數中剖析的輸出會更加清晰.
現在你已經有了最基本的指令碼, 你只需要按照下面的方式正確的完善剩餘的指令碼:

#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)
這裡有幾個有趣的點:

我使用了generateScript函數, 這個函數返回了一個跟trace_address函數一樣的同樣的包含著粗糙地代碼的字串.
執行這個代碼. 你知道浙江返回一個SBValue.
明智的檢查一下EvaluateExpression是否失敗. 如果失敗了, 提取出錯誤資訊並儘早退出.
這個for迴圈會遍曆val對象中的記憶體位址, 這會輸出script的代碼, 並將他們放到地址清單中.
現在地址已經被放到了一個列表中, 你通過那個列表來預定義將要處理的函數. 這將會返回將要輸出的棧記錄字串.
最後, 你手動的分配記憶體, 我相信你是一個好的記憶體管理能收並且總是在後面清空分配的記憶體. 你已經寫完了記憶體泄露指令碼的大部分內容, 但是現在你需要學習更進階的知識, 是時候做一些正確的事情並free之前分配的記憶體.
回到Xcode的LLDB控制台中然後重新載入指令碼:
(lldb) reload_script
假如你沒有遇到任何錯誤, 用LLDB的search命令抓取一個RayView的引用:

(lldb) search RayView -b
只是為了介紹一下, 這裡還有另外一種實現方式搜尋所有ShadesOfRay模組中的子類:

(lldb) search UIView -m ShadesOfRay -b
如果你有了一個RayView的子類引用, 在這個引用上運行你最新建立的msl命令, 就像下面這樣:

(lldb) msl 0x00007fa838414330
在Xcode中你將會得到期望的輸出!

frame #0 : 0x11197d485 libsystem_malloc.dylibcalloc + 30<br/>frame #1 : 0x10d3cbba1 libobjc.A.dylibclass_createInstance + 85
frame #2 : 0x10d3d5de4 libobjc.A.dylib_objc_rootAlloc + 42<br/>frame #3 : 0x10cde7550 ShadesOfRay-[ViewController
generateRayViewTapped:] + 64
frame #4 : 0x10e512d22 UIKit`-[UIApplication
sendAction:to:from:forEvent:] + 83
歡呼吧!你已經建立了一個可以追蹤一個對象的棧記錄的指令碼.現在是時候給指令碼一些很酷的選項將代碼升級一下了!

Swift對象的棧記錄

好了--我知道你想讓我聊一些swift的代碼.你同樣會學習一個swift的例子.
50 Shades of RayAPP 裡麵包含的是一個swift模組, 名字叫做SomeSwiftModule. 在這個模組中有一個SomeSwiftCode的類有一個獨特的靜態變數.
SomeSwiftCode.swift中的代碼非常簡單:

public final class SomeSwiftCode {
private init() {}
static let shared = SomeSwiftCode()
}
你將會用LLDB來調用這個方法並且檢查這個很熟被建立的位置的棧記錄.
首先, 你需要匯入你的Swift模組!在LLD不中輸入下面內容:

(lldb) e -lswift -O -- import SomeSwiftModule
讓面的命令運行成功之後你不會得到任何輸出.
在LLDB中, 可以用下面的命令訪問這個靜態變數:

(lldb) e -lswift -O -- SomeSwiftCode.shared
你會得到這個對象的記憶體位址:

<SomeSwiftCode: 0x600000033640>
現在你要將這個記憶體位址傳給msl命令. 只需要從輸出中簡單的複製粘貼一下真是太簡單. 用search命令替代並搜尋SwiftObject的子類:

(lldb) search SwiftObject
你將會得到一些類似下面的輸出:

<__NSArrayM 0x6000004578b0>(
SomeSwiftModule.SomeSwiftCode
)
再說一次, Swift嘗試隱藏description中的指標. 那是swift魔法的一部分.
在search命令中的最後用--brief (-b)選項來抓取那個執行個體並且忽略對象的description方法.

(lldb) search SwiftObject -b
這將會抓取被打亂的名字, 但是在記憶體中它們是同樣的引用.

_TtC15SomeSwiftModule13SomeSwiftCode * [0x0000600000033640]
將這個地址傳給msl命令:

(lldb) msl 0x0000600000033640
你將會得到期望的棧記錄.

圖片.png
標記的那一幀清晰的指明了你在LLDB中調用這個靜態變數的位置. 你的與我的可能有所不同.
讓我們轉到我想簡單討論一下的最後一個話題: 當你在LLDB指令碼中建立功能的時候如何編譯這些指令碼以便你"不需要重複你做過的事情".

只運行Python代碼

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

圖片.png

好. Ray贊同了.
是時候嘗試一下turn_on_stack_logging函數了. 構建並運行應用程式. 正如你在前幾章中找到的, "Stripped 50 Shades of Ray" scheme 會精簡掉可執行檔中的內容因此這裡沒有可用的調試資訊.當你使用msl命令的時候記住那些描述.
如果應用程式運行起來了, 點擊Generate a Ray!按鈕來建立一個新的RayView執行個體.因為MallocStackLogging是禁用的, 讓我們看一下會發生什麼...
暫停執行並在LLDB中輸入下面的內容搜尋所有的RayViews:
(lldb) search RayView -b
你將會得到一些類似下面的輸出:

RayView * [0x00007fc23eb00620]
看一下msl用這個地址是否生效:

(lldb) msl 0x00007fc23eb00620
什麼都沒有. 與我們預料的結果一樣, 因為那個環境變數沒有被應用到這個進程上. 時候會回過頭來並看看turn_on_stack_logging做了什麼事情. 在LLDB中輸入下面的內容:

(lldb) po turn_on_stack_logging(1)
你將會得到一些與你將MallocStackLogging環境變數應用到你的進程上之後類似的輸出:

圖片.png
繼續執行並點擊底部的按鈕建立另外一個RayView執行個體.
如果你已經做完了上面的操作, 暫停執行並再次搜尋所有的RayView執行個體.
這一次你將會得到一個新的地址. 希望啟用了stack logging, 你將會得到這個記錄.
複製這個新地址並將它應用到msl命令上:

(lldb) msl 0x00007f8250f0a170
這會給出棧記錄!

圖片.png

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

Ray不喜歡沒有精簡過的函數.
如果你能回想起前面的章節, 你之前建立了一個可符號化棧記錄的sbt命令. 在sbt.py指令碼中, 你建立了一個帶有一個數字數組和SBTarget參數的processStackTraceStringFromAddresses函數. 這個函數為棧記錄返回了一個有可符號化潛力的字串.
你已經完成了這個函數最難的部分, 所以為什麼不將這個功能包含在msl.py指令碼裡可選的執行呢?
轉到msl.py函數的最上面的位置, 並添加下面的匯入語句:
import sbt
在msl.py檔案的handle_command函數中, 找到下面的代碼:

retString = sbt.processStackTraceStringFromAddresses(
addresses,
target)
用下面的代碼替換上面的代碼:

if options.resymbolicate:
retString = sbt.processStackTraceStringFromAddresses(
addresses,
target)
else:
retString = processStackTraceStringFromAddresses(
addresses,
target)
你的條件是檢查options.resymbolicate選項.如果為true, 然後調用sbt模組的邏輯來看一下它能否產生一個重新符號化後的函數的字串.
因為你寫那個函數作為一個屬性並且處理一個Python的數字列表, 所以從你的msl指令碼中可以輕鬆的通過這些資訊.
在你測試這個代碼之前, 還有最後一段需要實現.你需要建立一個快捷命令去啟用turn_on_stack_logging.
跳到__lldb_init_module函數中(這個函數仍然在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

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

我們為什麼要學這些?

希望, 這個注意的完整實現, 重新搜尋和已經被驗證過的有用的實現, 甚至可以鼓舞你去創造你自己的指令碼. 在你的[i|mac|tv|watch]OS裝置中的許多架構中還隱藏這許多強大的功能.
你所需要做的就是發現這些隱藏著的寶物並且並將他們開發成瘋狂的商業調試工具, 或者將他們引用到逆向工程中來更好的理解發生了什麼事情.
這裡有一個值得在你實際的iOS裝置中去瀏覽的目錄列表:
? /Developer/
? /usr/lib/
? /System/Library/PrivateFrameworks/
繼續前行, 去實現我們的夢想吧!

Advanced+Apple+Debugging(14)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.