iOS - 自定义LLDB命令

作者: 一道彩虹飞 | 来源:发表于2018-01-22 21:54 被阅读0次

    命令的作用

    在console中打印出内存地址对象的创建栈,效果如下:

    (lldb) mallocstack 0x115a43740
    frame #0 : 0x1a8a07368 libsystem_malloc.dylib`calloc + 40
    frame #1 : 0x1a7b4445c libobjc.A.dylib`class_createInstance + 76
    frame #2 : 0x1a7b55bb8 libobjc.A.dylib`_objc_rootAlloc + 52
    frame #3 : 0x10183dd90 TestApp`-[HTYBottomView buttonImage] + 80
    frame #4 : 0x10183a348 TestApp`-[HTYBottomView setupView] + 232
    frame #5 : 0x10183a1ec TestApp`-[HTYBottomView initWithCoder:] + 164
    frame #6 : 0x18c5d3fb4 UIKit`-[UIClassSwapper initWithCoder:] + 248
    frame #7 : 0x18c771d24 UIKit`UINibDecoderDecodeObjectForValue + 680
    frame #8 : 0x18c771a64 UIKit`-[UINibDecoder decodeObjectForKey:] + 104
    

    例子适用场景

    有时候在console中会看到一些直接用内存地址表示的对象。举个例子,像这些layout warnings

    2017-07-22 09:50:16.832586+0800 TestApp[20583:2520440] [LayoutConstraints] Unable to simultaneously satisfy constraints.
    2017-07-22 09:55:45.391041+0800 TestApp[20813:2526791] [LayoutConstraints] Unable to simultaneously satisfy constraints.
        Probably at least one of the constraints in the following list is one you don't want. 
        Try this: 
            (1) look at each constraint and try to figure out which you don't expect; 
            (2) find the code that added the unwanted constraint or constraints and fix it. 
    (
        "<MASLayoutConstraint:0x1c44b0da0 UIImageView:0x115a43740.width == 44>",
        "<MASLayoutConstraint:0x1c02a90c0 UIButton:0x115b4ed90.left == HTYBottomView:0x115b4dd80.left + 130>",
        "<MASLayoutConstraint:0x1c02a8fa0 UIButton:0x115b4ed90.right == HTYBottomView:0x115b4dd80.right>",
        "<MASLayoutConstraint:0x1c44ace40 UIImageView:0x115a43740.left == HTYBottomView:0x115b4dd80.left + 16>",
        "<MASLayoutConstraint:0x1c44b0ec0 UIImageView:0x115a43740.centerX == HTYBottomView:0x115b4dd80.centerX>"
    )
    
    Will attempt to recover by breaking constraint 
    <MASLayoutConstraint:0x1c44b0da0 UIImageView:0x115a43740.width == 44>
    
    Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
    The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
    

    如果想知道某个内存地址对应的Class和它的创建栈,可以这样做:

    Xcode的方式

    1. 开启Logging Malloc Stack
    2. 开启Xcode的Debug Memory Graph,通过搜索UI控件的内存地址定位到UI Debugger里的UI控件,然后在Backtrace查看到UI控件的创建栈,定位到是哪个UI控件,再哪里创建的。

    通过上面的定位过程,可以解决问题,但是效率不高,步骤比较多。如果可以在console里就直接把内存对象的创建栈打印出来,不就方便多了吗?

    LLDB的方式 - Xcode功能对应的代码实现

    1. 新建Symbolick Breakpoint,把控制Logging Malloc Stack开关的变量打印出来
    Symbol:getenv
    Action:po (char*)$arg1
    Automatically continue after evaluating actions: √
    
    ...
    "MallocStackLogging"
    ...
    

    这样,可以找到MallocStackLogging变量

    2. 新建Symbolick Breakpoint,定位MallocStackLogging变量的相关库
    Symbol:getenv
    Condition:((int)strcmp("MallocStackLogging",$arg1)==0) 
    Action:bt
    Automatically continue after evaluating actions: X
    
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
      * frame #0: 0x0000000117368a26 libsystem_c.dylib`getenv
        frame #1: 0x00000001165e96f3 libobjc.A.dylib`environ_init + 464
        frame #2: 0x00000001165ecb0c libobjc.A.dylib`_objc_init + 32
        frame #3: 0x000000011727287b libdispatch.dylib`_os_object_init + 13
    

    这样,可以找到对应的动态库是libsystem_malloc.dylib

    3. 打印出libsystem_malloc.dylib的所有方法
    (lldb) image dump symtab libsystem_malloc.dylib
    
    ...
    [  254]    254   X Code            0x0000000000015187 0x00000001174a0187 0x00000000000001f7 0x000f0000 __mach_stack_logging_get_frames
    ...
    

    这个,很可疑

    关于__mach_stack_logging_get_frames

    贴到Google,官网的heap.py例子就出来了。

    def dump_stack_history_entries(options, result, addr, history):
        # malloc_stack_entry *get_stack_history_for_address (const void * addr)
        expr_prefix = '''
    typedef int kern_return_t;
    typedef struct $malloc_stack_entry {
        uint64_t address;
        uint64_t argument;
        uint32_t type_flags;
        uint32_t num_frames;
        uint64_t frames[512];
        kern_return_t err;
    } $malloc_stack_entry;
    '''
        single_expr = '''
    #define MAX_FRAMES %u
    typedef unsigned task_t;
    $malloc_stack_entry stack;
    stack.address = 0x%x;
    stack.type_flags = 2;
    stack.num_frames = 0;
    stack.frames[0] = 0;
    uint32_t max_stack_frames = MAX_FRAMES;
    stack.err = (kern_return_t)__mach_stack_logging_get_frames (
        (task_t)mach_task_self(),
        stack.address,
        &stack.frames[0],
        max_stack_frames,
        &stack.num_frames);
    if (stack.num_frames < MAX_FRAMES)
        stack.frames[stack.num_frames] = 0;
    else
        stack.frames[MAX_FRAMES-1] = 0;
    stack''' % (options.max_frames, addr)
    

    其中,这段代码正是我们想要的 - 打印对象的创建栈。

    材料都全了,拼拼凑凑,debug->改->debug->改...,Python脚本就出来啦。把它的path放到~/.lldbinit

    command script import ~/Documents/source_code/lldb/mallocstack.py
    

    然后就可以开始用了。

    (lldb) mallocstack 0x115a43740
    frame #0 : 0x1a8a07368 libsystem_malloc.dylib`calloc + 40
    frame #1 : 0x1a7b4445c libobjc.A.dylib`class_createInstance + 76
    frame #2 : 0x1a7b55bb8 libobjc.A.dylib`_objc_rootAlloc + 52
    frame #3 : 0x10183dd90 TestApp`-[HTYBottomView buttonImage] + 80
    frame #4 : 0x10183a348 TestApp`-[HTYBottomView setupView] + 232
    frame #5 : 0x10183a1ec TestApp`-[HTYBottomView initWithCoder:] + 164
    frame #6 : 0x18c5d3fb4 UIKit`-[UIClassSwapper initWithCoder:] + 248
    frame #7 : 0x18c771d24 UIKit`UINibDecoderDecodeObjectForValue + 680
    frame #8 : 0x18c771a64 UIKit`-[UINibDecoder decodeObjectForKey:] + 104
    frame #9 : 0x18c5d3c58 UIKit`-[UIRuntimeConnection initWithCoder:] + 188
    frame #10: 0x18c771d24 UIKit`UINibDecoderDecodeObjectForValue + 680
    frame #11: 0x18c771e9c UIKit`UINibDecoderDecodeObjectForValue + 1056
    frame #12: 0x18c771a64 UIKit`-[UINibDecoder decodeObjectForKey:] + 104
    frame #13: 0x18c5d2f98 UIKit`-[UINib instantiateWithOwner:options:] + 1168
    frame #14: 0x18c3d32cc UIKit`-[UIViewController _loadViewFromNibNamed:bundle:] + 372
    frame #15: 0x18c195dd4 UIKit`-[UIViewController loadView] + 176
    frame #16: 0x18c076dd8 UIKit`-[UIViewController loadViewIfRequired] + 184
    frame #17: 0x18c076d08 UIKit`-[UIViewController view] + 28
    frame #18: 0x18c1fb6a0 UIKit`-[UINavigationController _startCustomTransition:] + 892
    frame #19: 0x18c11e634 UIKit`-[UINavigationController _startDeferredTransitionIfNeeded:] + 696
    frame #20: 0x18c11e254 UIKit`-[UINavigationController __viewWillLayoutSubviews] + 156
    frame #21: 0x18c11e15c UIKit`-[UILayoutContainerView layoutSubviews] + 188
    frame #22: 0x18c0744f0 UIKit`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1224
    frame #23: 0x18b590a84 QuartzCore`-[CALayer layoutSublayers] + 148
    frame #24: 0x18b584d70 QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 296
    frame #25: 0x18b584c2c QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 32
    frame #26: 0x18b4fb88c QuartzCore`CA::Context::commit_transaction(CA::Transaction*) + 276
    frame #27: 0x18b522ae4 QuartzCore`CA::Transaction::commit() + 520
    frame #28: 0x18c069d78 UIKit`_afterCACommitHandler + 256
    frame #29: 0x1851fc6e4 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
    frame #30: 0x1851fa18c CoreFoundation`__CFRunLoopDoObservers + 412
    frame #31: 0x1851fa670 CoreFoundation`__CFRunLoopRun + 1076
    frame #32: 0x18511503c CoreFoundation`CFRunLoopRunSpecific + 436
    frame #33: 0x19596af94 GraphicsServices`GSEventRunModal + 100
    frame #34: 0x18c0da86c UIKit`UIApplicationMain + 208
    frame #35: 0x101b07aa4 TestApp`main + 124
    frame #36: 0x1a88a7d1c libdyld.dylib`start + 4
    frame #37: 0x0 None`None 
    frame #38: 0x1b2336981 libsystem_pthread.dylib`_thread + 1
    

    能不能更方便些?

    Xcode开启Loggin Malloc Stack的功能,只能在程序运行前进行设置。如果当程序已经在运行中时,也可以动态开关,那不就更方便了。
    libsystem_malloc.dylib里再捞一捞,

    ...
    [  359]    359   X Code            0x000000000000e887 0x0000000117499887 0x00000000000000f9 0x000f0000 turn_off_stack_logging
    [  360]    360   X Code            0x000000000000ca2c 0x0000000117497a2c 0x00000000000001d4 0x000f0000 turn_on_stack_logging
    ...
    

    这2个,就是吧

    幸运的是,libmalloc是开源的,dive到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);
    extern void turn_off_stack_logging();
    

    这样,通过在console中调用turn_on_stack_logging(2)turn_off_stack_logging,就可以动态开关Logging Malloc Stack功能了。

    附带:完整的Python脚本

    import lldb
    import os
    import shlex
    import optparse
    
    
    def handle_command(debugger, command, result, dict):
        '''
        mallocstack for po malloc stack  
        '''
    
        command_args = shlex.split(command)
        parser = get_options()
        try:
            (options, args) = parser.parse_args(command_args)
        except:
            result.SetError(parser.usage)
            return
    
        implementation(debugger, result, options, args)
    
    
    def implementation(debugger, result, options, args):
        cleanCommand = args[0]
        frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
        target = debugger.GetSelectedTarget()
        script = get_script(cleanCommand, options)
        sbval = frame.EvaluateExpression(script, get_expression_options())
    
        if sbval.error.fail:
            result.AppendMessage(str(sbval.error))
            return
    
        val = lldb.value(sbval)
        addresses = []
        for i in range(val.count.sbvalue.unsigned):
            address = val.addresses[i].sbvalue.unsigned
            loadAddr = target.ResolveLoadAddress(address).GetLoadAddress(target)
            addresses.append(loadAddr)
    
        retString = get_stack_trace_from_addresses(addresses, target)
    
        frame.EvaluateExpression('free(' + str(val.addresses.sbvalue.unsigned) + ')', get_expression_options())
        result.AppendMessage(retString)
    
    
    def get_stack_trace_from_addresses(frameAddresses, target):
    
        frame_string = ''
        for index, frameAddr in enumerate(frameAddresses):
            addr = target.ResolveLoadAddress(frameAddr)
            symbol = addr.symbol
            name = symbol.name
            offset_str = ''
            offset = addr.GetLoadAddress(target) - addr.symbol.addr.GetLoadAddress(target)
            if offset > 0:
                offset_str = '+ {}'.format(offset)
    
            frame_string += 'frame #{:<2}: {} {}`{} {}\n'.format(index, hex(addr.GetLoadAddress(target)), addr.module.file.basename, name, offset_str)
    
        return frame_string
    
    
    def get_expression_options():
        expr_options = lldb.SBExpressionOptions()
        expr_options.SetUnwindOnError(True)
        expr_options.SetLanguage (lldb.eLanguageTypeObjC_plus_plus)
        expr_options.SetCoerceResultToId(True)
        expr_options.SetGenerateDebugInfo(True)
        return expr_options
    
    
    def get_script(addr, options):
        script = '''  
    typedef int kern_return_t;
    typedef struct $malloc_stack_entry {
        uint64_t address;
        uint64_t argument;
        uint32_t type_flags;
        uint32_t num_frames;
        uint64_t frames[512];
        kern_return_t err;
    } $malloc_stack_entry;
    
    #define MAX_FRAMES %u
    typedef unsigned task_t;
    $malloc_stack_entry stack;
    stack.address = 0x%x;
    stack.type_flags = 2;
    stack.num_frames = 0;
    stack.frames[0] = 0;
    uint32_t max_stack_frames = MAX_FRAMES;
    stack.err = (kern_return_t)__mach_stack_logging_get_frames (
        (task_t)mach_task_self(),
        stack.address,
        &stack.frames[0],
        max_stack_frames,
        &stack.num_frames);
    if (stack.num_frames < MAX_FRAMES)
        stack.frames[stack.num_frames] = 0;
    else
        stack.frames[MAX_FRAMES-1] = 0;
    stack''' % (100, addr)
        return script
    
    
    def get_options():
        usage = "usage: %prog [options] <address>"
        description = '''mallocstack for po malloc stack.'''
        parser = optparse.OptionParser(
            description=description,
            prog="mallocstack",
            usage=usage
        )
        return parser
    
    
    if __name__ == '__main__':
        lldb.debugger = lldb.SBDebugger.Create()
    
    handle_command.__doc__ = get_options().format_help()
    lldb.debugger.HandleCommand(
        'command script add -f %s.handle_command mallocstack' %
        __name__)
    
    print '"mallocstack" commands have been installed, use the "--help" options for detailed help.'
    

    相关文章

      网友评论

        本文标题:iOS - 自定义LLDB命令

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