日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

程序Crash后的调试技巧

發布時間:2025/7/25 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 程序Crash后的调试技巧 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

當我們的程序突然死掉了,Xcode突然送出一段 "message sent to deallocated instance" 的錯誤,我們該怎樣定位我們的程序bug呢?

又或者我們已經通過AdHoc發布了我們的β版程序,更甚至于我們的程序已經發布到了app store上;而當我們的程序突然在測試人員,或者是最終用戶那里突然當掉,是否能收集到這樣的日志信息,供我們解析bug呢?

下面的文章中我將逐步深入地說明這些技巧

模擬器上顯示堆棧信息

當我們在模擬器上調試時,可能經常遇到下面的內存訪問錯誤:

1 2011-01-17 20:21:11.41 App[26067:207] *** -[Testedit retain]: message sent to deallocated instance 0x12e4b0

首先,我們為了定位問題,需要Xcode幫我們顯示棧信息,可以通過Scode中執行文件的屬性來設置。如下圖所示,選中 MallocStackLogging 選項。該選項只能在模擬器上有效,并且如果你改變了iOS的版本后也需要再次設定該選項。

這之后,你就可以在終端輸入 info malloc-history 命令,如下所示;

1 (gdb) info malloc-history 0x12e4b0

之后得到如下的堆棧信息,從此分析具體的問題所在。

除此之外,也可以使用下面的命令;

1 (gdb) shell malloc_history {pid/partial-process-name} {address}

例如下圖所示;

另外,內存使用時“EXC_BAD_ACCESS”的錯誤信息也是經常遇到的,這時我們只要將上面執行文件屬性中的 NSZombieEnabled 選上,也能定位該問題。

最后,這些設置信息都是可以在運行期確認的,如下;

1 NSLog(@"NSZombieEnabled: %s", getenv("NSZombieEnabled"));

在iPhone上輸出日志

如果不是在模擬器上,又或者我們的設備沒有連接到PC上,那么如何調試我們的程序呢?假如我們通過AdHoc發布了程序,希望隨時得到測試人員的反饋,可以利用下面的方法,將標準出力(stderr)信息記錄到文件中,然后通過郵件新式發給開發者。

1. 設置一個開關,用來清空日志文件內容,并切換輸出位置;

1 2 3 4 5 6 7 8 - (BOOL)deleteLogFile { [self finishLog]; BOOL success = [[NSFileManager defaultManager] removeItemAtPath:[self loggingPath] error:nil]; [self startLog]; return success; }

當我們調用上面的deleteLogFile后,就會清空之前的日志,并設置新的輸出。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - (void)finishLog { fflush(stderr); dup2(dup(STDERR_FILENO), STDERR_FILENO); close(dup(STDERR_FILENO)); } - (NSString*)loggingPath { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *logPath = [documentsDirectory stringByAppendingPathComponent:@"console.log"]; return logPath; } - (void)startLog { freopen([[self loggingPath] cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr); }

2. 當日志取得之后,可以通過下面的方式發送郵件給開發者

1 2 3 4 5 6 7 8 9 10 11 - (void)sendLogByMail { MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init]; picker.mailComposeDelegate = self; [picker setSubject:[NSString stringWithFormat:@"%@ - Log", [self appName]]]; NSString *message = [NSString stringWithContentsOfFile:[self loggingPath] encoding:NSUTF8StringEncoding error:nil]; [picker setMessageBody:message isHTML:NO]; [self.navigationController presentModalViewController:picker animated:YES]; [picker release]; }

iPhone應用程序的CrashReporter機能

蘋果在固件2.0發布的時候,其中一項特性是向iPhone開發者通過郵件發送錯誤報告,以便開發人員更好的了解自己的軟件運行狀況。不過不少開發者報告此服務有時無法獲取到~/Library/Logs/CrashReporter/MobileDevice directory的錯誤信息。

現在蘋果提供了一種更簡單的方法,使iPhone開發者可以通過iTunes更容易的查看崩潰報告。具體方法使進入iTunesConnect(在進入之前確定你有iPhone開發者帳號),點擊管理你應用程序,之后就可以看到用戶崩潰日志了。

這里我介紹一下從設備中取出CrashLog,并解析的方法。

CrashLog的位置

程序Crash之后,將設備與PC中的iTunes連接,設備中的CrashLog文件也將一并同步到PC中。其中位置如下;

1 2 3 4 5 6 7 8 Mac: ~/Library/Logs/CrashReporter/MobileDevice Windows Vista/7: C:\Users\<user_name>\AppData\Roaming\Apple computer\Logs\CrashReporter/MobileDevice Windows XP: C:\Documents and Settings\<user_name>\Application Data\Apple computer\Logs\CrashReporter

在這些目錄下,會有具體設備的目錄,其下就是許多*.crash的文件。

比如程序TestEditor在iPhone1設備上的crashLog如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ~Library/Logs/CrashReporter/MobileDevice/iPhone1/TestEditor_2010-09-23-454678_iPhone1.crash 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] Date/Time: 2010-09-23 11:25:56.357 +0900 OS Version: iPhone OS 3.1.3 (7E18) Report Version: 104 Exception Type: EXC_BAD_ACCESS (SIGBUS) Exception Codes: KERN_PROTECTION_FAILURE at 0x00000059 Crashed Thread: 0 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 ... (以下略)

雖然我們看到了出為題時的堆棧信息,但是因為沒有符號信息,仍然不知道到底哪里出問題了...

.dSYM文件

編譯調試相關的符號信息都被包含在編譯時的 xxxx.app.dSYM 文件當中,所以我們在發布程序前將它們保存起來,調試Crash問題的時候會很有用。

首先,我們來找到該文件。

用Xcode編譯的程序,在其編譯目錄下都會生成 [程序名].app.dSMY 文件,比如 Xcode 4 的編譯目錄缺省的是

1 2 3 4 5 ~Library/Developer/Xcode/DerivedData # 在改目錄下搜尋編譯后的.dSMY文件 $ cd ~/Library/Developer/Xcode/DerivedData $ find . -name '*.dSYM'

另外,我們也可以通過 Xcode的Preferences... -> Locations -> Locations 的Derived Data來確認該目錄的位置。

上面例子中的程序,我們就找到了其位置是
1 ~/Library/Developer/Xcode/DerivedData/TestEditor-aahmlrjpobenlsdvhjppcfqhogru/ArchiveIntermediates/TestEditor/BuildProductsPath/Release-iphoneos/TestEditor.app.dSYM

※ 大家每次像App Store發布自己程序的時候都記著保存該文件哦,要不然出現Crash的時候,就無從下手了。

解決符號問題

接下來,我們再來介紹一下使用.dSYM文件來恢復程序符號的方法。

首先,使用一個Xcode提供的叫做 symbolicatecrash 的小工具,它可以實現我們在CrashLog中添加符號信息的機能。該文件位于下面的位置,為方便起見,可以把它拷貝到系統默認路徑下。

1 2 3 /Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKit.framework/Versions/A/Resources/symbolicatecrash $ sudo cp /Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKit.framework/Versions/A/Resources/symbolicatecrash /usr/local/bin
使用下面的命令,可以在終端輸出有符號信息的CrashLog
1 2 3 $ symbolicatecrash [CrashLog file] [dSYM file] $ symbolicatecrash TestEditor_2010-09-23-454678_iPhone1.crash TestEditor.app.dSYM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 0x1000 + 7172

由此,我們可以具體定位程序中出問題的地方。

用StackTrace取得崩潰時的日志

異常處理機制

任何語言都有異常的處理機制,Objective-C也不例外。與C++/Java類似的語法,它也提供@try, @catch, @throw, @finally關鍵字。使用方法如下。

1 2 3 4 5 6 7 8 9 10 11 12 @try { ...?? } @catch (CustomException *ce) { ...?? } @catch (NSException *ne) { // Perform processing necessary at this level. ...? } @catch (id ue) { ...?? } @finally { // Perform processing necessary whether an exception occurred or not. ...?? }

同時對于系統Crash而引起的程序異常退出,可以通過UncaughtExceptionHandler機制捕獲;也就是說在程序中catch以外的內容,被系統自帶的錯誤處理而捕獲。我們要做的就是用自定義的函數替代該ExceptionHandler即可。

這里主要有兩個函數
  • NSGetUncaughtExceptionHandler() 得到現在系統自帶處理Handler;得到它后,如果程序正常退出時用來回復系統原先設置
  • NSSetUncaughtExceptionHandler() 紅色設置自定義的函數
簡單的使用例子如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void MyUncaughtExceptionHandler(NSException *exception) { printf("uncaught %s\n", [[exception name] cString]); // // 顯示當前堆棧內容 NSArray *callStackArray = [exception callStackReturnAddresses]; int frameCount = [callStackArray count]; void *backtraceFrames[frameCount]; for (int i=0; i<frameCount; i++) { backtraceFrames[i] = (void *)[[callStackArray objectAtIndex:i] unsignedIntegerValue]; } } int main() { // NSUncaughtExceptionHandler *ueh = NSGetUncaughtExceptionHandler(); NSSetUncaughtExceptionHandler(&MyUncaughtExceptionHandler); // } - (void)exit_processing:(NSNotification *)notification { NSSetUncaughtExceptionHandler(ueh); } - (void)viewDidLoad { // 這里重載程序正常退出時UIApplicationWillTerminateNotification接口 UIApplication *app = [UIApplication sharedApplication]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(exit_processing:) name:UIApplicationWillTerminateNotification object:app] }

處理signal

使用Objective-C的異常處理是不能得到signal的,如果要處理它,我們還要利用unix標準的signal機制,注冊SIGABRT, SIGBUS, SIGSEGV等信號發生時的處理函數。該函數中我們可以輸出棧信息,版本信息等其他一切我們所想要的。

例子代碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <signal.h> void stacktrace(int sig, siginfo_t *info, void *context) { [mstr appendString:@"Stack:\n"]; void* callstack[128]; int i, frames = backtrace(callstack, 128); char** strs = backtrace_symbols(callstack, frames); for (i = 0; i <; frames; ++i) { [mstr appendFormat:@"%s\n", strs[i]]; } } int main(int argc, char *argv[]) { struct sigaction mySigAction; mySigAction.sa_sigaction = stacktrace; mySigAction.sa_flags = SA_SIGINFO; sigemptyset(&mySigAction.sa_mask); sigaction(SIGQUIT, &mySigAction, NULL); sigaction(SIGILL , &mySigAction, NULL); sigaction(SIGTRAP, &mySigAction, NULL); sigaction(SIGABRT, &mySigAction, NULL); sigaction(SIGEMT , &mySigAction, NULL); sigaction(SIGFPE , &mySigAction, NULL); sigaction(SIGBUS , &mySigAction, NULL); sigaction(SIGSEGV, &mySigAction, NULL); sigaction(SIGSYS , &mySigAction, NULL); sigaction(SIGPIPE, &mySigAction, NULL); sigaction(SIGALRM, &mySigAction, NULL); sigaction(SIGXCPU, &mySigAction, NULL); sigaction(SIGXFSZ, &mySigAction, NULL); NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, nil); [pool release]; return (retVal); }

總結

綜上所述,我們可以看到用StackTrace取得崩潰時日志的手順如下
  • 用NSGetUncaughtExceptionHandler()取得當前系統異常處理Handler
  • 用NSSetUncaughtExceptionHandler()注冊自定義異常處理Handler
  • 注冊signal處理機制
    • 注冊Handler中打印堆棧,版本號等信息
    • 必要的時候將其保存到dump.txt文件
  • 異常程序退出
  • 如果程序不是異常退出,則還原之前系統的異常處理函數句柄
  • 如果下次程序啟動,發現有dump.txt的異常文件,啟動郵件發送報告機制
整體的代碼框架如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include <signal.h> void stacktrace(int sig, siginfo_t *info, void *context) { [mstr appendString:@"Stack:\n"]; void* callstack[128]; int i, frames = backtrace(callstack, 128); char** strs = backtrace_symbols(callstack, frames); for (i = 0; i <; frames; ++i) { [mstr appendFormat:@"%s\n", strs[i]]; } } void MyUncaughtExceptionHandler(NSException *exception) { printf("uncaught %s\n", [[exception name] cString]); // // 顯示當前堆棧內容 NSArray *callStackArray = [exception callStackReturnAddresses]; int frameCount = [callStackArray count]; void *backtraceFrames[frameCount]; for (int i=0; i<frameCount; i++) { backtraceFrames[i] = (void *)[[callStackArray objectAtIndex:i] unsignedIntegerValue]; } } int main(int argc, char *argv[]) { struct sigaction mySigAction; mySigAction.sa_sigaction = stacktrace; mySigAction.sa_flags = SA_SIGINFO; sigemptyset(&mySigAction.sa_mask); sigaction(SIGQUIT, &mySigAction, NULL); sigaction(SIGILL , &mySigAction, NULL); sigaction(SIGTRAP, &mySigAction, NULL); sigaction(SIGABRT, &mySigAction, NULL); sigaction(SIGEMT , &mySigAction, NULL); sigaction(SIGFPE , &mySigAction, NULL); sigaction(SIGBUS , &mySigAction, NULL); sigaction(SIGSEGV, &mySigAction, NULL); sigaction(SIGSYS , &mySigAction, NULL); sigaction(SIGPIPE, &mySigAction, NULL); sigaction(SIGALRM, &mySigAction, NULL); sigaction(SIGXCPU, &mySigAction, NULL); sigaction(SIGXFSZ, &mySigAction, NULL); // NSUncaughtExceptionHandler *ueh = NSGetUncaughtExceptionHandler(); NSSetUncaughtExceptionHandler(&MyUncaughtExceptionHandler); // NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, nil); [pool release]; return (retVal); } - (void)exit_processing:(NSNotification *)notification { NSSetUncaughtExceptionHandler(ueh); } - (void)viewDidLoad { // 這里重載程序正常退出時UIApplicationWillTerminateNotification接口 UIApplication *app = [UIApplication sharedApplication]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(exit_processing:) name:UIApplicationWillTerminateNotification object:app] }
輸入的CrashLog如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Signal:10 Stack: 0 TestEditor 0x0006989d dump + 64 1 TestEditor 0x00069b4b signalHandler + 46 2 libSystem.B.dylib 0x31dcd60b _sigtramp + 26 3 TestEditor 0x000252b9 -[PopClientcreateUnreadMessageWithUIDL:maxMessageCount:] + 76 4 TestEditor 0x00025b85 -[PopClientgetUnreadIdList:] + 348 5 TestEditor 0x000454dd -[Connection receiveMessages:] + 688 6 TestEditor 0x00042db1 -[Connection main] + 188 7 Foundation 0x305023f9 __NSThread__main__ + 858 8 libSystem.B.dylib 0x31d6a5a8 _pthread_body + 28 AppVer:TestEditor 1.2.0 System:iPhone OS OS Ver:3.0 Model:iPhoneDate:09/06/08 21:25:59JST

其中從_sigtramp函數下面開始進入我們的程序,即地址0x000252b9開始。其所對應的具體文件名和行號我們能知道嗎?

利用之前介紹的dSYM文件和gdb,我們可以得到這些信息。

1 2 3 4 5 6 7 cd $PROJ_PATH$/build/Release-iphoneos/TestEditor.app.dSYM/ cd Contents/Resources/DWARF gdb TestEditor gdb>info line *0x000252b9 Line 333 of "~/IbisMail/Classes/Models/PopClient.m"; starts at address 0x2a386 <-[PopClient retrieve:]+86> and ends at 0x2a390 <-[PopClient retrieve:]+96>

轉自http://www.yifeiyang.net/iphone-development-skills-of-debugging-articles-3-crash-after-debugging-skills-program/

轉載于:https://www.cnblogs.com/likwo/archive/2012/04/16/2451630.html

總結

以上是生活随笔為你收集整理的程序Crash后的调试技巧的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。