美文网首页
iOS_调试技巧详解

iOS_调试技巧详解

作者: Lin__Chuan | 来源:发表于2018-08-29 17:31 被阅读40次

    上次介绍了一下LLDB的使用, 但实际上在解决一些问题的时候, 并不是直接使用LLDB调试指令的, 会通过各种工具或技巧来解决问题, 本篇文章就是来探讨一些相关的方法.

    1. signal SIGABRT

    image.png

    这在OC中经常出现, 一般情况下, 数组越界或者调用的方法不存在, 会触发这个错误
    解决方法:

    • 方法1. 打开全局断点, 错误一发生, Xcode会自动定位到错误的位置
    image.png
    • 方法2. 运用LLDB的调试指令, 直接寻找引发错误的内存地址
    (lldb) image lookup --address 0x00000001087418c3
          Address: LCCustomTools_OC[0x000000010000f8c3] (LCCustomTools_OC.__TEXT.__text + 59507)
          Summary: LCCustomTools_OC`-[AppDelegate application:didFinishLaunchingWithOptions:] + 195 at AppDelegate.m:29
    

    以上这两种问题, 在Swift中又不一样, 编译器会直接提示代码错误.


    数组越界.png 无法调用方法.png

    那么这个signal SIGABRT到底是什么呢?

    • 这是一个信号, SIG是所有unix信号的前缀名, ABRTabort program的简称.
    • 当操作系统发现不安全的情况时, 如UIKit 等框架通常会在特定的前提条件没有满足或一些糟糕的情况出现时调用 C 函数 abort (由它来发送此信号)

    这里展示了所有的Unix信号


    image.png
    在以上列出的信号中,程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP
    不能恢复至默认动作的信号有:SIGILL,SIGTRAP
    默认会导致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
    默认会导致进程退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
    默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
    默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH
    

    2. EXC_BAD_ACCESS(Zombie Objects)

    EXC_BAD_ACCESS,指向某块内存发送消息,但是该内存无法响应对应的消息指令。比如, 向一个已经释放的对象发送消息, 就会报此错误.
    在MRC环境下, 测试以下代码,

    image.png

    因为对象arr已经被释放, 所以再次调用arrobjectAtIndex:方法,就会报EXC_BAD_ACCESS, 也叫坏内存访问, 为了精确定位到到底是哪里的坏内存被访问了, 在Product -> Scheme -> Edict Scheme中, 勾选Zombie Objects

    image.png
    再此运行代码, 就能发现具体出错位置. image.png
    • 这里我们能看到arr已经由NSArray变成一个僵尸对象.
    • 当一个对象的引用计数变为0时, 程序会将需要 dealloc 的对象转化为僵尸对象。如果之后再给这个僵尸对象发消息,则抛出异常,并打印出相应的信息

    3. Address Sanitizer

    但是如果是在 malloc 对象方面, Zombie Objects就很难起作用了. 比如下面这种, 明显90已经超出了原来的内存分配范围, 仍然去赋值, 程序会马上退出

    image.png

    还是在原来的界面, 勾选Address Sanitizer, 再次运行, 就能发现问题所在

    Address Sanitizer 设置.png

    报堆内存溢出


    出错信息.png

    Address Sanitizer 就已经支持的错误检查包括:

    • Use-after-free, 野指针问题, 访问已释放的内存区域
    • Heap buffer overflow, 堆内存溢出
    • Stack buffer overflow, 栈内存溢出
    • Global variable overflow, 全局变量溢出
    • Overflows in C++ containers
    • Use-after-return
    • Use-after-scope

    4. Memory Leak (内存泄露)

    当希望某个对象释放掉的时候,它没有按照预想被释放掉,导致不必要的内存占用, 比较常见的是Retain Circle(循环引用).

    在Teacher.m里
    -(void)teach:(void (^)())block
    {
        self.caseBlock = block;  
        if (self.caseBlock) {
            self.caseBlock();
        }
    }
    
    在TeacherVC.m里
    [self.teacher teach:^ {
        self.name = @"jack";
    }];
    
    // dealloc
    - (void)dealloc
    {
        NSLog(@"==== dealloc ====");
    }
    
    • 当我们push到下一个TeacherVC里时, 执行上面的代码, 再pop回上一个页面, 会发现本应该执行的dealloc方法并没有执行, 说明TeacherVC这个控制器没有销毁, 造成了内存泄露.

    • 原因在于self强引用了teacher, teacher又强引用了一个block,而该block在回调时又调用了self,会导致该block又强引用了self,造成了一个保留环,最终导致self无法释放。

    self -> teacher -> block -> self

    解决方法:

    __weak typeof(self) weakSelf = self;
    [self.teacher teach:^{
        typeof(weakSelf) strongSelf = weakSelf;
        strongSelf.name = @"jack";
    }];
    
    • 通过__weak修饰, 在block回调里面用weakSelf, 打破了闭环

    self -> teacher -> block -> strongSelf -> weakSelf

    • 一般会在block回调里再强引用一下weakSelf(typeof(weakSelf) strongSelf = weakSelf;),因为__weak修饰的都是存在栈内,可能会被系统释放,造成后面调用weakSelfweakSelf可能已经是nil了,后面用weakSelf调用任何代码都是无效的。

    instruments里面看到的现象是这样的, 可以很明显看到是在调用teach方法里面泄露的, 可以在Xcode -> Open Developer Tool -> Instrument, 打开Instrument

    image.png

    参考
    Unix中的信号
    ARC下用块(block)的循环引用问题样例探究
    调试技巧: 内存

    相关文章

      网友评论

          本文标题:iOS_调试技巧详解

          本文链接:https://www.haomeiwen.com/subject/kwwoiftx.html