LLDB调试和实战

LLDB

LLDB官方文档教程

  • Xcode4.0开始,编译器改用LLVM,调试器从gdb改为LLDB
  • LLDB全称Low Level Debugger,轻量级的高性能调试器,默认内置于Xcode中
  • 查看已设置的所有断点信息:breakpoint list
  • 查看设置断点的帮助文档:help breakpoint set
    • -n用来给方法名设置断点,范围是所有相同方法名的文件
    • -f用来指定哪个文件,一般是带.m的文件
    • -l 数字可以指定哪一行
    • -c表示条件断点判断
    • -o表示提供一次断点
  • 更多help
    • help thread
    • help breakpoint set
  • 设置观察点,类似KVO功能:watchpoint set var self->_testA(当变量发生变化时便会断下来)在观察页面断点,然后输入命令设置观察点,之后继续运行便可开启观察了
  • 无论网络返回什么值,变量jumpUrl都固定为某一value:在代码行左边设置断点,然后Edit Breakpoint,Condition为空,Action选择Debugger Command,内容填入:expression jumpUrl = @"https://www.baidu.com" 再勾选上Options的Automatically cotinue即可固定jumpUrl的值
  • 列举所有断点:breakpoint list
  • 删除断点:breakpoint delete index
  • 禁用/开启断点:breakpoint disable/enable index
  • 给所有方法名为xx的函数设置断点(C或OC方法都行)
    • breakpoint set -name xx
    • br s -n xx
    • b xx
  • 给特定类的某个方法添加断点:breakpoint set -name "-[ClassName test:]"
  • 给OC方法添加断点:breakpoint set --selector 方法名(等价于breakpoint set -S 方法名)
  • 给某行代码添加断点:breakpoint set -f file.m -l 行数
  • 当程序断点调试完后,可用命令continue,让程序继续运行
  • 若某方法不想被执行,可以不用注释代码重新运行程序,在方法开始位置加断点,待程序执行到该位置,执行命令:thread return [],断点后的内容不会执行,Xcode会出现堆栈向上回溯现象,然后点击LLDB continue按钮继续运行,程序不会执行到方法的内容。
  • call:调用。提供一个按钮,在按钮方法中设置断点,控制台上键入命令:call [self.view setBackgroundColor:[UIColor redColor]],回车,然后继续执行程序,可以看到控制器背景颜色被修改了
  • 进制打印。p默认按十进制输出,p/x按十六进制,p/t按二进制,p/o按八进制
    • 打印字符的ASCII:p/d 'A'
  • 有时无法打印frame,用:p (CGRect)[self.view frame]
  • 打印沙盒路径:po NSHomeDirectory()
  • 关于p、po、expersion
    • expression完整语法:expression --
    • print简写为p,功能相当于expression --,--的意思是命令的参数终止,跟在--后面的都是命令的表达式、数据变量等。p用来处理基本数据类型
    • print object简写为po,功能相当于expression -O -- anObj。po用来处理oc对象类型
  • print a输出 $0 = 20$表示变量或对象的引用,我们可以用$0来操作该变量或对象:
(lldb) print a
(NSInteger) $0 = 20
(lldb) exp $0 = 100
(NSInteger) $9 = 100
(lldb) p a
(NSInteger) $10 = 100
  • 流控制命令
    • 继续:process continue, continue, c
    • 下一步:thread step-over, next, n
    • 进入:thread step-in, step, s
    • 跳出:thread step-out, finish, f
  • 给指定函数地址func_addr位置设置断点:breakpoint set -a func_addr
  • frame表示堆栈帧
    • thread info:显示断点所在当前线程的当前堆栈信息,即调用对象,方法名称和传入参数
    • thread backtrace可看到当前线程(主线程)的堆栈信息,即堆栈帧列表,以frame #编号形式陈列
    • frame variable:打印断点所在作用域的所有变量,包括调用对象,方法名称,传入参数和所有临时变量
    • frame select number:进入所在堆栈帧查看信息。若该堆栈帧在方法体中,会打印出具体的方法体代码。另外可通过命令down让帧向上走(Xcode左边的show Debug navigator可看到指示帧),命令up让帧向下走来控制帧移动。

接手新项目,不看代码直接锁定控制器

老生常谈的符号断点 for Xcode ,找出你想要的ViewController

  • 用符号断点(Symbolic breakpoint)来拦截UIViewController的viewDidLoad(只能是viewDidLoad,因每个控制器几乎都实现了viewDidLoad),可让程序暂停在viewDidLoad的堆栈顺序里,Symbolic中做如下设置:
-[UIViewController viewDidLoad]
  • 优化
    但这么操作很麻烦,总是要暂停。所以,更好的方式是把断点加在BaseViewController里,然后勾上断点的automatically continue after evaluating actions选项,Action选项选择Debugger command,填写命令是po self,就把当前的ViewController名称打印在控制台。
    (大多数项目里的ViewController都是继承自BaseViewController,而BaseViewController又是继承自UIViewController的。不设置到UIViewController,是因为控制台里使用po命令找不到self这个标志)
  • 更新20190710——由于公司项目没有基类控制器BaseViewController,经过大量命令尝试,发现了一个巧妙的方法,也是通过添加Symbolic断点来打印控制器名称,程序无需停下来。Symbolic中Action选择Debugger command,命令为
thread return
thread return
po self
continue

注意千万不能勾选automatically continue,否则app会卡死,continue本身就表示遇到断点也继续执行。

报出exc_bad_access错误

  • 这是由于访问已释放的对象(僵尸对象)导致的,定位错误位置,用如下方式:
    • Xcode的Product->Scheme->Edit Scheme,设置Run(Debug)的Diagnostics选项的Memory Management,勾选Zombie Object。
      其实就是开启僵尸对象检测,默认情况下不要开启,否则系统会检测指针访问的对象是否是僵尸对象,影响效率。记得检测完后关闭它。
    • 原理:对象释放时,retainCount为0,使用内置的Zombie对象,替代原来被释放的对象,无论向该对象发送什么消息,都会触发异常,抛出调试信息。
  • 注意:访问已释放对象有时候正常有时候会崩溃,非常危险。之所以有时候正常,是当僵尸对象所在内存空间系统还没分配给别人时,这个时候还是可以访问的,因为对象数据还在。详见:iOS-野指针与僵尸对象

实战

定位点击事件位置的几种方法

点击事件涉及几种情况:tableViewCell、collectionViewCell、UIButton、UIImageView或UIView的手势tab点击,每一种拦截方式都不同,但只要懂得锁定这几种方式的技巧,就能快速找到点击事件的代码位置。

  • 先暂停LLDB运行,控制台输入命令"br s -r . -s projectName",然后点击待观察按钮,之后点击LLDB continue execution按钮继续运行,便能在Xcode左边的堆栈列表中找到代码位置。发现该命令是给整个工程的所有方法打了断点,从而实现点击定位(这会引起很多问题,很久之前找到的方式,不要用了,看下面的)。
  • 【表格cell点击定位】先暂停LLDB运行,控制台输入命令breakpoint set -S 方法名(注意:方法名是取@selecter()括号中的内容),该命令等价于
breakpoint set --selector 方法名

表格cell点击一般有tableViewCell或collectionViewCell点击,方法名为tableView:didSelectRowAtIndexPath: 或collectionView:didSelectItemAtIndexPath:。若断点设置成功,会输出如下:

Breakpoint 1: 77 locations

以上1表示方法名称标识号,77表示成功设置了77处涉及该方法名称的断点。也可用命令 breakpoint list查看已设置的所有断点
之后点击LLDB continue execution按钮继续运行,然后点击视图便可在Xcode左边的堆栈列表中找到代码位置。如果不需要了,可以重新运行Xcode 或删除该断点:

breakpoint delete 1    【#这里1是上述方法名称的标识号】

更多用法可用命令help breakpoint set查看。

  • 【按钮点击定位】添加Symbol断点,Edit breakpoint的Symbol填写-[UIControl sendAction:to:forEvent:] 或用LLDB在控制台添加断点。点击目标按钮,堆栈会停在sendAction:to:forEvent中,LLDB命令register read,可查看到21个寄存器的信息,如下所示
(lldb) register read
General Purpose Registers:
       rax = 0x00007fc8efc72db0
       rbx = 0x00007fc8efc72db0
       rcx = 0x00007fc8efc72db0
       rdx = 0x000000010e603878 "itemClick:"
       rdi = 0x00007fc8efc76a50
       rsi = 0x0000000111354ce4 "sendAction:to:forEvent:"
       rbp = 0x00007ffee16bb7a0
       ...
        fs = 0x0000000000000000
        gs = 0x0000000000000000

在rdx后面可看到点击事件的方法名称itemClick:,此时不要激动,我们还需要找到方法调用者,因为工程中可能存在多个同名方法。怎么操作?答案是LLDB命令po $rax,打印的便是调用者对象,如

(lldb) po $rax
>

如此我们便可知道有[HomeNavigationGridView itemClick:],从而定位到按钮点击事件在程序中的代码位置。

  • 【手势点击定位】对于用手势UITapGestureRecognizer添加的点击事件,根据上述步骤操作,用命令-[UIGestureRecognizerTarget _sendActionWithGestureRecognizer:]来定位代码。这个命令是给手势事件打断点在堆栈中获取到的。
    之后用LLDB命令po $raxpo $rdx便可获取到完整的调用者和方法名称,如
(lldb) po $rax
(action=informationTap:, target=)

(lldb) po $rdx
; target= <(action=informationTap:, target=)>>

由此可知有[HotInformationView informationTap:HotInformationItem],从而定位到手势事件的代码

  • 可能遇到的问题。如果该点击事件用来跳转,且跳转后的控制器中含有UIWebView或WKWebView控件,由于该控件内部封装了一些手势,会影响我们的断点定位,可能导致定位失败,一直卡在UIWebView或WKWebView控件内部封装的手势堆栈中。
    解决方式:控制器跳转定位。一般控制器跳转都会经过pushViewController: animated:方法,

    • 若项目继承了UINavigationController自定义导航栏控制器,则可以在自定义导航栏控制器中重写pushViewController: animated:,然后给该方法打断点,则点击时发生控制器跳转会在该方法中停下来,通过左边的堆栈列表信息可以回溯到上一方法,即点击事件和控制器跳转所在位置。
    • 若项目没有自定义导航栏控制器,则可以填写Symbolic断点,Edit breakpoint的Symbolic填写命令-[UINavigationController pushViewController:animated:]也能在左边的堆栈列表信息中回溯到上一方法。
  • 资料

    • XCode LLDB调试小技巧基础篇提高篇汇编篇——精品1.2k,底部有详细的参数--进制对照表
    • 小笨狼与LLDB的故事——1.2k
    • iOS开发断点调试高级技巧
    • Xcode关于LLDB的使用
    • 逆向使用lldb调试命令(cycript)——Xcode的Debug View Hierarchy能够可视化视图层级,选中按钮,在右边的Show the Object Inspector能看到按钮类型和地址,target,Action(Selector)。选中UIImageView,在Accessibility--Identifier一栏能看到图片名称。
    • LLDB调试利器及高级用法

你可能感兴趣的:(LLDB调试和实战)