Demystifying iOS Application Crash Logs
This is a blog post by Soheil Moayedi Azarpour, an independent iOS developer. You can also find him on Google+. Have you ever had the following experience as an app developer? Before you submit your app, you perform a lot of testing to make sure your app runs flawlessly. It works fine on your device, […] By Soheil Azarpour.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Demystifying iOS Application Crash Logs
50 mins
- What Is A Crash Log, and Where Do I Get One?
- What Generates a Crash Log?
- A Sample Crash Log
- Demystification with Symbolication
- Low Memory Crashes
- Exception Codes
- Swimming Time!
- Scenario 1: Bad Code for Breakfast
- Scenario 2: Button FUBAR
- Scenario 3: Another Bug On the Table
- Scenario 4: Leggo Those Licks!
- Where to Go From Here?
Scenario 3: Another Bug On the Table
Another user complains saying, “I can’t delete bookmarked masters from the bookmarks view…” And another email about the same issue reads, “If I try to delete a master in bookmarks, the app crashes…”
By now, you are used to these emails not being very helpful. To the crash logs!
Incident Identifier: 5B62D681-D8FE-41FE-8D52-AB7E6D6B2AC7
CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31
Hardware Model: iPhone4,1
Process: Rage Masters [20088]
Path: /var/mobile/Applications/B2121A89-3D1F-4E61-BB18-5511E1DC150F/Rage Masters.app/Rage Masters
Identifier: Rage Masters
Version: ??? (???)
Code Type: ARM (Native)
Parent Process: launchd [1]
Date/Time: 2012-11-03 13:38:45.762 -0400
OS Version: iOS 6.0 (10A403)
Report Version: 104
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Crashed Thread: 0
Last Exception Backtrace:
0 CoreFoundation 0x36bff29e __exceptionPreprocess + 158
1 libobjc.A.dylib 0x34f0f97a objc_exception_throw + 26
2 CoreFoundation 0x36bff158 +[NSException raise:format:arguments:] + 96
3 Foundation 0x346812aa -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 86
4 UIKit 0x37f04b7e -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] + 7690
5 UIKit 0x3803a4a2 -[UITableView deleteRowsAtIndexPaths:withRowAnimation:] + 22
6 Rage Masters 0x000fd9ca -[RMBookmarksViewController tableView:commitEditingStyle:forRowAtIndexPath:] (RMBookmarksViewController.m:68)
7 UIKit 0x3809a5d4 -[UITableView(UITableViewInternal) animateDeletionOfRowWithCell:] + 80
8 UIKit 0x37fbb0a8 -[UIApplication sendAction:to:from:forEvent:] + 68
9 UIKit 0x37fbb05a -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 26
10 UIKit 0x37fbb038 -[UIControl sendAction:to:forEvent:] + 40
11 UIKit 0x37fba8ee -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 498
12 UIKit 0x37fbb0a8 -[UIApplication sendAction:to:from:forEvent:] + 68
13 UIKit 0x37fbb05a -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 26
14 UIKit 0x37fbb038 -[UIControl sendAction:to:forEvent:] + 40
15 UIKit 0x37fba8ee -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 498
16 UIKit 0x37fbade4 -[UIControl touchesEnded:withEvent:] + 484
17 UIKit 0x37ee35f4 -[UIWindow _sendTouchesForEvent:] + 520
18 UIKit 0x37ed0804 -[UIApplication sendEvent:] + 376
19 UIKit 0x37ed011e _UIApplicationHandleEvent + 6150
20 GraphicsServices 0x3708359e _PurpleEventCallback + 586
21 GraphicsServices 0x370831ce PurpleEventCallback + 30
22 CoreFoundation 0x36bd416e __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 30
23 CoreFoundation 0x36bd4112 __CFRunLoopDoSource1 + 134
24 CoreFoundation 0x36bd2f94 __CFRunLoopRun + 1380
25 CoreFoundation 0x36b45eb8 CFRunLoopRunSpecific + 352
26 CoreFoundation 0x36b45d44 CFRunLoopRunInMode + 100
27 GraphicsServices 0x370822e6 GSEventRunModal + 70
28 UIKit 0x37f242fc UIApplicationMain + 1116
29 Rage Masters 0x000fb004 main (main.m:16)
30 libdyld.dylib 0x3b630b1c start + 0
This looks very similar to the previous crash log. It’s another SIGABRT exception. Perhaps you wonder if it’s the same issue: sending a message to an object that has not implemented the method?
Let’s traverse through the backlog and see what methods were called. Start from the bottom. The last call to your code in Rage Masters is at frame 6:
6 Rage Masters 0x00088c66 -[RMBookmarksViewController tableView:commitEditingStyle:forRowAtIndexPath:] (RMBookmarksViewController.m:68)
Well, this is a UITableViewDataSource method. Huh?! You are pretty sure Apple has implemented this method – you can override it, but it is not like it hasn’t been implemented. Plus, it is an optional delegate method. So the problem isn’t a call to an unimplemented method.
Take a look at the frames after that call:
3 Foundation 0x346812aa -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 86
4 UIKit 0x37f04b7e -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] + 7690
5 UIKit 0x3803a4a2 -[UITableView deleteRowsAtIndexPaths:withRowAnimation:] + 22
In frame 5, UITableView is calling another method of its own, deleteRowsAtIndexPaths:withRowAnimation: and then _endCellAnimationsWithContext: is called, which looks like an internal Apple method. Then the Foundation framework raises an exception, handleFailureInMethod:object:file:lineNumber:description:.
Putting the above together with the user complaints, it looks as if you are dealing with a buggy deletion procedure in UITableView. Go to Xcode. Do you know where to go? Can you tell from the crash log? It’s line 68 in RMBookmarksViewController.m:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
Can you tell where the problem is? I’ll wait while you take a look.
You got it! How about the data source? The code deletes a row in the table view, but doesn’t change the data source behind it. To fix this, replace the above method with the following one:
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
RMMaster *masterToDelete = [bookmarks objectAtIndex:indexPath.row];
[bookmarks removeObject:masterToDelete];
[[RMBookmarks sharedBookmarks] unbookmarkMaster:masterToDelete];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
That should do it! Take that, you nasty bug!! BIFF!!! BANG!!!! CRASH!!!!!
Scenario 4: Leggo Those Licks!
The email says, “My app crashes when a rage master is licking a lollipop…” Another user writes, “I tell the rage master to lick the lollipop few times, and then the app crashes!”
Here’s the crash log:
Incident Identifier: 081E58F5-95A8-404D-947B-5E104B6BC1B1
CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31
Hardware Model: iPhone4,1
OS Version: iPhone OS 6.0 (10A403)
Kernel Version: Darwin Kernel Version 13.0.0: Sun Aug 19 00:28:05 PDT 2012; root:xnu-2107.2.33~4/RELEASE_ARM_S5L8940X
Date: 2012-11-03 13:39:59 -0400
Time since snapshot: 4353 ms
Free pages: 968
Active pages: 7778
Inactive pages: 4005
Throttled pages: 92319
Purgeable pages: 0
Wired pages: 23347
Largest process: Rage Masters
Processes
Name <UUID> rpages recent_max [reason] (state)
lsd <6a9f5b5f36b23fc78f87b6d8f1f49a9d> 331 331 [vm] (daemon) (idle)
afcd <b0aff2e7952e34a9882fec81a8dcdbb2> 141 141 [vm] (daemon) (idle)
itunesstored <4e0cd9f873de3435b4119c48b2d6d13d> 1761 1761 [vm] (daemon) (idle)
softwareupdatese <2bc4b5ae016431c98d3b34f81027d0ae> 311 311 [vm] (daemon) (idle)
Amazon <4600481f07ec3e59a925319b7f67ba14> 2951 2951 [vm] (suspended)
accountsd <ac0fce15c1a2350d951efc498d521ac7> 519 519 [vm] (daemon) (idle)
coresymbolicatio <edba67001f76313b992056c712153b4b> 126 126 [vm] (daemon) (idle)
Skype <504cf2fe60cb3cdea8273e74df09836b> 3187 3187 [vm] (background)
MobileMail <bff817c61ce33c85a43ea9a6c98c29f5> 14927 14927 [vm] (continuous)
MobileSMS <46778de076363d67aeea207464cfc581> 2134 2134 [vm] (background)
MobilePhone <3fca241f2a193d0fb8264218d296ea41> 2689 2689 [vm] (continuous)
librariand <c9a9be81aa9632f0a913ce79b911f27e> 317 317 [vm] (daemon)
kbd <3e7136ddcefc3d77a01499db593466cd> 616 616 [vm] (daemon)
tccd <eb5ddcf533663f8d987d67cae6a4c4ea> 224 224 [vm] (daemon)
Rage Masters <90b45d6281e934209c5b06cf7dc4d492> 28591 28591 [vm] (frontmost) (resume)
ptpd <04a56fce67053c57a7979aeea8e5a7ea> 879 879 (daemon)
iaptransportd <f784f30dc09d32078d87b450e8113ef6> 230 230 (daemon)
locationd <892cd1c9ffa43c99a82dba197be5f09e> 1641 1641 (daemon)
syslogd <cbef142fa0a839f0885afb693fb169c3> 237 237 (daemon)
mediaserverd <80657170daca32c9b8f3a6b1faac43a2> 4869 4869 (daemon)
dataaccessd <2a3f6a518f3f3646bf35eddd36f25005> 1786 1786 (daemon)
aosnotifyd <d4d14f2914c3343796e447cfef3e6542> 549 549 (daemon)
wifid <9472b090746237998cdbb9b34f090d0c> 455 455 (daemon)
SpringBoard <27372aae101f3bbc87804edc10314af3> 18749 18749
backboardd <5037235f295b33eda98eb5c72c098858> 5801 5801 (daemon)
UserEventAgent <6edfd8d8dba23187b05772dcdfc94f90> 601 601 (daemon)
mediaremoted <4ff39c50c684302492e396ace813cb25> 293 293 (daemon)
pasteboardd <8a4279b78e4a321f84a076a711dc1c51> 176 176 (daemon)
springboardservi <ff6f64b3a21a39c9a1793321eefa5304> 0 0 (daemon)
syslog_relay <45e9844605d737a08368b5215bb54426> 0 0 (daemon)
DTMobileIS <23303ca402aa3705870b01a9047854ea> 0 0 (daemon)
notification_pro <845b7beebc8538ca9ceef731031983b7> 169 169 (daemon)
syslog_relay <45e9844605d737a08368b5215bb54426> 0 0 (daemon)
ubd <74dc476d1785300e9fcda555fcb8d774> 976 976 (daemon)
twitterd <4b4946378a9c397d8250965d17055b8e> 730 730 (daemon)
configd <4245d73a9e96360399452cf6b8671844> 809 809 (daemon)
absinthed.N94 <7f4164c844fa340caa940b863c901aa9> 99 99 (daemon)
filecoordination <fbab576f37a63b56a1039153fc1aa7d8> 226 226 (daemon)
distnoted <a89af76ec8633ac2bbe99bc2b7964bb0> 137 137 (daemon)
apsd <94d8051dd5f5362f82d775bc279ae608> 373 373 (daemon)
networkd <0032f46009f53a6c80973fe153d1a588> 219 219 (daemon)
aggregated <8c3c991dc4153bc38aee1e841864d088> 112 112 (daemon)
BTServer <c92fbd7488e63be99ec9dbd05824f5e5> 522 522 (daemon)
fairplayd.N94 <7bd896bd00783a48906090d05cf1c86a> 210 210 (daemon)
fseventsd <996cc4ca03793184aea8d781b55bce08> 384 384 (daemon)
imagent <1e68080947be352590ce96b7a1d07b2f> 586 586 (daemon)
mDNSResponder <3e557693f3073697a58da6d27a827d97> 295 295 (daemon)
lockdownd <ba1358c7a8003f1b91af7d5f58dd5bbe> 389 389 (daemon)
powerd <2d2ffed5e69638aeba1b92ef124ed861> 174 174 (daemon)
CommCenter <1f425e1e897d32e8864fdd8eeaa803a8> 2212 2212 (daemon)
notifyd <51c0e03da8a93ac8a595442fcaac531f> 211 211 (daemon)
ReportCrash <8c32f231b2ed360bb151b2563bcaa363> 337 337 (daemon)
This log is very different from what you’ve seen so far!
This is a low memory crash log from iOS 6. As discussed earlier, low memory crash logs are different from other types of crash logs because they don’t point to a specific file or line of code. Instead, they paint a picture of the memory situation on the device at the time of the crash.
The header, at least, is similar to that of other crash logs: it provides the Incident Identifier, CrashReporter Key, Hardware Model, OS Version, and some other information.
The next section is specific to low memory crash logs:
- Free pages refers to available memory. Each page is approximately 4KB, so in the log above, available memory is about 3,872 KB (or 3.9 MB).
- Purgeable pages are those parts of memory that can be purged and reused. In the log above, it is 0KB.
- Largest process refers to the application that was using most of the memory at the time of the crash, which in this case is your app!
- Processes gives you a list of processes, along with their memory usage at the time of the crash. You are given the name of the process (first column), the unique identifier of the process (second column), and the number of pages being used by the process (third column). In the last column, State, you can see the state of each app. Usually, the app that caused the crash is the app with the frontmost state. Here it is Rage Masters, which was using 28591 pages (or 114.364 MB) – that’s a lot of memory!
Usually, the largest process and the frontmost app are the same, and are also the process that has caused the low memory crash. But you may see some instances where the largest process and the frontmost app are not the same. For example, if the largest process is SpringBoard, ignore it, because SpringBoard is the process that shows the apps on the home screen, the popups that appear when you double tap the home button, etc. and is always active.
When low memory situations happen, iOS sends a low memory warning to the active application and terminates background processes. If the frontmost app still continues to grow in memory, iOS jettisons it.
To find the reason for the low memory issues, you need to profile your app using Instruments. You won’t be doing that here, since we already have a tutorial for that. :] Instead, you’ll take a shortcut and just respond to the low memory warning notification that the app receives to solve this particular crash.
Switch to Xcode and go to RMLollipopLicker.m. This is where the lollipop licker view controller is implemented. Take a look at the source code:
#import "RMLollipopLicker.h"
#define COUNT 20
@interface RMLollipopLicker ()
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@property (weak, nonatomic) IBOutlet UILabel *label;
@property (weak, nonatomic) IBOutlet UILabel *lickedTimeLabel;
@end
@implementation RMLollipopLicker {
NSOperationQueue *queue;
NSMutableArray *lollipops;
}
#pragma mark - Life cycle
- (void)viewDidLoad {
[super viewDidLoad];
self.progressView.progress = 0.0;
self.label.text = [NSString stringWithFormat:@"Tap on run and I'll lick a lollipop %d times!", COUNT];
self.lickedTimeLabel.text = @"";
lollipops = [[NSMutableArray alloc] init];
queue = [[NSOperationQueue alloc] init];
}
- (void)lickLollipop {
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"Lollipop" withExtension:@"plist"];
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:fileURL];
NSString *lollipop = [dictionary objectForKey:@"Lollipop"];
[lollipops addObject:lollipop];
}
#pragma mark - IBActions
- (IBAction)doneButtonPressed:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)runButtonPressed:(id)sender {
[sender setEnabled:NO];
[queue addOperationWithBlock:^{
for (NSInteger i = 0 ; i <= COUNT ; i++) {
[self lickLollipop];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.label.text = [NSString stringWithFormat:@"Licked a strawberry lollipop %d time(s)!", i];
self.lickedTimeLabel.text = [NSString stringWithFormat:@"Licked the same lollipop %d time(s)!", lollipops.count-1];
self.progressView.progress = (float)(i/COUNT);
if (i >= COUNT) {
self.label.text = [NSString stringWithFormat:@"Tap on run and I'll lick a lollipop %d times!", COUNT];
self.progressView.progress = 0.0;
[sender setEnabled:YES];
}
}];
}
}];
}
@end
When the user taps the run button, the app starts a background operation, calls lickLollipop a number of times, and then updates the UI to reflect the number of licks. lickLollipop reads a big NSString from a property list (PLIST) and adds it to an array. This data is not crucial, and can be recreated without affecting the user experience.
It’s a good habit to take advantage of every situation where you can purge data and recreate it without adversely affecting the user experience. This frees up memory, making low memory warnings less likely.
So how can you improve the code here? Implement didReceiveMemoryWarning and get rid of the data in lollipops as follows:
-(void)didReceiveMemoryWarning {
[lollipops removeAllObjects];
[super didReceiveMemoryWarning];
}
That should be it!