LLDB官网教程 - 中文版
苹果官网教程英文版

1、关于LLDB和调试

调试是指创建和使用一个分析框架来分离因果关系和测试假设.
调试最重要的工具是调试器(debugger)
调试器(debugger)可以帮助你了解程序在运行时的行为,而无需修改代码。LLDB为Apple平台上的开发人员提供了基础调试环境。你可以从“终端”窗口或Xcode源代码编辑器中使用它去查找和消除Swift,C,C ++和Objective-C代码中的问题。
注意:本文档重点介绍LLDB提供的命令行提示符功能.
调试器(debugger)具有两个主要功能:
控制执行流程(execution flow)和访问状态(accessing state);
你主要通过在代码中的不同位置设置断点(break points)来控制程序的执行。每当程序达到设置的断点时,调试器就会暂时停止执行程序。在执行停止后,你可以使用调试器检查(inspect)或修改不同变量的当前状态,单步执行,进入或退出下一条语句,然后根据需要继续执行。


2、LLDB 快速浏览

你可以使用LLDB调试器逐步(step-by-step)运行程序,设置断点(breakpoints)以及检查(inspect)和修改(modify)程序状态
通过与一个小示例进行交互,你可以基本了解调试器的功能
以下 Swift 代码定义了一个迎宾(Greeter)类负责迎接个人。迎宾类会记录它的熟人并在后面相遇调整问候语。

  1. class Greeter {
  2. private var acquaintances: Set<String> = []
  3. func hasMet(personNamed name: String) -> Bool {
  4. return acquaintances.contains(name)
  5. }
  6. func greet(personNamed name: String) {
  7. if hasMet(personNamed: name) {
  8. print("Hello again, \(name)!")
  9. } else {
  10. acquaintances.insert(name)
  11. print("Hello, \(name). Nice to meet you!")
  12. }
  13. }
  14. }
  15. let greeter = Greeter()
  16. greeter.greet(personNamed: "Anton")
  17. greeter.greet(personNamed: "Mei")
  18. greeter.greet(personNamed: "Anton")

如果创建一个名为Greeter.swift使用上面的代码并运行swiftc命令,将文件名作为命令行参数-g选项一起传递以生成调试信息,将在当前目录中创建了一个名为Greeter的可执行文件

  1. $ swiftc -g Greeter.swift
  2. $ ls
  3. Greeter.dSYM
  4. Greeter.swift
  5. Greeter*

运行Greeter可执行文件会产生以下输出:

  1. $ ./Greeter
  2. Hello, Anton. Nice to meet you!
  3. Hello, Mei. Nice to meet you!
  4. Hello again, Anton!

通过LLDB调试器运行Greeter程序,请将其作为命令行参数传递给lldb命令。此lldb Greeter执行启动一个交互式控制台,允许你运行LLDB命令与程序交互。

  1. $ lldb Greeter
  2. (lldb) target create "Greeter"
  3. Current executable set to 'Greeter' (x86_64).

注意:在LLDB提示符中输入help可以调出关于命令的大量文档。请参见使用命令行帮助
相关章节:理解LLDB命令语法
使用 breakpoint Set(b)命令在第18行设置一个断点,传递以行号为其值的—line number(-l)选项,使调试器在Greeter类型声明之后停止。

  1. (lldb) breakpoint set --line 18
  2. Breakpoint 1: where = Greeter`main + 70 at Greeter.swift:18, address = 0x0000000100001996

使用breakpoint Set(b)命令设置另一个断点,传递—name (-n) 选项并将函数名greet作为其值,以便在调用greet(personName:)方法时停止调试器。

  1. breakpoint set --name greet
  2. Breakpoint 2: where = Greeter`Greeter.Greeter.greet (personNamed : Swift.String) -> () + 27 at Greeter.swift:9, address = 0x0000000100001bab

相关章节:管理断点
如果使用 process launch(run或r)命令运行进程,则进程将在第18行的断点处停止。
注意:启动程序的调试会话不会自动运行该程序。这允许你设置可能在启动后不久触发的断点。

  1. (lldb) process launch
  2. Process 97209 launched: 'Greeter' (x86_64)
  3. Process 97209 stopped
  4. * thread #1: tid = 0x1288be3, 0x0000000100001996 Greeter`main + 70 at Greeter.swift:18, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  5. frame #0: 0x0000000100001996 Greeter`main + 70 at Greeter.swift:18
  6. 15 }
  7. 16 }
  8. 18
  9. -> 18 let greeter = Greeter()
  10. 19
  11. 20 greeter.greet(personNamed: "Anton")
  12. 21 greeter.greet(personNamed: "Mei")

输入thread step over(next或n)命令,将进程推进到第20行的下一个函数调用。

  1. (lldb) thread step-over
  2. Process 97209 stopped
  3. * thread #1: tid = 0x1288be3, 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20, queue = 'com.apple.main-thread', stop reason = step over
  4. frame #0: 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20
  5. 17
  6. 18 let greeter = Greeter()
  7. 19
  8. -> 20 greeter.greet(personNamed: "Anton")
  9. 21 greeter.greet(personNamed: "Mei")
  10. 22 greeter.greet(personNamed: "Anton")

使用thread step-in(step或s)命令让调试器单步执行greet(personname:)方法。

  1. (lldb) thread step-in
  2. Process 97209 stopped
  3. * 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
  4. frame #0: 0x0000000100001bab Greeter`Greeter.greet(name="Anton", self=0x0000000100606b10) -> () + 27 at Greeter.swift:9
  5. 6 }
  6. 7
  7. 8 func greet(personNamed name: String) {
  8. -> 9 if hasMet(personNamed: name) {
  9. 10 print("Hello again, \(name)!")
  10. 11 } else {
  11. 12 acquaintances.insert(name)

再次输入thread step-over(next或n)命令,传递值为4的—count(-c)选项,以跳到greet(personNamed:)方法中else分支的最后一行。相关章节:控制流程执行

  1. (lldb) thread step-over --count 4
  2. Process 97209 stopped
  3. * 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
  4. frame #0: 0x0000000100001e0c Greeter`Greeter.greet(name="Mei", self=0x0000000100606b10) -> () + 636 at Greeter.swift:13
  5. 10 print("Hello again, \(name)!")
  6. 11 } else {
  7. 12 acquaintances.insert(name)
  8. -> 13 print("Hello, \(name). Nice to meet you!")
  9. 14 }
  10. 15 }
  11. 16 }

使用thread backtrace(backtrace或bt)命令,查看当前函数的调用堆栈。

  1. (lldb) thread backtrace
  2. * 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
  3. frame #0: 0x0000000100001a98 Greeter`Greeter.hasMet(name="Anton", self=0x0000000101200190) -> Bool + 24 at Greeter.swift:5
  4. * frame #1: 0x0000000100001be4 Greeter`Greeter.greet(name="Anton", self=0x0000000101200190) -> () + 84 at Greeter.swift:9
  5. frame #2: 0x00000001000019eb Greeter`main + 155 at Greeter.swift:20
  6. frame #3: 0x00007fff949d05ad libdyld.dylib`start + 1
  7. frame #4: 0x00007fff949d05ad libdyld.dylib`start + 1

使用不带参数的frame variable (f v)命令,查看当前堆栈帧中的所有变量。
请注意,acquaintances属性使用名称Anton填充,因为它是在上一行中插入的。

  1. (lldb) frame variable
  2. (String) name = "Anton"
  3. (Greeter.Greeter) self = 0x0000000100502920 {
  4. acquaintances = ([0] = "Anton")
  5. }

使用expression (e)命令,你可以更改acquaintances属性的状态以更改程序的最终输出

  1. (lldb) expression -- acquaintances.insert("Mei")
  2. (lldb) expression -- acquaintances.remove("Anton")
  3. (String?) $R1 = "Anton"

使用breakpoint disable命令禁用greet(personNamed:)方法上的断点,并将断点ID 2作为参数传递。然后,输入processcontinue(continue或c)命令继续执行进程。
请注意,写入控制台的输出与以前在没有调试器的情况下运行的程序的输出不同。
相关章节:检查调用堆栈

  1. (lldb) breakpoint disable 2
  2. 1 breakpoints disabled.
  3. (lldb) process continue
  4. Resuming thread 0x12f1d19 in process 97209
  5. Process 97209 resuming
  6. Hello again, Mei!
  7. Hello, Anton. Nice to meet you!
  8. Process 97209 exited with status = 0 (0x00000000)

3、了解LLDB命令语法

你可以在调试会话中输入命令与LLDB进行交互。每个LLDB命令由零个或多个子命令组成,并且可以指定一个或多个选项或参数,形式如下图:
无法复制加载中的内容

子命令(Subcommands)

子命令是用空格来分隔相关操作的标记。通常,命令的最后一个子命令是一个动词,指示要执行的操作。例如,管理断点相关的命令要以breakpoint开头,例如breakpoint list和breakpoint disable命令,分别列出了断点列表和禁用断点。

参数(Arguments)

命令可能需要一个或多个参数。参数是以空格作为分隔的标记,用于指示要执行的操作。例如,该breakpoint disable命令需要一个参数来指定要禁用哪个断点,例如breakpoint disable 1,将禁用ID为1的断点。 注意: 在参数上用单引号(’ ‘)或双引号(” “)括起来可以指定包含空格的参数。在单引号或双引号中,你可以使用反斜杠字符(\)来转义非分隔引号,例如”some \”quoted\” string”。

可选项(Options)

命令还可以包含一个或多个选项。
选项是以双破折号(—)开头的,以空格作为分隔的标记,可以在不同的组合中使用,用来修改要执行的操作。一些选项还提供了使用单破折号(-)的简写形式。例如,当breakpoint set命令指定—one-shot(-o)选项时(如中所示)breakpoint set —one—shot,断点将在第一次导致程序停止时被删除。
一些选项指定一个以空格分隔的值,用作命令的命名参数。例如,breakpoint set命令可以通过—name选项在指定的函数名上设置断点。
有些命令可能需要某些组合的选项。例如,breakpoint set命令可以通过—file和—line选项,在指定的文件名和行号上设置断点。 注意:接受选项和自由格式参数的命令,例如expression命令,必须在最后一个选项和第一个参数之间放置一个以空格分隔的双破折号(—)。这样可确保以破折号(-)开头像选项的参数被解释为参数。

命令形式(Command Forms)

相同的LLDB命令可以以不同的形式表示
例如,下面表格中每个命令执行的操作都是对变量someVariable的描述并打印结果:
无法复制加载中的内容

  • 命令的规范形式作为要执行的动作的描述性表示。
  • 命令的缩写形式使用命令和子命令的缩写形式(例如e 表示 expression)和选项的缩写形式(例如-O 表示—object-description)。
  • 任意命令序列可以创建别名,为执行常见操作提供了一种方便的简写方式,例如po,评估和打印对象的表达。
  • 为了清晰起见,本文档主要以规范形式引用命令,后面用括号括起任何缩写形式或别名。建议你在调试自己的代码时使用更方便的简写方式。

    使用命令行帮助(Using Command-Line Help)

    LLDB通过help命令在调试器会话中提供了大量的文档
    在不带任何参数的情况下调用help命令会列出所有可用的调试器命令以及现有的命令的别名。
    1. (lldb) help
    2. Debugger commands:
    3. apropos -- Find a list of debugger commands related to a particular word/subject.
    4. breakpoint -- A set of commands for operating on breakpoints.
    5. ..
    你可以通过将特定命令或子命令作为help命令的参数,可以获得有关特定命令或子命令的用法的信息。例如,要获得breakpoint set命令帮助:
    1. (lldb) help breakpoint set
    2. Sets a breakpoint or set of breakpoints in the executable.
    3. Syntax: breakpoint set <cmd-options
    4. ...
    help命令适用于任何可用形式的命令,包括alias。例如,确定别名为po的命令:
    1. (lldb) help po
    2. Evaluate an expression (ObjC++ or Swift) in the current program context, using user defined variables and
    3. variables currently in scope.
    4. ...
    5. 'po' is an abbreviation for 'expression -O -- '

4、管理断点

如之前所述,断点在执行中的指定位置中断程序的执行。
一个开发者用调试器与程序交互的主要方式就是断点。

设置一个断点(Setting a Breakpoint )

你可以使用 breakpoint set 命令设置一个断点,通过传递 —name(-n)选项来指定函数名,或者通过 —file(-f) 和 —line(-l)指定一个源文件、行号。 在下面的例子中设置了断点,分别在 sayHello 的函数中 和main.swift文件中第五行代码中。

  1. (lldb) breakpoint set -n sayHello
  2. Breakpoint 1: where = main`main.sayHello () -> () + 15 at main.swift:2, address = 0x00000001000014cf
  3. (lldb) breakpoint set -f main.swift -l 5
  4. Breakpoint 2: where = main`main + 70 at main.swift:5, address = 0x00000001000014a6

在发生Swift错误或引发Objective-C异常时,你可以将—language-exception(-E)选项传递为Swift或objc作为值来设置断点。若要将断点设置成仅在引发特定类型的错误或引发异常时才停止,请传递—exception-typename(-O)选项,并将类型名称作为值

  1. (lldb) breakpoint set -E Swift -O EnumErrorType
  1. (lldb) breakpoint set -E objc

列出断点(Listing Breakpoints)

你可以使用 breakpoint list 命令展示所有已设置的断点

  1. (lldb) breakpoint list
  2. Current breakpoints:
  3. 1: name = 'sayHello', locations = 1
  4. 1.1: where = main`main.sayHello () -> () + 15 at main.swift:2, address = main[0x00000001000011cf], unresolved, hit count = 0
  5. 2: file = 'main.swift', line = 4, exact_match = 0, locations = 1
  6. 2.1: where = main`main + 70 at main.swift:7, address = main[0x00000001000011a6], unresolved, hit count = 0

设置一个断点会创建一个逻辑断点,该断点可能会解析到一个或多个位置。
每个逻辑断点都有一个按顺序分配的整数ID,从1开始。每个逻辑断点都由ID标识, 后面跟一个点(.),然后从1开始分配自己的顺序。例如: 第一个断点的第二个位置的ID是 1.2 。
当逻辑断点处于活动状态意味着如果将任何新代码加载到程序中,会自动创建新位置,如果你设置的断点无法解析到程序中的任何位置,则会创建一个挂起断点,挂起的断点可能表示断点规范中有错字,或者未加载文件或共享库。

  1. >> (lldb) breakpoint set --file main.swift --line 12
  2. Breakpoint created: 2: file ='main.swift', line = 12, locations = 0 (pending)
  3. WARNING: Unable to resolve breakpoint to any actual locations.

修改断点(Modifying a Breakpoint)

你可以使用 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)指定断点停止的线程的索引

例如,以下代码片段展示了如何修改第一个断点,使其在第一次停在其任何位置时被删除:
注意: 最初设置断点时,也可以传递许多配置选项。输入help breakpoint set命令以获取可用选项的完整列表。

  1. (lldb) breakpoint modify --one-shot 1

在断点处运行命令(Running Commands at a Breakpoint)

当程序执行到断点的设置处会停止执行并允许运行LLDB命令,你还可以使用 breakpoint command add命令指定每次到达断点时需要执行的命令,该命令将断点ID或位置ID作为参数。 例如,在第一个断点处的第一个位置添加一个命令:

  1. (lldb) breakpoint command add 1.1
  2. Enter your debugger command(s). Type 'DONE' to end.
  3. > thread backtrace
  4. > DONE

默认情况下,该breakpoint command add命令使用LLDB命令解释器,并打开一个交互式提示,该提示的行以直角括号(>)开头。每行输入一个命令。输入命令完成后,输入DONE以退出交互式提示。
重要说明: 最初添加到断点的命令不会得到校验。如果断点命令没有执行,请确保检查命令语法以确保其正确。
如果将 process continue命令作为最后一个断点命令输入,则调试器执行完之前所有的命令之后会继续运行程序,这对于记录有关程序状态的信息而不中断用户与程序的交互来说很方便。

  1. (lldb) breakpoint command add 1.1
  2. Enter your debugger command(s). Type 'DONE' to end.
  3. > frame variable
  4. > process continue
  5. > DONE

要指定某个断点内联而不是在交互式提示中执行的命令,请将—one-liner(-o)选项与单行命令一起传递给breakpoint command add命令,并在其前加上引号作为其值。

  1. (lldb) breakpoint command add 1.1 -o "bt"

禁用或启用断点(Disabling and Enabling a Breakpoint)

当一个逻辑断点处于禁用状态时,不会在任何位置上停止。如果你要禁用断点而不删除它,可以使用breakpoint disable命令。 通过断点ID作为入参来禁用一个逻辑断点。

  1. (lldb) breakpoint disable 1
  2. 1 breakpoints disabled.

使用breakpoint disable命令禁用单个断点位置,并传递位置ID。

  1. (lldb) breakpoint disable 2.1
  2. 1 breakpoints disabled.

使用breakpoint enable命令来启用一个逻辑断点或断点位置,并传递断点ID或位置ID,在下面的例子中,启用了第一个断点和启用了第二个断点的第一个位置:

  1. (lldb) breakpoint enable 1
  2. 1 breakpoints enabled.
  3. (lldb) breakpoint enable 2.1
  4. 1 breakpoints enabled.

要仅启用逻辑断点的某些位置,请使用breakpoint disable命令,通过传递断点ID,后跟点分隔的通配符(*),然后使用breakpoint enable命令,传递要启用的任何单个断点位置。在以下示例中,禁用了第一个断点的所有位置,然后启用了第一个断点的第一个位置:

  1. (lldb) breakpoint disable 1.*
  2. 2 breakpoint disabled*
  3. (lldb) breakpoint enable 1.1
  4. 1 breakpoint enabled

删除断点(Deleting a Breakpoint)

删除断点将禁用它,并防止重新启用它。使用breakpoint delete命令删除断点,并将断点ID作为参数传递。例如,要删除第一个断点:

  1. (lldb) breakpoint delete 1
  2. 1 breakpoints deleted; 2 breakpoint locations disabled.

观察点(Watchpoints)

观察点是断点的一种,你设置的地址或者变量在任何时候停止都可以访问而不是在执行的时候设置的。
观察点受运行调试程序的硬件上的寄存器数限制。
你可以使用观察点来隔离执行过程中变量的更改,这对于在代码中多个组件之间共享的调试状态特别有用,一旦知道了变量的更改位置和更改方式,便可以在要调查的执行点上创建断点,然后删除观察点。

设置一个观察点(Setting a Watchpoint)

你可以用watchpoint set variable命令在变量上设置观察点,并使用watchpoint set expression命令在表达式中的地址上设置监视点。

  1. (lldb) watchpoint set variable places
  2. Watchpoint created: Watchpoint 1: addr = 0x100004a40 size = 8 state = enabled type = w
  3. declare @ 'main.swift:7'
  4. watchpoint spec = 'places'
  5. new value: 1 value
  6. (lldb) watchpoint set expression -- (int *)$places + 8
  7. Watchpoint created: Watchpoint 2: addr = 0x100005f33 size = 8 state = enabled type = w
  8. new value: 0x0000000000000000

默认情况下,使用—watch (-w)作为参数 值为 read, write、read_write去指定一个变量或地址的观察权限。
默认情况下,观察点使用目标的指针字节大小监视读写。可以修改显示的区域的字节数—size(-s)该选项的值为1,2,4,或8。

列出观察点( Listing Watchpoints)

与断点一样,你可以使用watchpoint list命令显示所有已设置的观察点。观察点也具有分配的整数ID。与断点不同,观察点不会解析到任何特定位置,因为它会监视变量或地址的任何修改。

  1. (lldb) watchpoint list
  2. Current watchpoints:
  3. Watchpoint 1: addr = 0x100004a50 size = 8 state = disabled type = w
  4. declare @ 'main.swift:7'
  5. watchpoint spec = 'places

修改观察点(Modifying a Watchpoint)

使用watchpoint modify命令通过将观察点ID作为参数以及—condition(-c)选项和表达式作为其值设置观察点停止的时间。

  1. (lldb) watchpoint modify --condition !places.isEmpty
  2. 1 watchpoints modified.

向观察点添加命令(Adding Commands to Watchpoints)

当命中观察点时添加命令时可以使用watchpoint command add命令,并用观察点ID作为参数传递

  1. (lldb) watchpoint command add 1
  2. Enter your debugger command(s). Type 'DONE' to end.
  3. > bt
  4. > DONE

删除一个观察点(Deleting a Watchpoint)

由于观察点受硬件限制,因此在不再需要它们时将其删除很重要。可以使用watchpoint delete命令删除观察点,该命令使用观察点ID作为参数。

  1. (lldb) watchpoint delete 1
  2. 1 watchpoints deleted.

5、控制执行流程

控制执行流程(Controlling Process Execution)

一旦程序在断点处停下,它会将控制权转移到调试器。然后,开发人员可以基于调试器逐步执行指令,选择继续执行程序或者退出。

单步执行函数调用

如果调试器在一个函数调用中停下,你可以使用thread step-in(step或s)命令单步执行函数并继续执行下一行。
如果调试器在函数调用时没有停止,那么 thread step-in 命令与thread step-over 命令具有相同的作用。
thread step out(finish)命令会执行执行thread step in命令的逆向操作,具体提现是它会继续执行,直到调用下一个函数的 return语句,或抛出函数调用堆栈,然后从当前函数跳出到调用它的位置。

  1. (lldb) thread step-over
  2. Process 97209 stopped
  3. * thread #1: tid = 0x1288be3, 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20, queue = 'com.apple.main-thread', stop reason = step over
  4. frame #0: 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20
  5. 17
  6. 18 let greeter = Greeter()
  7. 19
  8. -> 20 greeter.greet(personNamed: "Anton")
  9. 21 greeter.greet(personNamed: "Mei")
  10. 22 greeter.greet(personNamed: "Anton")
  11. (lldb) thread step-in
  12. Process 97209 stopped
  13. * 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
  14. frame #0: 0x0000000100001bab Greeter`Greeter.greet(name="Anton", self=0x0000000100606b10) -> () + 27 at Greeter.swift:9
  15. 6 }
  16. 7
  17. 8 func greet(personNamed name: String) {
  18. -> 9 if hasMet(personNamed: name) {
  19. 10 print("Hello again, \(name)!")
  20. 11 } else {
  21. 12 acquaintances.insert(name)
  22. (lldb) thread step-out
  23. Process 97209 stopped
  24. * thread #1: tid = 0x1288be3, 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20, queue = 'com.apple.main-thread', stop reason = step out
  25. frame #0: 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20
  26. 17
  27. 18 let greeter = Greeter()
  28. 19
  29. -> 20 greeter.greet(personNamed: "Anton")
  30. 21 greeter.greet(personNamed: "Mei")
  31. 22 greeter.greet(personNamed: "Anton")

继续执行(Continuing Execution)

一旦你检查完程序当前的断点,可以使用 process continue(continue或c)命令继续执行程序。

  1. (lldb) process continue
  2. Resuming thread 0x12f1d19 in process 97209
  3. Process 97209 resuming

6、检查调用堆栈

当程序运行时,它将有关正在执行的操作的信息存储在称为调用栈的数据结构中。每次调用一个方法时,程序都会在调用堆栈的顶部推入一个新的堆栈框架,其中包含以下内容:传递给该方法的参数(如果有),该方法的局部变量(如果有),以及在方法调用完成后返回的地址(指针)
当程序在断点处停止时,你可以操作(交互)调试器来检查当前堆栈结构的状态。这允许你分析(推理、判断)一个方法的行为以及它如何与程序的其他部分交互。
除了获取有关当前堆栈结构信息之外,你还可以操作调试器来检查当前线程以及程序其他线程的整个调用堆栈

获取有关当前框架的信息(Getting Information About the Current Frame)

通过输入frame info命令,你可以在代码中获取当前帧的位置,包括源文件和行数

  1. (lldb) frame info
  2. frame #0: 0x0000000100001277 Validation`(string="Sw0rdf!sh") -> Bool).(containsSymbol #1)(String) -> Bool + 23 at Validation.swift:8

检查变量(Inspecting Variables)

你可以使用frame variable(f v)命令获取堆栈框架中所有变量的列表。
要获取有关单个变量的信息,你可以使用frame variable命令,将变量名称作为一个参数传递

计算表达式(Evaluating Expressions)

LLDB最强大的特性之一是在内部的某个调试会话中计算表达式的能力。
在“ LLDB快速浏览”一章中使用的示例中,expression(e)命令用于修改存储属性的状态,以更改程序的最终输出。

  1. lldb) expression -- acquaintances.insert("Mei")
  2. (lldb) expression -- acquaintances.remove("Anton")
  3. (String?) $R1 = "Anton"

expression (e)命令将传递的参数作为目标语言中的表达式计算。例如,在调试Swift程序时,可以在当前堆栈结构的上下文中对Swift代码进行评估,就像它是一个read-eval-print (REPL)循环一样。这是在执行过程中在不同的点自查(反省、内省)变量的一种强大的方法。

打印模式(Printing Modes)

在调试会话期间检查值时,了解frame variableexpression命令之间的区别是很重要的。

frame variable(f v) expression — (p) expression -O —(po)
Does not run code
不运行代码
Runs your code
运行代码
Runs your code
运行代码
Uses LLDB formatters
使用LLDB格式化
Uses LLDB formatters
使用LLDB格式化
Adds code to format objects
添加代码以格式化对象

使用frame variable(f v)命令不会运行任何代码,因此不会产生任何副作用。访问属性或计算调用一个方法的结果通常会改变程序的状态,从而模糊你试图调试的问题。因此,使用frame variable(f v)命令是进行粗略检查的最安全选择
expression命令是p和po的别名,这是在调试时常用的操作。两者之间的区别是p使用内置的LLDB数据格式化程序,然而po调用由开发人员提供的代码来创建该对象的表示,例如在Swift中的debugDescription方法。如果没有可用的自定义表示,po命令将退回到p命令提供的表示形式上。
注意: 使用p时要使用默认的LLDB格式;使用po时要使用类型的实现来控制表示。

获取回溯(Getting a Backtrace)

一个回溯当前活动函数调用调用的列表。通过使用thread backtrace(bt)命令,你可以更清楚地推断导致程序处于当前状态的事件链。

  • 调用不带参数thread backtrace命令,会产生对当前线程的回溯。
  • 你可以将整数作为参数传递给thread backtrace命令,来限制显示的帧数.
  • 或者,使用all作为参数调用thread backtrace命令会生成所有线程的完整回溯。 ``` (lldb) thread backtrace
  • thread #1: tid = 0x1288be3, 0x0000000100001a98 GreeterGreeter.hasMet(name="Anton", self=0x0000000101200190) -> Bool + 24 at Greeter.swift:5, queue = 'com.apple.main-thread', stop reason = step in frame #0: 0x0000000100001a98 GreeterGreeter.hasMet(name=”Anton”, self=0x0000000101200190) -> Bool + 24 at Greeter.swift:5
  • frame #1: 0x0000000100001be4 GreeterGreeter.greet(name="Anton", self=0x0000000101200190) -> () + 84 at Greeter.swift:9 frame #2: 0x00000001000019eb Greetermain + 155 at Greeter.swift:20 frame #3: 0x00007fff949d05ad libdyld.dylibstart + 1 frame #4: 0x00007fff949d05ad libdyld.dylibstart + 1
    1. <a name="OhdvK"></a>
    2. ## 线程列表展示(Listing Threads)
    3. 程序通常跨多个线程执行代码。要**获取进程中所有当前线程的列表**,请使用**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 ```

    7、附录A:针对GDB用户的LLDB –命令摘要

  • LLDB是一个支持Xcode和所有苹果发布的开发产品的调试引擎。一些新使用Xcode的用户可能更熟悉GDB命令。默认情况下,LLDB包含一个基于GDB命令建模的别名库,以便于轻松入门。
  • 本附录中的表格列出了常用的GDB命令,并提供了等价的LLDB命令和替代形式。还列出了LLDB中内置的GDB兼容性别名。
  • 注意: 完整的LLDB命令名可以用唯一的简短形式匹配。例如: 你可以使用br se而不是断点设置。

    执行命令(Execution Commands)

GDB LLDB
Launch a process with no arguments
启动一个不带参数的进程
(gdb) run
(gdb) r
(lldb) process launch
(lldb) run
(lldb) r
Launch a process with arguments
启动一个参数为的进程
(gdb) run
(gdb) r
(lldb) process launch —
(lldb) r
Launch process a.out with arguments 1 2 3 without having to supply the args every time
使用参数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
Launch a process with arguments in a new terminal window (OS X only)
在一个新的终端窗口中启动一个带参数的进程(仅限OS X)
- (lldb) process launch —tty —
(lldb) pro la -t —
Launch a process with arguments in an existing Terminal window, /dev/ttys006 (OS X only)
在现有的终端窗口/dev/ttys006中启动一个带参数的进程(仅适用于OS X)
- (lldb) process launch —tty=/dev/ttys006 —
(lldb) pro la -t/dev/ttys006 —
Set environment variables for process before launching
在启动前设置进程的环境变量
(gdb) set env DEBUG 1 (lldb) settings set target.env-vars DEBUG=1
(lldb) set se target.env-vars DEBUG=1
Set environment variables for process and launch process in one command
在一个命令中为进程设置环境变量和启动进程
- (lldb) process launch -v DEBUG=1
Attach to the process with process ID 123
附加到进程ID为123的进程中
(gdb) attach 123 (lldb) process attach —pid 123
(lldb) attach -p 123
Attach to a process named a.out
附加到进程名为a.out的进程中
(gdb) attach a.out (lldb) process attach —name a.out
(lldb) pro at -n a.out
Wait for a process named a.out to launch and attach
等待进程名为a.out的进程启动然后附加到进程中
(gdb) attach -waitfor a.out (lldb) process attach —name a.out —waitfor
(lldb) pro at -n a.out -w
Attach to a remote GDB protocol server running on the system eorgadd, port 8000
连接到一个运行在系统eorgadd上的远程GDB服务器,端口8000
(gdb) target remote eorgadd:8000 (lldb) gdb-remote eorgadd:8000
Attach to a remote GDB protocol server running on the local system, port 8000
连接到本地系统上运行的远程GDB服务器,端口8000
(gdb) target remote localhost:8000 (lldb) gdb-remote 8000
Attach to a Darwin kernel in kdp mode on the system eorgadd
在系统eorgadd上以kdp模式附加Darwin内核
(gdb) kdp-reattach eorgadd (lldb) kdp-remote eorgadd
Do a source-level single step in the currently selected thread
在当前选定的线程中执行源码级的单步(深入)操作
(gdb) step
(gdb) s
(lldb) thread step-in
(lldb) step
(lldb) s
Do a source-level single step over in the currently selected thread
在当前选定的线程中执行源码级单步操作
(gdb) next
(gdb) n
(lldb) thread step-over
(lldb) next
(lldb) n
Do an instruction-level single step in the currently selected thread
在当前选定的线程中执行指令级的单步(深入)操作
(gdb) stepi
(gdb) si
(lldb) thread step-inst
(lldb) si
Do an instruction-level single step over in the currently selected thread
在当前选定的线程中执行指令级单步操作
(gdb) nexti
(gdb) ni
(lldb) thread step-inst-over
(lldb) ni

Step out of the currently selected frame
继续执行当前的线程
(gdb) finish (lldb) thread step-out
(lldb) finish
Backtrace and disassemble every time you stop
每次停止时,显示反汇编和调用堆栈
- (lldb) target stop-hook add
Enter your stop hook command(s). Type ‘DONE’ to end.
> bt
> disassemble —pc
> DONE
Stop hook #1 added.

断点命令(Breakpoint Commands)


| GDB | LLDB | | :—- | :—- | | Set a breakpoint at all functions named main
在所有名为main的函数上设置断点 | | | (gdb) break main | (lldb) breakpoint set —name main(lldb) br s -n main(lldb) b main | | Set a breakpoint in file test.c at line 12
在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 | | Set a breakpoint at all C++ methods whose basename is main
在所有basename为main的c++方法上设置断点 | | | (gdb) break main(Note: This will break on any C functions named main.) | (lldb) breakpoint set —method main(lldb) br s -M main | | Set a breakpoint at an Objective-C function: -[NSString stringWithFormat:]
在Objective-C函数设置断点:-[NSString stringWithFormat:] | | | (gdb) break -[NSString stringWithFormat:] | (lldb) breakpoint set —name “-[NSString stringWithFormat:]”(lldb) b -[NSString stringWithFormat:] | | Set a breakpoint at all Objective-C methods whose selector is count
在所有选择器为count的Objective-C方法上设置断点 | | | (gdb) break count(Note: This will break on any C or C++ functions named count.) | (lldb) breakpoint set —selector count(lldb) br s -S count | | Set a breakpoint by a regular expression on a function name
通过正则表达式在函数名上设置断点 | | | (gdb) rbreak regular-expression | (lldb) breakpoint set —regex regular-expression(lldb) br s -r regular-expression | | Set a breakpoint by a regular expression on a source file’s contents
通过正则表达式在源文件的内容上设置断点 | | | (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 | | List all breakpoints
列出所有断点 | | | (gdb) info break | (lldb) breakpoint list(lldb) br l | | Delete a breakpoint
删除一个断点 | | | (gdb) delete 1 | (lldb) breakpoint delete 1(lldb) br del 1 |

观察点命令(Watchpoint Commands)

GDB LLDB
Set a watchpoint on a variable when it is written to
在变量上设置一个观察点当写入变量时触发
(gdb) watch global_var (lldb) watchpoint set variable global_var(lldb) wa s v global_var
Set a watchpoint on a memory location when it is written to
在内存位置上设置一个观察点当写入内存时触发
(gdb) watch -location g_char_ptr (lldb) watchpoint set expression — my_ptr(lldb) wa s e — my_ptr Note: The size of the region to watch for defaults to the pointer size if no -x byte_size is specified. This command takes “raw” input, evaluated as an expression returning an unsigned integer pointing to the start of the region, after the option terminator (—).
Set a condition on a watchpoint
在观察点上设置一个条件
(lldb) watch set var global(lldb) watchpoint modify -c ‘(global==5)’(lldb) c…(lldb) bt* thread #1: tid = 0x1c03, 0x0000000100000ef5 a.outmodify + 21 at main.cpp:16, stop reason = watchpoint 1frame #0: 0x0000000100000ef5 a.outmodify + 21 at main.cpp:16frame #1: 0x0000000100000eac a.outmain + 108 at main.cpp:25frame #2: 0x00007fff8ac9c7e1 libdyld.dylibstart + 1(int32_t) global = 5
List all watchpoints
列出所有监测点
(gdb) info break (lldb) watchpoint list(lldb) watch l
Delete a watchpoint
删除一个监视点
(gdb) delete 1 (lldb) watchpoint delete 1(lldb) watch del 1

求值表达式(Evaluating Expressions)

GDB LLDB
Evaluate a generalized expression in the current frame
在当前帧中求一个广义表达式的值
(gdb) print (int) printf (“Print nine: %d.”, 4 + 5) Or if you don’t want to see void returns:(gdb) call (int) printf (“Print nine: %d.”, 4 + 5) (lldb) expr (int) printf (“Print nine: %d.”, 4 + 5) Or use the print alias:(lldb) print (int) printf (“Print nine: %d.”, 4 + 5)
Create and assign a value to a convenience variable
创建一个便利变量并将其赋值
(gdb) set $foo = 5(gdb) set variable $foo = 5 Or use the print command:(gdb) print $foo = 5 Or use the call command:(gdb) call $foo = 5 To specify the type of the variable:(gdb) set $foo = (unsigned int) 5 LLDB evaluates a variable declaration expression as you would write it in C:(lldb) expr unsigned int $foo = 5
Print the Objective-C description of an object
打印一个对象的Objective-C描述
(gdb) po [SomeClass returnAnObject] (lldb) expr -O — [SomeClass returnAnObject] Or use the po alias:(lldb) po [SomeClass returnAnObject]
Print the dynamic type of the result of an expression
打印表达式结果的动态类型
(gdb) set print object 1(gdb) p someCPPObjectPtrOrReference Note: Only for C++ objects. (lldb) expr -d run-target — SomeClass returnAnObject expr -d run-target — someCPPObjectPtrOrReference Or set dynamic type printing as default:(lldb) settings set target.prefer-dynamic run-target
Call a function to stop at a breakpoint in the function
调用函数以在函数的断点处停止
(gdb) set unwindonsignal 0(gdb) p function_with_a_breakpoint() (lldb) expr -u 0 — function_with_a_breakpoint()

检查变量(Examining Variables)

GDB LLDB
Show the arguments and local variables for the current frame
显示当前帧的参数和局部变量
(gdb) info argsand(gdb) info locals (lldb) frame variable(lldb) fr v
Show the local variables for the current frame
显示当前帧的局部变量
(gdb) info locals (lldb) frame variable —no-args(lldb) fr v -a
Show the contents of the local variable bar
显示局部变量栏的内容
(gdb) p bar (lldb) frame variable bar(lldb) fr v bar(lldb) p bar
Show the contents of the local variable bar formatted as hex
显示十六进制格式的本地变量栏的内容
(gdb) p/x bar (lldb) frame variable —format x bar(lldb) fr v -f x bar
Show the contents of the global variable baz
显示全局变量baz的内容
(gdb) p baz (lldb) target variable baz(lldb) ta v baz
Show the global/static variables defined in the current source file
显示当前源文件中定义的全局/静态变量
(lldb) target variable(lldb) ta v
Display the variables argc and argv every time you stop
每次停止时显示变量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
Display the variables argc and argv only when you stop in the function named main
只有当你停止在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”
Display the variable this only when you stop in the C class named MyClass
只有当你停在名为MyClass的C类中时才显示变量
this
(lldb) target stop-hook add —classname MyClass —one-liner “frame variable this”(lldb) ta st a -c MyClass -o “fr v this”

检查当前线程状态(Examining Thread State:)

GDB LLDB
Show the stack backtrace for the current thread
显示当前线程的调用堆栈
(gdb) bt (lldb) thread backtrace(lldb) bt
Show the stack backtraces for all threads
显示所有线程的调用堆栈
(gdb) thread apply all bt (lldb) thread backtrace all(lldb) bt all
Backtrace the first five frames of the current thread
显示当前线程的前五帧调用堆栈
(gdb) bt 5 (lldb) thread backtrace -c 5(lldb) bt 5 (lldb-169 and later)(lldb) bt -c 5 (lldb-168 and earlier)
Select a different stack frame by index for the current thread
根据索引为当前线程选择不同的调用堆栈
(gdb) frame 12 (lldb) frame select 12(lldb) fr s 12(lldb) f 12
List information about the currently selected frame in the current thread
列出当前线程中当前选定框架的信息
(lldb) frame info
Select the stack frame that called the current stack frame
选择调用当前堆栈帧的堆栈帧
(gdb) up (lldb) up(lldb) frame select —relative=1
Select the stack frame that is called by the current stack frame
选择当前堆栈帧调用的堆栈帧
(gdb) down (lldb) down(lldb) frame select —relative=-1(lldb) fr s -r-1
Select a different stack frame using a relative offset
使用相对偏移量选择不同的堆栈帧
(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
Show the general-purpose registers for the current thread
显示当前线程的通用寄存器
(gdb) info registers (lldb) register read
Write a new decimal value 123 to the current thread register rax
向当前线程寄存器rax写入一个新的十进制值123
(gdb) p $rax = 123 (lldb) register write rax 123
Skip 8 bytes ahead of the current program counter (instruction pointer)
比当前程序计数器提前8个字节(指令指针)
(gdb) jump *$pc+8 (lldb) register write pc $pc+8 The LLDB command uses backticks to evaluate an expression and insert the scalar result.
Show the general-purpose registers for the current thread formatted as signed decimal
显示格式化为带符号十进制的当前线程的通用寄存器
(lldb) register read —format i(lldb) re r -f i LLDB now supports the GDB shorthand format syntax, but no space is permitted after the command:(lldb) register read/d Note: LLDB tries to use the same format characters as printf(3) when possible. Type help format to see the full list of format specifiers.
Show all registers in all register sets for the current thread
显示当前线程的所有寄存器集中的所有寄存器
(gdb) info all-registers (lldb) register read —all(lldb) re r -a
Show the values for the registers named rax, rsp and rbp in the current thread
显示当前线程中名为rax、rsp和rbp的寄存器的值
(gdb) info all-registers rax rsp rbp (lldb) register read rax rsp rbp
Show the values for the register named rax in the current thread formatted as binary
显示当前格式化为二进制的线程中名为rax的寄存器的值
(gdb) p/t $rax (lldb) register read —format binary rax(lldb) re r -f b rax LLDB now supports the GDB shorthand format syntax, but no space is permitted after the command:(lldb) register read/t rax(lldb) p/t $rax
Read memory from address 0xbffff3c0 and show four hex uint32_t values
从地址0xbffff3c0读取内存,并显示四个十六进制uint32_t值
(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 now supports the GDB shorthand format syntax, but no space is permitted after the command:(lldb) memory read/4xw 0xbffff3c0(lldb) x/4xw 0xbffff3c0(lldb) memory read —gdb-format 4xw 0xbffff3c0
Read memory starting at the expression argv[0]
从argv[0]表达式开始读取内存
(gdb) x argv[0] (lldb) memory read argv[0] Note that any command can inline a scalar expression result (as long as the target is stopped) using back ticks (`) around any expression:(lldb) memory read --sizesizeof(int)`argv[0]
Read 512 bytes of memory from address 0xbffff3c0 and save results to a local file as text
从地址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
Save binary memory data to a file starting at 0x1000 and ending at 0x2000
将二进制内存数据保存到一个从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
Disassemble the current function for the current frame
反汇编当前帧的当前函数
(gdb) disassemble (lldb) disassemble —frame(lldb) di -f
Disassemble any functions named main
反汇编名为main的函数
(gdb) disassemble main (lldb) disassemble —name main(lldb) di -n main
Disassemble an address range
反汇编地址范围
(gdb) disassemble 0x1eb8 0x1ec3 (lldb) disassemble —start-address 0x1eb8 —end-address 0x1ec3(lldb) di -s 0x1eb8 -e 0x1ec3
Disassemble 20 instructions from a given address
从一个给定地址拆下20条指令
(gdb) x/20i 0x1eb8 (lldb) disassemble —start-address 0x1eb8 —count 20(lldb) di -s 0x1eb8 -c 20
Show mixed source and disassembly for the current function for the current frame
显示混合源和当前帧的当前功能的反汇编
(lldb) disassemble —frame —mixed(lldb) di -f -m
Disassemble the current function for the current frame and show the opcode bytes
反汇编当前帧的当前函数,并显示操作码字节
(lldb) disassemble —frame —bytes(lldb) di -f -b
Disassemble the current source line for the current frame
反汇编当前帧的当前信号线
(lldb) disassemble —line(lldb) di -l

可执行和共享库查询命令(Executable and Shared Library Query Commands)


| GDB | LLDB | | :—- | :—- | | List the main executable and all dependent shared libraries
列出主要可执行文件和所有依赖的共享库 | | | (gdb) info shared | (lldb) image list | | Look up information for a raw address in the executable or any shared libraries
在可执行文件或任何共享库中查找原始地址的信息 | | | (gdb) info symbol 0x1ec4 | (lldb) image lookup —address 0x1ec4(lldb) im loo -a 0x1ec4 | | Look up functions matching a regular expression in a binary
在二进制文件中查找匹配正则表达式的函数 | | | (gdb) info function | This one finds debug symbols:(lldb) image lookup -r -n This one finds non-debug symbols:(lldb) image lookup -r -s Provide a list of binaries as arguments to limit the search. | | Look up information for an address in a.out only
只在a.out中查找地址信息 | | | — | (lldb) image lookup —address 0x1ec4 a.out(lldb) im loo -a 0x1ec4 a.out | | Look up information for a type Point by name
按名称查找类型点的信息 | | | (gdb) ptype Point | (lldb) image lookup —type Point(lldb) im loo -t Point | | Dump all sections from the main executable and any shared libraries
从主可执行文件和所有共享库中转储所有节 | | | (gdb) maintenance info sections | (lldb) image dump sections | | Dump all sections in the a.out module
转储a.out模块中的所有节 | | | — | (lldb) image dump sections a.out | | Dump all symbols from the main executable and any shared libraries
转储主可执行文件和所有共享库中的所有符号 | | | — | (lldb) image dump symtab | | Dump all symbols in a.out and liba.so
Dump所有符号在a.out和liba.so | | | — | (lldb) image dump symtab a.out liba.so |

杂项(Miscellaneous)

GDB LLDB
Echo text to the screen
将文本回显到屏幕
(gdb) echo Here is some text\n (lldb) script print “Here is some text”
Remap source file pathnames for the debug session
重新映射调试会话的源文件路径名
(gdb) set pathname-substitutions /buildbot/path /my/path (lldb) settings set target.source-map /buildbot/path /my/path Note: If your source files are no longer located in the same location as when the program was built—maybe the program was built on a different computer—you need to tell the debugger how to find the sources at the local file path instead of the build system file path.
Supply a catchall directory to search for source files in
提供一个用于搜索源文件的通用目录
(gdb) directory /my/path (No equivalent command.)