次第Crash后的调剂技巧

当大家的次第突然死掉了,Xcode突然送出一段 “message sent to deallocated
instance” 的谬误,我们该怎么定位咱们的程序bug呢?

又或者我们曾经经过AdHoc公布了大家的β版程序,更甚至于我们的程序已经昭示到了app
store上;而当我们的先后突然在测试人士,或者是最后用户这里突然当掉,是否能搜集到这么的日记消息,供我们解析bug呢?

ACCESS,下边的稿子中自我将逐渐长远地证实那几个技术

模拟器上呈现堆栈新闻

当大家在模拟器上调节时,可能时时碰到下面的内存访问错误:

1

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

ACCESS 1

率先,大家为了定位问题,需要Xcode帮我们来得栈音讯,可以透过Scode中实施文书的属性来设置。如下图所示,选中
MallocStackLogging
选项。该选项只好在模拟器上有效性,并且只要您改变了iOS的版本后也亟需重新设定该选项。

ACCESS 2

这之后,你就可以在巅峰输入 info malloc-history 命令,如下所示;

1

(gdb) info malloc-history 0x12e4b0

从此未来收获如下的堆栈信息,从此分析现实的问题所在。

ACCESS 3

除此之外,也得以采纳下边的吩咐;

1

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

诸如下图所示;

ACCESS 4

除此以外,内存使用时“EXC_BAD_ACCESS”的错误音讯也是时常遇上的,这时我们假使将地点执行文件属性中的
NSZombieEnabled 选上,也能定点该问题。

末尾,那么些设置消息都是可以在运行期确认的,如下;

1

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

在vivo上输出日志

只要不是在模拟器上,又或者我们的装备尚未连接到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); }
  1. 当日志取得之后,可以经过下边的法门发送邮件给开发者

    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]; }

黑莓应用程序的CrashReporter机能

苹果在固件2.0披露的时候,其中一项特征是向One plus开发者通过邮件发送错误报告,以便开发人士更好的摸底自己的软件运行意况。然则不少开发者报告此服务有时不能获得到~/Library/Logs/CrashReporter/MobileDevice
directory的错误新闻。

现在苹果提供了一种更简约的艺术,使华为开发者可以通过iTunes更易于的查阅崩溃报告。具体方法使进入iTunesConnect(在进入前边确定你有HUAWEI开发者帐号),点击管理你应用程序,之后就足以观望用户崩溃日志了。

此地自己介绍一下从设备中取出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在Samsung1设备上的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/

相关文章