函数调用堆栈
函数调用堆栈最常用的是收集crash
信息,解决问题用的,这方面网上有很多的资料,也有成熟的第三方,如Fabric
、Bugly
等。这里要说的是单纯的收集当前函数调用堆栈,用于收集卡顿检测、无法复现bug等。
函数调用堆栈的收集有3种方式,前2种方法比较简单,直接调用系统的方法就可以。
第一种方式
//返回的是当前堆栈的地址数组
NSArray<NSNumber *> *arr = [NSThread callStackReturnAddresses];
符号地址
由于 ASLR
的原因,在每次程序启动时,会在指定的进程空间上加上一个随机的偏移量(slide
),导致我们看到的 stack address
(日志中的地址)和 symbol address
(符号对应的地址)无法对应。
随机内存布局(ASLR)是操作系统为防止缓冲区溢出攻击而存在的内存保护机制。该机制通过在程序载入内存时,将地址进行随机偏移来实现。
简单的说,symble address
= stack address
- slide
;
获取ASLR
#import <dlfcn.h>
//针对32位和64位处理一下
#if __arm64__ || __x86_64__
#define FILE_BASE_ADDR 0x100000000
#else
#define FILE_BASE_ADDR 0x4000
#endif
Dl_info info;
if (dladdr((const void *)main, &info)){
int * fbase = (int*)info.dli_fbase;
printf("ASLR: 0x%lX\n", (unsigned long)(fbase) - FILE_BASE_ADDR);
}
我们可以通过在lldb
里执行:
image list -o -f
来验证我们所取得的地址的正确性。
解析
解析可以通过2个命令进行,dwarfdump
、atos
。
dwarfdump
这个命令显示的比较详情,需要自己更进一步解析。
//0x100004260这个就是减过slide的地址
dwarfdump --arch arm64 --lookup 0x100004260 Backtrace.app.dSYM/
---------------------------------------------------------------
File: Backtrace.app.dSYM/Contents/Resources/DWARF/Backtrace (arm64)
---------------------------------------------------------------
Looking up address: 0x0000000100004260 in .debug_info... found!
0x000382b7: Compile Unit: length = 0x000001b4 version = 0x0004 abbr_offset = 0x00000000 addr_size = 0x08 (next CU at 0x0003846f)
0x000382c2: TAG_compile_unit [110] *
AT_producer( "Apple LLVM version 9.0.0 (clang-900.0.39.2)" )
AT_language( DW_LANG_ObjC )
AT_name( "..Backtrace/Backtrace/ViewController.m" )
AT_stmt_list( 0x0000773c )
AT_comp_dir( "../Backtrace" )
AT_APPLE_optimized( true )
AT_APPLE_major_runtime_vers( 0x02 )
AT_low_pc( 0x0000000100004208 )
AT_high_pc( 0x00000144 )
0x000383ad: TAG_subprogram [115] *
AT_low_pc( 0x000000010000423c )
AT_high_pc( 0x00000070 )
AT_frame_base( reg29 )
AT_object_pointer( {0x000383c6} )
AT_name( "-[ViewController bar]" )
AT_decl_file( "../Backtrace/ViewController.m" )
AT_decl_line( 36 )
AT_prototyped( true )
AT_APPLE_optimized( true )
Line table dir : '../Backtrace'
Line table file: 'ViewController.m' line 0, column 20 with start address 0x0000000100004260
Looking up address: 0x0000000100004260 in .debug_frame... not found.
atos
atos -arch arm64 -o Backtrace 0x100004260
//这个行数不正确 暂不知原因
-[ViewController bar] (in BSBacktraceLogger) (ViewController.m:0)
第二种方式
#include <execinfo.h>
//定义一个指针数组
void* callstack[128];
//该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在callstack中。
//参数128用来指定callstack中可以保存多少个void* 元素。
//函数返回值是实际获取的指针个数,最大不超过128大小在callstack中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址。
int frames = backtrace(callstack, 128);
//backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组.
//参数callstack应该是从backtrace函数获取的数组指针,frames是该数组中的元素个数(backtrace的返回值)
//函数返回值是一个指向字符串数组的指针,它的大小同callstack相同.每个字符串包含了一个相对于callstack中对应元素的可打印信息.
char **strs = backtrace_symbols(callstack, frames);
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (int i=0;i<frames;i++)
{
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
//注意释放
free(strs);
backtrace 源码在这里,需要的可以看下实现。
这个方式最简单,而且是已经解析过的,直接把这个数组上传就可以了。
第三种方式
统计函数调用堆栈一般分为3步:
- 获取函数地址
- 通过代码或者
dSYM
获取对应的符号地址 - 拿到对应的代码信息
前面2种都是通过调用系统函数来获取的,第3种方式完全是通过自己的代码逻辑来进行获取。
通过thread_get_state
函数获取指定线程的rip
、rbp
,再遍历所有的动态库,看rbp
是否在当前的动态库里,在的话就解析动态库获取符号地址和字符地址。
网友评论