一、引言
移动App 发布后,如果想获取 App 的业务运行状态,通常是通过服务端接口反映到状态或者是用户反馈,缺少客户端的异常错误的线上监控、告警与异常数据聚合并沉淀的平台
。也无法在多维度进行异常数据的对比,使得收集应用信息和收集崩溃日志变得日益迫切。
37手游研发的 Bugless 定位于从线上问题追踪的视角出发,检测代码异常,通过回溯问题,从而解决代码本身问题。它的主要功能:
- 实时监控SDK业务异常
- 汇总包体崩溃排重与聚合后的数据
- 统计影响设备数
- 上报崩溃日志
- 收集iOS系统向上兼容性问题
- 监控客户端请求的网络问题
<!-- more -->
Bugless 目标定位是,支持不同项目
不同端
的异常上报告警,智能推送通知,及时发现异常,尽最快速度降低影响时间和范围,减少造成的损失。同时 Bugless 也支持后台聚合错误信息数据,分析历史异常数据,协助开发人员对项目进行实现监控和产品迭代优化。
二、认识崩溃和异常
在讲解 Bugless 之前,让我们从三个层面来介绍,让大家认识App为什么会出现崩溃和异常,以及如何应对。
2.1、App 层面
App 出现崩溃(crash)原因,是因为违反iOS系统运行规则导致的,产生crash的三种类型:
2.1.1 内存引发闪退。
一般是由以下几个方面引起:
- 无效的内存访问
- 内存访问越界
- 运行时方法调用不存在
- 解引用指向无效内存地址的指针
- 跳转到无效地址的指令
2.1.2 响应超时
启动
、挂起
、恢复
、结束
等事件响应不及时
2.1.3 触发Watchdog机制
Watchdog
是为了防止一个应用占用过多系统资源,如果超出了该场景规定的运行时间,“看门狗”就会强制kill掉这个应用,在 crashlog 会看到 “0x8badf00d
”的错误代码。
2.2、Mach 异常和 Unix 信号
Mach
是 iOS 和 macOS 操作系统的微内核,Mach 异常就是最底层的内核级异常。在 iOS 系统中,每个 Thread、Task、Host 都有一个异常端口数据。开发者可以通过设置 Thread、Task、Host 的异常端口来捕获 Mach 异常。Mach 异常会被转换成相应的 Unix 信号,并传递给出错的线程。
在常见的异常崩溃信息中,经常会看到有 Exception Type: EXC_BAD_ACCESS (SIGSEGV)
这样的字段和内容,EXC_BAD_ACCESS
和 SIGSEGV
,分别是指 Mach 异常和 Unix 信号。所以这个 Exception Type 意思是 Mach 层的异常 EXC_BAD_ACCESS 被转换成 SIGSEGV 信号并传递给出错的线程。在 Triggered by Thread
中,我们也可以看到出错的线程编号,例如Triggered by Thread: 0
,0 就是主线程 main-thread。之所以会将 Mach 异常转换成 Unix 信号,是为了兼容 POSIX 标准(SUS 规范),这样一来,开发者即使不了解 Mach 内核也可以通过 Unix 信号的方式进行兼容开发。
Unix 信号的种类有很多,在 iOS 应用程序中,常见的 Unix 信号有如下几种:
SIGILL
:程序非法指令信号,通常是因为可执行文件本身出现错误,或者试图执行数据段。堆栈溢出时也有可能产生该信号。SIGABRT
:程序中止命令中止信号,调用 abort 函数时产生该信号。SIGBUS
:程序内存字节地址未对齐中止信号,比如访问一个 4 字节长的整数,但其地址不是 4 的倍数。SIGFPE
:程序浮点异常信号,通常在浮点运算错误、溢出及除数为等算术错误时都会产生该信号。SIGKILL
:程序结東接收中止信号,用来立即结東程序运行,不能被处理、阻塞和忽略。SIGSEGV
:程序无效内存中止信号,即试图访问未分配的内存,或向没有写权限的内存地址写数据。SIGPIPE
:程序管道破裂信号,通常是在进程间通信时产生该信号。SIGSTOP
:程序进程中止信号,与 SIGKILLー样不能被处理、阻塞和忽略。
在 iOS App 中,一般情况采集以上几个常见的信号,就能满足日常采集 App 异常的需求。
本节引用于:iOS全埋点解决方案 (豆瓣)
2.3、Bugless 崩溃捕获流程原理
跟 App 紧密相关的异常莫过于 Objective-C 抛出异常,也是我们最容易捕获到的一种异常。捕获此异常方法如下:
<center>注册异常捕获函数</center>
以下是捕获流程图:
App 启动初始化后,会判断是否开启异常监听,如果开启就监听系统开放的API,当iOS系统产生异常,只要监听系统的回调即可。
Objective-C 产生异常的表现形式,如图表前5列中的 Invalid 类型异常。除了Objective-C异常以外,还有两种异常分别由 Mach Exception Handler
和 POSIX signer handler
捕获到,崩溃表现形式形如表中的 SEGV_ACCERR
类型。
2.3.1 Bugless 上报闪退堆栈
从数据全量收集出发,获取闪退的日志时机有两个:
- 第一时机:闪退立即上报,但第一次可能因为进程被杀死而发送不成功。
- 第二时机:是重新启动发现上次有闪退日志,进行上报。但如果用户不再次启动,可能就无法上传。
2.3.2 Bugless 异常分析流程
拿到一份闪退日志,按如下步骤可初步定位出异常的类型。如下图所示:
2.3.3 Bugless 堆栈解析
按流程初略分析异常产生原因之后,如何定位问题所在位置呢?我们这时就需要用到崩溃堆栈解析工具。 对比两款符号化工具Symbolicatecrash(命令行工具)和SymbolicateX(UI工具),
总的来看,两个工具都使用了相同解析关键工具atos。
解析过程为,首先遍历出属于 ‘cheng’
这个主程序的全部内存地址,存储为addresses数组,再通过 symbolicationCommand
函数传入符号表dsym文件,架构armv7或arm64,以及loadAddress
进行符号化,如以下代码示例:
Symbolicatecrash
:使用到Xcode自带内存地址转函数堆栈命令atos。SymbolicateX
:SymbolicateX是第三方开源工具,基于它进行二次开发为的命令行解析工具XcheckSymb,可使用atosl替代atos工具,实现跨平台的日志解析,以达到不再依赖macOS系统及Xcode的xcrun,为将堆栈符号化作成通用的在线服务作铺垫。
后续对解析工具的优化,将朝着解决堆栈解析效率低的问题出发:
- 一方面缩短解析时长;
- 另一方面引入批量异步解析和缓存重复堆栈机制。
2.4、聚合
崩溃标题:主要根据偏移量进行区分。
苹果官方聚合方案:
使用AppBundleName 加内存地址,再加偏移量。
例如 :syios: 0f100afc000 8691804
新方案:
Exception Codes
做标题,结合闪退线程中第一个有效偏移量, 如下图所示日志中二进制文件名cheng所对应的第一个偏移量: 4437668
<center>新方案标题</center>
堆栈聚合
根据去除堆栈变量后的hash值聚合。
聚合先过滤掉崩溃线程的内存地址、偏移量,再将文本做hash标签,按标签进行聚合,再按设备标示进行排重。以此种方法聚合堆栈由于iOS系统版本的不同堆栈md5值会有出入。(具体原因是,不同系统当前崩溃堆栈依赖库行数可能不同。)
过滤方法如下,
正则过滤排除内存地址和偏移量正则条件如下:
三、网络层面异常
- 1)能按分钟报告诸如找不到页面(状态码404)、服务不可用(503)网络异常等。
- 2)详细统计出,客户端请求超时次数,计算出超时请求设备的占比。
- 3)通过检查返回的数据是不是预期的JSON格式,监测是否出现域名劫持的情况。
四、服务器业务层面异常
通过对客户端网络请求的错误上报,实时上报SDK业务异常,可以方便的监测账号认证异常、下单应用内购买异常及发货异常。
五、告警
5.1、实时告警
Bugless 提供按分钟、每小时或按天进行错误累计并告警,一旦超过阀值就会通过企业微信进行告警
告警系统的结构图如下:
小助手告警消息示例如下:
六、Bugless 系统
以上告警系统上线后,只能获得零散的告警信息,借助了bugless后台,可以满足我们对多维度进行异常数据的对比需求。 下面具体介绍Bugless后台做了些什么。
6.1、Bugless 后台
Bugless后台统计出了业务异常:
表1 自动生成账号密码错误
6.2、Bugless 接入应用案例
目前为止Bugless接入上线4款游戏,共接到有效告警三次。包括: 1) 研发下单商品ID错误
2) 苹果应用内购买服务异常
3) 手机注册重复请求率高
6.3、准确性
与苹果iTunes Connect的崩溃日志做统计数值对比基本吻合。
Bugless崩溃上报正确性验证(Bugless VS Xcode Organizer Crashes) 仅漏报2台设备,评估是闪退后没有再启动,没上报上来。同一处崩溃,苹果iTunes后台收集到61台设备闪退,Bugless收集到59台设备受影响。
如下图所示,
Bugless后台日志详情
表 3 Bugless后台日志详情
表 4 Bugless解析日志
七、总结
7.1、Bugless 应用过程中存在的问题
在使用过程中也发现了几个问题,其中告警误报的情况时有发生。由于先期对阈值把握不足,阈值就调到足够低,这样不会放过绝大多数的有效数据样本。随着数据样本增加,告警的阈值逐步精确起来,误报情况将得到改善。
7.2、结束语
本次对Bugless项目的技术关键点的设计、开发和上线,可以看出该项目能持续有效的对苹果平台发行业务问题排查提供数据支撑。当然该项目仍有一个自身不断完善的过程。比如二次开发的符号解析工具,缺少了系统库函数堆栈信息,有待改进;另一方面崩溃日志解析性能有待进一步提升,减少用户等待时间。
随着业务的拓宽,Bugless 也有了更多服务用户的机会。将Bugless推广到更多的业务领域,诸如联运SDK、海外业务等。同时提供埋点上报供研发使用,让游戏可以通过自建平台(非第三方平台)统计到用户的使用习惯,如有定制报表需求可提供一对一的技术支持,给更多用户带来便利。
八、附录
参考链接 - 异常堆栈字段说明
- https://developer.apple.com/documentation/foundation/nsexception
- https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/examining_the_fields_in_a_crash_report
- https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/understanding_the_exception_types_in_a_crash_report
SymbolicateX iOS/Mac 项目崩溃文件自动符号化工具
- https://github.com/tomlee130/SymbolicatorX