天天看點

配合LLDB調試器進行iOS代碼調試(一)

在一款完整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。

(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{

當程式運作到斷點處斷開時,Xcode左側的堆棧塊如下圖所示:

配合LLDB調試器進行iOS代碼調試(一)

從圖中可以看出,程式目前處于激活狀态的線程有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

 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的用法和技巧還有很多,它可以大大提高我們調試代碼的效率,有疏漏和錯誤之處,還望與志同道合的朋友共同學習進步。

繼續閱讀