http://www.infoq.com/cn/news/2017/10/LLDB-debug-Swift?utm_source=tuicool&utm_medium=referral


作为开发者,我们工作70%的时间都用于调试。20%用于架构设计和团队沟通,仅有10%的时间用于写代码。

调试好比犯罪电影中同时扮演罪犯和侦探的角色。
— Filipe Fortes via Twitter

因此如何让这70%的时间变得高效显得尤为重要。LLDB是个很好的选择。尽管Xcode拥有功能完善的调试面板,控制台仍然是调试中重要的组成部分。接下来我会探讨一些工作中常用到的一些LLDB调试技巧。

从哪开始?

LLDB拥有大量有用的调试工具。我会选一些重要的命令讨论:

  1. 获取变量值:expressioneprintpop
  2. 获取执行环境+特定语言命令:bugreportframelanguage
  3. 执行流程控制:processbreakpointthreadwatchpoint
  4. 其他:commandplatformgui

下面列出了LLDB常用命令的描述和例子。你可以保存该图片方便以后查阅。

1.获取变量值和状态

命令:expressioneprintpop

调试工具最基本的功能是打印和修改变量的值。expressione就是这样的工具。你可以在运行时执行几乎任何表达式或命令。

假设你正在调试valueOfLifeWithoutSumOf()方法,该方法用于两数相加并与42相减。

现在运行得到了错误的结果。你可以像下图这样修改代码尝试定位问题:

更好的方式是使用expression命令在运行时修改变量的值。设置断点并运行。

按照LLDB格式打印变量的值:

(lldb) e <variable>

完全相似的方式执行表达式:

(lldb) e <expression>
(lldb) e sum 
(Int) $R0 = 6 // 当前调试会话中可以使用$R0代替该变量
(lldb) e sum = 4 // 修改sum变量的值
(lldb) e sum 
(Int) $R2 = 4 // 调试期间sum值变为4

expression命令有一些选项。LLDB使用双破折号--分隔选项和表达式:

(lldb) expression <some flags> -- <variable>

expression拥有大约30个选项。这些选择都值得你来探索。终端输入以下命令可以获取详细的文档:

> lldb
> (lldb) help # To explore all available commands
> (lldb) help expression # To explore all expressions related sub-commands

下面列出了几个比较常用的选项:

  • -D <count>(--depth <count>) - 设置打印聚合类型的递归深度(默认无限递归)。
  • -O(--object-desctiption) - 打印description方法。
  • -T(--show-types) - 显示每个变量的类型。
  • -f <format>(--format<format>) - 设置输出格式。
  • -i <boolean>(--ignore-breakpoints <boolean>) - 运行表达式时忽略表达式内的断点。

假如现在有个logger对象。该对象拥有一些字符串和结构体作为属性。如果只想打印层级1的属性,可使用-D选项:

(lldb) e -D 1 -- logger
(LLDB_Debugger_Exploration.Logger) $R5 = 0x0000608000087e90 {
  currentClassName = "ViewController"
  debuggerStruct ={...}
}

LLDB默认会递归打印对象的所有属性,展示非常详尽的内容:

(lldb) e -- logger
(LLDB_Debugger_Exploration.Logger) $R6 = 0x0000608000087e90 {
  currentClassName = "ViewController"
  debuggerStruct = (methodName = "name",lineNumber = 2,commandCounter = 23)
}

也可以像如下使用e -O --或者别名po来打印:

(lldb) po logger
<Logger: 0x608000087e90>

这样直接打印可读性不高。通过实现customstringconvertible协议中的var description: String { return ...}属性,po会返回可读性更好的描述。

在这段开头也提到了print命令。除了print命令没有可用选项无需传递参数外,print <expression/variable>expression --<expression/variable>几乎一样。

2.获取执行环境+特定语言命令

bugreportframelanguage

你是否经常需要拷贝粘贴日志到任务管理器中来定位问题。LLDB的bugreport命令可以生成一份详细的app当前状态的报告。该命令对于想要延迟追踪定位问题非常有用。为了保存app的状态,你可以使用bugreport来生成报告。

(lldb) bugreport unwind --outfile <path to output file>

生成的报告如下:

!Example of bugreport command output

frame命令可以打印出当前线程的栈帧:

使用如下命令来快速了解你在哪和当前执行环境:

(lldb) frame info
frame #0: 0x000000010bbe4b4d LLDB-Debugger-Exploration`ViewController.valueOfLifeWithoutSumOf(a=2,b=2,self=0x00007fa0c1406900) -> Int at ViewController.swift:96

该信息对文章后面提到的断点管理非常有用。

LLDB有一些针对特定语言的命令。这些命令有针对C++,Objective-C,Swift和RenderScript的。这篇文章中只讨论针对swift的两个命令:demanglerefcount

demangle命令正如其名字描述的一样用于修复损坏的Swift类型名(编译期间为避免命名空间问题而产生)。想要了解更多可以观看WWDC14 session -"Advanced Swift Debugging in LLDB"

refcount命令的命名也非常直观,可用于显示对象的引用计数。让我们回到前面讨论过的用于输出的对象 -logger

(lldb) language swift refcount logger
refcount data: (strong = 4,weak = 0)

这对于调试查找内存泄露问题是很有帮助的。

3.执行流程控制

processbreakpointthread

这是我最喜欢的部分。使用LLDB的这些命令(尤其是breakpoint)可以让调试变得更自动化。有助于提升调试速度。

process用于控制要调试的进程,可把LLDB依附到特定target或从target上解绑。运行target时Xcode已经为我们做好了将LLDB依附到该进程的工作,所以这里不再讨论LLDB的依附。可通过阅读苹果手册-"Using LLDB as a Standalone Debugger"来了解如何依附LLDB到进程中。

process status用于打印当前进程及断点处的相关信息:

(lldb) process status
Process 27408 stopped
* thread #1,queue = 'com.apple.main-thread',stop reason = step over
frame #0: 0x000000010bbe4889 LLDB-Debugger-Exploration`ViewController.viewDidLoad(self=0x00007fa0c1406900) -> () at ViewController.swift:69
66
67           let a = 2,b = 2
68           let result = valueOfLifeWithoutSumOf(a,and: b)
-> 69           print(result)
70
71
72

使用如下命令可继续执行:

(lldb) process continue
(lldb) c // 等同于上面命令

这等同于Xcode调试面板中 "continue" 按钮:

breakpoint用于断点的各种操作。这里不会讨论这些太常见的命令:breakpoint enablebreakpoint disable,和breakpoint delete

使用list子命令可以打印出所有断点:

(lldb) breakpoint list
Current breakpoints:
1: file = '/Users/Ahmed/Desktop/Recent/LLDB-Debugger-Exploration/LLDB-Debugger-Exploration/ViewController.swift',line = 95,exact_match = 0,locations = 1,resolved = 1,hit count = 1
1.1: where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.valueOfLifeWithoutSumOf (Swift.Int,and : Swift.Int) -> Swift.Int + 27 at ViewController.swift:95,address = 0x0000000107f3eb3b,resolved,hit count = 1
2: file = '/Users/Ahmed/Desktop/Recent/LLDB-Debugger-Exploration/LLDB-Debugger-Exploration/ViewController.swift',line = 60,hit count = 1
2.1: where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.viewDidLoad () -> () + 521 at ViewController.swift:60,address = 0x0000000107f3e609,hit count = 1

列表中的第一个数字是断点ID,可用于引用对应的断点。下面从控制台设置一个新的断点:

(lldb) breakpoint set -f ViewController.swift -l 96
Breakpoint 3: where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.valueOfLifeWithoutSumOf (Swift.Int,and : Swift.Int) -> Swift.Int + 45 at ViewController.swift:96,address = 0x0000000107f3eb4d

上面例子中-f选项用于指明断点所在的文件。-l选项用于指明断点所在的行数。对于上面例子有个更简洁的表达:

(lldb) b ViewController.swift:96

也可以使用以命令对某个方法设置断点:

(lldb) breakpoint set --one-shot -f ViewController.swift -l 90
(lldb) br s -o -f ViewController.swift -l 91 // 上面命令的精简版

有时候需要断点仅命中一次。命中后随即删除该断点。通过如下命令行可以实现:

 下面进入最有趣的部分-断点自动化。你知道可以设置特定动作用于断点发生时执行吗?调试时你是否喜欢在代码中使用print()来打印你感兴趣的值?下面有个更好的方法。 

通过breakpoint command可以设置命中断点后执行的命令。甚至可以设置不会打断执行的"透明"断点。从技术上来说"透明"断点已然会打断执行,但通过在命令链中添加continue命令可以变得无感知。

(lldb) b ViewController.swift:96 // 设置断点
Breakpoint 2: where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.valueOfLifeWithoutSumOf (Swift.Int,address = 0x000000010c555b4d
(lldb) breakpoint command add 2 // Setup some commands 
Enter your debugger command(s).  Type 'DONE' to end.
> p sum // Print value of "sum" variable
> p a + b // Evaluate a + b
> DONE

通过breakpoint command list <breakpoint id>命令可以检查一遍命令是否都正确:

(lldb) breakpoint command list 2
Breakpoint 2:
Breakpoint commands:
p sum
p a + b

当断点命中时控制台会有如下输出:

Process 36612 resuming
p sum
(Int) $R0 = 6
p a + b
(Int) $R1 = 4

这正是我们所期望的。甚至可以在命令链的后面添加continue来防止执行被打断。

(lldb) breakpoint command add 2 // Setup some commands
Enter your debugger command(s).  Type 'DONE' to end.
> p sum // Print value of "sum" variable
> p a + b // Evaluate a + b
> continue // Resume right after first hit
> DONE

输出结果:

p sum
(Int) $R0 = 6
p a + b
(Int) $R1 = 4
continue
Process 36863 resuming
Command #3 'continue' continued the target.

通过thread和其子命令可以完全的控制执行流程:step-overstep-instep-outcontinue。这些命令等同于Xcode调试面板里用于控制执行流程的按钮。

对于这些命令也有预定义的快捷命令:

(lldb) thread step-over
(lldb) next // 等同于"thread step-over"
(lldb) n // 等同于"next"
(lldb) thread step-in
(lldb) step // 等同于"thread step-in"
(lldb) s // 等同于"step"

通过info子命令可获取关于当前线程的更多信息:

(lldb) thread info 
thread #1: tid = 0x17de17,0x0000000109429a90 LLDB-Debugger-Exploration`ViewController.sumOf(a=2,self=0x00007fe775507390) -> Int at ViewController.swift:90,stop reason = step i

list子命令用于显示当前所有活跃线程:

(lldb) thread list
Process 50693 stopped
* thread #1: tid = 0x17de17,stop reason = step in
  thread #2: tid = 0x17df4a,0x000000010daa4dc6  libsystem_kernel.dylib`kevent_qos + 10,queue = 'com.apple.libdispatch-manager'
  thread #3: tid = 0x17df4b,0x000000010daa444e libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #5: tid = 0x17df4e,0x000000010da9c34a libsystem_kernel.dylib`mach_msg_trap + 10,name = 'com.apple.uikit.eventfetch-thread'

其他

commandplatformgui

LLDB中有一个用于管理其他命令的命令。听起来很奇怪,但非常有用。首先,该命令允许你从文件执行LLDB命令。因此你可以创建一个包含大量有用命令的脚本当做一个命令来执行。下面是一个包含两条命令的文件:

thread info // 显示当前线程信息
br list // 显示所有断点

真正命令执行如下:

(lldb) command source /Users/Ahmed/Desktop/lldb-test-script
Executing commands in '/Users/Ahmed/Desktop/lldb-test-script'.
thread info
thread #1: tid = 0x17de17,stop reason = step in
br list
Current breakpoints:
1: file = '/Users/Ahmed/Desktop/Recent/LLDB-Debugger-Exploration/LLDB-Debugger-Exploration/ViewController.swift',hit count = 0
1.1: where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.viewDidLoad () -> () + 521 at ViewController.swift:60,address = 0x0000000109429609,hit count = 0

不幸的是,无法在执行脚本文件时传递参数(除非在脚本文件中创建一个可用变量)。

script子命令提供了一些高级用法,可用于管理(adddeleteimportlist)自定义的Python脚本。script是的命令自动化变成了可能。更详尽的信息请查阅Python scripting for LLDB指南。示例中,我们创建了一个功能非常简单的script.py脚本,该脚本仅包含了print_hello()命令用于打印"Hello Debug个!"到控制台:

import lldb
def print_hello(debugger,command,result,internal_dict):
    print "Hello Debugger!"
def __lldb_init_module(debugger,internal_dict):
    debugger.HandleCommand('command script add -f script.print_hello print_hello') // Handle script initialization and add command from this module
    print 'The "print_hello" python command has been installed and is ready for use.' // Print confirmation that everything works

接下来需要导入该Python脚本,导入后可直接使用脚本命令:

(lldb) command import ~/Desktop/script.py
The "print_hello" python command has been installed and is ready for use.
(lldb) print_hello
Hello Debugger!

status子命令可用于快速查看当前平台信息。status会打印出:SDK路径,处理器架构,操作系统版本和该SDK支持的可用设备列表。

(lldb) platform status
Platform: ios-simulator
Triple: x86_64-apple-macosx
OS Version: 10.12.5 (16F73)
Kernel: Darwin Kernel Version 16.6.0: Fri Apr 14 16:21:16 PDT 2017; root:xnu-3789.60.24~6/RELEASE_X86_64
Hostname: 127.0.0.1
WorkingDir: /
SDK Path: "/Applications/Xcode.app/Contents/Developer/Platforms/iPhonesimulator.platform/Developer/SDKs/iPhonesimulator.sdk"
Available devices:
614F8701-3D93-4B43-AE86-46A42FEB905A: iPhone 4s
CD516CF7-2AE7-4127-92DF-F536FE56BA22: iPhone 5
0D76F30F-2332-4E0C-9F00-B86F009D59A3: iPhone 5s
3084003F-7626-462A-825B-193E6E5B9AA7: iPhone 6
...

Xcode中无法使用LLDB的可视化模式,但可以在终端中使用。

(lldb) gui // 在Xcode中执行gui命令会报错:该命令只能在无交互的终端执行

!This is how LLDB GUI mode looks like

结论

这篇文章大致描绘出了LLDB的强大。尽管LLDB存在于我们的开发环境中,但大多数人并没有发掘出它的能量。希望这篇关于LLDB基本功能和自动化调试的概览能对大家有所帮助。

文章遗漏了LLDB的很多功能。像可视调试技巧就没有提到。如果你对这样的主题感兴趣,请在下面的评论中留言。能写一些大家感兴趣的东西很高兴。

鼓励你打开终端,开启LLLDB,输入help。很详细的文档会呈现出来。尽管这是一个很耗时的过程,但仍然期望你能花时间去阅读这个文档。只有精通你的生产工具才能变得真正的高效。

参考

  • Official LLDB site— you’ll find here all possible materials related to LLDB. Documentation,guides,tutorials,sources and much more.
  • LLDB Quick Start Guide by Apple— as usual,Apple has a great documentation. This guide will help you to get started with LLDB really quickly. Also,they’ve described how to do debugging with LLDB without Xcode
  • How debuggers work: Part 1 — Basics— I enjoyed this series of articles a lot. It’s Just fantastic overview how debuggers really work. Article describes all underlying principles using code of hand-made debugger written in C. I strongly encourage you to read all parts of these great series (Part 2,Part 3).
  • WWDC14 Advanced Swift Debugging in LLDB— great overview what’s new in LLDB in terms of Swift debugging. And how LLDB helps you be more productive with an overall debugging process using built-in functions and features.
  • Introduction To LLDB Python Scripting— the guide on Python scripting for LLDB which allows you to start really quickly.
  • Dancing in the Debugger. A Waltz with LLDB— a cLever introduction to some LLDB basics. Some information is a bit outdated (like (lldb) thread return command,for example. Unfortunately,it doesn't work with Swift properly because it can potentially bring some damage to reference counting). Still,it’s a great article to start your LLDB journey.

使用LLDB调试Swift的更多相关文章

  1. iOS:调试无法在XCode中运行

    我正在使用XCode4和iOSSDK4.3.我的调试工作正常,但现在我发现在设置断点时,应用暂停,但XCode不关注编辑器中的行.点击断点时也不会显示绿色箭头.我有一个在AppDelegate中分配的UINavigationController.当我在didFinishLaunchingWithOptions中设置断点时,一切都按预期工作:这是当一个断点暂停了mapViewController中的

  2. ios – 异常断点处于活动状态时,应用程序在启动时崩溃

    我刚开始继续开发一款适用于商店的传统iPad应用程序.我注意到项目中的异常断点未启用.当我启用它时,应用程序在启动时崩溃,但在输出窗口中没有给出任何信息,而在线程视图中只有相当无用的信息(见下文)我试着解决它..>将Autolayout设置为关闭.>通过编辑和重新保存故事板文件..但到目前为止没有运气.我的猜测是,故事板中的某些内容被破坏了,因为AppDelegates“确实完成了启动……”

  3. xcode – 如何通过LLDB命令行添加断点操作?

    如果你从Xcode编辑一个断点,有一个超级有用的选项,可以添加一个“Action”,以便在每次遇到断点时自动执行.如何从LLDB命令行添加此类操作?

  4. xcode – 如何在LLDB断点条件下使用堆栈内容?

    问题:我有一种情况,我们在发布期间有媒体播放,并且objc_exception_throw()在此期间大约有5次点击,但总是被捕获,并且它在媒体播放器对象的南边.我厌倦了(a)必须手动连续n次,或者(b)在播放完成之前必须禁用断点.我尝试过的:>使断点忽略前五次命中(问题:它并不总是正好五次)>使用我的目标作为模块创建我自己的符号断点(问题:没有改变)我想做什么:想到的一个解决方案是在断点命中时评

  5. 如何在iOS / Swift的顶部导航栏中添加“继续”按钮

    我想在导航栏的右侧添加一个“继续”按钮.如何实现这一目标?我一直在尝试使用UIBarButtonItem上的一些方法,但无法使其正常工作.我迄今为止的最大努力是:但我在第一行遇到错误.它不喜欢“style”参数.我也试过了但没有运气.仍然停留在样式参数上.有任何想法吗?

  6. ios – 无法识别的选择器发送到实例NSTimer Swift

    解决方法让updateTime成为一个类方法.如果它是在一个纯粹的Swift类中,你需要在@objc前面说明该方法的声明,如:

  7. Xcode 4.3.1:在以下情况下不显示代码行:由于未捕获的异常而终止应用程序

    在Xcode的早期版本中,当我遇到崩溃时,Xcode通常会打开发生崩溃的文件,并经常将我带到崩溃的行.最新的Xcode似乎没有做那些让调试变得更难的东西.我是否需要启用一些新设置?

  8. ios – XCode断点应该只挂起当前线程

    我需要调试多线程错误.因此,为了获得生成崩溃的条件,我需要在代码中的特定点停止一个线程,并等待另一个线程到达第二个断点.我现在遇到的问题是,如果一个线程遇到断点,则所有其他线程都被挂起.有没有办法只停止一个线程,让其他线程运行,直到它们到达第二个断点?)其他更有趣的选择:当你点击第一个断点时,你可以进入控制台并写入这应该在该断点处暂停当前上下文中的线程一小时.然后在Xcode中恢复执行.

  9. 在Xcode中的选择器上添加符号断点

    我的应用程序中有一个错误,显示以下(部分)堆栈跟踪:为了调试这个,我决定在–[EventboolValue]上添加一个符号断点,推断当发送该选择器时,调试器将停止.然而,没有任何反应.设置断点后,应用程序只是士兵并生成相同的异常而不停止.我已经定义了断点如下:我正在使用LLDB调试器和Xcode4.2解决方法在选择器上设置断点会导致lldb在执行该选择器时停止,而不是在发送时停止.在你的情况下,没

  10. ios – 如何在Xcode 6中的异常断点上打印异常?

    我的应用程序正在崩溃,它似乎陷入了异常断点(这是有道理的),但我无法找到崩溃的原因.这是我尝试过的:>po$eax>po$rax>po$r0>po*(id*)($esp4)对于上述所有尝试,我收到以下错误:错误:使用未声明的标识符’$‘错误:解析表达式时出错1次我也找到了这个LLDBCommandGuide,但没有找到任何有用的东西(有点令人困惑,你不知道你在寻找什么)

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部