1. Malloc日志
下面我们将了解在创建对象时,MallocStackLogging如何获取堆栈跟踪。
我们将创建一个自定义的LLDB命令。该命令为我们提供对象何时在内存中分配或释放的堆栈跟踪,即使堆栈跟踪早已从调试器中消失也可以。
了解对象在程序中创建位置的堆栈跟踪不仅对逆向工程有用,而且在日常调试中也有很好的用例。当一个进程崩溃时,了解该内存的历史记录以及崩溃前发生的任何分配或解除分配事件,是非常有帮助的。
1.1 脚本配置
我们有两个脚本要实现。让我们逐一了解它们以及如何使用它们:
- msl.py:这是命令(MallocStackLogging的缩写)是我们将在这里使用的脚本。里面有一个基本的逻辑框架。
- lookup.py: 在之前的版本上添加了一些附加选项。将使用其中一个选项把搜索限制在进程中的特定模块。
- sbt.py:使用非符号化符号进行回溯,并对其进行符号化。
- search.py:遍历堆中的所有对象并搜索特定子类。用于快速获取对特定类实例的引用。
1.2 MallocStackLogging解释
如果不熟悉MallocStackLogging
环境变量,下面将对其进行描述,并展示它的典型用法。
当MallocStackLogging
环境变量被传递到进程中并设置为true
时,它将监视堆上内存的分配和释放。
启用这个选项后,运行项目,就可以看到一些打印信息。
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按钮。
点击按钮后,创建了一个图片视图。请执行以下步骤:
- 选择位于Xcode中LLDB控制台顶部的Debug Memory Graph。
- 选择在左侧面板中Show the Debug navigator。
- 在左侧面板的底部,选择Show only content from workspace。
- 选择RayView的引用。
- 在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
函数期望传入一个int
型stack_logging_mode_type
的参数。其中stack_logging_mode_all
选项的是1
。
我们再看看另外一个函数__mach_stack_logging_get_frames
。stack_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
}
- LLDB在执行的时候允许返回一个对象。这里我们创建了一个C结构体。
- 声明一个
LLDBStackAddress
结构体的实例。 - 全局变量
mach_task_self_
是调用mach_task_self
时的返回值。 - 分配100个
mach_vm_address_t
。 - 执行
__mach_stack_logging_get_frames
。如果有任何可用的栈记录信息,LLDBStackAddress
结构体addresses
数组中会有很多地址信息。 - 打印出
__mach_stack_logging_get_frames
返回的所有地址。 - 最后, 谁申请谁释放。刚刚申请的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)
- 使用
generateScript
函数返回一个跟trace_address
函数一样的字符串脚本。 - 执行脚本返回一个
SBValue
。 - 检查
EvaluateExpression
是否出错。 如果出错了,提取出错误信息并退出。 - for循环会遍历
val
对象中的内存地址放入addresses
列表。 - 通过
addresses
列表来对应函数名,返回将要输出的栈记录字符串。 - 脚本里面申请了内存,最后用完后需要我们手动释放这部分内存。
(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
选项,重新运行。
我们来尝试一下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
网友评论