第四章:给代码创建断点

无论你是在技术栈中使用 Swift,Objective-C,C++,C,还是完全不同的语言,都需要学习如何创建断点。 在 Xcode 中点击侧面板很容易使用 GUI 创建一个断点,但是 LLDB 控制台可以更好地控制断点。

在本章中,你将学习所有关于断点的知识,以及如何使用 LLDB 创建断点。

信号(Signals)

在这一章中,你将会看到我提供的一个项目。 它叫做信号(Signals),你可以在本章的资源包中找到它。

第四章:给代码创建断点_第1张图片
Stopping in Code-1.png

使用 Xcode 打开信号项目。 信号是一个基本的主从模板项目,采用美式足球应用程序的主题,显示一些名称奇怪的调用。

在内部,这个项目监听几个 Unix 信号,并在信号项目收到信号时显示出来。

Unix 信号是进程间通信的基本形式。 例如,其中一个信号 SIGSTOP 可用于保存状态并暂停进程的执行,而其对应的 SIGCONT 被发送到程序以恢复执行。 这两个信号可以被调试器用来暂停和继续程序的执行。

这是一个有趣的应用程序,因为它不仅探索了 Unix 信号处理,而且还突出了当控制进程(LLDB)处理将 Unix 信号传递给受控进程时发生的情况。 默认情况下,LLDB 具有处理不同信号的自定义操作。 有些信号在 LLDB 被连接时不会传递到受控进程。

为了显示信号,你可以从应用程序内部发出一个信号,或者从其他应用程序(如终端)在外部发送信号。

另外,还有一个 UISwitch 来切换信号处理。 当切换开关时,它调用 C 函数 sigprocmask 来禁用或启用信号的处理。

最后,应用程序有一个 Timeout 的导航栏按钮,它在应用程序中发出 SIGSTOP 信号,实质上是“冻结”程序。 但是,如果将 LLDB 连接到信号程序(当你通过 Xcode 进行构建和运行时默认连接),调用 SIGSTOP 将允许你在 Xcode 中使用 LLDB 检查执行状态。

确保 iPhone X 模拟器被选为目标。 构建并运行应用程序。 一旦项目运行,导航到 Xcode 控制台并暂停调试器。

第四章:给代码创建断点_第2张图片
Stopping in Code-2.png

恢复 Xcode 并留意模拟器。 每当调试器停止时,UITableView 将添加新的一行然后恢复执行。 这是通过信号项目监视 SIGSTOP Unix 信号事件并在数据模型出现时向里面添加一行来实现的。 当一个进程停止时,任何新的信号都不会被立即处理,因为这个程序是有点停止的。

Xcode 断点

在你通过 LLDB 控制台学习酷且耀眼的断点之前,值得一提的是,你可以通过 Xcode 单独实现。

符号断点(Symbolic Breakpoint)是 Xcode 的一个很好的调试功能。 它们让你在应用程序中的某个符号上设置一个断点。 一个符号的例子是 -[NSObject init],它指向 NSObject 实例的 init 方法。

Xcode 中符号断点的简洁之处在于,一旦你输入了一个符号断点,下一次程序启动时就不必再输入它。

现在你将尝试使用符号断点来显示所有将要创建的 NSObject 实例。

如果应用程序正在运行,关闭它。 接下来,切换到 Breakpoint Navigator。 在左下方,单击加号按钮选择 Symbolic Breakpoint... 选项。

第四章:给代码创建断点_第3张图片
Stopping in Code-3.png

一个弹出窗口会出现。 在弹出窗口的 Symbol 部分输入:-[NSObject init]。 在 Action 下,选择 Add Action,然后从下拉列表中选择 Debugger Command。 接下来,在下面的框中输入 po [$arg1 class]。

最后,选中 Automatically continue after evaluating actions。 你的弹出窗口应该类似于以下内容:

第四章:给代码创建断点_第4张图片
Stopping in Code-4.png

构建并运行应用程序。 当通过控制台运行信号程序的时候,Xcode 会显示所有类初始化的名字,在查看的时候,有相当多。

你设置了一个每次 -[NSObject init] 被调用时触发的断点 。 当断点触发时,一条命令在 LLDB 中运行,并且程序自动继续执行。

注意:在第十章“汇编寄存器调用约定”中,你将学习如何正确使用和操作寄存器,但是现在,只需知道 $arg1 与 $rdi 寄存器是同义的,可以简单地认为是 init 被调用时持有类的实例。

一旦检查完所有类名称的显示,通过在断点导航器中右键单击断点并选择“Delete Breakpoint”来删除符号断点。

除了符号断点之外,Xcode 还支持另外几种类型的错误断点。 其中之一是异常断点(Exception Breakpoint)。 有时候,你的程序出了问题,只是单单崩溃。 发生这种情况时,第一个反应应该是启用异常断点,每次抛出异常时都会触发异常断点。 Xcode 会告诉你崩溃行,这大大有助于追捕造成这次崩溃的罪魁祸首。

最后,有 Swift 错误断点(Swift Error Breakpoint),它会在 Swift 抛出一个错误时通过在 swift_willThrow 方法上创建一个断点来停止。 如果你使用可能容易出错的 API,这是一个很好的选择,因为它可以让你快速诊断情况,而不会错误地假设代码的正确性。

LLDB 断点语法

现在你已经快速了解了使用 Xcode 的 IDE 调试功能,现在是时候了解如何通过 LLDB 控制台创建断点了。 为了创建有用的断点,你需要学习如何查询你正在寻找的东西。

image 命令是一个很好的工具来帮助反思细节,这对设置断点很重要。

本书中你将使用两种配置来进行代码搜索。 第一种是以下内容:

(lldb) image lookup -n "-[UIViewController viewDidLoad]"

该命令显示 -[UIViewController viewDidLoad] 函数的实现地址(该方法的偏移地址位于框架二进制文件中)。 -n 参数告诉 LLDB 查找符号或函数名称。 输出结果如下:

1 match found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk//System/Library/Frameworks/UIKit.framework/UIKit:
        Address: UIKit[0x00000000001c67c8] (UIKit.__TEXT.__text + 1854120)
        Summary: UIKit`-[UIViewController viewDidLoad]

另一种有用的,类似的命令是这样的:

(lldb) image lookup -rn test

这是一个区分大小写的正则表达式,查找单词“test”。 如果在任何地方发现了小写单词“test”,那么在任何函数中,在当前可执行进程中加载的任何模块(即 UIKit,Foundation,Core Data 等等),这个命令将显示结果。

注意:如果需要精确匹配(如果查询中包含空格,请加上引号)请使用 -n 参数,执行正则表达式搜索请使用 -rn 参数。 -n 命令有助于找出确切的参数来匹配断点,尤其是在处理 Swift 时,而 -rn 参数选项将会在本书中重点介绍,因为好用的正则表达式可以消除相当多的输入 —— 很快你就会发现。

Objective-C 属性

学习如何查询已加载的代码对于学习如何在代码中创建断点是至关重要的。 Objective-C 和 Swift 在由编译器创建时都有特定的属性签名,当查找代码时会产生不同的查询策略。

例如,信号项目中声明了以下 Objective-C 类:

@interface TestClass : NSObject 
@property (nonatomic, strong) NSString *name; 
@end

编译器将为属性 name 的 setter 和 getter 生成代码。 getter 将如下所示:

-[TestClass name]

...而 setter 就像这样:

-[TestClass setName:]

构建并运行应用程序,然后暂停调试器。 接下来,通过在 LLDB 中输入以下内容来验证这些方法是否存在:

(lldb) image lookup -n "-[TestClass name]"

在控制台输出中,你会得到如下类似的东西:

1 match found in /Users/derekselander/Library/Developer/Xcode/DerivedData/Signals-atqcdyprrotlrvdanihoufkwzyqh/Build/Products/Debug-iphonesimulator/Signals.app/Signals:
        Address: Signals[0x0000000100001d60] (Signals.__TEXT.__text + 0)
        Summary: Signals`-[TestClass name] at TestClass.h:28

LLDB 将显示有关可执行进程中包含的函数的信息。 输出可能看起来很可怕,但这里有一些好消息。

注意:当查询匹配到很多代码时,image lookup 命令可以产生大量的输出,这些输出可能会非常难以观察。在二十二章“SB 示例,查找改进”中,你将构建一个更清晰的命令替代 LLDB 的 image lookup 命令,以避免查看太多的输出。

控制台输出告诉你 LLDB 能够发现这个函数是在 Signals 可执行进程中实现的,在 __text 部分的 __TEXT 段的偏移量为 0x0000000100001d60。 LLDB 也能够知道这个方法是在 TestClass.h 的第 28 行中声明的。

你也可以检查 setter,就像这样:

(lldb) image lookup -n "-[TestClass setName:]"

你会得到类似于前一个命令的输出,这次显示了 name 的实现地址和 setter 声明。

Objective-C 属性和点符号

对于开始使用 Objective-C(或仅仅 Swift)的开发人员来说,经常会引起误解的是属性的 Objective-C 点符号语法。

Objective-C 点符号是一个有些争议的编译器特性,它允许属性快速生成 getter 或 setter。

考虑以下的内容:

TestClass *a = [[TestClass alloc] init];

// Both equivalent for setters
[a setName:@"hello, world"];
a.name = @"hello, world";

// Both equivalent for getters
NSString *b; 
b = [a name]; // b = @"hello, world"
b = a.name;   // b = @"hello, world"

在上面的例子中,-[TestClass setName:] 方法被调用两次,即使使用点符号。 对于 getter 也可以这样说,-[TestClass name]。 当你处理 Objective-C 代码并尝试给属性点符号的 setter 和 getter 方法创建断点时,这很重要。

Swift 属性

在 Swift 中,属性的语法有很大不同。 看看 SwiftTestClass.swift 中包含的代码:

class SwiftTestClass: NSObject {
  var name: String!
}

确保信号项目在 LLDB 中运行并暂停。 通过在调试窗口中输入 Command + K 来清除 LLDB 控制台。

在 LLDB 控制台中,输入以下内容:

(lldb) image lookup -rn Signals.SwiftTestClass.name.setter

你会得到类似于下面的输出:

1 match found in /Users/derekselander/Library/Developer/Xcode/DerivedData/Signals-atqcdyprrotlrvdanihoufkwzyqh/Build/Products/Debug-iphonesimulator/Signals.app/Signals:
        Address: Signals[0x000000010000cc70] (Signals.__TEXT.__text + 44816)
        Summary: Signals`Signals.SwiftTestClass.name.setter : Swift.ImplicitlyUnwrappedOptional at SwiftTestClass.swift:28

在输出结果 Summary 一词后寻找信息。 这里有几个有趣的事情需要注意。

你看到函数名称有多长!? 这整个内容需要输入一个有效的 Swift 断点! 如果你想在这个 setter 上设置一个断点,你必须输入以下内容:

(lldb) b Signals.SwiftTestClass.name.setter : Swift.ImplicitlyUnwrappedOptional

使用正则表达式是引出这个怪物的不错的替代方案。

除了你生成的 Swift 函数名的长度之外,请注意 Swift 属性是如何形成的。 包含属性 name 的函数签名拥有紧跟在属性后面的单词 setter。 也许同样的惯例也适用于 getter 方法?

使用以下正则表达式查询语句同时搜索 SwiftTestClass 中 name 属性的 setter 和 getter:

(lldb) image lookup -rn Signals.SwiftTestClass.name

这使用正则表达式查询语句来显示所有包含短语 Signals.SwiftTestClass.name 的内容。

由于这是一个正则表达式,因此句点(.)被评估为通配符,这反过来又匹配实际函数签名中的句点。

你会得到相当多的输出,每当你在控制台输出中看到 Summary 一词时,都会得到磨炼。 你会发现输出匹配 getter(Signals.SwiftTestClass.name.getter),setter(Signals.SwiftTestClass.name.setter),以及两个包含 materializeForSet —— Swift 构造函数的辅助方法的方法。

Swift 属性的函数名称有一个模式:

ModuleName.Classname.PropertyName.(getter|setter)

在代码中创建智能断点时,显示方法,查找模式以及缩小搜索范围的能力是发现 Swift / Objective-C 语言内部结构的好方法。

最后...创造断点

现在你知道如何查询代码中函数和方法的存在,是时候开始为它们创建断点了。

如果你已经运行了信号应用程序,请停止并重新启动应用程序,然后按暂停按钮停止应用程序并启动 LLDB 控制台。

有几种不同的方法来创建断点。 最基本的方法是简单地输入字母 b,后面跟你的断点名称。 这在 Objective-C 和 C 中相当简单,因为名称很短并且很容易输入(例如 -[NSObject init] 或 -[UIView setAlpha:])。 这在 C++ 和 Swift 里非常棘手,因为编译器会将方法变成带有相当长名称的符号。

由于 UIKit 主要是 Objective-C(至少在编写本文时!),请使用 b 参数创建一个断点,如下所示:

(lldb) b -[UIViewController viewDidLoad]

你会看到以下输出:

Breakpoint 1: where = UIKit`-[UIViewController viewDidLoad], address = 0x0000000102bbd788

当你创建一个有效的断点时,控制台会显示关于该断点的一些信息。 在这种特殊情况下,断点创建为断点 1,因为这是此特定调试会话中的第一个断点。 在创建更多断点时,此断点 ID 将增加。

恢复调试器。 一旦你恢复执行,一个新的 SIGSTOP 信号将被显示。 点击单元格以调出详情的 UIViewController。 当调用详情视图控制器的 viewDidLoad 时,程序应该暂停。

注意:和许多简写命令一样,b 是另一个更长的 LLDB 命令的缩写。 尝试运行 b 命令的 help 来自己弄清楚实际的命令,并学习所有可以在后台做的很酷的技巧。

除了 b 命令之外,还有另一个更长的断点设置命令,它提供了许多选项。 你将在接下来的几章中探索这些选项。 许多命令将来自 breakpoint set 命令的各种选项。

正则表达式断点和范围

另一个非常强大的命令是正则表达式断点 rbreak,它是断点集合 -r %1 的缩写。 你可以使用智能的正则表达式快速创建许多断点,以便随时随地停止。

回到上一个示例,使用极长的 Swift 属性函数名称来代替输入:

(lldb) b Breakpoints.SwiftTestClass.name.setter : Swift.ImplicitlyUnwrappedOptional

你可以简单地输入:

(lldb) rb SwiftTestClass.name.setter

rb 命令将扩展为 rbreak(假设没有以“rb”开头的其他 LLDB 命令)。 这将在 SwiftTestClass 中的 name 的 setter 属性上创建一个断点。

更简化一点,你可以简单地使用以下内容:

(lldb) rb name\.setter

这将在所有包含短语 name.setter 的内容上生成断点。 如果项目中没有任何其他名为 name 的 Swift 属性,这将生效; 否则,将为每个包含具有 setter 的“name”属性的类创建多个断点。

让我们来了解一下这些正则表达式的复杂性。

在 UIViewController 的每个 Objective-C 实例方法上创建一个断点。 在 LLDB 会话中输入以下内容:

(lldb) rb '\-\[UIViewController\ '

丑陋的反斜杠是转义符,表示你希望字符在正则表达式里。 因此,此查询会在包含字符串 -[UIViewController 后跟一个空格的每个方法上中断。

但是等等…… Objective-C 的类别怎么样? 它们采用 (-|+)[ClassName(categoryName) method] 的形式。 你还必须重写正则表达式以包含类别。

在 LLDB 会话中输入以下内容,并在提示时输入 y 以确认:

(lldb) breakpoint delete

此命令将删除设置的所有断点。

接下来,输入以下内容:

(lldb) rb '\-\[UIViewController(\(\w+\))?\ 

在断点中的 UIViewController 之后,提供了带有一个或多个字母数字字符后跟空格的可选括号。

使用正则表达式断点可以使用单个表达式捕获各种断点。

你可以使用 -f 选项将断点的范围限制为特定文件。 例如,你可以输入以下内容:

(lldb) rb . -f DetailViewController.swift

如果你正在调试 DetailViewController.swift,这将非常有用。
它将在此文件中的所有属性 getter/setter,块(block)/闭包(closure),扩展(extension)/类别(category)和函数(function)/方法(method)上设置断点。 -f 称为范围限制。

如果你异常疯狂并且是痛苦的粉丝(医生称之为自虐?),你可以省略范围限制并简单地这样做:

(lldb) rb .

这将在所有内容上创造一个断点…… 是的,所有! 这将在信号项目中的所有代码上创建断点,UIKit 和 Foundation 中的所有代码 ,所有事件运行循环代码都被(期望上)触发至 60 赫兹 —— 所有。 因此,如果执行此操作,做好在调试器中输入不少 continue 的准备。

还有其他方法可以限制搜索范围。 可以使用 -s 选项限制到单个库:

(lldb) rb . -s Commons

这将在 Commons 库中的所有内容上设置断点,这是一个包含在信号项目中的动态库。

这不仅限于自己的代码;你可以使用相同的策略在 UIKit 中的每个函数上创建断点,如下所示:

(lldb) rb . -s UIKit

即使这仍然有点疯狂。 在iOS 11.0中有非常多的方法 —— 大约有 86577 个 UIKit 方法。 如何只在命中 UIKit 的第一种方法时停止,并简单地继续? -o 选项为此提供了解决方案。 它创造了所谓的“一次性”断点。 当这些断点命中时,断点将被删除。 所以它只会打一次。

要查看此操作,请在 LLDB 会话中输入以下内容:

(lldb) breakpoint delete 
(lldb) rb . -s UIKit -o

注意:在计算机执行此命令时要耐心等待,因为 LLDB 要创建大量断点。 还要确保使用的是模拟器,否则你会等很长时间!

接下来,继续调试器,然后单击表视图中的单元格。 调试器在此操作中调用的第一个 UIKit 方法上停止。 最后,继续调试器,断点将不再触发。

其他很酷的断点选项

-L 选项允许按源语言进行过滤。 因此,如果你只想在信号应用程序的 Commons 模块中寻找 Swift 代码,可以执行以下操作:

(lldb) breakpoint set -L swift -r . -s Commons

这将在 Commons 模块中的每个 Swift 方法上设置断点。

如果你想在 Swift 的 if let 周围寻找一些有趣的东西,但完全忘记它在应用程序哪里,该怎么办? 您可以使用源正则表达式断点来帮助计算感兴趣的位置! 像这样:

(lldb) breakpoint set -A -p "if let"

这将在包含 if let 的每个源代码位置创建一个断点。 你当然可以更加花哨,因为 -p 采用正则表达式断点来处理复杂的表达式。 -A 选项表示搜索项目已知的所有源文件。

如果要将上述断点查询过滤为仅在 MasterViewController.swift 和 DetailViewController.swift,则可以执行以下操作:

(lldb) breakpoint set -p "if let" -f MasterViewController.swift -f DetailViewController.swift

注意 -A 已经消失,以及每个 -f 如何指定文件名。 我很懒,所以我通常会默认 -A 从所有文件入手。

最后,你还可以按特定模块进行过滤。 如果你想为 Signals 可执行文件中的任何内容创建一个“if let”的断点(忽略其他框架,如 Commons),你可以这样做:

(lldb) breakpoint set -p "if let" -s Signals -A

这将获取所有源文件(-A),但仅将其过滤为属于 Signals 可执行文件的文件(使用 -s Signals 选项)。

还有没有一个很酷的断点选项示例? 好的,你跟我谈过这个。 让我们稍微提高复杂性并制作一个“高级”断点。

如果你想在 -[UIView setTintColor:] 上设置一个断点,但只有在从 Signals 可执行文件中实现的代码中调用该方法时才会停止该怎么做?

有几种方法可以实现这一点,有一个创造性方法来实现 —— 使用断点条件或 -c 选项。

首先,你需要弄清楚 Signals 可执行文件中的代码驻留在内存中的上限和下限。 通常,代码位于 __TEXT 段的 __text 部分。 不要担心这对于现在意味着什么,我们将在后面的章节中介绍 Mach-O 文件格式的细节。 现在,只需将 __TEXT 段视为每个可执行文件和框架所具有的可读和可执行代码的分组。

你可以使用 LLDB 转储 Signals 可执行文件中的 Mach-O 段和部分,使用以下命令:

(lldb) image dump sections Signals

抓取 __TEXT 段的上限和下限,因为实际的可执行代码将驻留在这些地址范围中。

第四章:给代码创建断点_第5张图片
Stopping in Code-5.png

对于我的情况,地址范围从 0x0000000108056000 开始,到 0x0000000108067000 结束(你的地址范围将不同)。 因此,我可以使用以下断点仅在 -[UIView setTintColor:] 从 Signals 可执行文件中的任何代码中调用时停止。

(lldb) breakpoint set -n "-[UIView setTintColor:]" -c "*(uintptr_t*)$rsp <= 0x0000000108067000 && *(uintptr_t*)$rsp >= 0x0000000108056000"

这里使用 x86_64 调用约定的知识(这只适用于 64 位 iOS 模拟器),以及调用函数时堆栈指针寄存器的工作方式。 我们不会详细介绍它是如何工作的,但快速总结一下,在 x86_64 汇编中,在调用函数之后,堆栈指针将包含一个指向调用函数的返回地址的指针。 你将在第12章“汇编和堆栈”中深入研究堆栈指针寄存器和基址指针寄存器。

为了使这个断点策略发挥作用,你还要做最后一件事。 通常,如果你创建断点,LLDB 将在函数开头跳过一些有助于设置逻辑的汇编指令(称为函数序言)。 当发生这种情况时,堆栈指针的头部将不再包含指向返回地址的指针(头部将指向新的东西)。 这意味着你需要告知 LLDB 在开始设置汇编指令之前就停止。 你可以使用以下命令执行此操作:

(lldb) settings set target.skip-prologue false

在后面的章节中,我将使你将此设置保存到 LLDB 初始化文件(~/.lldbinit)中,因为它对于探索传递给函数的参数非常有用。 即使这一点在目前没有任何意义,请不要担心 —— 你会到达那里!

修改和删除断点

现在你已经基本了解了如何创建这些断点,你可能想知道如何更改它们。 如果你找到了感兴趣的对象并希望删除断点,或暂时禁用它,该怎么做? 如果你需要修改断点以在下次触发时执行特定操作,该怎么做?

首先,你需要了解如何唯一地标识一个或一组断点。 你还可以在创建时使用 -N 选项命名断点…… 如果使用数字你不太喜欢。

构建并运行应用程序以重置 LLDB 会话。 接下来,暂停调试器并在 LLDB 会话中输入以下内容:

(lldb) b main

输出看起来像这样:

Breakpoint 1: 70 locations.

这将创建一个包含 70 个位置的断点,与各个模块中的“main”函数相匹配。

在这种情况下,断点 ID 为 1,因为它是你在此会话中创建的第一个断点。 要查看有关此断点的详细信息,可以使用 breakpoint list 子命令。 输入以下内容:

(lldb) breakpoint list 1

输出看起来类似于下面的截断输出:

1: name = 'main', locations = 70, resolved = 70, hit count = 0
  1.1: where = Signals`main at AppDelegate.swift, address = 0x00000001098b1520, resolved, hit count = 0 
  1.2: where = Foundation`-[NSThread main], address = 0x0000000109bfa9e3, resolved, hit count = 0 
  1.3: where = Foundation`-[NSBlockOperation main], address = 0x0000000109c077d6, resolved, hit count = 0 
  1.4: where = Foundation`-[NSFilesystemItemRemoveOperation main], address = 0x0000000109c40e99, resolved, hit count = 0 
  1.5: where = Foundation`-[NSFilesystemItemMoveOperation main], address = 0x0000000109c419ee, resolved, hit count = 0 
  1.6: where = Foundation`-[NSInvocationOperation main], address = 0x0000000109c6aee4, resolved, hit count = 0 
  1.7: where = Foundation`-[NSDirectoryTraversalOperation main], address = 0x0000000109caefa6, resolved, hit count = 0 
  1.8: where = Foundation`-[NSOperation main], address = 0x0000000109cfd5e3, resolved, hit count = 0 
  1.9: where = Foundation`-[_NSFileAccessAsynchronousProcessAssertionOperation main], address = 0x0000000109d55ca9, resolved, hit count = 0 
  1.10: where = UIKit`-[_UIFocusFastScrollingTest main], address = 0x000000010b216598, resolved, hit count = 0 
  1.11: where = UIKit`-[UIStatusBarServerThread main], address = 0x000000010b651e97, resolved, hit count = 0 
  1.12: where = UIKit`-[_UIDocumentActivityDownloadOperation main], address = 0x000000010b74f718, resolved, hit count = 0

这显示了该断点的详细信息,包括包含“main”一词的所有位置。

更简洁的方法是输入以下内容:

(lldb) breakpoint list 1 -b

这将为你提供在视觉上更容易的输出。 如果你有一个封装了很多断点的断点 ID,那么这个简短的标志就是一个很好的解决方案。

如果要查询 LLDB 会话中的所有断点,只需省略 ID,如下所示:

(lldb) breakpoint list

你还可以指定多个断点 ID 和范围:

(lldb) breakpoint list 1 3 
(lldb) breakpoint list 1-3

使用 breakpoint delete 删除所有断点有点笨拙。 你可以简单地使用 breakpoint list 命令中使用的相同 ID 模式来删除集合。

你可以通过指定 ID 来删除单个断点,如下所示:

(lldb) breakpoint delete 1

但是,“main”的断点有 70 个位置(可能更多或更少,具体取决于 iOS 版本)。你也可以删除单个位置,如下所示:

(lldb) breakpoint delete 1.1

这将删除断点 1 的第一个子断点,结果是只删除一个 main 函数断点,同时保持其余的 main 断点处于活动状态。

接下来?

你在本章中已经学习了很多内容。 断点是一个很大的主题,掌握快速找到感兴趣的内容的艺术对于成为调试专家至关重要。 你还开始使用正则表达式探索函数搜索。 现在是了解正则表达式语法的好时机,因为你将在本书的其余部分使用大量正则表达式。

查看 https://docs.python.org/2/library/re.html 以学习(或重新学习)正则表达式。 尝试弄清楚如何进行不区分大小写的断点查询。

你刚刚开始发现编译器如何在 Objective-C 和 Swift 中生成函数。 尝试找出停止 Objective-C 块或 Swift 闭包的语法。 完成后,尝试设计一个仅在信号项目的 Commons 框架内的 Objective-C 块上停止的断点。 这些是你将来需要构建更复杂断点的正则表达式技能。

你可能感兴趣的:(第四章:给代码创建断点)