欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

使用 LLDB 调试器调试 iOS 代码 (a)

最编程 2024-03-01 20:11:19
...

在一款完整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的用法和技巧还有很多,它可以大大提高我们调试代码的效率,有疏漏和错误之处,还望与志同道合的朋友共同学习进步。

推荐阅读