第二十四章:SB Examples, Malloc Logging

作为这一部分的最后一章, 你将会经过同样的步骤, 我自己理解当一个对象被创建的到时候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.

图片.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实例出现在了模拟器中), 执行下面的步骤:

  1. 选择Xcode中位于LLDB控制台顶部的Debug Memory Graph.
  2. 选择左侧面板中的Show the Debug navigator.
  3. 在左侧面板的底部选择Show only content from workspace.
  4. 选择RayView的引用.
  5. 在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.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的内容. 这个搜索看起来就是下面这个样子:

图片.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_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在执行的时候仅让你返回一个对象.但是, 作为你自己的一个有创造性的字符串理论版本, 可以创建包含任何你希望返回的类型的C结构体.
  2. 用这个函数声明一个上面说的结构体的实例.
  3. 还记得前面说的mach_task_self引用的内容吗?全局变量mach_task_self_是调用mach_task_self时的返回值.
  4. 因为你在操作底层的东西, 所以没有ARC帮助你在堆中分配项目. 你正在分配100个mach_vm_address_t的空间, 这些空间足以处理任何栈记录.
  5. 然后执行__mach_stack_logging_get_frames. 如果有任何可用的栈记录信息, LLDBStackAddress结构体的将会被添加到addresses数组中.
  6. 打印出我们发现的所有地址.
  7. 最后, 你创建的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参数. 这里不需要提供这个参数, 因为这个命令不会处理任何古怪的反斜杠和破折号字符. 这就意味着从optionsargs变量中剖析的输出会更加清晰.
现在你已经有了最基本的脚本, 你只需要按照下面的方式正确的完善剩余的脚本:

#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函数, 这个函数返回了一个跟trace_address函数一样的同样的包含着粗糙地代码的字符串.
  2. 执行这个代码. 你知道浙江返回一个SBValue.
  3. 明智的检查一下EvaluateExpression是否失败. 如果失败了, 提取出错误信息并尽早退出.
  4. 这个for循环会遍历val对象中的内存地址, 这会输出script的代码, 并将他们放到地址列表中.
  5. 现在地址已经被放到了一个列表中, 你通过那个列表来预定义将要处理的函数. 这将会返回将要输出的栈记录字符串.
  6. 最后, 你手动的分配内存, 我相信你是一个好的内存管理能收并且总是在后面清空分配的内存. 你已经写完了内存泄露脚本的大部分内容, 但是现在你需要学习更高级的知识, 是时候做一些正确的事情并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 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/
继续前行, 去实现我们的梦想吧!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容