2013年9月4日 星期三

Crash Report 介紹



Crash Report簡介

iOS的app在當機的時候,操作系統通常會生成一個crash report,保存在iOS的設備上,並傳到iTunes Connect上,而crash report上面通常會有許多有用的信息,來讓我們追縱當初發生crash時的情況。一般情況下,上面會有每一個正在執行thread的back trace,所以我們能從這些資訊上面來了解到,thread在crash的當下正在做什麼事情,並且能分辨出crash在哪個thread上。

發生Crash的情況

通常最主要發生crash的情況有兩種:

1、違反Mac系統規則

2、Bug

違反iOS的規則包括在程式啟動,恢複,hangUp,程式退出,這個時候的watchDog超時,以及使用者強制退出跟low memory。

WatchDog 超時

iOS 4.x開始,離開app的時候,app不會立即停止,而是退到後台10min。
但是,如果你的app在appDelegate裡面的method耗太久的時間的話,那麼apple就有可能會終止你的app,並且產生一個crash report。
  • application:didFinishLaunchingWithOptions:
  • applicationWillResignActive:
  • applicationDidEnterBackground:
  • applicationWillEnterForeground:
  • applicationDidBecomeActive:
  • applicationWillTerminate:


如果你的app在一開始的時候就有需要跟service連結,並且你將他放置在以上的delegate下的話,建議改用non sync的方式處理,總之在以上的delegate 耗的時間越少越好,NSOperation,或是GCD都是不錯的選擇。
用户强制退出
iOS 4.x開始支持multi task,如果app 太lag卡在某一個畫面,使用者可以透過雙擊Home鍵來停止APP,  這時候apple 將會生成一個crash report。

Memory Warning

在執行中的app是擁有對記憶體的最高使用權限,但這並不表示app可以使用所有的記憶體,每一個app都只能使用一部份可用的memory 。
當memory使用到達一定的程度時,apple則會發出一個UIApplicationDidReceiveMemoryWarningNotification的通知。
此時,為了讓app能正常動作,apple會開始砍掉一些在後台的app。當所有app都被砍光了以後,如果你的app還需要更多的memory,那麼apple也會將你的app也stop掉,並且產生一個crash report。
另外補充說明一點,這時候後台的app,並不會因此而產生crash report。

BUG

這算是程式設計師最常遇到的問題了,而他的種類繁多,以下大致上歸類出幾種較常發生的問題,並且一步步的解析,希望對大家有所幫助。

Crash Report實例

//1、app 訊息
Incident Identifier: 996E2337-6952-468E-90FE-D317CE417943
CrashReporter Key:   8dda5438858aa4dddbe541af415c70e691032e33
Hardware Model:      iPod5,1
Process:         BaseProject [636]
Path:            /var/mobile/Applications/91BAE6E4-2628-4152-BB64-6452DAEF4C57/BaseProject.app/BaseProject
Identifier:      BaseProject
Version:         ??? (???)
Code Type:       ARM (Native)
Parent Process:  debugserver [635]
//2、基本訊息
Date/Time:       2013-08-28 18:57:16.567 +0800
OS Version:      iOS 6.1.3 (10B329)
Report Version:  104
//3、exception
Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Crashed Thread:  0
//4、back trace
Last Exception Backtrace:
0   CoreFoundation                0x3127629e __exceptionPreprocess + 158
1   libobjc.A.dylib               0x38f5a97a objc_exception_throw + 26
2   CoreFoundation                0x31279e02 -[NSObject(NSObject) doesNotRecognizeSelector:] + 166
3   CoreFoundation                0x3127852c ___forwarding___ + 388
4   CoreFoundation                0x311cff64 _CF_forwarding_prep_0 + 20
5   Foundation                    0x31b84272 __NSFireDelayedPerform + 446
6   CoreFoundation                0x3124b5da __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 10
7   CoreFoundation                0x3124b28c __CFRunLoopDoTimer + 268
8   CoreFoundation                0x31249efc __CFRunLoopRun + 1228
9   CoreFoundation                0x311bceb8 CFRunLoopRunSpecific + 352
10  CoreFoundation                0x311bcd44 CFRunLoopRunInMode + 100
11  GraphicsServices              0x34d842e6 GSEventRunModal + 70
12  UIKit                         0x330d22fc UIApplicationMain + 1116
13  BaseProject                     0x0007879c main (main.m:19)
14  BaseProject                     0x00076f7c start + 36

//5、thread back trace
Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib        0x39458350 __pthread_kill + 8
1   libsystem_c.dylib             0x393cf11e pthread_kill + 54
2   libsystem_c.dylib             0x3940b96e abort + 90
3   libc++abi.dylib               0x389a9d4a abort_message + 70
4   libc++abi.dylib               0x389a6ff4 default_terminate() + 20
5   libobjc.A.dylib               0x38f5aa74 _objc_terminate() + 144
6   libc++abi.dylib               0x389a7078 safe_handler_caller(void (*)()) + 76
7   libc++abi.dylib               0x389a7110 std::terminate() + 16
8   libc++abi.dylib               0x389a8594 __cxa_rethrow + 84
9   libobjc.A.dylib               0x38f5a9cc objc_exception_rethrow + 8
10  CoreFoundation                0x311bcf1c CFRunLoopRunSpecific + 452
11  CoreFoundation                0x311bcd44 CFRunLoopRunInMode + 100
12  GraphicsServices              0x34d842e6 GSEventRunModal + 70
13  UIKit                         0x330d22fc UIApplicationMain + 1116
14  BaseProject                     0x0007879c main (main.m:19)
15  BaseProject                     0x00076f7c start + 36

Thread 1 name:  Dispatch queue: com.apple.libdispatch-manager
Thread 1:
0   libsystem_kernel.dylib        0x39448648 kevent64 + 24
1   libdispatch.dylib             0x39378974 _dispatch_mgr_invoke + 792
2   libdispatch.dylib             0x39378654 _dispatch_mgr_thread$VARIANT$mp + 32

Thread 2:
0   libsystem_kernel.dylib        0x39458d98 __workq_kernreturn + 8
1   libsystem_c.dylib             0x393a6cf6 _pthread_workq_return + 14
2   libsystem_c.dylib             0x393a6a12 _pthread_wqthread + 362
3   libsystem_c.dylib             0x393a68a0 start_wqthread + 4

Thread 3:
0   libsystem_kernel.dylib        0x39458d98 __workq_kernreturn + 8
1   libsystem_c.dylib             0x393a6cf6 _pthread_workq_return + 14
2   libsystem_c.dylib             0x393a6a12 _pthread_wqthread + 362
3   libsystem_c.dylib             0x393a68a0 start_wqthread + 4

Thread 4 name:  WebThread
Thread 4:
0   libsystem_kernel.dylib        0x39447eb4 mach_msg_trap + 20
1   libsystem_kernel.dylib        0x39448048 mach_msg + 36
2   CoreFoundation                0x3124b040 __CFRunLoopServiceMachPort + 124
3   CoreFoundation                0x31249d9e __CFRunLoopRun + 878
4   CoreFoundation                0x311bceb8 CFRunLoopRunSpecific + 352
5   CoreFoundation                0x311bcd44 CFRunLoopRunInMode + 100
6   WebCore                       0x371bf500 RunWebThread(void*) + 440
7   libsystem_c.dylib             0x393b130e _pthread_start + 306
8   libsystem_c.dylib             0x393b11d4 thread_start + 4

//6、thread state
Thread 0 crashed with ARM Thread State (32-bit):
    r0: 0x00000000    r1: 0x00000000      r2: 0x00000000      r3: 0x3af18534
    r4: 0x00000006    r5: 0x3af18b88      r6: 0x1d840144      r7: 0x2fd8b9c4
    r8: 0x1d840120    r9: 0x00000300     r10: 0x00000000     r11: 0x000ced5c
    ip: 0x00000148    sp: 0x2fd8b9b8      lr: 0x393cf123      pc: 0x39458350
  cpsr: 0x00000010
//7、二進制映像檔
Binary Images:
   0x75000 -    0xdcfff +BaseProject armv7  <ce7c736fdbac3cdfaa3c0ff31eef0122> /var/mobile/Applications/91BAE6E4-2628-4152-BB64-6452DAEF4C57/BaseProject.app/BaseProject
  0x1eb000 -   0x1effff  AccessibilitySettingsLoader armv7  <d910faf80cd632cc8f0d9f05d3944144> /System/Library/AccessibilityBundles/AccessibilitySettingsLoader.bundle/AccessibilitySettingsLoader
0x2fe71000 - 0x2fe91fff  dyld armv7  <280610df5ed43ec7aa00629a27009302> /usr/lib/dyld
0x3047e000 - 0x3047efff  Accelerate armv7  <b68ff92e404931f3bcb6361720f77724> /System/Library/Frameworks/Accelerate.framework/Accelerate
0x3047f000 - 0x305bdfff  vImage armv7  <30522b92940d3dd184c8e46780594048> /System/Library/Frameworks/Accelerate.framework/Frameworks/vImage.framework/vImage
0x305be000 - 0x306a1fff  libBLAS.dylib armv7  <d8edada1cea133458ca779e34a3a7f88> /System/Library/Frameworks/Accelerate.framework/Frameworks/vecLib.framework/libBLAS.dylib
0x306a2000 - 0x30957fff  libLAPACK.dylib armv7  <9e08aead79d13043bab622402a270fba> /System/Library/Frameworks/Accelerate.framework/Frameworks/vecLib.framework/libLAPACK.dylib
0x30958000 - 0x309b1fff  libvDSP.dylib armv7  <09e2a5e3e9203950890ba57592523132> 
(以下略)


1、app訊息:這個是crash app的一些相關資訊


  • Incident Identifier是crash report的id,也就是上一篇提到的UUID
  • CrashReporter Key可以把他想像成是設備的ID,但並不是真的HW的ID,而只是UUID對應這個HW產生的一組ID,而這個ID通常可以拿來應用在大量的report裡面發現時常發生crash的是否是同一台設備。
  • Hardware Model就如同看到的一樣 iPod5.1,剩下就BJ4
2、基本訊息:上面記錄著crash 發生的時間跟設備的版本號,以及app的版本,可以藉由這邊知道發生問題的版本,是什麼居多。

3、exception:這邊大都記錄著crash時apple throw的exception type,舉個例子像是你看到 0x8badf00d,像是這種時候大多都是上面提到的watch dog超時被hight light了。0x8badf00d -> (8 bad food) -> (ate bad food)。
或者是SIGABRT之類的

4、back trace:這邊就是最主要的關鍵了,這邊記錄著crash的時候,當下是正在執行哪一行code。執行的順序是依次由14的start去呼叫13的main,再由13的main去呼叫UIApplicationMain以此類推回去一直到0的時候exception handler那邊發現了錯誤,而產生report。數字旁邊記錄的名字像是BaseProject這個都是二進制映像檔的名稱。

5、thread back trace:如同back trace,而這邊再詳細述敘出各個thread的back trace

6、thread state:這個是當下ARM裡面每一個暫存器的狀態。

7、二進制映像檔:這邊是印出crash的時候加載的映像檔,以及映像檔的memory stack的區間。(上一篇提到相對的address 換算的公式就是要取得crash時當下的映像檔的起始位置)

Memory 不足

Incident Identifier: 996E2337-6952-468E-90FE-D317CE417943
CrashReporter Key:   8dda5438858aa4dddbe541af415c70e691032e33
Date/Time:       2013-08-28 18:57:16.567 +0800
OS Version:      iOS 6.1.3 (10B329)
Free pages:                96
Wired pages:       10558
Purgeable pages:        0
Largest process:   BaseProject
Processes
Name                                         UUID                         Count resident pages
BaseProject                   9320 (jettisoned) (active)
mediaserverd                  255
dataaccessd                    505
syslogd                               71
apsd                                  171
securityd                           243
notifyd                             2027
CommCenter                   189
SpringBoard                  2158 (active)
accessoryd                         91
configd                              371
fairplayd                              93
mDNSResponder           292
lockdownd                     1204
launchd                               72

當發生以上問題的時候,只能藉由Instruments裡面Allocations和Leaks來分析記憶體分配問跟記憶體洩漏的問題了。

Exception Decode:

這邊在補充說明一下,通常Exception是由文字開頭,然候後面有一串16進位,這個數值就是crash的原因,在這邊可以知道crash是什麼種類的錯誤。除了如同上述提到的0x8badf00d 以外還有
  • 0xbad22222:表示VoIP應用過於頻繁而中斷。
  • 0xdeadl0cc:"Dead Look",代表後台佔用到系統資源而被中斷。
  • 0xdeadfa11:"Dead Fail",當使用者在執行app的過程中,長按關機鍵,當出現"滑動來關機"以後再長按HOME鍵,用來強制關閉app的時候產生的異常編碼。

以上,讓我們更加清楚crash檔該怎麼閱讀,下一篇會介紹如何trace,back trace的資訊。

2013年9月2日 星期一

Crash and dSYM

因為近來的工作需要,所以開始著手研究了一下exception的問題,其實在iOS中,APPLE非常人性化的在APP Crash的時候,會自動在手機裡面生成一個.crash file 給我們。

而通常我們拿到這個檔案,如果單純從客戶端那邊看(沒有 .dSYM 檔的話) 是會看到以下的情形

.Crash File
// 1:Thread 資訊
Incident Identifier: CAF9ED40-2D59-45EA-96B0-52BDA1115E9F
CrashReporter Key:   30af939d26f6ecc5f0d08653b2aaf47933ad8b8e
Process:         TestEditor [12506]
Path:            /var/mobile/Applications/60ACEDBC-600E-42AF-9252-42E32188A044/TestEditor.app/TestEditor
Identifier:      TestEditor
Version:         ??? (???)
Code Type:       ARM (Native)
Parent Process:  launchd [1]
// 2:基本資訊
Date/Time: 2013-09-23 11:25:56.357 +0900 OS Version: iPhone OS 3.1.3 (7E18) Report Version: 104
// 3:例外
Exception Type: EXC_BAD_ACCESS (SIGBUS) Exception Codes: KERN_PROTECTION_FAILURE at 0x00000059 Crashed Thread: 0
// 4:Back Trace
Thread 0 Crashed: 0 UIKit 0x332b98d8 0x331b2000 + 1079512 1 UIKit 0x3321d1a8 0x331b2000 + 438696 2 UIKit 0x3321d028 0x331b2000 + 438312 3 UIKit 0x332b9628 0x331b2000 + 1078824 4 UIKit 0x33209d70 0x331b2000 + 359792 5 UIKit 0x33209c08 0x331b2000 + 359432 6 QuartzCore 0x324cc05c 0x324ae000 + 122972 7 QuartzCore 0x324cbe64 0x324ae000 + 122468 8 CoreFoundation 0x3244f4bc 0x323f8000 + 357564 9 CoreFoundation 0x3244ec18 0x323f8000 + 355352 10 GraphicsServices 0x342e91c0 0x342e5000 + 16832 11 UIKit 0x331b5c28 0x331b2000 + 15400 12 UIKit 0x331b4228 0x331b2000 + 8744 13 TestEditor 0x00002c3a 0x1000 + 7226 14 TestEditor 0x00002c04 0x1000 + 7172
// 5:Thread State
Thread 0 crashed with ARM Thread State (32-bit):
    r0: 0x00000000    r1: 0x00000000      r2: 0x00000001      r3: 0x39529fc8
    r4: 0xffffffff    r5: 0x2fd7d301      r6: 0x2fd7d300      r7: 0x2fd7d9d0
    r8: 0x2fd7d330    r9: 0x3adbf8a8     r10: 0x2fd7d308     r11: 0x00000032
    ip: 0x00000025    sp: 0x2fd7d2ec      lr: 0x001bdb25      pc: 0x30301838
  cpsr: 0x00000010
// 6:二進制映像
Binary Images: 0x1000 - 0x7fff +TestEditor armv7 /var/mobile/Applications/60ACEDBC-600E-42AF-9252-42E32188A044/TestEditor.app/TestEditor

我們可以看到4:Back Trace 那邊,後面的記憶體位置 對我們而言沒任何的幫助。

因此,如果遇到這種情況的話,我們就必須將這些memory address 丟到memory stack 下去尋找。
而apple也提供了我們一個很好用的工具 symbolicatecrash 我們只需將 crash file與 dSYM file一併給他,就可以得到以下的結果

格式 : symbolicatecrash [crash file] [dSYM file]

Thread 0 Crashed:
0   UIKit                           0x332b98d8 -[UIWindowController transitionViewDidComplete:fromView:toView:] + 668
1   UIKit                           0x3321d1a8 -[UITransitionView notifyDidCompleteTransition:] + 160
2   UIKit                           0x3321d028 -[UITransitionView _didCompleteTransition:] + 704
3   UIKit                           0x332b9628 -[UITransitionView _transitionDidStop:finished:] + 44
4   UIKit                           0x33209d70 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 284
5   UIKit                           0x33209c08 -[UIViewAnimationState animationDidStop:finished:] + 60
6   QuartzCore                      0x324cc05c _ZL23run_animation_callbacksdPv + 440
7   QuartzCore                      0x324cbe64 _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv + 156
8   CoreFoundation                  0x3244f4bc CFRunLoopRunSpecific + 2192
9   CoreFoundation                  0x3244ec18 CFRunLoopRunInMode + 44
10  GraphicsServices                0x342e91c0 GSEventRunModal + 188
11  UIKit                           0x331b5c28 -[UIApplication _run] + 552
12  UIKit                           0x331b4228 UIApplicationMain + 960
13  TestEditor                      0x00002c3a main (main.m:14)
14  TestEditor                      0x00002c04 start


這時候對應adress的method name全都出來了!!
但是相信這時候應該很多人會有疑問,symbolicatecrash 在哪裡呢? .dSYM file 又在哪裡呢?
Symbolicatecrash:(預設location)
/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKit.framework/Versions/A/Resources/symbolicatecrash

通常會將Symbolicatecrash檔案,複製到/usr/bin/ 裡面,好讓Terminate可以直接執行呼叫
sudo cp /Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKit.framework/Versions/A/Resources/symbolicatecrash /usr/local/bin

.dSYM檔基本上預設是放在 
{TARGET_BUILD}/TestEditor.app.dSYM

但是通常的情況下,是不需要有這些動作的,因為XCODE在開啟的時候,預設是會去驗證相同的UUID下的dSYM檔,而自動幫你轉好。

難道以上的都是廢話嗎?
這倒不至於,為了避免發生dSYM檔案遺失而照成mapping不到symbole,可以參照這段sh file,在release並且實機的環境下,先將dSYM複製一份到你的專案上,再用以上的方法,自行輸出產生一個LOG FILE。

另外再補充一點
atos -arch armv7 -o app name.app/app name 0x0003b508
armv7,這個要取決於你的arm的指令集,這邊也有可能是armv6, armv7s之類的,.crash file裡面有提供訊息。
app name.app/app name則是要填入你build完的app。
0x0003b508,這個則是要填入相對的address。
什麼是相對的address?
address = slide Address + crash Address - image Address

slide Address 通常是0x1000,或是可用指令取得。
image Address 就是 binary image裡面對應的memory sector的起始位址。

而驗證app 與 crash 檔的UUID的指令為
xcrun dwarfdump --uuid ${dSYMInput} | tr '[:upper:]' '[:lower:]' | tr -d '-'
(這行會印出uuid 與 指令集)
grep {uuid} {.crash} (uuid為上述看到的uuid值,.crash為實體檔案)
補上 將dSYM以及app 檔案複製一份到專案的sh file

#!/bin/sh
if [ "${CONFIGURATION}" = "Debug" ]; then
echo “Skipping debug”
exit 0;
fi
if [ "$EFFECTIVE_PLATFORM_NAME" == "-iphonesimulator" ]; then
echo “Skipping simulator build”
exit 0;
fi
mkdir -p "${PROJECT_DIR}/dSYM"
mkdir -p "${PROJECT_DIR}/dSYM/$(date +%Y%m%d)"
SRC_PATH=${TARGET_BUILD_DIR}/${DWARF_DSYM_FILE_NAME}
SRC_PATH_APP=${TARGET_BUILD_DIR}/${EXECUTABLE_PATH}
#UUID Path
RELATIVE_DEST_PATH_UUID=dSYM/$(date +%Y%m%d)/${EXECUTABLE_NAME}_UUID
DEST_PATH_UUID=${PROJECT_DIR}/${RELATIVE_DEST_PATH_UUID}
rm -f ${DEST_PATH_UUID}
xcrun dwarfdump --uuid ${SRC_PATH_APP} | tr '[:upper:]' '[:lower:]' | tr -d '-' | tr -d '(' | tr -d ')' > ${DEST_PATH_UUID}
exec < $DEST_PATH_UUID
while read -a LINE; do
    UUID_NAME="${LINE[1]}_${LINE[2]}"
done
#dSYM Path
RELATIVE_DEST_PATH=dSYM/$(date +%Y%m%d)/${EXECUTABLE_NAME}.${UUID_NAME}.app.dSYM
DEST_PATH=${PROJECT_DIR}/${RELATIVE_DEST_PATH}
#APP Path
RELATIVE_DEST_PATH_APP=dSYM/$(date +%Y%m%d)/${EXECUTABLE_NAME}.${UUID_NAME}.app
DEST_PATH_APP=${PROJECT_DIR}/${RELATIVE_DEST_PATH_APP}
echo "moving ${SRC_PATH} to ${DEST_PATH}"
echo "moving ${SRC_PATH_APP} to ${DEST_PATH_APP}"
cp -r "${SRC_PATH}" "${DEST_PATH}"
cp -r "${SRC_PATH_APP}" "${DEST_PATH_APP}"

下一章 我們要來分析 如何從上述的.crash裡面劃分的六大項,來取得發生crash的地方。