和 LLDB 调试器来一场说跳就跳的华尔兹(一)

序:原文 Dancing in the Debugger — A Waltz with LLDB
声明:译文有一部分参考自:与调试器共舞 - LLDB 的华尔兹


前言

你是否曾经为试图理解尼的代码和打印一个变量的值而感到苦恼?

NSLog(@"%@", whatIsInsideThisThing);

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

NSNumber *n = @7; // theFunctionThatShouldReallyBeCalled();

或短路一个逻辑检查?

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

抑或伪造一个函数的实现?

int calculateTheTrickyValue {
  return 9;

  /*
   Figure this out later.
   ...
}

并且每次都必须重新编译,重新运行吗?

构建软件是复杂的,并且bug总是会出现。常见的修复周期是修改代码,编译,再次运行,并希望变的最好。

其实并不需要这样。您可以使用调试器哈!即使你已经知道如何使用调试器来检查一个变量,可是调试器可以做的远不止这些。

本文打算挑战你对调试的认知,更详细地解释了一些你可能不知道的基本原理,然后给你展示一些有趣的例子。让我们随着舞曲开始旋转起来,看看我们会以何种水平结束。

LLDB

LLDB 是一个有着 REPL 特性,并内置 C++Python 插件的开源调试器。该调试器捆绑在Xcode内部,并内置于Xcode窗口底部的控制台面板里。调试器允许您在程序运行的特定时刻暂停程序,来检查变量的值,执行自定义的指令,然后按照你所认为合适的步骤来操作程序的进展。(这里 是调试器如何工作的大体介绍。)

之前尼很有可能使用过调试器,即使只是在Xcode窗口页面添加断点。但是有一些小窍门,可以使你有一些很漂亮而又酷爽的事情去做。GDB to LLDB 参考的是一个伟大的可用命令的鸟瞰图,你还有可能想要安装 Chisel ,一个可以使调试器更加有趣的 LLDB 插件的开源合辑。

与此同时,让我们以 在调试器中如何打印一个变量的值 来开始这场华尔兹的旅程吧。

基础知识

这里是一段打印一个字符串的简单示例小程序。注意,在第16行添加了一个断点:

断点的示例小程序

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

help

最简单的命令是 help ,这一指令将会列出所有的命令。如果你忘记某条命令是做什么的或者想了解更多的命令,你可以使用 help 命令查看更多的细节,例如 help printhelp thread 。如果你忘记了 help 命令是做什么的?你可以使用 help help 命令,但如果你知道的足够多,也许你还没有完全忘记命令是做什么的。

print

使用 print 命令打印一个值是很简单的。

print 的使用

LLDB 实际上会做前缀匹配,因此尼也可以使用 prinprip 命令,但是不可以使用 pr ,因为 LLDB 不能把它和 process 命令做区分(幸运的是,p 并没有歧义)。
你可能还会注意到,结果中有个 $0。实际上你可以使用它来代指这个结果。试试 print $0 + 7,你会看到 45。任何以美元符开头的东西都是存在于 LLDB 的命名空间的,它们是为了帮助你进行调试而存在的。

expression

如果想修改一个值怎么办?这里使用 expression 命令。

expression

这一命令不仅仅修改了调试器中的值,实际上还修改了程序中的值。如果你在这个基础上继续执行程序,将会打印83 huang qimeng,神奇吧!
从现在开始,我们下面会使用这两个命令的简化形式 pe

什么是 print 命令

这里有一个有趣的表达式:p count = 18,如果我们执行这个命令并打印count的值,我们会看到结果和这个表达式 expression count = 18 是一个吊样的。

expression不同的是,print不需要参数,比如e -h +17到底是以-h为标识,仅仅执行+17呢,还是计算17h的差值呢?连字符确实让人困惑,你可能得不到尼想要的结果。

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

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

'print' is an abbreviation for 'expression --'.
(print是 `expression --` 的简写)

打印对象

输入:

p objects

然后输出是一堆奇怪的东西:

(NSString *) $7 = 0x0000000104da4040 @"huang qimeng"

如果我们尝试打印结构更复杂的对象,结果甚至会更糟:

(lldb) p @[ @"foo", @"bar" ]

(NSArray *) $8 = 0x00007fdb9b71b3e0 @"2 objects"

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

(lldb) e -O -- $8
<__NSArrayI 0x7fdb9b71b3e0>(
foo,
bar
)

幸运的是,e -o --也有个别名,即poprint object的缩写),我们可以这样使用:

(lldb) po $8
<__NSArrayI 0x7fdb9b71b3e0>(
foo,
bar
)
(lldb) po @"lunar"
lunar
(lldb) p @"lunar"
(NSString *) $13 = 0x00007fdb9d0003b0 @"lunar"

打印变量

可以为print指定不同的打印格式。他们都以print/<fmt>或者简化的p/<fmt>格式书写。例子如下:
默认格式:

(lldb) p 16
16

十六进制:

(lldb) p/x 16
0x10

二进制(t代表two):

(lldb) p/t 16
0b00000000000000000000000000010000
(lldb) p/t (char)16
0b00010000

你也可以使用p/c打印字符,或者p/s打印以空终止的字符串*char **,这里是输出格式的完整说明。

变量

现在你已经可以打印对象和简单类型的变量了,以及如何使用expression命令在调试器中修改他们了。~~此处废话不翻译~~,不过为了能使用声明的变量,变量必须以美元符号$开头。

(lldb) e int $a = 2
(lldb) p $a * 19
38
(lldb) e NSArray *$array = @[ @"Saturday", @"Sunday", @"Monday" ]
(lldb) p [$array count]
2
(lldb) po [[$array objectAtIndex:0] uppercaseString]
SATURDAY
(lldb) p [[$array 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无法分辨涉及的类型(注:返回的类型),这种事情经常出现,给个说明就好了:

(lldb) p (char)[[$array objectAtIndex:$a] characterAtIndex:0]
'M'
(lldb) p/d (char)[[$array objectAtIndex:$a] characterAtIndex:0]
77

变量使调试器的使用变得更容易了,你想不到吧?😄

未完待续...

移步 和 LLDB 调试器来一场说跳就跳的华尔兹(二)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容