<acronym id='ymib'><em id='ymib'></em><td id='ymib'><div id='ymib'></div></td></acronym><address id='ymib'><big id='ymib'><big id='ymib'></big><legend id='ymib'></legend></big></address>
  • <i id='ymib'><div id='ymib'><ins id='ymib'></ins></div></i>
  • <tr id='ymib'><strong id='ymib'></strong><small id='ymib'></small><button id='ymib'></button><li id='ymib'><noscript id='ymib'><big id='ymib'></big><dt id='ymib'></dt></noscript></li></tr><ol id='ymib'><table id='ymib'><blockquote id='ymib'><tbody id='ymib'></tbody></blockquote></table></ol><u id='ymib'></u><kbd id='ymib'><kbd id='ymib'></kbd></kbd>

    <code id='ymib'><strong id='ymib'></strong></code>
    1. <ins id='ymib'></ins>

        <span id='ymib'></span>
        <i id='ymib'></i>

        1. <fieldset id='ymib'></fieldset>
            <dl id='ymib'></dl>

            iOS如何区分App和SDK内部crash

            • 时间:
            • 浏览:24
            • 来源:124软件资讯网

            最近在开发iOS平台上的SDK ,提供应互助方使用  。为了监控SDK自身的瓦解率  ,我们在SDK中加入了抓取crash的功效  。但网络上来的日志中有较多互助方App的crash ,而且接入SDK的App数目许多  ,发生的瓦解日志量很是大  。靠人力从海量的日志中筛选出我们SDK的crash日志很是难题 。

            于是就有了这个问题 ,怎样自动区分SDK内部的crash和App的crash ?

            iOS crash日志花样

            想区分差别类型的crash  ,需要先相识iOS的crash日志花样 。iOS的crash陈诉日志可以分为头部(Header)、异常信息(Exception Information)、诊断信息(Additional Diagnostic Information)、线程客栈(Backtraces)、线程状态(Thread State)、库信息(Binary Images)这六个部门  。如图:

            其中:

            1. 头部(Header): 硬件型号  ,系统版本  ,历程名称、id  ,bundleid  ,瓦解时间  ,crash日志陈诉花样版本号等信息 。

            2. 异常信息(Exception Information): 瓦解类型、瓦解代码及触发瓦解的线程等信息  。

            3. 诊断信息(Additional Diagnostic Information): 很是简略的诊断信息  。不是每个瓦解都市有诊断信息  。

            4. 线程客栈(Backtraces): 瓦解发生时  ,各个线程的要领挪用栈的详细信息  。触发瓦解的线程会被标志上Crashed  。

            5. 线程状态(Thread State): 瓦解时寄存器的状态  。

            6. 库信息(Binary Images): 加载的动态库信息  。

            检察crash日志时 ,首先会在【异常信息(Exception Information)】中通过名叫“Triggered by Thread”的字段检察是哪个线程发了crash  。例如上图中的“Triggered by Thread: 0”表现是0号线程也就是主线程发生了crash 。在【线程客栈(Backtraces)】信息中  ,也会看的线程号下面会用“Thread xx Crashed”标志该线程发生了crash 。

            在【线程客栈(Backtraces)】信息中 ,有要领编号  ,要领所属的模块名  ,要领地址  ,要领符号信息或者要领所在的段地址及偏移量 。每个要领的地址是包罗在所属模块的地址规模内 。如图:

            图中显示  ,0号线程即主线程发生了crash ,地址是0x00000001000effdc  ,在TheElements这个模块内  。在【库信息(Binary Images)】信息中可以找到这个二进制模块 ,也就是App可执行文件TheElements  ,其他的模块是系统加载的动态库  ,好比UIKit、CoreFoundation等(这张图并没有显示出来 ,在第一张图中可以看到另外两个系统的动态库  ,其他系统库的太多了 ,图中并没有展示)  。

            怎样确定动态库的crash

            熟悉了crash日志陈诉的花样 ,我们知道动态库的crash的要领栈中是带有动态库的名字的  ,一眼就能看出是哪个模块发生了crash 。通过花样化好的crash日志  ,就能够区分App的crash和引入的动态SDK的crash  。若是要在App运行时crash后立刻判断  ,可以通过crash的地址  ,找到包罗这个地址的二进制模块就行了  。

            怎样确定静态库的crash

            通过Crash的地址可以找到该要领所属的二进制模块 。然而  ,若是SDK是静态库引入的  ,其代码会被加入到App的代码段中  ,SDK的代码和App的代码属于统一个二进制模块  ,这样就不容易判断了  。例如第一张图中的crash在 [TestCrash illegalAccess], 这段代码是在SDK中的  ,而所属的模块是TestSDKApp  ,即App的代码段 ,这样就不知道是App照旧SDK内部crash了  。

            Thread 0 Crashed:
            0   TestSDKApp                        0x00000001021529d4 +[TestCrash illegalAccess] + 27092 (TestCrash.m:21)
            1   UIKitCore                         0x000000020df86768 -[UIApplication sendAction:to:from:forEvent:] + 96

            Binary Images:
            0x10214c000 - 0x102153fff TestSDKApp arm64  <3f44b26a30f93ebd8f79abd1fe7e0ac9>  /var/containers/Bundle/Application/9293836E-A3C3-4C61-BDAD-BEED79AC9EDE/TestSDKApp.app/TestSDKApp

            对于这个问题  ,其时我们有这几个选择: 一个是服务端网络到crash日志后 ,通过符号文件剖析出对应的客栈信息  ,然后通过crash的符号来判断是app的crash照旧sdk的内部的crash  。另一个要领就是通过地址来判断  。

            通过符号来判断

            这个思绪很简朴  ,就是人检察crash日志识别差别crash的历程  。大致步骤如下:

            1. 符号化服务端crash日志

            2. 网络SDK中所有的特征符号  ,好比类名 ,要领名等等

            3. 处置惩罚crash日志  ,对比SDK中的特征符号 ,确定该crash是SDK内部crash

            思绪简朴 ,可是会带来许多问题  。如网络SDK中类名费时艰苦;SDK中某些引入的第三方库没有符号信息;差别版本的SDK符号特征纷歧样 。每个问题都能折磨人  。

            通过地址来判断

            既然都知道了crash发生的地址  ,为什么不通过地址来判断是否在SDK内部呢 ,就像动态库的crash一样 ?于是问题就变为了怎样确定SDK代码被毗连进App后的起始地址和竣事地址  。

            静态库SDK二进制文件结构

            这里对SDK的文件花样不详细先容  ,看图:

            图中红框中标出的是该SDK包罗的object文件内容  ,即文件编译后的产物  。这里的顺序是怎样确定的呢  ?看下xcode中【Targets->Build Phases->Complie Sources】就明确了  ,是xcode编译这些源文件的顺序  。如图:

            也就说  ,SDK的文件相当于一个object文件的容器  ,把源文件的编译产物按顺序打包组织在一起就是SDK的二进制文件了  。把SDK二进制毗连进App可执行文件后是什么样的  ?我们先看下App可执行文件的结构  。

            App可执行文件结构

            同样的  ,我们对App可执行文件的花样不详细睁开  ,只看毗连进App的SDK是怎样组织的 。如图:



            这两张图是App可执行文件中反汇编后  ,获取的要领地址列表  。SDK中要领太多  ,只截取了最先部门和竣事部门 。可以看出  ,SDK中的要领都是集中在一块  ,而且是根据SDK文件中的object文件顺序排列  。
            于是可以得出这样一个结论

            SDK中文件的编译顺序最终体现在毗连进App可执行文件中要领的地址顺序  。也就是  ,SDK毗连进App可执行文件后 ,基本上是在一块一连的地址上;App执行时  ,加载进内存也会在一块一连的内存地址上  。

            这样我们就通过crash要领地址就能确定是否是SDK内部的crash  。于是乎我们就剩下最后一个问题了  ,怎样确定SDK代码段的起始地址和竣事地址  ?

            怎样确定SDK代码起始和竣事地址

            仔细的人可能已经以前面的图片中看出来怎么做了  。对  ,就是在编译文件最前面添加一个文件 ,最后面添加一个文件 。好比图片中的CodeTextBegin.m 和 CodeTextEnd.m  ,然后第一个文件的第一个要领地址就是SDK编译文件中所有要领的起始地址  ,最后一个文件的最后一个要领地址就是SDK编译文件中所有要领的竣事地址 。例如:
            CodeTextBegin.m放在所有编译文件的最前面  ,内里的第一个要领是获取该要领自身的地址 。

            // CodeTextBegin.m
            #import "CodeAddress.h"

            // 返回这个要领的地址
            extern void * getSDKStartAddress(void) {
                return &getSDKStartAddress;
            }

            CodeTextEnd.m放在所有编译文件的最后面  ,内里的最后一个要领是获取该要领自身的地址 。

            // CodeTextEnd.m
            #import "CodeAddress.h"

            // 返回这个要领的地址
            extern void * getSDKEndAddress(void) {
                return &getSDKEndAddress;
            }

            这样我们挪用这个两个要领就能都到所有编译文件中的要领的起止地址  。

            为什么是SDK编译文件中的所有要领呢 ?由于  ,SDK中还可以引入其他SDK  。这个时间  ,引入的其他SDK的所有要领会添加在编译文件的最后一个要领后面  。以是 ,若是我们要包罗SDK所有要领时  ,应该在SDK中引入一个SDK  ,而且放在所有引入的SDK最后面  ,这个最后面的SDK中  ,有个要领可以返回自身的地址  。

            这样  ,我们就能通过crash时的要领地址来判断是否是SDK内部的crash了  。

            在凭据上面的要领做完后测试  ,SDK运行时获取的要领地址和在MachOView工具中看到的App可执行文件中的地址对应不上  ,这是为什么  ?
            实在是iOS系统引入了ASLR机制  ,即Address space layout randomization  。在App运行时  ,iOS系统会给加载进内存的二进制模块一个随机的偏移地址 ,我们只需要把运行时的地址减掉这个偏移地址好了 代码如下:

            // 获取可执行模块的slide
            extern long getExecuteImageSlide(void) {
                long slide = -1;
                for (uint32_t i = 0; i < _dyld_image_count(); i++) {
                    if (_dyld_get_image_header(i)->filetype == MH_EXECUTE) {
                        slide = _dyld_get_image_vmaddr_slide(i);
                        break;
                    }
                }
                return slide;
            }

            用获取到的要领地址减去偏移值就是App可执行文件中的地址:

            // 真实的起始地址
            void* startAddr =  getSDKStartAddress() -  getExecuteImageSlide();

            // 真实的竣事地址
            void* endAddr = getSDKEndAddress() -  getExecuteImageSlide();

            总结

            前面根据解决问题的步骤贴图讲的 ,比力杂乱 。这里总结下  ,我们可以通过SDK的起止地址是否包罗crash的要领地址来判断crash是否发生在SDK内部  。 详细如下:

            1. 动态库有明确的起止地址  ,可以用crash要领的地址直接来判断  。

            2. 静态库中的要领会按编译时的顺序毗连进App可执行文件  。

            3. 在第一个编译文件最前面添加一个要领  ,返回其自身地址  ,作为SDK的起始地址

            4. 在最后一个编译文件的最后面添加一个要领  ,返回其自身地址  ,作为SDK的所有编译文件要领的竣事地址  ,若是SDK中有引入其他第三方库则需步骤5

            5. 在最后一个被引入SDK的三方库后面 ,添加一个库  ,库中导出一个(4)中一样的要领  ,作为SDK的竣事地址

            6. crash时  ,把获取到的crash地址与SDK的起止地址举行比力就能知道是否是SDK内部的crash  。主要比力地址时 ,若是crash地址减去了slide  ,则对应的SDK起止地址也应减去slide 。

            Demo地址
            检察二进制文件工具MachOView地址