工具🔧LLVM

LLDB调试(一)

2021-03-03  本文已影响0人  浅墨入画

前言:

查看基于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
上一篇下一篇

猜你喜欢

热点阅读