命令的作用
在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.'
网友评论