本文翻译整理自:LLDB Debugging Guide(Updated: 2016-09-13
https://developer.apple.com/library/archive/documentation/General/Conceptual/lldb-guide/chapters/Introduction.html#//apple_ref/doc/uid/TP40016717
文章目录
一、关于LLDB和调试
调试是指创建和使用分析框架来隔离因果路径和测试假设。
调试最重要的工具是调试器,它可以帮助您了解程序在运行时的行为方式,而无需修改代码。
LLDB为Apple平台上的开发人员提供了底层调试环境。
您可以从终端窗口或Xcode源代码编辑器中使用它来查找和消除Swift、C、C++和Objective-C代码中的问题。
注意: 本文档重点介绍通过命令行提示符提供的LLDB功能。
有关从Xcode使用LLDB的更多信息,请参阅 使用Xcode进行调试。
概览
调试器有两个主要功能:控制执行流和访问状态。
您主要通过在代码中的不同位置设置断点来控制程序的执行。
每当程序遇到设置的断点时,调试器会暂时停止程序的执行。
当执行停止时,您可以使用调试器检查或修改不同变量的当前状态,跨过、进入或退出下一条语句,并根据需要继续执行。
另见
二、LLDB快速之旅
您可以使用LLDB调试器逐步运行程序、设置断点以及检查和修改程序状态。
您可以通过与一个小示例进行交互来基本了解调试器的功能。
下面的Swift代码定义了一个负责问候个人的Greeter
类型。
Greeter
类型跟踪它的熟人,并在随后的相遇中调整它的问候。
class Greeter {
private var acquaintances: Set<String> = []
func hasMet(personNamed name: String) -> Bool {
return acquaintances.contains(name)
}
func greet(personNamed name: String) {
if hasMet(personNamed: name) {
print("Hello again, \(name)!")
} else {
acquaintances.insert(name)
print("Hello, \(name). Nice to meet you!")
}
}
}
let greeter = Greeter()
greeter.greet(personNamed: "Anton")
greeter.greet(personNamed: "Mei")
greeter.greet(personNamed: "Anton")
如果使用上述代码创建一个名为Greeter.swift
的文件 并运行swiftc
命令,将文件名作为命令行参数传递-g
选项以生成调试信息,则会在当前目录中创建一个名为Greeter
的可执行文件。
$ swiftc -g Greeter.swift
$ ls
Greeter.dSYM
Greeter.swift
Greeter*
运行Greeter
可执行文件会产生以下输出:
$ ./Greeter
Hello, Anton. Nice to meet you!
Hello, Mei. Nice to meet you!
Hello again, Anton!
要通过LLDB调试器运行Greeter
程序,请将其作为命令行参数传递给lldb
命令。
$ lldb Greeter
(lldb) target create "Greeter"
Current executable set to 'Greeter' (x86_64).
此命令启动一个交互式控制台,允许您运行LLDB命令以与程序交互。
注意: 在LLDB提示符中键入help
以显示有关命令的大量留档。
另请参见使用命令行帮助。
相关章节: 理解LLDB命令语法
使用breakpoint set(b)
命令在第18行设置断点,传递--line-number (-l)
选项,行号作为其值,以使调试器在Greeter
类型声明后停止。
(lldb) breakpoint set --line 18
Breakpoint 1: where = Greeter`main + 70 at Greeter.swift:18, address = 0x0000000100001996
使用breakpoint set(b)
命令设置另一个断点,传递--name(-n)
选项,函数名greet
作为其值,以便调试器在调用greet(personNamed:)
方法时停止。
breakpoint set --name greet
Breakpoint 2: where = Greeter`Greeter.Greeter.greet (personNamed : Swift.String) -> () + 27 at Greeter.swift:9, address = 0x0000000100001bab
相关章节: 管理断点
如果使用process launch
(run
或r
)命令运行进程,则进程将在第18行的断点处停止。
注意:为程序启动调试会话不会自动运行该程序。
这允许您设置可能在启动后不久触发的断点。
(lldb) process launch
Process 97209 launched: 'Greeter' (x86_64)
Process 97209 stopped
* thread #1: tid = 0x1288be3, 0x0000000100001996 Greeter`main + 70 at Greeter.swift:18, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100001996 Greeter`main + 70 at Greeter.swift:18
15 }
16 }
18
-> 18 let greeter = Greeter()
19
20 greeter.greet(personNamed: "Anton")
21 greeter.greet(personNamed: "Mei")
输入thread step-over
(next
或n
)命令,将进程推进到第20行的下一个函数调用。
(lldb) thread step-over
Process 97209 stopped
* thread #1: tid = 0x1288be3, 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20, queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20
17
18 let greeter = Greeter()
19
-> 20 greeter.greet(personNamed: "Anton")
21 greeter.greet(personNamed: "Mei")
22 greeter.greet(personNamed: "Anton")
使用thread step-in
(step
或s
)命令将调试器单步执行到greet(personNamed:)
方法中。
(lldb) thread step-in
Process 97209 stopped
* thread #1: tid = 0x1288be3, 0x0000000100001bab Greeter`Greeter.greet(name="Anton", self=0x0000000100606b10) -> () + 27 at Greeter.swift:9, queue = 'com.apple.main-thread', stop reason = step in
frame #0: 0x0000000100001bab Greeter`Greeter.greet(name="Anton", self=0x0000000100606b10) -> () + 27 at Greeter.swift:9
6 }
7
8 func greet(personNamed name: String) {
-> 9 if hasMet(personNamed: name) {
10 print("Hello again, \(name)!")
11 } else {
12 acquaintances.insert(name)
再次输入 thread step-over
(next
或 n
) 命令,传递值为 4
的 --count
(-c
) 选项,跳到 greet(personNamed:)
方法中else分支的最后一行。
(lldb) thread step-over --count 4
Process 97209 stopped
* thread #1: tid = 0x1288be3, 0x0000000100001e0c Greeter`Greeter.greet(name="Mei", self=0x0000000100606b10) -> () + 636 at Greeter.swift:13, queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x0000000100001e0c Greeter`Greeter.greet(name="Mei", self=0x0000000100606b10) -> () + 636 at Greeter.swift:13
10 print("Hello again, \(name)!")
11 } else {
12 acquaintances.insert(name)
-> 13 print("Hello, \(name). Nice to meet you!")
14 }
15 }
16 }
相关章节: 控制流程执行
使用thread backtrace
(backtrace
或bt
)命令显示导致当前函数被调用的帧。
(lldb) thread backtrace
* thread #1: tid = 0x1288be3, 0x0000000100001a98 Greeter`Greeter.hasMet(name="Anton", self=0x0000000101200190) -> Bool + 24 at Greeter.swift:5, queue = 'com.apple.main-thread', stop reason = step in
frame #0: 0x0000000100001a98 Greeter`Greeter.hasMet(name="Anton", self=0x0000000101200190) -> Bool + 24 at Greeter.swift:5
* frame #1: 0x0000000100001be4 Greeter`Greeter.greet(name="Anton", self=0x0000000101200190) -> () + 84 at Greeter.swift:9
frame #2: 0x00000001000019eb Greeter`main + 155 at Greeter.swift:20
frame #3: 0x00007fff949d05ad libdyld.dylib`start + 1
frame #4: 0x00007fff949d05ad libdyld.dylib`start + 1
使用不带参数的frame variable
(f v
)命令查看当前堆栈帧中的所有变量。
(lldb) frame variable
(String) name = "Anton"
(Greeter.Greeter) self = 0x0000000100502920 {
acquaintances = ([0] = "Anton")
}
请注意,acquaintances
属性使用名称Anton
填充,因为它被插入到前一行。
使用expression
(e
)命令,您可以更改acquaintances
属性的状态以更改程序的最终输出。
(lldb) expression -- acquaintances.insert("Mei")
(lldb) expression -- acquaintances.remove("Anton")
(String?) $R1 = "Anton"
使用breakpoint disable command
禁用greet(personNamed:)
方法上的断点,并将断点ID2
作为参数传递。
然后,输入process continue
(continue
或c
)命令以恢复进程的执行。
(lldb) breakpoint disable 2
1 breakpoints disabled.
(lldb) process continue
Resuming thread 0x12f1d19 in process 97209
Process 97209 resuming
Hello again, Mei!
Hello, Anton. Nice to meet you!
Process 97209 exited with status = 0 (0x00000000)
请注意,写入控制台的输出与之前运行时的程序输出不同,没有调试器。
相关章节: 检查调用堆栈
三、理解LLDB命令语法
您可以通过在调试会话中输入命令来与LLDB交互。
每个LLDB命令由零个或多个子命令组成,并且可以另外指定一个或多个选项或参数,形式如下:
<command> [<subcommand>...] [--<option> [<option-value>]]... [argument]...
1、子命令
子命令是以空格分隔的标记,用于组织相关操作。
通常,命令的最后一个子命令是指示要执行的操作的动词。
例如,与管理断点相关的命令以breakpoint
开头,例如breakpoint list
和breakpoint disable
命令,它们分别列出和禁用断点。
2、论据
一个命令可能需要一个或多个参数。
参数是以空格分隔的标记,用于指示要执行的操作。
例如,breakpoint disable
命令需要单个参数来指定要禁用哪个断点,例如breakpoint disable 1
,它禁用ID等于1
的断点。
注意: 通过用单引号(' '
)或双引号(" "
)包围参数来指定包含空格的参数。
在单引号或双引号中,可以使用反斜杠字符(\
)来转义非分隔引号,例如"some \"quoted\" string"
。
3、选项
一个命令还可以包含一个或多个选项。
选项是以双破折号(--
)开头的空格分隔标记,可以在不同的组合中使用,以修改要执行的操作。
一些选项还提供了使用单破折号(-
)的速记形式。
例如,当breakpoint set
命令指定--one-shot
(-o
)选项时,如breakpoint set —one--shot
中,断点在第一次导致程序停止时被删除。
一些选项指定一个以空格分隔的值作为命令的命名参数。
例如,breakpoint set
命令可以通过传递带有函数名称作为选项值的--name
选项来设置特定函数的断点。
某些命令可能需要某些组合中的选项。
例如,breakpoint set
命令可以通过将--file
和--line
选项与相应的文件名和行号传递给代码中的特定位置来设置断点。
注意: 接受选项和自由形式参数的命令,如表达式命令,必须在最后一个选项和第一个参数之间放置一个空格分隔的双破折号(--
)。
这确保了以破折号(-
)开头的类似选项的参数被解释为参数。
4、命令窗体
等效的LLDB命令可以以各种不同的形式表示。
例如,以下每个命令都执行相同的操作,即评估变量someVariable
结果:
规范形式 | expression --object-description -- someVariable |
---|---|
缩写形式 | e -O -- someVariable |
别名 | po someVariable |
- 命令的规范形式充当要执行的操作的描述性表示。
- 命令的缩写形式使用命令和子命令的缩写,例如
e
表示expression
,而选项的缩写形式,例如-O
表示--object-description
。 - 可以为任何命令子序列创建别名,为执行常见操作提供方便的速记,例如
po
计算和打印对象表达式。
为清楚起见,本文档主要引用规范形式的命令,然后是括号中的任何缩写形式或别名。
鼓励您在调试自己的代码时使用更方便的速记。
5、使用命令行帮助
LLDB通过help
命令在调试器会话中提供广泛的留档。
不带任何参数调用help
命令会列出所有可用的调试器命令以及现有的命令别名。
(lldb) help
Debugger commands:
apropos -- Find a list of debugger commands related to a particular word/subject.
breakpoint -- A set of commands for operating on breakpoints.
...
您可以通过将特定命令或子命令作为help
命令的参数传递来获取有关其用法的信息。
例如,要获取breakpoint set
命令的帮助:
(lldb) help breakpoint set
Sets a breakpoint or set of breakpoints in the executable.
Syntax: breakpoint set <cmd-options>
...
该help
命令适用于任何可用形式的命令,包括alias
。
例如,要确定po
别名的命令:
(lldb) help po
Evaluate an expression (ObjC++ or Swift) in the current program context, using user defined variables and
variables currently in scope.
...
'po' is an abbreviation for 'expression -O -- '
四、管理断点
如前所述,断点在执行的指定点中断程序的执行。
断点是开发人员使用调试器开始与程序交互的主要方式。
1、设置断点
您可以使用breakpoint set
命令来设置断点,通过传递--name
(-n
)选项来指定函数名称,或者通过传递--file
(-f
)和--line
(-l
)选项来指定源文件和行号。
在以下示例中,在任何名为sayHello
的函数和main.swift
文件的第5
行中设置断点:
(lldb) breakpoint set -n sayHello
Breakpoint 1: where = main`main.sayHello () -> () + 15 at main.swift:2, address = 0x00000001000014cf
(lldb) breakpoint set -f main.swift -l 5
Breakpoint 2: where = main`main + 70 at main.swift:5, address = 0x00000001000014a6
您还可以通过传递--language-exception``Swift
(-E
)选项或objc
作为值,将断点设置为在引发Swift错误或引发Objective-C异常时发生。
要另外将断点配置为仅在引发特定类型的错误或引发异常时停止,请传递--exception-typename
(-O
)选项,并将类型名称作为值。
(lldb) breakpoint set -E Swift -O EnumErrorType
(lldb) breakpoint set -E objc
2、列出断点
使用breakpoint list
命令显示所有已设置的断点。
(lldb) breakpoint list
Current breakpoints:
1: name = 'sayHello', locations = 1
1.1: where = main`main.sayHello () -> () + 15 at main.swift:2, address = main[0x00000001000011cf], unresolved, hit count = 0
2: file = 'main.swift', line = 4, exact_match = 0, locations = 1
2.1: where = main`main + 70 at main.swift:7, address = main[0x00000001000011a6], unresolved, hit count = 0
设置断点会创建一个逻辑断点,该断点可以解析为一个或多个位置。
每个逻辑断点都有一个顺序分配的整数ID,从1
开始。
每个位置由其逻辑断点的ID标识,后跟一个点(.
),然后从1
开始自己的顺序分配的整数ID。
例如,第一个设置断点的第二个位置的ID为1.2
。
逻辑断点是活动的,这意味着如果任何新代码加载到程序中,都会自动创建新位置。
如果您设置的断点不解析为程序中的任何位置,则会创建一个挂起的断点。
挂起的断点可能表示断点规范中的错别字,或者文件或共享库未加载。
>> (lldb) breakpoint set --file main.swift --line 12
Breakpoint created: 2: file ='main.swift', line = 12, locations = 0 (pending)
WARNING: Unable to resolve breakpoint to any actual locations.
3、修改断点
您可以使用breakpoint modify
命令通过将断点ID或位置ID作为参数传递以及以下任何配置选项来修改逻辑断点或单个位置:
--condition
(-c
)指定一个表达式,该表达式必须计算为true才能停止断点--ignore-count
(-i
)指定停止前跳过断点的次数--one-shot
(-o
)第一次停止时删除断点--queue-name
(-q
)指定断点停止所在队列的名称--thread-name
(-T
)指定断点停止所在线程的名称--thread-id
(-t
)指定断点停止的线程的ID(TID)--thread-index
(-x
)指定断点停止所在线程的索引
例如,以下代码片段显示了如何在第一个断点停止在其任何位置时修改要删除的第一个断点:
(lldb) breakpoint modify --one-shot 1
注意: 许多配置选项也可以在最初设置断点时传递。
输入help breakpoint set
命令以获取可用选项的完整列表。
4、在断点处运行命令
当到达设置的断点时,它会停止程序的执行,并允许运行LLDB命令。
您还可以使用breakpoint command add
命令指定每次到达断点时要运行的命令,该命令以断点ID或位置ID作为参数。
例如,向第一个断点的第一个位置添加命令:
(lldb) breakpoint command add 1.1
Enter your debugger command(s). Type 'DONE' to end.
> thread backtrace
> DONE
默认情况下,breakpoint command add
命令使用LLDB命令解释器并打开一个交互式提示符,其中包含以右尖括号(>
)开头的行。
每行输入一个命令。
输入完命令后,键入DONE
以退出交互式提示符。
重要提示:最初添加到断点时不会验证命令。
如果断点命令似乎没有正在执行,请务必检查命令语法,以确保其正确。
如果输入process continue
命令作为最后一个断点命令,调试器会在执行所有上述命令后自动继续运行程序。
这对于记录有关程序状态的信息特别方便,而不会中断用户与程序的交互。
(lldb) breakpoint command add 1.1
Enter your debugger command(s). Type 'DONE' to end.
> frame variable
> process continue
> DONE
要指定对指定断点内联执行的命令,而不是在交互式提示符中,您可以将--one-liner
(-o
)选项传递给breakpoint command add
命令,该选项的值是由引号括起来的单行命令。
(lldb) breakpoint command add 1.1 -o "bt"
5、禁用和启用断点
禁用逻辑断点时,它不会在其任何位置停止。
要禁用断点而不删除它,请使用breakpoint disable
命令。
通过将断点ID作为参数传递来禁用逻辑断点。
(lldb) breakpoint disable 1
1 breakpoints disabled.
使用breakpoint disable
命令禁用单个断点位置,并传递位置ID。
(lldb) breakpoint disable 2.1
1 breakpoints disabled.
使用breakpoint enable
命令启用逻辑断点或断点位置,传递断点ID或位置ID。
在以下示例中,启用第一个断点,然后启用第二个断点的第一个位置:
(lldb) breakpoint enable 1
1 breakpoints enabled.
(lldb) breakpoint enable 2.1
1 breakpoints enabled.
要仅启用逻辑断点的某些位置,请使用breakpoint disable
命令,传递断点ID后跟一个以点分隔的通配符(*
),然后使用breakpoint enable
命令,传递要启用的任何单个断点位置。
在以下示例中,第一个断点的所有位置都被禁用,然后启用第一个断点的第一个位置:
(lldb) breakpoint disable 1.*
2 breakpoint disabled*
(lldb) breakpoint enable 1.1
1 breakpoint enabled.
6、删除断点
删除断点会禁用它并防止它被重新启用。
使用breakpoint delete
命令删除断点,并将断点ID作为参数传递。
例如,要删除第一个断点:
(lldb) breakpoint delete 1
1 breakpoints deleted; 2 breakpoint locations disabled.
7、观察点
观察点是您在地址或变量上设置的一种断点,用于在访问值时停止,而不是在执行点设置。
观察点受到运行被调试程序的硬件上的寄存器数量的限制。
当变量在执行过程中发生更改时,您可以使用观察点来隔离,这对于调试代码中多个组件共享的状态特别有用。
一旦您知道变量在哪里以及如何更改,您就可以在要调查的执行点创建断点,然后删除观察点。
7.1 设置观察点
您可以使用watchpoint set variable
命令在变量上设置观察点,使用watchpoint set expression
命令在表达式的地址上设置观察点。
(lldb) watchpoint set variable places
Watchpoint created: Watchpoint 1: addr = 0x100004a40 size = 8 state = enabled type = w
declare @ 'main.swift:7'
watchpoint spec = 'places'
new value: 1 value
(lldb) watchpoint set expression -- (int *)$places + 8
Watchpoint created: Watchpoint 2: addr = 0x100005f33 size = 8 state = enabled type = w
new value: 0x0000000000000000
默认情况下,观察点监视对变量或地址的写访问。
通过传递--watch
(-w
)选项并read
、write
或read_write
来指定要监视的访问类型。
默认情况下,观察点使用目标的指针字节大小监视读写。
通过传递值为1
、2
、4或8``-s``--size
(-s4
)选项来更改用于监视区域的字节数。
7.2 列出观察点
与断点一样,您可以使用watchpoint list
命令来显示已设置的所有观察点。
(lldb) watchpoint list
Current watchpoints:
Watchpoint 1: addr = 0x100004a50 size = 8 state = disabled type = w
declare @ 'main.swift:7'
watchpoint spec = 'places'
与断点一样,观察点具有分配的整数ID。
与断点不同,观察点不会解析到任何特定位置,因为它会监视变量或地址的任何更改。
7.3 修改观察点
使用watchpoint modify
命令来配置设置的观察点何时停止,方法是将观察点ID作为参数传递--condition
(-c
)选项并将表达式作为其值。
(lldb) watchpoint modify --condition !places.isEmpty
1 watchpoints modified.
7.4 向观察点添加命令
要添加命令以在击中观察点时运行,请使用watchpoint command add
命令,将观察点ID作为参数传递。
(lldb) watchpoint command add 1
Enter your debugger command(s). Type 'DONE' to end.
> bt
> DONE
7.5 删除观察点
由于观察点受硬件限制,因此在不再需要它们后删除它们非常重要。
您可以使用watchpoint delete
命令删除观察点,并将观察点ID作为参数传递。
(lldb) watchpoint delete 1
1 watchpoints deleted.
五、控制流程执行
一旦程序在断点处停止,它就会将控制权转移给调试器。
然后,开发人员可以与调试器交互以单步执行指令并继续执行或退出。
1、步进、退出和越过函数调用
使用thread step-over
(next
或n
)命令评估当前行并继续到下一行。
如果调试器在函数调用时停止,您可以使用thread step-in
(step
或s
)命令进入函数内部并继续。
如果调试器没有在函数调用时停止,thread step-in
命令与thread step-over
命令具有相同的效果。
该thread step-out
(finish
)命令通过继续执行执行thread step-in
命令的逆操作,直到调用下一个函数返回语句,或者弹出堆栈帧,并步出当前函数到调用它的地方。
(lldb) thread step-over
Process 97209 stopped
* thread #1: tid = 0x1288be3, 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20, queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20
17
18 let greeter = Greeter()
19
-> 20 greeter.greet(personNamed: "Anton")
21 greeter.greet(personNamed: "Mei")
22 greeter.greet(personNamed: "Anton")
(lldb) thread step-in
Process 97209 stopped
* thread #1: tid = 0x1288be3, 0x0000000100001bab Greeter`Greeter.greet(name="Anton", self=0x0000000100606b10) -> () + 27 at Greeter.swift:9, queue = 'com.apple.main-thread', stop reason = step in
frame #0: 0x0000000100001bab Greeter`Greeter.greet(name="Anton", self=0x0000000100606b10) -> () + 27 at Greeter.swift:9
6 }
7
8 func greet(personNamed name: String) {
-> 9 if hasMet(personNamed: name) {
10 print("Hello again, \(name)!")
11 } else {
12 acquaintances.insert(name)
(lldb) thread step-out
Process 97209 stopped
* thread #1: tid = 0x1288be3, 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20, queue = 'com.apple.main-thread', stop reason = step out
frame #0: 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20
17
18 let greeter = Greeter()
19
-> 20 greeter.greet(personNamed: "Anton")
21 greeter.greet(personNamed: "Mei")
22 greeter.greet(personNamed: "Anton")
2、持续执行
在当前断点检查完程序后,可以使用process continue
(continue
或c
)命令恢复程序的执行。
(lldb) process continue
Resuming thread 0x12f1d19 in process 97209
Process 97209 resuming
六、检查调用堆栈
当程序运行时,它将有关其正在做什么的信息存储在称为调用堆栈的数据结构中。
每次调用一个方法时,程序都会在调用堆栈之上推送一个新的堆栈帧,其中包含以下内容:传递给方法的参数(如果有)、方法的局部变量(如果有)以及方法调用完成后返回的地址。
当程序在断点处停止时,您可以与调试器交互以检查当前堆栈帧的状态。
这允许您推理方法的行为以及它如何与程序的其他部分交互。
除了获取有关当前堆栈帧的信息外,您还可以与调试器交互以检查当前线程以及程序使用的其他线程的整个调用堆栈。
1、获取有关当前框架的信息
通过输入frame info
命令,您可以获得代码中当前帧的位置,包括源文件和行号。
lldb) frame info
frame #0: 0x0000000100001277 Validation`(string="Sw0rdf!sh") -> Bool).(containsSymbol #1)(String) -> Bool + 23 at Validation.swift:8
2、检查变量
使用frame variable
(f v
)命令获取堆栈帧中所有变量的列表。
(lldb) frame variable
(String) name = "Anton"
(Greeter.Greeter) self = 0x0000000100502920 {
acquaintances = ([0] = "Anton")
}
要获取单个变量的信息,可以使用frame variable
命令,将变量名作为参数传递。
(lldb) frame variable name
(String) name = "Anton"
3、计算表达式
LLDB最强大的特性之一是能够从调试会话中评估表达式。
在快速浏览LLDB章节中使用的示例中,expression
(e
)命令用于修改存储属性的状态以更改程序的最终输出。
(lldb) expression -- acquaintances.insert("Mei")
(lldb) expression -- acquaintances.remove("Anton")
(String?) $R1 = "Anton"
(e
)expression
命令将传递的参数计算为目标语言中的表达式。
例如,在调试Swift程序时,您可以将Swift代码计算为当前堆栈帧上下文中的读取-评估-打印(REPL)循环。
这是在执行过程中不同点自省变量的强大方法。
4、打印模式
在调试会话期间检查值时,了解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
。
5、获取回溯
一个回溯是当前活动函数调用的列表。
通过使用thread backtrace
(bt
)命令,您可以更清楚地推理导致程序处于当前状态的事件链。
不带参数调用thread backtrace
命令会产生当前线程的回溯。
(lldb) thread backtrace
* thread #1: tid = 0x1288be3, 0x0000000100001a98 Greeter`Greeter.hasMet(name="Anton", self=0x0000000101200190) -> Bool + 24 at Greeter.swift:5, queue = 'com.apple.main-thread', stop reason = step in
frame #0: 0x0000000100001a98 Greeter`Greeter.hasMet(name="Anton", self=0x0000000101200190) -> Bool + 24 at Greeter.swift:5
* frame #1: 0x0000000100001be4 Greeter`Greeter.greet(name="Anton", self=0x0000000101200190) -> () + 84 at Greeter.swift:9
frame #2: 0x00000001000019eb Greeter`main + 155 at Greeter.swift:20
frame #3: 0x00007fff949d05ad libdyld.dylib`start + 1
frame #4: 0x00007fff949d05ad libdyld.dylib`start + 1
您可以将整数作为参数传递给thread backtrace
命令,以限制显示的帧数。
或者,使用all
作为参数调用thread backtrace
命令会生成所有线程的完整回溯。
6、列表线程
程序经常跨多个线程执行代码。
要获取进程中所有当前线程的列表,您可以使用thread list
命令。
(lldb) thread list
Process 96461 stopped
* thread #1: tid = 0x1384af1, 0x000000010000111f main`sayHello() -> () + 15 at main.swift:2, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
附录A:GDB用户的LLDB-命令摘要
LLDB是支持所有调试操作的引擎,用于Xcode和所有正式分发的Apple开发产品。
一些不熟悉Xcode的用户可能更熟悉GDB命令。
默认情况下,LLDB包含一个以GDB命令为模型的别名库,以便于入门。
本附录中的表列出了常用的GDB命令,提供了等效的LLDB命令和替代形式。
还列出了LLDB中内置的GDB兼容性别名。
注意: 完整的LLDB命令名称可以由唯一的短形式匹配。
例如,您可以使用br se
代替breakpoint set
。
1、执行命令
GDB | LLDB |
---|---|
启动一个没有参数的进程 | |
(gdb) run (gdb) r | (lldb) process launch (lldb) run (lldb) r |
启动一个进程参数<args> | |
(gdb) run <args> (gdb) r <args> | (lldb) process launch -- <args> (lldb) r <args> |
使用参数1 2 3启动进程a.out ,而无需每次都提供参数 | |
% gdb --args a.out 1 2 3 (gdb) run ... (gdb) run ... | (% lldb -- a.out 1 2 3 (lldb) run ... (lldb) run ... |
在新的终端窗口中启动带有参数的进程(仅限OS X) | |
— | (lldb) process launch --tty -- <args> (lldb) pro la -t -- <args> |
在现有终端窗口中启动带有参数的进程,/dev/ttys006 (仅限OS X) | |
— | (lldb) process launch --tty=/dev/ttys006 -- <args> (lldb) pro la -t/dev/ttys006 -- <args> |
在启动前为进程设置环境变量 | |
(gdb) set env DEBUG 1 | (lldb) settings set target.env-vars DEBUG=1 (lldb) set se target.env-vars DEBUG=1 |
在一个命令中设置流程和上线过程的环境变量 | |
(lldb) process launch -v DEBUG=1 | |
使用进程ID 123附加到进程 | |
(gdb) attach 123 | (lldb) process attach --pid 123 (lldb) attach -p 123 |
附加到名为a.out | |
(gdb) attach a.out | (lldb) process attach --name a.out (lldb) pro at -n a.out |
等待一个名为a.out 的进程启动并附加 | |
(gdb) attach -waitfor a.out | (lldb) process attach --name a.out --waitfor (lldb) pro at -n a.out -w |
连接到系统上运行的远程GDB协议服务器eorgadd ,端口8000 | |
(gdb) target remote eorgadd:8000 | (lldb) gdb-remote eorgadd:8000 |
连接到本地系统上运行的远程GDB协议服务器,端口8000 | |
(gdb) target remote localhost:8000 | (lldb) gdb-remote 8000 |
在系统eorgadd上以kdp模式连接到eorgadd | |
(gdb) kdp-reattach eorgadd | (lldb) kdp-remote eorgadd |
在当前选定的线程中执行源级单步 | |
(gdb) step (gdb) s | (lldb) thread step-in (lldb) step (lldb) s |
在当前选定的线程中执行源级单步操作 | |
(gdb) next (gdb) n | (lldb) thread step-over (lldb) next (lldb) n |
在当前选定的线程中执行指令级单步 | |
(gdb) stepi (gdb) si | (lldb) thread step-inst (lldb) si |
在当前选定的线程中执行指令级单步操作 | |
(gdb) nexti (gdb) ni | (lldb) thread step-inst-over (lldb) ni |
退出当前选定的帧 | |
(gdb) finish | (lldb) thread step-out (lldb) finish |
每一次你停下来回溯并拆卸 | |
— | (lldb) target stop-hook add``Enter your stop hook command(s). Type 'DONE' to end. > bt > disassemble --pc > DONE Stop hook #1 added. |
2、断点命令
GDB | LLDB |
---|---|
在所有函数中设置断点main | |
(gdb) break main | (lldb) breakpoint set --name main (lldb) br s -n main (lldb) b main |
在文件test.c 的第12行设置断点 | |
(gdb) break test.c:12 | (lldb) breakpoint set --file test.c --line 12 (lldb) br s -f test.c -l 12 (lldb) b test.c:12 |
在basename为main的所有C++方法中设置main | |
(gdb) break main (注意:这将在任何名为 main 的C函数上中断。) | (lldb) breakpoint set --method main (lldb) br s -M main |
在Objective-C函数处设置断点:-[NSString stringWithFormat:] | |
(gdb) break -[NSString stringWithFormat:] | (lldb) breakpoint set --name "-[NSString stringWithFormat:]" (lldb) b -[NSString stringWithFormat:] |
在所有选择器为count的Objective-C方法中设置count | |
(gdb) break count (注意:这将在任何名为 count 的C或C++函数上中断。) | (lldb) breakpoint set --selector count (lldb) br s -S count |
通过函数名称上的正则表达式设置断点 | |
(gdb) rbreak regular-expression | (lldb) breakpoint set --regex regular-expression (lldb) br s -r regular-expression |
通过源文件内容的正则表达式设置断点 | |
(gdb) shell grep -e -n pattern source-file (gdb) break source-file:CopyLineNumbers | (lldb) breakpoint set --source-pattern regular-expression --file SourceFile (lldb) br s -p regular-expression -f file |
列出所有断点 | |
(gdb) info break | (lldb) breakpoint list (lldb) br l |
删除断点 | |
(gdb) delete 1 | (lldb) breakpoint delete 1 (lldb) br del 1 |
3、观察点命令
GDB | LLDB |
---|---|
在变量写入时设置观察点 | |
(gdb) watch global_var | (lldb) watchpoint set variable global_var (lldb) wa s v global_var |
在写入内存位置时在内存位置上设置观察点 | |
(gdb) watch -location g_char_ptr | (lldb) watchpoint set expression -- my_ptr (lldb) wa s e -- my_ptr 注意:如果未指定 -x byte_size ,则要监视的区域的大小默认为指针大小。 |
此命令接受“原始”输入,计算为返回指向区域开头的无符号整数的表达式,在选项终止符(-- )之后。 | |
在观察点上设置条件 | |
— | (lldb) watch set var global (lldb) watchpoint modify -c ‘(global==5)’ (lldb) c … (lldb) bt * thread #1: tid = 0x1c03, 0x0000000100000ef5 a.out`modify + 21 at main.cpp:16, stop reason = watchpoint 1 frame #0: 0x0000000100000ef5 a.out`modify + 21 at main.cpp:16 frame #1: 0x0000000100000eac a.out`main + 108 at main.cpp:25 frame #2: 0x00007fff8ac9c7e1 libdyld.dylib`start + 1 (int32_t) global = 5 |
列出所有观察点 | |
(gdb) info break | (lldb) watchpoint list (lldb) watch l |
删除观察点 | |
(gdb) delete 1 | (lldb) watchpoint delete 1 (lldb) watch del 1 |
4、检查变量
GDB | LLDB |
---|---|
显示当前帧的参数和局部变量 | |
(gdb) info args 和 (gdb) info locals | (lldb) frame variable (lldb) fr v |
显示当前帧的局部变量 | |
(gdb) info locals | (lldb) frame variable --no-args (lldb) fr v -a |
显示局部变量bar | |
(gdb) p bar | (lldb) frame variable bar (lldb) fr v bar (lldb) p bar |
显示格式化为十六进制的局部变量bar 的内容 | |
(gdb) p/x bar | (lldb) frame variable --format x bar (lldb) fr v -f x bar |
显示全局变量baz | |
(gdb) p baz | (lldb) target variable baz (lldb) ta v baz |
显示当前源文件中定义的全局/静态变量 | |
— | (lldb) target variable (lldb) ta v |
每次停止时显示变量argc 和argv 。 | |
(gdb) display argc (gdb) display argv | (lldb) target stop-hook add --one-liner "frame variable argc argv" (lldb) ta st a -o "fr v argc argv" (lldb) display argc (lldb) display argv |
仅当您停止在名为main 的函数中时,才显示变量argc 和 argv | |
— | (lldb) target stop-hook add --name main --one-liner "frame variable argc argv" (lldb) ta st a -n main -o "fr v argc argv" |
只有当你停止在C类命名为MyClass 显示变量*this | |
— | (lldb) target stop-hook add --classname MyClass --one-liner "frame variable *this" (lldb) ta st a -c MyClass -o "fr v *this" |
5、计算表达式
GDB | LLDB |
---|---|
评估当前帧中的广义表达式 | |
(gdb) print (int) printf ("Print nine: %d.", 4 + 5) 或者如果您不想看到void返回: (gdb) call (int) printf ("Print nine: %d.", 4 + 5) | (lldb) expr (int) printf ("Print nine: %d.", 4 + 5) 或使用 print 别名:(lldb) print (int) printf ("Print nine: %d.", 4 + 5) |
创建便利变量并为其赋值 | |
(gdb) set $foo = 5 (gdb) set variable $foo = 5 或者使用 print 命令:(gdb) print $foo = 5 或者使用 call 命令:(gdb) call $foo = 5 要指定变量的类型:(gdb) set $foo = (unsigned int) 5 | LLDB计算变量声明表达式,就像您在C中编写它一样:(lldb) expr unsigned int $foo = 5 |
打印对象的Objective-Cdescription | |
(gdb) po [SomeClass returnAnObject] | (lldb) expr -O -- [SomeClass returnAnObject] 或使用po 别名:(lldb) po [SomeClass returnAnObject] |
打印表达式结果的动态类型 | |
(gdb) set print object 1 (gdb) p someCPPObjectPtrOrReference 注意:仅适用于C++对象。 | (lldb) expr -d run-target -- [SomeClass returnAnObject] (lldb) expr -d run-target -- someCPPObjectPtrOrReference 或设置动态类型打印为默认值:(lldb) settings set target.prefer-dynamic run-target |
调用函数在函数中的断点处停止 | |
(gdb) set unwindonsignal 0 (gdb) p function_with_a_breakpoint() | (lldb) expr -u 0 -- function_with_a_breakpoint() |
6、检查线程状态
GDB | LLDB |
---|---|
显示当前线程的堆栈回溯 | |
(gdb) bt | (lldb) thread backtrace (lldb) bt |
显示所有线程的堆栈回溯 | |
(gdb) thread apply all bt | (lldb) thread backtrace all (lldb) bt all |
回溯当前线程的前五帧 | |
(gdb) bt 5 | (lldb) thread backtrace -c 5 (lldb) bt 5 (lldb-169 and later) (lldb) bt -c 5 (lldb-168 and earlier) |
按索引为当前线程选择不同的堆栈帧 | |
(gdb) frame 12 | (lldb) frame select 12 (lldb) fr s 12 (lldb) f 12 |
列出当前线程中当前选定帧的信息 | |
— | (lldb) frame info |
选择调用当前堆栈帧的堆栈帧 | |
(gdb) up | (lldb) up (lldb) frame select --relative=1 |
选择当前堆栈帧调用的堆栈帧 | |
(gdb) down | (lldb) down (lldb) frame select --relative=-1 (lldb) fr s -r-1 |
使用相对偏移量选择不同的堆栈帧 | |
(gdb) up 2 (gdb) down 3 | (lldb) frame select --relative 2 (lldb) fr s -r2 (lldb) frame select --relative -3 (lldb) fr s -r-3 |
显示当前线程的通用寄存器 | |
(gdb) info registers | (lldb) register read |
写一个新的十进制值123 到当前线程寄存器rax | |
(gdb) p $rax = 123 | (lldb) register write rax 123 |
跳过当前程序计数器(指令指针)前面的8个字节 | |
(gdb) jump *$pc+8 | (lldb) register write pc $pc+8`` LLDB命令使用反引号来计算表达式并插入标量结果。 |
显示格式化为有符号十进制的当前线程的通用寄存器 | |
— | (lldb) register read --format i (lldb) re r -f i LLDB现在支持GDB速记格式语法,但命令后不允许空格:(lldb) register read/d 注意:LLDB尽可能使用与printf(3) 相同的格式字符。 |
键入help format 以查看格式说明符的完整列表。 | |
显示当前线程的所有寄存器集中的所有寄存器 | |
(gdb) info all-registers | (lldb) register read --all (lldb) re r -a |
显示当前线程中名为 rax 、rsp 和 rbp 的寄存器的值 | |
(gdb) info all-registers rax rsp rbp | (lldb) register read rax rsp rbp |
在格式化为二进制的当前线程中显示名为 rax 的寄存器的值 | |
(gdb) p/t $rax | (lldb) register read --format binary rax (lldb) re r -f b rax LLDB现在支持GDB简写格式语法,但命令后面不允许有空格: (lldb) register read/t rax (lldb) p/t $rax |
Read memory from address 0xbffff3c0 and show four hex uint32_t values | |
(gdb) x/4xw 0xbffff3c0 | (lldb) memory read --size 4 --format x --count 4 0xbffff3c0 (lldb) me r -s4 -fx -c4 0xbffff3c0 (lldb) x -s4 -fx -c4 0xbffff3c0 LLDB现在支持GDB简写格式语法,但命令后面不允许有空格 : (lldb) memory read/4xw 0xbffff3c0 (lldb) x/4xw 0xbffff3c0 (lldb) memory read --gdb-format 4xw 0xbffff3c0 |
读取内存开始于表达式argv[0] | |
(gdb) x argv[0] | (lldb) memory read argv[0] 请注意,任何命令都可以使用任何表达式周围的反引号(````)内联标量表达式结果(只要目标停止):`(lldb) memory read --size `sizeof(int)` `argv[0] |
从地址0xbffff3c0 读取512字节内存,并将结果以文本形式保存到本地文件中 | |
(gdb) set logging on (gdb) set logging file /tmp/mem.txt (gdb) x/512bx 0xbffff3c0 (gdb) set logging off | (lldb) memory read --outfile /tmp/mem.txt --count 512 0xbffff3c0 (lldb) me r -o/tmp/mem.txt -c512 0xbffff3c0 (lldb) x/512bx -o/tmp/mem.txt 0xbffff3c0 |
将二进制内存数据保存到文件开始于0x1000 ,结束于0x2000 | |
(gdb) dump memory /tmp/mem.bin 0x1000 0x2000 | (lldb) memory read --outfile /tmp/mem.bin --binary 0x1000 0x1200 (lldb) me r -o /tmp/mem.bin -b 0x1000 0x1200 |
为当前帧反汇编当前函数 | |
(gdb) disassemble | (lldb) disassemble --frame (lldb) di -f |
反汇编任何名为main | |
(gdb) disassemble main | (lldb) disassemble --name main (lldb) di -n main |
反汇编地址范围 | |
(gdb) disassemble 0x1eb8 0x1ec3 | (lldb) disassemble --start-address 0x1eb8 --end-address 0x1ec3 (lldb) di -s 0x1eb8 -e 0x1ec3 |
从给定地址反汇编20条指令 | |
(gdb) x/20i 0x1eb8 | (lldb) disassemble --start-address 0x1eb8 --count 20 (lldb) di -s 0x1eb8 -c 20 |
显示当前帧的当前函数的混合源和反汇编 | |
— | (lldb) disassemble --frame --mixed (lldb) di -f -m |
反汇编当前帧的当前函数并显示操作码字节 | |
— | (lldb) disassemble --frame --bytes (lldb) di -f -b |
拆解当前帧的电流源线 | |
— | (lldb) disassemble --line (lldb) di -l |
7、可执行和共享库查询命令
GDB | LLDB |
---|---|
列出主要的可执行文件和所有依赖的共享库 | |
(gdb) info shared | (lldb) image list |
在可执行文件或任何共享库中查找原始地址的信息 | |
(gdb) info symbol 0x1ec4 | (lldb) image lookup --address 0x1ec4 (lldb) im loo -a 0x1ec4 |
查找与二进制中的正则表达式匹配的函数 | |
(gdb) info function <FUNC_REGEX> | 这个找到调试符号:(lldb) image lookup -r -n <FUNC_REGEX> 这个查找非调试符号:(lldb) image lookup -r -s <FUNC_REGEX> 提供二进制文件列表作为参数以限制搜索。 |
仅在a.out 中查找地址信息 | |
— | (lldb) image lookup --address 0x1ec4 a.out (lldb) im loo -a 0x1ec4 a.out |
查找类型的信息Point 名称 | |
(gdb) ptype Point | (lldb) image lookup --type Point (lldb) im loo -t Point |
转储主可执行文件和任何共享库中的所有部分 | |
(gdb) maintenance info sections | (lldb) image dump sections |
转储a.out 模块中的所有部分 | |
— | (lldb) image dump sections a.out |
转储主可执行文件和任何共享库中的所有符号 | |
— | (lldb) image dump symtab |
转储所有符号在a.out 和liba.so | |
— | (lldb) image dump symtab a.out liba.so |
8、杂项
GDB | LLDB |
---|---|
将文本回显到屏幕 | |
(gdb) echo Here is some text\n | (lldb) script print "Here is some text" |
重新映射调试会话的源文件路径名 | |
(gdb) set pathname-substitutions /buildbot/path /my/path | (lldb) settings set target.source-map /buildbot/path /my/path 注意:如果您的源文件不再位于构建程序时的同一位置——也许程序是在不同的计算机上构建的——您需要告诉调试器如何在本地文件路径而不是构建系统文件路径上找到源文件。 |
提供一个目录来搜索源文件 | |
(gdb) directory /my/path | (没有等效的命令。) |
2024-06-16(日)
标签:Debugging,Apple,--,lldb,命令,LLDB,Greeter,断点,gdb From: https://blog.csdn.net/lovechris00/article/details/139711541