作为这一部分的最后一章, 你将会经过同样的步骤, 我自己理解当一个对象被创建的到时候MallocStackLogging
环境变量通常是怎样得到堆栈记录的.
从这里开始, 你将会创建一个可以给你一个对象在内存中被创建或者销毁的堆栈记录的自定义的LLDB命令--甚至在堆栈记录已经离开调试器很久之后获取到堆栈记录.
知道在你的程序中一个对象是在哪里创建的堆栈记录不仅有利于逆向工程, 而且在你日复一日的调试工作中也非常有帮助.当一个进程崩溃的时候, 在你的进程结束之前知道那一块内存的历史和发生的任何allocation
或者deallocation
事件就显得极为重要.
这是脚本使用与栈帧相关的逻辑的另外一个例子, 但是这一章将会聚焦于如何查找循环引用, 学习, 然后实现一个非常强大的自定义命令.
设置脚本
在本章中你有一组脚本来使用(和实现). 让我们挨个看一下你如何使用他们:
• msl.py: 这是在本章中你将要使用的一个(MallocStackLogging的缩写)的命令脚本. 这里有一个逻辑的基本范围.
• lookup.py: 等一下--你已经创建了这个命令, 对吧?是的, 但是我将会给你我自己的在丑陋的代买中添加了一组额外的选项look up
命令的版本. 你将会使用这些命令中的一个来过滤出一个进程中你搜索的那个模块.
• sbt.py: 这个命令将会追踪无符号化的符号, 并且将它符号化.你在之前的章节中创建了这个脚本, 你将会在本章的末尾经常用到. 如果你跳过了之前的章节, 你可以在本章的资源文件夹里面找到它.
• search.py: 这个命令将会枚举堆中的每一个对象并且搜索一个特定的子类. 这是快速抓取一个特定类的实例的引用的非常方便的命令.
注意:这些脚本来自[ https://github.com/DerekSelander/lldb]( 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
.
在你启用了这个环境变量之后, 构建
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
按钮.
当一个新的
Ray
被创建之后(也就是说, 你看到了一个新的英俊异常的Ray Wenderlich
实例出现在了模拟器中), 执行下面的步骤:
- 选择Xcode中位于LLDB控制台顶部的
Debug Memory Graph
. - 选择左侧面板中的
Show the Debug navigator
. - 在左侧面板的底部选择
Show only content from workspace
. - 选择
RayView
的引用. - 在Xcode右侧的面板中, 确保
Show the Memory Inspector
处于选中状态.
在你做完了上面的操作之后, 你将会通过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: 是!
在
MallocStackLogging
变量仍然被勾选的情况下爱构建并运行这个程序. 从输出中, 你可以看到进程启动的地方, 下面是找到的存在MallocStackLogging
的代码.
将你的符号断点修改为当程序找到
MallocStackLogging
环境变量的时候只提取栈记录:
• Symbol: getenv
• Condition: ((int)strcmp("MallocStackLogging", $arg1) == 0)
• Action: bt
• Automatically continue after evaluating actions: 是!
在你修改完符号断点以后, 重新运行APP.
在控制台中你将得到一些栈记录的信息. 找出最靠前的栈帧的内容:
* 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的作者, 我很有可能会检查一个有条件的环境变量看一下当我的代码初始化的时候是否可以正常运行.这些栈帧看起来做了同样的事情, 加上模块的名字, 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中搜索一下并看看能找到哪些信息.
谷歌中的JIT函数
谷歌一下任何包含turn_on_stack_logging
的内容. 这个搜索看起来就是下面这个样子:
在我写这本书的时候, 我从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
的逻辑.
希望你的应用程序还在运行. 如果没有运行, 重新让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_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在执行的时候仅让你返回一个对象.但是, 作为你自己的一个有创造性的字符串理论版本, 可以创建包含任何你希望返回的类型的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结构体:
用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.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
欢呼吧!你已经创建了一个可以追踪一个对象的栈记录的脚本.现在是时候给脚本一些很酷的选项将代码升级一下了!
Swift对象的栈记录
好了--我知道你想让我聊一些swift的代码.你同样会学习一个swift的例子.
50 Shades of Ray
APP 里面包含的是一个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
你将会得到期望的栈记录.
标记的那一帧清晰的指明了你在LLDB中调用这个静态变量的位置. 你的与我的可能有所不同.
让我们转到我想简单讨论一下的最后一个话题: 当你在LLDB脚本中创建功能的时候如何编译这些脚本以便你"不需要重复你做过的事情".
只运行Python代码
停止运行这个APP! 在scheme列表中, 选择Stripped 50 Shades of Ray
这个scheme.
确保MallocStackLogging
环境变量在Stripped 50 Shades of Ray
这个scheme中没有勾选.
好. 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
环境变量应用到你的进程上之后类似的输出:
继续执行并点击底部的按钮创建另外一个RayView实例.
如果你已经做完了上面的操作, 暂停执行并再次搜索所有的RayView
实例.
这一次你将会得到一个新的地址. 希望启用了stack logging
, 你将会得到这个记录.
复制这个新地址并将它应用到msl
命令上:
(lldb) msl 0x00007f8250f0a170
这会给出栈记录!
这是一个惊喜!你可以在将要监测任何
allocation
或者deallocation
事件之前启用malloc
日志而不需要重启你的进程.等一下!稍等几秒钟....这里有一个被精简过的符号.
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
面对这个如此漂亮的栈记录我高兴的简直要哭了!终于可以松口气了!
我们为什么要学这些?
希望, 这个注意的完整实现, 重新搜索和已经被验证过的有用的实现, 甚至可以鼓舞你去创造你自己的脚本. 在你的[i|mac|tv|watch]OS
设备中的许多框架中还隐藏这许多强大的功能.
你所需要做的就是发现这些隐藏着的宝物并且并将他们开发成疯狂的商业调试工具, 或者将他们引用到逆向工程中来更好的理解发生了什么事情.
这里有一个值得在你实际的iOS设备中去浏览的目录列表:
• /Developer/
• /usr/lib/
• /System/Library/PrivateFrameworks/
继续前行, 去实现我们的梦想吧!