arrière-plan
iOS 16 est cassé : juejin.cn/post/715360…
iOS 16 est à nouveau cassé : juejin.cn/post/722551…
Les plantages analysés dans cet article ne sont également déclenchés que sur le système iOS16, et notre APP rapporte plus de 2 000 plantages chaque jour.
Raison du plantage :
Impossible de former une référence faible à l'instance (0x1107c6200) de la classe _UIRemoteInputViewController. Il est possible que cet objet ait été surlibéré ou soit en cours de désallocation.
Un objet de type _UIRemoteInputViewController ne peut pas être référencé faiblement. Il se peut que l'objet ait été surlibéré ou qu'il soit en train d'être libéré. Les références faibles à des objets qui ont été publiés ou sont en cours de publication planteront. Ce type de plantage est souvent observé dans l'utilisation de __weak pour se modifier dans dealloc.
_UIRemoteInputViewController est évidemment lié au clavier. Après avoir lu le journal de l'utilisateur, il se bloque également après l'apparition du clavier.
Pile de plantages :
0 libsystem_kernel.dylib ___abort_with_payload()
1 libsystem_kernel.dylib _abort_with_payload_wrapper_internal()
2 libsystem_kernel.dylib _abort_with_reason()
3 libobjc.A.dylib _objc_fatalv(unsigned long long, unsigned long long, char const*, char*)()
4 libobjc.A.dylib _objc_fatal(char const*, ...)()
5 libobjc.A.dylib _weak_register_no_lock()
6 libobjc.A.dylib _objc_storeWeak()
7 UIKitCore __UIResponderForwarderWantsForwardingFromResponder()
8 UIKitCore ___forwardTouchMethod_block_invoke()
9 CoreFoundation ___NSSET_IS_CALLING_OUT_TO_A_BLOCK__()
10 CoreFoundation -[__NSSetM enumerateObjectsWithOptions:usingBlock:]()
11 UIKitCore _forwardTouchMethod()
12 UIKitCore -[UIWindow _sendTouchesForEvent:]()
13 UIKitCore -[UIWindow sendEvent:]()
14 UIKitCore -[UIApplication sendEvent:]()
15 UIKitCore ___dispatchPreprocessedEventFromEventQueue()
16 UIKitCore ___processEventQueue()
17 UIKitCore ___eventFetcherSourceCallback()
18 CoreFoundation ___CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__()
19 CoreFoundation ___CFRunLoopDoSource0()
20 CoreFoundation ___CFRunLoopDoSources0()
21 CoreFoundation ___CFRunLoopRun()
22 CoreFoundation _CFRunLoopRunSpecific()
23 GraphicsServices _GSEventRunModal()
24 UIKitCore -[UIApplication _run]()
25 UIKitCore _UIApplicationMain()
analyse de la pile
Le crash s'est produit à l'intérieur de la fonction système. Analysez d'abord la pile pour comprendre le contexte du crash. Heureusement, libobjc dispose d'un code open source, ce qui améliore considérablement l'efficacité du dépannage.
_weak_register_no_lock
Lancer une erreur fatale Le code de niveau supérieur, après avoir supprimé certaines informations non essentielles, est le suivant.
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
objc_object *referent = (objc_object *)referent_id;
if (deallocatingOptions == ReturnNilIfDeallocating ||
deallocatingOptions == CrashIfDeallocating) {
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
deallocating =
! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
}
if (deallocating) {
if (deallocatingOptions == CrashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of " <=== 崩溃
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
}
}
La raison directe est que le allowWeakReference de l'instance _UIRemoteInputViewController renvoie false.
options == CrashIfDeallocating 就会 crash。否则的话返回 nil。不过 CrashIfDeallocating 写死在了代码段,没有权限修改。整个 storeWeak 的调用链路上都没有可以 hook 的方法。
__UIResponderForwarderWantsForwardingFromResponder
调用 storeWeak 的地方反汇编
if (r27 != 0x0) {
r0 = [[&var_60 super] init];
r27 = r0;
if (r0 != 0x0) {
objc_storeWeak(r27 + 0x10, r25);
objc_storeWeak(r27 + 0x8, r26);
}
}
xcode debug r27 的值
<_UITouchForwardingRecipient: 0x2825651d0> - recorded phase = began, autocompleted phase = began, to responder: (null), from responder: (null)
otool 查看 _UITouchForwardingRecipient 这个类的成员变量
ivars 0x1cfb460 __OBJC_$_INSTANCE_VARIABLES__UITouchForwardingRecipient
entsize 32
count 4
offset 0x1e445d0 _OBJC_IVAR_$__UITouchForwardingRecipient.fromResponder 8
name 0x19c7af3 fromResponder
type 0x1a621c5 @"UIResponder"
alignment 3
size 8
offset 0x1e445d8 _OBJC_IVAR_$__UITouchForwardingRecipient.responder 16
name 0x181977f responder
type 0x1a621c5 @"UIResponder"
第一个 storeweak 赋值 offset 0x10 responder: UIResponder 取值 r25。
第二个 storeweak 赋值 offset 0x8 fromResponder: UIResponder 取值 r26。
XCode debug 采集 r25 r26 的值
fromResponder | responder |
---|---|
UIView | UITransitionView |
UITransitionView | xxxRootWindow |
xxxRootWindow | UIWindowScene |
UIWindowScene | UIApplication |
到这里就比较清晰了,_UITouchForwardingRecipient 是在保存响应者链。其中_UITouchForwardingRecipient.responder = _UITouchForwardingRecipient.fromResponder.nextReponder
(这里省略了一长串的证明过程,最近卷的厉害,没有时间整理之前的文档了)。崩溃发生在 objc_storeWeak(_UITouchForwardingRecipient.responder)
, 我们可以从 nextReponder
这个方法入手校验 responder
是否合法。
结论
修复方案
Trouvez la classe nextresponder
dont _UIRemoteInputViewController
est , accrochez sa nextresponder
méthode new_nextresponder
et jugez dans la méthode, si allowsWeakReference == NO
alors return nil
. Cette classe se trouve au point d'arrêt à l'adresse du crash _UISizeTrackingView
.
- (UIResponder *)xxx_new_nextResponder {
UIResponder *res = [self xxx_new_nextResponder];
if (res == nil){
return nil;
}
static Class nextResponderClass = nil;
static bool initialize = false;
if (initialize == false && nextResponderClass == nil) {
nextResponderClass = NSClassFromString(@"_UIRemoteInputViewController");
initialize = true;
}
if (nextResponderClass != nil && [res isKindOfClass:nextResponderClass]) {
if ([res respondsToSelector:NSSelectorFromString(@"allowsWeakReference")]) {
BOOL (*allowsWeakReference)(id, SEL) =
(__typeof__(allowsWeakReference))class_getMethodImplementation([res class], NSSelectorFromString(@"allowsWeakReference"));
if (allowsWeakReference && (IMP)allowsWeakReference != _objc_msgForward) {
if (!allowsWeakReference(res, @selector(allowsWeakReference))) {
return nil;
}
}
}
}
return res;
}
petit rappel
- Le schéma implique deux classes privées, et il est recommandé d'utiliser le commutateur pour les dispenser afin d'éviter le risque d'audition.
- La réparation du plantage du système est encore une ancienne règle. Vous devez ajouter un commutateur pour limiter la version du système. Lorsque le plan de réparation déclenche d'autres problèmes, vous pouvez revenir en arrière dans le temps. Il existe certains risques dans le crochet et le point de crochet de ce plan est relativement faible.
- Je n'ai coupé que le code de base, et j'espère le comprendre et l'approuver avant d'adopter cette solution.