和 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行添加了一个断点:

和 LLDB 调试器来一场说跳就跳的华尔兹(一)_第1张图片
断点的示例小程序

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

help

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

print

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

和 LLDB 调试器来一场说跳就跳的华尔兹(一)_第2张图片
print 的使用

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

expression

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

和 LLDB 调试器来一场说跳就跳的华尔兹(一)_第3张图片
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/或者简化的p/格式书写。例子如下:
默认格式:

(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 调试器来一场说跳就跳的华尔兹(二)喔

你可能感兴趣的:(和 LLDB 调试器来一场说跳就跳的华尔兹(一))