LLDB探究

一、LLDB 是什么?

LLDB是Mac OS X上Xcode的默认调试器,支持在桌面和iOS设备和模拟器上调试C ,Objective-C和C++。它是新一代高性能调试器,它可以高效利用LLVM项目中的现有库,例如Clang表达式解析器和LLVM反汇编程序。

随着Xcode 5的发布,LLDB调试器已经取代了GDB,成为了Xcode工程中默认的调试器。它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。LLDB为Xcode提供了底层调试环境,其中包括内嵌在Xcode IDE中的位于调试区域的控制面板。

Chisel是facebook下一个开源LLDB命令集合,由于我这里调试的是Xcode自带的LLDB命令,如果想探究Chisel的请移步到文章最下方查看文章相关链接。

与此同时,让我们以在调试器中打印变量来开始我们的旅程吧。

二、基础命令

image.png

这是一个简单加了断点的程序,程序会在这一行停止运行,并且控制台会被打开,允许我们和调试器交互。这时候我们应该打些什么命令呢?

1、帮助 help

最简单命令是help,它会列举出所有的命令。如果你忘记了一个命令是做什么的,或者想知道更多的话,你可以通过help <command-name>来了解更多细节,例如help print或者help thread。只需要在控制台上图lldb字样的地方键入help即可。

image.png
2、打印对象 print 和 po

打印值很简单;只要试试print命令:

image.png

LLDB 实际上会作前缀匹配。所以你也可以使用prinpri,或者p。但你不能使用pr,因为 LLDB 不能消除process的歧义 (幸运的是p并没有歧义)。而print则是expression --的简写方式。

你可能还注意到了,结果中有个$0。实际上你可以使用它来指向这个结果。试试print $0 + 7,你会看到 130。任何 $ 符开头的东西都是存在于 LLDB 的命名空间的,它们是为了帮助你进行调试而存在的。

打印复杂对象时,print可能显得力不从心 ,我们想看的是对象的 description 方法的结果,这时可以使用popo其实是e -o --的别名。

甚至可以给print 指定不同的打印格式。它们都是以print/<fmt>或者简化的p/<fmt>格式书写。

//默认的格式
(lldb) p 16
(int) $3 = 16
//十六进制:
(lldb) p/x 16
(int) $4 = 0x00000010
//二进制 (t 代表 two):
(lldb) p/t 16
(int) $5 = 0b00000000000000000000000000010000
(lldb) p/t (char)16
(char) $6 = 0b00010000
3、修改对象 expression

如果想改变一个值怎么办?我们要用到的是expression这个方便的命令。

image.png

上图中修改了num的值,断点步进后可以看到NSLog的对应值已经发生了变化。

三、变量

现在你已经可以打印对象和简单类型,并且知道如何使用 expression 命令在调试器中修改它们了。现在让我们使用一些变量来减少输入量。就像你可以在 C 语言中用 int a = 0 来声明一个变量一样,你也可以在 LLDB 中做同样的事情。不过为了能使用声明的变量,变量必须以$符开头。

(lldb) e int $a = 2
(lldb) p $a * 19
(int) $1 = 38
(lldb) e NSArray *$array = @[ @"Saturday", @"Sunday", @"Monday" ]
(lldb) p [$array count]
(NSUInteger) $2 = 3
(lldb) po [[$array objectAtIndex:0] uppercaseString]
SATURDAY
(lldb) p (char)[[$array objectAtIndex:$a] characterAtIndex:0]
(char) $4 = 'M'

四、UI调试

因为全局变量是可访问的,可以像这样打印整个视图层级:

(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
<UIWindow: 0x7fb3fbf05b70; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x60000165f1b0>; layer = <UIWindowLayer: 0x600001809a80>>
   | <UITransitionView: 0x7fb3fbc19e30; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x60000182f320>>
   |    | <UIDropShadowView: 0x7fb3fbc1a570; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x60000182f3e0>>
   |    |    | <UIView: 0x7fb3ff116c30; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x600001802e00>>
   |    |    |    | <UIView: 0x7fb3fbe0a090; frame = (87 384; 240 128); autoresize = RM+BM; layer = <CALayer: 0x600001835e00>>
更新UI

就像上文变量中提到那样,我们可以拿到这个view:

(lldb) expression id $myView = (id)0x7fb3fbe0a090

尝试做一些修改:

(lldb) expression (void)[$myView setBackgroundColor:[UIColor redColor]]

但是只有程序继续运行之后才会看到界面的变化。因为改变的内容必须被发送到渲染服务中,然后显示才会被更新。

渲染服务实际上是一个另外的进程 (被称作 backboardd)。这就是说即使我们正在调试的内容所在的进程被打断了,backboardd 也还是继续运行着的。

这意味着你可以运行下面的命令,而不用继续运行程序:

(lldb) expression (void)[CATransaction flush]

这个时候就能看到背景颜色的改变了。

五、流程控制

通过xcode加断点调试时,调试条上回出现四个可以控制程序执行流程的按钮:

image.png

从左到右分别是 continue program execution 、 step over 、 step into 和 step out。

1、 continue program execution 按钮,会取消程序的暂停,允许程序正常执行 (要么一直执行下去,要么到达下一个断点)。

在 LLDB 中,你可以使用process continue或者thread continue命令来达到同样的效果。

2、 step over 按钮,会以黑盒的方式执行一行代码。如果所在这行代码是一个函数调用,那么就不会跳进这个函数,而是会执行这个函数,然后继续。

LLDB 则可以使用 thread step-overnext,或者 n 命令。

3、 step in 按钮,可以跳进一个函数调用来调试或者检查程序的执行情况。

在LLDB中使用 thread step-instep,或者 s 命令。注意,当前行不是函数调用时,nextstep 效果是一样的。

4、step out 按钮 ,如果你曾经不小心跳进一个函数,但实际上你想跳过它,常见的反应是重复的运行 n 直到函数返回。其实这种情况,step out 按钮是你的救世主。它会继续执行到下一个返回语句 (直到一个堆栈帧结束) 然后再次停止。

在LLDB中使用 thread step-out 命令。

六、断点

Xcode在断点导航中提供了一系列工具创建和管理断点,我们可以来看LLDB中等价的命令,主要是breakpoint命令。

1、查看 启用/禁用
image.png

上图是xcode查看断点的地方,点击断点会开启或关闭断点。对应的LLDB如下:

//查看断点 命令输出列表显示每个逻辑断点都有一个整数标识
//输出列表中另一个信息是断点位置是否是已解析的(resolved)。这个标识表示当与之相关的文件地址被加载到程序进行调试时,其位置是已解析的。
  //例如,如果在共享库中设置的断点之后被卸载了,则断点的位置还会保留,但其不能再被解析。
(lldb) breakpoint list
Current breakpoints:
1: file = '/Users/qdd7/Documents/OC-Demo/Demo29-master/Demo29-master/main.m', line = 23, exact_match = 0, locations = 1, resolved = 1, hit count = 1

  1.1: where = Demo29-master`main + 248 at main.m:23:9, address = 0x0000000109a10728, resolved, hit count = 1 
//禁用断点
(lldb) breakpoint disable 1
1 breakpoints disabled.
//启用断点
(lldb) breakpoint enable 1
1 breakpoints enabled.
2、 创建/删除

在Xcode创建断点的方式一种是 直接在代码左边的行数出点击 即可创建断点。对应的LLDB如下:

//在main.m的第24行创建断点
(lldb) breakpoint set -f main.m -l 24
Breakpoint 2: where = Demo29-master`main + 264 at main.m:28:5, address = 0x0000000109a10738
//删除刚才的断点
(lldb) breakpoint delete 2
1 breakpoints deleted; 0 breakpoint locations disabled.

七、查看 线程/调用栈 状态

在进程停止后,LLDB会选择一个当前线程和线程中当前帧(frame)。很多检测状态的命令可以用于这个线程或帧。

为了检测进程的当前状态,可以从以下命令thread list开始:

(lldb) thread list
Process 13180 stopped
* thread #1: tid = 0x3cd30e, 0x00007fff5182322a libsystem_kernel.dylib`mach_msg_trap + 10, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
  thread #2: tid = 0x3cd499, 0x00007fff51824bfe libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #5: tid = 0x3cd49a, 0x00007fff51824bfe libsystem_kernel.dylib`__workq_kernreturn + 10
  ...

星号(*)表示thread #1为当前线程。为了获取线程的跟踪栈,可以使用以下命令thread backtrace

//默认为当前线程 也可以指定线程 : thread backtrace 2
(lldb) thread backtrace
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
  * frame #0: 0x00007fff5182322a libsystem_kernel.dylib`mach_msg_trap + 10
    frame #1: 0x00007fff5182376c libsystem_kernel.dylib`mach_msg + 60
    frame #2: 0x00007fff23b0caf5 CoreFoundation`__CFRunLoopServiceMachPort + 197
    ...

如果想查看所有线程的调用栈,则可以使用以下命令:thread backtrace all

检查帧参数和本地变量的最简便的方式是使用frame variable命令:

(lldb) frame variable
(ViewController *) self = 0x00007fb7e180a5f0
(SEL) _cmd = "viewDidLoad"
(NSUInteger) num = 123
(__NSCFConstantString *) str = 0x000000010c6590b0 @"learning LLDB"
(__NSArrayI *) arr = 0x00006000036fede0 @"2 elements"

如果没有指定任何变量名,则会显示所有参数和本地变量。如果指定参数名或变量名,则只打印指定的值。如:

(lldb) frame variable self
(ViewController *) self = 0x00007fb7e180a5f0
image

image指令是target module指令的缩写,借助它我们能够查看当前的Binary Images相关的信息。日常开发我们主要利用它寻址。image命令的用法也挺多,首先可以用它来查看工程中使用的库,如下所示:

(lldb) image list
[  0] 4EA7C9AA-212E-3B57-9A3D-B78FB0DBC3E7 0x000000010c656000 /Users/qdd7/Library/Developer/Xcode/DerivedData/Demo29-master-bsvyynbgkgcozefqlgdaouuznpzi/Build/Products/Debug-iphonesimulator/Demo29-master.app/Demo29-master 
[  1] CE635DB2-D47E-3C05-A0A3-6BD982E7E750 0x0000000114088000 /usr/lib/dyld 
[  2] 32684ACA-A9FF-35E2-BB46-62CFF84251FE 0x000000010c662000 /Users/qdd7/Documents/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/dyld_sim 
...

我们还可以用它来查找可执行文件或共享库的原始地址,这一点还是很有用的,当我们的程序崩溃时,我们可以使用这条命令来查找崩溃所在的具体位置,如下所示:

NSArray *array = @[@1,@2];
NSLog(@"array 3 : %@",array[2]);
// 以下是异常代码
2019-10-17 15:54:53.153364+0800 Demo29-master[13218:3994766] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 2 beyond bounds [0 .. 1]'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff23baa1ee __exceptionPreprocess + 350
    1   libobjc.A.dylib                     0x00007fff50864b20 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff23c3cb71 _CFThrowFormattedException + 194
    3   CoreFoundation                      0x00007fff23c1b30d -[__NSArrayI objectAtIndexedSubscript:] + 93
    4   Demo29-master                       0x0000000100a16121 -[ViewController viewDidLoad] + 273
    5   UIKitCore                           0x00007fff46f03d96 -[UIViewController _sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 83
    6   UIKitCore                           0x00007fff46f08cef -[UIViewController loadViewIfRequired] + 1084
    ...

根据以上信息,我们可以判断崩溃位置是在ViewController中,要想知道具体在哪一行,可以使用以下命令image lookup --address

(lldb) image lookup --address 0x0000000100a16121
      Address: Demo29-master[0x0000000100001121] (Demo29-master.__TEXT.__text + 273)
      Summary: Demo29-master`-[ViewController viewDidLoad] + 273 at ViewController.m:32

可以看到,最后定位到了ViewController.m:32行,正是我们代码所在的位置。

文章相关链接

深入了解GDB和LLDB
Chisel的初步使用

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