iOS 16 plante à nouveau

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 nextresponderdont _UIRemoteInputViewControllerest , accrochez sa nextresponderméthode new_nextresponderet jugez dans la méthode, si allowsWeakReference == NOalors 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

  1. 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.
  2. 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.
  3. Je n'ai coupé que le code de base, et j'espère le comprendre et l'approuver avant d'adopter cette solution.

Je suppose que tu aimes

Origine juejin.im/post/7240789855138873403
conseillé
Classement