網站首頁 編程語言 正文
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_n
時x0
寄存器值怎么來的,這個函數一千多行指令分析工作量太大,但這里可以明確的是這個函數只有這一處調用 _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 的內存管理問題了。
原文鏈接:https://juejin.cn/post/7171402732988235806
相關推薦
- 2022-07-02 C語言細致講解線程同步的集中方式_C 語言
- 2022-07-29 C++數據結構之單鏈表的實現_C 語言
- 2022-08-26 C++超集C++/CLI模塊的基本語法_C 語言
- 2022-09-24 一文詳解Golang協程調度器scheduler_Golang
- 2021-12-06 Linux學習之mkdir命令詳解_Linux
- 2023-03-22 Go語言實現猜謎小游戲_Golang
- 2022-06-10 docker-compose部署Yapi的方法_docker
- 2022-09-20 Go泛型實戰教程之如何在結構體中使用泛型_Golang
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支