Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Debug Debug

Debug Debug

How to debug an iOS application

Weizhong Yang

October 22, 2012
Tweet

More Decks by Weizhong Yang

Other Decks in Technology

Transcript

  1. Debug Debug
    Crash Report 與各種常⾒見疑難雜症
    楊維中 a.k.a zonble
    [email protected]
    Friday, August 3,

    View full-size slide

  2. 講者
    • KKBOX 軟體⼯工程師
    • KKBOX Mac/iOS Client
    • ⼩小⿆麥注⾳音、Yahoo! 輸⼊入法、嘸蝦⽶米 X1、
    OpenVanilla
    • 其他有的沒的(台灣天氣 iPhone…etc)
    Friday, August 3,

    View full-size slide

  3. 今天的講題
    • 難度介於⾮非常初級到⾮非常深⼊入…因為新
    ⼿手⽼老⼿手寫 code 都會有 bug
    • 如何閱讀 Crash Report,判斷是哪種問
    題…
    • 也需要了解⼀一下常⾒見症狀
    • https://github.com/zonble/Crashy
    Friday, August 3,

    View full-size slide

  4. 摘要
    • 從哪裡找到 Crash Report
    • 從 Crash Report 找到程式錯在哪⼀一⾏行
    • EXC_BAD_ACCESS 錯誤
    • EXC_CRASH 錯誤
    • failed to resume in time
    • 還有⼀一個之前讓我抓了兩個星期的 Bug
    Friday, August 3,

    View full-size slide

  5. 什麼是 bug?
    • ⼀一般⽽而⾔言,程式運作不如預期,就可以
    視作是 bug
    • 但是其實很多⼈人會把 spec 當做 bug…
    • 今天主要討論的是各種 Crash、系統
    Framework 視為異常⾏行為,以及 OS 會
    把 app 給 kill 掉的狀況
    Friday, August 3,

    View full-size slide

  6. 找 Bug 的⼯工具
    • Debugger:GDB、LLDB
    • Crash Report
    Friday, August 3,

    View full-size slide

  7. 抓取 Crash Report 的
    ⽅方法
    • Xcode -> Organizer
    • Console.app(系統監視程式)
    • iTunes Connect
    • QuincyKit
    Friday, August 3,

    View full-size slide

  8. Friday, August 3,

    View full-size slide

  9. Friday, August 3,

    View full-size slide

  10. 閱讀 Crash Report
    • 判斷錯誤類型
    • 找到錯誤發⽣生在程式的哪個位置
    • 哪個 Thread
    • 第幾⾏行
    Friday, August 3,

    View full-size slide

  11. • 平時我們透過 50 pin 連接線連接 iOS
    Device 實機測試的時候,Xcode 可以幫
    我們將 crash log 中的記憶體位置,轉換
    成實際的程式碼⾏行數
    • 所以可以得到跟 gdb/lldb 的 backtrace ⼀一
    樣的內容
    Friday, August 3,

    View full-size slide

  12. 開發時 Crash 看到的 Back Trace
    Thread 0 name: Dispatch queue: com.apple.main-thread
    Thread 0 Crashed:
    0 libsystem_kernel.dylib
    0x3629632c 0x36285000 + 70444
    1 libsystem_c.dylib
    0x32de2208 0x32d95000 + 315912
    2 libsystem_c.dylib
    0x32ddb298 0x32d95000 + 287384
    3 libsystem_c.dylib
    0x32dedefa 0x32d95000 + 364282
    4 libsystem_c.dylib
    0x32deeb2a 0x32d95000 + 367402
    5 libsystem_c.dylib
    0x32d99934 0x32d95000 + 18740
    6 CoreFoundation
    0x35929730 0x35927000 + 10032
    7 CoreFoundation
    0x359296a4 0x35927000 + 9892
    8 Crashy
    0x000037dc -[CCRootTableViewController tableView:didSelectRowAtIndexPath:]
    (CCRootTableViewController.m:157)
    9 UIKit
    0x334c6936 0x33411000 + 743734
    10 UIKit
    0x33540620 0x33411000 + 1242656
    11 Foundation
    0x354fa92c 0x3545f000 + 637228
    12 CoreFoundation
    0x359b4a2c 0x35927000 + 580140
    13 CoreFoundation
    0x359b4692 0x35927000 + 579218
    14 CoreFoundation
    0x359b3268 0x35927000 + 574056
    15 CoreFoundation
    0x3593649e 0x35927000 + 62622
    16 CoreFoundation
    0x35936366 0x35927000 + 62310
    17 GraphicsServices
    0x375d2432 0x375ce000 + 17458
    18 UIKit
    0x33442cce 0x33411000 + 203982
    19 Crashy
    0x00002ca6 main (main.m:7)
    20 Crashy
    0x00002c4c start + 32
    Friday, August 3,

    View full-size slide

  13. • 但是我們常常會拿到與⺫⽬目前開發版本不
    ⼀一致的 log
    • Tester 還在⽤用之前的舊版
    • 已經 release 了
    • 如此只會看到⼀一堆記憶體位置
    Friday, August 3,

    View full-size slide

  14. 平常會拿到的是這種 log
    Thread 0 name: Dispatch queue: com.apple.main-thread
    Thread 0 Crashed:
    0 libsystem_kernel.dylib
    0x3629632c 0x36285000 + 70444
    1 libsystem_c.dylib
    0x32de2208 0x32d95000 + 315912
    2 libsystem_c.dylib
    0x32ddb298 0x32d95000 + 287384
    3 libsystem_c.dylib
    0x32dedefa 0x32d95000 + 364282
    4 libsystem_c.dylib
    0x32deeb2a 0x32d95000 + 367402
    5 libsystem_c.dylib
    0x32d99934 0x32d95000 + 18740
    6 CoreFoundation
    0x35929730 0x35927000 + 10032
    7 CoreFoundation
    0x359296a4 0x35927000 + 9892
    8 Crashy
    0x000157dc 0x13000 + 10204
    9 UIKit
    0x334c6936 0x33411000 + 743734
    10 UIKit
    0x33540620 0x33411000 + 1242656
    11 Foundation
    0x354fa92c 0x3545f000 + 637228
    12 CoreFoundation
    0x359b4a2c 0x35927000 + 580140
    13 CoreFoundation
    0x359b4692 0x35927000 + 579218
    14 CoreFoundation
    0x359b3268 0x35927000 + 574056
    15 CoreFoundation
    0x3593649e 0x35927000 + 62622
    16 CoreFoundation
    0x35936366 0x35927000 + 62310
    17 GraphicsServices
    0x375d2432 0x375ce000 + 17458
    18 UIKit
    0x33442cce 0x33411000 + 203982
    19 Crashy
    0x00014ca6 0x13000 + 7334
    20 Crashy
    0x00014c4c 0x13000 + 7244
    Friday, August 3,

    View full-size slide

  15. 要有當初的 Debug
    Symbol 才能解開
    • 上架時、給 tester 時,都應該保存當時
    的 Xcode Archive
    • 只記得當初是⽤用哪個 git revision 發⾏行然
    後重編也不⾒見得會編出同樣的東⻄西,因
    為我們中間可能會升級 Xcode
    • 使⽤用 Jenkins 等 CI 系統也是好主意
    Friday, August 3,

    View full-size slide

  16. Debug Symbol
    • 放在你的 .app.dSYM ⽂文件 bundle 中
    • 打開⽂文件 bundle,進⼊入
    .app.dSYM/Contents/Resources/
    DWARF
    Friday, August 3,

    View full-size slide

  17. atos
    • 將記憶體位置還原成程式所在位置
    • atos -arch armv7 -o -l

    Friday, August 3,

    View full-size slide

  18. Friday, August 3,

    View full-size slide

  19. EXC_BAD_ACCESS
    Friday, August 3,

    View full-size slide

  20. 記憶體管理
    • 有點懶得細講…
    • 有了 ARC 之後問題應該少很多
    • 跑⼀一下靜態分析多半可以找出問題
    • 原則其實很簡單,主要是要培養好習慣
    • 只要是成員變數與 getter 就 retain
    • dealloc 時 release 所有成員變數
    • ⼀一般 method 都回傳 auto release 物件
    Friday, August 3,

    View full-size slide

  21. 記憶體管理
    • Notifications
    • 不是 Push Notification,是指
    Notification Center
    • addObserver… 之後,dealloc 的時候要
    removeObserver:self
    • KVO 也⼀一樣要注意
    Friday, August 3,

    View full-size slide

  22. EXC_CRASH
    Friday, August 3,

    View full-size slide

  23. 常⾒見 Exception
    • 超出 Array 範圍
    • addObject:、setObject:forKey: 傳⼊入了 nil
    • 呼叫了物件不⽀支援的 selector
    • 還有很多…
    Friday, August 3,

    View full-size slide

  24. Raising Exceptions
    • assert
    • NSAssert
    • 可以⽤用 -DNS_BLOCK_ASSERTIONS=1
    在 release build 取消 NSAssert
    • [NSException raise: format:]…
    Friday, August 3,

    View full-size slide

  25. Try Catch
    • 我們可以使⽤用 @try @catch 語法捕捉
    NSException,⽤用法與許多其他語⾔言
    (Java、Python)⼀一樣
    • 但是慣例上,蘋果不建議使⽤用 @try
    @catch 做流程控制;原因是,
    NSException 往往被定義成「⼀一定不可以
    這樣寫」的程式錯誤
    Friday, August 3,

    View full-size slide

  26. NSError與NSException
    • 兩者並不相同
    • NSError是所謂的「使⽤用者錯誤」,例如
    網路連線抓不到東⻄西…等,我們可以將
    錯誤包裝成NSError物件,然後跳出錯誤
    提⽰示
    • NSException則是嚴重的程式錯誤
    Friday, August 3,

    View full-size slide

  27. 出現 Exception 的
    Crash Log
    Friday, August 3,

    View full-size slide

  28. 往每個 thread 裡頭是
    讀不到東⻄西的
    Thread 0 Crashed:
    0 libsystem_kernel.dylib
    0x3629632c __pthread_kill + 8
    1 libsystem_c.dylib
    0x32de2208 pthread_kill + 48
    2 libsystem_c.dylib
    0x32ddb298 abort + 88
    3 libc++abi.dylib
    0x35d98f64 abort_message + 40
    4 libc++abi.dylib
    0x35d96346 _ZL17default_terminatev + 18
    5 libobjc.A.dylib
    0x37d87350 _objc_terminate + 140
    6 libc++abi.dylib
    0x35d963be _ZL19safe_handler_callerPFvvE + 70
    7 libc++abi.dylib
    0x35d9644a std::terminate() + 14
    8 libc++abi.dylib
    0x35d9781e __cxa_rethrow + 82
    9 libobjc.A.dylib
    0x37d872a2 objc_exception_rethrow + 6
    10 CoreFoundation
    0x35936506 CFRunLoopRunSpecific + 398
    11 CoreFoundation
    0x35936366 CFRunLoopRunInMode + 98
    12 GraphicsServices
    0x375d2432 GSEventRunModal + 130
    13 UIKit
    0x33442cce UIApplicationMain + 1074
    14 Crashy
    0x00009ca6 main (main.m:7)
    15 Crashy
    0x00009c4c start + 32
    這只是看系統怎麼 throw Exception 還有 kill ⽽而已
    Friday, August 3,

    View full-size slide

  29. 要去看 Last Exception
    Backtrace
    Last Exception Backtrace:
    0 CoreFoundation
    0x359e088f __exceptionPreprocess + 163
    1 libobjc.A.dylib
    0x37d87259 objc_exception_throw + 33
    2 CoreFoundation
    0x359351d7 -[__NSArrayM insertObject:atIndex:] + 187
    3 Crashy
    0x0000a9a7 -[CCRootTableViewController
    tableView:didSelectRowAtIndexPath:] (CCRootTableViewController.m:191)
    4 UIKit

    0x334c693d -[UITableView
    _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 945
    5 UIKit

    0x33540627 -[UITableView
    _userSelectRowAtPendingSelectionIndexPath:] + 159
    6 Foundation
    0x354fa933 __NSFireDelayedPerform + 415
    7 CoreFoundation
    0x359b4a33
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 15
    8 CoreFoundation
    0x359b4699 __CFRunLoopDoTimer + 365
    9 CoreFoundation
    0x359b326f __CFRunLoopRun + 1207
    10 CoreFoundation
    0x359364a5 CFRunLoopRunSpecific + 301
    11 CoreFoundation
    0x3593636d CFRunLoopRunInMode + 105
    12 GraphicsServices
    0x375d2439 GSEventRunModal + 137
    13 UIKit
    0x33442cd5 UIApplicationMain + 1081
    Friday, August 3,

    View full-size slide

  30. 解開之前的樣⼦子
    Last Exception Backtrace:
    (0x359e088f 0x37d87259 0x359351d7 0xa9a7
    0x334c693d 0x33540627 0x354fa933 0x359b4a33
    0x359b4699 0x359b326f 0x359364a5 0x3593636d
    0x375d2439 0x33442cd5 0x9cad 0x9c54)
    ⼀一樣去⽤用 atos 查
    Friday, August 3,

    View full-size slide

  31. Console Log
    • 發⽣生 Exception 的時候,在 Console Log
    中,同時也會顯⽰示與錯誤有關的訊息,
    往往會⽐比 Crash Report 更有幫助
    Friday, August 3,

    View full-size slide

  32. UIKit
    Friday, August 3,

    View full-size slide

  33. 無窮迴圈
    - (void)loadView
    {

    self.view;
    }
    # self.view 是 nil 的時候會呼叫 -loadView
    Friday, August 3,

    View full-size slide

  34. 無窮迴圈
    [aView addSubView:aView]
    # UIKit 居然沒有阻⽌止這種事情…
    Friday, August 3,

    View full-size slide

  35. 不可以 push 另外⼀一個
    Navigation Controller
    UINavigationController *navController =
    [[UINavigationController alloc]
    initWithRootViewController:nil];
    [self.navigationController
    pushViewController:navController
    animated:YES];
    [navController release];
    Friday, August 3,

    View full-size slide

  36. ⼀一邊推⼀一邊拉會亂掉
    UIViewController *viewController =
    [[UIViewController alloc] init];
    [self.navigationController
    pushViewController:viewController animated:YES];
    [self.navigationController
    performSelector:@selector(popToRootViewController
    Animated:) withObject:nil afterDelay:0.1];
    [viewController release];
    # Push 的動畫時間為 0.25 秒
    Friday, August 3,

    View full-size slide

  37. failed to resume in time
    Friday, August 3,

    View full-size slide

  38. Console Log
    Jun 3 02:08:06 unknown SpringBoard[15] :
    net.zonble.Crashy failed to resume in time
    Jun 3 02:08:06 unknown SpringBoard[15] :
    Forcing crash report of Crashy[1473]...
    Jun 3 02:08:07 unknown SpringBoard[15] :
    Finished crash reporting.
    Friday, August 3,

    View full-size slide

  39. Crash Report
    Exception Type: 00000020
    Exception Codes: 0x8badf00d
    Highlighted Thread: 0
    Application Specific Information:
    net.zonble.Crashy failed to launch in time
    Elapsed total CPU time (seconds): 21.560 (user 21.560,
    system 0.000), 54% CPU
    Elapsed application CPU time (seconds): 19.573, 49%
    CPU
    Friday, August 3,

    View full-size slide

  40. failed to resume in time
    • 在 application:
    didFinishLaunchingWithOptions: 太久,被
    系統當做無回應被 kill 掉
    • 解法:想想有什麼東⻄西⾮非要在啟動時執
    ⾏行?
    • performSelector: withObject: afterDelay:
    • dispatch_after
    Friday, August 3,

    View full-size slide

  41. 另外…
    不良設計的
    Protocol/Delegate
    好危險的
    Friday, August 3,

    View full-size slide

  42. ⼀一個簡單的 delegate 設計
    @protocol CCBuggyObjectDelegate
    - (void)buggyObjectWillStart:(CCBuggyObject *)inObject;
    - (void)buggyObjectDidStart:(CCBuggyObject *)inObject;
    - (void)buggyObjectWillStop:(CCBuggyObject *)inObject;
    - (void)buggyObjectDidStop:(CCBuggyObject *)inObject;
    @end
    @interface CCBuggyObject : NSObject {

    id delegate;
    }
    - (void)start;
    - (void)stop;
    @property (assign, nonatomic) id delegate;
    @end
    Friday, August 3,

    View full-size slide

  43. 實作部分
    - (void)start
    {

    NSLog(@"start step 1");

    [delegate buggyObjectWillStart:self];

    NSLog(@"start step 2");

    [delegate buggyObjectDidStart:self];

    }
    - (void)stop
    {

    NSLog(@"Stop step 1");

    [delegate buggyObjectWillStop:self];

    NSLog(@"Stop step 2");

    [delegate buggyObjectDidStop:self];

    }
    Friday, August 3,

    View full-size slide

  44. delegate method 亂實作就
    爆炸了…
    - (void)buggyObjectWillStart:(CCBuggyObject *)inObject
    {

    [inObject start]; # 無窮迴圈
    }
    - (void)buggyObjectWillStart:(CCBuggyObject *)inObject
    {

    [inObject release]; # ⾃自殺⾏行為
    }
    Friday, August 3,

    View full-size slide

  45. 這樣實作 Table View
    Delegate 穩死無疑 :)
    - (NSInteger)numberOfSectionsInTableView:(UITableView
    *)tableView
    {
    [tableView reloadData];
    return 1;
    }
    # Table View 的 Data Source 與 Delegate method 的最⼤大
    差別,就在於 Data Source method 絕對不可以呼叫 -
    reloadData。
    Friday, August 3,

    View full-size slide

  46. 如果是這樣也會出事…
    - (void)buggyObjectWillStart:(CCBuggyObject *)inObject
    {
    if (something_happens()) {
    [inObject stop];
    }
    }
    # 我們⽤用下⼀一張投影⽚片解釋為什麼
    Friday, August 3,

    View full-size slide

  47. 因為 start 接下來的部分
    還是會繼續⾛走下去
    - (void)start
    {

    NSLog(@"start step 1");

    [delegate buggyObjectWillStart:self];
    #這邊呼叫 stop,但是會繼續⾛走到 start step 2,如
    果 stop 之後⾺馬上 start,這邊的 code 就會不預期的
    執⾏行了兩次

    NSLog(@"start step 2");

    [delegate buggyObjectDidStart:self];

    }
    Friday, August 3,

    View full-size slide

  48. 阻⽌止在呼叫了 stop 之後繼
    續往下⾛走…很醜
    - (void)start {
    stopEverCalled = YES;
    // start step 1;
    [delegate buggyObjectWillStart:self];
    if (stopEverCalled) return;
    # 沒事⼀一堆這種檢查真⾟辛苦…
    // start step 2;
    [delegate buggyObjectDidStart:self];

    }
    - (void)stop {
    stopEverCalled = YES;
    // Do something…
    }
    Friday, August 3,

    View full-size slide

  49. 正規的設計
    @protocol CCBuggyObjectDelegate
    - (BOOL)buggyObjectShouldStart:(CCBuggyObject
    *)inObject;
    - (void)buggyObjectDidStart:(CCBuggyObject *)inObject;
    - (BOOL)buggyObjectShouldStop:(CCBuggyObject
    *)inObject;
    - (void)buggyObjectDidStop:(CCBuggyObject *)inObject;
    @end
    Friday, August 3,

    View full-size slide

  50. 正規的設計
    - (void)start
    {
    // start step 1;
    if (![delegate buggyObjectShouldStart:self]) {
    return;
    }
    // start step 2;
    [delegate buggyObjectDidStart:self];

    }
    Friday, August 3,

    View full-size slide

  51. Das ist alles
    Friday, August 3,

    View full-size slide