iOS闪退监控方案

实现方案

基本思路:日志捕获采用 KSCrash,捕获的日志上传服务器,然后在服务器对日志进行符号化。

KSCrash 的上传日志需要注意启动闪退的情况,一般是应用启动如果存在日志,需要先 hold 主线程,等上传完再释放。

基本模型如下:

//防止在登录前就必闪情况
__block BOOL finished = NO;
[self uploadIfExistWithCompleteHandler:^{
    finished = YES;
}];
//防止上传失败
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    finished = YES;
});
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
while (!finished) {
    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}

符号化方案

以下主要讨论的是服务端通过脚本自动符号化日志需求

方案一:通过修改配置,不需要自主符号化日志,也能看到日志堆栈类和函数名

通过在 Xcode 中设置 Build Settings -> Strip Style -> Debugging Symbols

Strip Style 有三个选项如下

  • All Symbols:剥离所有符号表和重定向信息
  • Non-Global Symbols:剥离非全局的符号(包括调试符号),保留外部符号
  • Debugging Symbols:剥离调试符号,保留局部符号和全局符号

开启设备符号化需要在最终版本中包含基本符号,所以要在 build settings 中设置 Strip StyleDebugging Symbols。也会造成最终的二进制文件大小增加 5% 左右,这也是之前 PLCrashReporter 中提到的,不过当时查到的数据是 30-50%,确实测试后没有如此大的差距,也算是解了疑惑,由于打包包含了基本符号表导致的二进制大小增加。

注意:系统库无法符号化,需要 symbolicatecrash 来符号化

方案二:通过 symbolicatecrash 自主符号化日志

atos 是苹果提供的符号化工具,在 Mac OS 系统下默认安装,他的缺点是只能一个地址一个地址逐个翻译。

symbolicatecrash 是 Xcode 自带的一个程序,他是对 atos 的封装,可以翻译整个 crash 文件。

接下来通过使用 symbolicatecrash 进行符号化日志

第一步:获取 symbolicatecrash 文件,通过使用以下命令搜索,找到之后拷到闪退日志目录下

find /Applications/Xcode.App -name symbolicatecrash -type f

第二步:设置路径

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

第三步:符号化日志,cd 到闪退日志目录下,然后运行下面命令

./symbolicatecrash log.txt -d xxx.App.dSYM > result.txt  

获取符号文件

获取项目符号文件

iOS 的打包一般有两种方式,Xcode 打包或者脚本打包。

不过哪种方式都会生成 xxx.xcarchive 文件,打开就可以看到 dSYMs 符号文件。

  1. Xcode 打包:在 Xcode 中工具栏上 Window -> Organizeer -> 选择相应的App -> Archives -> 选择对应的包进入就可以看到 xxx.xcarchive 文件
  2. 脚本打包:一般ipa包生成同时旁边也会生成 xxx.xcarchive 文件

这里有个问题,闪退日志必须和符号化文件匹配才能解析,我们可以通过 UUID 来匹配

dSYM 的 UUID 获取方式

命令:dwarfdump --uuid appName.app.dSYM

例如:dwarfdump --uuid /Users/xxx/Desktop/Demo.app.dSYM
结果:UUID: 35E34DE0-4C45-36CF-8F11-1F33BA40F3ED (arm64)

日志自动带上了 UUID

image.png

此时符号化的日志如下

image-1.png

获取系统符号文件

系统符号文件会根据不同 CPU 架构(armv7,armv7s,arm64,arm64e),以及不同系统对应的文件都不一样。通过 symbolicatecrash 工具进行符号化时,工具会自动会在 /Users/xxx/Library/Developer/Xcode/iOS DeviceSupport 目录下进行匹配解析。

方式一:从真机上获取

当你用 Xcode 第一次连接某台设备进行真机调试时,会看到 Xcode 显示 Processing symbol files ,这时候就是在拷贝真机上的符号文件到 Mac OS 系统的 /Users/xxx/Library/Developer/Xcode/iOS DeviceSupport 目录下。

image-2.png

14.6 (18F72) arm64e 就是对应的系统符号文件

如果仅通过真机来获取系统的符号化文件,比较局限,把不同系统版本以及不同 CPU 架构类型的手机准备齐全比较难。

方式二:从固件中提取符号文件

下面以获取 iPhone12 系统为 iOS 14.6 为例。

第一步:下载对应的固件,从中获取 dyld_shared_cache_xxx 可执行文件,其中 xxx 是 CPU 架构类型。

在 www.theiphonewiki.com 下载。优点整理的齐全,缺点下载速度有点慢。

image-3.png

下载完解压得到,获取最大的 dmg 文件

image-4.png

运行 018-17771-088.dmg 获取 dyld_shared_cache_arm64e,路径 System -> Library -> Caches -> com.apple.dyld

image-5.png

第二步:通过 dyld 生成 dsc_extractor 可执行文件。

在苹果的开源网站下载 dyld 源码,例子中下载了 dyld-750.6.tar.gz 解压

image-6.png

修改 dsc_extractor.cpp 文件,把 #if 0 改成 #if 1,屏蔽其他代码

#if 1
// test program
#include 
#include 
#include 


typedef int (*extractor_proc)(const char* shared_cache_file_path, const char* extraction_root_path,
                              void (^progress)(unsigned current, unsigned total));

int main(int argc, const char* argv[])
{
    if ( argc != 3 ) {
        fprintf(stderr, "usage: dsc_extractor  \n");
        return 1;
    }

    //void* handle = dlopen("/Volumes/my/src/dyld/build/Debug/dsc_extractor.bundle", RTLD_LAZY);
    void* handle = dlopen("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle", RTLD_LAZY);
    if ( handle == NULL ) {
        fprintf(stderr, "dsc_extractor.bundle could not be loaded\n");
        return 1;
    }

    extractor_proc proc = (extractor_proc)dlsym(handle, "dyld_shared_cache_extract_dylibs_progress");
    if ( proc == NULL ) {
        fprintf(stderr, "dsc_extractor.bundle did not have dyld_shared_cache_extract_dylibs_progress symbol\n");
        return 1;
    }

    int result = (*proc)(argv[1], argv[2], ^(unsigned c, unsigned total) { printf("%d/%d\n", c, total); } );
    fprintf(stderr, "dyld_shared_cache_extract_dylibs_progress() => %d\n", result);
    return 0;
}
#endif

修改 dsc_iterator.cpp 文件,屏蔽头文件 #include "SupportedArchs.h"

在终端上 cd 到 dyld 源码目录 launch-cache 下,在终端命令行编译并生成 dsc_extractor 可执行文件

clang++ -o dsc_extractor dsc_extractor.cpp dsc_iterator.cpp

第三步:通过 dyld_shared_cache_xxxdsc_extractor 获取系统符号文件

新建一个文件夹把 dsc_extractordyld_shared_cache_arm64e 两个可执行文件放入其中

通过以下命令就可以生成系统符号文件

dsc_extractor  

比如:

./dsc_extractor ./dyld_shared_cache_arm64e ./Symbols

新建一个文件夹 14.6 (18F72) arm64e,把 Symbols 拖入其中,然后拖入 /Users/xxx/Library/Developer/Xcode/iOS DeviceSupport 目录下。

此时符号化的日志如下

image-7.png

参考资料

  • 有赞crash平台符号化实践
  • iOS 崩溃日志在线符号化实践
  • 使用symbolicatecrash分析crash日志

你可能感兴趣的:(iOS闪退监控方案)