libAccessibility通知Crash排查记录分析

目录
  • Crash 信息
  • 复现场景
  • 简单引用分析
  • 寻找 Crash 对象
  • 通知中心是否一定弱引用 observer

Crash 信息

Last Exception :
0  libobjc.A.dylib                0x00000001bee86f40 _objc_msgSend +  32
1  CoreFoundation                 0x00000001a6132834 ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ +  28
2  CoreFoundation                 0x00000001a61cefd4 ____CFXRegistrationPost_block_invoke +  52
3  CoreFoundation                 0x00000001a61a21d0 __CFXRegistrationPost +  456
4  CoreFoundation                 0x00000001a61488ac __CFXNotificationPost +  728
5  Foundation                     0x00000001a7917754 -[NSNotificationCenter postNotificationName:object:userInfo:] +  96
6  CoreFoundation                 0x00000001a6132834 ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ +  28
7  CoreFoundation                 0x00000001a61cefd4 ____CFXRegistrationPost_block_invoke +  52
8  CoreFoundation                 0x00000001a61a21d0 __CFXRegistrationPost +  456
9  CoreFoundation                 0x00000001a61488ac __CFXNotificationPost +  728
10 CoreFoundation                 0x00000001a616fe88 _CFNotificationCenterPostNotificationWithOptions +  136
11 libAccessibility.dylib         0x00000001a9e275f4 __updateCachePreferenceAndRepostNotification +  144
12 libAccessibility.dylib         0x00000001a9e21ea8 ____axsHandlePrefChanged_block_invoke +  132
13 libdispatch.dylib              0x00000001a5e06e6c __dispatch_call_block_and_release +  32
14 libdispatch.dylib              0x00000001a5e08a30 __dispatch_client_callout +  20
15 libdispatch.dylib              0x00000001a5e16f48 __dispatch_main_queue_drain +  928
16 libdispatch.dylib              0x00000001a5e16b98 __dispatch_main_queue_callback_4CF +  44
17 CoreFoundation                 0x00000001a6159800 ___CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ +  16
18 CoreFoundation                 0x00000001a6113704 ___CFRunLoopRun +  2532
19 CoreFoundation                 0x00000001a6126bc8 _CFRunLoopRunSpecific +  600
20 GraphicsServices               0x00000001c225a374 _GSEventRunModal +  164
21 UIKitCore                      0x00000001a8a96648 -[UIApplication _run] +  1100
22 UIKitCore                      0x00000001a8817d90 _UIApplicationMain +  364
23 CustomApp                      0x0000000100fe1c64 main (main.mm:0)
24 ???                            0x0000000109285ce4 0x000000010926c000 + 0
------
Exception Type: SIGSEGV SEGV_ACCERR
Exception Codes: fault addr: 0x0080000000000008
Crashed Thread: 0
0x1a9e1e000 - 0x1a9e43fff  arm64 <309485b32e2c3803ae988866d303d7fd> libAccessibility.dylib

libAccessibility 在发送通知时产生了 Crash。

复现场景

在某些路径可以复现 Crash:

这里取出对象 isa 中的 class 对象 PAC 验签后使用,在 _objc_msgSend + 32 寻址时 Crash,是典型的对象内存管理异常问题。

但按对通知中心的认知,会对 observer 弱引用,post 时不应该出现释放后引用指针未置 nil 的情况。先顺便回溯分析一下调用栈。

简单引用分析

CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER 会跳转到入参 block 的 invoke 函数:

Foundation`__66-[NSNotificationCenter _addObserver:selector:name:object:options:]_block_invoke:
    0x1a8531d0c <+0>:  mov    x2, x1
    0x1a8531d10 <+4>:  ldp    x8, x1, [x0, #0x20]
    0x1a8531d14 <+8>:  mvn    x0, x8
    0x1a8531d18 <+12>: b      0x1a69cadb8

这个函数看起来就很熟悉了,在我们常规的添加通知接口产生。 block 的 invoke 函数第一个参数是 block 本身,<+4> 处是取出该 block 的两个引用对象,他们分别是:

<UILabel: 0x12abb49c0…>
_accessibilityButtonShapesChangedNotification:

后续就是调用 objc_msgSend 发送消息了。 接着看一下 block 对该 observer 的引用类型,最简单的方法就是查看该 block 的 dispose 函数指令,里面会对引用的 strong 对象 release、weak 对象 storeWeak,把 block 内存消息打印出来:

(lldb) re read x0
      x0 = 0x0000000281ae8f90
(lldb) x 0x0000000281ae8f90
0x281ae8f90: 30 91 f9 2e 02 00 00 00 06 00 00 c1 00 00 00 00  0...............
0x281ae8fa0: 34 42 4a a8 01 00 00 00 e0 02 95 ff 01 00 00 00  4BJ.............

flags 记录了 block 变量的各种信息,处于 block 第二个 8 字节0xc1000006,其 24 位为 1 说明在堆上,25 位为 0 说明没有 dispose/copy 函数,所以这个 block 对 observer 相当于 assign 引用。

有些奇怪,向上分析成本太高,直接开启 Zombie 试图去找到这个 Crash 的对象及其产生时机。

寻找 Crash 对象

开启 Zombie 后果然轻松复现:

-[UILabel _accessibilityButtonShapesChangedNotification:]: message sent to deallocated instance 0x12b7fc7d0
(lldb) x 0x12b7fc7d0
0x12b7fc7d0: d3 0b cb 83 02 00 00 00 00 00 00 00 00 00 00 00  ................
0x12b7fc7e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
(lldb) po (Class)0x0283cb0bd3
_NSZombie_UILabel
(lldb) p/t 0x0283cb0bd3
(long) $10 = 0b0000000000000000000000000000001010000011110010110000101111010011

Zombie 机制大概是在对象 dealloc 时把对象的 isa 类部分指向一个新的类(比如这里的 _NSZombie_UILabel),后续再向该对象发送消息就会被拦截下来报错,所以对象地址不会变。

那就 Hook UILabel 的-initWithFrame: / dealloc方法,打印其地址、堆栈、父视图链条等消息,触发 zombie 时根据地址找到对应的信息:

dealloc 时 UILabel:0x13bb30d90, 调用栈:(
	0   CustomApp                           0x0000000102879b0c -[UIView(QBCrashFix) qbCrashFix_dealloc] + 136
	1   CustomApp                           0x0000000103d8a3e0 -[UILabel(Foo) dealloc] + 96
	2   libobjc.A.dylib                     0x00000001aafdd5d8 B3A78098-C0FB-3DCD-B1AC-0712762510DB + 5592
	3   libobjc.A.dylib                     0x00000001aafe0f40 objc_autoreleasePoolPop + 256
	4   CoreFoundation                      0x00000001b1ca6a74 _CFAutoreleasePoolPop + 32
	5   CoreFoundation                      0x00000001b1cb93f8 42C5C917-0447-3995-B50F-DE4D132C2435 + 594936

发现了罪魁祸首:

@implementation UILabel (Foo)
…
- (void)dealloc {
    objc_setAssociatedObject(…);
#if !__has_feature(objc_arc)
    [super dealloc];
#endif
}
@end

这是在分类里面的定义,相当于把-[UILabel dealloc]方法调用跳过了,看下其实现:

UIKitCore`-[UILabel dealloc]:
…
    0x1b3ed0624 <+52>:  bl     0x1b54ad680               ; objc_msgSend$defaultCenter
    0x1b3ed0628 <+56>:  bl     0x1b75b7840
    0x1b3ed062c <+60>:  mov    x20, x0
    0x1b3ed0630 <+64>:  ldr    x2, [x19, x21]
    0x1b3ed0634 <+68>:  bl     0x1b544b400               ; objc_msgSend$_removeObserver:
…

发现内部有移除 Notification 的逻辑,由于跳过了这些指令导致通知中心该 observer 未被移除引发 Crash。

这个文件是直接 copy 的开源代码,导致了全局-[UILabel dealloc]走不到,再次证明了无脑 copy 代码的危害性。

另外,比较好奇的点是该场景通知中心对 UILabel 的引用似乎不是弱引用。

通知中心是否一定弱引用 observer

直接 Debug 发现在 -[UILabel initWithFrame:] 中会直接调用到底层接口注册 Notification:

在 CFXNotificationRegistrarAdd 时参数情况是:

(lldb) po $x0
<CFXNotificationRegistrar 0x11aa05cf0 [0x2091ba3a0]>
(lldb) po $x1
UIAccessibilityButtonShapesEnabledStatusDidChangeNotification
(lldb) po $x3
<UILabel: 0x146f56920…>

确实和 Crash 时的通知一致。

断点到 -[UILabel initWithFrame:] 结束时去看一下该对象的 isa 情况:

(lldb) x 0x146f56920
0x146f56920: bb 90 e7 07 02 00 00 01 00 00 00 00 00 00 00 00  ................
0x146f56930: 00 00 00 00 00 00 00 00 30 0a 08 3f 01 00 00 00  ........0..?....
(lldb) p/t 0x0100000207e790bb
(long) $4 = 0b0000000100000000000000000000001000000111111001111001000010111011

发现 isa 第三个位也就是 weakly_referenced 的值为0,说明底层注册 Notification 接口并不会像 -[NSNotificationCenter addObserver:selector:name:object:] 一样对 observer 弱引用,所以需要在 dealloc 手动移除通知。

以上就是libAccessibility通知Crash排查记录分析的详细内容,更多关于libAccessibility通知Crash排查的资料请关注我们其它相关文章!

(0)

相关推荐

  • CrashRpt使用案例详解

    CrashRpt介绍及简单应用 1.简介 CrashRpt是一个开源的第三方包,在程序出现未处理异常时,能够收集错误信息,并生成程序错误报告.CrashRpt可以将报告按照指定的方式(例如HTTP或SMTP)发送给开发者或者保存在本地,并且可以对生成的错误报告进行分析,定位错误位置,找出错误原因. 2.CrashRpt源码结构 CrashRpt开源代码主要可分为三部分: CrashRpt:用于拦截程序没有处理的异常,生成MiniDump文件,并和使用该库指定的信息(例如日志文件和屏幕截图等)一起

  • iOS中程序异常Crash友好化处理详解

    前言 前两天接到个面试,面试官问到上线的app怎么避免闪退,首先想到的就是在编码的时候进行各种容错,但貌似并不是面试官想要的答案,所以表现的很糟糕.今天有时间就来整理一下,希望有所帮助. 实现效果如图: 效果实现: 用法: 1.将截图的中CatchedHelper文件夹拖到你的项目工程中. 2.在AppDelegate.m中找到以下方法并如下添加代码: - (BOOL)application:(UIApplication *)application didFinishLaunchingWithO

  • Android app会crash的原因及解决方法

    android main入口的commonInit()方法内处,有这么一句话, Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler)); 如果没有这句话,app就不会crash.不信,你往里面看, public KillApplicationHandler(LoggingHandler loggingHandler) { @Override public void uncaught

  • os_object_release Crash 排查记录分析

    目录 Crash 信息 确认目标对象类型 定位 Crash 场景 Crash 信息 线上存在一个持续很久的 Crash,由于没有明确业务栈且量级不算大,让它成为了老赖之一,Crash 栈是这样的: Thread 55 0 libdispatch.dylib 0x0000000188a8cf8c __os_object_release_internal_n + 80 1 libdispatch.dylib 0x0000000188a96eec __dispatch_lane_invoke + 11

  • Swift踩坑实战之一个字符引发的Crash

    最近因为一个字符引发了 Crash,因为实际的业务场景不便描述,这里便用一段测试代码作说明. 话不多说,直接上代码: let testCharacters: Set<Character> = ["!", "\"", "$", "%", "&", "'", "+", ",", "<", &quo

  • iOS监控笔记之启动crash

    前言 相较于正常的崩溃问题,启动crash造成的损失要远远大得多.正常来说,如果有足够强健的构建发布系统,大多数时候能在版本上线之前及时发现问题并且修复,但是仍然存在小概率的线上意外.启动crash一般同时具备损害严重以及难以捕获两大特点 启动过程 从应用图标被用户点击开始,直到应用可以开始响应发生了很多事情.正常来说,尽管我们希望crash监控工具启动的尽可能早,但接入方往往总是等到launch事件之后才能启动工具,而在这个时间之前发生的崩溃就是启动crash,下面列出了在应用直到launch

  • Android Java crash 处理流程详解

    目录 一.背景 二.App端Crash注册 2.1 commonInit() 2.2 KillApplicationHandler 类 2.2.1 ensureLogging() 2.2.2 ApplicationErrorReport 三.AMS端处理崩溃逻辑 3.1 AMS.handleApplicationCrash 3.1.1 AMS.handleApplicationCrashInner() 3.2 addErrorToDropBox() 3.3 AppErrors.crashAppl

  • libAccessibility通知Crash排查记录分析

    目录 Crash 信息 复现场景 简单引用分析 寻找 Crash 对象 通知中心是否一定弱引用 observer Crash 信息 Last Exception : 0 libobjc.A.dylib 0x00000001bee86f40 _objc_msgSend + 32 1 CoreFoundation 0x00000001a6132834 ___CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 28 2 CoreFoundati

  • j2Cache线上异常排查问题解决记录分析

    目录 问题背景 问题分析 假设问题 小心求证 问题重现 问题解决 问题后记-下面才是真正的原因 重新假设 最终解决 问题背景 开发反馈,线上有个服务在运行一段时间后,就会抛异常导致redis缓存不可用.项目使用了j2Caceh,异常是j2Cache的RedisCacheProvider抛出来的,如: Exception in thread "main" redis.clients.jedis.exceptions.JedisException: Could not get a reso

  • 记一次tomcat进程cpu占用过高的问题排查记录

    本文主要记录一次tomcat进程,因TCP连接过多导致CPU占用过高的问题排查记录. 问题描述 linux系统下,一个tomcat web服务的cpu占用率非常高,top显示结果超过200%.请求无法响应.反复重启依然同一个现象. 问题排查 1.获取进程信息 通过jdk提供的jps命令可以快速查出jvm进程, jps pid 2.查看jstack信息 jstack pid 发现存在大量log4j线程block,处于waiting lock状态 org.apache.log4j.Category.

  • JVM完全解读之GC日志记录分析

    相信大家在系统学习jvm的时候都会有遇到过这样的问题,散落的jvm知识点知道很多,但是真正在线上环境遇到一些莫名其妙的gc异常时候却无从下手去分析. 关于这块的苦我也表示能够理解,之前光是JVM相关的八股文就整理了许多,但是经常是不知道如何在实战中使用.最近也尝试在模拟一些案例来训练自己的JVM相关知识,本文特意记录下这段调优经历. Java应用的GC评估 可能大多数程序员在开发完某个需求之后,往线上环境一丢,然后就基本不怎么关注后续的变化了.但是是否有考虑过,这些新引入的代码会对原有系统造成的

  • 异常排查记录amqp协议链接陷阱

    目录 前言 问题背景 异常信息 原因分析 异常一分析: 异常二分析: 解决问题 前言 amqp是一种通用的消息队列数据传输协议,典型的MQ应用RabbitMQ就实现了amqp协议,所以,我们在使用amqp-client链接rabbitmq时,可以使用amqp的链接协议连接rabbitmq.但是博主在尝试使用amqp协议链接时,碰到了一个隐藏的连接协议规范问题,故记录在此. amqp协议文档:https://www.rabbitmq.com/uri-spec.html 问题背景 amqp-clie

  • kafka并发写大消息异常TimeoutException排查记录

    目录 前言 定位异常点 分析抛异常的逻辑 真实原因-解决方案 结语 前言 先简单介绍下我们的使用场景,线上5台Broker节点的kafka承接了所有binlog订阅的数据,用于Flink组件接收数据做数据中台的原始数据.昨儿开发反馈,线上的binlog大量报错,都是kafka的异常,而且都是同一条topic抛的错,特征也很明显,发送的消息体非常大,主观判断肯定是写入大消息导致的超时了,异常详情如下: thread: kafka-producer-network-thread | producer

  • Nginx文件已经存在全局反向代理问题排查记录

    目录 项目场景: 问题描述 原因分析: 解决方案: 总结 项目场景: 阿里云搭建的宝塔Linux面板,上面已经搭建过其它网站了,我现在给一个新增的网站增加一个反向代理端口,但是通过宝塔面板添加反向代理的时候,出现了下图伪静态的错误. 问题描述 伪静态/nxinx主配置/vhost/文件已经存在全局反向代理 这个问题是其实是告诉我们nginx配置文件里面一个网站只能包含一个location /,不然就会产生报错了. 原因分析: 问题已经非常清楚了,就是nginx.conf的相关配置出现问题. 第一

  • Nginx记录分析响应慢的请求及替换网站响应内容的配置

    nginx记录分析网站响应慢的请求(ngx_http_log_request_speed) nginx模块ngx_http_log_request_speed可以用来找出网站哪些请求很慢,针对站点很多,文件以及请求很多想找出哪些请求比较慢的话,这个插件非常有效.作者的初衷是写给自己用的,用来找出站点中处理时间较长的请求, 这些请求是造成服务器高负载的很大根源. 日志记录之后,在使用perl脚本分析日志,即可知道哪些请求需要修正. 1. 模块安装 nginx第三方模块安装方法这里就一笔略过了. 配

  • IIS&Apache 攻击记录分析篇

    在这里,我为大家介绍一下两种常见的网页服务器中最重要的记录文件,分析服务器遭到攻击后,黑客在记录文件中会留下什么记录.目前最常见的网页服务器有两种:Apache和微软的Internet Information Server(简称IIS),这两种服务器都有一般版本和SSL认证版本.本文将使用和现实黑客的攻击手段类似的攻击方法去测试服务器并分析相关文件,有条件的朋友可在自己的机器上测试. IIS的预设记录文件地址在C:\winnt\system32\logfiles\w3svc1目录下,文件名是当天

随机推荐