Skip to content
ShenYj edited this page Apr 11, 2022 · 8 revisions

LLDB 常用调试指令

.

简介

The LLDB Debugger

通过 LLDB 我们可以对 App 进行动态调试,LLDB 内置于 Xcode中,在真机运行后,会在手机上部署 Debug Server,通过 LLDB -- debugserver 建立通信。 新的 iPhone 在没有进行过真机调试前,手机上是不会存在 debugserver

  • debugserver 路径

    • Mac Xcode 下存在路径 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/15.2/DeveloperDiskImage.dmg/usr/bin/debugserver

    • iPhone 下路径
      Develop/usr/bin

常用命令

help

任何命令行工具类的工具都需要这样一个帮助入口, 帮助我们快速熟悉一个新的指令, 使用格式如下:

help expression
help breakpoint

LLDB 包含很多工具,区别与我们使用的某些固定工具直接 xxx -h/--help

print

可以使用printprinpri或者p, 但是不能使用pr, 因为LLDB不能消除和process的歧义 p/po

po: 输出值 或者 对象的地址
p: 输出值 + 值类型 + 引用名 + 内存地址

通过打印相同的实例对象p对比

(lldb) p p
(Person *) $5 = 0x0000000100550b50
(lldb) po p
<Person: 0x100550b50>

结果中有个$5, 任何以$符开头的东西都是存在于LLDB的命名空间的, 它们是为了帮助我们进行调试而存在的. 除此之外, p还支持进制转换, 默认p是十进制打印

  • p/t

    二进制打印

    e.g.

    (lldb) p/t p
    (Person *) $1 = 0b0000000000000000000000000000000100000000010101010000101101010000
  • p/c

    数字转十进制字符

    (lldb) p/c 66
    (int) $9 = B\0\0\0
  • p/c

    打印以空中指的字符串(以\0结尾的字符串)

  • p/x

    十六进制打印

    (lldb) p/x 100
    (int) $7 = 0x00000064
    (lldb) p/x (float)100.0
    (float) $9 = 0x42c80000
    (lldb) p/x (double)100.0
    (double) $10 = 0x4059000000000000
  • p/o

    八进制打印

    (lldb) p/o 100
    (int) $6 = 0144
  • p/d

    字符转十进制数字

    (lldb) p/d 'A'
    (char) $8 = 65

expression

在运行时修改环境中的某个值, 这非常有用, 配合断点调试, 帮助我们 mock 一些场景

e.g.

expression count = 18

通常简写为e, 我的习惯是expr

如果设置了选项,可以通过 -- 连接表达式, -- 作为分隔,如果没有选项 -- 可以省略

  • e.g. 比如常见的 NSLog 打印对象信息,等同于 expression -O -- 对象, 一般直接使用 po 对象 即可
  • e.g. expression -- self.view.backgroundColor = [UIColor redColor] 这里的 -- 可以省略

除了完整的使用 expression 命令,还可以用 callprintp 替代,效果是一样的

更新UI

通过执行以下命令, 可以在控制台快速查看视图层级

(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]

一些断点调试场景下, 可以在LLDB环境下完成

e.g. 例如在断点下通过在控制台下同构LLDBexpression指令调整了某个UIView控件的背景色, 直接执行一下指令来刷新UI

(lldb) e (void)[CATransaction flush]

补充: 只有程序在继续运行之后才会看到界面的变化. 因为改变的内容必须被发送到渲染服务中,然后显示才会被更新. 渲染服务实际上是一个另外的进程(被称作backboardd).这就是说即使我们正在调试的内容所在的进程被打断了, backboardd也是继续运行着的. 因此我们在断点情况下通过LLDB执行上面的命令,实现了UI的更新操作.

memory read

查看当前对象的内存情况, 简写x

e.g. p是实例化的一个对象指针

(lldb) x p
0x100550b50: 8d 22 00 00 01 80 1d 00 78 10 00 00 01 00 00 00  ."......x.......
0x100550b60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

Tips:

  • iOS 采用小端存储
  • 上面输出结果中,最左侧一列显示的是内存地址,右侧是十六进制数据 (也就是每一小段2个十六进制就是1 字节),每一行地址从左至右递增
    • 那么比如 22这组值的地址就是 0x100550b50 + 10x100550b51

LLDB 现在支持 GDB 速记格式语法,但是命令后不能有空格 GDB 到 LLDB 命令映射

  • x/4xg

    (lldb) x/4xg p
    0x100550b50: 0x001d80010000228d 0x0000000100001078
    0x100550b60: 0x0000000000000000 0x0000000000000000

    完整的lldb命令是: memory read --size 8 --format x --count 4 p

    参数解读: 从地址 p上 读取内存并显示 4 个8字节(g) 以十六进制(x) 展示

    按照GDB的格式: x/nfu addr(memory read/个数+格式+每一个的字节数 内存地址)

    GDB - Examining Memory - x/nfu addr
    n, f, and u are all optional parameters that specify how much memory to display and how to format it; addr is an expression giving the address where you want to start displaying memory. If you use defaults for nfu, you need not type the slash ‘/’. Several commands set convenient defaults for addr.
    
    n, the repeat count
    The repeat count is a decimal integer; the default is 1. It specifies how much memory (counting by units u) to display. If a negative number is specified, memory is examined backward from addr.
    
    f, the display format
    The display format is one of the formats used by print (‘x’, ‘d’, ‘u’, ‘o’, ‘t’, ‘a’, ‘c’, ‘f’, ‘s’), ‘i’ (for machine instructions) and ‘m’ (for displaying memory tags). The default is ‘x’ (hexadecimal) initially. The default changes each time you use either x or print.
    
    u, the unit size
    The unit size is any of
    
        b Bytes.
    
        h Halfwords (two bytes).
    
        w Words (four bytes). This is the initial default.
    
        g Giant words (eight bytes).
    
    • 4 : the repeat count

      • 打印4段, 否则需要肉眼分段查看, 对应全名参数为--count 4
    • x : the display format

      • 每一段的打印格式,以十六进制打印, 对应全名参数为--format x

        lldb --format 支持的格式有
        "default"
        'B' or "boolean"
        'b' or "binary"
        'y' or "bytes"
        'Y' or "bytes with ASCII"
        'c' or "character"
        'C' or "printable character"
        'F' or "complex float"
        's' or "c-string"
        'd' or "decimal"
        'E' or "enumeration"
        'x' or "hex"
        'X' or "uppercase hex"
        'f' or "float"
        'o' or "octal"
        'O' or "OSType"
        'U' or "unicode16"
        "unicode32"
        'u' or "unsigned decimal"
        'p' or "pointer"
        "char[]"
        "int8_t[]"
        "uint8_t[]"
        "int16_t[]"
        "uint16_t[]"
        "int32_t[]"
        "uint32_t[]"
        "int64_t[]"
        "uint64_t[]"
        "float16[]"
        "float32[]"
        "float64[]"
        "uint128_t[]"
        'I' or "complex integer"
        'a' or "character array"
        'A' or "address"
        "hex float"
        'i' or "instruction"
        'v' or "void"
        'u' or "unicode8"
        

        比如小写十六进制阅读不舒服,想要换成大写,就可以写成memory read --size 8 --format X --count 4 obj
        但是要注意 lldb的命令参数和gdb的参数并不通用

    • g : Words (four bytes). This is the initial default.

      • 每一段的大小 4个字节, 等同于 --size 8
      • 对应参数 --size <byte-size>
        • b 1个字节
        • h 2个字节
        • w 4个字节
        • g 8个字节

iOS为小端存储模式, 数据的高字节存储在高地址中,数据的低字节存储在低地址
对比xx/4xg结果,x/4xg是按照正常的书写格式输出, 低位在右, 高位在左, 阅读和使用更加方便

thread

  • thread backtrace

    查看线程和堆栈框架描述的信息

    也可以使用bt查看线程信息

    在结果中可以看到很多 frame, 代表一帧,一般一帧就代表一个函数调用

  • thread backtrace all可以使用bt all

  • thread return

    让函数直接返回某个值,不会执行断点后面的代码

  • thread continue、 continue、 c

    程序继续运行

  • thread step-over、next、n

    单步运行,把子函数当做整体一步执行

  • thread step-in、step、s

    单步运行,遇到子函数会进入子函数

  • thread step-out、finish

    直接执行完当前函数的所有代码,返回到上一个函数

  • thread step-inst-over、nexti、ni

    汇编指令,执行一句汇编指令

  • thread step-inst、stepi、si

    汇编指令

  • si、nis、n 类似

    • s、n 是源码级别
    • si、ni 是汇编指令级别

frame

  • frame variable

    打印当前栈帧的变量, 不指定变量就是打印全部

breakpoint

代码断点

  • breakpoint set

    设置断点

    • breakpoint set -a + 函数地址
    • breakpoint set -n + 函数名

    e.g.

    • breakpoint set -n test
    • breakpoint set -n touchedBegan:withEvent:
    • breakpoint set -n "-[ViewController touchesBegan:withEvent:]"
  • breakpoint list

    列出所有的断点(每个断点都有自己的编号)

  • breakpoint disable + 断点编号

    禁用断点

  • breakpoint enable + 断点编号

    启用断点

  • breakpoint delete + 断点编号

    删除断点

  • breakpoint command add + 断点编号

    给断点预先设置需要执行的命令,到触发断点时,就会按顺序执行

  • breakpoint command list + 断点编号

    查看某个断点设置的命令

  • breakpoint command delete + 断点编号

    删除某个断点设置的命令

watchpoint

内存断点,在内存数据发生改变的时候触发

  • watchpoint set variable + 变量

    e.g. watchpoint set variable self->_age

    这里需要注意是直接设置的成员变量,而不是 self.age,当值改变时就会断到属性声明处

  • watchpoint set expression + 地址

    e.g. watchpoint set expression &(self->_age)

除此之外,与 breakpoint 一样,下面的指令具有相似的功能

  • watchpoint list
  • watchpoint disable + 断点编号
  • watchpoint enable + 断点编号
  • watchpoint delete + 断点编号
  • watchpoint command add + 断点编号
  • watchpoint command list + 断点编号
  • watchpoint command delete + 断点编号

image

  • image lookup

    模块查找

    • image lookup -t + 类型

      查找某个类型的信息

      e.g. image lookup -t NSInteger, 就会显示占用大小,所在头文件等信息

    • image lookup -a + 地址

      根据内存地址查找在模块中的位置,可以在崩溃时用来快速定位

    • image lookup -n + 符号或者函数名

      查找某个符号或者函数的位置

  • image list

    列出所加载的模块信息

    • image list -o -f

      打印出模块的偏移地址、全路径

外链

Getting Started

Social

Clone this wiki locally