LLDB 详解
- LLDB 详解
- 编译器集成优势
- LLDB 的主要功能
- 命令格式
- 原始(raw)命令
- 选项终止符: --
- LLDB 中的变量
- 唯一匹配原则
- help
- expression
- print、call、po
- 控制流程:continue、next、step、finish
- register read / write
- thread backtrace
- thread return
- frame 相关命令
- breakpoint
- 设置断点
- 管理断点
- 断点命令
- watchpoint
LLDB 详解
LLDB,全称为Low-Level Debugger,是苹果公司开发的一款开源、高性能的源代码级调试器。作为Clang和LLVM项目的一部分,LLDB被设计为C、C++、Objective-C和Swift等编程语言的原生调试器。它提供了丰富的功能和灵活的接口,使得开发者能够有效地定位和修复代码中的错误。
编译器集成优势
LLDB将调试信息转换为Clang类型,以便利用Clang编译器基础设施。这允许LLDB在表达式中支持最新的C、C++、Objective-C语言功能和运行时,而无需重新实现任何此功能。它还利用编译器在使函数调用表达式、解码指令和提取指令详细信息等时处理所有ABI细节。
LLDB的主要优点有:
- C、C++、Objective-C的最新语言支持。
- 可以声明局部变量和类型的多行表达式。
- 当支持时,使用JIT作为表达式。
- 当无法使用JIT时,评估表达式中间表示(IR)。
LLDB 的主要功能
- 断点设置:在特定的代码行或函数处设置断点,控制程序的执行流程。
- 变量查看与修改:在运行时查看和修改变量的值,包括基本类型、结构体、类和数组等。
- 内存查看:检查和修改内存内容,帮助理解程序的数据布局。
- 线程和进程控制:管理线程和进程的执行状态,如暂停、继续、切换线程和终止进程。
- 表达式计算:在调试环境中计算复杂的表达式,支持多种编程语言的语法。
- 堆栈回溯:查看函数调用栈,追踪代码执行路径。
- 插件支持:通过插件系统扩展LLDB的功能,以满足特定的调试需求。
- 脚本编写:使用Python或Swift编写自定义调试脚本,自动化调试过程。
命令格式
<noun> <verb> [-option [option-value]] [argument [argument...]]
// [] 项为可选项
<命令> <子命令> [-选项 [选项值]] [参数 [参数值...]]
原始(raw)命令
LLDB支持不带命令选项(options)的原始(raw)命令,原始命令会将命令后面的所有东西当做参数(arguement)传递。不过很多原始命令也可以带命令选项,当你使用命令选项的时候,需要区分命令选项和参数。
常用的 expression 就是 raw 命令,一般情况下我们使用 expression 打印一个东西是这样的:
选项终止符: –
「选项」可以放在命令行的任何位置,但是如果参数以“-”开头,则必须通过添加选项终止符--
来告诉 LLDB 你已经完成了当前命令的选项。
// 选项: -stop-at-entry, 参数:-program_arg
process launch --stop-at-entry -- -program_arg value
当我们想用 expression 打印一个对象的时候。需要使用 -O 命令选项,我们应该用 – 将命令选项和参数区分:
该命令可以简写为 po。
LLDB 中的变量
在 LLDB 中定义变量时,需要在变量前面添加$
符号。定义好变量后就可以像平常一样使用变量,调用方法。方法调用时,LLDB 无法确定返回值的类型,需要自己指定。
// 定义变量array
(lldb) e NSArray *$array = @[@"I", @"love", @"LLDB"](lldb) po $array
<__NSArrayI 0x600000ffdb00>(
I,
love,
LLDB
)// 调用方法
(lldb) po [$array count]
3
(lldb) po [[$array objectAtIndex:1] uppercaseString]
LOVE(lldb) e int $b = 0
(lldb) po $b
0// 不指定返回值
(lldb) po [[$array objectAtIndex:$b] characterAtIndex:0]
0x0000000000000049// 指定返回值
(lldb) po (char)[[$array objectAtIndex:$b] characterAtIndex:0]
'I'
唯一匹配原则
LLDB的命令遵循唯一匹配原则:假如根据前n个字母已经能唯一匹配到某个命令,则只写前n个字母等效于写下完整的命令。利用该规则,可以简写一些命令。
help
好记性不如 help
命令。和平常的 pod --help
ruby --help
不一样,LLDB help 本身是一个命令而不是一个选项,它可以告诉你关于某个命令(包括 help 命令)的一切信息。
(lldb) help
Debugger commands:apropos -- List debugger commands related to a word or subject.breakpoint -- Commands for operating on breakpoints (see 'help b' forshorthand.)command -- Commands for managing custom LLDB commands.disassemble -- Disassemble specified instructions in the currenttarget. Defaults to the current function for thecurrent thread and stack frame.dwim-print -- Print a variable or expression.expression -- Evaluate an expression on the current thread. Displaysany returned value with LLDB's default formatting.frame -- Commands for selecting and examing the current thread'sstack frames.gdb-remote -- Connect to a process via remote GDB server.If no host is specifed, localhost is assumed.gdb-remote is an abbreviation for 'process connect--plugin gdb-remote connect://<hostname>:<port>'gui -- Switch into the curses based GUI mode.help -- Show a list of all debugger commands, or give detailsabout a specific command.kdp-remote -- Connect to a process via remote KDP server.If no UDP port is specified, port 41139 isassumed.kdp-remote is an abbreviation for 'process connect--plugin kdp-remote udp://<hostname>:<port>'language -- Commands specific to a source language.log -- Commands controlling LLDB internal logging.memory -- Commands for operating on memory in the current targetprocess.platform -- Commands to manage and create platforms.plugin -- Commands for managing LLDB plugins.process -- Commands for interacting with processes on the currentplatform.quit -- Quit the LLDB debugger.register -- Commands to access registers for the current thread andstack frame.script -- Invoke the script interpreter with provided code anddisplay any results. Start the interactive interpreterif no code is supplied.session -- Commands controlling LLDB session.settings -- Commands for managing LLDB settings.source -- Commands for examining source code described by debuginformation for the current target process.statistics -- Print statistics about a debugging sessionswift-healthcheck -- Provides logging related to the Swift expressionevaluator, including Swift compiler diagnostics. Thismakes it easier to identify project misconfigurationsthat result in module import failures in the debugger.The command is meant to be run after a expressionevaluator failure has occurred.target -- Commands for operating on debugger targets.thread -- Commands for operating on one or more threads in thecurrent process.trace -- Commands for loading and using processor traceinformation.type -- Commands for operating on the type system.version -- Show the LLDB debugger version.watchpoint -- Commands for operating on watchpoints.
Current command abbreviations (type 'help command alias' for more info):add-dsym -- Add a debug symbol file to one of the target's current modulesby specifying a path to a debug symbols file or by using theoptions to specify a module.attach -- Attach to process by ID or name.b -- Set a breakpoint using one of several shorthand formats.bt -- Show the current thread's call stack. Any numeric argumentdisplays at most that many frames. The argument 'all' displaysall threads.c -- Continue execution of all threads in the current process.call -- Evaluate an expression on the current thread. Displays anyreturned value with LLDB's default formatting.continue -- Continue execution of all threads in the current process.detach -- Detach from the current target process.di -- Disassemble specified instructions in the current target. Defaults to the current function for the current thread andstack frame.dis -- Disassemble specified instructions in the current target. Defaults to the current function for the current thread andstack frame.display -- Evaluate an expression at every stop (see 'help targetstop-hook'.)down -- Select a newer stack frame. Defaults to moving one frame, anumeric argument can specify an arbitrary number.env -- Shorthand for viewing and setting environment variables.exit -- Quit the LLDB debugger.f -- Select the current stack frame by index from within the currentthread (see 'thread backtrace'.)file -- Create a target using the argument as the main executable.finish -- Finish executing the current stack frame and stop afterreturning. Defaults to current thread unless specified.history -- Dump the history of commands in this session.Commands in the history list can be run again using "!<INDEX>". "!-<OFFSET>" will re-run the command that is <OFFSET> commandsfrom the end of the list (counting the current command).image -- Commands for accessing information for one or more targetmodules.j -- Set the program counter to a new address.jump -- Set the program counter to a new address.kill -- Terminate the current target process.l -- List relevant source code using one of several shorthand formats.list -- List relevant source code using one of several shorthand formats.n -- Source level single step, stepping over calls. Defaults tocurrent thread unless specified.next -- Source level single step, stepping over calls. Defaults tocurrent thread unless specified.nexti -- Instruction level single step, stepping over calls. Defaults tocurrent thread unless specified.ni -- Instruction level single step, stepping over calls. Defaults tocurrent thread unless specified.p -- Print a variable or expression.parray -- parray <COUNT> <EXPRESSION> -- lldb will evaluate EXPRESSION toget a typed-pointer-to-an-array in memory, and will displayCOUNT elements of that type from the array.po -- Evaluate an expression on the current thread. Displays anyreturned value with formatting controlled by the type's author.poarray -- poarray <COUNT> <EXPRESSION> -- lldb will evaluate EXPRESSION toget the address of an array of COUNT objects in memory, and willcall po on them.print -- Print a variable or expression.q -- Quit the LLDB debugger.r -- Launch the executable in the debugger.rbreak -- Sets a breakpoint or set of breakpoints in the executable.re -- Commands to access registers for the current thread and stackframe.repl -- Evaluate an expression on the current thread. Displays anyreturned value with LLDB's default formatting.run -- Launch the executable in the debugger.s -- Source level single step, stepping into calls. Defaults tocurrent thread unless specified.shell -- Run a shell command on the host.si -- Instruction level single step, stepping into calls. Defaults tocurrent thread unless specified.sif -- Step through the current block, stopping if you step directlyinto a function whose name matches the TargetFunctionName.step -- Source level single step, stepping into calls. Defaults tocurrent thread unless specified.stepi -- Instruction level single step, stepping into calls. Defaults tocurrent thread unless specified.t -- Change the currently selected thread.tbreak -- Set a one-shot breakpoint using one of several shorthand formats.undisplay -- Stop displaying expression at every stop (specified by stop-hookindex.)up -- Select an older stack frame. Defaults to moving one frame, anumeric argument can specify an arbitrary number.v -- Show variables for the current stack frame. Defaults to allarguments and local variables in scope. Names of argument,local, file static and file global variables can be specified.var -- Show variables for the current stack frame. Defaults to allarguments and local variables in scope. Names of argument,local, file static and file global variables can be specified.vo -- Show variables for the current stack frame. Defaults to allarguments and local variables in scope. Names of argument,local, file static and file global variables can be specified.x -- Read from the memory of the current target process.
For more information on any command, type 'help <command-name>'.
expression
expression 命令的作用是执行一个表达式,并将表达式返回的结果输出。
- 执行某个表达式。 我们在代码运行过程中,可以通过执行某个表达式来动态改变程序运行的轨迹。 假如我们在运行过程中,突然想把 self.view 颜色改成红色,看看效果。我们不必写下代码,重新 run,只需暂停程序,用 expression 改变颜色,再刷新一下界面,就能看到效果。
(lldb) expression -- self.view.backgroundColor = [UIColor purpleColor];
(lldb) expression -- (void)[CAtransaction flush]
- 将返回值输出。 也就是说我们可以通过 expression 来打印东西。 假如我们想打印 self.view:
(lldb) expression -- self.view
print、call、po
一般情况下,我们直接用 expression 还是用得比较少的,更多时候我们用的是 print、call、po。这三个命令其实都是 expression 命令,执行某个表达式,并将执行的结果输出到控制台上。常用来动态调用方法,动态修改变量。
// 调用 description 进行打印
'call' is an abbreviation for 'expression --'
'print' is an abbreviation for 'expression --'
下面代码效果相同:
根据唯一匹配原则,如果你没有自己添加特殊的命令别名。e 也可以表示 expression 的意思,p 和 print 等价。
上面打印的都是指针,而不是对象本身。如果我们想打印对象,需要使用到命令选项:-O。为了更方便的使用,LLDB 为 expression -O 定义了一个别名:po。
// 调用 description 进行打印
'po' is an abbreviation for 'expression -O --'
使用 po 指令动态修改变量:
// 背景颜色为 red
(lldb) po [self.view backgroundColor]
UIExtendedSRGBColorSpace 1 0 0 1
// 修改背景颜色为blue
(lldb) po [self.view setBackgroundColor:[UIColor blueColor]]
UICachedDeviceRGBColor
// 背景颜色发生改变
(lldb) po [self.view backgroundColor]
UIExtendedSRGBColorSpace 0 0 1 1
// 不用继续运行程序,马上进行渲染,模拟器/真机上也发生改变
(lldb) po [CATransaction flush]
控制流程:continue、next、step、finish
上图为 Xcode 里面的控制按键,其实都是与 LLDB 命令对应的,从左至右分别为:
- continue 按钮, 对应命令
process continue
(简写continue
、c
),会取消程序的暂停,允许程序正常执行。 - step over 按钮,对应命令
thread step-over
(简写next
、n
),会以黑盒的方式执行一行代码。如果所在这行代码是一个函数调用,不会跳进这个函数,而是会执行这个函数,然后继续。 - step in 按钮,对应命令
thread step-in
(简写step
、s
),如果所在这行代码是一个函数调用,会跳进这个函数,可用来调试或检查函数调用。当前行不是函数调用时,next 和 step 效果是一样的。 - step out 按钮,对应命令
thread step-out
(简写finish
)。如果你不小心跳进一个函数,但实际上你想跳过它,可以用该命令跳出函数。
register read / write
寄存器指令,用来查看或修改系统库函数调用参数和返回值,寄存器与 CPU 架构有关。
(lldb) register read
General Purpose Registers:x0 = 0x000000010140a590x1 = 0x000000016f0ef2b0x2 = 0x0000000000000001x3 = 0x000000016f0ef578x4 = 0x0000000000000010x5 = 0x0000000000000620x6 = 0x0000000280ecc620x7 = 0x0000000000000000x8 = 0x0000000100d29000 (void *)0x0000000800000003x9 = 0x0000000201644dc0 dyld`_main_threadx10 = 0x0000000000000002x11 = 0x0000000101414488x12 = 0x0000000000000002x13 = 0x0000000000000000x14 = 0x0000000249680000x15 = 0x00000001a954f065 UIKitCore`_OBJC_$_INSTANCE_METHODS_UIViewController(UIAlertControllerContentViewController|UIResponderChainTraversal|UIActionSheetPresentationControllerAccess|UIImagePickerControllerAdditions|PLImagePickerViewControllerInterface|ForHomeOnly|UIPopoverController_Internal|_UIMultiColumnViewController|_UIKitIsUIViewController|UIPerformsActions|UITabBarControllerItem|UISplitViewController|BinaryCompatibility|UINavigationControllerItemInternal|UINavigationControllerItem|UINavigationControllerContextualToolbar|UINavigationControllerContextToolbar_Internal|UIContainerViewControllerProtectedMethods|UIContainerViewControllerCallbacks|UIContainerViewControllerCallbacks_Internal|StateRestoration_Internal|StateRestoration|ActivityContinuationPrivate|ActivityContinuationInternal|ActivityContinuation|EmbeddedViewSupport|UIFirstResponderSupport|UICollectionViewControllerSupport|UIViewControllerTransitioning|AdaptiveSizing|AdaptiveSizing_Internal|UIViewControllerClassDumpWarning|UIKeyCommand|ForUISplitViewController|AccessibilityHUD|OrientationDebugging|NSExtensionAdditionsInternal|NSExtensionAdditions|_UIApplicationRotationFollowing|_UIAlwaysOnEnvironment|_UIFallbackEnvironment|UnwindSegueSupport|_UISheetPresentationController|ViewServices|ViewService_Internal|ViewService_StateRestoration) + 1965x16 = 0x00000001a8861570 UIKitCore`-[UIViewController viewDidLoad]x17 = 0x0000000100e1dd4c libMainThreadChecker.dylib`__trampolines + 18880x18 = 0x0000000000000000x19 = 0x000000010140ada0x20 = 0x000000010140a590x21 = 0x00000002016bb000 UIKitCore`UIKeyboardCachedIsRightHandDrivex22 = 0x0000000000000000x23 = 0x0000000000000000x24 = 0x0000000000000018x25 = 0x0000000000000000x26 = 0x0000000000000000x27 = 0x0000000000000460x28 = 0x0000000201694000 UIKitCore`_MergedGlobals + 4fp = 0x000000016f0ef410lr = 0x0000000100d10644 Landmarks`-[ViewController viewDidLoad] + 100 at ViewController.m:20:5sp = 0x000000016f0eed10pc = 0x0000000100d10648 Landmarks`-[ViewController viewDidLoad] + 104 at ViewController.m:23:6cpsr = 0x60000000
thread backtrace
有时候我们想要了解线程堆栈信息,可以使用 thread backtrace(简写为 bt),它的作用是将线程的堆栈打印出来。
thread return
thread return 可以接受一个表达式,调用命令之后直接从当前的 frame 返回表达式的值。
效果相当于在断点位置直接调用 return NO;
,不会执行断点后面的代码。
frame 相关命令
-
frame variable:打印出当前 frame 的所有变量。
-
frame info:查看当前 frame 的信息。
-
frame select:选择某个 frame。
breakpoint
调试过程中,我们用得最多的可能就是断点了。LLDB 中的断点命令也非常强大。
设置断点
breakpoint set 命令用于设置断点,LLDB 提供了很多种设置断点的方式。
使用 -n 根据方法名设置断点:
// 给所有类中的 viewWillAppear: 设置一个断点
(lldb) breakpoint set -n viewWillAppear:
设置好断点后,会为该断点生成一个编号(id),从 1 开始递增。
使用 -f 指定文件:
// 给所有类中的 viewWillAppear: 设置一个断点
(lldb) breakpoint set -f ViewController.m -n viewDidLoad
使用 -l 指定文件某一行设置断点:
// 给 ViewController.m 第 38 行设置断点
(lldb) breakpoint set -f ViewController.m -l 38
使用 -c 设置条件断点:
// text: 方法接受一个 ret 的参数,我们想让 ret == YES 的时候程序中断
(lldb) breakpoint set -n text: -c ret == YES
使用 -o 设置单次断点:
// 如果刚刚那个断点我们只想让它中断一次
(lldb) breakpoint set -n text: -o
管理断点
- breakpoint list:查看已经设置了哪些断点。
- breakpoint disable x:使 id 为 x 的断点暂时失效。
- breakpoint enable x:使 id 为 x 的断点生效。
- breakpoint delete x:删除 d 为 x 的断点。
断点命令
breakpoint command add 命令就是给断点添加命令的命令。
假设我们需要在 ViewController 的 viewDidLoad 中查看 self.view 的值,我们首先给 -[ViewController viewDidLoad] 添加一个断点。添加成功之后,这个断点的 id 为 3,然后我们给它增加一个命令:po self.view:
-o 完整写法是 –one-liner,表示增加一条命令。添加完命令之后,每次程序执行到这个断点就可以自动打印出 self.view 的值了。
如果我们想增加多条命令,我们可以输入 breakpoint command add 3,对断点 3 增加命令,输入 DONE 表示结束:
如果想查看某个断点已有的命令,可以使用 breakpoint command list。
// 查看 id 为 3 的断点已有的命令
breakpoint command list 3
breakpoint command delete 可以让我们删除某个断点的命令:
// 删除 id 为 3 的断点的所有命令
breakpoint command delete 3
watchpoint
breakpoint 有一个孪生兄弟 watchpoint。如果说 breakpoint 是对方法生效的断点,watchpoint 就是对地址生效的断点。我们可以用 watchpoint 观察这个属性的地址。如果地址里面的东西改变了,就让程序中断。
和 breakpoint 一样,watchpoint 也有 id。失效、生效、删除的语法也和 breakpoint 一致。
watchpoint set 命令用于添加一个 watchpoint。只要这个地址中的内容变化了,程序就会中断。一般情况下,要观察变量或者属性,使用 watchpoint set variable 命令即可:
// 观察 self->_string
watchpoint set variable self->_string
watchpoint set variable 传入的是变量名。需要注意的是,这里不接受方法,所以不能使用watchpoint set variable self.string,因为 self.string 调用的是 _string 的取方法。
给 watchpoint 添加命令的方法,和 breakpoint 完全一致,这里就不赘述了。