五、iOS逆向之《LLDB》

前言

LLDB是我们平时调试中使用最多的工具之一,p或者po是使用最多的指令。除了ppo之外,还有什么指令可以使用呢?今天再次来提升一下对LLDB的使用吧。

一、LLDB

LLDB是个开源的内置于XCode的具有REPL(read-eval-print-loop)特征的Debugger,其可以安装C++或者Python插件。在日常的开发和调试过程中给开发人员带来了非常多的帮助。

二、设置断点(breakPoint set)


# breakpoint 设置断点指令
# set 是子指令,表示设置
# -n 是选项,是name的缩写。
# XXX 方法名称
(lldb) breakpoint set -n “XXX” “XXX”

# 举个栗子🌰
# 可以这么用,给名字为“viewDidLoad”的方法添加断点
(lldb) breakpoint set -n "viewDidLoad"
# 还可以这么用,给控制器“ViewController”中的“viewDidLoad”方法添加断点
(lldb) breakpoint set -n "-[ViewController viewDidLoad]"

试试


(lldb) breakpoint set -n "viewDidLoad" #设置断点
Breakpoint 1: 104 locations. #成功设置一组104个断点
(lldb) breakpoint list #查看所有断点
Current breakpoints: #这里打印了所有有关viewDidLoad的断点。
1: name = 'viewDidLoad', locations = 104, resolved = 104, hit count = 0
  1.1: where = LLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:18, address = 0x000000010028b764, resolved, hit count = 0 
  1.2: where = DocumentManager`-[UIDocumentBrowserViewController viewDidLoad], address = 0x0000000103921e4d, resolved, hit count = 0 
  1.3: where = DocumentManager`-[DOCInfoViewController viewDidLoad], address = 0x000000010392c7da, resolved, hit count = 0 
  ....

# 给控制器“ViewController”中的“viewDidLoad”方法添加断点
(lldb) breakpoint set -n "-[ViewController viewDidLoad]"
# 成功设置断点 位置是ViewController viewDidLoad 中 第18行的位置
Breakpoint 1: where = LLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:18, address = 0x00000001024c7764 
# 查看所有断点信息
(lldb) breakpoint list
Current breakpoints:
1: name = '-[ViewController viewDidLoad]', locations = 1, resolved = 1, hit count = 0
  1.1: where = LLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:18, address = 0x00000001024c7764, resolved, hit count = 0 

还有没有其他设置断点的方式呢?列举几个常用的。

  • 给所有名为xx的函数设置一个断点
# breakpoint 设置断点指令
# set 是子指令,表示设置
# -name 是选项,名称的意思
# xx 方法名称
(lldb) breakpoint set -name xx #完整写法
(lldb) br s -n xx #简单缩写
(lldb) b xx #变态缩写
  • 在文件F指定行L设置断点
# breakpoint  设置断点指令
# set 是子指令,表示设置
# -file 是子指令,表示文件
# F 表示文件的名称 
# -line 表示行号
(lldb) breakpoint set -file F -line L #完整写法
(lldb) br s -f F -l L #简单缩写
(lldb) b F:L #变态缩写
  • 给所有名为xx的C++函数设置一个断点(希望没有同名的C函数)
# breakpoint  设置断点指令
# set 是子指令,表示设置
# -method 是子指令,表示方法名
# xx 方法名称
(lldb) breakpoint set -method xx  #完整写法
(lldb) br s -M xx #简单缩写 没有变态缩写
  • 给一个OC函数[objc msgSend:]设置一个断点
# breakpoint  设置断点指令
# set 是子指令,表示设置
# -name 是子指令,表示文件
# objc 对象
#  msgSend 方法名称
(lldb) breakpoint set -name “[objc msgSend:]” #完整写法
(lldb) b -n “[objc msgSend:]” #简单缩写
  • 给所有名为xx的OC方法设置一个断点(希望没有名为xx的C或者C++函数)
# breakpoint  设置断点指令
# set 是子指令,表示设置
# -selector 是子指令,表示选择器
# xx 表示方法名称
(lldb) breakpoint set -selector xx
(lldb) br s -S count
  • 给所有函数名正则匹配成功的函数设置一个断点
(lldb) breakpoint set --func-regex regular-expression
(lldb) br s -r regular-expression
  • 给指定函数地址func_addr的位置设置一个断点
(lldb) br set -a func_addr
  • 断点查看
(lldb) breakpoint list
(lldb) br l
  • 断点删除
(lldb) breakpoint delete index
(lldb) br del index
  • 禁用断点
# index 为断点编号,可以通过 ‘breakpoint list’查看编号
(lldb)breakpoint disable index
  • 开启断点
# index 为断点编号,可以通过 ‘breakpoint list’查看编号
(lldb)breakpoint enable index

二、断点的流程控制

设置好断点后怎么控制前进、下一步呢?
Xcode已经为我们提供了可视化的工具,但是如果你习惯了命令行操作不希望双手离开键盘降低你的效率,了解一下也是很有帮助的。


流程控制可视化界面
  • 继续
(lldb) process continue # 完整写法
(lldb) continue # 简单缩写
(lldb) c # 变态缩写
  • 下一步
(lldb) thread step-over # 完整写法
(lldb) next  # 简单缩写
(lldb) n # 变态缩写
  • 进入 ,进入主要是进入函数中查看相关代码或者汇编代码
(lldb) thread step-in # 完整写法
(lldb) step # 简单缩写
(lldb) s # 变态缩写
  • 跳出 和进入相反
(lldb) thread step-out
(lldb) finish
(lldb) f

三、hook概念(stop-hook )

  • target stop-hook

我们知道,用LLDB debug,大多数时候需要让程序stop。
target stop-hook命令就是让你可以在每次stop的时候去执行一些命令
target stop-hook只对breakpoint和watchpoint的程序stop生效,直接点击Xcode上的pause或者debug view hierarchy不会生效
假如我们想在每次程序stop的时候,都用命令打印当前view。我们可以添加一个stop-hook:

(lldb) target stop-hook add -o "frame variable"
Stop hook #4 added.
  • target stop-hook add & display

target stop-hook add表示添加stop-hook,-o的全称是--one-liner,表示添加一条命令。

- Hook 1 (frame variable)

(ViewController *) self = 0x00007fd55b12e380

(SEL) _cmd = "viewDidLoad"

(NSMutableURLRequest *) request = 0x00007fd55b1010c0

在程序stop的时候,他会自动执行frame variable,打印出了所有的变量。

e.g: 下面2行代码效果相同

(lldb) target stop-hook add -o "p self.view"
(lldb) display self.view

  • target stop-hook list

当添加完stop-hook之后,我们想看当前所有的stop-hook怎么办呢?使用stop-hook list

(lldb) target stop-hook list
Hook: 4
  State: enabled
  Commands:
    frame variable

Hook: 5
  State: enabled
  Commands:
    expression self.view

Hook: 6
  State: enabled
  Commands:
    expr -- self.view

我们可以看到,我们添加了4个stop-hook,每个stop-hook都有一个id,他们分别是4,5,6

  • target stop-hook delete & undisplay

有添加的命令,当然也就有删除的命令。使用target stop-hook delete可以删除stop-hook,如果你觉得这个命令有点长,懒得敲。你也可以用undisplay

(lldb) target stop-hook delete 4
(lldb) undisplay 5

我们用target stop-hook delete和undisplay分别删除了id为4和5的stop-hook

  • target stop-hook disable/enable

当我们暂时想让某个stop-hook失效的时候,可以使用target stop-hook disable

(lldb) target stop-hook disable 8

如果我们想让所有的stop-hook失效,只需不传入stop-hookid即可:

(lldb) target stop-hook disable

有disable就有enable,我们又想让stop-hook生效了。可以使用target stop-hook enable

(lldb) target stop-hook enable 8

同理,不传入参数表示让所有stop-hook生效

(lldb) target stop-hook enable

四、image

当我们有一个地址,想查找这个地址具体对应的文件位置,可以使用image lookup --address,简写为image lookup -a e.g: 当我们发生一个crash。

2015-12-17 14:51:06.301 TLLDB[25086:246169] *** Terminating app due to uncaught exception ‘NSRangeException‘, reason: ‘*** -[__NSArray0 objectAtIndex:]: index 1 beyond bounds for empty NSArray‘
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010accde65 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x000000010a746deb objc_exception_throw + 48
    2   CoreFoundation                      0x000000010ac7c395 -[__NSArray0 objectAtIndex:] + 101
    3   TLLDB                               0x000000010a1c3e36 -[ViewController viewDidLoad] + 86
    4   UIKit                               0x000000010b210f98 -[UIViewController loadViewIfRequired] + 1198
    5   UIKit                               0x000000010b2112e7 -[UIViewController view] + 27

我们可以看到是由于-[__NSArray0 objectAtIndex:]:超出边界而导致的crash,但是objectAtIndex:的代码到底在哪儿呢?

# -a是-address的缩写
(lldb) image lookup -a 0x000000010a1c3e36
      Address: TLLDB[0x0000000100000e36] (TLLDB.__TEXT.__text + 246)
      Summary: TLLDB`-[ViewController viewDidLoad] + 86 at ViewController.m:32

根据0x000000010a1c3e36 -[ViewController viewDidLoad]里面的地址,使用image lookup -address查找,我们可以看到代码位置在ViewController.m里面的32行。

  • 还可以通过image查看类信息
(lldb) image lookup -t Persen
1 match found in /Users/chian/Library/Developer/Xcode/DerivedData/LLDB-culbvnuqlgmqubfafrwfnblamkly/Build/Products/Debug-iphonesimulator/LLDB.app/LLDB:
id = {0x10000002b}, name = "Persen", byte-size = 24, decl = ViewController.h:11, compiler_type = "@interface Persen : NSObject{
    int _age;
    NSString * _name;
}
@property ( getter = name,setter = setName:,readwrite,nonatomic ) NSString * name;
@property ( getter = age,setter = setAge:,assign,readwrite,nonatomic ) int age;
@end

  • 还可以通过(lldb) image lookup -t "method name"来定位方法位置。
(lldb) image lookup -n test1
1 match found in /Users/chian/Library/Developer/Xcode/DerivedData/LLDB-culbvnuqlgmqubfafrwfnblamkly/Build/Products/Debug-iphonesimulator/LLDB.app/LLDB:
        Address: LLDB[0x00000001000014e0] (LLDB.__TEXT.__text + 304)
        Summary: LLDB`-[ViewController test1] at ViewController.m:25

可以看到LLDB定位到test1在第25行
通过以上的命令完全可以看不出image到底是干嘛的!😝
所以暂时只能记得其拥有特殊功效。

  • 通过help可以看到更多命令,描述很详细的解释了各个命令的功效。想要了解每个功能,还需要平时多加练习和记忆。

(lldb) help image
# 用于访问一个或多个目标模块信息的命令。
     Commands for accessing information for one or more target modules.
# 语法:形象
Syntax: target modules <sub-command> ...
# “image”是“target modules”的缩写
The following subcommands are supported:

      add          -- Add a new module to the current target's modules.
      dump         -- Commands for dumping information about one or more target
                      modules.
      list         -- List current executable and dependent shared library
                      images.
      load         -- Set the load addresses for one or more sections in a
                      target module.
      lookup       -- Look up information within executable and dependent
                      shared library images.
      search-paths -- Commands for managing module search paths for a target.
      show-unwind  -- Show synthesized unwind instructions for a function.

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

五、register

register指令能够获取和修改各个寄存器的信息。
官方的解释是这样的:

     # 访问当前线程和堆栈帧寄存器的命令。
     Commands to access registers for the current thread and stack frame.
# 语法:寄存器[读|写]…
Syntax: register [read|write] ...
# 支持下列子命令:
The following subcommands are supported:
               # 从当前帧。如果没有指定寄存器,则将它们全部转储。
      read  -- Dump the contents of one or more register values from the
               current frame.  If no register is specified, dumps them all.
               # 修改单个寄存器值。
      write -- Modify a single register value.
# 要获得任何特定子命令的更多帮助,请键入“help <command> <subcommand>”。
For more help on any particular subcommand, type 'help <command> <subcommand>'.

我们可以通过register read来获取当前断点中寄存器中的内容:

(lldb) register read
General Purpose Registers:
       rax = 0x0000000108340cd8  (void *)0x0000000108340d28: __NSSetM
       rbx = 0x000000010763e010  libobjc.A.dylib`objc_release
       rcx = 0x82c1b42ba17f0088
       rdx = 0x82c1b42ba17f0088
       rdi = 0x00006000035be140
       rsi = 0x0000000000000001
       rbp = 0x00007ffee8ef0120
       rsp = 0x00007ffee8ef0120
        r8 = 0x000000000000003f
        r9 = 0x00006000035be140
       r10 = 0x00000000000007fb
       r11 = 0x00007f82b08240f0
       r12 = 0x000000010b48d44d  "touchesBegan:withEvent:"
       r13 = 0x00006000035be140
       r14 = 0x00007f82b050bf90
       r15 = 0x00007ffee8ef0130
       rip = 0x000000010763ed06  libobjc.A.dylib`objc_object::sidetable_release(bool) + 4
    rflags = 0x0000000000000202
        cs = 0x000000000000002b
        fs = 0x0000000000000000
        gs = 0x0000000000000000

具体怎么用我还没学到😆。见谅见谅

六、expression 指令

expression命令是执行一个表达式,并将表达式返回的结果输出,是LLDB调试命令中最重要的命令,也是我们常用的ppo 命令的 鼻祖。
他主要有2个功能:

  • 执行表达式
(lldb) expression self.view.backgroundColor = UIColor.yellowColor
(UICachedDeviceRGBColor *) $2 = 0x000060000208c700 // 修改值
(lldb) expression [CATransaction flush] // 直接刷新界面
  • 输出返回值
(lldb) expression self.view
(UIView *) $3 = 0x00007f82b040c670
  • 衍生出的扩展命令
    • p
    • print
    • e
    • call
    • po

其实都是expression的别名,甚至我们还可以自己设置别名。这里不展开描述了。

七、frame 参数检查

  • frame

  • frame 官方解释
(lldb) help frame
     # 用于选择和检查当前线程堆栈帧的命令。
     Commands for selecting and examing the current thread's stack frames.
  
# 语法:frame <subcommand> [<subcommand-options>]
Syntax: frame <subcommand> [<subcommand-options>]

The following subcommands are supported:
支持下列子命令:

                  # info——列出关于当前堆栈框架的信息线程的索引
      info     -- List information about the current stack frame in the current
                  thread.
                  # 选择——选择当前堆栈帧当前线程(参见“线程回溯”)。
      select   -- Select the current stack frame by index from within the
                  current thread (see 'thread backtrace'.)
                  # 变量——显示当前堆栈框架的变量。默认为所有作用域中的参数
                    和局部变量。参数的名字,可以是本地、文件静态和文件全局变量指定。
                    可以指定聚合变量的子变量如“var - > child.x”。
      variable -- Show variables for the current stack frame. Defaults to all
                  arguments and local variables in scope. Names of argument,
                  local, file static and file global variables can be
                  specified. Children of aggregate variables can be specified
                  such as 'var->child.x'.

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

frame 框架的意思,但它应解释成(帧)它能显示堆栈信息,
我们在控制台上输入命令bt,可以打印出来所有的frame。如果仔细观察,这些frame和左边红框里的堆栈是一致的。平时我们看到的左边的堆栈就是frame。


image.png
  • frame variable

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

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

可以看到,他将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 info

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,这样更方便

八、watchpoint

  • watchpoint

breakpoint有一个孪生兄弟watchpoint。如果说breakpoint是对方法生效的断点,watchpoint就是对地址生效的断点。
官方解释:


(lldb) help watchpoint
     Commands for operating on watchpoints.

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

The following subcommands are supported:

      command -- Commands for adding, removing and examining LLDB commands
                 executed when the watchpoint is hit (watchpoint 'commmands').
      delete  -- Delete the specified watchpoint(s).  If no watchpoints are
                 specified, delete them all.
      disable -- Disable the specified watchpoint(s) without removing it/them. 
                 If no watchpoints are specified, disable them all.
      enable  -- Enable the specified disabled watchpoint(s). If no watchpoints
                 are specified, enable all of them.
      ignore  -- Set ignore count on the specified watchpoint(s).  If no
                 watchpoints are specified, set them all.
      list    -- List all watchpoints at configurable levels of detail.
      modify  -- Modify the options on a watchpoint or set of watchpoints in
                 the executable.  If no watchpoint is specified, act on the
                 last created watchpoint.  Passing an empty argument clears the
                 modification.
      set     -- Commands for setting a watchpoint.

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

如果我们想要知道某个属性什么时候被篡改了,我们该怎么办呢?有人可能会说对setter方法打个断点不就行了么?但是如果更改的时候没调用setter方法呢? 这时候最好的办法就是用watchpoint。我们可以用他观察这个属性的地址。如果地址里面的东西改变了,就让程序中断。

watchpoint set命令用于添加一个watchpoint。只要这个地址中的内容变化了,程序就会中断。

  • watchpoint set variable
    通过这个命令可以为array对象设置观察点
(lldb) watchpoint set variable array
Watchpoint created: Watchpoint 1: addr = 0x7ffee04500e0 size = 8 state = enabled type = w
    declare @ '/Users/chian/Desktop/LLDB/LLDB/ViewController.m:30'
    watchpoint spec = 'array'
    new value: 0x0000600002474540

watchpoint set variable传入的是变量名。需要注意的是,这里不接受方法。
设置成功后可以通过watchpoint list 可以查看到所有被标记的watchpoint。
当我执行p array = @[@"a",@"b"]修改array后,watchpoint打印出了我旧值和新值。

# 查看所有观察点watchpoint
(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0x7ffee04500e0 size = 8 state = enabled type = w
    declare @ '/Users/chian/Desktop/LLDB/LLDB/ViewController.m:30'
    watchpoint spec = 'array'
    new value: 0x0000600002474540

# 修改变量值
(lldb) p array = @[@"a",@"b"]

Watchpoint 1 hit:
old value: 0x0000600002474540
new value: 0x000060000247fb40
error: Execution was interrupted, reason: watchpoint 1.
The process has been returned to the state before expression evaluation.

# 打印地址看到真实数据
(lldb) po 0x000060000247fb40
<__NSArrayI 0x60000247fb40>(
a,
b
)

除了以上指令之外,watchpoint当然也有和breakpoint一样的指令。
例如:

  • watchpoint disable
  • watchpoint enable
  • watchpoint delete

九 、thread

  • thread backtrace & bt

有时候我们想要了解线程堆栈信息,可以使用thread backtrace
thread backtrace作用是将线程的堆栈打印出来。
e.g: 当发生crash的时候,我们可以使用thread backtrace(线程回溯)查看堆栈调用 。
bt则是 backtrace 的缩写,输入bt等同于thread backtrace

线程回溯
  • thread return

Debug的时候,也许会因为各种原因,我们不想让代码执行某个方法,或者要直接返回一个想要的值。这时候就该thread return上场了
thread return可以接受一个表达式,调用命令之后直接从当前的frame返回表达式的值。

结语

以上篇幅介绍的只是冰山一角。希望这篇文章能够给大家一些帮助,来更多的了解LLDB

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

推荐阅读更多精彩内容

  • LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。平时用Xc...
    小笨狼阅读 20,430评论 31 187
  •   LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。平时用...
    Thinkdifferents阅读 1,498评论 1 4
  • [转]浅谈LLDB调试器文章来源于:http://www.cocoachina.com/ios/20150126/...
    loveobjc阅读 2,480评论 2 6
  • 一、概述 LLDB全称 [ Low Level Debugger ], 默认内置于Xcode中的动态调试工具。标准...
    Superman168阅读 15,179评论 2 19
  • 转载 与调试器共舞 - LLDB 的华尔兹: https://objccn.io/issue-19-2/ 推荐:i...
    F麦子阅读 3,325评论 0 10