Wednesday, March 3, 2010

Debugging EXC_BAD_ACCESS from a NSString

While writing an iPhone application, I kept getting a mysterious crash during execution. There were no warnings or errors, but when I got to a specific point every time, it would crash. I decided to examine the console for messages. I found the message "EXC_BAD_ACCESS" there.

I Googled this message and found this site on setting the environment variables NSZombieEnabled and MallocStackLoggingNoCompact.

A little background
NSZombieEnabled - If I'm understanding this correctly, when this environment variable is set to YES, when an object is deallocated, instead of releasing it, the compiler will set its type to _NSZombie. Because the memory is never freed, the object remains on the stack, but its type is now _NSZombie. With a little command line usage, you can see where it was attempted to be freed, and whether or not it is intentional.

MallocStackLoggingNoCompact - "causes malloc to remember the caller requesting each allocation, and remembers it after the allocation is freed." (from Mac OSX Reference Library)

To set the environment variables, use this procedure:
  1. Ctrl+Click on the executable in your project
  2. Select "Get Info"
  3. Select "Arguments" tab
  4. Enter the variable information as seen in the screenshot below

Now, when I run the program, at the point of the crash, the console says:
2010-03-01 19:13:46.924 CruzNomad[7952:207] * -[CFString stringByAddingPercentEscapesUsingEncoding:]: message sent to deallocated instance 0x58448e0


At the gdb prompt, I issued the command: info malloc-history 0x58448e0. This gave me the object's allocation/ deallocation history:


Alloc: Block address: 0x058448e0 length: 64
Stack - pthread: 0xa0b33500 number of frames: 25
0: 0x98e089bc in malloc_zone_malloc
1: 0x21516aa in _CFRuntimeCreateInstance
2: 0x2152bf8 in __CFStringCreateImmutableFunnel3
3: 0x21567d9 in CFStringCreateCopy
4: 0x21742fc in _CFStringCreateWithFormatAndArgumentsAux
5: 0xdb546 in -[NSPlaceholderString initWithFormat:locale:arguments:]
6: 0xdb4d8 in +[NSString stringWithFormat:]
7: 0x23aa3 in -[BuisnessCardViewController viewDidLoad] at /Users/.../Classes/BuisnessCardViewController.m:85
8: 0x3d6796 in -[UIViewController view]
9: 0x347b4 in -[gm_menuViewController btn5_Pressed:] at /Users/.../Classes/menuViewController.m:535
10: 0x357459 in -[UIApplication sendAction:to:from:forEvent:]
11: 0x3baba2 in -[UIControl sendAction:to:forEvent:]
12: 0x3bcdc3 in -[UIControl(Internal) _sendActionsForEvents:withEvent:]
13: 0x3bbb0f in -[UIControl touchesEnded:withEvent:]
14: 0x370e33 in -[UIWindow _sendTouchesForEvent:]
15: 0x35a81c in -[UIApplication sendEvent:]
16: 0x3610b5 in _UIApplicationHandleEvent
17: 0x2984ed1 in PurpleEventCallback
18: 0x2197b80 in CFRunLoopRunSpecific
19: 0x2196c48 in CFRunLoopRunInMode
20: 0x298378d in GSEventRunModal
21: 0x2983852 in GSEventRun
22: 0x362003 in UIApplicationMain
23: 0x2c8c in main at /Users/.../source/main.m:14
24: 0x2bfa in start
 
Line 7 says the problem was in line 85 of BuisnessCardViewController.m. That line is here:
fullAddress = [NSString stringWithFormat:@"%@ %@", fullAddress, myString];

I read that the method stringWithFormat: returns an autorelease object. I had to retain the variable right after its first usage, earlier in the method.

fullAddress = [NSString stringWithFormat:@"%@ %@", fullAddress, myString];
[fullAddress retain];

I use it a few times in the implementation file. This is the first operation I perform on it, so I decided to retain it here.

I posted this question on StackOverflow and the iPhone Developer Forums (you need a login for the latter).

Such a rewarding feeling to get an issue debugged!