实现方案
基本思路:日志捕获采用 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 Style
为 Debugging 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 符号文件。
- Xcode 打包:在 Xcode 中工具栏上 Window -> Organizeer -> 选择相应的App -> Archives -> 选择对应的包进入就可以看到 xxx.xcarchive 文件
- 脚本打包:一般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
此时符号化的日志如下
获取系统符号文件
系统符号文件会根据不同 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
目录下。
14.6 (18F72) arm64e
就是对应的系统符号文件
如果仅通过真机来获取系统的符号化文件,比较局限,把不同系统版本以及不同 CPU 架构类型的手机准备齐全比较难。
方式二:从固件中提取符号文件
下面以获取 iPhone12 系统为 iOS 14.6 为例。
第一步:下载对应的固件,从中获取 dyld_shared_cache_xxx
可执行文件,其中 xxx 是 CPU 架构类型。
在 www.theiphonewiki.com 下载。优点整理的齐全,缺点下载速度有点慢。
下载完解压得到,获取最大的 dmg 文件
运行 018-17771-088.dmg
获取 dyld_shared_cache_arm64e
,路径 System -> Library -> Caches -> com.apple.dyld
第二步:通过 dyld 生成 dsc_extractor
可执行文件。
在苹果的开源网站下载 dyld 源码,例子中下载了 dyld-750.6.tar.gz
解压
修改 dsc_extractor.cpp
文件,把 #if 0
改成 #if 1
,屏蔽其他代码
#if 1
// test program
#include <stdio.h>
#include <stddef.h>
#include <dlfcn.h>
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 <path-to-cache-file> <path-to-device-dir>\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_xxx
和 dsc_extractor
获取系统符号文件
新建一个文件夹把 dsc_extractor
和 dyld_shared_cache_arm64e
两个可执行文件放入其中
通过以下命令就可以生成系统符号文件
dsc_extractor <path-to-cache-file> <path-to-device-dir>
比如:
./dsc_extractor ./dyld_shared_cache_arm64e ./Symbols
新建一个文件夹 14.6 (18F72) arm64e
,把 Symbols
拖入其中,然后拖入 /Users/xxx/Library/Developer/Xcode/iOS DeviceSupport
目录下。
此时符号化的日志如下