如何符号化Objective-C调用栈

本文讲述的是符号化“残破”的栈,如果你有一个系统生成的crash日志,请交给Xcode自带的symbolicatecrash脚本。

Symbolicatecrash脚本的核心也是通过atos功能逐行符号化,但人家封装好了,比自己手动一行一行做快很多。

示例栈:

    0   XSQSymbolicateDemo                  0x00000001000ba530 XSQSymbolicateDemo + 25904
    1   XSQSymbolicateDemo                  0x00000001000ba4f0 XSQSymbolicateDemo + 25840
    2   XSQSymbolicateDemo                  0x00000001000ba4bc XSQSymbolicateDemo + 25788
    3   XSQSymbolicateDemo                  0x00000001000ba478 XSQSymbolicateDemo + 25720
    4   UIKit                               0x00000001966870ec  + 96
    5   UIKit                               0x000000019668706c  + 80
    6   UIKit                               0x00000001966715e0  + 440
    7   UIKit                               0x0000000196686950  + 576
    8   UIKit                               0x000000019668646c  + 2480
    9   UIKit                               0x0000000196681804  + 3192
    10  UIKit                               0x0000000196652418  + 340
    11  UIKit                               0x0000000196e4bf64  + 2400

这是我写的一个demo app,并且在编译后期滤去了符号表,所以仅能看到一些奇怪的地址。

如何符号化第三方app内的符号

以第一行:

0   XSQSymbolicateDemo                  0x00000001000ba530 XSQSymbolicateDemo + 25904

为例

需要条件:

(1)atos工具(Xcode安装时一般会自带)

(2)确认app运行的架构(armv7、arm64)

(3)app对应的dSYM文件(出包时获得)

(4)app代码载入到内存的基地址(后文详细介绍)

方法:

在命令行中输入:

xcrun atos -arch arm64 -o ./XSQSymbolicateDemo.app.dSYM/Contents/Resources/DWARF/XSQSymbolicateDemo  -l  0x1000b4000 0x00000001000ba530

即可得到符号化后的结果:

-[ViewController helloWorld2] (in XSQSymbolicateDemo) (ViewController.m:100)
如何符号化系统动态库中的符号

以这一行为例:

4   UIKit                               0x00000001966870ec  + 96

需要条件:

(1)atos工具(Xcode安装时一般会自带)

(2)确认app运行的架构(armv7、arm64)

(2)该OS版本、该动态库的符号文件(将该手机连接到电脑的Xcode上,会自动同步系统符号文件)

(3)该动态库载入到内存的基地址(后文详细介绍)

方法:

在命令行中输入:

xcrun atos -arch arm64 -o ~/Library/Developer/Xcode/iOS\ DeviceSupport/10.3.1\ \(14E304\)/Symbols/System/Library/Frameworks/UIKit.framework/UIKit -l  0x196642000 0x00000001966870ec
-[UIApplication sendAction:to:from:forEvent:] (in UIKit) + 96

即可得到符号化后的结果:

-[UIApplication sendAction:to:from:forEvent:] (in UIKit) + 96
如何获取基地址

注意:基地址在进程每次启动时决定,所以重启进程后,符号化时必须使用当次启动的基地址

方案一:从iOS生成的crash日志中获取

在iOS系统生成的crash日志中的下半部分,有这样的一些信息:

如何符号化Objective-C调用栈_第1张图片

蓝色框圈出来的部分,即为app代码载入到内存的基地址

红色框圈出来的部分,即为各个动态库载入到内存的基地址

方案二:在app运行时打印

可以在app中调用如下代码获取各个image的基地址:

void printAllImage()
{
    for (int i = 0; i < _dyld_image_count(); i++) {
        char *image_name = (char *)_dyld_get_image_name(i);
        const struct mach_header *mh = _dyld_get_image_header(i);
        intptr_t vmaddr_slide = _dyld_get_image_vmaddr_slide(i);
        
        NSLog(@"Image name %s at address 0x%llx and ASLR slide 0x%lx.\n",
               image_name, (mach_vm_address_t)mh, vmaddr_slide);
    }
}

得到如下输出:

Image name /var/containers/Bundle/Application/351121C8-CFE4-49AD-ACC0-A70C5BF1C4A6/XSQSymbolicateDemo.app/XSQSymbolicateDemo at address 0x1000b4000 and ASLR slide 0xb4000.
 Image name /System/Library/Frameworks/Foundation.framework/Foundation at address 0x190f0c000 and ASLR slide 0xeedc000.
 Image name /usr/lib/libarchive.2.dylib at address 0x190ee0000 and ASLR slide 0xeedc000.
 Image name /usr/lib/libbz2.1.0.dylib at address 0x190e9e000 and ASLR slide 0xeedc000.
 Image name /usr/lib/libSystem.B.dylib at address 0x18ef04000 and ASLR slide 0xeedc000.
 Image name /usr/lib/system/libcache.dylib at address 0x18f35a000 and ASLR slide 0xeedc000.
......

可以看到第一行代表的是app自身,之后的每一行是app载入的动态库们。

介绍加载和ASLR

大致理解:

在进程启动的时候,内核加载器或者dyld会将指令加载到内存中。

ASLR全名Address Space Layout Randomization,地址空间布局随机化,用于防范恶意程序对已知地址进行攻击

在ASLR引入之前,由于加载的规则是固定的,所以理论上,一个进程不管重启多少次,每条指令对应的内存中的地址都是一样的。而每条指令对应到内存中的哪个地址,可以通过分析Mach-O文件分析出来。这就容易产生安全漏洞。

ASLR引入后,在进程启动前期的加载阶段,会生成一个随机数offset,让加载形成的内存整体偏移一个offset。

这样一个进程多次启动,每次行程的内存空间布局都不完全一致。同一个指令,经过多次启动,每次都会被布局到一个新计算出来的地址。

所以仅仅凭借“一个指令在内存中的地址”和dSYM文件,是无法进行符号化的,因为这个“地址”同时依赖于ASLR生成的offset。

我理解其实只需要一个offset,配合已知的架构、加载方式等信息,应该就能推测出app自身的基地址和各个库的基地址。尝试后也证明,各个库的基地址-offset后的值在同个设备的多次启动上是一致的。
但是为了图省事,还是自己打印一下所有库的基地址吧(´・ω・`)

你可能感兴趣的:(如何符号化Objective-C调用栈)