美文网首页
工具篇-LLDB 调试器

工具篇-LLDB 调试器

作者: 刘朝龙er | 来源:发表于2019-12-27 14:33 被阅读0次

    LLDB (Low Level Debugger) 轻量级高性能调试器,掌握 LLDB 可以有效的提升 Debug 能力,提高工作效率。

    一、常用命令

    对象操作 (p/po/e/call)

    NSArray *array = @[@"a", @"b", @"b", @"c"];
    
    //(lldb) p array
    //(__NSArray0 *) $3 = 0x00007fff8db5c830 @"0 elements"
    //(lldb) p array = @[@"a"]
    //(__NSSingleObjectArrayI *) $4 = 0x00000001005013b0 @"1 element"
    
    // (lldb) po array
    //<__NSArrayI 0x1006022f0>(
    //a,
    //b,
    //b,
    //c
    //)
    
    ZSan *zsan  = [[ZSan alloc] init];
    zsan->_no   = 1;
    
    //(lldb) e zsan->_no = 2;
    // (int) $2 = 2
    

    lldbp 打印对象的描述信息,包括类型,指针地址等信息;po 打印的是对象的详细信息。

    po 同时也可以修改对象信息,e 可以修改对象中属性的值,call 可以让对象执行方法。

    p/po/e/call 都可以打印对象的信息。

    函数操作 (bt/frame/image)

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // 断点处
            NSArray *array = @[@"a", @"b", @"b", @"c"];
        }
        return 0;
    }
    //  (lldb) bt
    //  * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
    //      * frame #0: 0x0000000100000d58 BLBaseNSObject`main(argc=1, argv=0x00007ffeefbff4f8) at main.m:122:22
    //        frame #1: 0x00007fff6c55e7fd libdyld.dylib`start + 1
    //        frame #2: 0x00007fff6c55e7fd libdyld.dylib`start + 1
    
    //(lldb) frame select 0
    //frame #0: 0x0000000100000d58 BLBaseNSObject`main(argc=1, argv=0x00007ffeefbff4f8) at main.m:122:22
    //   119
    //   120             NSArray *array = @[@"a", @"b", @"b", @"c"];
    //   121
    //-> 122             NSLog(@"%@", array);
    //                             ^
    //   123         }
    //   124         return 0;
    //   125     }
    

    bt(backtrace) 打印当前调用堆栈(crash堆栈);frame 打印函数调用详情以及调用顺序;image 打印栈地址对应的代码。

    frame selecte 0 可以查看当前函数调用的信息,通过 updown 可以追踪函数的调用和被调用关系。

    frame variable 很方便的查方法的调用者及方法名称。

    image lookup -a 地址 这个方法主要用于寻址,常用与在崩溃的时候,查找崩溃地址对应的文件代码的位置。

    当项目遇到崩溃的时候同时利用 btimage loopup -a 地址 可以更好的找到问题。

    属性&变量操作 (watchpoint)

    有时候我们开发过程中会遇到类似的问题,有一些属性或者变量值发生了变化,但是不知道具体是哪里开始发生的变化,导致最后的结果与我们预期有了出入。

    这里就可以使用 watchpoint 来监听我们的属性或者变量。

    ZSan *zsan  = [[ZSan alloc] init];
    zsan.ccc    = 10;
    zsan.ccc    = 1000;
    
    //(lldb) watchpoint set variable zsan->_ccc
    //Watchpoint created: Watchpoint 1: addr = 0x100555c30 size = 4 state = enabled type = w
    //    declare @ '/Users/Charlyliu/Desktop/学习项目/OC/BLBaseNSObject/BLBaseNSObject/main.m:108'
    //    watchpoint spec = 'zsan->_ccc'
    //    new value: 0
    //
    //Watchpoint 1 hit:
    //old value: 0
    //new value: 10
    //(lldb) pt
    //error: 'pt' is not a valid command.
    //(lldb) bt
    //* thread #1, queue = 'com.apple.main-thread', stop reason = watchpoint 1
    //  * frame #0: 0x0000000100001a70 BLBaseNSObject`-[ZSan setCcc:](self=0x0000000100555c10, _cmd="setCcc:", ccc=10) at main.m:60:35
    //    frame #1: 0x0000000100001c69 BLBaseNSObject`main(argc=1, argv=0x00007ffeefbff4f8) at main.m:114:14
    //    frame #2: 0x00007fff6c55e7fd libdyld.dylib`start + 1
    //    frame #3: 0x00007fff6c55e7fd libdyld.dylib`start + 1
    //(lldb) n
    //(lldb) bt
    //* thread #1, queue = 'com.apple.main-thread', stop reason = step over
    //  * frame #0: 0x0000000100001c69 BLBaseNSObject`main(argc=1, argv=0x00007ffeefbff4f8) at main.m:120:111
    //    frame #1: 0x00007fff6c55e7fd libdyld.dylib`start + 1
    //    frame #2: 0x00007fff6c55e7fd libdyld.dylib`start + 1
    //(lldb) c
    //Process 36061 resuming
    //2019-12-27 13:40:11.600731+0800 BLBaseNSObject[36061:1628481] 测试多层继承关系成员变量大小的内存对齐逻辑---40
    //2019-12-27 13:40:11.601062+0800 BLBaseNSObject[36061:1628481] 测试多层继承关系内存分配---48
    //
    //Watchpoint 1 hit:
    //old value: 10
    //new value: 1000
    //(lldb) bt
    //* thread #1, queue = 'com.apple.main-thread', stop reason = watchpoint 1
    //  * frame #0: 0x0000000100001a70 BLBaseNSObject`-[ZSan setCcc:](self=0x0000000100555c10, _cmd="setCcc:", ccc=1000) at main.m:60:35
    //    frame #1: 0x0000000100001cd5 BLBaseNSObject`main(argc=1, argv=0x00007ffeefbff4f8) at main.m:123:14
    //    frame #2: 0x00007fff6c55e7fd libdyld.dylib`start + 1
    //    frame #3: 0x00007fff6c55e7fd libdyld.dylib`start + 1
    //(lldb) image lookup 0x0000000100001a70
    //error: invalid combination of options for the given command
    //(lldb) image lookup -a 0x0000000100001a70
    //      Address: BLBaseNSObject[0x0000000100001a70] (BLBaseNSObject.__TEXT.__text + 64)
    //      Summary: BLBaseNSObject`-[ZSan setCcc:] + 32 at main.m:60:35
    //(lldb) 
    
    • watchpoint set variable zsan->_ccc 注意,这里的 zsan->_ccc 不能使用点语法,这条命令主要是监听属性地址,如果其地址内保存的值发生变化就会走进断点,而点语法实际上是调用的 getter 方法。
    • watchpoint set variable testInt 设置变量观察,testInt变量发生改变后触发。
    • watchpoint set expression -- 0x000000010f46c070 设置内存观察。
    • watchpoint list / watch l 当前所有观察列表。
    • watchpoint modify -c 'testInt == 2' 设置条件触发观察。
    • watchpoint delete 2 删除对应的观察。

    这里我们可以配合 bt 堆栈信息使用,当发现值变化的时候可以查阅堆栈信息定位修改代码的位置。

    内存操作 (memory/x {read/write})

    ZSan *zsan  = [[ZSan alloc] init];
    zsan->_no   = 1;
    zsan->_num  = 1;
    zsan->_age  = 18;
    
    //(lldb) memory read zsan
    //0x1007009a0: 21 23 00 00 01 80 1d 00 01 00 00 00 01 00 00 00  !#..............
    //0x1007009b0: 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    // 表示修改 0x1007009a8 后的值,将其值修改为 4;根据刚才获取内存信息可知,从 0x1007009a8 地址之后是 _no 的值,值位 0x01,这里我们将其修改成为 0x04,也就是 zsan->_no = 4
    //(lldb) memory write 0x1007009a8 4 
    //(lldb) po zsan->_no
    //4
    

    memory read 可以用 x 代替,使用起来更方便,用法 memory read/数量格式字节数 读取对象/或某个具体的内存地址 或者 x/数量格式字节数 读取对象/或某个具体的内存地址

    / 后各个字段描述:

    • 数量,表示读取从该对象起始地址或该内存地址起始地址后多长的地址
    • 格式
      • x16 进制
      • f 是浮点
      • d10 进制
    • 字节数
      • bbyte 1 字节
      • hhalf word 2 字节
      • wword 4 字节
      • ggiant word 8 字节

    举例:读取 szan 对象地址,要求以 8 个字节,用 16 进制格式读取 4 组数据。
    x/4xg szan 或者 x/4xg 0x10055d610,其中 0x10055d610szan 对象的地址。

    memory write 地址 修改后的数据 ,通过 write 可以修改内存中的数据。

    断点操作 (breakpoind/b)

    Xcode 工具为我们提供了断点调试的功能,为什么还要使用这个命令呢?LLDB 的断点使用起来速度更快,功能更全面。

    0. 流程控制

    • c/continue 继续执行,跳到下一个断点或者没有断点程序执行完毕
    • next/n(step-over) 单步运行,将子函数当做整体一步运行
    • stepi/s(step-in) 单步运行,遇到子函数会进入
    • finish(step-out) 如果进入一个子函数,则可以通过这个方法退出子函数

    1. 增删查

    • set 添加断点
      • -a {内存地址} 根据函数地址打断点(常用与逆向)
      • -n {xxx:} 设置方法名断点 breakpoint/b set -n login:
      • -n { [Class xxxx]} 根据对应的类设置该类的方法名断点(可多个:一组) breakpoint/b set -n "-[Class1 xxx]" -n "-[Class2 xxx]"
      • -l {lineNumber} 根据行号设置断点
      • -f {fileName} 根据文件名中的信息设置断点
      • -F {函数全名}根据函数全名打断点
      • -r {部分函数名} 根据部分函数名打断点
      • -N{别名} 给断点添加别名
    • delete {breakpoint_num} 删除一个断点
    • list {breakpoint_num} 列举指定断点信息,breakpoint_num 可选
    • enable/disable {breakpoint_num} 启用或禁用某个/某组断点
    • commond 执行一些特殊命令
      • add {breakpoint_num} {特殊命令(如p/po/bt等)} 断点执行后执行一些特殊LLDB操作
      • delete{breakpoint_num}删除某个断点添加的特殊操作

    2. 条件、命令

    • -i {number} 设置断点忽略次数
    • -o 只断住一次
    • -c {条件(类似数据库where操作)} 设置断点条件

    其他操作

    • methods命令可以打印当前对象的属性和方法
    • previews命令可以打印当前视图的层级结构
    • help 获取帮助信息,查看更多 LLDB 命令

    参考文档

    LLDB调试利器及高级用法
    断点远比你想想中的强大
    动态调试及LLDB技巧集合

    相关文章

      网友评论

          本文标题:工具篇-LLDB 调试器

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