美文网首页
(十五)SB示例 Malloc日志

(十五)SB示例 Malloc日志

作者: 收纳箱 | 来源:发表于2020-03-10 23:04 被阅读0次

    1. Malloc日志

    下面我们将了解在创建对象时,MallocStackLogging如何获取堆栈跟踪。

    我们将创建一个自定义的LLDB命令。该命令为我们提供对象何时在内存中分配或释放的堆栈跟踪,即使堆栈跟踪早已从调试器中消失也可以。

    了解对象在程序中创建位置的堆栈跟踪不仅对逆向工程有用,而且在日常调试中也有很好的用例。当一个进程崩溃时,了解该内存的历史记录以及崩溃前发生的任何分配或解除分配事件,是非常有帮助的。

    1.1 脚本配置

    我们有两个脚本要实现。让我们逐一了解它们以及如何使用它们:

    • msl.py:这是命令(MallocStackLogging的缩写)是我们将在这里使用的脚本。里面有一个基本的逻辑框架。
    • lookup.py: 在之前的版本上添加了一些附加选项。将使用其中一个选项把搜索限制在进程中的特定模块。
    • sbt.py:使用非符号化符号进行回溯,并对其进行符号化。
    • search.py:遍历堆中的所有对象并搜索特定子类。用于快速获取对特定类实例的引用。

    1.2 MallocStackLogging解释

    如果不熟悉MallocStackLogging环境变量,下面将对其进行描述,并展示它的典型用法。

    MallocStackLogging环境变量被传递到进程中并设置为true时,它将监视堆上内存的分配和释放。

    Malloc Stack

    启用这个选项后,运行项目,就可以看到一些打印信息。

    ShadesOfRay(62232,0x105de6dc0) malloc: stack logs being written into /tmp/stack-logs.62232.1020d0000.ShadesOfRay.EO2R9T.index
    ShadesOfRay(62232,0x105de6dc0) malloc: recording malloc and VM allocation stacks to disk using standard recorder
    ShadesOfRay(62232,0x105de6dc0) malloc: process 62192 no longer exists, stack logs deleted from /tmp/stack-logs.62192.1043e3000.ShadesOfRay.KeDSdc.index
    

    点击Generate a Ray按钮。

    点击按钮

    点击按钮后,创建了一个图片视图。请执行以下步骤:

    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
      步骤

    在做完了上面的操作之后,我们就可以通过Xcode中的Backtrace部分得到RayView实例是在哪里被创建的精确栈记录。

    在getenv中追踪

    我们可以跟踪一个实例化的对象的调用栈,但是我们要比苹果做得更好。

    我们的命令将能够通过LLDB随意打开MallocStackLogging功能。这意味着我们不必依赖环境变量。这还有一个额外的好处,如果在调试会话期间忘记打开进程,则无需重新启动进程。

    MallocStackLogging是传递到进程中的环境变量。这意味着C getenv函数可能用于检查是否提供了此参数。如果提供了,则执行相应的逻辑。

    当进程启动时,需要使用getenv查看的所有配置项。创建一个符号断点,以便在调用getenv时查看char*参数。

    符号断点

    这个会输出很多环境变量,我们进行下一步。限制断点条件为((int)strcmp("MallocStackLogging", $arg1) == 0),并用bt打印调用栈。

    符号断点
     * 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
    

    libsystem_malloc.dylib这个模块感觉和MallocStackLogging有很大关系。使用lookup命令查看libsystem_malloc.dylib模块实现的所有方法。在iOS 13上, 命中了461条。实在太多了,我们只关心log相关的方法:

    (lldb) lookup . -m libsystem_malloc.dylib
    (lldb) lookup (?i)log -m libsystem_malloc.dylib
    ****************************************************
    25 hits in: libsystem_malloc.dylib
    ****************************************************
    purgeable_log
    szone_log
    nanov2_log
    default_zone_log
    nano_log
    __mach_stack_logging_copy_uniquing_table
    __mach_stack_logging_enumerate_records
    __mach_stack_logging_frames_for_uniqued_stack
    __mach_stack_logging_get_frames
    __mach_stack_logging_get_frames_for_stackid
    __mach_stack_logging_set_file_path
    __mach_stack_logging_stackid_for_vm_region
    __mach_stack_logging_start_reading
    __mach_stack_logging_stop_reading
    __mach_stack_logging_uniquing_table_copy_from_serialized
    __mach_stack_logging_uniquing_table_read_stack
    __mach_stack_logging_uniquing_table_release
    __mach_stack_logging_uniquing_table_retain
    __mach_stack_logging_uniquing_table_serialize
    __mach_stack_logging_uniquing_table_sizeof
    __stack_logging_early_finished
    malloc_register_stack_logger
    malloc_zone_log
    turn_off_stack_logging
    turn_on_stack_logging
    

    现在只有25条了,其中有3条比较有意思的函数:

    __mach_stack_logging_get_frames
    turn_off_stack_logging
    turn_on_stack_logging
    

    看名字就知道turn_on_stack_logging__mach_stack_logging_get_frames值得我们研究。

    stack_logging.h网站中的头文件中可以看到下面的代码:

    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函数期望传入一个intstack_logging_mode_type的参数。其中stack_logging_mode_all选项的是1

    我们再看看另外一个函数__mach_stack_logging_get_framesstack_logging.h也包含了这个函数的声明。

    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 */
    

    那这些参数,比如task_t task是什么意思,又是怎么获得的呢?在Google中搜索之后,从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参数。

    1.4 测试一下这些函数

    在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
      mach_vm_address_t address = (mach_vm_address_t)addr;
      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
      // 6
      for (int i = 0; i < stackaddress.count; i++) {
        printf("[%d] %llu\n", i, stackaddress.addresses[i]);
      }
      free(stackaddress.addresses);  // 7
    }
    
    1. LLDB在执行的时候允许返回一个对象。这里我们创建了一个C结构体。
    2. 声明一个LLDBStackAddress结构体的实例。
    3. 全局变量mach_task_self_是调用mach_task_self时的返回值。
    4. 分配100个mach_vm_address_t
    5. 执行__mach_stack_logging_get_frames。如果有任何可用的栈记录信息,LLDBStackAddress结构体addresses数组中会有很多地址信息。
    6. 打印出__mach_stack_logging_get_frames返回的所有地址。
    7. 最后, 谁申请谁释放。刚刚申请的100个mach_vm_address_t,需要被释放掉。
    LLDB测试

    点击一下**Generate a Ray! **按钮,然后暂停运行:

    (lldb) search RayView -b
    RayView * [0x00007fba7540e570]
    
    (lldb) po trace_address(0x00007fba7540e570)
    [0] 140734573540504
    [1] 140734573542215
    [2] 140734556578141
    [3] 4366603222
    [4] 140734401953791
    [5] 140734391624723
    [6] 140734391624321
    ...
    
    (lldb) image lookup -a 140734573540504
          Address: libsystem_malloc.dylib[0x000000000000f498] (libsystem_malloc.dylib.__TEXT.__text + 60164)
          Summary: libsystem_malloc.dylib`malloc_zone_calloc + 139
    

    还有一种解析内存地址的办法。复制第三帧的地址,用SBAddress方法来获取地址对应的信息。

    (lldb) script print(lldb.SBAddress(4366603222, lldb.target))
    ShadesOfRay`-[ViewController generateRayViewTapped:] + 54 at ViewController.m:41:18
    
    用lldb.value浏览C数组

    trace_address函数结束的地方设置一个断点。

    (lldb) e -lobjc++ -O -i0 -- trace_address(0x00007fba7540e570)
    (lldb) script print(lldb.frame.FindVariable('stackaddress'))
    (LLDBStackAddress) stackaddress = {
      addresses = 0x00007fba754143c0
      count = 27
    }
    (lldb) script a = lldb.value(lldb.frame.FindVariable('stackaddress'))
    (lldb) script print(a)
    (LLDBStackAddress) stackaddress = {
      addresses = 0x00007fba754143c0
      count = 27
    }
    (lldb) script print(a.count)
    (uint32_t) count = 27
    (lldb) script print(a.addresses[0])
    (mach_vm_address_t) [0] = 140734573540504
    (lldb) script print(a.addresses[3])
    (mach_vm_address_t) [3] = 4366603222
    (lldb) script print(lldb.SBAddress(4366603222, lldb.target))
    ShadesOfRay`-[ViewController generateRayViewTapped:] + 54 at ViewController.m:41:18
    
    将数字传入到栈帧中

    打开~/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.split中这里没有使用posix=False。因为这个命令不会处理任何反斜杠和破折号字符。

    #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对象中的内存地址放入addresses列表。
    5. 通过addresses列表来对应函数名,返回将要输出的栈记录字符串。
    6. 脚本里面申请了内存,最后用完后需要我们手动释放这部分内存。
    (lldb) reload_script
    (lldb) search RayView -b
    RayView * [0x00007fba7540e570]
    RayView * [0x00007fba7540f590]
    
    (lldb) msl 0x00007fba7540e570
    frame #0 : 0x7fff52437498 libsystem_malloc.dylib`malloc_zone_calloc + 139
    frame #1 : 0x7fff52437b47 libsystem_malloc.dylib`calloc + 24
    frame #2 : 0x7fff5140a15d libobjc.A.dylib`_objc_rootAllocWithZone + 37
    frame #3 : 0x1044513d6 ShadesOfRay`-[ViewController generateRayViewTapped:] + 54
    frame #4 : 0x7fff48093fff UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 83
    ...
    

    1.6 Swift对象的栈记录

    50 Shades of Ray里面包含有一个名字叫做SomeSwiftModule的Swift模块。模块中,有一个SomeSwiftCode的类,声明了一个单例。

    public final class SomeSwiftCode {
        private init() {}
        static let shared = SomeSwiftCode()
    }
    
    (lldb) e -lswift -O -- import SomeSwiftModule
    (lldb) e -lswift -O -- SomeSwiftCode.shared
    <SomeSwiftCode: 0x600003267c40>
    
    (lldb) search SomeSwiftModule.SomeSwiftCode
    <__NSArrayM 0x600003ca8210>(
    SomeSwiftModule.SomeSwiftCode
    )
    
    (lldb) search SomeSwiftModule.SomeSwiftCode -b
    _TtC15SomeSwiftModule13SomeSwiftCode * [0x0000600003267c40]
    

    Swifth会试图在description中隐藏指针。在search命令中使用--brief(-b)选项使用我们的方法获取实例信息。之后使用msl查看调用栈:

    (lldb) msl 0x0000600003267c40
    frame #0 : 0x7fff52437339 libsystem_malloc.dylib`malloc_zone_malloc + 140
    frame #1 : 0x7fff52437ac4 libsystem_malloc.dylib`malloc + 21
    frame #2 : 0x104a47af9 libswiftCore.dylib`swift_slowAlloc + 25
    frame #3 : 0x104a47b74 libswiftCore.dylib`_swift_allocObject_(swift::TargetHeapMetadata<swift::InProcess> const*, unsigned long, unsigned long) + 20
    frame #4 : 0x10476e9a4 SomeSwiftModule`SomeSwiftModule.SomeSwiftCode.__allocating_init() -> SomeSwiftModule.SomeSwiftCode + 52
    ...
    

    1.7 只运行Python代码

    我们现在使用Stripped 50 Shades of Ray这个scheme,确保了取消Malloc Stack选项,重新运行。

    scheme

    我们来尝试一下turn_on_stack_logging函数。运行程序,点击一下**Generate a Ray! **按钮,然后暂停运行:

    (lldb) search RayView -b
    RayView * [0x00007fc413d0cd80]
    (lldb) msl 0x00007fc413d0cd80
    

    什么都没有。因为MallocStackLogging环境变量没有被应用到这个进程上。在LLDB中输入:

    (lldb) po turn_on_stack_logging(1)
    ShadesOfRay(64227,0x115dcddc0) malloc: stack logs being written into /tmp/stack-logs.64227.1122eb000.ShadesOfRay.egknZC.index
    ShadesOfRay(64227,0x115dcddc0) malloc: recording malloc and VM allocation stacks to disk using standard recorder
    0x0000000000000001
    

    我们得到了一些与将MallocStackLogging环境变量开启时类似的输出。继续运行,点击一下**Generate a Ray! **按钮,之后暂停。

    (lldb) search RayView -b
    RayView * [0x00007fc413d0cd80]
    RayView * [0x00007fc413d15570]
    (lldb) msl 0x00007fc413d15570
    frame #0 : 0x7fff52437498 libsystem_malloc.dylib`malloc_zone_calloc + 139
    frame #1 : 0x7fff52437b47 libsystem_malloc.dylib`calloc + 24
    frame #2 : 0x7fff5140a15d libobjc.A.dylib`_objc_rootAllocWithZone + 37
    frame #3 : 0x10b7753d6 ShadesOfRay`___lldb_unnamed_symbol4$$ShadesOfRay + 54
    frame #4 : 0x7fff48093fff UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 83
    ...
    

    注意到我们的第三帧是stripped的!

    frame #3 : 0x10b7753d6 ShadesOfRay`___lldb_unnamed_symbol4$$ShadesOfRay + 54
    

    但是我们的sbt中的processStackTraceStringFromAddresses是有解析它的能力的。在msl.py中先import sbt,并在handle_command函数中替换retString = processStackTraceStringFromAddresses(addresses, target)为:

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

    在测试代码之前,我们还需要创建一个快捷命令来启用turn_on_stack_logging。找到文件中的__lldb_init_module函数,然后添加下面的代码:

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

    这这个命令声明了一个快捷命令enable_logging来打开malloc栈日志。

    实验一下。🎉🎉🎉

    lldb) reload_script
    (lldb) msl 0x00007fbe7e430840 -r
    frame #0 : 0x7fff52437498 libsystem_malloc.dylib`malloc_zone_calloc + 139
    frame #1 : 0x7fff52437b47 libsystem_malloc.dylib`calloc + 24
    frame #2 : 0x7fff5140a15d libobjc.A.dylib`_objc_rootAllocWithZone + 37
    frame #3 : 0x10b7753d6 ShadesOfRay`-[ViewController generateRayViewTapped:] + 54
    frame #4 : 0x7fff48093fff UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 83
    

    相关文章

      网友评论

          本文标题:(十五)SB示例 Malloc日志

          本文链接:https://www.haomeiwen.com/subject/htlvdhtx.html