前言

本文为Advanced Apple Debugging & Reverse Engineering的第一章阅读笔记,
有不正之处望指正

基础

关闭Rootless模式

  • 重启电脑,启动后长按Commond+R
  • 进入控制台
  • 输入 csrutil disable; reboot
  • 不久后就会重启电脑

File

1
2
$ lldb /Projects/Sketch/build/Debug/Sketch.app
>> Current executable set to '/Projects/Sketch/build/Debug/Sketch.app' (x86_64)

等同于

1
2
3
$ lldb
(lldb) file /Projects/Sketch/build/Debug/Sketch.app
>> Current executable set to '/Projects/Sketch/build/Debug/Sketch.app' (x86_64).

process

  • launch 启动执行目录的程序

image

  • lookup

    • -n NAME 查找具体代码

      1
      (lldb) image lookup -n "-[UIViewController viewDidLoad]"
    • -rn NAME 查找所有符合NAME规则的符号。符号可以正则,所以说符合规则

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      (lldb) image lookup -rn test
      #Swift
      (lldb) image lookup -rn Signals.SwiftTestClass.name.setter
      2 matches found in /Users/derekselander/Library/Developer/Xcode/DerivedData/Signals-bqrjxlceauwfuihjesxmgfodimef/Build/Products/Debug-iphonesimulator/Signals.app/Signals:
      Address: Signals[0x000000010000aba0] (Signals.__TEXT.__text +38704)
      Summary: Signals`@objc Signals.SwiftTestClass.name.setter :raywenderlich.com 47
      Advanced Apple Debugging Chapter 4: Stopping in Code Swift.ImplicitlyUnwrappedOptional<Swift.String> at SwiftTestClass.swift
      Address: Signals[0x000000010000ac60] (Signals.__TEXT.__text + 38896)
      Summary: Signals`Signals.SwiftTestClass.name.setter :Swift.ImplicitlyUnwrappedOptional<Swift.String> at SwiftTestClass.swift
      #Mark 一下这边出现了两个,是因为这个工程既有Swift代码也有OC代码,苹果为了做桥接加了第一个后续可以无视

Breakpoint

  • 设置断点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 简写的形式
    (lldb) b -[UIViewController viewDidLoad]
    Breakpoint 1: where = UIKit`-[UIViewController viewDidLoad], address = 0x0000000102bbd788
    //如果想在某个文件中的某行设置一个断点,可使用以下命令:
    (lldb) breakpoint set --file foo.c --line 12
    //如果想给某个函数设置断点,可使用以下命令:
    (lldb) breakpoint set --name foo
    //如果想给C++中所有命名为foo的方法设置断点,可以使用以下命令:
    (lldb) breakpoint set --method foo
    # 下面两者等价
    (lldb) b Breakpoints.SwiftTestClass.name.setter :Swift.ImplicitlyUnwrappedOptional<Swift.String>
    (lldb) rb SwiftTestClass.name.setter
    # 部分正则参考
    (lldb) rb name\.setter // image lookup后给所有匹配上name.setter的打上断点
    (lldb) rb '\-\[UIViewController\ ' // 给这个类的所有方法打上断点
    (lldb) rb '\-\[UIViewController(\(\w+\))?\ ' // 给这个类的所有Category打上断点(没有验证)
    (lldb) rb . -f DetailViewController.swift // 给这个文件内的所有方法打上断点
    (lldb) rb . //给所有的方法打上断点 很危险
    (lldb) rb . -s UIKit // 只断点UIKit库里面的所有方法
    # 特殊用法
    ^(@).* 这个需要考证是用来屏蔽上面说的桥接问题
  • 其他操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 断点列表
    (lldb) breakpoint list 1
    1: name = 'main', locations = 20, resolved = 20, hit count = 0
    1.1: where = Breakpoints`main + 22 at AppDelegate.swift:12, address =0x00000001057676e6, resolved, hit count = 0
    1.2: where = Foundation`-[NSThread main], address = 0x000000010584d182,resolved, hit count = 0
    1.3: where = Foundation`-[NSBlockOperation main], address =0x000000010585df4a, resolved, hit count = 0
    ...
    # 断点删除
    (lldb) breakpoint delete 1
    (lldb) breakpoint delete 1.1 这个准确的说是disable并不是真正意义上的删除

Expression & po & p

po = expression -O –

Note: lldb的Context的问题,如果当前代码是在OC下的话要用OC语法,如果是在Swift的环境下要用Swift代码

1
2
3
4
5
6
7
8
# Swift环境下
(lldb) po [UIApplication sharedApplication] # 不能这么使用
(lldb) expression -l objc -O -- [UIApplication sharedApplication]
(lldb) po UIApplication.shared
# OC环境下
(lldb) po $R0.title
(lldb) expression -l swift -- $R0.title

创建实例的时候要在实例名字前面加$符号,不然不能够正常使用

1
2
3
4
5
6
7
8
9
10
# 反例
(lldb) po id test = [NSObject new]
(lldb) po test
error: use of undeclared identifier 'test'
# 正确用法
(lldb) po id $test = [NSObject new]
(lldb) po $test
<NSObject: 0x60000001d190>
(lldb) expression -l swift -O -- $test

细节:在用Expression调用方法的时候,设置的Breakpoint是不会被主动触发的。

1
2
3
4
# 不会被触发
(lldb) expression -l swift -O -- $R0.viewDidLoad()
# 会被触发
(lldb) expression -l swift -O -i 0 -- $R0.viewDidLoad()

Thread, Frame & Step

  • Thread & Frame

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 打印当前栈内情况
    (lldb) thread backtrace
    # 打印栈顶信息
    (lldb) frame info
    frame #0: 0x00000001075d0ae0 Signals`MasterViewController.viewWillAppear(animated=<invalid> (0xd1),self=0x00007fff5862dac0) -> () at MasterViewController.swift:47
    # 根据数据选择栈
    (lldb) frame select 1
    # 打印当前所在栈块内的实例
    (lldb) frame variable
    # 打印self 符号的所有实例
    (lldb) frame variable -F self
  • Step

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 重新启动程序
    (lldb) run
    # 继续
    (lldb) continue
    # 断点下一步 step over
    (lldb) next
    # 进入函数下一步 step into
    (lldb) step
    # 跳出当前函数 step out
    (lldb) finish

TypeFormat

参考链接

Customizing Commands

  1. LLDB支持自定义命令行操作 (著名的chisel就是使用这种方法进行编写的)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 这个命令相比大家都不陌生,是用来打印当前的视图结构的
    (lldb) po [[[[[UIApplication sharedApplication] keyWindow] rootViewController] view] recursiveDescription]
    # 自定义命令(运行时替换)
    # Note:如果想全局替换可以把这个写入~/.lldbinit里面即可
    (lldb) command alias -- Yay_Autolayout expression -l objc -O -- [[[[[UIApplication sharedApplication] keyWindow] rootViewController] view] recursiveDescription]
    # 下面的调用等价于第一个
    (lldb) Yay_Autolayout

正则表达

  • LLDB的正则实现原理是基于Shell的SED命令,详细可以参考sed命令
    1
    2
    (lldb) command regex rlook 's/(.+)/image lookup -rn %1/'
    (lldb) rlook viewDidLoad

简写

c = continue
ex = expression
b = breakpoint
n = next

杂项

  • $ ttys //查找当前终端的名称
    1
    2
    ~ $ tty
    >> /dev/ttys027