调试器LLDB

摘取自网络,自己有做整理,备自己偶尔健忘时复习使用!

LLDB是LLVM的调试器。
Xcode从4.0开始编译器改用LLVM(GCC--->LLVM)
相应的调试器也从gdb改为LLDB。
从 Xcode5.0开始所有工程 被自动设置 为使用LLDB。

LLDB基础知识

LLDB控制台
LLDB控制台.png

LLDB控制台平时会输出一些log信息。想输入命令调试必须让程序进入暂停状态。
那么怎么让程序进入暂停状态呢?
方式主要有2种:
断点或者watchpoint: 在代码中设置一个断点(watchpoint),
当程序运行到断点位置的时候,会进入stop状态
直接暂停:控制台上方有一个暂停按钮,上图红框已标出,点击即可暂停程序。


LLDB语法

了解语法可以帮助我们清晰的使用LLDB:
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]

解释:
<command>(命令)和<subcommand>(子命令):LLDB调试命令的名称。
命令和子命令按层级结构排列:一个命令对象为跟随其的子命令对象创建一个上下文,子命令又为其子命令创建一个上下文依此类推。
<action>:执行命令的操作
<options>:命令选项
<arguement>:命令的参数
[]:表示命令是可选的,可以有也可以没有
假设我们给main方法设置一个断点,使用下面的命令:


breakpoint set -n main

command: breakpoint 表示断点命令
action: set 表示设置断点
option: -n 表示根据方法name设置断点
arguement: mian 表示方法名为mian


原始(raw)命令

LLDB支持不带命令选项(options)的原始(raw)命令,原始命令会将命令后面的所有东东做参数(arguement)传递。很多原始命令也可以带命令选项,当你使用命令选项的时候,需在命令选项后面加--区分命令选项和参数。

e.g: 常用的expression就是raw命令,一般情况下我们使用expression打印一个东西是这样的:

(lldb) expression count
(int) $2 = 6

当想打印一个对象的时候需要使用-O命令选项,应该用--将命令选项和参数区分:

(lldb) expression -O -- self
<ViewController: 0x888000f17666>

唯一匹配原则

LLDB的命令遵循唯一匹配原则:假如根据前n个字母已能唯一匹配到某个命令,则只写前n个字母等效于写下完整的命令。
e.g: 前面提到我设置断点的命令,我们可以使用唯一匹配原则简写,
下面2条命令是等效的:

breakpoint set -n main
br s -n main

真的很人性化!



LLDB命令

expression

expression
英 [ɪkˈspreʃn] 美 [ɪkˈspreʃn]
n.
表示;表达;表露;表情;神色;词语;措辞;表达方式

expression命令的作用是执行一个表达式,将表达式返回的结果输出。
expression的完整语法是这样的:

expression <cmd-options> -- <expr>

<cmd-options>:命令选项,一般情况下使用默认的即可不需要特别标明。
--: 命令选项结束符,表示所有的命令选项已经设置完毕,如果没有命令选项,--可以省略
<expr>: 要执行的表达式
expression是LLDB里面最重要的命令。因能实现2个功能。

在代码运行过程中可通过执行某个表达式动态改变程序运行的轨迹。
假如在运行过程中,想把self.view颜色改成红色。

不必写下代码,重新run!!!

只需要123
1.暂停程序,
2.用expression改变颜色
3.刷新界面
就能看到效果

  // 改变颜色
  (lldb) expression -- self.view.backgroundColor = [UIColor redColor]
  // 刷新界面
  (lldb) expression -- (void)[CATransaction flush]

可通过expression来打印一些东东。
假如想打印self.view:

   (lldb) expression -- self.view
    (UIView *) $1 = 0x00007fe322c18a10
p & print & call

更多时候我们用的是p、print、call。
这三个命令都是expression --的别名
(--表示不再接受命令选项,详情见前面原始(raw)命令这一节)

print: 打印某个东东,可是变量和表达式
p: 可以看做是print的简写
call: 调用某个方法。
看起来他们可能有不一样的地方,实际都是执行某个表达式(变量也当做表达式),将执行的结果输出到控制台上。
所以你可以用p调用某个方法,也可以用call打印东西
e.g: 下面代码效果相同:

(lldb) expression -- self.view
(UIView *) $5 = 0x00007fb2a40344a0
(lldb) p self.view
(UIView *) $6 = 0x00007fb2a40344a0
(lldb) print self.view
(UIView *) $7 = 0x00007fb2a40344a0
(lldb) call self.view
(UIView *) $8 = 0x00007fb2a40344a0
(lldb) e self.view
(UIView *) $9 = 0x00007fb2a40344a0

根据唯一匹配原则,你没有自己添加特殊的命令别名。e也可以表示expression的意思。原始命令默认没有命令选项,所以e也能带给你同样的效果.


po

OC里所有的对象都是用指针表示的,所以一般打印的时候打印出来的是对象的指针,而不是对象本身。如果想打印对象。我们需要使用命令选项:-O。为了更方便的使用,LLDB为expression -O --定义了一个别名:po

(lldb) expression -- self.view
(UIView *) $13 = 0x00007fb2a40344a0
(lldb) expression -O -- self.view
<UIView: 0x7fb2a40344a0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fb2a4018c80>>
(lldb) po self.view
<UIView: 0x7fb2a40344a0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fb2a4018c80>>

thread

thread backtrace & bt

想了解线程堆栈信息,可使用thread backtrace
thread backtrace作用是将线程的堆栈打印出来。

thread backtrace [-c <count>] [-s <frame-index>] [-e <boolean>]
thread backtrace后面跟的都是命令选项:

-c:设置打印堆栈的帧数(frame)
-s:设置从哪个帧(frame)开始打印
-e:是否显示额外的回溯
实际上这些命令选项一般不需使用。
e.g: 当发生crash的时候,可以使用thread backtrace查看堆栈调用

(lldb) thread backtrace
* thread #1: tid = 0xdd88, 0x000000010afb380b libobjc.A.dylib`objc_msgSend + 11, queue = ‘com.apple.main-thread‘, stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    frame #0: 0x000000010afb380b libobjc.A.dylib`objc_msgSend + 11
  * frame #1: 0x000000010aa9f75e TLLDB`-[ViewController viewDidLoad](self=0x00007fa270e1f440, _cmd="viewDidLoad") + 174 at ViewController.m:26
    frame #2: 0x000000010ba67f98 UIKit`-[UIViewController loadViewIfRequired] + 1198
    frame #3: 0x000000010ba682e7 UIKit`-[UIViewController view] + 29
    frame #4: 0x000000010b93eab0 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 61
    frame #5: 0x000000010b93f199 UIKit`-[UIWindow _setHidden:forced:] + 282
    frame #6: 0x000000010b950c2e UIKit`-[UIWindow makeKeyAndVisible] + 42
可看到crash发生在-[ViewController viewDidLoad]中的第26行,只需检查这行代码。

LLDB为backtrace定义了一个别名:bt,效果与thread backtrace相同,

(lldb) bt

thread return
Debug的时候也许因为各种原因,不想让代码执行某个方法,或者要直接返回一个想要的值。这时就该thread return上场了。

thread return可以接受一个表达式,调用命令之后直接从当前的frame返回表达式的值。

e.g: 我们有一个someMethod方法,默认情况下是返回YES。我们想要让他返回NO

技术分享

我们只需在方法的开始位置加一个断点,当程序中断的时候,输入命令即可:

(lldb) thread return NO

效果相当于在断点位置直接调用return NO;,不会执行断点后面的代码.

c & n & s & finish

在调试程序的时候,经常用到下面这4个按钮:

技术分享

LLDB命令也可以完成上面的操作,如果不输入命令直接按Enter键,LLDB会自动执行上次的命令。
真TMD的人性化!!!

对应这4个按钮的LLDB命令:

  1. c/ continue/ thread continue: 三命令等同按钮一。(程序继续运行)
  2. n/ next/ thread step-over: 三命令等同按钮二。(单步运行)
  3. s/ step/ thread step-in: 三命令等同按钮三。(进入某个方法)
  4. finish/ step-out: 两命令等同按钮四。(直接走完当前方法返回到上层)
thread其他不常用的命令

thread 相关的还有一些不常用的命令,就简单介绍一下,如果要了解更多可使用命令help thread查阅
thread jump: 直接让程序跳到某一行。由于ARC下编译器实际插入了不少retain,release命令。跳过一些代码不执行很可能会造成对象内存混乱发生crash。
thread list: 列出所有的线程
thread select: 选择某个线程
thread until: 传入一个line的参数,让程序执行到这行的时候暂停
thread info: 输出当前线程的信息


frame

前面我们提到过很多次frame(帧)。可能有的朋友对frame这个概念还不太了解。
打个断点

技术分享

在控制台上输入命令bt,可打印出来所有的frame。仔细观察这些frame和左边红框里的堆栈是一致的。我们看到的左边的堆栈就是frame。

frame variable

Debug的时候经常做的事就是查看变量的值,通过frame variable命令,可打印出当前frame的所有变量

(lldb) frame variable
(ViewController *) self = 0x000078888888888
(SEL) _cmd = "text:"
(BOOL) ret = YES
(int) a = 6

可以看到将self,_cmd,ret,a等本地变量都打印了出来

如果需要打印指定变量,也可以给frame variable传入参数:

(lldb) frame variable self->_string
(NSString *) self->_string = nil

不过frame variable只接受变量作为参数,不接受表达式。
也就是说我们无法使用frame variable self.string,因为self.string是调用string的getter方法。所以打印指定变量大家更喜欢用p或者po。

其他不常用命令

一般frame variable打印所有变量用得比较多,frame还有2个不怎么常用的命令:

frame info: 查看当前frame的信息

(lldb) frame info
frame #0: 0x0000000101bf87d5 TLLDB`-[ViewController text:](self=0x00007fa158526e60, _cmd="text:", ret=YES) + 37 at 

frame select: 选择某个frame

(lldb) frame select 1
frame #1: 0x0000000101bf872e TLLDB`-[ViewController viewDidLoad](self=0x00007fa158526e60, _cmd="viewDidLoad") + 78 at ViewController.m:23
   20      
   21      - (void)viewDidLoad {
   22          [super viewDidLoad];
-> 23          [self text:YES];
   24          NSLog(@"1");
   25          NSLog(@"2");
   26          NSLog(@"3");

当我们选择frame 1的时候,他会把frame1的信息和代码打印出来。不过一般我都是直接在Xcode左边点击某个frame,这样更方便


breakpoint

调试过程中,我们用得最多的可能就是断点了。LLDB中的断点命令也非常强大

breakpoint set

breakpoint set命令用于设置断点,LLDB提供了很多种设置断点的方式:

使用-n根据方法名设置断点:

e.g: 我们想给所有类中的viewWillAppear:设置一个断点:

(lldb) breakpoint set -n viewWillAppear:
Breakpoint 13: 33 locations.

使用-f指定文件

e.g: 我们只需要给ViewController.m文件中的viewDidLoad设置断点:

(lldb) breakpoint set -f ViewController.m -n viewDidLoad
    Breakpoint 22: where = TLLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:22, address = 0x000000010272a6f4

这里需要注意,如果方法未写在文件中(比如写在category文件中,或者父类文件中),指定文件之后,将无法给这个方法设置断点。

使用-l指定文件某一行设置断点

e.g: 我们想给ViewController.m第38行设置断点

(lldb) breakpoint set -f ViewController.m -l 38
Breakpoint 23: where = TLLDB`-[ViewController text:] + 37 at ViewController.m:38, address = 0x000000010272a7d5

使用-c设置条件断点

e.g: text:方法接受一个ret的参数,我们想让ret == YES的时候程序中断:

(lldb) breakpoint set -n text: -c ret == YES
Breakpoint 7: where = TLLDB`-[ViewController text:] + 30 at ViewController.m:37, address = 0x0000000105ef37ce

使用-o设置单次断点

e.g: 如果刚刚那个断点我们只想让他中断一次:

(lldb) breakpoint set -n text: -o
‘breakpoint 3‘: where = TLLDB`-[ViewController text:] + 30 at ViewController.m:37, address = 0x000000010b6f97ce

breakpoint command

有的时候我们可能需要给断点添加一些命令,比如每次走到这个断点的时候,我们都需要打印self对象。我们只需要给断点添加一个po self命令,就不用每次执行断点再自己输入po self

breakpoint command add

breakpoint command add命令就是给断点添加命令的命令。

e.g: 假设我们需要在ViewControllerviewDidLoad中查看self.view的值
我们首先给-[ViewController viewDidLoad]添加一个断点

(lldb) breakpoint set -n "-[ViewController viewDidLoad]"
‘breakpoint 3‘: where = TLLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:23, address = 0x00000001055e6004

可以看到添加成功之后,这个breakpoint的id为3,然后我们给他增加一个命令:po self.view

(lldb) breakpoint command add -o "po self.view" 3

-o完整写法是--one-liner,表示增加一条命令。3表示对id为3breakpoint增加命令。
添加完命令之后,每次程序执行到这个断点就可以自动打印出self.view的值了

如果我们一下子想增加多条命令,比如我想在viewDidLoad中打印当前frame的所有变量,但是我们不想让他中断,也就是在打印完成之后,需要继续执行。我们可以这样玩:

(lldb) breakpoint command add 3
Enter your debugger command(s).  Type ‘DONE‘ to end.
> frame variable
> continue
> DONE

输入breakpoint command add 3对断点3增加命令。他会让你输入增加哪些命令,输入‘DONE‘表示结束。这时候你就可以输入多条命令了

breakpoint command list

查看某个断点已有的命令,可使用breakpoint command list

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