美文网首页ios developersiOS
iOS调试之LLDB命令

iOS调试之LLDB命令

作者: 一眼万年的星空 | 来源:发表于2021-08-29 22:35 被阅读0次

    前言

    在iOSAPP开发中,我们的代码经常需要调试跟踪,最常用的是LLDB Debugger程序调试器,LLDB Debugger (LLDB) 是一个开源、底层调试器(low level debugger),具有REPL (Read-Eval-Print Loop,交互式解释器)、C++和Python插件,可以在terminal中使用。具有流向控制 (flow control) 和数据检查 (data inspection) 功能。

    常用LLDB命令简单操作

    image

    P和PO命令

    p 命令: print 命令的简写,使用p 命令可以查看基本数据类型的值;如果使用p命令查看的是对象,那么只会返回对象的指针地址。 p命令后面除了可以接变量、常量,还可以接表达式。
    po 命令:print object的缩写,可以理解为打印对象。功能与p命令类似,也可以打印常量、变量,打印表达式返回的对象等。p 和 po 的区别在于使用 po 只会输出对应的值,而 p 则会返回值的类型以及命令结果的引用名。

    P,PO指令
    在输出结果中有类似于0,1这样的符号,它是指向对象的一个引用,在控制面板中可以直接使用这个符号来操作对应的对象,它们存在于LLDB的全名空间中,目的是为了辅助调试。$后面的数值是递增的,每打印一个与对象相关的命令,这个值都会加1。以上是我们在开发中最为常用的指令,我就不过多叙述。

    使用p做进制转换

    //默认打印为10进制
    (lldb) p 10
    (int) $0 = 10
    //转16进制
    (lldb) p/x 10
    (int) $1 = 0x0000000a
    //转8进制
    (lldb) p/o 10
    (int) $2 = 012
    //转二进制
    (lldb) p/t 10
    (int) $3 = 0b00000000000000000000000000001010
    //字符转10进制数字
    (lldb) p/d 'A'
    (char) $4 = 65
    //10进制数字转字符
    (lldb) p/c 66
    (int) $5 = B\0\0\0
    

    再来看看我们不常用但是很有用的指令

    image寻址

    image 命令主要用于寻址,一般是通过各种组合命令实现不同功能。
    image list命令用来查看工程中使用的库:

    image list命令

    image lookup --address+地址

    该命令用于查找可执行文件或共享库的原始地址,当遇到程序崩溃时,可以使用这条命令来根据崩溃日志的原始地址查找崩溃所在的具体位置。


    image lookup --address+地址

    LLDB线程和帧状态检测

    在进程停止后,LLDB会选择一个当前线程和线程中当前帧(frame)。很多检测状态的命令可以用于这个线程或帧。

    1. thread list

    用于列出所有线程,如下图所示,其中星号(*)表示thread#1为当前线程。

    thread list

    2.thread backtrace堆栈打印,简写bt

    bt命令可以打印出线程的堆栈信息,bt命令是打印当前线程的堆栈信息,如下图所示。该信息比左侧的Debug Navigator 看到的还要详细一些。如果嫌堆栈打印太长,可以加一个值限制,如bt 10。

    thread backtrace堆栈
    bt all 命令可以打印所有线程的堆栈信息。

    thread return 跳出当前方法的执行

    thread return主要用于控制程序流程,如想要直接跳过执行某个方法,或者直接让某方法返回一个想要的值,thread return后有一个可选参数,在执行时它会把可选参数加载进返回寄存器里,然后立刻执行返回命令,跳出当前栈帧(如:numberOfSectionsInTableView:方法直接thread return 10,就可以直接跳过方法执行,返回10)。

    expression可简写为expr 计算以及生成一个表达式

    (lldb) expr (int)printf ("Print nine: %d.\n", 4 + 5) 
    Print nine: 9.
    (int) $0 = 15
    

    创建一个变量并分配值

    (lldb) expr int $val = 10
    (lldb) expr $val
    (int) $val = 10
    

    exp打印值、修改值

    (lldb) expr width
    (CGFloat) $0 = 10
    (lldb) expr width = 2
    (CGFloat) $1 = 2
    (lldb) po width
    2
    
    (lldb) p width
    (CGFloat) $3 = 2
    

    p、po与expr的关系

    (lldb) expr -- person
    (Person *) $0 = 0x000000010053b7f0
    (lldb) p person
    (Person *) $1 = 0x000000010053b7f0
    (lldb) expr -o -- person
    <Person: 0x10053b7f0>
    
    (lldb) po person
    <Person: 0x10053b7f0>
    

    p是expr --的简写,它的工作是把接收到参数在当前环境中进行编译,然后打印出来 po是expr -o --的简写,它所做的操作和p相同。如果接收到的参数是一个指针,那么它会调用对象的description方法并打印;如果接收到的参数是一个core foundation对象,那么它会调用CFShow方法并打印。如果这两个方法都调用失败,那么po打印出和p相同的内容。

    LLDB内存读取

    (lldb) x person
    0x10053a6b0: 5d 22 00 00 01 80 1d 00 00 00 00 00 00 00 00 00  ]"..............
    0x10053a6c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    (lldb) x/4gx person
    0x10053a6b0: 0x001d80010000225d 0x0000000000000000
    0x10053a6c0: 0x0000000000000000 0x0000000000000000
    (lldb) x/3wx person
    0x10053a6b0: 0x0000225d 0x001d8001 0x00000000
    (lldb) x &width
    0x7ffeefbff4f0: 00 00 00 00 00 00 00 00 b0 a6 53 00 01 00 00 00  ..........S.....
    0x7ffeefbff500: 30 f5 bf ef fe 7f 00 00 01 00 00 00 00 00 00 00  0...............
    (lldb) x/4gx &width
    0x7ffeefbff4f0: 0x0000000000000000 0x000000010053a6b0
    0x7ffeefbff500: 0x00007ffeefbff530 0x0000000000000001
    (lldb) x/4go
    0x7ffeefbff510: 03777735757772440
    0x7ffeefbff518: 03777755321776311
    0x7ffeefbff520: 00
    0x7ffeefbff528: 01
    
    x/4gx|x/4gw

    x就是memory read内存读取并打印的作用, x是读取内存的命令,x/4gx中第一个x是读取内存命令,后面的g是每次读取8字节,x的意思是16进制显示结果,4表示连续打印4段。
    对于g,常用的大小格式为b对应byte 1字节,h对应half word 2字节,w对应word 4字节,g对应giant word 8字节
    对于x,我们还可以用o对应8机制,b对应2进制,x对应16进制,f对应浮点,d对应10进制。

    call方法调用

    (lldb) call width
    (CGFloat) $0 = 0
    (lldb) call testFunction()
    123456
    

    register read

    返回当前线程通用寄存器的值(对64为对应x0-x31)

    (lldb) register read
    General Purpose Registers:
          x0 = 0x0000000102905540
            x1 = 0x00000001e1fd3c56  
            x2 = 0x0000000000000001
            x3 = 0x000000016dac7c40
            x4 = 0x0000000000000010
            x5 = 0x000000016dac784f
            x6 = 0x000000016dac7940
            x7 = 0x0000000000000000
            x8 = 0x000000010233cff8  "viewDidLoad"
            x9 = 0x000000010233d038  (void *)0x000000010233d050: ViewController
    

    查看一个对象的 isa 指针

    Class cls = NSClassFromString(@"HGObject"); 
    id obj = [[cls alloc] init]; 
    NSLog(@"%@, %@", cls, obj);
    
    isa指针
    运行代码发现:
    1.cls 没有显示具体的地址值。
    2.在 obj 中也根本没有看到 isa 这个成员变量。
    看不到任何的地址显示,所以只能是借助 LLDB 调试工具,这里既是是使用简单的 p 或者 po 指令都是不可以的。需要借助上面说的 操作内存 的指令。
    isa指针

    LLDB动态注入代码逻辑

    场景一

    动态注入代码
    主要看 yesOrNo 属性,当在代码已经运行起来的时候,想重新运行,就要把 yesOrNo 的值给改成 true 。你会怎么做?其实在上面已经介绍了,使用 expr 可以搞定,但是这里还有可以有更高级的。 如下图所示,弄一个断点,双击让断点变成编辑状态。
    动态注入代码
    点击上图中的红框框部分,如下所示:
    动态注入代码
    写上 expression yesOrNo = true 会发现这样的话不用每次运行到这个断点的时候都去 lldb 一次。

    帮助命令help

    可以列出所有可以用于调试代码的命令及功能说明,如图:


    help

    结束语

    本文小编分享了测试中iOS调试常用的几类LLDB命令,如果需要用到其他的LLDB命令可以借助help命令进行查询其用法,iOS代码调试除了LLDB命令之外,还可以进行断点调试,最常使用的是LLDB命令和断点操作混合调试,本文只是抛砖引玉,需要的同学可以在网上进行更深入了解学习。

    青山不改,绿水长流,后会有期,感谢每一位佳人的支持!

    相关文章

      网友评论

        本文标题:iOS调试之LLDB命令

        本文链接:https://www.haomeiwen.com/subject/myvgiltx.html