Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >记 os_object_release Crash 排查

记 os_object_release Crash 排查

作者头像
波儿菜
发布于 2022-12-10 07:35:54
发布于 2022-12-10 07:35:54
67100
代码可运行
举报
文章被收录于专栏:iOS技术iOS技术
运行总次数:0
代码可运行

Crash 信息

线上存在一个持续很久的 Crash,由于没有明确业务栈且量级不算大,让它成为了老赖之一,Crash 栈是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()触发软中断结束进程 ,尝试从源码入手,顶层函数逻辑是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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定义是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 调用):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
_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的调用,排查后发现基本上在另外一个线程都有这么一段调用栈:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 的类型:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 的内存管理问题了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
iOS疑难Crash的寄存器赋值追踪排查技术
我们会借助一些崩溃日志收集库来定位和排查线上的崩溃信息,但是有些崩溃堆栈所提供的信息有限又不是必现崩溃,很难直观排查出问题的所在。这里我给大家分享一个采用寄存器赋值追踪的技术来排查和分析崩溃日志的技巧。话不多说先看案例:
欧阳大哥2013
2021/04/01
3.5K3
iOS疑难Crash的寄存器赋值追踪排查技术
记 libAccessibility 通知 Crash 排查
这里取出对象 isa 中的 class 对象 PAC 验签后使用,在 _objc_msgSend + 32 寻址时 Crash,是典型的对象内存管理异常问题。
波儿菜
2022/12/10
6840
记 libAccessibility 通知 Crash 排查
iOS底层GCD (技术总结)
今天,我们再来研究一下 GCD 部分的栅栏函数底层实现,信号量和调度组的应用。也算是 GCD 篇章的一个结尾。好的,下面就开始今天的内容。
CC老师
2021/08/25
4270
OC底层探索22-GCD(上)OC底层探索22-GCD(上)define DISPATCH_QUEUE_WIDTH_FULL 0x1000ulldefine DISPATCH_QUEU
在项目中增加一个符号断点:dispatch_queue_create;(还可以是其他的GCD-API即可);
用户8893176
2021/08/09
5820
OC底层探索22-GCD(上)OC底层探索22-GCD(上)define DISPATCH_QUEUE_WIDTH_FULL         0x1000ulldefine DISPATCH_QUEU
GCD原理探究(一)——创建队列
1,栈区,由编译器自动分配并释放,在运行的时候分配,用于存储函数的参数、局部变量、指针等。
拉维
2021/03/25
8000
GCD原理探究(一)——创建队列
iOS底层 - 关于死锁,你了解多少?
我们从GCD函数和队列的内容中最后的经典案例中关于死锁的案例开始,从死锁的发生开始,看看其产生的本质原因是为什么。
CC老师
2021/08/25
5510
This application is modifying the autolayout engine from a background thread, which can lead to engi
然后在解析了从服务器返回的信息后,直接new了UIAlertView对象,准备弹窗提示,这时候控制台打印了一串异常的信息:
tandaxia
2018/09/27
1.6K0
阿里、字节:一套高效的iOS面试题( 多线程 GCD底层原理篇)
dispatch_group_create() + dispatch_group_wait()
会写bug的程序员
2020/06/18
5K0
阿里、字节:一套高效的iOS面试题( 多线程 GCD底层原理篇)
OC底层探索23-GCD(下)OC底层探索23-GCD(下)
在上篇OC底层探索22-GCD(上)中分析了GCD的串/并队列的创建,同步、异步函数执行,而且留下了:死锁、栅栏函数的坑会在本文中补上;
用户8893176
2021/08/09
3760
OC底层探索23-GCD(下)OC底层探索23-GCD(下)
XCode LLDB调试小技巧基础篇提高篇汇编篇
导语: 记录平时用到的XCode LLDB调试小技巧 工欲善其事必先利其器,介绍一些LLDB调试的命令和小技巧~ 基础篇 1.print命令 p 输出基本类型,例如double,int po 输
MelonTeam
2018/01/04
5K0
XCode LLDB调试小技巧基础篇提高篇汇编篇
iOS_Crash 异常类型
断点异常类型表示跟踪陷阱(trace trap)中断了该进程。跟踪陷阱使附加的调试器有机会在进程执行的特定点中断进程。 在 ARM 处理器上显示为 EXC_BREAKPOINT(SIGTRAP) 在 x86_64 处理器上显示为 EXC_BAD_INSTRUCTION(SIGILL)
mikimo
2023/10/18
2.6K0
iOS runloop 的基本使用 、构成及应用案例(基于CFRunLoopDoSources0自定义Operation)
原文链接:https://blog.csdn.net/z929118967/article/details/114638658
公众号iOS逆向
2021/03/24
1.1K0
iOS runloop 的基本使用 、构成及应用案例(基于CFRunLoopDoSources0自定义Operation)
30天从零接手ios开发
由于公司原因,唯一会ios的伙伴要离开了,临时要接手ios的开发任务,只有30天的交接时间,记录下这个过程
韦东锏
2023/10/27
3880
30天从零接手ios开发
iOS PerformSelector 遗漏问题
performSelecor响应了OC语言的动态性:延迟到运行时才绑定方法。当我们在使用以下方法时:
全栈程序员站长
2022/11/17
5840
iOS - 老生常谈内存管理(五):Tagged Pointer
在objc4源码中,我们经常会在函数中看到Tagged Pointer。Tagged Pointer究竟是何方神圣?请开始阅读本文。
师大小海腾
2020/04/24
2K0
iOS - 老生常谈内存管理(五):Tagged Pointer
iOS-GCD
sync同步函数serial串行队列:不会开启线程,在当前线程执行任务,会产生堵塞
Wilbur-L
2020/12/14
6620
iOS-GCD
深入剖析 iOS 性能优化
在集合里数据量小的情况下时间复杂度对于性能的影响看起来微乎其微。但如果某个开发的功能是一个公共功能,无法预料调用者传入数据的量时,这个复杂度的优化显得非常重要了。
用户7451029
2020/06/16
1.7K0
深入剖析 iOS 性能优化
Objective 锁
由于锁是自旋锁,线程不会休眠,所以当低优先级线程先对操作进行Lock造作后,CPU调度高优先级线程造作,由于低优先级别UnLock就调用高优先级线程。高优先级无法处理该操作,而高优先级线程一直调用CPU资源, 系统等待高优先级线程执行完毕后才给低优先级线程资源。
老沙
2019/09/28
7000
深入理解iOS Crash Log
USB连接设备,接着在XCode菜单栏依次选择:Window -> Devices And Simulators,接着选择View Device Logs
用户2932962
2019/07/31
4.6K1
深入理解iOS Crash Log
iOS 底层拾遗:autorelease 优化
由于 ARC 下 retain/release/autorelease 的调用都是编译器代劳,所以需要使用编译后的代码进行分析,通常笔者选择 Xcode 自带的工具,它有一个优势是自动将一些符号地址改为符号名,并且可以选择 Running 或 Archiving 下的汇编代码,后者生成的代码往往是前者的优化版本。
波儿菜
2019/12/23
1.4K0
相关推荐
iOS疑难Crash的寄存器赋值追踪排查技术
更多 >
LV.0
这个人很懒,什么都没有留下~
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档