LLDB 常用方法总结

前言

LLDB 简介

    LLDB 是集成于 Xcode 的默认调试器,支持在桌面、iOS设备和模拟器上调试C、Objective-C和C++程序。它是一套开源调试器,提供诸如读取DWARF(一种调试信息格式)、分步执行、回溯跟踪等功能。掌握 LLDB 的使用可以极大的提高我们的调试效率

LLDB 与 GDB

    在 Xcode4.2 以前 Xcode 编译器还是 GCC(前端) + LLVM(后端) 架构,此时调试器还是 GDB 的时代。在 Xcode4.2 Clang(前端) + LLVM3.0(后端) 成为了默认编译器,之后从 Xcode4.3 开始 LLDB 成为了 Xcode 的默认调试器。LLDB 官网有LLDB 与 GDB命令对照表

LLDB 支持平台

macOS desktop user space debugging for i386 and x86_64

iOS simulator debugging on i386 and x86_64

iOS device debugging on ARM and AArch64

Linux local user-space debugging for i386, x86_64 and PPC64le

FreeBSD local user-space debugging for i386 and x86_64

Windows local user-space debugging for i386 (*)

    mac 环境以外使用 LLDB 需要配置环境,mac 环境下安装 Xcode 即可使用。本文仅探讨 Xcode 环境下 LLDB 的使用

语法

基本格式

<noun> <verb> [-options [value]] [argument [argument...]]

即:<关键字> <动作> [-可选项 [可选项的值]] [参数1 [参数2···]]

特别说明

  • 关键字动作可选项参数 都是通过 空格 进行分隔。参数 如果带有空格需要使用 "" 包裹参数
  • 如果参数中包含有 \"" ,那么使用 \ 对它们进行转义
  • 如果参数以 - 开始,需要在可选项和参数之间使用 -- 加以分割
  • 可选项顺序可以不是固定的

示例

(lldb) process launch --stop-at-entry -- -program_arg_1 value -program_arg_2 value

关键字:process

动作:launch

可选项:-stop-at-entry

参数:-program_arg_1 value、-program_arg_2 value

作用:启动程序并传入 `-program_arg_1 value、-program_arg_2 value 作为参数
(lldb) breakpoint set --file foo.c --line 12

关键字:breakpoint

动作:set

可选项:-file、-line

可选项的值:foo.c、12

作用:在文件 `foo.c` 的 `12` 行设置断点
(lldb) breakpoint set --name foo

关键字:breakpoint

动作:set

可选项:-name

可选项的值:foo

作用:为函数名为 `foo` 的函数设置断点

变量打印

    大部分 iOS Coder 在实际开发中用得最多的 LLDB 命令可能就是 popo的全称是print object,它的作用是调用 NSObject 对象的 debugDescription 方法。而另一个常用命令 p 全称是 print,作用是打印 NSObject 对象的地址或基本数据类型的值,并生成一个临时变量

(lldb) p testInt
(NSInteger) $9 = 5
(lldb) po testInt
5

// 另一个与 p 相同作用命令:
(lldb) expression -- testInt
(NSInteger) $7 = 5
(lldb) expression -o -- testInt
5
  • 以特定格式打印变量:x(16进制)、c(字符)、t(二进制)
(lldb) p/x testInt
(NSInteger) $11 = 0x0000000000000005
(lldb) p/c testInt
(NSInteger) $12 = \x05\0\0\0\0\0\0\0
(lldb) p/t testInt
(NSInteger) $14 = 0b0000000000000000000000000000000000000000000000000000000000000101
  • 声明变量
  • 变量名必须以 $ 符号开头
(lldb) expression NSString *$str = @"Hello World"
(lldb) po $str
Hello World
  • 修改变量
(lldb) p testInt
(NSInteger) $15 = 5
(lldb) expression testInt = 100
(NSInteger) $16 = 100
(lldb) p testInt
(NSInteger) $17 = 100

断点调试

  • 通过函数名设置断点
(lldb) breakpoint set --name "-[ViewController testMethod]"
(lldb) br s -n "-[ViewController testMethod]"
(lldb) b -[ViewController testMethod]

// 成功后:
Breakpoint 3: where = lldbTest2`-[ViewController testMethod] + 12 at ViewController.m:30:1, address = 0x000000010638161c
  • 通过地址设置断点
(lldb) breakpoint set --address 0x0000000106216070
(lldb) br s -a 0x0000000106216070

// 成功后:
Breakpoint 2: where = lldbTest2`lldbTest2[0x0000000100003070], address = 0x000000010342e070
  • 列出所有断点
(lldb) breakpoint list
(lldb) br l
  • 删除断点
(lldb) breakpoint delete 1
(lldb) br del 1

流程控制

Xcode调试工具.png

    在调试过程中我们最常用的就是 Xcode 提供的调试工具,除断点控制以外,从左到右四个按钮分别是:continuestep overstep instep out,在 LLDB 中存在命令与之对应

  • continue
(lldb) process continue
(lldb) continue
(lldb) c

// 会取消程序的暂停,允许程序正常执行 (要么一直执行下去,要么到达下一个断点)
  • step over
(lldb) thread step-over
(lldb) next
(lldb) n

// 会以黑盒的方式执行一行代码。如果所在这行代码是一个函数调用,那么就不会跳进这个函数,而是会执行这个函数,然后继续
  • step in
(lldb) thread step-in
(lldb) step
(lldb) s

// 每执行一次上述命令断点会跳转到下一行源代码位置,如果下一行是一个函数调用,会进入函数调用内部
// 注:当前行不是函数调用时,next 和 step 效果是一样的
  • step out
(lldb) thread step-out
(lldb) finish

// 如果你不小心跳进一个函数,但实际上你想跳过它,常见的反应是重复的运行 n 直到函数返回。但其实这种情况 step out 可以解决。它会继续执行到下一个返回语句 (直到一个堆栈帧结束) 然后再次停止
  • 除此以外还有一个常用命令 thread return
thread return <Value>

// thread return 可以用来控制程序流程。它有一个可选参数,在执行时它会把可选参数加载进返回寄存器里,然后立刻执行返回命令,跳出当前栈帧
// 注:这会给ARC的引用计数造成一些问题,或者使函数内的清理部分失效。但是如果在函数的开头执行这个命令,可以非常好的隔离这个函数,伪造返回值

调试信息

  • 查看当前调用方法和行数
  • frame info / fr i
(lldb) frame info
frame #0: 0x000000010a560522 lldbTest2`-[ViewController viewDidLoad](self=0x00007fb9ca42b940, _cmd="viewDidLoad") at ViewController.m:25:21
  • 查看当前方法和所有变量
  • frame variable / fr v
(lldb) frame variable
(ViewController *) self = 0x00007fb9ca42b940
(SEL) _cmd = "viewDidLoad"
(NSInteger) testInt = 5
  • 查看当前调试线程、行数、和源码信息
  • thread info
(lldb) thread info
thread #1: tid = 0x6f66ea, 0x000000010a560522 lldbTest2`-[ViewController viewDidLoad](self=0x00007fb9ca42b940, _cmd="viewDidLoad") at ViewController.m:25:21, queue = 'com.apple.main-thread', stop reason = step out
  • 查看当前所有线程状态
  • thread list
(lldb) thread list
Process 81659 stopped
* thread #1: tid = 0x6f66ea, 0x000000010a560522 lldbTest2`-[ViewController viewDidLoad](self=0x00007fb9ca42b940, _cmd="viewDidLoad") at ViewController.m:25:21, queue = 'com.apple.main-thread', stop reason = step out

  thread #3: tid = 0x6f6710, 0x000000010d48fbfe libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #4: tid = 0x6f6712, 0x000000010d48fbfe libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #6: tid = 0x6f6717, 0x000000010d48e22a libsystem_kernel.dylib`mach_msg_trap + 10, name = 'com.apple.uikit.eventfetch-thread'
  • 当前线程堆栈回溯信息
  • thread backtrace / bt
(lldb) thread backtrace
* thread #1, queue = 'com.apple.main-thread', stop reason = step out

  * frame #0: 0x000000010a560522 lldbTest2`-[ViewController viewDidLoad](self=0x00007fb9ca42b940, _cmd="viewDidLoad") at ViewController.m:25:21
    frame #1: 0x000000010dffd43b UIKitCore`-[UIViewController loadViewIfRequired] + 1183
    frame #2: 0x000000010dffd868 UIKitCore`-[UIViewController view] + 27
    frame #3: 0x000000010e635c33 UIKitCore`-[UIWindow addRootViewControllerViewIfPossible] + 122
    frame #4: 0x000000010e636327 UIKitCore`-[UIWindow _setHidden:forced:] + 289
    frame #5: 0x000000012795fbbd UIKit`-[UIWindowAccessibility _orderFrontWithoutMakingKey] + 86
    ……
  • 列出当前所有可执行文件
  • image list / im li
(lldb) image list
……
  • 查找函数
  • image lookup --name <function-or-symbol> / im loo -r -n <function-or-symbol>
(lldb) image lookup --name testMethod
1 match found in /Users/ryanyuan/Library/Developer/Xcode/DerivedData/lldbTest2-fpaxelkuicxzoafqzpwnnjimxgbk/Build/Products/Debug-iphonesimulator/lldbTest2.app/lldbTest2:
        Address: lldbTest2[0x00000001000015c0] (lldbTest2.__TEXT.__text + 272)
        Summary: lldbTest2`-[ViewController testMethod] at ViewController.m:28
  • 根据地址查找函数
  • image lookup --address <address-expression> / im loo -a <address-expression>
(lldb) image lookup --address 0x0000000105212536
      Address: lldbTest2[0x0000000100001536] (lldbTest2.__TEXT.__text + 582)
      Summary: lldbTest2`-[ViewController testMethod] + 310 at ViewController.m:30:5

// 该方法可以用来从 crash log 中定位函数,后续博客会更新相关内容,待续...

观察者

    在调试过程中有时没办法判断断点的具体位置,需要在某个值发生变化时触发断点调试,这个时候就需要用到 LLDB 中的 watchpoint

  • 设置观察者,当变量被修改时触发
  • watchpoint set variable <Value> / wa s v <Value>
(lldb) watchpoint set variable testInt
Watchpoint created: Watchpoint 1: addr = 0x7ffee7f94878 size = 8 state = enabled type = w
    declare @ '/Users/ryanyuan/Desktop/lldbTest2/lldbTest2/ViewController.m:23'
    watchpoint spec = 'testInt'
    new value: 5
    
// 拦截到变化后
Watchpoint 1 hit:
old value: 5
new value: 0

  • 根据内存地址设置观察者
  • watchpoint set expression -- <Address> / wa s e -- <Address>
(lldb) watchpoint set expression -- 0x000000010f46c070
Watchpoint created: Watchpoint 1: addr = 0x10f46c070 size = 8 state = enabled type = w
    new value: 4574177224
  • 当前所有观察者列表
  • watchpoint list / watch l
(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0x7ffeeabfa878 size = 8 state = enabled type = w
    declare @ '/Users/ryanyuan/Desktop/lldbTest2/lldbTest2/ViewController.m:23'
    watchpoint spec = 'testInt'
    new value: 0
Watchpoint 2: addr = 0x10f46c070 size = 8 state = enabled type = w
    new value: -620567661233521176
    condition = 'testInt == 2'
  • 设置带有条件的观察者,当被修改的变量满足条件时触发
  • watchpoint modify -c '<condition>'
(lldb) watchpoint modify -c 'testInt == 2'
(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0x7ffeeabfa878 size = 8 state = enabled type = w
    declare @ '/Users/ryanyuan/Desktop/lldbTest2/lldbTest2/ViewController.m:23'
    watchpoint spec = 'testInt'
    new value: 0
Watchpoint 2: addr = 0x10f46c070 size = 8 state = enabled type = w
    new value: -620567661233521176
    condition = 'testInt == 2'

  • 删除观察者
  • watchpoint delete <WatchpointNumber> / watch del <WatchpointNumber>
(lldb) watchpoint delete 2
1 watchpoints deleted.

帮助

    在调试过程中最常用的指令应该是 help ,对于任何指令我们都可以使用 help <关键字> <动作>查看详细的描述和参数,经常使用 help 可以让我们了解更多的调试方式,对于 LLDB 的使用也会更加灵活

参考

Goals — The LLDB Debugger

iOS LLDB常用调试技巧记录汇总

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345