iOS 16 trava novamente

fundo

iOS 16 está quebrado: juejin.cn/post/715360…

iOS 16 está quebrado novamente: juejin.cn/post/722551…

As falhas analisadas neste artigo também são acionadas apenas no sistema iOS16, e nosso APP relata mais de 2 mil falhas todos os dias.

Motivo do acidente:

Não é possível formar uma referência fraca à instância (0x1107c6200) da classe _UIRemoteInputViewController. É possível que este objeto tenha sido liberado em excesso ou esteja em processo de desalocação.

Um objeto do tipo _UIRemoteInputViewController não pode ser referenciado fracamente. Pode ser que o objeto tenha sido liberado em excesso ou esteja em processo de liberação. Referências fracas a objetos que foram lançados ou estão sendo lançados travarão. Esse tipo de travamento é frequentemente visto no uso de __weak para modificar self em dealloc.

_UIRemoteInputViewController está obviamente relacionado ao teclado. Depois de ler o log do usuário, ele também trava após o teclado aparecer.

Pilha de falha:

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()

análise de pilha

A falha ocorreu dentro da função do sistema. Primeiro analise a pilha para entender o contexto da falha. Felizmente, libobjc tem código-fonte aberto, o que melhora muito a eficiência da solução de problemas.

_weak_register_no_lock

Throw fatal errr O código de nível superior, após excluir algumas informações não-chave, é o seguinte.

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;
            }
        }
    }
}

O motivo direto é que o allowWeakReference da instância _UIRemoteInputViewController retorna 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 是否合法。

结论

修复方案

Encontre a classe nextrespondercujo _UIRemoteInputViewControlleré , conecte seu nextrespondermétodo new_nextrespondere julgue o método, se allowsWeakReference == NOentão return nil. Essa classe pode ser encontrada no ponto de interrupção no endereço da falha _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;
 }
    

lembrete amigável

  1. O esquema envolve duas aulas particulares e é recomendável usar o switch para entregá-las para evitar o risco de auditoria.
  2. O reparo da falha do sistema ainda é uma regra antiga. Você deve adicionar uma opção para limitar a versão do sistema. Quando o plano de reparo acionar outros problemas, você pode reverter no tempo. Existem certos riscos no gancho e no ponto do gancho deste plano é relativamente pequeno.
  3. Eu apenas cortei o código principal e espero entendê-lo e aprová-lo antes de adotar esta solução.

Guess you like

Origin juejin.im/post/7240789855138873403
ios