作為這一部分的最后一章, 你將會經(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
.
在你啟用了這個環(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
按鈕.
當(dāng)一個新的
Ray
被創(chuàng)建之后(也就是說, 你看到了一個新的英俊異常的Ray Wenderlich
實例出現(xiàn)在了模擬器中), 執(zhí)行下面的步驟:
- 選擇Xcode中位于LLDB控制臺頂部的
Debug Memory Graph
. - 選擇左側(cè)面板中的
Show the Debug navigator
. - 在左側(cè)面板的底部選擇
Show only content from workspace
. - 選擇
RayView
的引用. - 在Xcode右側(cè)的面板中, 確保
Show the Memory Inspector
處于選中狀態(tài).
在你做完了上面的操作之后, 你將會通過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: 是!
在
MallocStackLogging
變量仍然被勾選的情況下愛構(gòu)建并運行這個程序. 從輸出中, 你可以看到進(jìn)程啟動的地方, 下面是找到的存在MallocStackLogging
的代碼.
將你的符號斷點修改為當(dāng)程序找到
MallocStackLogging
環(huán)境變量的時候只提取棧記錄:
? Symbol: getenv
? Condition: ((int)strcmp("MallocStackLogging", $arg1) == 0)
? Action: bt
? Automatically continue after evaluating actions: 是!
在你修改完符號斷點以后, 重新運行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)容. 這個搜索看起來就是下面這個樣子:
在我寫這本書的時候, 我從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
的邏輯.
希望你的應(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
}
拆解時間到了!
- 正如你所知, LLDB在執(zhí)行的時候僅讓你返回一個對象.但是, 作為你自己的一個有創(chuàng)造性的字符串理論版本, 可以創(chuàng)建包含任何你希望返回的類型的C結(jié)構(gòu)體.
- 用這個函數(shù)聲明一個上面說的結(jié)構(gòu)體的實例.
- 還記得前面說的
mach_task_self
引用的內(nèi)容嗎?全局變量mach_task_self_
是調(diào)用mach_task_self
時的返回值. - 因為你在操作底層的東西, 所以沒有ARC幫助你在堆中分配項目. 你正在分配100個
mach_vm_address_t
的空間, 這些空間足以處理任何棧記錄. - 然后執(zhí)行
__mach_stack_logging_get_frames
. 如果有任何可用的棧記錄信息,LLDBStackAddress
結(jié)構(gòu)體的將會被添加到addresses
數(shù)組中. - 打印出我們發(fā)現(xiàn)的所有地址.
- 最后, 你創(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)體:
用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ù), 因為這個命令不會處理任何古怪的反斜杠和破折號字符. 這就意味著從options
和args
變量中剖析的輸出會更加清晰.
現(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)
這里有幾個有趣的點:
- 我使用了
generateScript
函數(shù), 這個函數(shù)返回了一個跟trace_address
函數(shù)一樣的同樣的包含著粗糙地代碼的字符串. - 執(zhí)行這個代碼. 你知道浙江返回一個
SBValue
. - 明智的檢查一下
EvaluateExpression
是否失敗. 如果失敗了, 提取出錯誤信息并盡早退出. - 這個for循環(huán)會遍歷
val
對象中的內(nèi)存地址, 這會輸出script
的代碼, 并將他們放到地址列表中. - 現(xiàn)在地址已經(jīng)被放到了一個列表中, 你通過那個列表來預(yù)定義將要處理的函數(shù). 這將會返回將要輸出的棧記錄字符串.
- 最后, 你手動的分配內(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 Ray
APP 里面包含的是一個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
你將會得到期望的棧記錄.
標(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中沒有勾選.
好. 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)程上之后類似的輸出:
繼續(xù)執(zhí)行并點擊底部的按鈕創(chuàng)建另外一個RayView實例.
如果你已經(jīng)做完了上面的操作, 暫停執(zhí)行并再次搜索所有的RayView
實例.
這一次你將會得到一個新的地址. 希望啟用了stack logging
, 你將會得到這個記錄.
復(fù)制這個新地址并將它應(yīng)用到msl
命令上:
(lldb) msl 0x00007f8250f0a170
這會給出棧記錄!
這是一個驚喜!你可以在將要監(jiān)測任何
allocation
或者deallocation
事件之前啟用malloc
日志而不需要重啟你的進(jìn)程.等一下!稍等幾秒鐘....這里有一個被精簡過的符號.
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
面對這個如此漂亮的棧記錄我高興的簡直要哭了!終于可以松口氣了!
我們?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)我們的夢想吧!