objc.io#19#与调试器共舞 - LLDB 的华尔兹

10 篇文章 0 订阅
9 篇文章 0 订阅

你是否曾经苦恼于理解你的代码,而去尝试打印一个变量的值?

1
NSLog(@ "%@" , whatIsInsideThisThing);

或者跳过一个函数调用来简化程序的行为?

1
NSNumber *n = @7;  // 实际应该调用这个函数:Foo();

或者短路一个逻辑检查?

1
if  (1 || theBooleanAtStake) { ... }

或者伪造一个函数实现?

1
2
3
4
5
6
int calculateTheTrickyValue {
   return  9;
   /*
    先这么着
    ...
}

并且每次必须重新编译,从头开始?

构建软件是复杂的,并且 Bug 总会出现。一个常见的修复周期就是修改代码,编译,重新运行,并且祈祷出现最好的结果。

但是不一定要这么做。你可以使用调试器。而且即使你已经知道如何使用调试器检查变量,它可以做的还有很多。

这篇文章将试图挑战你对调试的认知,并详细地解释一些你可能还不了解的基本原理,然后展示一系列有趣的例子。现在就让我们开始与调试器共舞一曲华尔兹,看看最后能达到怎样的高度。

LLDB

LLDB是一个有着 REPL 的特性和 C++ ,Python 插件的开源调试器。LLDB 绑定在 Xcode 内部,存在于主窗口底部的控制台中。调试器允许你在程序运行的特定时暂停它,你可以查看变量的值,执行自定的指令,并且按照你所认为合适的步骤来操作程序的进展。(这里有一个关于调试器如何工作的总体的解释。)

你以前有可能已经使用过调试器,即使只是在 Xcode 的界面上加一些断点。但是通过一些小的技巧,你就可以做一些非常酷的事情。GDB to LLDB 参考是一个非常好的调试器可用命令的总览。你也可以安装 Chisel,它是一个开源的 LLDB 插件合辑,这会使调试变得更加有趣。

与此同时,让我们以在调试器中打印变量来开始我们的旅程吧。

基础

这里有一个简单的小程序,它会打印一个字符串。注意断点已经被加在第 8 行。断点可以通过点击 Xcode 的源码窗口的侧边槽进行创建。

1.png

程序会在这一行停止运行,并且控制台会被打开,允许我们和调试器交互。那我们应该打些什么呢?

help

最简单命令是 help,它会列举出所有的命令。如果你忘记了一个命令是做什么的,或者想知道更多的话,你可以通过 help来了解更多细节,例如 help print 或者 help thread。如果你甚至忘记了 help 命令是做什么的,你可以试试 help help。不过你如果知道这么做,那就说明你大概还没有忘光这个命令。??

print

打印值很简单;只要试试 print 命令:

2.png

LLDB 实际上会作前缀匹配。所以你也可以使用 prin,pri,或者 p。但你不能使用 pr,因为 LLDB 不能消除和 process 的歧义 (幸运的是 p 并没有歧义)。

你可能还注意到了,结果中有个 0使print 0 。 实 际 上 你 可 以 使 用 它 来 指 向 这 个 结 果 。 试 试 p r i n t 0 + 7,你会看到 106。任何以美元符开头的东西都是存在于 LLDB 的命名空间的,它们是为了帮助你进行调试而存在的。

expression

如果想改变一个值怎么办?你或许会猜 modify。其实这时候我们要用到的是 expression 这个方便的命令。

这不仅会改变调试器中的值,实际上它改变了程序中的值。这时候继续执行程序,将会打印 42 red balloons。神奇吧。

注意,从现在开始,我们将会偷懒分别以 p 和 e 来代替 print 和 expression。

什么是 print 命令

考虑一个有意思的表达式:p count = 18。如果我们运行这条命令,然后打印 count 的内容。我们将看到它的结果与 expression count = 18 一样。

和 expression 不同的是,print 命令不需要参数。比如 e -h +17 中,你很难区分到底是以 -h 为标识,仅仅执行 +17 呢,还是要计算 17 和 h 的差值。连字符号确实很让人困惑,你或许得不到自己想要的结果。

幸运的是,解决方案很简单。用 – 来表征标识的结束,以及输入的开始。如果想要 -h 作为标识,就用 e -h – +17,如果想计算它们的差值,就使用 e – -h +17。因为一般来说不使用标识的情况比较多,所以 e – 就有了一个简写的方式,那就是 print。

输入 help print,然后向下滚动,你会发现:

1
2
'print'  is an abbreviation  for  'expression --' .   
(print是 `expression --` 的缩写)

打印对象

尝试输入

1
p objects

输出会有点啰嗦

1
(NSString *) $7&nbsp;=&nbsp;0x0000000104da4040&nbsp;@</code><code class="js string">"red&nbsp;balloons"</code></div></div></td></tr></tbody></table></div></div><p>如果我们尝试打印结构更复杂的对象,结果甚至会更糟</p><div><div id="highlighter_456668" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;p&nbsp;@[&nbsp;@</code><code class="js string">"foo"</code><code class="js plain">,&nbsp;@</code><code class="js string">"bar"</code>&nbsp;<code class="js plain">]</code></div><div class="line number2 index1 alt1"><code class="js plain">(NSArray&nbsp;*)&nbsp;$8 = 0x00007fdb9b71b3e0 @ "2 objects"

实际上,我们想看的是对象的 description 方法的结果。我么需要使用 -O (字母 O,而不是数字 0) 标志告诉 expression 命令以 对象 (Object) 的方式来打印结果。

1
2
3
4
(lldb) e -O -- $8(</code></div><div class="line number2 index1 alt1"><code class="js plain">foo,</code></div><div class="line number3 index2 alt2"><code class="js plain">bar</code></div><div class="line number4 index3 alt1"><code class="js plain">)</code></div></div></td></tr></tbody></table></div></div><p>幸运的是,e -o -- 有也有个别名,那就是 po (print object 的缩写),我们可以使用它来进行简化:</p><div><div id="highlighter_918088" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div><div class="line number4 index3 alt1">4</div><div class="line number5 index4 alt2">5</div><div class="line number6 index5 alt1">6</div><div class="line number7 index6 alt2">7</div><div class="line number8 index7 alt1">8</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;po&nbsp;$8(
foo,
bar
)
(lldb) po @ "lunar"
lunar
(lldb) p @ "lunar"
(NSString *) $13&nbsp;=&nbsp;0x00007fdb9d0003b0&nbsp;@</code><code class="js string">"lunar"</code></div></div></td></tr></tbody></table></div></div><p><strong>打印变量</strong></p><p>可以给 print 指定不同的打印格式。它们都是以 print/<fmt>或者简化的 p/<fmt>格式书写。下面是一些例子:</fmt></fmt></p><p>默认的格式</p><div><div id="highlighter_167699" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;p&nbsp;16</code></div><div class="line number2 index1 alt1"><code class="js plain">16</code></div></div></td></tr></tbody></table></div></div><p>十六进制:</p><div><div id="highlighter_400482" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;p/x&nbsp;16</code></div><div class="line number2 index1 alt1"><code class="js plain">0x10</code></div></div></td></tr></tbody></table></div></div><p>二进制 (t 代表 two):</p><div><div id="highlighter_144633" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div><div class="line number4 index3 alt1">4</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;p/t&nbsp;16</code></div><div class="line number2 index1 alt1"><code class="js plain">0b00000000000000000000000000010000</code></div><div class="line number3 index2 alt2"><code class="js plain">(lldb)&nbsp;p/t&nbsp;(char)16</code></div><div class="line number4 index3 alt1"><code class="js plain">0b00010000</code></div></div></td></tr></tbody></table></div></div><p>你也可以使用 p/c 打印字符,或者 p/s 打印以空终止的字符串 (译者注:以 '\0' 结尾的字符串)。&nbsp;</p><p><a href="https://sourceware.org/gdb/onlinedocs/gdb/Output-Formats.html" target="_self">这里</a>是格式的完整清单。</p><p><strong>变量</strong></p><p>现在你已经可以打印对象和简单类型,并且知道如何使用 expression 命令在调试器中修改它们了。现在让我们使用一些变量来减少输入量。就像你可以在 C 语言中用 int a = 0 来声明一个变量一样,你也可以在 LLDB 中做同样的事情。不过为了能使用声明的变量,变量必须以美元符开头。</p><div><div id="highlighter_333018" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div><div class="line number4 index3 alt1">4</div><div class="line number5 index4 alt2">5</div><div class="line number6 index5 alt1">6</div><div class="line number7 index6 alt2">7</div><div class="line number8 index7 alt1">8</div><div class="line number9 index8 alt2">9</div><div class="line number10 index9 alt1">10</div><div class="line number11 index10 alt2">11</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;e&nbsp;int&nbsp;$a = 2
(lldb) p $a&nbsp;*&nbsp;19</code></div><div class="line number3 index2 alt2"><code class="js plain">38</code></div><div class="line number4 index3 alt1"><code class="js plain">(lldb)&nbsp;e&nbsp;NSArray&nbsp;*$array = @[ @ "Saturday" , @ "Sunday" , @ "Monday"  ]
(lldb) p [$array&nbsp;count]</code></div><div class="line number6 index5 alt1"><code class="js plain">2</code></div><div class="line number7 index6 alt2"><code class="js plain">(lldb)&nbsp;po&nbsp;[[$array objectAtIndex:0] uppercaseString]
SATURDAY
(lldb) p [[$array&nbsp;objectAtIndex:$a] characterAtIndex:0]
error: no known method  '-characterAtIndex:' ; cast the message send to the method's  return  type
error: 1 errors parsing expression

悲剧了,LLDB 无法确定涉及的类型 (译者注:返回的类型)。这种事情常常发生,给个说明就好了:

1
2
3
4
(lldb) p (char)[[$array&nbsp;objectAtIndex:$a] characterAtIndex:0]
'M'
(lldb) p/d (char)[[$array&nbsp;objectAtIndex:$a] characterAtIndex:0]
77

变量使调试器变的容易使用得多,想不到吧???

流程控制

当你通过 Xcode 的源码编辑器的侧边槽 (或者通过下面的方法) 插入一个断点,程序到达断点时会就会停止运行。

调试条上会出现四个你可以用来控制程序的执行流程的按钮。

4.png

从左到右,四个按钮分别是:continue,step over,step into,step out。

第一个,continue 按钮,会取消程序的暂停,允许程序正常执行 (要么一直执行下去,要么到达下一个断点)。在 LLDB 中,你可以使用 process continue 命令来达到同样的效果,它的别名为 continue,或者也可以缩写为 c。

第二个,step over 按钮,会以黑盒的方式执行一行代码。如果所在这行代码是一个函数调用,那么就不会跳进这个函数,而是会执行这个函数,然后继续。LLDB 则可以使用 thread step-over,next,或者 n 命令。

如果你确实想跳进一个函数调用来调试或者检查程序的执行情况,那就用第三个按钮,step in,或者在LLDB中使用 thread step in,step,或者 s 命令。注意,当前行不是函数调用时,next 和 step 效果是一样的。

大多数人知道 c,n 和 s,但是其实还有第四个按钮,step out。如果你曾经不小心跳进一个函数,但实际上你想跳过它,常见的反应是重复的运行 n 直到函数返回。其实这种情况,step out 按钮是你的救世主。它会继续执行到下一个返回语句 (直到一个堆栈帧结束) 然后再次停止。

例子

考虑下面一段程序:

5.png

假如我们运行程序,让它停止在断点,然后执行下面一些列命令:

1
2
3
4
5
6
7
p i
n
s
p i
finish
p i
frame info

这里,frame info 会告诉你当前的行数和源码文件,以及其他一些信息;查看 help frame,help thread 和 help process 来获得更多信息。这一串命令的结果会是什么?看答案之前请先想一想。

1
2
3
4
5
6
7
8
9
10
11
12
13
(lldb) p i
(int) $0&nbsp;=&nbsp;99</code></div><div class="line number3 index2 alt2"><code class="js plain">(lldb)&nbsp;n</code></div><div class="line number4 index3 alt1"><code class="js plain">2014-11-22&nbsp;10:49:26.445&nbsp;DebuggerDance[60182:4832768]&nbsp;101&nbsp;is&nbsp;odd!</code></div><div class="line number5 index4 alt2"><code class="js plain">(lldb)&nbsp;s</code></div><div class="line number6 index5 alt1"><code class="js plain">(lldb)&nbsp;p&nbsp;i</code></div><div class="line number7 index6 alt2"><code class="js plain">(int)&nbsp;$2 = 110
(lldb) finish
2014-11-22 10:49:35.978 DebuggerDance[60182:4832768] 110 is even!
(lldb) p i
(int) $4&nbsp;=&nbsp;99</code></div><div class="line number12 index11 alt1"><code class="js plain">(lldb)&nbsp;frame&nbsp;info</code></div><div class="line number13 index12 alt2"><code class="js plain">frame&nbsp;</code><code class="js preprocessor">#0:&nbsp;0x000000010a53bcd4&nbsp;DebuggerDance`main&nbsp;+&nbsp;68&nbsp;at&nbsp;main.m:17</code></div></div></td></tr></tbody></table></div></div><p>它始终在 17 行的原因是 finish 命令一直运行到 isEven() 函数的 return,然后立刻停止。注意即使它还在 17 行,其实这行已经被执行过了。</p><p><strong>Thread Return</strong></p><p>调试时,还有一个很棒的函数可以用来控制程序流程:thread return 。它有一个可选参数,在执行时它会把可选参数加载进返回寄存器里,然后立刻执行返回命令,跳出当前栈帧。这意味这函数剩余的部分不会被执行。这会给 ARC 的引用计数造成一些问题,或者会使函数内的清理部分失效。但是在函数的开头执行这个命令,是个非常好的隔离这个函数,伪造返回值的方式 。</p><p>让我们稍微修改一下上面代码段并运行:</p><div><div id="highlighter_576120" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div><div class="line number4 index3 alt1">4</div><div class="line number5 index4 alt2">5</div><div class="line number6 index5 alt1">6</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">p&nbsp;i</code></div><div class="line number2 index1 alt1"><code class="js plain">s</code></div><div class="line number3 index2 alt2"><code class="js plain">thread&nbsp;</code><code class="js keyword">return</code>&nbsp;<code class="js plain">NO</code></div><div class="line number4 index3 alt1"><code class="js plain">n</code></div><div class="line number5 index4 alt2"><code class="js plain">p&nbsp;even0</code></div><div class="line number6 index5 alt1"><code class="js plain">frame&nbsp;info</code></div></div></td></tr></tbody></table></div></div><p>看答案前思考一下。下面是答案:</p><div><div id="highlighter_166993" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div><div class="line number4 index3 alt1">4</div><div class="line number5 index4 alt2">5</div><div class="line number6 index5 alt1">6</div><div class="line number7 index6 alt2">7</div><div class="line number8 index7 alt1">8</div><div class="line number9 index8 alt2">9</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;p&nbsp;i</code></div><div class="line number2 index1 alt1"><code class="js plain">(int)&nbsp;$0&nbsp;=&nbsp;99</code></div><div class="line number3 index2 alt2"><code class="js plain">(lldb)&nbsp;s</code></div><div class="line number4 index3 alt1"><code class="js plain">(lldb)&nbsp;thread&nbsp;</code><code class="js keyword">return</code>&nbsp;<code class="js plain">NO</code></div><div class="line number5 index4 alt2"><code class="js plain">(lldb)&nbsp;n</code></div><div class="line number6 index5 alt1"><code class="js plain">(lldb)&nbsp;p&nbsp;even0</code></div><div class="line number7 index6 alt2"><code class="js plain">(BOOL)&nbsp;$2&nbsp;=&nbsp;NO</code></div><div class="line number8 index7 alt1"><code class="js plain">(lldb)&nbsp;frame&nbsp;info</code></div><div class="line number9 index8 alt2"><code class="js plain">frame&nbsp;</code><code class="js preprocessor">#0:&nbsp;0x00000001009a5cc4&nbsp;DebuggerDance`main&nbsp;+&nbsp;52&nbsp;at&nbsp;main.m:17</code></div></div></td></tr></tbody></table></div></div><p><strong>断点</strong></p><p>我们都把断点作为一个停止程序运行,检查当前状态,追踪 bug 的方式。但是如果我们改变和断点交互的方式,很多事情都变成可能。</p><p>断点允许控制程序什么时候停止,然后允许命令的运行。</p><p>想象把断点放在函数的开头,然后用 thread return 命令重写函数的行为,然后继续。想象一下让这个过程自动化,听起来不错,不是吗?</p><p><strong>管理断点</strong></p><p>Xcode 提供了一系列工具来创建和管理断点。我们会一个个看过来并介绍 LLDB 中等价的命令 (是的,你可以在调试器内部添加断点)。</p><p>在 Xcode 的左侧面板,有一组按钮。其中一个看起来像断点。点击它打开断点导航,这是一个可以快速管理所有断点的面板。<br></p><p style="text-align:center"><img src="http://cc.cocimg.com/api/uploads/20141219/1418975164535909.png" title="1418975164535909.png" alt="6.png" style="width: 643px; height: 323px;"></p><p>在这里你可以看到所有的断点 - 在 LLDB 中通过 breakpoint list (或者 br li) 命令也做同样的事儿。你也可以点击单个断点来开启或关闭 - 在 LLDB 中使用 breakpoint enable<breakpointid>和 breakpoint disable<breakpointid>:</breakpointid></breakpointid></p><div><div id="highlighter_424613" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div><div class="line number4 index3 alt1">4</div><div class="line number5 index4 alt2">5</div><div class="line number6 index5 alt1">6</div><div class="line number7 index6 alt2">7</div><div class="line number8 index7 alt1">8</div><div class="line number9 index8 alt2">9</div><div class="line number10 index9 alt1">10</div><div class="line number11 index10 alt2">11</div><div class="line number12 index11 alt1">12</div><div class="line number13 index12 alt2">13</div><div class="line number14 index13 alt1">14</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;br&nbsp;li</code></div><div class="line number2 index1 alt1"><code class="js plain">Current&nbsp;breakpoints:</code></div><div class="line number3 index2 alt2"><code class="js plain">1:&nbsp;file&nbsp;=&nbsp;</code><code class="js string">'/Users/arig/Desktop/DebuggerDance/DebuggerDance/main.m'</code><code class="js plain">,&nbsp;line&nbsp;=&nbsp;16,&nbsp;locations&nbsp;=&nbsp;1,&nbsp;resolved&nbsp;=&nbsp;1,&nbsp;hit&nbsp;count&nbsp;=&nbsp;1</code></div><div class="line number4 index3 alt1"><code class="js spaces">&nbsp;&nbsp;</code><code class="js plain">1.1:&nbsp;where&nbsp;=&nbsp;DebuggerDance`main&nbsp;+&nbsp;27&nbsp;at&nbsp;main.m:16,&nbsp;address&nbsp;=&nbsp;0x000000010a3f6cab,&nbsp;resolved,&nbsp;hit&nbsp;count&nbsp;=&nbsp;1</code></div><div class="line number5 index4 alt2"><code class="js plain">(lldb)&nbsp;br&nbsp;dis&nbsp;1</code></div><div class="line number6 index5 alt1"><code class="js plain">1&nbsp;breakpoints&nbsp;disabled.</code></div><div class="line number7 index6 alt2"><code class="js plain">(lldb)&nbsp;br&nbsp;li</code></div><div class="line number8 index7 alt1"><code class="js plain">Current&nbsp;breakpoints:</code></div><div class="line number9 index8 alt2"><code class="js plain">1:&nbsp;file&nbsp;=&nbsp;</code><code class="js string">'/Users/arig/Desktop/DebuggerDance/DebuggerDance/main.m'</code><code class="js plain">,&nbsp;line&nbsp;=&nbsp;16,&nbsp;locations&nbsp;=&nbsp;1&nbsp;Options:&nbsp;disabled</code></div><div class="line number10 index9 alt1"><code class="js spaces">&nbsp;&nbsp;</code><code class="js plain">1.1:&nbsp;where&nbsp;=&nbsp;DebuggerDance`main&nbsp;+&nbsp;27&nbsp;at&nbsp;main.m:16,&nbsp;address&nbsp;=&nbsp;0x000000010a3f6cab,&nbsp;unresolved,&nbsp;hit&nbsp;count&nbsp;=&nbsp;1</code></div><div class="line number11 index10 alt2"><code class="js plain">(lldb)&nbsp;br&nbsp;del&nbsp;1</code></div><div class="line number12 index11 alt1"><code class="js plain">1&nbsp;breakpoints&nbsp;deleted;&nbsp;0&nbsp;breakpoint&nbsp;locations&nbsp;disabled.</code></div><div class="line number13 index12 alt2"><code class="js plain">(lldb)&nbsp;br&nbsp;li</code></div><div class="line number14 index13 alt1"><code class="js plain">No&nbsp;breakpoints&nbsp;currently&nbsp;set.</code></div></div></td></tr></tbody></table></div></div><p><strong>创建断点</strong></p><p>在上面的例子中,我们通过在源码页面器的滚槽 16 上点击来创建断点。你可以通过把断点拖拽出滚槽,然后释放鼠标来删除断点 (消失时会有一个非常可爱的噗的一下的动画)。你也可以在断点导航页选择断点,然后按下删除键删除。</p><p>要在调试器中创建断点,可以使用 breakpoint set 命令。</p><div><div id="highlighter_591579" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;breakpoint&nbsp;set&nbsp;-f&nbsp;main.m&nbsp;-l&nbsp;16</code></div><div class="line number2 index1 alt1"><code class="js plain">Breakpoint&nbsp;1:&nbsp;where&nbsp;=&nbsp;DebuggerDance`main&nbsp;+&nbsp;27&nbsp;at&nbsp;main.m:16,&nbsp;address&nbsp;=&nbsp;0x000000010a3f6cab</code></div></div></td></tr></tbody></table></div></div><p>也可以使用缩写形式 br。虽然 b 是一个完全不同的命令 (_regexp-break 的缩写),但恰好也可以实现和上面同样的效果。</p><div><div id="highlighter_589312" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;b&nbsp;main.m:17</code></div><div class="line number2 index1 alt1"><code class="js plain">Breakpoint&nbsp;2:&nbsp;where&nbsp;=&nbsp;DebuggerDance`main&nbsp;+&nbsp;52&nbsp;at&nbsp;main.m:17,&nbsp;address&nbsp;=&nbsp;0x000000010a3f6cc4</code></div></div></td></tr></tbody></table></div></div><p>也可以在一个符号 (C 语言函数) 上创建断点,而完全不用指定哪一行</p><div><div id="highlighter_327760" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div><div class="line number4 index3 alt1">4</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;b&nbsp;isEven</code></div><div class="line number2 index1 alt1"><code class="js plain">Breakpoint&nbsp;3:&nbsp;where&nbsp;=&nbsp;DebuggerDance`isEven&nbsp;+&nbsp;16&nbsp;at&nbsp;main.m:4,&nbsp;address&nbsp;=&nbsp;0x000000010a3f6d00</code></div><div class="line number3 index2 alt2"><code class="js plain">(lldb)&nbsp;br&nbsp;s&nbsp;-F&nbsp;isEven</code></div><div class="line number4 index3 alt1"><code class="js plain">Breakpoint&nbsp;4:&nbsp;where&nbsp;=&nbsp;DebuggerDance`isEven&nbsp;+&nbsp;16&nbsp;at&nbsp;main.m:4,&nbsp;address&nbsp;=&nbsp;0x000000010a3f6d00</code></div></div></td></tr></tbody></table></div></div><p>这些断点会准确的停止在函数的开始。Objective-C 的方法也完全可以:</p><div><div id="highlighter_678810" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div><div class="line number4 index3 alt1">4</div><div class="line number5 index4 alt2">5</div><div class="line number6 index5 alt1">6</div><div class="line number7 index6 alt2">7</div><div class="line number8 index7 alt1">8</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;breakpoint&nbsp;set&nbsp;-F&nbsp;</code><code class="js string">"-[NSArray&nbsp;objectAtIndex:]"</code></div><div class="line number2 index1 alt1"><code class="js plain">Breakpoint&nbsp;5:&nbsp;where&nbsp;=&nbsp;CoreFoundation`-[NSArray&nbsp;objectAtIndex:],&nbsp;address&nbsp;=&nbsp;0x000000010ac7a950</code></div><div class="line number3 index2 alt2"><code class="js plain">(lldb)&nbsp;b&nbsp;-[NSArray&nbsp;objectAtIndex:]</code></div><div class="line number4 index3 alt1"><code class="js plain">Breakpoint&nbsp;6:&nbsp;where&nbsp;=&nbsp;CoreFoundation`-[NSArray&nbsp;objectAtIndex:],&nbsp;address&nbsp;=&nbsp;0x000000010ac7a950</code></div><div class="line number5 index4 alt2"><code class="js plain">(lldb)&nbsp;breakpoint&nbsp;set&nbsp;-F&nbsp;</code><code class="js string">"+[NSSet&nbsp;setWithObject:]"</code></div><div class="line number6 index5 alt1"><code class="js plain">Breakpoint&nbsp;7:&nbsp;where&nbsp;=&nbsp;CoreFoundation`+[NSSet&nbsp;setWithObject:],&nbsp;address&nbsp;=&nbsp;0x000000010abd3820</code></div><div class="line number7 index6 alt2"><code class="js plain">(lldb)&nbsp;b&nbsp;+[NSSet&nbsp;setWithObject:]</code></div><div class="line number8 index7 alt1"><code class="js plain">Breakpoint&nbsp;8:&nbsp;where&nbsp;=&nbsp;CoreFoundation`+[NSSet&nbsp;setWithObject:],&nbsp;address&nbsp;=&nbsp;0x000000010abd3820</code></div></div></td></tr></tbody></table></div></div><p>如果想在 xcode 的UI上创建符号断点,你可以点击断点栏左侧的 + 按钮。<br></p><p style="text-align:center"><img src="http://cc.cocimg.com/api/uploads/20141219/1418975294572059.png" title="1418975294572059.png" alt="Image_2014-11-22_at_11.52.50_AM.png" style="width: 424px; height: 165px;"></p><p>然后选择第三个选项:<br></p><p style="text-align:center"><img src="http://cc.cocimg.com/api/uploads/20141219/1418975319702707.png" title="1418975319702707.png" alt="7.png" style="width: 526px; height: 313px;"></p><p>这时会出现一个弹出框,你可以在里面添加例如 -[NSArray objectAtIndex:] 这样的符号断点。这样每次调用这个函数的时候,程序都会停止,不管是你调用还是苹果调用。</p><p>如果你 Xcode 的 UI 上右击任意断点,然后选择 "Edit Breakpoint" 的话,会有一些非常诱人的选择。<br></p><p style="text-align:center"><img src="http://cc.cocimg.com/api/uploads/20141219/1418975393713266.png" title="1418975393713266.png" alt="8.png" style="width: 446px; height: 183px;"></p><p>这里,断点已经被修改为只有当 i 是 99 的时候才会停止。你也可以使用 "ignore" 选项来告诉断点最初的 n 次调用 (并且条件为真的时候) 的时候不要停止。</p><p>接下来介绍 'Add Action' 按钮...</p><p><strong>断点行为 (Action)</strong></p><p>上面的例子中,你或许想知道每一次到达断点的时候 i 的值。我们可以使用 p i 作为断点行为。这样每次到达断点的时候,都会自动运行这个命令。<br></p><p style="text-align:center"><img src="http://cc.cocimg.com/api/uploads/20141219/1418975379871617.png" title="1418975379871617.png" alt="9.png" style="width: 599px; height: 265px;"></p><p>你也可以添加多个行为,可以是调试器命令,shell 命令,也可以是更直接的打印:<br></p><p style="text-align:center"><img src="http://cc.cocimg.com/api/uploads/20141219/1418975531388271.png" title="1418975531388271.png" alt="10.png" style="width: 473px; height: 540px;"></p><p>可以看到它打印 i,然后大声念出那个句子,接着打印了自定义的表达式。</p><p>下面是在 LLDB 而不是 Xcode 的 UI 中做这些的时候,看起来的样子。</p><div><div id="highlighter_121390" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div><div class="line number4 index3 alt1">4</div><div class="line number5 index4 alt2">5</div><div class="line number6 index5 alt1">6</div><div class="line number7 index6 alt2">7</div><div class="line number8 index7 alt1">8</div><div class="line number9 index8 alt2">9</div><div class="line number10 index9 alt1">10</div><div class="line number11 index10 alt2">11</div><div class="line number12 index11 alt1">12</div><div class="line number13 index12 alt2">13</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;breakpoint&nbsp;set&nbsp;-F&nbsp;isEven</code></div><div class="line number2 index1 alt1"><code class="js plain">Breakpoint&nbsp;1:&nbsp;where&nbsp;=&nbsp;DebuggerDance`isEven&nbsp;+&nbsp;16&nbsp;at&nbsp;main.m:4,&nbsp;address&nbsp;=&nbsp;0x00000001083b5d00</code></div><div class="line number3 index2 alt2"><code class="js plain">(lldb)&nbsp;breakpoint&nbsp;modify&nbsp;-c&nbsp;</code><code class="js string">'i&nbsp;==&nbsp;99'</code>&nbsp;<code class="js plain">1</code></div><div class="line number4 index3 alt1"><code class="js plain">(lldb)&nbsp;breakpoint&nbsp;command&nbsp;add&nbsp;1</code></div><div class="line number5 index4 alt2"><code class="js plain">Enter&nbsp;your&nbsp;debugger&nbsp;command(s).&nbsp;&nbsp;Type&nbsp;</code><code class="js string">'DONE'</code>&nbsp;<code class="js plain">to&nbsp;end.</code></div><div class="line number6 index5 alt1"><code class="js plain">&gt;&nbsp;p&nbsp;i</code></div><div class="line number7 index6 alt2"><code class="js plain">&gt;&nbsp;DONE</code></div><div class="line number8 index7 alt1"><code class="js plain">(lldb)&nbsp;br&nbsp;li&nbsp;1</code></div><div class="line number9 index8 alt2"><code class="js plain">1:&nbsp;name&nbsp;=&nbsp;</code><code class="js string">'isEven'</code><code class="js plain">,&nbsp;locations&nbsp;=&nbsp;1,&nbsp;resolved&nbsp;=&nbsp;1,&nbsp;hit&nbsp;count&nbsp;=&nbsp;0</code></div><div class="line number10 index9 alt1"><code class="js spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="js plain">Breakpoint&nbsp;commands:</code></div><div class="line number11 index10 alt2"><code class="js spaces">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="js plain">p&nbsp;i</code></div><div class="line number12 index11 alt1"><code class="js plain">Condition:&nbsp;i&nbsp;==&nbsp;99</code></div><div class="line number13 index12 alt2"><code class="js spaces">&nbsp;&nbsp;</code><code class="js plain">1.1:&nbsp;where&nbsp;=&nbsp;DebuggerDance`isEven&nbsp;+&nbsp;16&nbsp;at&nbsp;main.m:4,&nbsp;address&nbsp;=&nbsp;0x00000001083b5d00,&nbsp;resolved,&nbsp;hit&nbsp;count&nbsp;=&nbsp;0</code></div></div></td></tr></tbody></table></div></div><p>接下来说说自动化。</p><p><strong>赋值后继续运行</strong></p><p>看编辑断点弹出窗口的底部,你还会看到一个选项: "Automatically continue after evaluation actions." 。它仅仅是一个选择框,但是却很强大。选中它,调试器会运行你所有的命令,然后继续运行。看起来就像没有执行任何断点一样 (除非断点太多,运行需要一段时间,拖慢了你的程序)。</p><p>这个选项框的效果和让最后断点的最后一个行为是 continue 一样。选框只是让这个操作变得更简单。调试器的输出是:</p><div><div id="highlighter_474255" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div><div class="line number4 index3 alt1">4</div><div class="line number5 index4 alt2">5</div><div class="line number6 index5 alt1">6</div><div class="line number7 index6 alt2">7</div><div class="line number8 index7 alt1">8</div><div class="line number9 index8 alt2">9</div><div class="line number10 index9 alt1">10</div><div class="line number11 index10 alt2">11</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;breakpoint&nbsp;set&nbsp;-F&nbsp;isEven</code></div><div class="line number2 index1 alt1"><code class="js plain">Breakpoint&nbsp;1:&nbsp;where&nbsp;=&nbsp;DebuggerDance`isEven&nbsp;+&nbsp;16&nbsp;at&nbsp;main.m:4,&nbsp;address&nbsp;=&nbsp;0x00000001083b5d00</code></div><div class="line number3 index2 alt2"><code class="js plain">(lldb)&nbsp;breakpoint&nbsp;command&nbsp;add&nbsp;1</code></div><div class="line number4 index3 alt1"><code class="js plain">Enter&nbsp;your&nbsp;debugger&nbsp;command(s).&nbsp;&nbsp;Type&nbsp;</code><code class="js string">'DONE'</code>&nbsp;<code class="js plain">to&nbsp;end.</code></div><div class="line number5 index4 alt2"><code class="js plain">&gt;&nbsp;</code><code class="js keyword">continue</code></div><div class="line number6 index5 alt1"><code class="js plain">&gt;&nbsp;DONE</code></div><div class="line number7 index6 alt2"><code class="js plain">(lldb)&nbsp;br&nbsp;li&nbsp;1</code></div><div class="line number8 index7 alt1"><code class="js plain">1:&nbsp;name&nbsp;=&nbsp;</code><code class="js string">'isEven'</code><code class="js plain">,&nbsp;locations&nbsp;=&nbsp;1,&nbsp;resolved&nbsp;=&nbsp;1,&nbsp;hit&nbsp;count&nbsp;=&nbsp;0</code></div><div class="line number9 index8 alt2"><code class="js spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="js plain">Breakpoint&nbsp;commands:</code></div><div class="line number10 index9 alt1"><code class="js spaces">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="js keyword">continue</code></div><div class="line number11 index10 alt2"><code class="js spaces">&nbsp;&nbsp;</code><code class="js plain">1.1:&nbsp;where&nbsp;=&nbsp;DebuggerDance`isEven&nbsp;+&nbsp;16&nbsp;at&nbsp;main.m:4,&nbsp;address&nbsp;=&nbsp;0x00000001083b5d00,&nbsp;resolved,&nbsp;hit&nbsp;count&nbsp;=&nbsp;0</code></div></div></td></tr></tbody></table></div></div><p>执行断点后自动继续运行,允许你完全通过断点来修改程序!你可以在某一行停止,运行一个 expression 命令来改变变量,然后继续运行。</p><p><strong>例子</strong></p><p>想想所谓的"打印调试"技术吧,不要这么做:</p><div><div id="highlighter_985088" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">NSLog(@</code><code class="js string">"%@"</code><code class="js plain">,&nbsp;whatIsInsideThisThing);</code></div></div></td></tr></tbody></table></div></div><p>而是用个打印变量的断点替换 log 语句,然后继续运行。</p><p>也不要:</p><div><div id="highlighter_747797" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div><div class="line number4 index3 alt1">4</div><div class="line number5 index4 alt2">5</div><div class="line number6 index5 alt1">6</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">int&nbsp;calculateTheTrickyValue&nbsp;{</code></div><div class="line number2 index1 alt1"><code class="js spaces">&nbsp;&nbsp;</code><code class="js keyword">return</code>&nbsp;<code class="js plain">9;</code></div><div class="line number3 index2 alt2"><code class="js spaces">&nbsp;&nbsp;</code><code class="js plain">/*</code></div><div class="line number4 index3 alt1"><code class="js spaces">&nbsp;&nbsp;&nbsp;</code><code class="js plain">Figure&nbsp;</code><code class="js keyword">this</code>&nbsp;<code class="js plain">out&nbsp;later.</code></div><div class="line number5 index4 alt2"><code class="js spaces">&nbsp;&nbsp;&nbsp;</code><code class="js plain">...</code></div><div class="line number6 index5 alt1"><code class="js plain">}</code></div></div></td></tr></tbody></table></div></div><p>而是加一个使用 thread return 9 命令的断点,然后让它继续运行。</p><p>符号断点加上 action 真的很强大。你也可以在你朋友的 Xcode 工程上添加一些断点,并且加上大声朗读某些东西的 action。看看他们要花多久才能弄明白发生了什么。??</p><p><strong>完全在调试器内运行</strong></p><p>带开始舞蹈之前,还有一件事要看一看。实际上你可以在调试器中执行任何 C/Objective-C/C++/Swift 的命令。唯一的缺点就是不能创建新函数... 这意味着不能创建新的类,block,函数,有虚拟函数的 C++ 类等等。除此之外,它都可以做。</p><p>我们可以申请分配一些字节:</p><div><div id="highlighter_195228" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div><div class="line number4 index3 alt1">4</div><div class="line number5 index4 alt2">5</div><div class="line number6 index5 alt1">6</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;e&nbsp;char&nbsp;*$str = (char *)malloc(8)
(lldb) e (void)strcpy($str,&nbsp;</code><code class="js string">"munkeys"</code><code class="js plain">)</code></div><div class="line number3 index2 alt2"><code class="js plain">(lldb)&nbsp;e&nbsp;$str[1] =  'o'
(char) $0&nbsp;=&nbsp;</code><code class="js string">'o'</code></div><div class="line number5 index4 alt2"><code class="js plain">(lldb)&nbsp;p&nbsp;$str
(char *) $str&nbsp;=&nbsp;0x00007fd04a900040&nbsp;</code><code class="js string">"monkeys"</code></div></div></td></tr></tbody></table></div></div><p>我们可以查看内存 (使用 x 命令),来看看新数组中的四个字节:</p><div><div id="highlighter_827612" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;x/4c&nbsp;$str
0x7fd04a900040: monk

我们也可以去掉 3 个字节 (x 命令需要斜引号,因为它只有一个内存地址的参数,而不是表达式;使用 help x 来获得更多信息):

1
2
(lldb) x/1w `$str + 3`
0x7fd04a900043: keys

做完了之后,一定不要忘了释放内存,这样才不会内存泄露。(哈,虽然这是调试器用到的内存):

1
(lldb) e (void)free($str)</code></div></div></td></tr></tbody></table></div></div><p><strong>让我们起舞</strong></p><p>现在我们已经知道基本的步调了,是时候开始跳舞并玩一些疯狂的事情了。我曾经写过一篇<a href="http://arigrant.com/blog/2014/1/19/adventures-in-the-land-of-nsarray" target="_self"> NSArray 深度探究</a>的博客。这篇博客用了很多 NSLog 语句,但实际上我的所有探索都是在调试器中完成的。看看你能不能弄明白怎么做的,这会是一个有意思的练习。</p><p><strong>不用断点调试</strong></p><p>程序运行时,Xcode 的调试条上会出现暂停按钮,而不是继续按钮:<br></p><p style="text-align:center"><img src="http://cc.cocimg.com/api/uploads/20141219/1418975779906433.png" title="1418975779906433.png" alt="11.png" style="width: 402px; height: 53px;"></p><p>点击按钮会暂停 app (这会运行 process interrupt 命令,因为 LLDB 总是在背后运行)。这会让你可以访问调试器,但看起来可以做的事情不多,因为在当前作用域没有变量,也没有特定的代码让你看。</p><p>这就是有意思的地方。如果你正在运行 iOS app,你可以试试这个: (因为全局变量是可访问的)</p><div><div id="highlighter_950342" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="js plain">(lldb)&nbsp;po&nbsp;[[[UIApplication&nbsp;sharedApplication]&nbsp;keyWindow]&nbsp;recursiveDescription]&lt;uiwindow:&nbsp;0x7f82b1fa8140;&nbsp;frame&nbsp;=&nbsp;(0&nbsp;0;&nbsp;320&nbsp;568);&nbsp;gesturerecognizers&nbsp;=&nbsp;;&nbsp;layer&nbsp;=&nbsp;&gt;</code></div><div class="line number2 index1 alt1"><code class="js spaces">&nbsp;&nbsp;&nbsp;</code><code class="js plain">|&nbsp;&lt;uiview:&nbsp;0x7f82b1d01fd0;&nbsp;frame&nbsp;=&nbsp;(0&nbsp;0;&nbsp;320&nbsp;568);&nbsp;autoresize&nbsp;=&nbsp;w+h;&nbsp;layer&nbsp;=&nbsp;&gt;&lt;/uiview:&nbsp;0x7f82b1d01fd0;&nbsp;frame&nbsp;=&nbsp;(0&nbsp;0;&nbsp;320&nbsp;568);&nbsp;autoresize&nbsp;=&nbsp;w+h;&nbsp;layer&nbsp;=&nbsp;&gt;&lt;/uiwindow:&nbsp;0x7f82b1fa8140;&nbsp;frame&nbsp;=&nbsp;(0&nbsp;0;&nbsp;320&nbsp;568);&nbsp;gesturerecognizers&nbsp;=&nbsp;;&nbsp;layer&nbsp;=&nbsp;&gt;</code></div></div></td></tr></tbody></table></div></div><p>你可以看到整个层次。<a href="https://github.com/facebook/chisel" target="_self">Chisel</a>中 pviews 就是这么实现的。</p><p><strong>更新UI</strong></p><p>有了上面的输出,我们可以获取这个 view:</p><div><div id="highlighter_984220" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;e&nbsp;id&nbsp;$myView = (id)0x7f82b1d01fd0

然后在调试器中改变它的背景色:

1
(lldb) e (void)[$myView&nbsp;setBackgroundColor:[UIColor&nbsp;blueColor]]</code></div></div></td></tr></tbody></table></div></div><p>但是只有程序继续运行之后才会看到界面的变化。因为改变的内容必须被发送到渲染服务中,然后显示才会被更新。</p><p>渲染服务实际上是一个另外的进程 (被称作 backboardd)。这就是说即使我们正在调试的内容所在的进程被打断了,backboardd 也还是继续运行着的。</p><p>这意味着你可以运行下面的命令,而不用继续运行程序:</p><div><div id="highlighter_540177" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;e&nbsp;(void)[CATransaction&nbsp;flush]</code></div></div></td></tr></tbody></table></div></div><p>即使你仍然在调试器中,UI 也会在模拟器或者真机上实时更新。Chisel 为此提供了一个别名叫做 caflush,这个命令被用来实现其他的快捷命令,例如 hide<view>,show<view>以及其他很多命令。所有 Chisel 的命令都有文档,所以安装后随意运行 help show 来看更多信息。</view></view></p><p><strong>Push 一个 View Controller</strong></p><p>想象一个以 UINavigationController 为 root ViewController 的应用。你可以通过下面的命令,轻松地获取它:</p><div><div id="highlighter_136851" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;e&nbsp;id&nbsp;$nvc = [[[UIApplication sharedApplication] keyWindow] rootViewController]

然后 push 一个 child view controller:

1
2
3
4
(lldb) e id $vc&nbsp;=&nbsp;[UIViewController&nbsp;</code><code class="js keyword">new</code><code class="js plain">]</code></div><div class="line number2 index1 alt1"><code class="js plain">(lldb)&nbsp;e&nbsp;(void)[[$vc view] setBackgroundColor:[UIColor yellowColor]]
(lldb) e (void)[$vc&nbsp;setTitle:@</code><code class="js string">"Yay!"</code><code class="js plain">]</code></div><div class="line number4 index3 alt1"><code class="js plain">(lldb)&nbsp;e&nbsp;(void)[$nvc pushViewContoller:$vc&nbsp;animated:YES]</code></div></div></td></tr></tbody></table></div></div><p>最后运行下面的命令:</p><div><div id="highlighter_960642" class="syntaxhighlighter js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;caflush&nbsp;</code><code class="js comments">//&nbsp;e&nbsp;(void)[CATransaction&nbsp;flush]</code></div></div></td></tr></tbody></table></div></div><p>navigation Controller 就会立刻就被 push 到你眼前。</p><p><strong>查找按钮的 target</strong></p><p>想象你在调试器中有一个 $myButton 的变量,可以是创建出来的,也可以是从 UI 上抓取出来的,或者是你停止在断点时的一个局部变量。你想知道,按钮按下的时候谁会接收到按钮发出的 action。非常简单:
1
2
3
4
5
6
(lldb) po [$myButton&nbsp;allTargets]</code></div><div class="line number2 index1 alt1"><code class="js plain">{(</code></div><div class="line number3 index2 alt2"><code class="js spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="js plain">)}</code></div><div class="line number4 index3 alt1"><code class="js plain">(lldb)&nbsp;po&nbsp;[$myButton actionsForTarget:(id)0x7fb58bd2e240 forControlEvent:0](
_handleTap:
)

现在你或许想在它发生的时候加一个断点。在 -[MyEventListener _handleTap:] 设置一个符号断点就可以了,在 Xcode 和 LLDB 中都可以,然后你就可以点击按钮并停在你所希望的地方了。

观察实例变量的变化

假设你有一个 UIView,不知道为什么它的 _layer 实例变量被重写了 (糟糕)。因为有可能并不涉及到方法,我们不能使用符号断点。相反的,我们想监视什么时候这个地址被写入。

首先,我们需要找到 _layer 这个变量在对象上的相对位置:

1
2
(lldb) p (ptrdiff_t)ivar_getOffset((struct Ivar *)class_getInstanceVariable([MyView class],  "_layer" ))
(ptrdiff_t) $0&nbsp;=&nbsp;8</code></div></div></td></tr></tbody></table></div></div><p>现在我们知道 ($myView + 8) 是被写入的内存地址:
1
2
3
(lldb) watchpoint set expression – (int *)$myView&nbsp;+&nbsp;8</code></div><div class="line number2 index1 alt1"><code class="js plain">Watchpoint&nbsp;created:&nbsp;Watchpoint&nbsp;3:&nbsp;addr&nbsp;=&nbsp;0x7fa554231340&nbsp;size&nbsp;=&nbsp;8&nbsp;state&nbsp;=&nbsp;enabled&nbsp;type&nbsp;=&nbsp;w</code></div><div class="line number3 index2 alt2"><code class="js spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="js keyword">new</code>&nbsp;<code class="js plain">value:&nbsp;0x0000000000000000</code></div></div></td></tr></tbody></table></div></div><p>这被以 wivar $myView _layer 加入到 Chisel 中。

非重写方法的符号断点

假设你想知道 -[MyViewController viewDidAppear:] 什么时候被调用。如果这个方法并没有在MyViewController 中实现,而是在其父类中实现的,该怎么办呢?试着设置一个断点,会出现以下结果:

1
2
3
(lldb) b -[MyViewController viewDidAppear:]
Breakpoint 1: no locations (pending).
WARNING:  Unable to resolve breakpoint to any actual locations.

因为 LLDB 会查找一个符号,但是实际在这个类上却找不到,所以断点也永远不会触发。你需要做的是为断点设置一个条件 [self isKindOfClass:[MyViewController class]],然后把断点放在 UIViewController 上。正常情况下这样设置一个条件可以正常工作。但是这里不会,因为我们没有父类的实现。

viewDidAppear: 是苹果实现的方法,因此没有它的符号;在方法内没有 self 。如果想在符号断点上使用 self,你必须知道它在哪里 (它可能在寄存器上,也可能在栈上;在 x86 上,你可以在 esp+4 找到它)。但是这是很痛苦的,因为现在你必须至少知道四种体系结构 (x86,x86-64,armv7,armv64)。想象你需要花多少时间去学习命令集以及它们每一个的调用约定,然后正确的写一个在你的超类上设置断点并且条件正确的命令。幸运的是,这个在 Chisel 被解决了。这被成为 bmessage:</p><div><div id="highlighter_20773" class="syntaxhighlighter  js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;bmessage&nbsp;-[MyViewController&nbsp;viewDidAppear:]</code></div><div class="line number2 index1 alt1"><code class="js plain">Setting&nbsp;a&nbsp;breakpoint&nbsp;at&nbsp;-[UIViewController&nbsp;viewDidAppear:]&nbsp;</code><code class="js keyword">with</code>&nbsp;<code class="js plain">condition&nbsp;(void*)object_getClass((id) esp+4 找到它)。但是这是很痛苦的,因为现在你必须至少知道四种体系结构 (x86,x86-64,armv7,armv64)。想象你需要花多少时间去学习命令集以及它们每一个的调用约定,然后正确的写一个在你的超类上设置断点并且条件正确的命令。幸运的是,这个在 Chisel 被解决了。这被成为 bmessage:</p><div><div id="highlighter_20773" class="syntaxhighlighter  js"><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div><div class="line number2 index1 alt1">2</div><div class="line number3 index2 alt2">3</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="js plain">(lldb)&nbsp;bmessage&nbsp;-[MyViewController&nbsp;viewDidAppear:]</code></div><div class="line number2 index1 alt1"><code class="js plain">Setting&nbsp;a&nbsp;breakpoint&nbsp;at&nbsp;-[UIViewController&nbsp;viewDidAppear:]&nbsp;</code><code class="js keyword">with</code>&nbsp;<code class="js plain">condition&nbsp;(void*)object_getClass((id) rdi) == 0x000000010e2f4d28

Breakpoint 1: where = UIKit`-[UIViewController viewDidAppear:], address = 0x000000010e11533c

LLDB 和 Python

LLDB 有内建的,完整的 Python 支持。在LLDB中输入 script,会打开一个 Python REPL。你也可以输入一行 python 语句作为 script 命令 的参数,这可以运行 python 语句而不进入REPL:

1
2
(lldb) script import os
(lldb) script os.system( "open http://www.objc.io/" )

这样就允许你创造各种酷的命令。把下面的语句放到文件 ~/myCommands.py 中:

1
2
def caflushCommand(debugger, command, result, internal_dict):
   debugger.HandleCommand( "e (void)[CATransaction flush]" )

然后再 LLDB 中运行:

1
command script import ~/myCommands.py

或者把这行命令放在 /.lldbinit 里,这样每次进入 LLDB 时都会自动运行。Chisel 其实就是一个 Python 脚本的集合,这些脚本拼接 (命令) 字符串 ,然后让 LLDB 执行。很简单,不是吗?

紧握调试器这一武器

LLDB 可以做的事情很多。大多数人习惯于使用 p,po,n,s 和 c,但实际上除此之外,LLDB 可以做的还有很多。掌握所有的命令 (实际上并不是很多),会让你在揭示代码运行时的运行状态,寻找 bug,强制执行特定的运行路径时获得更大的能力。你甚至可以构建简单的交互原型 - 比如要是现在以 modal 方式弹出一个 View Controller 会怎么样?使用调试器,一试便知。

这篇文章是为了想你展示 LLDB 的强大之处,并且鼓励你多去探索在控制台输入命令。

打开 LLDB,输入 help,看一看列举的命令。你尝试过多少?用了多少?

但愿 NSLog 看起来不再那么吸引你去用,每次编辑再运行并不有趣而且耗时。

调试愉快!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值