iOS 开发者旅途中的指南针 - LLDB 调试技术

文章转载于:iOS 开发者旅途中的指南针 - LLDB 调试技术

今天给大家介绍的内容,无关乎任何功能性开发技术,但又对开发的效率影响至深,这就是调试技术。

何为调试呢,比如我们用 print 函数在指定位置进行输出,来定位某些节点的变量内的取值:

12345
let result = parseJSON("[1,2,3]");print(result);

result = parseJSON("error");print(result);4

相信我们大家看到类似这样的代码都不会陌生,估计为开发者朋友都会或多或少的用这样的方式对程序进行调试。

这种方式有它的方便之处,就是我们不需要太多思考,需要跟踪某些地方的时候,直接输出就可以得到调试信息了。但这样做也有它的弊端,就是我们每次这样调试,都要反复的编译,运行,然后写进新的 print 语句,再继续编译,运行。反复的编译,运行会比较消耗时间。并且我们再调试完之后,很容易会忘记将调试语句删除,导致很多输出语句遗留再代码中,随着项目的长期进展后,这样会对项目后期的调试造成很多干扰。

而且,当我们想再次调试这段区域的时候,我们不得不再次写上这些输出语句。而有时对于稍微复杂一些的调试场景,print 输出这样的方式,往往还不能太好的应对。

那么有什么办法能解决这些麻烦呢,那就是调试技术,调试器几乎在大多数现代的开发环境中都会有,所以,iOS 开发也不例外,Xcode 环境为我们提供的对应调试工具就是 LLDB。

###初识 LLDB

LLDB 就是 XCode 为我们提供的调试工具。那么说了这么多,到底什么是调试工具呢? 也许说调试器你可能会感到比较陌生,但说到断点,相信你就会听着比较耳熟了。我们还以刚才我们提到的代码为例:

我们在第 23 行左边点击了一下,就创建了一个断点,这时我们再运行这个应用的时候,程序运行到这里就会被断点拦截:


并且在 Xcode 的命令行区域,显示了 (lldb) 提示符。

###基本调试操作
我们回到最初的问题,如果不使用 print 输出,我们怎么能得到 result 的值呢,这就是我们要讨论的断点调试机制了,我们先看一下 XCode 底部调试区域的几个按钮:

*第一个按钮是继续的意思,会让程序从断点处恢复,继续往下运行,我们点了这个按钮后,应用就会恢复正常运行状态。

*第二个按钮是(Step Over),单步执行的意思,每点这个按钮一次,程序就会从我们断点开始的地方,向下执行一步。

*第三个按钮是 (Step In),进入执行的意思,简单来说就是如果我们当前的断点在一个函数调用上,把么断点会继续进入这个函数的内部进行调试。

*第四个按钮是(Step Out),跳出的意思, 就是如果我们当前再一个函数中,它会跳出当前的函数,回到函数的调用处。

恩。。你说了这么多,完全听不懂啊

没关系,我们一一道来,还是回到我们最初的需求,我们的断点现在停在给 result 变量赋值的这条语句中,断点所在位置的语句是还没有被执行的,所以我们需要点一下 Step Over 按钮(也就是我们刚才列出的四个按钮的第二个),让程序执行一行代码。

执行完这行代码后,我们的 result 变量就被赋值完成了。那么问题来了,我们怎么得到 result 变量中得内容呢?

还记得我们的 LLDB 命令行么,我们使用一个叫做 po 的命令,就可以取到这个变量

现在,我们使用 LLDB 命令达到了和 print 语句同样的效果,得到了result 变量的取值。那么问题又来了,这样做有什么好处呢,怎么感觉比直接使用 print 输出更麻烦了呢?

###LLDB 探索之旅
LLDB 为我们提供了很多方便使用的命令,我们再 LLDB 命令行中,输入 help 命令即可看到这些命令的帮助信息:

12345678910111213141516
Debugger commands:

  apropos           -- Find a list of debugger commands related to a particular                       word/subject.  breakpoint        -- A set of commands for operating on breakpoints. Also see                       _regexp-break.  command           -- A set of commands for managing or customizing the                       debugger commands.  disassemble       -- Disassemble bytes in the current function, or elsewhere                       in the executable program as specified by the user.  expression        -- Evaluate an expression (ObjC++ or Swift) in the current                       program context, using user defined variables and                       variables currently in scope.  frame             -- A set of commands for operating on the current thread‘s                       frames.  ...............

这里我们看到了LLDB 命令的列表,要想获得某个命令更详细的帮助,我们开可以输入 help 命令名, 比如我们输入 help expression:

12345678910
help expression     Evaluate an expression (ObjC++ or Swift) in the current program context,     using user defined variables and variables currently in scope.  This     command takes ‘raw‘ input (no need to quote stuff).

Syntax: expression <cmd-options> -- <expr>

Command Options Usage:  expression [-AFLORTg] [-f <format>] [-G <gdb-format>] [-l <language>] [-a <boolean>] [-i <boolean>] [-t <unsigned-integer>] [-u <boolean>] [-v[<description-verbosity>]] [-d <none>] [-S <boolean>] [-D <count>] [-P <count>] [-Y[<count>]] [-V <boolean>] -- <expr>  expression [-AFLORTg] [-l <language>] [-a <boolean>] [-i <boolean>] [-t <unsigned-integer>] [-u <boolean>] [-d <none>] [-S <boolean>] [-D <count>] [-P <count>] [-Y[<count>]] [-V <boolean>] -- <expr>

就得到了关于 expression 命令的介绍。

基本情况就说这么多,那么咱们就来实践一下,体验一下 LLDB 的强大之处。

我们来看一个更强大的命令 expression, 我们来看一下它的描述:

123
Evaluate an expression (ObjC++ or Swift) in the current program context,using user defined variables and variables currently in scope. Thiscommand takes ‘raw‘ input (no need to quote stuff).

翻译一下哈,意思就是在当前程序环境中,执行任何的表达式,并且可以定义和操作已存在的变量。

怎么样,让我说的更具体吧,有了 LLDB我们不但可以在断点处输出某个变量的值,我们还可以修改甚至重新定义某些变量的值。

咱们开始吧,将我们刚才的程序做一下修改

1234567891011
var result = parseJSON("[1,2,3]");

if result?.count == 0 {

    print("No Data");

}else{

    print(result);

}

我们这里对 result 变量进行了判断,并进行了分别的输出。下面我们以让将断点设置到第一个语句上,然后运行程序。再断点处我们用 po命令来打印出 result 的值。

这时候,result 中的值,应该是解析后的 JSON 数组。所以我们恢复程序执行后,接下来的if 判断会走第二个分支,输出 result 中的内容。

那么如果我们在刚才断点时候,运行这个命令呢

1
e result = []

这里我们将 result的值修改为一个空数组,然后我们继续程序,接着你会发现,下面的 if 判断走了第一个分支,也就是说我们在断点处对变量进行的修改,是对全局程序生效的。

怎么样这个能力是我们之前的调试方法不能达到的吧~

我们上面的 e 命令是 expression 命令的缩写,详情可以参考 LLDB help 命令的帮助。

###控制流快捷命令
我们继续探索,还记得前面我们提到的几个控制流按钮吗,也就是这张图片

在 LLDB 命令行中,对于每个流程控制按钮都有相应的命令。

*n 命令,代表 Step Over 操作。

*s 命令,代表 Step Into 操作。

*finish 命令,代表 Step Out 操作。

*c 命令,代表恢复程序执行操作。

我们还是以这个程序为例,这次我们使用控制流命令来进行操作

我们运行这个程序,然后在断点检测到时,按照下面的顺序输入 LLDB 命令:

1234
snnc

我们第一个输入的s 命令,会步入 parseJSON 函数的调用,然后断点就会进入parseJSON函数中。

随后,我们又输入了n 命令,由于 parseJSON 中只有一个 return 语句,那么控制流就会跳出 parseJSON函数体,重新回到开始处。

紧接着我们再次输入n命令,这时候程序就会将parseJSON的结果赋值给result

最后我们按下 c 命令,来恢复程序的执行。随后的if判断中就会按照相应的条件输出内容了。
怎么样,这样操作起来就比较方便了,我们不必用鼠标点来点去了,完全用键盘敲命令就可以完成控制流的操作了。

###断点创建命令

我们除了通过用鼠标在代码行的左边点击的方式创建断点以外,我们还可以使用 LLDB 来创建断点,比如要创建一个我们之前这样的断点:

我们可以输入这样一条命令:

12
(lldb) breakpoint set -f ViewController.swift -l 28Breakpoint 2: where = Example`Example.ViewController.viewDidLoad (Example.ViewController)() -> () + 478 at ViewController.swift:29, address= 0x000000010f74f61e

输入命令后,紧接着会有一行输出,告诉我们断点创建成功,并且显示了创建的新断点的位置等基本信息。

这个命令也有简写形式:b ViewController.swift:28

我们还可以将断点直接设置到函数上,假设我们有这样一个函数:

12345
func add(a:Int, b:Int) -> Int {

    return a + b;

}

我们还可以这样设置断点:b add

这样就将断点设置再了 add 函数调用的开始位置。

我们开可以设置符号断点,比如这样:b -[NSArray objectAtIndex:]

这个断点会将所有对于 NSArray 的 objectAtIndex 方法的调用设置为断点。这里包括我们开发者对它的调用,以及系统框架内部对它的调用。符号断点对于调试是一个很好用的工具,它能够跟踪那些我们引用的系统库中的代码出现的问题。

我们还可以对已经创建的断点设置激发条件:

我们上面设置的条件表示,只有在 result 的 count 属性等于 0 的时候,断点才会被激发。

是不是觉得 LLDB 有点意思了呢。

###开始探险

那么现在我们就来用 LLDB 完成一些更加有意思的事情吧。

我们首先创建一个示例项目:

项目类型选择 Single View Application

然后点击 Next, 项目信息中的 Language 选择 Swift:

点击 Next 然后出现项目存储位置的选择,选择一个你自己的存储位置。接下来我们在 Main.storyboard 中拖放一个 Button 放到右上角:

随后,我们按住 Option 键,然后点击 ViewController.swift 文件,可以在设计界面旁边打开辅助界面。打开后,我们按住 Control 键,然后将我们刚刚创建的按钮拖动到代码视图中:

然后松开鼠标按键,我们就会看到一个弹出菜单:

我们将 Connection 的类型选择为 Action, Name 输入为buttonClicked,其他不用更改,然后点击`按钮。这样就完成了按钮事件的创建。

接下来,我们运行应用,就可以看到这样的界面了:

一切就绪,现在就可以展开我们的 LLDB 大法啦~

其实我们还可以不通过断点的方式来打开 LLDB 命令行,在我们先将程序运行起来,然后我们看一下调试区域的按钮:

注意下,蓝色的断点开关按钮右边还有一个暂停按钮,我们只需要点这个暂停按钮,就可以进入 LLDB 命令行调试状态。因为 LLDB 在 Xcode 运行中是一直驻留在后台的,所以我们其实是可以在任何时间都可以启动 LLDB 命令行的。

打开 LLDB 命令行后,我们可以输入这个命令,打印出当前的视图层级(又学一招~):

1234567
(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]<UIWindow: 0x7ffcd2f0f1e0; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x7ffcd2f10170>; layer = <UIWindowLayer: 0x7ffcd2f0ea80>>   | <UIView: 0x7ffcd2c6dc10; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7ffcd2c17f10>>   |    | <UIButton: 0x7ffcd2c6dfc0; frame = (20 62; 78 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7ffcd2c6bc10>>   |    |    | <UIButtonLabel: 0x7ffcd2f15af0; frame = (16 6; 46 18); text = ‘Button‘; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7ffcd2f16120>>   |    | <_UILayoutGuide: 0x7ffcd2c6fae0; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x7ffcd2c6faa0>>   |    | <_UILayoutGuide: 0x7ffcd2c70740; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x7ffcd2c6d3d0>>

大家仔细看一下,每个视图的标识中,都有一个 16进制的字符串,代表这个视图的 ID,比如这个:

UIView: 0x7ffcd2c6dc10

这个 ID 的作用非常的强大,得到了这个 ID, 我们就可以通过这个命令来得到这个视图的引用了:

1
(lldb) e id $view = (id) 0x7fbd71432590

简单解释下,通过 expression命令(这里用缩写形式 e),我们用 View 的 ID 值取得了这个 View 引用,并将它保存到 $view 变量中。

我们得到了引用之后,就可以对这个视图进行很多的操作了,比如我们可以在运行时改变这个视图的背景色:

1
(lldb) e (void) [$view setBackgroundColor:[UIColor redColor]]

当然,我们运行完这条命令,界面上不会马上反应出来,我们还需要调用这个命令刷新一下:

1
(lldb) e (void)[CATransaction flush]

这样,我们在看一下我们运行的程序,主界面的背景色变成红色了吧。

我们甚至还可以用它来找到某些控件上添加的事件,我们找到我们自己添加的 UIBUtton 的 ID:

UIButton: 0x7ffcd2c6dfc0

然后运行下面的命令:

123456
(lldb) e id $button = (id) 0x7ffcd2c6dfc0

(lldb) po [$button  allTargets]{(    <lldb.ViewController: 0x7feff2d67330>)}

我们得到 UIButton 的引用后,然后又输出了他的 allTargets 属性,得到了这个UIButton 所对应的事件target 对象的地址,接下来我们再用刚刚得到的这个target 地址获取它的 action 属性:

1234
(lldb) po [$button actionsForTarget:(id)0x7feff2d67330 forControlEvent:0]<__NSArrayM 0x7feff2c22350>(buttonClicked:)

我们这样就得到了,这个按钮所对应的方法名了。那么接下来,我们可以在这个方法上设置断点,或者用 LLDB 的运行时能力替换这个方法的实现等等。总之,对于我们调试应用来说,LLDB 是一个非常强大而高效的工具。这里只介绍了它的冰山一角,关于更多的内容,大家可以使用 help 命令,进行深入的研究。相信大家发挥聪明才智,能够发现更多它的强大之处。

###一点点延展,关于 Chisel

最后,再给大家延展一下。LLDB 本身的命令系统非常健壮,并且它还支持 Python 的脚本扩展,这样它又有了很不错的扩展性,我们可以根据自己的需要来扩展自己的脚本。

Chisel 正是 LLDB 扩展的一个典型例子,这是由 Facebook 团队开发的一个开源的 LLDB 的 Python 扩展集合,它再 LLDB 命令的基础上,又为我们提供了更加方便的操作接口。

比如我们要打印当前的视图层级,如果用 LLDB 原生的命令,我们需要这样:

1
(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]

而 Chisel 为我们提供了更简洁的接口:

1
(lldb) pviews

同样的,这条用于刷新显示的命令:

1
(lldb) e (void)[CATransaction flush]

Chisel 也为我们提供了简便的接口:

1
(lldb) caflush

这里只给大家做一个简单的介绍,关于 Chisel 的更多内容,大家可以参看它的主页:https://github.com/facebook/chisel

LLDB 自身完善的命令行系统,以及它的扩展能力,都成为提升我们开发效率的利器。正确的使用好调试工具,一定会帮助我们快速的解决更多的问题。

现在,通过 help 命令,来开始对 LLDB 命令行的探索吧,相信你能在这里发现更多的宝藏。

时间: 2024-08-04 21:14:41

iOS 开发者旅途中的指南针 - LLDB 调试技术的相关文章

关于iOS开发者账号功能总结-真机调试(二)

转载请注明出处:http://blog.csdn.net/dengbin9009/article/details/43970059 在上篇文章中说道如何从开发者网站创建App ID,添加Device,创建并下载开发证书,创建并下载配置文件,详情见:关于iOS开发者账号功能总结-真机调试(一). 在这篇文章中将要介绍的时如何利用下载号的证书cer文件和配置文件mobileprovision. 上篇文章我们已经将证书和配置文件下载到桌面,不过为了Mac的贞洁(笔者处女座),已经把两个文件放到特定的文

关于iOS开发者账号功能总结-真机调试(一)

转载请注明出处:http://blog.csdn.net/dengbin9009/article/details/43966163 在日常的iOS开发中,模拟器虽然可以完成大部分的功能调试,但是由于电脑和手机上的CPU和GPU的差别,在对某些处理时确实会有表象不同的时候. 所以真机调试必不可少,而在iOS开发中需要用到真机调试就必须要有匹配的证书,下面就告诉大家如何从刚到手的一个开发中账号到真机调试的完整过程. 先总体说一下真机调试的流程: 创建AppID -> 添加Devices -> 创建

iOS LLDB调试器

随着Xcode 5的发布,LLDB调试器已经取代了GDB,成为了Xcode工程中默认的调试器.它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能.LLDB为Xcode提供了底层调试环境,其中包括内嵌在Xcode IDE中的位于调试区域的控制面板,在这里我们可以直接调用LLDB命令.如图1所示: 图1:位于Xcode调试区域的控制台 在本文中,我们主要整理一下LLDB调试器提供给我们的调试命令,更详细的内容可以查看The LLDB Debugger. LLDB命令结构 在使用LL

【转】浅谈LLDB调试器

随着Xcode 5的发布,LLDB调试器已经取代了GDB,成为了Xcode工程中默认的调试器.它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能.LLDB为Xcode提供了底层调试环境,其中包括内嵌在Xcode IDE中的位于调试区域的控制面板,在这里我们可以直接调用LLDB命令.如图1所示: 图1:位于Xcode调试区域的控制台 在本文中,我们主要整理一下LLDB调试器提供给我们的调试命令,更详细的内容可以查看The LLDB Debugger. LLDB命令结构 在使用LL

[转]如何通过 App Store 审核(iOS 开发者经验分享)

CocoaChina交流社区曾经发起一个主题讨论,关于iOS开发者遇到审核失败的原因及解决办法的,有价值的回复内容如下: wubo9935 App中设计的图标与Apple原生图标类似,Apple原生图标有专利保护,并且在Design Guideline里面规定,App的图标不能与Apple图标雷同,如iTunes,App Store, iPod等的图标.若出现雷同App将被拒. 逐风 App的设置界面.按钮使用了类似iPhone的操作方式以及icon的圆角设计 -> 重新设计… App的年龄设置

在chrome开发者工具中观察函数调用栈、作用域链与闭包

在chrome开发者工具中观察函数调用栈.作用域链与闭包 在chrome的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察JavaScript的执行过程,直观感知函数调用栈,作用域链,变量对象,闭包,this等关键信息的变化.因此,断点调试对于快速定位代码错误,快速了解代码的执行过程有着非常重要的作用,这也是我们前端开发者必不可少的一个高级技能. 当然如果你对JavaScript的这些基础概念[执行上下文,变量对象,闭包,this等]了解还不够的话,想要透彻掌握断点调试可能会有一些困

ios逆向过程中lldb调试技巧

在ios逆向过程中,善于运用lldb,会给逆向带来很大的方便 一般的命令: 1.image list -o -f  看看各个模块在内存中的基址 2.register read r0  读取寄存器r0的值.register read  读取所有寄存器的值 3.expression(或者缩写expr)  表达式 例子: expression $r6 = 1   // 设置r6寄存器的值 expression $r6       // 查看r6寄存器的值 expression username(源代码

iOS中教你快速掌握LLDB调试技巧

摘要 LLDB是Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能.平时用Xcode运行程序,实际走的都是LLDB.熟练使用LLDB,可以让你debug事半功倍. LLDB控制台 Xcode中内嵌了LLDB控制台,在Xcode中代码的下方,我们可以看到LLDB控制台. LLDB控制台平时会输出一些log信息.如果我们想输入命令调试,必须让程序进入暂停状态.让程序进入暂停状态的方式主要有2种: 1. 断点或者watchpoint: 在代码中设置一个断点(w

学习笔记之--认识Xcode中的重要成员:lldb调试器

之前对lldb调试器了解比较少,平时主要用来打印日志和暂定时用鼠标查看属性数据以及使用p po一些简单的命令语句. 今天看了一些关于lldb的文章,顿时觉得之前对它了解太少了,原来它还有那么多的功能. 好记性不如烂笔头,我把方便易用的命令记录下来,方便以后查看. 一.ldb的语法结构 lldb的语法结构如下:<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]