zoukankan      html  css  js  c++  java
  • iOS dealloc中初始化weak指针崩溃防护


    开发过程中,总是难免写出一些bug导致崩溃,即使有些崩溃原因显而易见,我们也很难完全避免, 这时候就要通过一些技术手段来避免问题。

    今天想给大家分享一下关于在dealloc中初始化一个指向自身的weak指针产生崩溃的防护方案。 

    在某些类的dealloc中我们会去做一些清理工作,这时候可能就会去初始化一个指向自身的weak指针,尤其是在使用懒加载的时候。

    问题分析


    这段代码在走到dealloc的时候是必崩的。

    #import "WeakTestObj.h"
    @implementation WeakTestObj
    - (void)dealloc {    
      __weak typeof(self) weakSelf = self;
    }
    @end

    原因呢我们可以看一下崩溃堆栈和输出的错

     

     

    很明显,当我们在dealloc初始化指向自身的weak指针时,在objc_initWeak中发生了错误,原因是因为该对象正在被释放当中。

    但为何对象在释放时初始化指向自身的weak指针会崩溃呢,我们可以通过runtime源码(https://opensource.apple.com/tarballs/objc4/)进一步分析。

    我们先通过Xcode看一下崩溃时的汇编代码

    libobjc.A.dylib`objc_initWeak:
        0x7fff2019209e <+0>:   pushq  %rbp
        0x7fff2019209f <+1>:   movq   %rsp, %rbp
        0x7fff201920a2 <+4>:   pushq  %r15
        0x7fff201920a4 <+6>:   pushq  %r14
        0x7fff201920a6 <+8>:   pushq  %r13
        0x7fff201920a8 <+10>:  pushq  %r12
        0x7fff201920aa <+12>:  pushq  %rbx
        0x7fff201920ab <+13>:  subq   $0x28, %rsp
        0x7fff201920af <+17>:  testq  %rsi, %rsi
        0x7fff201920b2 <+20>:  je     0x7fff20192227            ; <+393>
        0x7fff201920b8 <+26>:  movq   %rsi, %r13
        0x7fff201920bb <+29>:  movq   %rdi, -0x40(%rbp)
        0x7fff201920bf <+33>:  movabsq $0x7ffffffffff8, %r14     ; imm = 0x7FFFFFFFFFF8 
        0x7fff201920c9 <+43>:  movl   %r13d, %eax
        0x7fff201920cc <+46>:  shrl   $0x9, %eax
        0x7fff201920cf <+49>:  movl   %r13d, %r12d
        0x7fff201920d2 <+52>:  shrl   $0x4, %r12d
        0x7fff201920d6 <+56>:  xorl   %eax, %r12d
        0x7fff201920d9 <+59>:  andl   $0x3f, %r12d
        0x7fff201920dd <+63>:  shlq   $0x6, %r12
        0x7fff201920e1 <+67>:  leaq   0x5fea34d8(%rip), %rax    ; (anonymous namespace)::SideTablesMap
        0x7fff201920e8 <+74>:  leaq   (%rax,%r12), %r15
        0x7fff201920ec <+78>:  movq   %r15, %rdi
        0x7fff201920ef <+81>:  movl   $0x50000, %esi            ; imm = 0x50000 
        0x7fff201920f4 <+86>:  callq  0x7fff201952b4            ; symbol stub for: os_unfair_lock_lock_with_options
        0x7fff201920f9 <+91>:  movq   %r13, %rax
        0x7fff201920fc <+94>:  shrq   $0x3c, %rax
        0x7fff20192100 <+98>:  movq   %rax, -0x38(%rbp)
        0x7fff20192104 <+102>: movq   %r13, %rax
        0x7fff20192107 <+105>: shrq   $0x31, %rax
        0x7fff2019210b <+109>: andl   $0x7f8, %eax              ; imm = 0x7F8 
        0x7fff20192110 <+114>: addq   0x64853871(%rip), %rax    ; (void *)0x00007fff80030870: objc_debug_taggedpointer_ext_classes
        0x7fff20192117 <+121>: movq   %rax, -0x30(%rbp)
        0x7fff2019211b <+125>: xorl   %eax, %eax
        0x7fff2019211d <+127>: movq   %r13, %rcx
        0x7fff20192120 <+130>: testq  %r13, %r13
        0x7fff20192123 <+133>: js     0x7fff20192192            ; <+244>
        0x7fff20192125 <+135>: movq   (%rcx), %rbx
        0x7fff20192128 <+138>: cmpq   %rax, %rbx
        0x7fff2019212b <+141>: je     0x7fff201921ba            ; <+284>
        0x7fff20192131 <+147>: movq   (%rbx), %rax
        0x7fff20192134 <+150>: leaq   -0x1(%rax), %rcx
        0x7fff20192138 <+154>: cmpq   $0xf, %rcx
        0x7fff2019213c <+158>: jb     0x7fff2019214d            ; <+175>
        0x7fff2019213e <+160>: movq   0x20(%rbx), %rcx
        0x7fff20192142 <+164>: andq   %r14, %rcx
        0x7fff20192145 <+167>: testb  $0x1, (%rcx)
        0x7fff20192148 <+170>: je     0x7fff2019214d            ; <+175>
        0x7fff2019214a <+172>: movq   %rbx, %rax
        0x7fff2019214d <+175>: movq   0x20(%rax), %rax
        0x7fff20192151 <+179>: andq   %r14, %rax
        0x7fff20192154 <+182>: testb  $0x20, 0x3(%rax)
        0x7fff20192158 <+186>: jne    0x7fff201921ba            ; <+284>
        0x7fff2019215a <+188>: movq   %r15, %rdi
        0x7fff2019215d <+191>: callq  0x7fff201952ba            ; symbol stub for: os_unfair_lock_unlock
        0x7fff20192162 <+196>: leaq   0x5fe9f1d3(%rip), %rdi    ; runtimeLock
        0x7fff20192169 <+203>: movl   $0x50000, %esi            ; imm = 0x50000 
        0x7fff2019216e <+208>: callq  0x7fff201952b4            ; symbol stub for: os_unfair_lock_lock_with_options
        0x7fff20192173 <+213>: movq   %rbx, %rdi
        0x7fff20192176 <+216>: movq   %r13, %rsi
        0x7fff20192179 <+219>: xorl   %edx, %edx
        0x7fff2019217b <+221>: callq  0x7fff2017bcdc            ; initializeAndMaybeRelock(objc_class*, objc_object*, mutex_tt<false>&, bool)
        0x7fff20192180 <+226>: movq   %r15, %rdi
        0x7fff20192183 <+229>: movl   $0x50000, %esi            ; imm = 0x50000 
        0x7fff20192188 <+234>: callq  0x7fff201952b4            ; symbol stub for: os_unfair_lock_lock_with_options
        0x7fff2019218d <+239>: movq   %rbx, %rax
        0x7fff20192190 <+242>: jmp    0x7fff2019211d            ; <+127>
        0x7fff20192192 <+244>: movq   -0x38(%rbp), %rcx
        0x7fff20192196 <+248>: leaq   0x5fe9e653(%rip), %rdx    ; objc_debug_taggedpointer_classes
        0x7fff2019219d <+255>: movq   (%rdx,%rcx,8), %rbx
        0x7fff201921a1 <+259>: movq   -0x30(%rbp), %rcx
        0x7fff201921a5 <+263>: leaq   0x5fe9e504(%rip), %rdx    ; (void *)0x00007fff80030688: __NSUnrecognizedTaggedPointer
        0x7fff201921ac <+270>: cmpq   %rdx, %rbx
        0x7fff201921af <+273>: jne    0x7fff20192128            ; <+138>
        0x7fff201921b5 <+279>: jmp    0x7fff20192125            ; <+135>
        0x7fff201921ba <+284>: leaq   0x5fea33ff(%rip), %rax    ; (anonymous namespace)::SideTablesMap
        0x7fff201921c1 <+291>: leaq   0x20(%r12,%rax), %rdi
        0x7fff201921c6 <+296>: movq   %rax, %r12
        0x7fff201921c9 <+299>: movq   %r13, %rsi
        0x7fff201921cc <+302>: movq   -0x40(%rbp), %r14
        0x7fff201921d0 <+306>: movq   %r14, %rdx
        0x7fff201921d3 <+309>: movl   $0x1, %ecx
        0x7fff201921d8 <+314>: callq  0x7fff201905ff            ; weak_register_no_lock
    ->  0x7fff201921dd <+319>: movq   %rax, %rbx
        0x7fff201921e0 <+322>: testq  %rax, %rax
    

      

    崩溃发生在85行处,然后我们往上查找,可以看到是在weak_register_no_lock 这个方法内部发生了崩溃。

    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;
        objc_object **referrer = (objc_object **)referrer_id;
    
        if (referent->isTaggedPointerOrNil()) return referent_id;
    
        // ensure that the referenced object is viable
        if (deallocatingOptions == ReturnNilIfDeallocating ||
            deallocatingOptions == CrashIfDeallocating) {
            bool deallocating;
            if (!referent->ISA()->hasCustomRR()) {
                deallocating = referent->rootIsDeallocating();
            }
            else {
                // Use lookUpImpOrForward so we can avoid the assert in
                // class_getInstanceMethod, since we intentionally make this
                // callout with the lock held.
                auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
                lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
                                           referent->getIsa());
                if ((IMP)allowsWeakReference == _objc_msgForward) {
                    return nil;
                }
                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;
                }
            }
        }
    
        // now remember it and where it is being stored
        weak_entry_t *entry;
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            append_referrer(entry, referrer);
        } 
        else {
            weak_entry_t new_entry(referent, referrer);
            weak_grow_maybe(weak_table);
            weak_entry_insert(weak_table, &new_entry);
        }
    
        // Do not set *referrer. objc_storeWeak() requires that the 
        // value not change.
    
        return referent_id;
    }
    

      

    可以看一下第31行代码处,如果自身在dealloc中并且deallocatingOptions == CrashIfDeallocating,则打印错误日志并退出。

    系统为什么要这么做呢?  因为在对象dealloc时,系统会去查找对象的弱引用表,并把所有指向该对象的弱引用置为nil,如果我们在对象的dealloc中去初始化一个指向该对象的弱引用指针,很明显这是会产生冲突的。

    现在我们已经知道了崩溃产生的具体原因和位置,接下来就开始思考如何防护。

    在37行代码处我们可以看到,如果 deallocatingOptions != CrashIfDeallocating系统直接返回了nil,此时不发生崩溃。因此我们也可以通过同样的操作来避免崩溃的出现。

    在到崩溃发生前,其实经过了好几个C方法的调用,分别是 objc_initWeak,

    storeWeak,  weak_register_no_lock,理论上我们hook这当中任意一个方法都是可以的。hook之后判断一下该对象是否正在释放当中,如果是的话,直接返回nil。

    至于如何判断是否正在释放中,我们也可以通过runtime的源码找到答案。

    本人采用的是NSObjct的一个私有方法。

    - (BOOL)_isDeallocating {   
         return _objc_rootIsDeallocating(self);
    }
    

      

    解决方案


    接下来看一下具体的实现代码

    #import "fishhook.h"  //fishhook,请自行搜索
    
    //分类暴露私有方法
    @interface NSObject (runtimePrivate)
    
    - (BOOL)_isDeallocating;
    
    @end
    
    static id(*sys_objc_initWeak)(id _Nullable * _Nonnull location, id _Nullable obj);
    
    id _Nullable
    aigis_objc_initWeak(id _Nullable * _Nonnull location, id _Nullable obj) {
    
        //判断是否正在释放
        if ([obj respondsToSelector:@selector(_isDeallocating)]) {
            if([obj _isDeallocating]) {
                return nil;
            }
        }
        //调用系统的initWeak
        return sys_objc_initWeak(location,obj);
    }
    
    //调用此方法进行hook
    void start_objec_weak_defender() {
            
            //hook objc_initWeak 方法
            struct rebinding rebindObj;
            rebindObj.name = "objc_initWeak";
            rebindObj.replacement = aigis_objc_initWeak;
            rebindObj.replaced = (void *)&sys_objc_initWeak;
            struct rebinding rebindings[] = {rebindObj};
            int result = rebind_symbols(rebindings, 1);
    }
    

      

      

    此外,细心的朋友们应该也注意到另一种方案,hook  weak_register_no_lock之后,直接修改deallocatingOptions参数。(ps: 此方案未实现过,感兴趣的朋友们可以自行探索)

    weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
    

      

  • 相关阅读:
    某个应用使cpu使用率100%
    cpu上下文切换(下)
    kafka集群安装和kafka-manager
    cpu上下文切换
    oralce 记一次 External Procedure initial connection 处理
    Oracle 监听
    Oracle 序列
    Oracle 同义词
    发布到远程存储库时遇到错误: Git failed with a fatal error.
    报表加入参数
  • 原文地址:https://www.cnblogs.com/bigly/p/14835432.html
Copyright © 2011-2022 走看看