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 +  1152
2  libdispatch.dylib              0x0000000188aa14bc __dispatch_workloop_worker_thread +  764
3  libsystem_pthread.dylib        0x00000001d4bde7a4 __pthread_wqthread +  276
——-
Exception Type: SIGTRAP
Exception Codes: fault addr: 0x0000000188a8cf8c
Crashed Thread: 55
Thread 55 crashed with ARM Thread State (64-bit):
    x0:0x0000000281a86580    x1:0x0000000000000002
0x188a8a000 - 0x188acefff  arm64 <ff408738d75b3061ad994a929c0162d2> libdispatch.dylib

由于不能明确是哪个业务代码引起的,所以先确认 Crash 的对象是哪个类型。

确认目标对象类型

Crash 日志看不出来目标对象类型,只知道是一个 SIGTRAP,应该是 GCD 调用__builtin_trap()触发软中断结束进程 ,尝试从源码入手,顶层函数逻辑是这样的:

DISPATCH_NOINLINE
void _os_object_release_internal_n(_os_object_t obj, uint16_t n) {
	return _os_object_release_internal_n_inline(obj, n);
}
DISPATCH_ALWAYS_INLINE
static inline void _os_object_release_internal_n_inline(_os_object_t obj, int n)
{
	int ref_cnt = _os_object_refcnt_sub(obj, n);
	if (likely(ref_cnt >= 0)) {
		return;
	}
	if (unlikely(ref_cnt < -1)) {
		_OS_OBJECT_CLIENT_CRASH("Over-release of an object");
	}
	// _os_object_refcnt_dispose_barrier() is in _os_object_dispose()
	return _os_object_dispose(obj);
}

_OS_OBJECT_CLIENT_CRASH()就是调用的__builtin_trap(),那确认就是一个os_object_t对象的 Over-Release 问题了。os_object_t定义是这样的:

typedef struct _os_object_s {
	_OS_OBJECT_HEADER(
	const _os_object_vtable_s *os_obj_isa,
	os_obj_ref_cnt,
	os_obj_xref_cnt);
} _os_object_s;
typedef struct _os_object_s *_os_object_t;

这就是 GCD 类的结构体定义,和 NSObject 类似的内存布局,但os_object_t衍生类众多还需明确是哪一个。

继续看上一个函数_dispatch_lane_invoke,发现它的代码量很大,且由于 GCD 大量的 inline 函数,很难确定是哪里调用了_os_object_release_internal_n。这个时候就要换一种方式,直接反汇编就能快速确认。

使用和 Crash 栈相同系统设备切 release 环境运行,但有点奇怪的是反汇编代码和_dispatch_lane_invoke偏移对不上。那就用 hopper 直接打开 uuid 对应的 libdispatch.dylib 可执行文件吧,找到偏移处:

接下来就要确认bl _os_object_release_internal_nx0寄存器值怎么来的,这个函数一千多行指令分析工作量太大,但这里可以明确的是这个函数只有这一处调用 _os_object_release_internal_n

那又回到 GCD 源码,估计就是尾部的一个调用了(代码有修改,去除无用代码和 inline 调用):

_dispatch_lane_invoke(…) {
	dispatch_queue_t dq = dqu._dq;
	…
	return _os_object_release_internal_n(dou._os_obj, 2);
}

翻了一下各个 Crash 日志x1寄存器都是 2 可以对得上。同时运行时反汇编指令虽然对不上,但对比找到同样逻辑的汇编代码段,br到这个偏移也能确认x0就是dispatch_queue_t

定位 Crash 场景

既然产生 Over-Release 的对象是 dispatch_queue_t,那推测就是业务代码使用时存在内存管理问题,最蠢的方式就是找到所有的dispatch_queue_create()调用排查各个场景是否有问题。

不过在这之前可以多看一下 Crash 日志,调用栈有dispatch_workloop_worker_thread可以推测当前时机是业务block加入了 GCD 队列,现在已经开始调度了。举个例子,如果在dispatch_async(queue, block)时 queue 就已经释放了,那 Crash 栈就会有dispatch_async,说明在调用dispatch_async(queue, block)时 queue 是正常的,在调度过程要结束时 queue 才被其它线程释放,立即走到_dispatch_lane_invoke的尾调用时才触发了 Over-Release。

那其它线程引起 queue 释放的时机和当前 Crash 时机应该很近,也就是说其它线程此时的堆栈大概率有释放这个dispatch_queue_t的调用,排查后发现基本上在另外一个线程都有这么一段调用栈:

9  libdispatch.dylib              0x0000000188a8dfc0 -[OS_dispatch_queue _xref_dispose] +  56
10 AnyProject                       0x0000000107c9b724 -[AnySDKClass dealloc] +  164
11 AnyProject                        0x0000000107cbc10c -[AnySDKClass .cxx_destruct] +  76

那大概率问题就出在AnySDKClass,运行时找到其dealloc方法:

…
    0x107ddab88 <+124>: bl     0x109994540               ; symbol stub for: dispatch_sync
    0x107ddab8c <+128>: add    x0, x19, #0x10            ; =0x10
    0x107ddab90 <+132>: mov    x1, #0x0
    0x107ddab94 <+136>: bl     0x10999589c               ; symbol stub for: objc_storeWeak
    0x107ddab98 <+140>: ldr    x0, [x19, #0x18]
    0x107ddab9c <+144>: str    xzr, [x19, #0x18]
    0x107ddaba0 <+148>: bl     0x1099957e8               ; symbol stub for: objc_release
    0x107ddaba4 <+152>: ldr    x0, [x19, #0x58]
    0x107ddaba8 <+156>: str    xzr, [x19, #0x58]
    0x107ddabac <+160>: bl     0x1099957e8               ; symbol stub for: objc_release
…

断点到对应偏移0x107ddabac处,找到这个 queue 的类型:

br set -a 0x107ddabac
po $x0
<OS_dispatch_queue_serial: anyName[0x2809e2900] = { xref = 1, ref = 1, sref = 1, target = com.apple.root.default-qos.overcommit[0x12e435100], width = 0x1, state = 0x001ffe2000000000, in-flight = 0}>

那剩下的工作就是找到对应 SDK 源码,分析出这个 serial queue 的内存管理问题了。

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

(0)

相关推荐

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

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

  • CrashRpt使用案例详解

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

  • 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

  • iOS监控笔记之启动crash

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

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

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

  • 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

  • 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

  • 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目录下,文件名是当天

随机推荐