17-lldb(上)调试指令

前言

在我们日常开发中,使用XCode进行编码实现功能的时候,经常会打断点进行调试,同时配合lldb的某些指令,查看变量的信息等。本篇文章将给大家讲解下lldb的常用的调试指令,当然,不仅仅是断点相关的。

一、lldb概述

lldb(Low Lever Debug) 👉 默认内置于Xcode中的动态调试工具。标准的lldb提供了一组广泛的命令,旨在与老版本的GDB命令兼容。 除了使用标准配置外,还可以很容易地自定义lldb以满足实际需要。

lldb语法

<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
  • <command>(命令)和<subcommand>(子命令) 👉 lldb调试命令的名称
  • <action> 👉 执行命令的操作
  • <options> 👉 命令选项
  • <arguement> 👉 命令的参数
  • [] 👉 表示命令是可选的,可以有也可以没有

例如👇

breakpoint set -n test

这条lldb指令所对应的参数释义👇

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

二、断点

日常开发中,最常用的是断点调试。但是在逆向环境中,我们并没有第三方应用的源码,所以不可能通过在源码上直接设置断点。这种情况,只能在XCode中lldb上使用breakpoint指令设置断点,或者直接在终端进行断点设置。

案例演示

1. breakpoint指令

新建lldbDemo工程,打开ViewController.m文件,写入以下代码👇

#import "ViewController.h"

@implementation ViewController

void test1(){
   NSLog(@"3");
}

- (void)viewDidLoad {
   [super viewDidLoad];
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
   NSLog(@"1");
   NSLog(@"2");
   test1();
}

@end

我们使用指令对test1下断点👇

breakpoint set -n test1

显示Breakpoint 2,说明是设置的第2个断点,第1个断点是使用Xcode在touchesBegan中对NSLog(@"1")设置的。

2. 对指定方法设置断点

还是在ViewController.m文件中,写入以下代码👇

#import "ViewController.h"

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
}

- (IBAction)save:(id)sender {
   NSLog(@"保存");
}

- (IBAction)pause:(id)sender {
   NSLog(@"暂停");
}

- (IBAction)continueGame:(id)sender {
   NSLog(@"继续");
}

@end

这是第三方App中的源码,我们当做不知道,只知道方法的名称,此时如何下断点呢?

逆向环境中,运行第三方程序,使用暂停可进入lldb控制台,相当于Debug进程👇

我们对上面的三个方法都打上断点👇

breakpoint set -n "-[ViewController save:]" -n "-[ViewController pause:]" -n "-[ViewController continueGame:]"

Breakpoint 1: 3 locations的释义👇

  • Breakpoint 1 👉 分组1
  • 3 locations 👉 分组1下设置了3个断点
breakpoint list

还可以使用breakpoint list查看断点👇

每个断点的信息都很详细。

3. 断点的禁用、启动和删除
  • 禁用断点
breakpoint disable 1

表示将分组1下的所有断点禁用👇

  • 启用断点
breakpoint enable 1

分组1下的所有断点启用👇

也可以对单一断点禁用或启用👇

breakpoint disable 1.1
  • 删除断点
  • 删除分组1下的所有断点
breakpoint delete 1
  • 删除全部断点
breakpoint delete

很友好,还要确认一下。

不支持删除某单一断点,例如👇

breakpoint delete 4.1
-------------------------
0 breakpoints deleted; 1 breakpoint locations disabled.
4. 更多breakpoint指令

使用help breakpoint指令👇

(lldb) help breakpoint
Commands for operating on breakpoints (see 'help b' for shorthand.)

Syntax: breakpoint <subcommand> [<command-options>]

The following subcommands are supported:

      clear   -- Delete or disable breakpoints matching the specified source
                 file and line.
      command -- Commands for adding, removing and listing LLDB commands
                 executed when a breakpoint is hit.
      delete  -- Delete the specified breakpoint(s).  If no breakpoints are
                 specified, delete them all.
      disable -- Disable the specified breakpoint(s) without deleting them.  If
                 none are specified, disable all breakpoints.
      enable  -- Enable the specified disabled breakpoint(s). If no breakpoints
                 are specified, enable all of them.
      list    -- List some or all breakpoints at configurable levels of detail.
      modify  -- Modify the options on a breakpoint or set of breakpoints in
                 the executable.  If no breakpoint is specified, acts on the
                 last created breakpoint.  With the exception of -e, -d and -i,
                 passing an empty argument clears the modification.
      name    -- Commands to manage name tags for breakpoints
      read    -- Read and set the breakpoints previously saved to a file with
                 "breakpoint write".  
      set     -- Sets a breakpoint or set of breakpoints in the executable.
      write   -- Write the breakpoints listed to a file that can be read in
                 with "breakpoint read".  If given no arguments, writes all
                 breakpoints.

For more help on any particular subcommand, type 'help <command> <subcommand>'.
(lldb) 

也可以了解更多选项的参数设置👇

(lldb) help breakpoint set
Sets a breakpoint or set of breakpoints in the executable.

Syntax: breakpoint set <cmd-options>

Command Options Usage:
  breakpoint set [-DHd] -l <linenum> [-G <boolean>] [-C <command>] [-c <expr>] [-i <count>] [-o <boolean>] [-q <queue-name>] [-t <thread-id>] [-x <thread-index>] [-T <thread-name>] [-R <address>] [-N <breakpoint-name>] [-u <column>] [-f <filename>] [-m <boolean>] [-s <shlib-name>] [-K <boolean>]
  breakpoint set [-DHd] -a <address-expression> [-G <boolean>] [-C <command>] [-c <expr>] [-i <count>] [-o <boolean>] [-q <queue-name>] [-t <thread-id>] [-x <thread-index>] [-T <thread-name>] [-N <breakpoint-name>] [-s <shlib-name>]
  breakpoint set [-DHd] -n <function-name> [-G <boolean>] [-C <command>] [-c <expr>] [-i <count>] [-o <boolean>] [-q <queue-name>] [-t <thread-id>] [-x <thread-index>] [-T <thread-name>] [-R <address>] [-N <breakpoint-name>] [-f <filename>] [-L <source-language>] [-s <shlib-name>] [-K <boolean>]
  breakpoint set [-DHd] -F <fullname> [-G <boolean>] [-C <command>] [-c <expr>] [-i <count>] [-o <boolean>] [-q <queue-name>] [-t <thread-id>] [-x <thread-index>] [-T <thread-name>] [-R <address>] [-N <breakpoint-name>] [-f <filename>] [-L <source-language>] [-s <shlib-name>] [-K <boolean>]
  breakpoint set [-DHd] -S <selector> [-G <boolean>] [-C <command>] [-c <expr>] [-i <count>] [-o <boolean>] [-q <queue-name>] [-t <thread-id>] [-x <thread-index>] [-T <thread-name>] [-R <address>] [-N <breakpoint-name>] [-f <filename>] [-L <source-language>] [-s <shlib-name>] [-K <boolean>]
  breakpoint set [-DHd] -M <method> [-G <boolean>] [-C <command>] [-c <expr>] [-i <count>] [-o <boolean>] [-q <queue-name>] [-t <thread-id>] [-x <thread-index>] [-T <thread-name>] [-R <address>] [-N <breakpoint-name>] [-f <filename>] [-L <source-language>] [-s <shlib-name>] [-K <boolean>]
  breakpoint set [-DHd] -r <regular-expression> [-G <boolean>] [-C <command>] [-c <expr>] [-i <count>] [-o <boolean>] [-q <queue-name>] [-t <thread-id>] [-x <thread-index>] [-T <thread-name>] [-R <address>] [-N <breakpoint-name>] [-f <filename>] [-L <source-language>] [-s <shlib-name>] [-K <boolean>]
  breakpoint set [-DHd] -b <function-name> [-G <boolean>] [-C <command>] [-c <expr>] [-i <count>] [-o <boolean>] [-q <queue-name>] [-t <thread-id>] [-x <thread-index>] [-T <thread-name>] [-R <address>] [-N <breakpoint-name>] [-f <filename>] [-L <source-language>] [-s <shlib-name>] [-K <boolean>]
  breakpoint set [-ADHd] -p <regular-expression> [-G <boolean>] [-C <command>] [-c <expr>] [-i <count>] [-o <boolean>] [-q <queue-name>] [-t <thread-id>] [-x <thread-index>] [-T <thread-name>] [-N <breakpoint-name>] [-f <filename>] [-m <boolean>] [-s <shlib-name>] [-X <function-name>]
  breakpoint set [-DHd] -E <source-language> [-G <boolean>] [-C <command>] [-c <expr>] [-i <count>] [-o <boolean>] [-q <queue-name>] [-t <thread-id>] [-x <thread-index>] [-T <thread-name>] [-N <breakpoint-name>] [-O <type-name>] [-h <boolean>] [-w <boolean>]
  breakpoint set [-DHd] -P <python-class> [-k <none>] [-v <none>] [-G <boolean>] [-C <command>] [-c <expr>] [-i <count>] [-o <boolean>] [-q <queue-name>] [-t <thread-id>] [-x <thread-index>] [-T <thread-name>] [-N <breakpoint-name>] [-f <filename>] [-s <shlib-name>]
  breakpoint set [-DHd] -y <linespec> [-G <boolean>] [-C <command>] [-c <expr>] [-i <count>] [-o <boolean>] [-q <queue-name>] [-t <thread-id>] [-x <thread-index>] [-T <thread-name>] [-R <address>] [-N <breakpoint-name>] [-m <boolean>] [-s <shlib-name>] [-K <boolean>]

       -A ( --all-files )
            All files are searched for source pattern matches.

       -C <command> ( --command <command> )
            A command to run when the breakpoint is hit, can be provided more
            than once, the commands will get run in order left to right.

       -D ( --dummy-breakpoints )
            Act on Dummy breakpoints - i.e. breakpoints set before a file is
            provided, which prime new targets.

     // 还有很多参数,这里不一一贴出来了。
5. 对包含字符串的符号设置断点

例如 👉 使用breakpoint set -n,对touchesBegan方法设置断点👇

breakpoint set -n touchesBegan

并没有设置成功,因为完整的符号是touchesBegan:withEvent:,此时应该使用命令👇

breakpoint set -r touchesBegan:

此时就设置成功了!

还可以使用breakpoint set --selector,对项目内指定名称的selector设置断点👇

breakpoint set --selector touchesBegan:withEvent:
6. 指定文件设置断点

使用breakpoint set --file,在指定文件内设置断点👇

breakpoint set --file ViewController.m --selector touchesBegan:withEvent:

没有成功,why?因为我们的VieWController中没定义touchesBegan:withEvent:方法,我们加上该方法,再看看👇

果然成功了!

7. lldb强大的缩写功能

对包含touchesBegan的符号设置断点👇

b -r touchesBegan

查看断点的列表的简写👇

br list

disable禁用断点的简写👇

br dis 1

当然,enable启用断点的简写👇

br en 1

三、代码执行

除了使用lldb指令设置断点外,常用的还可以进行代码执行👇

  1. expression指令,用于执行代码,缩写指令为p、exp
  2. po指令,意思是print object,用于打印对象,本质上调用了对象的description

案例演示

1. 使用expression指令

首先,对ViewController中的touchesBegan设置断点👇

然后点击屏幕,进入断点,使用expression指令执行代码👇

expression self.view.subviews
2. 设置背景色

接着上面的调试👇

expression self.view.backgroundColor = [UIColor redColor]

报错,无法直接修改backgroundColor属性。那么,我们换一种方式,修改layer下的backgroundColor属性👇

expression self.view.layer.backgroundColor = [UIColor yellowColor].CGColor

成功!

3. 对数组追加元素

首先,打开ViewController.m文件,写入以下代码👇

#import "ViewController.h"

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;

@end

@implementation Person

- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    if (self = [super init]) {
        self.name = name;
        self.age = age;
    }
    return self;
}

@end

@interface ViewController ()
@property(nonatomic, strong) NSMutableArray<Person *> * models;
@end

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   
   Person * p1 = [[Person alloc] initWithName:@"one" age:1];
   Person * p2 = [[Person alloc] initWithName:@"two" age:2];
   Person * p3 = [[Person alloc] initWithName:@"three" age:3];
    
   [self.models addObject:p1];
   [self.models addObject:p2];
   [self.models addObject:p3];
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  NSLog(@"models:%@",self.models);
}

-(NSMutableArray<Person *> *)models {
   if (!_models) {
       _models = [NSMutableArray array];
   }
   return _models;
}

@end

然后,我们对ViewController中的touchesBegan设置断点👇

br set -f ViewController.m -r touchesBegan

接着,点击屏幕,进入touchesBegan方法的断点,追加数组元素👇

p [self.models addObject:[[Person alloc] initWithName:@"haha" age:4]]

使用c(continue)指令,继续执行👇

上图可见,输出的models数组中存储了4个对象,说明元素追加成功

4. 修改数组中对象的属性

接着上面的案例,点击屏幕,进入touchesBegan方法的断点,获取数组的第一个元素👇

p (Person *)self.models.firstObject

其中,$0为标号,代表Person对象,可以使用。

接着,我们使用标号,修改对象name属性👇

p $0.name=@"123"

验证name属性是否修改成功👇

po self.models.firstObject
p ((Person *)0x600001ac2c40).name
5. 执行多行代码

点击屏幕,进入touchesBegan方法的断点,通过Option+Enter进行换行,在lldb控制台写入以下代码👇

p Person * $tmp = self.models.firstObject;
p $tmp.name = @"Zang";
p $tmp.age = 18;
6. 其他流程控制的指令
  • 使用c(continue)指令,继续执行(上面案例中使用过)
  • 使用n(next)指令,单步运行,将子函数当做整体一步执行
  • 使用s(step into)指令,单步运行,遇到子函数会进去

四、堆栈信息

1. 查看函数调用栈

使用bt指令,查看函数调用栈👇

其中

  • *符号 👉 指向当前的函数调用栈👇

使用up指令,查看上一个函数👇

image.png

同时*也指向上一个函数👇

同理,使用down指令,查看下一个函数

使用frame select指令,选择指定函数

frame select 10

使用上述指令,可以将断点定位到指定函数。它的作用可以查看函数的调用者,通过汇编代码分析参数的传递。但寄存器的环境并不会发生变化,数据保存的还是最后一个函数执行完毕的结果。

2. 查看方法的参数和局部变量

首先修改ViewController.m中的代码👇

#import "ViewController.h"

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   [self lalala1:@"HAHA"];
}

-(void)lalala1:(NSString *)str{
   [self lalala2:str];
}

-(void)lalala2:(NSString *)str{
   NSString *str1 = @"Zang";
   NSLog(@"%@:%@",str1, str);
}

@end

然后,点击屏幕,进入lalala2方法的断点,使用frame variable指令,查看方法的参数和局部变量👇

在逆向过程中,进入一个方法,最想看到的就是该方法的调用者、方法名称、参数等信息,此时我们就可以使用frame variable指令,再配合up、down、frame select指令,查看调用栈中其他方法的信息。

3. 修改方法的参数

接着上述案例,断点继续进入lalala1方法中,使用frame variable👇

然后使用p指令,修改str的值👇

p str = @"COOL"

使用c指令,继续运行👇

上图可知,输出结果变为修改后的内容。

⚠️注意:只针对当前未执行完的方法有效。对于已经执行完的方法,修改里面的内容,并不影响最终的结果。

4. 让方法直接返回,不执行里面的代码

修改ViewController.m中的代码👇

#import "ViewController.h"

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   [self check:@"HOOK"];
   NSLog(@"一切正常~");
}

-(void)check:(NSString *)str{
   
   if([str isEqualToString:@"HOOK"]){
       NSLog(@"有人HOOK我...");
       exit(0);
   }
}

@end

然后点击屏幕,进入check方法的断点👇

接着使用thread return指令,让check方法直接返回,不执行里面的代码👇

thread return

使用frame variable指令,查看thread return指令执行后的函数👇

此时已经回到touchesBegan:withEvent:方法,然后使用c指令,继续执行👇

综上,使用thread return,绕过了check方法。那什么场景才需要这么调试呢👇

原本执行到某方法,一进入就会中断。使用thread return指令绕过方法,如果可以正常执行,证明此方法为检测方法。后续可针对不同情况,选择Method Swizzle、fishHook、InlineHook对其进行HOOK,将方法直接return

五、内存断点

1. 在对象的属性上设置断点

回到Person那个案例,我们修改person对象的属性👇

- (void)viewDidLoad {
   [super viewDidLoad];

    Person * p1 = [[Person alloc] initWithName:@"one" age:1];
    p1.name = @"new";
}

在p1对象的name上设置断点,使用watchpoint指令👇

watchpoint set variable p1->_name

使用c指令,继续运行👇

使用po指令

在逆向开发中,此案例使用的场景👇
当调用name属性的get/set方法,都会触发此断点。可获取到name属性的原始值,和即将修改的值。配合bt指令,查看函数调用栈,可以跟踪name属性的修改是由哪个方法触发的

2. 对属性地址设置内存断点

还是使用上面的案例,进入断点,获取name属性的地址👇

p &p1->_name

使用watchpoint指令,对属性地址设置内存断点👇

watchpoint set expression 0x00006000028cb4c8

使用c指令,继续运行👇

使用po指令👇

六、其它指令

1. 当分组下断点被触发,自动执行指令

还原Person案例中ViewDidLoad的代码👇

- (void)viewDidLoad {
   [super viewDidLoad];

    Person * p1 = [[Person alloc] initWithName:@"one" age:1];
    Person * p2 = [[Person alloc] initWithName:@"two" age:2];
    Person * p3 = [[Person alloc] initWithName:@"three" age:3];
    
    [self.models addObject:p1];
    [self.models addObject:p2];
    [self.models addObject:p3];
}

touchesBegan方法设置断点👇

br set -f ViewController.m -r touchesBegan:

分组1断点,设置进入断点后的执行的指令👇

br command add 1

使用c指令,继续运行

再次点击屏幕,进入touchesBegan方法的断点,同时输出以下信息👇

2. 当任何断点被触发,自动执行指令
target stop-hook add -o "frame variable"

接着查看stop-hook的指令列表👇

target stop-hook list

删除某一条指令

target stop-hook delete 1

删除全部指令

target stop-hook delete

禁用某一条指令

target stop-hook disable 1

启用某一条指令

target stop-hook enable 1

添加执行代码👇

display self.view

等同于expr -- self.view

使用该案例的场景👇

对于frame variable指令,基本上每个断点触发后都要使用。

lldb每次重新启动后,必须要重新配置上述所有命令,这样才能生效,那有没有一劳永逸的方法呢?

3. 配置lldb初始化文件

根目录下,存储了lldb的初始化文件👇

cd ~
ls -all

除了.lldb文件外,还有一个.lldbinit文件,它的作用是当lldb启动,就会加载此文件,执行文件内的指令,那么我们可以使用vi ~/.lldbinit,写入以下指令👇

target stop-hook add -o "frame variable"

运行项目,lldb启动,输出以下内容👇

Stop hook #1 added.

接着进入viewDidLoad断点,就会输出以下内容👇

(ViewController *) self = 0x0000000135f093a0
(SEL) _cmd = "viewDidLoad"

这个就是一劳永逸!🍺🍺🍺🍺🍺🍺

总结

  1. 断点设置
  • breakpoint set -n xxx:对方法/函数名称设置断点
  • breakpoint set -r xxx:对包含字符串的符号设置断点
  • breakpoint set --selector xxx:对项目内指定名称的selector设置断点
  • breakpoint set --file xxx:在指定文件中设置断点
  • breakpoint list:查看断点列表
  • breakpoint disable:禁用断点
  • breakpoint enable:启用断点
  • breakpoint delete:删除断点
  • 缩写:breakbr,设置断点可缩写指令:b
  1. 代码执行
  • po指令:意思是print object,用于打印对象,本质上调用了对象的description
  • expression指令:用于执行代码
    ◦ 缩写:expp
    ◦ 可以使用标号
    ◦ 可执行多行代码
  • 流程控制
    ccontinue)指令:继续执行
    nnext)指令:单步运行,将子函数当做整体一步执行
    ni指令:单步运行汇编级别
    s指令:单步运行,遇到子函数会进去
    si指令:单步运行可跳转指令内部,汇编级别
    finish指令:直接走完当前方法,返回到上层frame
  1. 堆栈信息
  • bt指令:查看函数调用栈
  • up指令:查看上一个函数
  • down指令:查看下一个函数
  • frame select指令:选择指定函数
  • frame variable指令:查看方法调用者、方法名称、参数和局部变量
  • thread return指令:让方法直接返回,不执行里面的代码
  1. 内存断点:
  • watchpoint指令,设置内存断点
  1. 其他指令
  • br command add指令:给断点添加命令的命令
  • target stop-hook add -o "xxx":每次stop的时候去执行一些命令,针对breadpointwatchpoint
  • 配置lldb初始化文件,当lldb启动,就会加载此文件,执行文件内的指令,一劳永逸
  1. lldb更多文档
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容

  • lldb(Low Lever Debug):默认内置于Xcode中的动态调试工具。标准的lldb提供了一组广泛的命...
    帅驼驼阅读 1,033评论 1 3
  • 一、概述 LLDB全称 [ Low Level Debugger ], 默认内置于Xcode中的动态调试工具。标准...
    Superman168阅读 15,234评论 2 19
  • 一、什么是动态调试? 答:将程序运行起来,通过打断点、打印等方式,查看参数,返回值,函数调用流程等 二、Xcode...
    IIronMan阅读 889评论 0 2
  • LLDB(Low Lever Debug): 默认内置于Xcode中的动态调试工具。标准的 LLDB 提供了一组广...
    Code_人生阅读 851评论 0 2
  • LLDB指令的基本格式 对应着 例如给函数test设置断点: help指令 help指令可以帮助我们快速查找LLD...
    浪的出名阅读 800评论 0 0