脚本的出处
28-sb-examples-malloc-logging文章
脚本的诉求起因
标题中“创建时的调用栈区”指的是我们经常所说的‘Malloc Stack’,也就是项目的‘Scheme’下的‘Malloc Stack Logging’——以下简称MSL,如下图:
如果要使用MSL这项功能,我们需要在项目运行之前勾选上图中的选项。 假设我们的项目在运行中,并且在运行前Scheme中没有勾选MSL选项,可是我们又期望在运行过程随时中打开该功能,以便于从此刻记录将要生成的对象的Malloc Stack,那我们该怎么办呢?这就是该脚本的诉求。
脚本支持的功能
脚本支持在项目Debug期间,通过命令行实现以下功能:
- 随时开启/关闭MSL功能。
- 随时查看某一个对象(内存地址)的MSL。
最终的结果将会与“Debug Memory Graph”中的调用栈一致,如下图所示:
上图中标号中的命令由脚本最终实现,解释如下:
- “enable_logging”为Debug过程中开启MSL功能的命令。
- 命令msl 后输入某个内存中的对象地址,随后输出该对象的MSL。
脚本的原理分析
查找MSL的相关函数
-
使用lookup命令查找出 libsystem_malloc.dylib所有的函数名称
(lldb) lookup . -m libsystem_malloc.dylib **************************************************** 560 hits in: libsystem_malloc.dylib **************************************************** bitarray_size bitarray_create bitarray_get ... (此处省略大量所查到的函数名称) create_log_file open_log_file_from_directory __mach_stack_logging_get_frames turn_off_stack_logging turn_on_stack_logging
-
检索上一步的结果中包含‘logging’的函数,会发现 turn_off_stack_logging、turn_on_stack_logging、create_log_file、__mach_stack_logging_get_frames等函数。
-
在苹果的开源项目中搜索到如下代码:
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();
-
git上关于OS中libmalloc的stack_logging_test.c代码给出了__mach_stack_logging_get_frames的用法:
static void check_stacks(char *ptrs[], int num_ptrs, boolean_t lite_mode) { mach_vm_address_t frames[MAX_FRAMES]; uint32_t frames_count; for (int i = 0; i < num_ptrs; i++) { kern_return_t ret = (lite_mode) ? __mach_stack_logging_get_frames_for_stackid(mach_task_self(), get_stack_id_from_ptr(ptrs[i]), frames, MAX_FRAMES, &frames_count, NULL) : __mach_stack_logging_get_frames(mach_task_self(), (mach_vm_address_t) ptrs[i], frames, MAX_FRAMES, &frames_count); EXPECT_TRUE(ret == KERN_SUCCESS, "return from __mach_stack_logging_get_frames = %d\n", (int) ret); EXPECT_TRUE(frames_count > 0, "number of frames returned from __mach_stack_logging_get_frames = %u\n", frames_count); } }
脚本逻辑分析
-
借助命令别名开启MSL功能
command alias enable_logging expression -lobjc -O -- extern void turn_on_stack_logging(int); turn_on_stack_logging(1);
-
使用frame.EvaluateExpression 执行相关OC++ JIT代码,JIT代码中使用‘__mach_stack_logging_get_frames’函数获取对象内存初始化的时候调用栈帧地址数组。
/// OC JIT code def generateScript(addr, options): script = ' mach_vm_address_t addr = (mach_vm_address_t)' + str(addr) + ';\n' script += r''' typedef struct LLDBStackAddress { mach_vm_address_t *addresses; uint32_t count = 0; } LLDBStackAddress; LLDBStackAddress stackaddress; mach_vm_address_t address = (mach_vm_address_t)addr; void * task = mach_task_self_; stackaddress.addresses = (mach_vm_address_t *)calloc(100, sizeof(mach_vm_address_t)); __mach_stack_logging_get_frames(task, address, stackaddress.addresses, 100, &stackaddress.count); stackaddress ''' return script /// options 的配置 def generateOptions(): 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
-
遍历调用栈帧地址数组,通过当前地址 - 函数起始地址获取当前地址在函数中的偏移量
1.addr = target.ResolveLoadAddress(frameAddr) ///根据地址10或16进制数 获取lldb.SBAddress 2.addr.GetLoadAddress(target) - addr.symbol.addr.GetLoadAddress(target) /// 获取偏移量 3.name = addr.symbol.name /// 获取符号地址对应的函数名 (如果这里获取不到,可以借助 sbt 脚本中的获取方式,即使没有DWARF也可以还原OC的函数名)
-
最终获取的结果如 脚本支持的功能下图所示