lldb常用命令与调试技巧

20,204 阅读14分钟

一、基本介绍

LLDB是个开源的内置于XCode的调试工具,它能帮助我们在开发中更快的定位和调试bug,无论正向和逆向开发中都有很大的作用。lldb对于命令的简称,是头部匹配方式,只要不混淆(不提示歧义),你可以随意简称某个命令

xcode调试区

下面是xcode debug区域的按钮讲解 Xcode调试区按钮讲解

二、调试技巧

单步调试

单步调试通常分为两大类,一类为源码级别(source level),一类为指令级别(instrution level)。一行源代码一般需要多行汇编才可以实现,所以当我们越狱开发调试汇编指令单步调试需要用到(instrution level)指令级别。而每一大类又分为step-in和step-over,step-in会进入函数调用,而step-over会跳过函数调用

1)源码级别(source level)
  • step-in
(lldb) thread step-in
(lldb) step
(lldb) s

以上三条命令是等同的,在有函数调用的位置,进入函数内部。

  • step-over
(lldb) thread step-over
(lldb) next
(lldb) n

以上三条命令是等同的,在有函数调用的位置,不会进入函数内部。

总结

s和n都是跳转到断点的下一行代码位置,区别为,如果下一行代码有函数调用,s会进入函数内部,n则会跳过函数执行。如果没有函数调用则两组命令没有任何区别。

2)指令级别(instruction level)

指令级别的调试,我们需要在xcode的选项卡中设置Debug->Debug Workflow->Always Show Disassembly,这时候才能看到效果。指令级别的lldb对应的正好是我们点击按钮的同时按下了ctrl键。

  • step-in
(lldb) thread step-inst
(lldb) si

以上两条命令是等同的,在汇编界面,跳转下一步,在有bl指令的地方,si``会单步进入到bl指令所跳转的子函数内部

  • step-over
(lldb) thread step-inst-over
(lldb) ni

以上两条命令是等同的,在汇编界面,跳转下一步,在有bl指令的地方,ni``不会单步进入到bl指令所跳转的子函数内部

  • step-out
    • setp out 从一个函数跳出。
    • 如果没有执行s或者si,却执行了finish,其实会跳转到汇编指令bl的下一条位置(step out默认是从一个函数跳出,对系统函数调用一定是通过bl执行了函数调用,下一个位置必定为bl的下一个位置)
    • 要从嵌套的step out中退出,执行c命令即可跳转到下一个断点。
(lldb) thread step-out
(lldb) finish
(lldb) f
  • ccontinue的简写 表示继续运行
  • s(si)、n(ni)和Xcode调试工具的对应关系如文开始的图

三、lldb常用命令

1.计算表达式命令(expression、po、p)

  • 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
  • ppoexpr的关系
(lldb) expr -- person
(Person *) $0 = 0x000000010053b7f0
(lldb) p person
(Person *) $1 = 0x000000010053b7f0
(lldb) expr -o -- person
<Person: 0x10053b7f0>

(lldb) po person
<Person: 0x10053b7f0>

总结

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

  • 使用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

2. 内存读取

(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是读取内存的命令,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进制

3.call方法调用

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

4.变量检查(frame)

(lldb) fr v b
(Int??) b = nil
(lldb) fr v -r b
(Int??) b = nil
(lldb) fr v -R b
(Swift.Optional<Swift.Optional<Swift.Int>>) b = some {
  some = none {
    some = {
      _value = 0
    }
  }
}
(lldb) fr v -a b
(Int??) b = nil

我们常用fr v -R来查看类型的结构

5.检查线程状态

1) 堆栈打印(bt命令)

btthread backtrace的简写,如果嫌堆栈打印太长,可以加一个限制,如bt 10,只打印10行

(lldb) thread backtrace 1
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
  * frame #0: 0x0000000100000e02 TestWeak`main(argc=1, argv=0x00007ffeefbff530) at main.m:30:5
    frame #1: 0x00007fff6b47fcc9 libdyld.dylib`start + 1
(lldb) thread backtrace -c 1
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
    frame #0: 0x0000000100000e02 TestWeak`main(argc=1, argv=0x00007ffeefbff530) at main.m:30:5
(lldb) bt 10
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100000df1 TestWeak`main(argc=1, argv=0x00007ffeefbff530) at main.m:28:13
    frame #1: 0x00007fff6b47fcc9 libdyld.dylib`start + 1

我们的案例一共只有3行,没达到触发10行限制的条件。

2)thread list 列出当前线程列表
(lldb) thread list
Process 4500 stopped
* thread #1: tid = 0x33d9a, 0x0000000100000e02 TestWeak`main(argc=1, argv=0x00007ffeefbff530) at main.m:30:5, queue = 'com.apple.main-thread', stop reason = step over
3)thread select 选取某个线程作为后续命令的默认线程
(lldb) thread select 1
4)frame select 根据下标选择堆栈列表中某帧
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100000deb TestWeak`main(argc=1, argv=0x00007ffeefbff530) at main.m:37:5
    frame #1: 0x00007fff6ce68cc9 libdyld.dylib`start + 1
(lldb) frame select 1
frame #1: 0x00007fff6ce68cc9 libdyld.dylib`start + 1
libdyld.dylib`start:
->  0x7fff6ce68cc9 <+1>: movl   %eax, %edi
    0x7fff6ce68ccb <+3>: callq  0x7fff6ce7c82e            ; symbol stub for: exit
    0x7fff6ce68cd0 <+8>: hlt    
    0x7fff6ce68cd1 <+9>: nop    

此时会跳转到汇编页面,即使没有设置Always Show Disassembly

5)frame info 显示当前帧信息
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100000deb TestWeak`main(argc=1, argv=0x00007ffeefbff530) at main.m:37:5
    frame #1: 0x00007fff6ce68cc9 libdyld.dylib`start + 1
(lldb) frame info
frame #0: 0x0000000100000deb TestWeak`main(argc=1, argv=0x00007ffeefbff530) at main.m:37:5
(lldb) frame select 1
frame #1: 0x00007fff6ce68cc9 libdyld.dylib`start + 1
libdyld.dylib`start:
->  0x7fff6ce68cc9 <+1>: movl   %eax, %edi
    0x7fff6ce68ccb <+3>: callq  0x7fff6ce7c82e            ; symbol stub for: exit
    0x7fff6ce68cd0 <+8>: hlt    
    0x7fff6ce68cd1 <+9>: nop    
(lldb) frame info
frame #1: 0x00007fff6ce68cc9 libdyld.dylib`start + 1
6)up 移动当前帧(序号加1) down移动当前帧(序号减1)
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100000ddf TestWeak`main(argc=1, argv=0x00007ffeefbff530) at main.m:36:5
    frame #1: 0x00007fff6ce68cc9 libdyld.dylib`start + 1
(lldb) up
frame #1: 0x00007fff6ce68cc9 libdyld.dylib`start + 1
libdyld.dylib`start:
->  0x7fff6ce68cc9 <+1>: movl   %eax, %edi
    0x7fff6ce68ccb <+3>: callq  0x7fff6ce7c82e            ; symbol stub for: exit
    0x7fff6ce68cd0 <+8>: hlt    
    0x7fff6ce68cd1 <+9>: nop    
(lldb) up
error: Already at the top of the stack.
(lldb) down
frame #0: 0x0000000100000ddf TestWeak`main(argc=1, argv=0x00007ffeefbff530) at main.m:36:5
   33  	    person.age = 5;
   34  	    
   35  	    CGFloat width = 10;
-> 36  	    testFunction();
    	    ^
   37  	    NSLog(@"hello");
   38  	    return 0;
   39  	}
(lldb) down
error: Already at the bottom of the stack.

可以看到,如果已经是栈顶或者栈底,会提示已在最顶或者最底部。

7)register read 读取寄存器 register write写入寄存器
(lldb) register write rax 123
(lldb) register read rax
     rax = 0x000000000000007b
(lldb) register write rax 1
(lldb) register read rax
     rax = 0x0000000000000001
8)thread return 跳出当前方法的执行

Debug的时候,也许会因为各种原因,我们不想让代码执行某个方法,或者要直接返回一个想要的值。这时候就该thread return上场了。 有返回值的方法里,如:numberOfSectionsInTableView:,直接thread return 20,就可以直接跳过方法执行,返回20.

//跳出方法
(lldb) thread return
//让带有返回int值的方法直接跳出,并返回值20
(lldb) thread return 20

6.镜像(image)操作

1) image list镜像列表
(lldb) image list
[  0] 9D17C7F5-7D6B-387D-81E5-1C7ED33709BE 0x0000000100000000 /Users/liu_david/Library/Developer/Xcode/DerivedData/TestWeak-egnzdbndwsiikvcheqmcxvkqnwbw/Build/Products/Debug/TestWeak 
[  1] F9D4DEDC-8296-3E3F-B517-9C8B89A4C094 0x0000000100009000 /usr/lib/dyld 
[  2] 7C69F845-F651-3193-8262-5938010EC67D 0x00007fff35437000 /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation 
[  3] 6DF81160-5E7F-3E31-AA1E-C875E3B98AF6 0x00007fff6bcad000 /usr/lib/libobjc.A.dylib 
[  4] C0C9872A-E730-37EA-954A-3CE087C15535 0x00007fff69e4d000 /usr/lib/libSystem.B.dylib 
[  5] C0D70026-EDBE-3CBD-B317-367CF4F1C92F 0x00007fff32d7a000 /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation 
[  6] B6124448-7690-34AE-8939-ED84AAC630CE 0x00007fff6a04f000 /usr/lib/libauto.dylib 

这里只截取了部分,因为所有的镜像有几百个,包括了各种动态库

2) image lookup -a 查看崩溃位置
2020-09-16 00:41:45.605355+0800 TestWeak[2106:87796] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 4 beyond bounds [0 .. 2]'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff32e79b57 __exceptionPreprocess + 250
	1   libobjc.A.dylib                     0x00007fff6bcc05bf objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff32f2859e -[__NSCFString characterAtIndex:].cold.1 + 0
	3   CoreFoundation                      0x00007fff32deab70 +[NSNull null] + 0
	4   TestWeak                            0x0000000100001b8f -[Person getInt] + 287
	5   TestWeak                            0x0000000100001d73 main + 99
	6   libdyld.dylib                       0x00007fff6ce68cc9 start + 1
	7   ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) image lookup --address 0x0000000100001b8f
      Address: TestWeak[0x0000000100001b8f] (TestWeak.__TEXT.__text + 335)
      Summary: TestWeak`-[Person getInt] + 287 at main.m:31:13
(lldb) image lookup -a 0x0000000100001b8f
      Address: TestWeak[0x0000000100001b8f] (TestWeak.__TEXT.__text + 335)
      Summary: TestWeak`-[Person getInt] + 287 at main.m:31:13

如上所示,我们先得到开始一部分数组越界的崩溃信息,我们找到stack4的位置,可以看到是调用了[person getInt],这时候我们使用image lookup -a 地址就可以找到崩溃位置。 image lookup -aimage lookup --address的简写

3) image lookup -v -a查找完整的源代码行信息

同样使用上方数组越界案例

(lldb) image lookup -v -a 0x0000000100001b8f
      Address: TestWeak[0x0000000100001b8f] (TestWeak.__TEXT.__text + 335)
      Summary: TestWeak`-[Person getInt] + 287 at main.m:31:13
       Module: file = "/Users/liu_david/Library/Developer/Xcode/DerivedData/TestWeak-egnzdbndwsiikvcheqmcxvkqnwbw/Build/Products/Debug/TestWeak", arch = "x86_64"
  CompileUnit: id = {0x00000000}, file = "/Users/liu_david/Desktop/TestWeak/TestWeak/main.m", language = "objective-c"
     Function: id = {0x100000162}, name = "-[Person getInt]", range = [0x0000000100001a70-0x0000000100001bef)
     FuncType: id = {0x100000162}, byte-size = 0, decl = main.m:29, compiler_type = "int (void)"
       Blocks: id = {0x100000162}, range = [0x100001a70-0x100001bef)
    LineEntry: [0x0000000100001b76-0x0000000100001b97): /Users/liu_david/Desktop/TestWeak/TestWeak/main.m:31:13
       Symbol: id = {0x0000001c}, range = [0x0000000100001a70-0x0000000100001bf0), name="-[Person getInt]"
     Variable: id = {0x10000017f}, name = "self", type = "Person *const", location = DW_OP_fbreg(-40), decl = 
     Variable: id = {0x10000018b}, name = "_cmd", type = "SEL", location = DW_OP_fbreg(-48), decl = 
     Variable: id = {0x100000197}, name = "array", type = "NSArray *", location = DW_OP_fbreg(-56), decl = main.m:30

从信息可以看到,我们有三个变量入栈,分别是self_cmdarray image lookup -v -aimage lookup -v --address的简写

4) image lookup -name查找方法来源
(lldb) image lookup getInt
error: invalid combination of options for the given command
(lldb) image lookup -name getInt
1 match found in /Users/liu_david/Library/Developer/Xcode/DerivedData/TestWeak-egnzdbndwsiikvcheqmcxvkqnwbw/Build/Products/Debug/TestWeak:
        Address: TestWeak[0x0000000100001a70] (TestWeak.__TEXT.__text + 48)
        Summary: TestWeak`-[Person getInt] at main.m:29
1 match found in /usr/lib/libicucore.A.dylib:
        Address: libicucore.A.dylib[0x000000000003b046] (libicucore.A.dylib.__TEXT.__text + 239078)
        Summary: libicucore.A.dylib`icu::ResourceBundle::getInt(UErrorCode&) const
1 match found in /System/Library/Frameworks/Security.framework/Versions/A/Security:
        Address: Security[0x000000000012260e] (Security.__TEXT.__text + 1184206)
        Summary: Security`Security::Context::getInt(unsigned int, int) const
1 match found in /System/Library/Frameworks/SceneKit.framework/Versions/A/SceneKit:
        Address: SceneKit[0x000000000024f396] (SceneKit.__TEXT.__text + 2414070)
        Summary: SceneKit`getInt(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&, int&, bool, bool)
1 match found in /usr/lib/libTelephonyUtilDynamic.dylib:
        Address: libTelephonyUtilDynamic.dylib[0x0000000000012e10] (libTelephonyUtilDynamic.dylib.__TEXT.__text + 69904)
        Summary: libTelephonyUtilDynamic.dylib`ctu::cf::map_adapter::getInt(__CFString const*, int) const
1 match found in /System/Library/PrivateFrameworks/CorePrediction.framework/Versions/A/CorePrediction:
        Address: CorePrediction[0x00000000000475c4] (CorePrediction.__TEXT.__text + 286980)
        Summary: CorePrediction`-[CPMLEvalutionResult getInt]

可以看到,所有match到的方法位置信息都给我们了。只不过只有我们自定义的方法给了我们是在main.m:29

5) image lookup -type查看成员
(lldb) image lookup -type Person
Best match found in /Users/liu_david/Library/Developer/Xcode/DerivedData/TestWeak-egnzdbndwsiikvcheqmcxvkqnwbw/Build/Products/Debug/TestWeak:
id = {0x10000002b}, name = "Person", byte-size = 24, decl = main.m:12, compiler_type = "@interface Person : NSObject{
    BOOL _isMan;
    short _age;
    NSString * _name;
}
@property(nonatomic, copy, readwrite, getter = name, setter = setName:) NSString *name;
@property(nonatomic, assign, readwrite, getter = age, setter = setAge:) short age;
@property(nonatomic, assign, readwrite, getter = isMan, setter = setIsMan:) BOOL isMan;
@end"

(lldb) image lookup -type NSObject
Best match found in /Users/liu_david/Library/Developer/Xcode/DerivedData/TestWeak-egnzdbndwsiikvcheqmcxvkqnwbw/Build/Products/Debug/TestWeak:
id = {0x7fffffff000002e8}, name = "NSObject", byte-size = 8, decl = NSObject.h:53, compiler_type = "@interface NSObject{
    Class isa;
}
@end"

(lldb) im loo -t Person
Best match found in /Users/liu_david/Library/Developer/Xcode/DerivedData/TestWeak-egnzdbndwsiikvcheqmcxvkqnwbw/Build/Products/Debug/TestWeak:
id = {0x10000002b}, name = "Person", byte-size = 24, decl = main.m:12, compiler_type = "@interface Person : NSObject{
    BOOL _isMan;
    short _age;
    NSString * _name;
}
@property(nonatomic, copy, readwrite, getter = name, setter = setName:) NSString *name;
@property(nonatomic, assign, readwrite, getter = age, setter = setAge:) short age;
@property(nonatomic, assign, readwrite, getter = isMan, setter = setIsMan:) BOOL isMan;
@end"

可以看到,image lookup -type可以用im loo -t简写

7.断点(breakpoint)操作

1) b在某个函数设置一个断点
(lldb) b getInt
Breakpoint 2: 6 locations.
(lldb) c
Process 2274 resuming

如代码所示,我们使用bgetInt方法中设置了一个断点,执行c指令继续运行后,代码会断在getInt方法中第一行 b是以下的简写

  • br s -n
  • breakpoint set --name
2) b+文件:行 在某个文件的某行设置一个断点
(lldb) b main.m:45
Breakpoint 2: where = TestWeak`main + 102 at main.m:45:5, address = 0x0000000100001d76
(lldb) c
Process 2297 resuming

这种写法是以下命令的简写:

  • br s -f main.m -l 45
  • breakpoint set --file main.m --line 45
3) b -[类名 方法名]b +[类名 方法名]设置类中方法的断点
  • b -[类名 方法名]是设置实例方法的断点
  • b +[类名 方法名]是设置类方法的断点
(lldb) b -[Person getInt]
Breakpoint 2: where = TestWeak`-[Person getInt] + 30 at main.m:30:24, address = 0x0000000100001a6e
(lldb) b +[Person aaa]
Breakpoint 3: where = TestWeak`+[Person aaa] + 23 at main.m:27:5, address = 0x0000000100001a37
(lldb) c
Process 2344 resuming
(lldb) c
Process 2344 resuming

这种写法是以下命令的简写:

  • breakpoint set -- name -[类名 方法名]
4) breakpoint list 查看断点列表
(lldb) breakpoint list
Current breakpoints:
1: file = '/Users/liu_david/Desktop/TestWeak/TestWeak/main.m', line = 39, exact_match = 0, locations = 1, resolved = 1, hit count = 1

  1.1: where = TestWeak`main + 41 at main.m:39:5, address = 0x0000000100001d19, resolved, hit count = 1 

2: file = '/Users/liu_david/Desktop/TestWeak/TestWeak/main.m', line = 30, exact_match = 0, locations = 1, resolved = 1, hit count = 0

  2.1: where = TestWeak`-[Person getInt] + 30 at main.m:30:24, address = 0x0000000100001a6e, resolved, hit count = 0 

3: file = '/Users/liu_david/Desktop/TestWeak/TestWeak/main.m', line = 27, exact_match = 0, locations = 1, resolved = 1, hit count = 0

  3.1: where = TestWeak`+[Person aaa] + 23 at main.m:27:5, address = 0x0000000100001a37, resolved, hit count = 0 
5) br en 序号br dis 序号 启用/禁用断点
(lldb) br dis 2
1 breakpoints disabled.
(lldb) br en 2
1 breakpoints enabled.

在使用br dis 2后,2号断点就会变灰不可用,br en 2后,2号断点又会恢复亮色可使用状态

  • br en 序号breakpoint enable 序号的简写
  • br dis 序号breakpoint disable 序号的简写
6) br del 序号 根据序号移除一个断点
(lldb) br del 2
1 breakpoints deleted; 0 breakpoint locations disabled.

在删除2号断点后,它将立刻在界面上移除。

br del 序号breakpoint delete 序号的简写

Xcode中的断点调试技巧可参考:iOS Xcode Breakpoint(断点)调试

参考链接: