lldbLow Lever Debug):默认内置于Xcode中的动态调试工具。标准的lldb提供了一组广泛的命令,旨在与老版本的GDB命令兼容。 除了使用标准配置外,还可以很容易地自定义lldb以满足实际需要

lldb语法:

  1. <command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
  • <command>(命令)和<subcommand>(子命令):lldb调试命令的名称
  • <action>:执行命令的操作
  • <options>:命令选项
  • <arguement>:命令的参数
  • []:表示命令是可选的,可以有也可以没有

示例:

  1. breakpoint set -n test
  • commandbreakpoint表示断点命令
  • actionset表示设置断点
  • option-n表示根据方法name设置断点
  • arguementtest表示方法名为tset
断点设置

日常开发中,最常用的是Xcode断点
iOS逆向实战--023:lldb调试技巧 - 图1

但是在逆向环境中,我们并没有第三方应用的源码,也不可能通过界面设置断点

这种情况,只能使用breakpoint指令,在Xcodelldb控制台,或直接在终端进行断点设置

案例1:

breakpoint指令的使用

打开ViewController.m文件,写入以下代码:

```

import “ViewController.h”

@implementation ViewController

void test1(){ NSLog(@”3”); }

  • (void)viewDidLoad { [super viewDidLoad]; }

-(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event { NSLog(@”1”); NSLog(@”2”); test1(); }

@end

  1. > `test1`函数设置断点
  2. >

breakpoint set -n test1

Breakpoint 2: where = 001—LLDB调试`test1 + 16 at ViewController.m:13:5, >address = 0x0000000102ef1cb4

  1. > - 对符号设置断点
  2. > - `set`是子命令
  3. > - `-n`是选项,是`--name`的缩写
  4. > - 显示`Breakpoint 2`,说明在`001--LLDB调试`的进程中,设置的`第2个`断点,`第1个`是使用`Xcode``touchesBegan`中设置的
  5. > 案例2
  6. > 对控制器的指定方法设置断点
  7. > 打开`ViewController.m`文件,写入以下代码:
  8. >

import “ViewController.h”

@implementation ViewController

  • (void)viewDidLoad { [super viewDidLoad]; }

  • (IBAction)save:(id)sender { NSLog(@”保存”); }

  • (IBAction)pause:(id)sender { NSLog(@”暂停”); }

  • (IBAction)continueGame:(id)sender { NSLog(@”继续”); }

@end

  1. > 在逆向环境中,运行第三方程序,使用暂停可进入`lldb`控制台,相当于`Debug`进程附加<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-5b3650a0dc12432a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > `ViewController`中的多个符号设置断点
  4. >

breakpoint set -n “-[ViewController save:]” -n “-[ViewController pause:]” -n “-[ViewController continueGame:]”

Breakpoint 1: 3 locations.

  1. > - `Breakpoint 1`表示`分组1`
  2. > - `3 locations`表示`分组1`下设置了`3个`断点
  3. > 使用`breakpoint list`查看断点
  4. >

breakpoint list

Current breakpoints: 1: names = {‘-[ViewController save:]’, ‘-[ViewController pause:]’, ‘-[ViewController continueGame:]’}, locations = 3, resolved = 3, hit count = 0 1.1: where = 001—LLDB调试-[ViewController save:] + 60 at ViewController.m:17:5, address = 0x0000000104995cd0, resolved, hit count = 0 1.2: where = 001--LLDB调试-[ViewController pause:] + 60 at ViewController.m:21:5, address = 0x0000000104995d28, resolved, hit count = 0 1.3: where = 001—LLDB调试`-[ViewController continueGame:] + 60 at ViewController.m:25:5, address = 0x0000000104995d80, resolved, hit count = 0

  1. > 案例3
  2. > 断点的禁用、启动和删除
  3. > 禁用断点
  4. >

breakpoint disable 1

1 breakpoints disabled.

  1. > - `分组1`下的所有断点禁用
  2. > 启用断点
  3. >

breakpoint enable 1

1 breakpoints enabled.

  1. > - `分组1`下的所有断点启用
  2. > 也可以对单一断点禁用或启用,例如:
  3. >

breakpoint disable 1.1

1 breakpoints disabled.

  1. > 删除`分组1`下的所有断点
  2. >

breakpoint delete 1

1 breakpoints deleted; 0 breakpoint locations disabled.

  1. > 删除全部断点
  2. >

breakpoint delete

About to delete all breakpoints, do you want to do that?: [Y/n] y All breakpoints removed. (1 breakpoint)

  1. > 不支持删除某单一断点,例如:
  2. >

breakpoint delete 4.1

0 breakpoints deleted; 1 breakpoint locations disabled.

  1. > 案例4
  2. > 了解更多`breakpoint`指令
  3. > 使用`help`指令
  4. >

help breakpoint

  1. Commands for operating on breakpoints (see 'help b' for shorthand.)

Syntax: breakpoint []

The following subcommands are supported:

  1. clear -- Delete or disable breakpoints matching the specified source
  2. file and line.
  3. command -- Commands for adding, removing and listing LLDB commands
  4. executed when a breakpoint is hit.
  5. delete -- Delete the specified breakpoint(s). If no breakpoints are
  6. specified, delete them all.
  7. disable -- Disable the specified breakpoint(s) without deleting them. If
  8. none are specified, disable all breakpoints.
  9. enable -- Enable the specified disabled breakpoint(s). If no breakpoints
  10. are specified, enable all of them.
  11. list -- List some or all breakpoints at configurable levels of detail.
  12. modify -- Modify the options on a breakpoint or set of breakpoints in
  13. the executable. If no breakpoint is specified, acts on the
  14. last created breakpoint. With the exception of -e, -d and -i,
  15. passing an empty argument clears the modification.
  16. name -- Commands to manage name tags for breakpoints
  17. read -- Read and set the breakpoints previously saved to a file with
  18. "breakpoint write".
  19. set -- Sets a breakpoint or set of breakpoints in the executable.
  20. write -- Write the breakpoints listed to a file that can be read in
  21. with "breakpoint read". If given no arguments, writes all
  22. breakpoints.

For more help on any particular subcommand, type ‘help ‘.

  1. > 也可以了解更多选项的参数设置,例如:
  2. >

help breakpoint set

  1. Sets a breakpoint or set of breakpoints in the executable.

Syntax: breakpoint set

Command Options Usage: breakpoint set [-DHd] -l [-G ] [-C ] [-c ] [-i ] [-o ] [-q ] [-t ] [-x ] [-T ] [-R

] [-N ] [-u ] [-f ] [-m ] [-s ] [-K ] breakpoint set [-DHd] -a [-G ] [-C ] [-c ] [-i ] [-o ] [-q ] [-t ] [-x ] [-T ] [-N ] [-s ] …

  1. > 案例5
  2. > 对包含字符串的符号设置断点
  3. > 使用`breakpoint set -n`,对`touchesBegan`方法设置断点
  4. >

breakpoint set -n touchesBegan:

Breakpoint 5: no locations (pending). WARNING: Unable to resolve breakpoint to any actual locations.

  1. > - 并没有设置上断点,因为完整的符号为`touchesBegan:withEvent:`
  2. > 使用`breakpoint set -r`,对包含`touchesBegan`的符号设置断点
  3. >

breakpoint set -r touchesBegan:

Breakpoint 7: 101 locations.

  1. > 使用`breakpoint set --selector`,对项目内指定名称的`selector`设置断点
  2. >

breakpoint set —selector touchesBegan:withEvent:

Breakpoint 9: 96 locations.

  1. > 案例6
  2. > 指定文件设置断点
  3. > 使用`breakpoint set --file`,在指定文件内设置断点
  4. >

breakpoint set —file ViewController.m —selector touchesBegan:withEvent:

Breakpoint 2: where = 001—LLDB调试`-[ViewController touchesBegan:withEvent:] + 92 at ViewController.m:30:4, address = 0x0000000100745d44

  1. > 案例7
  2. > `lldb`强大的缩写功能
  3. > 对包含`touchesBegan`的符号设置断点
  4. >

b -r touchesBegan

Breakpoint 3: 105 locations.

  1. > 查看断点的列表
  2. >

break list

  1. > 更简单的写法:
  2. >

br list

  1. > 对禁用断点`disable`选项的简写:
  2. >

br dis 1

1 breakpoints disabled.

  1. > 对启用断点`enable`选项的简写:
  2. >

br en 1

1 breakpoints enabled.

  1. #####代码执行
  2. > `expression`指令,用于执行代码,缩写指令为`p``exp`
  3. > 配合`p`指令使用的`po`指令,意思是`print object`,用于打印对象,本质上调用了对象的`description`
  4. > 案例1
  5. > 使用`expression`指令执行代码
  6. > `ViewController`中的`touchesBegan`设置断点
  7. >

br set -f ViewController.m -r touchesBegan

Breakpoint 2: where = 001—LLDB调试`-[ViewController touchesBegan:withEvent:] + 92 at ViewController.m:30:4, address = 0x0000000102de5d44

  1. > 点击屏幕,进入断点,使用`expression`指令执行代码
  2. >

expression self.view.subviews

(__NSArrayM *) $0 = 0x00000002822eaee0 @”3 elements”

  1. > 案例2
  2. > 设置背景色
  3. > 使用`expression`指令,设置`self.view`的背景色
  4. >

expression self.view.backgroundColor = [UIColor redColor]

error: :1:11: property ‘backgroundColor’ not found on object of type ‘UIView *’

  1. > - 报错,无法直接修改`backgroundColor`属性
  2. > 换一种方式,修改`layer`下的`backgroundColor`属性
  3. >

expression self.view.layer.backgroundColor = [UIColor yellowColor].CGColor

(CGColorRef) $7 = 0x00000002808a0d20

  1. > 案例3
  2. > 对数组追加元素
  3. > 打开`ViewController.m`文件,写入以下代码:
  4. >

import “ViewController.h”

import “Person.h”

@interface ViewController () @property(nonatomic, strong) NSMutableArray * models; @end

@implementation ViewController

  • (void)viewDidLoad { [super viewDidLoad];

    Person p1 = [[Person alloc] initWithName:@”one” age:1]; Person p2 = [[Person alloc] initWithName:@”two” age:2]; Person * p3 = [[Person alloc] initWithName:@”three” age:3];

    [self.models addObject:p1]; [self.models addObject:p2]; [self.models addObject:p3]; }

-(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event { NSLog(@”models:%@”,self.models); }

-(NSMutableArray *)models { if (!_models) { _models = [NSMutableArray array]; } return _models; }

@end

  1. > `ViewController`中的`touchesBegan`设置断点
  2. >

br set -f ViewController.m -r touchesBegan

Breakpoint 1: where = 001—LLDB调试`-[ViewController touchesBegan:withEvent:] + 84 at ViewController.m:33:25, address = 0x0000000100cc5a6c

  1. > 点击屏幕,进入`touchesBegan`方法的断点,追加数组元素
  2. >

p [self.models addObject:[[Person alloc] initWithName:@”haha” age:4]]

  1. > 使用`c``continue`)指令,继续执行
  2. >

c

001—LLDB调试[12107:2333568] models:( ““, ““, ““, ““ )

  1. > 输出的`models`数组中存储了`4个`对象,元素追加成功
  2. > 案例4
  3. > 修改数组中对象的属性
  4. > 点击屏幕,进入`touchesBegan`方法的断点,获取数组的第一个元素
  5. >

p (Person *)self.models.firstObject

(Person *) $4 = 0x0000000281043640

  1. > - `$4`为标号,代表`Person`对象,可以使用
  2. > 使用标号,修改对象`name`属性
  3. >

p $4.name=@”123”

(NSTaggedPointerString *) $5 = 0xb868e91f5b072f35 @”123”

  1. > 验证`name`属性是否修改成功
  2. >

po self.models.firstObject

  1. >

p ((Person *)0x281043640).name

(NSTaggedPointerString *) $10 = 0xb868e91f5b072f35 @”123”

  1. > 案例5
  2. > 执行多行代码
  3. > 点击屏幕,进入`touchesBegan`方法的断点
  4. > 通过`Option+Enter`进行换行,在`lldb`控制台写入以下代码:
  5. >

p Person * $tmp = self.models.firstObject; p $tmp.name = @”Zang”;

p $tmp.age = 18;

(NSTaggedPointerString *) $6 = 0x996ea49769521a4e @”Zang” (int) $7 = 18

  1. > 案例6
  2. > 其他流程控制的指令
  3. > 使用`c``continue`)指令,继续执行
  4. >

c

  1. > 使用`n``next`)指令,单步运行,将子函数当做整体一步执行
  2. >

n

  1. > 使用`s`指令,单步运行,遇到子函数会进去
  2. >

s

  1. #####堆栈信息
  2. > 案例1
  3. > 查看函数调用栈
  4. > 使用`bt`指令,查看函数调用栈<br />
  5. ![](https://upload-images.jianshu.io/upload_images/9297953-cfb223159f11fadf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  6. > - `*`指向当前的函数调用栈
  7. > 使用`up`指令,查看上一个函数
  8. >

up

frame #1: 0x0000000100151c68 001—LLDB调试`test1 at >ViewController.m:22:5 19 } 20
21 void test1(){ -> 22 test2(); ^ 23 } 24
25 void test2(){

  1. > 同时`*`也指向上一个函数<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-214417d3fc4cab2a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > 使用`down`指令,查看下一个函数
  4. >

down

frame #0: 0x0000000100151c80 001—LLDB调试`test2 at >ViewController.m:26:4 23 } 24
25 void test2(){ -> 26 NSLog(@”test2”); ^ 27 } 28
29 @end

  1. > 使用`frame select`指令,选择指定函数
  2. >

frame select 10

frame #10: 0x000000019ef67480 UIKitCore__eventFetcherSourceCallback + 156 UIKitCore__eventFetcherSourceCallback: -> 0x19ef67480 <+156>: ldr x0, [x19, #0x10] 0x19ef67484 <+160>: mov x1, x21 0x19ef67488 <+164>: bl 0x19c2d1e28 0x19ef6748c <+168>: mov x1, x0

  1. > 使用上述指令,可以将断点定位到指定函数。它的作用可以查看函数的调用者,通过汇编代码分析参数的传递。但寄存器的环境并不会发生变化,数据保存的还是最后一个函数执行完毕的结果
  2. > 案例2
  3. > 查看方法的参数和局部变量
  4. > 打开`ViewController.m`文件,写入以下代码:
  5. >

import “ViewController.h”

@implementation ViewController

  • (void)viewDidLoad { [super viewDidLoad]; }

-(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event { [self lalala1:@”HAHA”]; }

-(void)lalala1:(NSString *)str{ [self lalala2:str]; }

-(void)lalala2:(NSString )str{ NSString str1 = @”Zang”; NSLog(@”%@:%@”,str1, str); }

@end

  1. > 点击屏幕,进入`lalala2`方法的断点,使用`frame variable`指令,查看方法的参数和局部变量
  2. >

frame variable

(ViewController ) self = 0x0000000105506860 (SEL) _cmd = “lalala2:” (__NSCFConstantString ) str = 0x0000000104f94080 @”HAHA” (__NSCFConstantString *) str1 = 0x0000000104f940a0 @”Zang”

  1. > 在逆向过程中,进入一个方法,最想看到的就是该方法的调用者、方法名称、参数等信息,我们可以使用`frame variable`指令进行查看。还可以配合`up``down``frame select`指令,查看调用栈中其他方法的信息
  2. > 案例3
  3. > 修改方法的参数
  4. > 上述案例,点击屏幕,进入`lalala2`方法的断点
  5. > 使用`frame variable`指令,查看方法的参数和局部变量
  6. >

frame variable

(ViewController ) self = 0x0000000100708b60 (SEL) _cmd = “lalala2:” (__NSCFConstantString ) str = 0x0000000100124080 @”HAHA” (__NSCFConstantString *) str1 = 0x00000001001240a0 @”Zang”

  1. > 使用`p`指令,修改`str`的值
  2. >

p str = @”COOL”

(NSTaggedPointerString *) $0 = 0xb4f673513386c4f4 @”COOL”

  1. > 使用`c`指令,继续运行
  2. >

c

001—LLDB调试[13539:2617881] Zang:COOL

  1. > 输出结果变为修改后的内容,只针对当前未执行完的方法有效。对于已经执行完的方法,修改里面的内容,并不影响最终的结果
  2. > 案例4
  3. > 让方法直接返回,不执行里面的代码
  4. > 打开`ViewController.m`文件,写入以下代码:
  5. >

import “ViewController.h”

@implementation ViewController

  • (void)viewDidLoad { [super viewDidLoad]; }

-(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event { [self check:@”HOOK”]; NSLog(@”一切正常~”); }

-(void)check:(NSString *)str{

if([str isEqualToString:@”HOOK”]){ NSLog(@”有人HOOK我…”); exit(0); } }

@end

  1. > 点击屏幕,进入`check`方法的断点
  2. > 使用`thread return`指令,让`check`方法直接返回,不执行里面的代码
  3. >

thread return

  1. > 使用`frame variable`指令,查看`thread return`指令执行后的函数
  2. >

frame variable

(ViewController ) self = 0x0000000100e07ee0 (SEL) _cmd = “touchesBegan:withEvent:” (__NSSetM ) touches = 0x0000000283e4dbc0 1 element (UITouchesEvent *) event = 0x0000000280b61200

  1. > - 已经回到`touchesBegan:withEvent:`方法
  2. > 使用`c`指令,继续运行
  3. >

c

001—LLDB调试[13574:2625998] 一切正常~

  1. > - `check`方法直接返回,方法内验证`str`参数的代码没有被触发
  2. > `thread return`指令,可用于调试阶段,绕过指定方法
  3. > 使用场景:原本执行到某方法,执行就会中断。使用`thread return`指令绕过方法,如果可以正常执行,证明此方法为检测方法。后续可针对不同情况,选择`Method Swizzle``fishHook``InlineHook`对其进行`HOOK`,将方法直接`return`
  4. #####内存断点
  5. > 案例1
  6. > 在对象的属性上设置断点
  7. > `p1`修改`name`属性时,设置断点<br />
  8. ![](https://upload-images.jianshu.io/upload_images/9297953-5c6a3664e29c8418.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  9. > 进入断点,使用`watchpoint`指令,在`p1`对象的`name`上设置断点
  10. >

watchpoint set variable p1->_name

Watchpoint created: Watchpoint 1: addr = 0x280207570 size = 8 state = enabled type = w declare @ ‘/Users/zang/Zang/Spark/LG/14/LLDB/001—LLDB调试/ViewController.m:13’ watchpoint spec = ‘p1->_name’ new value: 0x0000000102404088

  1. > 使用`c`指令,继续运行
  2. >

c

Watchpoint 1 hit: old value: 0x0000000102404088 new value: 0x00000001024040a8

  1. > 使用`po`指令
  2. >

po 0x0000000102404088

one

  1. >

po 0x00000001024040a8

new

  1. > 当调用`name`属性的`get/set`方法,都会触发此断点。可获取到`name`属性的原始值,和即将修改的值。配合`bt`指令,查看函数调用栈,可以跟踪`name`属性的修改是由哪个方法触发的
  2. > 案例2
  3. > 对属性地址设置内存断点
  4. > 进入断点,获取`name`属性的地址
  5. >

p &p1->_name

(NSString **) $0 = 0x0000000281e2ebb0

  1. > 使用`watchpoint`指令,对属性地址设置内存断点
  2. >

watchpoint set expression 0x0000000281e2ebb0

Watchpoint created: Watchpoint 1: addr = 0x281e2ebb0 size = 8 state = enabled type = w new value: 4371808392

  1. > 使用`c`指令,继续运行
  2. >

c

Watchpoint 1 hit: old value: 4371808392 new value: 4371808424

  1. > 使用`po`指令
  2. >

po 4371808392

one

  1. >

po 4371808424

new

  1. #####其他指令
  2. > 案例1
  3. > 当分组下断点被触发,自动执行指令
  4. > `touchesBegan`方法设置断点
  5. >

br set -f ViewController.m -r touchesBegan:

Breakpoint 3: where = 001—LLDB调试`-[ViewController touchesBegan:withEvent:] + 84 at ViewController.m:20:26, address = 0x0000000104945a6c

  1. > `分组3`断点,设置进入断点后的执行的指令
  2. >

br command add 3

Enter your debugger command(s). Type ‘DONE’ to end. >

  1. > 输入指令
  2. >

frame variable DONE

  1. 使用`c`指令,继续运行

c

  1. 点击屏幕,进入`touchesBegan`方法的断点,同时输出以下信息:

frame variable (ViewController ) self = 0x0000000103507fc0 (SEL) _cmd = “touchesBegan:withEvent:” (__NSSetM ) touches = 0x0000000282cea2e0 1 element (UITouchesEvent ) event = 0x00000002819c6640 (Person ) p1 = 0x00000001e3fe2f18

  1. 案例2
  2. 当任何断点被触发,自动执行指令

target stop-hook add -o “frame variable”

Stop hook #1 added.

  1. 查看`stop-hook`的指令列表

target stop-hook list

Hook: 1 State: enabled Commands: frame variable

  1. 删除某一条指令

target stop-hook delete 1

undisplay 1

  1. 删除全部指令

target stop-hook delete

Delete all stop hooks?: [Y/n] y

  1. 禁用某一条指令

target stop-hook disable 1

  1. 启用某一条指令

target stop-hook enable 1

  1. 添加执行代码

display self.view

  1. - 等同于`expr -- self.view`
  2. 对于`frame variable`指令,基本上每个断点触发后都要使用。但`lldb`每次启动都要重新配置命令,有没有一劳永逸的方法呢?
  3. 案例3
  4. 配置`lldb`初始化文件
  5. 在家目录下,存储了`lldb`的初始化文件

cd / ls

  1. ![](https://upload-images.jianshu.io/upload_images/9297953-618035172f2db5bb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  2. `lldbinit`文件的作用,当`lldb`启动,就会加载此文件,执行文件内的指令
  3. 使用`vi ~/.lldbinit`,写入以下指令:

target stop-hook add -o “frame variable”

  1. 运行项目,`lldb`启动,输出以下内容:

Stop hook #1 added.

  1. 进入`viewDidLoad`断点,输出以下内容:

(ViewController *) self = 0x0000000135f093a0 (SEL) _cmd = “viewDidLoad” ```

总结

断点设置

  • breakpoint set -n xxx:对方法/函数名称设置断点
  • breakpoint set -r xxx:对包含字符串的符号设置断点
  • breakpoint set --selector xxx:对项目内指定名称的selector设置断点
  • breakpoint set --file xxx:在指定文件中设置断点
  • breakpoint list:查看断点列表
  • breakpoint disable:禁用断点
  • breakpoint enable:启用断点
  • breakpoint delete:删除断点
  • 缩写:breakbr,设置断点可缩写指令:b

代码执行

  • po指令:意思是print object,用于打印对象,本质上调用了对象的description
  • expression指令:用于执行代码
    ◦ 缩写:expp
    ◦ 可以使用标号
    ◦ 可执行多行代码
  • 流程控制
    ccontinue)指令:继续执行
    nnext)指令:单步运行,将子函数当做整体一步执行
    ni指令:单步运行汇编级别
    s指令:单步运行,遇到子函数会进去
    si指令:单步运行可跳转指令内部,汇编级别
    finish指令:直接走完当前方法,返回到上层frame

堆栈信息

  • bt指令:查看函数调用栈
  • up指令:查看上一个函数
  • down指令:查看下一个函数
  • frame select指令:选择指定函数
  • frame variable指令:查看方法调用者、方法名称、参数和局部变量
  • thread return指令:让方法直接返回,不执行里面的代码

内存断点:

  • watchpoint指令,设置内存断点

其他指令

  • br command add指令:给断点添加命令的命令
  • target stop-hook add -o "xxx":每次stop的时候去执行一些命令,针对breadpointwatchpoint
  • 配置lldb初始化文件,当lldb启动,就会加载此文件,执行文件内的指令,一劳永逸

lldb更多文档