Debugging
(调试)是指创建并使用分析框架来隔离因果路径并检验假设。debugging
最重要的工具是debugger
(调试器),它可以帮助你了解程序在运行时的行为,而无需修改代码。
LLDB
为 Apple
平台上的开发人员提供了底层调试环境。你可以从 Terminal
窗口 或 Xcode
源代码编辑器 中使用它,以查找和消除Swift
,C
,C++
和 Objective-C
代码中的问题。
debugger
具有两个主要功能:控制执行流 和 访问状态。
你主要通过在代码中的不同位置设置breakpoints
来控制程序的执行。每当程序达到设置的breakpoint
时,debugger
就会暂时停止执行该程序。在执行停止后,你可以使用debugger
检查或修改不同变量的当前状态,step over、into
或退出下一条语句,然后根据需要继续执行。
LLDB 命令语法
通过在调试会话中输入命令来与 LLDB
进行交互。每个 LLDB command
由零个或多个subcommand
组成,并且可以以下形式另外指定一个或多个选项或参数:
1 | <command> [<subcommand>...] [--<option> [<option-value>]]... [argument]... |
subcommand
子命令
Subcommands
是用空格分隔的标记,用于组织相关的操作。通常,command
的最后subcommand
是指示要执行的操作的谓词。例如,与断点管理相关的命令以breakpoint
开头,例如 breakpoint list
和breakpoint disable
命令,它们分别列出和禁用断点。
Arguments
参数
command
可能需要一个或多个arguments
。arguments
是用空格分隔的标记,用于指示要执行的操作。例如,breakpoint disable
命令需要一个参数来指定要禁用的断点,例如breakpoint disable 1
,它会禁用ID等于1的断点。
用单引号(‘’)或双引号(“”)括起来来指定包含空格的参数。在单引号或双引号中,可以使用反斜杠字符()来转义非定界的引号,例如
"some \"quoted\" string"
。
Options
选项
command
可能还包含一个或多个options
。options
是以双破折号(--
)开头的以空格分隔的标记,可以在不同的组合中使用,以修改要执行的操作。某些options
还提供了使用单破折号(-
)的简写形式。例如,当breakpoint set
命令指定--one-shot (-o)
选项时,如breakpoint set —one--shot
,该breakpoint
将在第一次导致程序停止时被删除。
某些options
指定单个空格分隔的值作命令的命名参数。例如,breakpoint set
命令可以通过传递--name
选项,并将函数名称作为选项值来设置特定函数的断点。
某些命令可能需要某些组合的options
。例如,breakpoint set
命令可以通过传递--file
和--line
选项以及相应的文件名和行号来在代码中的特定位置设置断点。
接受
options
和 自由格式参数的命令(如expression
命令),必须在最后一个option
和第一个参数之间放置一个以空格分隔的双破折号(--
)。这样可以确保以破折号(-
)开头的option
的参数被解释为参数。
命令表单
等效的 LLDB
命令可以以各种不同的形式表示。例如,以下3个命令执行相同的操作:
- Canonical form 规范形式
expression --object-description -- someVariable
命令的canonical form
充当要执行的操作的描述性表示。
- Abbreviated form 缩写形式
e -O -- someVariable
命令的abbreviated form
使用命令和子命令的缩写形式(例如e
表示expression
)以及options
的缩写形式(例如-O
表示--object-description
)。
- Alias 别名
po someVariable
可以为任何命令子序列创建一个alias
,为执行常见操作提供方便的快捷方式。例如po
用来计算并打印对象表达式。
为了清楚起见,本文档主要以canonical form
引用命令,后跟括号中的任何缩写形式或别名。
使用命令行帮助
LLDB
通过 help
命令在debugger
会话中提供了大量文档。
调用不带任何参数的 help
命令将列出所有可用的 调试器命令 以及 现有的命令别名。例如:(lldb) help
通过将特定命令或子命令作为 help
命令的参数传递,可以获得有关特定命令或子命令的用法的信息。例如:(lldb) help breakpoint set
help
命令适用于任何可用形式的命令,包括别名。例如:(lldb) help po
Breakpoints 断点
breakpoint
在执行的指定点中断程序的执行。breakpoint
是开发人员使用debugger
开始与程序进行交互的主要方法。
设置 Breakpoint
你可以使用 breakpoint set
命令设置 breakpoint
:
breakpoint set -n 函数名或绝对路径
添加断点
1 | (lldb) breakpoint set -n sayHello |
breakpoint set -f 文件名 -l 行数
在某个源文件某一行设置断点1
2
3
4
5
6(lldb) breakpoint set -f OneViewController.swift -l 47
Breakpoint 7: where = test01`test01.OneViewController.sayHello() -> () + 20 at OneViewController.swift:48:15, address = 0x000000010f50c4f4
(lldb) breakpoint list
Current breakpoints:
7: file = 'OneViewController.swift', line = 47, exact_match = 0, locations = 1, resolved = 1, hit count = 0
7.1: where = test01`test01.OneViewController.sayHello() -> () + 20 at OneViewController.swift:48:15, address = 0x000000010f50c4f4, resolved, hit count = 0
3、将breakpoint
设置在随时发生 Swift
错误 或 抛出Objective-C
异常发生时,通过在 --language-exception (-E)
选项后传递 Swift
或objc
值
1 | # 将断点配置为仅在抛出特定类型的错误或引发异常时停止 |
1 | (lldb) breakpoint set -E objc |
修改 Breakpoint
通过使用 breakpoint ID
或location ID
作为参数以及以下任何配置的选项,可以使用breakpoint Modify
命令来修改逻辑断点或个别位置:
--condition (-c)
指定一个表达式,该表达式必须计算为true才能使断点停止--ignore-count (-i)
指定在停止之前跳过断点的次数--one-shot (-o)
第一次停止时删除断点--queue-name (-q)
指定断点在其上停止的队列的名称--thread-name (-T)
指定断点在其上停止的线程的名称--thread-id (-t)
指定断点在其上停止的线程的ID(TID)--thread-index (-x)
指定断点在其上停止的线程的索引
例如,下面的代码片段显示了如何修改第一个断点,使其在第一次停在其任何位置时被删除:
1 | (lldb) breakpoint modify --one-shot 1 |
在断点处运行命令
当达到设定的breakpoint
时,它将停止程序的执行并允许运行LLDB命令。你还可以通过使用breakpoint command add
命令来指定每次到达breakpoint
时要运行的命令,该命令将 breakpoint ID
或 location ID
作为参数。
例如,要将命令添加到第一个断点的第一个位置:
1 | (lldb) breakpoint command add 1.1 |
默认情况下,breakpoint command add
命令使用LLDB command interpreter
(LLDB命令解释器),并打开一个交互式提示,该提示的行以直角括号(>)
开头。每行输入一个命令。输入命令完成后,键入DONE退出交互式提示。
如果你输入process Continue
命令作为最后一个断点命令,则debugger
器将在执行所有前面的命令后自动继续运行程序。这对于记录有关程序状态的信息而又不中断用户与程序的交互性特别方便。
1 | (lldb) breakpoint command add 1.1 |
要指定要在指定的断点内联而不是在交互式提示中执行的命令,请将--one-liner (-o)
选项(带有单引号的值作为引号括起来)传递给breakpoint command add
命令
1 | (lldb) breakpoint command add 1.1 -o "bt" |
禁用和启用断点
- 禁用
logical breakpoint
时,它不会在任何位置停止。要禁用断点而不删除它,请使用breakpoint disable
命令。
1、通过传递breakpoint ID作为参数来禁用 logical breakpoint:
1 | (lldb) breakpoint disable 1 |
2、传递location ID 禁用单个断点位置
1 | (lldb) breakpoint disable 2.1 |
- 使用
breakpoint enable
命令启用logical breakpoint
或breakpoint location
,并传递breakpoint ID
或location ID
。
1 | (lldb) breakpoint enable 1 |
- 要仅禁用
logical breakpoint
的某些位置,请使用breakpoint disable命令,传递断点ID,后跟点分隔的通配符(*)。
1 | (lldb) breakpoint disable 1.* |
删除 Breakpoint
删除断点将禁用它,并防止重新启用它。使用breakpoint delete + breakpoint ID
命令:
1 | (lldb) breakpoint delete 1 |
Watchpoints
watchpoint
(监视点)是你在地址或变量上设置的一种breakpoint
,可在访问值时随时停止,而不是在执行时进行设置。
Watchpoints
受运行调试程序的硬件上的寄存器数限制。
可以使用 watchpoint
来隔离执行过程中何时更改变量,这对于调试在代码中多个组件之间共享的状态特别有用。一旦知道了变量的更改位置和更改方式,便可以在要调查的执行点上创建breakpoints
,然后删除watchpoint
。
设置 Watchpoint
使用 watchpoint set variable 变量名
命令在变量上设置watchpoint
,并使用 watchpoint set expression
命令在表达式中的地址上设置watchpoint
。
1 | # 设置当前断点上下文的变量 |
默认情况下,watchpoint
监视对变量或地址的写访问。通过将--watch (-w)
选项传递给值read,write或read_write,指定要监视的访问类型。
1 | watchpoint set variable -w [read | write | read_write] 变量名 |
默认情况下,监视点使用目标的指针字节大小监视读写。通过传递值为 1 | 2 | 4 | 8
的 --size (-s)
选项来更改用于监视区域的字节数。
列出 Watchpoints
与breakpoints
一样,您可以使用 watchpoint list
命令显示所有已设置的watchpoints
。
1 | (lldb) watchpoint list |
像breakpoint
一样,watchpoints
也具有分配的整数ID。与断点不同,watchpoints
不会解析到任何特定位置,因为它会监视变量或地址的任何更改。
修改 Watchpoint
使用watchpoint modify
命令通过将watchpoint ID
作为参数以及--condition (-c)
选项和表达式作为其值,来传递设置watchpoint
停止的时间。
1 | (lldb) watchpoint modify --condition !places.isEmpty |
向 Watchpoints 添加命令
要添加在击中监视点时运行的命令,请使用 watchpoint command add
命令,并将 watchpoint ID
作为参数传递。
1 | (lldb) watchpoint command add 1 |
删除 Watchpoint
由于watchpoints
受硬件限制,因此在不需要它们时将其删除很重要。可以使用watchpoint delete
命令删除watchpoint
,并将 watchpoint ID
作为参数传递。
1 | (lldb) watchpoint delete 1 |
控制程序执行
一旦程序在breakpoint
处停止,它将控制权转移到debugger
。然后,开发人员可以与debugger
进行交互,以逐步完成指令并继续执行或退出。
thread step-over
\next
\n
:单步运行,把子函数当作整体一步执行。thread step-in
\step
\s
:单步运行,遇到子函数会进入函数内。thread step-out
\finish
:直接执行完当前函数所有代码,返回到上一个函数。process continue
\continue
\c
:在完成对程序当前断点处的检查后,使用此命令恢复执行程序。
汇编指令级别控制
可以使用 Xcode
-> Debug
-> Debug Workflow
-> Always Show Disassembly
切换源码级别和汇编指令级别调试面板
thread step-inst-over
\nexti
\ni
:和n
类似thread step-inst
\stepi
\si
:和s
类似
检查调用 Stack
程序运行时,它将有关正在执行的操作的信息存储在称为call stack
(调用栈)的数据结构中。每次调用一个方法时,程序都会在call stack
的顶部pushes
一个新的stack frame
(栈帧),其中包含以下内容:传递给该方法的参数(如果有),该方法的局部变量(如果有的话)以及地址 方法调用完成后返回。
当程序在breakpoint
处停止时,你可以与debugger
进行交互以检查当前stack frame
的状态。这使你可以对方法的行为以及它与程序其他部分的交互方式进行推理。
除了获取有关当前stack frame
的信息之外,你还可以与debugger
进行交互,以检查当前线程以及程序使用的其他线程的整个call stack
。
获取有关当前帧的信息
通过输入frame info
命令,可以获得代码中当前frame
(帧)的位置,包括源文件和行号。
1 | (lldb) frame info |
检查变量
- 使用
frame variable (fr v)
命令获取stack frame
中所有变量的列表。
1 | (lldb) frame variable |
- 要获取有关单个变量的信息,请使用
frame variable
命令,并将变量名称作为参数传递。
1 | (lldb) frame variable name |
- 显示当前帧的局部变量
1 | (lldb) fr v -a |
- 显示格式为十六进制的局部变量栏的内容
1 | (lldb) fr v -f x num |
执行 Expressions
LLDB最强大的功能之一是能够从调试会话中evaluate expressions
(计算表达式)。
expression (e)
命令将传递的参数作为目标语言中的表达式进行计算。例如,在调试Swift
程序时,可以将 Swift
代码当作当前stack frame
上下文中的read-eval-print (REPL) loop
来执行。这是一种在执行过程中在不同点内省变量的强大方法。
1 | (lldb) expression -- self.view.backgroundColor = .red |
打印模式
在调试会话期间查看值时,重要的是要了解frame variable
和 expression
命令之间的差异。
frame variable (f v) |
expression -- (p) |
expression -O -- (po) |
---|---|---|
不运行代码 | 运行你的代码 | 运行你的代码 |
使用LLDB格式化程序 | 使用LLDB格式化程序 | 添加代码以格式化对象 |
使用frame variable (f v)
命令不会运行任何代码,因此不会产生任何副作用。访问属性或计算调用方法的结果通常会改变程序的状态,从而掩盖你试图调试的问题,因此,使用 frame variable (f v)
命令是进行粗略调查的最安全选择。
expression
命令是 p
和 po
的别名,这是调试时经常使用的操作。两者之间的区别在于,p
使用内置的 LLDB
数据格式化程序,而 po
调用由开发人员提供的创建该对象表示形式的代码,例如Swift
中的debugDescription
方法。 如果没有自定义表示形式,则po
命令将退回到p
命令提供的表示形式上。
如果要使用默认的
LLDB
格式,请使用p
;如果要使用类型的实现来控制表示,请使用po
。
获取 Backtrace
backtrace
(回溯) 是当前活动的函数调用的列表。通过使用 thread backtrace (bt)
命令,你可以更清楚地推断导致程序处于当前状态的事件链。
调用不带参数的thread backtrace
命令会生成当前线程的backtrace
。
1 | (lldb) thread backtrace |
可以将整数作为参数传递给thread backtrace
命令,以限制显示的frames
(帧数)。
或者,以all
作为参数调用thread backtrace
命令会产生所有线程的完整backtrace
。
线程清单
程序通常跨多个线程执行代码。要获取进程中所有当前线程的列表,请使用thread list
命令。
学习博客
FrizzleFur/DailyLearning/开发工具Tool🔧/