前言:
查看基于TCP的简单字节流
$ ls -l
total 392
-rw-rw-r--@ 1 wangning staff 2680 9 26 02:32 README.md
drwxr-xr-x@ 4 wangning staff 128 3 1 18:14 lldbinit-master
-rw-rw-r--@ 1 wangning staff 142530 9 26 02:32 lldbinit.py
-rwxr-xr-x 1 wangning staff 49856 2 24 20:30 test
$ lldb -file test
(lldb) target create "test"
Current executable set to '/Users/wangning/Documents/资料/2:27/第十一节课、LLDB深入学习/上课代码/03-lldb与.lldbinit/test' (x86_64).
(lldb) log list
Logging categories for 'dwarf':
all - all available logging categories
default - default set of logging categories
comp - log insertions of object files into DWARF debug maps
info - log the parsing of .debug_info
line - log the parsing of .debug_line
lookups - log any lookups that happen by name, regex, or address
map - log struct/unions/class type completions
...
(lldb) log enable gdb-remote packets
(lldb) r
" encoding="vector" format="vector-uint8" group_id="2" ehframe_regnum="20" dwarf_regnum="20" value_regnums="94"/>
<reg name="xmm4" regnum="111" offset="404" bitsize="128" group="vector" type="float" encoding="vector" format="vector-uint8" group_id="2" ehframe_regnum="21" dwarf_regnum="21" value_regnums="95"/>
<reg name="xmm5" regnum="112" offset="436" bitsize="128" group="vector" type="float" encoding="vector" format="vector-uint8" group_id="2" ehframe_regnum="22" dwarf_regnum="22" value_regnums="96"/>
一. LLDB源码探究
1.1 探究Xcode中的lldb 与 终端的lldb是不是同一个?
首先 Cmd + 空格键,搜索控制台并打开,在控制台点击开始
接下来终端输入命令
$ lldb -file test
(lldb) target create "test"
Current executable set to '/Users/wangning/Documents/资料/2:27/第十一节课、LLDB深入学习/上课代码/03-lldb与.lldbinit/test' (x86_64).
(lldb) r
Process 9246 launched: '/Users/wangning/Documents/资料/2:27/第十一节课、LLDB深入学习/上课代码/03-lldb与.lldbinit/test' (x86_64)
Process 9246 exited with status = 0 (0x00000000)
最后在控制台点击暂停
image.png
image.png
可以看出来终端使用的lldb就是Xcode中的lldb
// 终端输入which lldb打印/usr/bin/lldb,实际上只是做了一层包装,内部指向Xcode的lldb
$ which lldb
/usr/bin/lldb
1.2 llvm中调试lldb源码
打开llvm工程,target选择lldb,打开lldb源码类Driver.cpp,打上断点如下图
image.png
// 上图 main(int argc, char const *argv[]) 函数,可以传参数,方式如下
Product -> Edit Scheme -> Run -> Arguments -> Arguments Passed On Launch
// 以下学习例子没有传参数
// 查看argc参数
(lldb) po argc
1
// 查看 parray命令作用
(lldb) help parray
// 查看传递的数组 argv[] 中第一个参数
(lldb) parray 1 argv
(const char **) $1 = 0x00007ffeefbff4d0 {
(const char *) [0] = 0x00007ffeefbff688 "/Users/wangning/Desktop/llvm/llvm-project/build/Debug/bin/lldb"
}
此时想调试main(int argc, char const *argv[]) 函数中,下面printHelp打印的内容,该怎么办?
if (input_args.hasArg(OPT_help)) {
printHelp(T, argv0);
return 0;
}
如果hasArg方法返回 Yes 就能调试到 printHelp(T, argv0);
现在需要给hasArg方法传递 help 参数,此时需要用到寄存器 rax
// 查看 image lookup 命令作用以及缩写,发现是target modules
(lldb) help image lookup
'image' is an abbreviation for 'target modules'
//查看汇编地址,发现地址为0x0000000100007680 r表示 正则表达式,n表示名称
(lldb) image lookup -rn "llvm::opt::ArgList::hasArg"
1 match found in /Users/wangning/Desktop/llvm/llvm-project/build/Debug/bin/lldb:
Address: lldb[0x0000000100007680] (lldb.__TEXT.__text + 6944)
Summary: lldb`bool llvm::opt::ArgList::hasArg<(anonymous namespace)::ID>((anonymous namespace)::ID) const at ArgList.h:244
// -s 对指定地址反汇编化
(lldb) di -s 0x0000000100007680
lldb`llvm::opt::ArgList::hasArg<(anonymous namespace)::ID>:
0x100007680 <+0>: pushq %rbp
0x100007681 <+1>: movq %rsp, %rbp
0x100007684 <+4>: subq $0x10, %rsp
0x100007688 <+8>: movq %rdi, -0x8(%rbp)
0x10000768c <+12>: movl %esi, -0xc(%rbp)
0x10000768f <+15>: movq -0x8(%rbp), %rdi
0x100007693 <+19>: movl -0xc(%rbp), %esi
0x100007696 <+22>: callq 0x1000076b0 ; llvm::opt::ArgList::getLastArg<(anonymous namespace)::ID> at ArgList.h:250
0x10000769b <+27>: cmpq $0x0, %rax
// 给hasArg方法设置断点,跳过上面打的断点,会发现断点跳入方法hasArg(OptSpecifiers ...Ids)
(lldb) br set -a 0x10000769b
Breakpoint 4: where = lldb`bool llvm::opt::ArgList::hasArg<(anonymous namespace)::ID>((anonymous namespace)::ID) const + 27 at ArgList.h:245:31, address = 0x000000010000769b
// 此时需要修改寄存器,让hasArg方法返回Yes,此时一步步执行断点,发现断点跳入 printHelp(T, argv0); 并输出相关信息
(lldb) register write rax 1
// 此时如果继续执行就会 return 0,现在不想让直接return 0,想跳过4行代码
(lldb) thread jump -b 4
1.3 lldb调试定位类参数
运行的过程中,直接修改isLocationSucess值
// 定位类代码如下
@interface LGLocationManager: NSObject
// 定位是否成功
@property (nonatomic) BOOL isLocationSucess;
@end
@implementation LGLocationManager
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
@end
// 页面中点击view,调用getLocation方法,先在 if (_manager.isLocationSucess) { 这一行打断点
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self getLocation];
}
- (void)getLocation {
_manager = [[LGLocationManager alloc] init];
if (_manager.isLocationSucess) {
NSLog(@"LocationSucess");
} else {
NSLog(@"LocationFail");
}
}
// 运行的过程中,直接修改isLocationSucess值为Yes
// 查看lldb参数作用
(lldb) help br set
// 47为if (_manager.isLocationSucess) {所在行,其他参数作用课通过上面命令查看,
(lldb) br set -l 47 -f ViewController.m -C "e -- _manager.isLocationSucess = YES" -G true
e -- _manager.isLocationSucess = YES
(BOOL) $1 = YES
// 上面命令存在一个问题,如果行号发生变化将会无效
// 为防止干扰,先删除之前的断点
// 查看断点列表
(lldb) br list
// 删除所有断点
(lldb) br delete
// -p 启用正则,直接基于源码设置正则,点击屏幕 输出LocationSucess,说明isLocationSucess修改Yes成功
(lldb) br set -p "if \(_manager.isLocationSucess\)" -f ViewController.m -C "e -- _manager.isLocationSucess = YES" -G true
(BOOL) $2 = YES
// 现在还有一个问题,因为无法保证上面代码不变,所以基于内存地址来修改
// 现在我们在_manager初始化的时候就下断点,并且设置setisLocationSucess = YES; 这样_manager每次初始化,就会设置参数为Yes
(lldb) image lookup -vrn "\[LGLocationManager init\]"
Function: id = {0x100000147}, name = "-[LGLocationManager init]", range = [0x00000001061e2960-0x00000001061e29ec)
(lldb) di -s 0x00000001061e2960
LLDB_学习`-[LGLocationManager init]:
.... //这里还有信息
0x1061e297c <+28>: movq %rax, -0x20(%rbp)
// 给[LGLocationManager init] 初始化最后一行添加断点
(lldb) br set -a 0x1061e297c
Breakpoint 5: where = LLDB_学习`-[LGLocationManager init] + 28 at ViewController.m:20:12, address = 0x00000001061e297c
(lldb) br command add 5
Enter your debugger command(s). Type 'DONE' to end.
> e -- [(LGLocationManager *)$rax setIsLocationSucess:YES]
> continue
> DONE
// 点击view 成功打印如下
2021-03-02 23:27:05.180746+0800 LLDB_学习[12588:1127728] LocationSucess
// 基于上面内存地址还有问题,因为每次运行地址都可能变化
// 添加断点,指定不通语言类型打印
(lldb) br command add 5
Enter your debugger command(s). Type 'DONE' to end.
> e -l swift -- print("swift执行!")
> settings set target.language swift
> e -- print("swift执行!")
> settings set target.language objc
> e -- NSLog(@"OC下执行~")
> DONE
// 指定断点打到LGLocationManager初始化的地方
(lldb) br set -S "-[LGLocationManager init]" -K false
Breakpoint 8: where = LLDB_学习`-[LGLocationManager init] at ViewController.m:18, address = 0x00000001061e2960
(lldb) di -f
LLDB_学习`-[LGLocationManager init]:
// 也可以尝试用这种(不一定能把断点打到初始化的地方)
(lldb) br set -r "getLocation"
Breakpoint 7: 25 locations.
(lldb) di -f
LLDB_学习`-[LGLocationManager init]:
// 操作函数寄存器的方式,修改isLocationSucess值为Yes
(lldb) br set -S "-[LGLocationManager init]" -K false
Breakpoint 9: where = LLDB_学习`-[LGLocationManager init] at ViewController.m:18, address = 0x00000001061e2960
(lldb) br list
Current breakpoints:
9: name = '-[LGLocationManager init]', locations = 1, resolved = 1, hit count = 0
9.1: where = LLDB_学习`-[LGLocationManager init] at ViewController.m:18, address = 0x00000001061e2960, resolved, hit count = 0
(lldb) br command add 9
Enter your debugger command(s). Type 'DONE' to end.
> settings set target.language objc++
> br set -a `*(unsigned long *)$rsp` -o true -C "e -- [(LGLocationManager *)$rax setIsLocationSucess:YES]" -G true
> settings set target.language objc
> c
> DONE
2021-03-03 00:12:17.633257+0800 LLDB_学习[12588:1127728] LocationSucess
// 拆分上面长命令,来了解$rsp
(lldb) br command add 9.1
> e -f x -- $rsp
> e -f x -- (unsigned long *)$rsp
> e -f x -- *(unsigned long *)$rsp
> continue
> DONE
2021-03-03 19:45:20.971358+0800 LLDB_学习[12588:1127728] LocationFail
// 发现上面失败,现在添加断点
(lldb) br set -S "-[LGLocationManager init]" -K false
// 查看栈帧
(lldb) di -f
// 多显示50行分析内存地址
(lldb) di -s 0x1061e2a90 -c 50
1.4 lldb调试
上面viewcontroller 添加如下代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self getLocation];
NSLog(@"%ld", (long)[self thread_return]);
}
- (NSInteger)thread_return {
return 5;
}
方法thread_return正常返回5,现在调试的过程中想让返回10,我们在运行的时候手动修改返回值?
// 给方法thread_return打断点,修改返回值
(lldb) br set -r thread_return -C "thread return 10" -G true
Breakpoint 3: where = LLDB_学习`-[ViewController thread_return] + 12 at ViewController.m:60:5, address = 0x00000001067afc3c
2021-03-03 22:33:46.845935+0800 LLDB_学习[14452:1341031] 10
现在修改方法thread_return 返回字符串来进行测试
- (NSString *)thread_return {
return @"Hank";
}
(lldb) br set -r thread_return -C "thread return 'Cat'" -G true
崩溃:Thread 1: EXC_BAD_ACCESS (code=1, address=0x436174)
分析:野指针,返回的地址并不在堆栈上
// 现在进行如下操作,定义变量
(lldb) e -- NSString *$name = @"Cat"
(lldb) p $name
(NSTaggedPointerString *) $1 = 0xc55bb1dc31d6f026 @"Cat"
(lldb) e -- $name
(NSTaggedPointerString *) $2 = 0xc55bb1dc31d6f026 @"Cat"
(lldb) br set -r thread_return -C "thread return `$name`" -G true
Breakpoint 4: where = LLDB_学习`-[ViewController thread_return] + 12 at ViewController.m:68:5, address = 0x000000010c4eac2c
2021-03-03 22:46:42.705574+0800 LLDB_学习[14524:1351613] Cat
thread_return方法添加断点image.png
// 给下一行添加断点,地址看上图
(lldb) b 0x106e93c33
Breakpoint 5: where = LLDB_学习`-[ViewController thread_return] + 19 at ViewController.m:68:5, address = 0x0000000106e93c33
(lldb) re read rdi
rdi = 0x0000000106e960a0 @"Hank"
(lldb) po 0x0000000106e960a0
Hank
// 读上面地址32字节范围,本质读的是底层的结构体
(lldb) me read -s 8 -f x -c 4 -- 0x0000000106e960a0
0x106e960a0: 0x00007fff86d6d3c8 0x00000000000007c8
0x106e960b0: 0x0000000106e95d57 0x0000000000000004
(lldb) po (char *)0x0000000106e95d57
"Hank"
(lldb) image lookup -a 0x0000000106e95d57
Address: LLDB_学习[0x0000000100003d57] (LLDB_学习.__TEXT.__cstring + 76)
Summary: "Hank"
(lldb) po 0x00007fff86d6d3c8
__NSCFConstantString
(lldb) me read -s 8 -f x -c 4 -- 0x00007fff86d6d3c8
0x7fff86d6d3c8: 0x00007fff86d6d378 0x00007fff86d6d3a0
0x7fff86d6d3d8: 0x00007f9ccc833200 0x000200100000007f
(lldb) po 0x00007fff86d6d3a0
__NSCFString
...
一层层读下去,最终会打印到NSObject
现在通过修改内存地址,来改变thread_return返回值
- (NSString *)thread_return {
NSString *str = @"Cat";
return @"Hank";
}
// 获取str的地址
(lldb) e -f x -- (unsigned long *)str
(unsigned long *) $0 = 0x0000000102064020
(lldb) p str
(__NSCFConstantString *) $1 = 0x0000000102064020 @"Cat"
(lldb) me read -s 8 -f x -c 4 -- 0x0000000102064020
0x102064020: 0x00007fff86d6d3c8 0x00000000000007c8
0x102064030: 0x0000000102063d42 0x0000000000000003
// 基于上面地址往后偏移两个8字节,取的就是0x0000000102063d42
(lldb) e -f x -- *((unsigned long *)str +2)
(unsigned long) $3 = 0x0000000102063d42
(lldb) po (char *)0x0000000102063d42
"Cat"
// 接下来通过上面循环的方式打印出 "Hank" 的内存地址
(lldb) me write -s 8 `(unsigned long *)($rdi + 16)` 0x0000000102063d42
2021-03-03 23:30:42.705574+0800 LLDB_学习[14524:1351613] Cat
基于寄存器的方式来修改
(lldb) br set -S "-[ViewController thread_return]" -K false -o true
Breakpoint 2: where = LLDB_学习`-[ViewController thread_return] at ViewController.m:61, address = 0x000000010f417c00
(lldb) br command add 1
Enter your debugger command(s). Type 'DONE' to end.
> br s -a `*(unsigned long *)$rep` -o true
> c
> DONE
(lldb) e -f x -- *((unsigned long *)self.name +2)
(unsigned long) $3 = 0x0000000102063d42
(lldb) image lookup -a 0x0000000102063d42
Address: LLDB_学习[0x0000000100003d57] (LLDB_学习.__TEXT.__cstring + 76)
Summary: "Cat"
(lldb) me write -s 8 `(unsigned long *)($rdi + 16)` 0x0000000102063d42