美文网首页iOS开发Swift开发selector
Xcode LLVM 高级调试技巧

Xcode LLVM 高级调试技巧

作者: midmirror | 来源:发表于2018-06-28 16:58 被阅读681次

    Debug Without Build & Run

    • 直接启动已安装的 app:Debug -> Attach to Process
    • 编译一次,在多个设备调试:Product -> Perform Action -> Run without Buinding
    • 将 Product 的 ipa 编译产物直接拖入模拟器安装

    断点

    断点类型

    1. 普通断点

    过于普通以至于根本不需要介绍

    2. 条件断点

    在指定条件下的断点

    3. 符号断点

    针对某一个特定函数的断点,可以是一个 OC函数,也可以是 C++函数

    # 所有 UIViewController 的 viewDidLoad 都会触发
    viewDidLoad 
    # 特定 VC 的 viewDidLoad 触发,这里要注意带参 method 的 :
    [HomeViewController viewDidLoad:] 
    [UILabel setText:]
    
    # 一个实用的例子:unrecognized selector sent to instance 0xxxxxx
    [NSObject(NSObject) doesNotRecognizeSelector:]
    
    4. 异常断点

    调试时如果程序抛出异常,导致程序退出,就会暂停,能立马定位问题

    Exception:  # 选择抛出异常对象类型:OC 或 C++
    Break:      # 选择抛出异常来源: Throw 或 Catch 语句
    
    5. 内存断点(Watch 断点)

    观察某个变量值的变化,变化时触发。类似 KVO ,会打印出新值和旧值

    注意:内存地址每次 run 都会变化,需要重新 watch

    编辑断点

    断点参数
    参数 说明
    Condition 输入条件表达式,满足条件时触发断点
    Ignore 设置忽略断点次数(从第几次之后正式触发断点)
    Action 断点触发时的操作
    Options 勾选 Automatically continue after evaluating actions 之后程序会在断点产生后继续运行
    断点 Action

    断点+Action 可以让调试半自动化,避免每次触发断点,手动 po 的过程

    支持多个 Action,实现多个 po 或者 call 多行代码

    调试时正确的 Log 方式

    (断点条件)+(忽略次数)+ Log Message + 自动跳过断点(只打印 Log)

    Log 的好搭档

    NSStringFromRange

    NSStringFromClass

    NSStringFromSelector

    NSStringFromCGRect/CGSize/CGPoint

    NSStringFromCGVector

    NSStringFromUIOffset/UIEdgeInsets

    NSStringFromCGAffineTransform

    NSStringFromDirectionalEdgeInsets

    NSStringFromProtocol

    类型 说明
    Debugger Command 常用,触发断点时执行 lldb 命令(po/e/call)
    Log Message 常用,触发断点时打印一条 Log(当然也可以用 po),好处是不用重新 build、run
    Apple Script 执行脚本
    Shell Command 执行 shell 命令,比如 say hello 之类的...
    Capture GPU Frame OpenGL ES 调试,捕获断点处 GPU 当前绘制帧
    Sound 触发断点时播放一个声音(解决 console 打印 log 淹没所需信息)

    断点共享

    默认断点配置是在个人配置下的,不会加入 git,如果需要组员间共享断点(比如异常断点),可以设置断点为 Share Breakpoint

    LLDB 命令

    导读

    传统的编译器通常分为三个部分,前端(frontEnd),优化器(Optimizer)和后端(backEnd)。

    前端:主要负责词法和语法分析,将源代码转化为抽象语法树,生成中间码

    优化器:优化中间代码

    后端:将中间代码转化为各自平台的机器代码。

    • GCC

      GNU Compiler Collection,GNU编译器套件,支持 C、C++、Objective-C、Java、Go、汇编等

    • LLVM + Clang

      Low Level Virtual Machine(底层虚拟机),现代化模块化的编译器框架

      Clang 只需要完成词法和语法分析,代码优化和机器代码的生成工作由llvm完成。

      Clang是LLVM的前端,可以用来编译C,C++,ObjectiveC等语言。Clang则是以LLVM为后端的一款高效易用,并且与IDE结合很好的编译前端。

      有点:比 GCC 更快的速度,更人性化的错误提示(错误信息可读性更高,提示出错位置,能出错高亮,还提供修复建议),还支持静态代码分析

    • GDB 和 LLDB

      GNU Debugger:GCC 的调试器

      Low Level Debugger:LLVM 的调试器

    • GCC 到 LLVM

      GCC 定制性、可拓展性很差、Objective-C 在 GCC 得不到良好支持

    • Chris Lattner(克里斯·拉特纳)

      LLVM、Swift 之父。

      苹果公司开发者工具部门的主管,领导Xcode、Instruments和编译器团队。

      • 2000 年,研发 LLVM(伊利诺伊大学期间)

      • 2005 年,加入 Apple(LLVM 在 05-10 年期间得到了快速发展)

      • 2007 年,研发 Clang

      • 2010 年,研发 Swift 语言

        Swift was designed to scale from hello world to an entire operating system。

        Swift 的设计目标是从 Hello World 到操作系统,无论是作为脚本语言还是底层的系统设计,都能应付自如。Chris 相信 Swfit 将超越 Python,取代 C、Java,同时认为 Web 端 Javascript 发展已经够好,没有 Swfit 什么事。

      • 2017 年 1 月,加入 Tesla,负责 Autopilot

        Swift 项目交给了 Ted Kremenek,Ted 独自完成了 Clang 的静态分析器。

      • 2017 年 6 月,离开 Tesla

      Chris Lattner:Swift 只是我在苹果工作的一小部分,我花了大量的时间在其他事情上。实际上在苹果我也就晚上或者周末有空写写 Swift。我希望到了特斯拉之后我还能花同样的精力和时间在 Swift 上,毕竟我对这门语言统治世界充满期待。

    • Xcode 编译器过渡历史

      版本 阶段
      Xcode 2 及以前 GCC
      Xcode 3 推荐 LLVM-GCC 混合编译器,但还不是默认编译器
      Xcode 4 LLVM-GCC 逐步过渡到 LLVM
      Xcode 5 LLVM-GCC被遗弃,只保留 LLVM 5.0

      Xcode 4.x 的编译选项中出现过两种编译器:

      Apple LLVM Compiler 4.2:LLVM编译器,前端用的是 Clang。

      LLVM GCC 4.2:编译器核心是LLVM,前端是 GCC 4.2 编译器。

    设置断点

    # 给所有名为xx的函数设置一个断点
    breakpoint set —name xx
    br s -n xx
    b xx
    
    # 在文件F指定行L设置断点
    breakpoint set —file F —line L
    br s -f F -l L
    b F:L
    
    # 当执行到该线程时,调试器才会断下来。
    # 一段代码可能开了多线程,要在不同线程之间切来切去。这时就可以使用线程断点了
    breakPoint set –f 文件名 –l 行号 –t 线程id
    
    # 条件断点
    breakpoint set -f viewController.m -l 362 -c "width > 68"
    
    # 给所有名为xx的C++函数设置一个断点(希望没有同名的C函数)
    breakpoint set —method xxx
    br s -M xxx
    
    # 给一个OC函数[objc msgSend:]设置一个断点
    breakpoint set —name “[objc msgSend:]”
    b -n “[objc msgSend:]”
    
    # 给所有名为xx的OC方法设置一个断点(希望没有名为xx的C或者C++函数)
    breakpoint set —selector xx
    br s -S count
    
    # 给所有函数名正则匹配成功的函数设置一个断点(留个正则熟练的同学尝试)
    breakpoint set --func-regex regular-expression
    br s -r regular-expression
    
    # 给指定函数地址func_addr的位置设置一个断点
    br set -a xxx
    
    # 一次性断点
    # -- 是命令选项结束符,如果没有选项,可以省略
    breakpoint set --one-shot true --name "[UILabel setText:]"
    
    # 断点列表
    breakpoint list
    br l
    
    # 断点删除
    breakpoint delete xxx # index指明断点的序号,如果为空则删除所有断点
    breakpoint delete xxx -f # 强制删除(不需要确认)
    br del index
    
    # 断点触发时自动执行下去
    --auto-continue # 参数
    

    thread

    # 流程控制 c n s f
    process continue/continue/c     # 继续,到下一个断点
    thread step-over/next/n         # 步进,到下一行代码
    thread step-in/step/s           # 跳入所调用的函数
    thread step-out/finish/f        # 跳出所调用的函数
    
    thread return                   # 立刻返回(用于隔离某个函数,伪造返回值)
    thread select                   # 跳转到所选线程
    thread info                     # 当前线程信息
    thread list                     # 所有线程信息
    thread jump                     # 跳到指定内存地址、或某一行
    thread jump --by 1              # 跳过一行代码,也可以拖动滑块(只能往后跳,不可回跳)
    # 由于ARC下编译器实际插入了不少retain,release命令。
    # 跳过一些代码不执行很可能会造成对象内存混乱发生 crash。
    
    bt/thread backtrace             # 打印当前线程的堆栈信息(crash 发生的时候使用)
    bt all                          # 打印所有线程的堆栈信息
    

    p/po

    实际是 expression 的 alias

    p/expression --         # 打印基本数据类型、结构体
    po/expression -O --     # 打印对象(print object),调用 NSObject 的 description 方法打印对象
    
    # 特别的(可以直接用 lldb 进制换算)
    p/x     # 十六进制
    p/o     # 八进制
    p/t     # 二进制
    
    p &_toolView                        # 打印地址
    po [self.view recursiveDescription] # 打印视图层级结构
    
    重写 debugDescription 方法

    debugDescription方法是在使用po命令时调用的,实际上也是调用了description方法。

    #import <objc/runtime.h>
    
    @implementation TestModel
    - (NSString *)debugDescription
    {
        NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
        NSInteger count;
        objc_property_t *properties = class_copyPropertyList([self class], &count);
        for (NSInteger i = 0; i < count; i++){
            objc_property_t property = properties[i];
            const char* cPropertyName = property_getName(property);
            NSString *propertyName = [NSString stringWithUTF8String:cPropertyName];
            id value = [self valueForKey:propertyName]?:@"nil";
            [dictionary setObject:value forKey:propertyName];
        }
        free(properties);
        return [NSString stringWithFormat:@"<%@: %p> -- %@",[self class],self,dictionary]; }
    @end
    

    frame(栈帧)

    配合 bt 使用,进入任意作用域

    frame select xxx # 进入从 bt 列表选中的作用域
    frame variable # 可以打印出当前 frame 调用栈的的所有变量。只接受变量作为参数,不接受表达式。
    

    e/expr

    expression 动态的执行赋值表达式改变变量的值,同时打印出结果

    # 重新绘制界面, 也可以用 call
    expression [CATransaction flush]
    # 当使用 expr 调试界面颜色、坐标之类的时候,执行 flush 就可以看到效果
    # 注意:如果在动画过程中执行这个命令,就直接渲染出动画结束的效果。
    
    # 定义并打印一个对象
    e NSArray *$array = @[@"one", @"two"];
    po $array
    

    call

    不修改代码,不重新编译的情况下,调用函数改变程序运行流程

    call一般只在不需要显示输出,或是方法无返回值时使用。

    Watchpoint

    # 查看当前机器支持的最大 watchpoint 数量(开发机器是4个)和已经设置的 watchpoint
    watchpoint list 
    # 监视变量 xxx (缺省 -w 参数是 write,就是说写入的时候会触发断点)
    watchpoint set variable 0x00ff
    # self.string 实际是 setter method,这里不支持 method
    watchpoint set variable self->name
    watchpoint set variable -w read -- 0x00ff
    watchpoint set variable -w write -- 0x00ff
    # 条件断点 当所在内存地址 int 值为 20
    watchpoint modify -c '*(int *)0x1457fa70 == 20' 
    

    Image

    image 是 target modules 的 alias

    image list                      # 列出当前App中的所有module
    image lookup --address 0x00ff   # 通过内存地址定位出错代码所在行
    image lookup –name viewDidLoad  # 查找一个方法或者符号的所在文件位置
    image lookup -type HLSameThemeViewController # 会列出这个类的所有属性和成员变量,用于了解一个类
    

    用法

    # Example
    image lookup --address 0x0000000104c25550
        
    # 出错位置:ViewController.m:30
    Address: BGMultimediaDemo[0x0000000100001550] (BGMultimediaDemo.__TEXT.__text + 192)
    Summary: BGMultimediaDemo`-[ViewController viewDidLoad] + 192 at ViewController.m:30
    
    # 有时候会遇到的 [__NSArray0 objectAtIndex:] 数组越界,但是编译器没有直接给出具体位置
    # 可以尝试 image lookup -address
    

    比较实用的指令

    # 查询所有 executable、 framework 的装载情况
    call (void)CFShow((void *)CFBundleGetAllBundles())
    

    注意

    调试状态下,对于点语法支持不佳。经常有符号未找到的情况。

    调试状态下,还有注意类型转换、和返回值。

    建议:声明类型 + 使用发送消息的方式,必要时强制转换类型,申明返回值

    不输入命令的时候直接按回车,就会执行上一次执行的命令。

    dSYM

    如果没有 dSYM 文件,我们在源代码上设置断点就无法打到二进制文件上,就无法调试。

    通常只要拿到 ipa 和 dSYM 就可以 Attach to Process 进行调试了,不需要工程源码。

     # 手动添加一个 dSYM 文件
    target symbols add/add-dsym
    

    这一部分整理较少,之后会进行补充

    Chisel

    安装

    # 我自己是 brew reinstall chisel 才成功的
    brew install chisel 
    # 创建配置
    touch ~/.lldbinit 
    open ~/.lldbinit
    # 添加配置
    command script import /usr/local/opt/chisel/libexec/fblldb.py
    

    LLDB 具有完备的 Python 支持,Chisel 就是 Python 实现脚本。

    对于自定义脚本也可以添加到~ /.lldbinit中去

    对于 alias 起了别名的命令也可以加进去 command alias bpl breakpoint list

    打印相关

    命令 说明
    pviews 递归打印 key window 中的 view,前面介绍过了,相当于 [self.view recursiveDescription]
    pvc 递归打印 key window 中的 view controller
    presponder 打印指定对象的响应链
    pinternals 打印一个对象内部结构,用于查看属性、成员变量。
    pclass 打印一个对象的继承关系
    paltrace 打印 View的 autolayout 详细信息,相当于调用_autolayoutTrace
    ptv 打印 View 层级中最上面的一个 tableView,主要和 pcells 配合使用
    pcells 打印 View 层级中最上面的一个 tableView 的可见 cells
    pdata 需要指定 encoding 类型,相当于 [NSString initWithData:encoding:]
    pkp 打印 key path 对应的值,相当于 valueForKeyPath:
    pivar 打印对象成员变量, 注意:如果是属性,对应成员变量的名字默认有_前缀。

    查找相关

    命令 说明
    fv 根据类名模糊匹配 UIView,支持正则
    fvc 根据类名模糊匹配 UIViewController,支持正则
    taplog 输入 taplog 后,可以进行一次 UI 交互,打印点击的 View
    flicker 将 View 闪烁一下,以便于查找 View 的位置。
    vs 在 View 层级中搜索 View,并改变背景色显示出来。

    显示相关

    命令 说明
    visualize 使用 Mac 的预览.app 来预览 UIImage, CGImageRef, UIView, CALayer, NSData (of an image), UIColor, CIColor, or CGColorRef
    show/hide 显示/隐藏指定的 View
    mask/unmask 为 View 添加/取消遮罩
    border/unborder 为 View 添加/取消边框,用法: border 0x79ec3140 -c green -w 2
    slowanim/unslowanim 减慢/还原动画的速度(0~1)
    alamborder/ alamunborder 给存在 Ambiguous Layouts 问题的 View 加上/取消 Border (可以设置颜色,边框宽度 -c -w)
    caflush 实时刷新视图渲染,前面介绍过了,相当于 [CATransaction flush]

    常规调试

    命令 说明
    bmessage 添加一个符号断点,前面介绍过了
    wivar 给实例添加 watchpoint,观察赋值变化,前面也介绍过了

    参考

    下一期

    Runtime Sanitization
    • Address Sanitizer(地址消毒剂)
    • Thread Sanitizer
    Memory Management
    • Malloc Scribble
    • Malloc Guard Edges和Guard Malloc
    • Zombie Objects(僵尸对象)
    Instrument
    • Leaks
    • Time Profiler
    • Allocations
    • Core Animation

    相关文章

      网友评论

        本文标题:Xcode LLVM 高级调试技巧

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