在一款完整iOS移动应用的开发中,代码的调试和编写占着同等重要的地位。Xcode默认使用LLDB作为代码调试器,LLDB功能丰富且强大,恰当的使用它,可以帮助开发者事半功倍的完成代码调试的工作。
1.expression代码执行指令
关于LLDB调试器,最常用的指令应该是p与po了,开发者常用这两个命令来进行对象的打印操作,p会打印出对象地址和类型,po则会额外打印出对象的值得内容,实际上,这两个命令都是expression相关命令的简写。expression命令也并非简单的打印命令,实际上它是一个执行代码命令,执行后将返回值进行打印,这个命令有一个十分强大的特点,它可以真实改变程序运行中变量的值。例如在如下代码中的int c = a+b 一行添加一个断点,运行工程。
int a = 0;
int b = 1;
int c = a+b;
NSLog(@"%d",c);
如果开发者不进行任何认为操作,此时打印出的值应该是1,为了测试,可以在调试区输入如下命令:
(lldb) expression a=1
此后跳过断点继续运行程序,可以看到打印的结果如下,c变成2。
(lldb) expression a=1
(int) $0 = 1
2016-04-24 11:39:40.213 BreakPointTest[1010:79065] 2
通过上面的演示,我们发现使用LLDB调试代码十分方便的一个特点,当我们知道程序某个地方可能会出现问题,为了找到解决方法,不使用LLDB时我们可能需要在代码中添加大量的打印函数,并且多次尝试修改源代码才能解决问题,如果使用LLDB的expression命令,我们不仅不需要添加额外的打印代码,也不需要直接修改源代码,在调试区进行多次调试,直到找到正确的修改方法后再对源代码修改一次即可。
2.frame代码堆栈块信息相关指令
当Xcode进入断点调试或者遇到异常程序崩溃时,在Xcode左侧的导航区都会将程序运行中的相关堆栈块信息列举出来,例如使用如下测试代码,在text方法中的int c = a+b 一行添加一个断点。
#import "ViewController.h"
@interface ViewController ()
{
int ab;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
ab = 1;
[self test];
}
-(void)test{
int a = 0;
int b = 1;
int c = a+b;
NSLog(@"%d",c);
}
@end
当程序运行到断点处断开时,Xcode左侧的堆栈块如下图所示:
从图中可以看出,程序当前处于激活状态的线程有5个,程序目前断在线程1中的test方法堆栈块中,使用frame info指令可以打印当前堆栈块的信息,示例如下:
(lldb) frame info
frame #0: 0x0000000102497905 BreakPointTest`-[ViewController test](self=0x00007fcd5b413320, _cmd="test") + 37 at ViewController.m:39
在打印的信息中,会有所在的文件名称和函数名称及堆栈块标号和内存地址。
在实际代码调试过程中,程序运行的回溯是一个重要的方法,例如上面的代码例子,虽然现在断点断在test方法中,开发者可能需要在viewDidLoad方法中进行相关调试,例如上面viewDidLoad方法中有一个变量ab,如果想查看ab变量的值,我们就需要将当前选中调试的堆栈块选择为viewDidLoad方法所在的堆栈块,从Xcode左侧导航区可以看到,viewDidLoad方法堆栈块的标号为1,执行如下LLDB指令即可切换:
(lldb) frame select 1
frame #1: 0x00000001024978cb BreakPointTest`-[ViewController viewDidLoad](self=0x00007fcd5b413320, _cmd="viewDidLoad") + 91 at ViewController.m:31
28 int a = 0;
29 int b = 1;
30 int c = a+b;
-> 31 NSLog(@"%d",c);
32 }
33 @end
从打印信息可以看到,现在选中的调试堆栈块已经切换到viewDidLoad方法,再使用expression指令时就可以操作这个方法中的相关变量了。
在使用LLDB工具前,遇到这样的情况,我往往会采用打多个断点,一步步追溯代码的运行过程并检查过程中变量的值是否正确,调试起来并不十分方便,如果不小心错过了某个断点,又要重新开始,通过选择调试的frame堆栈块可以十分方便的解决这个问题。
与frame相关的还有一个指令十分有用,下面的指令可以打印出当前堆栈块中所有对象的内容:
(lldb) frame variable
(ViewController *) self = 0x00007fcd5b413320
(SEL) _cmd = "test"
(int) a = 0
(int) b = 1
(int) c = 0
variable后面也可以添加参数名来打印特定对象的内容:
(lldb) frame variable a
(int) a = 0
c指令继续运行线程和process continue效果一样。
call指令运行一个表达式,和 expression 效果一样。
detach指令结束当前调试的线程。
di指令反汇编当前函数与disassemble相同。
exit指令退出lldb调试器。
finish指令完成当前堆栈块的调试,程序会继续运行。
n指令进行单步调试,与next作用一样。
p指令与expression作用一样。
print指令用于变量的打印。
r指令重新运行应用程序。
quit指令结束调试。
bugreport指令用于创建堆栈信息报告。
command history指令用于打印LLDB调试命令记录。
help指令用于查询LLDB相关调试指令的用法。
apropo指令用于查询某些包含某些关键字的指令。
version指令用于查询LLDB调试器的版本,如下:
(lldb) version
lldb-350.0.21.3
image list命令用于打印工程中所有用到的库文件。
image相关指令还有一个十分有用的命令,image lookup --address可以查询某个内存地址的内容,如下:
(lldb) image lookup --address 0x000000010373e885
Address: CoreFoundation[0x00000000000f4885] (CoreFoundation.__TEXT.__text + 996309)
Summary: CoreFoundation`-[__NSArray0 objectAtIndex:] + 101
image lookup --type用于查询某种类型中包含的属性,如下:
(lldb) image lookup --type UILabel
Best match found in /Users/vip/Library/Developer/Xcode/DerivedData/BreakPointTest-cearqrjqbntqcnfgiqzpxhyadewi/Build/Products/Debug-iphonesimulator/BreakPointTest.app/BreakPointTest:
id = {0x000082c1}, name = "UILabel", byte-size = 8, decl = UILabel.h:18, compiler_type = "@interface UILabel : UIView
@property ( getter = text,setter = setText:,readwrite,copy,nonatomic ) NSString * text;
@property ( getter = font,setter = setFont:,readwrite,nonatomic ) UIFont * font;
@property ( getter = textColor,setter = setTextColor:,readwrite,nonatomic ) UIColor * textColor;
@property ( getter = shadowColor,setter = setShadowColor:,readwrite,nonatomic ) UIColor * shadowColor;
@property ( getter = shadowOffset,setter = setShadowOffset:,assign,readwrite,nonatomic ) CGSize shadowOffset;
@property ( getter = textAlignment,setter = setTextAlignment:,assign,readwrite,nonatomic ) NSTextAlignment textAlignment;
@property ( getter = lineBreakMode,setter = setLineBreakMode:,assign,readwrite,nonatomic ) NSLineBreakMode lineBreakMode;
@property ( getter = attributedText,setter = setAttributedText:,readwrite,copy,nonatomic ) NSAttributedString * attributedText;
@property ( getter = highlightedTextColor,setter = setHighlightedTextColor:,readwrite,nonatomic ) UIColor * highlightedTextColor;
@property ( getter = isHighlighted,setter = setHighlighted:,assign,readwrite,nonatomic ) BOOL highlighted;
@property ( getter = isUserInteractionEnabled,setter = setUserInteractionEnabled:,assign,readwrite,nonatomic ) BOOL userInteractionEnabled;
@property ( getter = isEnabled,setter = setEnabled:,assign,readwrite,nonatomic ) BOOL enabled;
@property ( getter = numberOfLines,setter = setNumberOfLines:,assign,readwrite,nonatomic ) NSInteger numberOfLines;
@property ( getter = adjustsFontSizeToFitWidth,setter = setAdjustsFontSizeToFitWidth:,assign,readwrite,nonatomic ) BOOL adjustsFontSizeToFitWidth;
@property ( getter = baselineAdjustment,setter = setBaselineAdjustment:,assign,readwrite,nonatomic ) UIBaselineAdjustment baselineAdjustment;
@property ( getter = minimumScaleFactor,setter = setMinimumScaleFactor:,assign,readwrite,nonatomic ) CGFloat minimumScaleFactor;
@property ( getter = allowsDefaultTighteningForTruncation,setter = setAllowsDefaultTighteningForTruncation:,assign,readwrite,nonatomic ) BOOL allowsDefaultTighteningForTruncation;
@property ( getter = preferredMaxLayoutWidth,setter = setPreferredMaxLayoutWidth:,assign,readwrite,nonatomic ) CGFloat preferredMaxLayoutWidth;
@property ( getter = minimumFontSize,setter = setMinimumFontSize:,assign,readwrite,nonatomic ) CGFloat minimumFontSize;
@property ( getter = adjustsLetterSpacingToFitWidth,setter = setAdjustsLetterSpacingToFitWidth:,assign,readwrite,nonatomic ) BOOL adjustsLetterSpacingToFitWidth;
@end"
x指令可以读取某段内存的二进制数据:
(lldb) x 0x000000010373e885
0x10373e885: 66 66 2e 0f 1f 84 00 00 00 00 00 55 48 89 e5 48 ff.........UH..H
0x10373e895: 8d 3d 6d f2 28 00 e8 c0 d9 f0 ff 48 89 05 c1 58 .=m.(......H...X
LLDB的用法和技巧还有很多,它可以大大提高我们调试代码的效率,有疏漏和错误之处,还望与志同道合的朋友共同学习进步。