一、基本介绍
LLDB
是个开源的内置于XCode
的调试工具,它能帮助我们在开发中更快的定位和调试bug
,无论正向和逆向开发中都有很大的作用。lldb对于命令的简称,是头部匹配方式,只要不混淆(不提示歧义),你可以随意简称某个命令。
xcode调试区
下面是xcode debug区域的按钮讲解
二、调试技巧
单步调试
单步调试通常分为两大类,一类为源码级别(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
-
c
是continue
的简写 表示继续运行 - 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
p
、po
与expr
的关系
(lldb) expr -- person
(Person *) $0 = 0x000000010053b7f0
(lldb) p person
(Person *) $1 = 0x000000010053b7f0
(lldb) expr -o -- person
(lldb) po person
总结
p
是expr --
的简写,它的工作是把接收到参数在当前环境中进行编译,然后打印出来
po
是expr -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>) b = some {
some = none {
some = {
_value = 0
}
}
}
(lldb) fr v -a b
(Int??) b = nil
我们常用fr v -R
来查看类型的结构
5.检查线程状态
1) 堆栈打印(bt
命令)
bt
是thread 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
如上所示,我们先得到开始一部分数组越界的崩溃信息,我们找到stack
中4
的位置,可以看到是调用了[person getInt]
,这时候我们使用image lookup -a 地址
就可以找到崩溃位置。
image lookup -a
是image 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
、_cmd
、array
image lookup -v -a
是image 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, std::__1::allocator > const&, std::__1::basic_string, std::__1::allocator >&, 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
如代码所示,我们使用b
在getInt
方法中设置了一个断点,执行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(断点)调试
参考链接:
- GDB to LLDB command map
- iOS之LLDB常用命令