bor.borygmus

A programming weblog by Hao Lian. • A long walk through an angry forest. • A series of memory leaks brought on by senility.

Today’s mystery stack trace is:

#0  0x02b67a93 in objc_msgSend
#1  0x05c2dbb0 in ??
#2  0x004c08d7 in
  -[UINavigationController _startTransition:fromViewController:toViewController:]
#3  0x004bb329 in -[UINavigationController _startDeferredTransitionIfNeeded]
#4  0x004c23a0 in
  -[UINavigationController pushViewController:transition:forceImmediate:]
#5  0x004bb1c3 in -[UINavigationController pushViewController:animated:]
#6  0x00009803 in -[YourController someAction:] at YourController.m:1821
#7  0x0040ae14 in -[UIApplication sendAction:to:from:forEvent:]
#8  0x0061214b in -[UIBarButtonItem(UIInternal) _sendAction:withEvent:]
#9  0x0040ae14 in -[UIApplication sendAction:to:from:forEvent:]
#10  0x004946c8 in -[UIControl sendAction:to:forEvent:]
#11  0x00496b4a in -[UIControl(Internal) _sendActionsForEvents:withEvent:]
#12  0x004956f7 in -[UIControl touchesEnded:withEvent:]
#13  0x0042e2ff in -[UIWindow _sendTouchesForEvent:]
#14  0x004101ec in -[UIApplication sendEvent:]
#15  0x00414ac4 in _UIApplicationHandleEvent
#16  0x02f43afa in PurpleEventCallback
#17  0x029eddc4 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
#18  0x0294e737 in __CFRunLoopDoSource1
#19  0x0294b9c3 in __CFRunLoopRun
#20  0x0294b280 in CFRunLoopRunSpecific
#21  0x0294b1a1 in CFRunLoopRunInMode
#22  0x02f422c8 in GSEventRunModal
#23  0x02f4238d in GSEventRun
#24  0x00418b58 in UIApplicationMain
#25  0x00002110 in main at main.m:6

And Xcode was kind enough to log an “EXC_BAD_ACCESS” signal.

We know from prior Objective-C experience that “EXC_BAD_ACCESS” means, almost always, access to an object that’s already been freed by the reference counting garbage collector. That is, hitting an object whose retain count already reached zero. So assume this is a memory allocation bug.

We also know the stack trace is useless since the objc_msgSend() is merely the place where the bad access occurred, not the cause. We know from Greg Parker’s weblog that we can read off the ecx and eax registers to get the sent message name (the method call that caused this mess) and the pointer to the object that’s been freed (if we’re lucky). Registers are like variables in Assembly, for readers lucky enough to have never been forced to learn Assembly.

(gdb) p/x $eax
$2 = 0x5a5e8c0
(gdb) x/s $ecx
0x2b6ac10 <__FUNCTION__.13255+742>: "release"

So we’re calling “release” on an object pointer at 0\x5a5e8c0. Going through Parker’s checklist, this pointer is absolutely normal: it’s divisible by 16, the top two and bottom two bits are not set, the bits are not all inverted, it’s not a CF container, and it’s not an ASCII string.

With normalcy in mind, we can now resort to a brute-force grep for “release” calls that could possibly be occur after a pushViewController() begins. Not bad, from a completely worthless stack trace. In this specific case, I knew that a lot of code was executed at view events: viewWillAppear() and viewDidAppear(). Putting a breakpoint on both methods and stepping over each line led me to a double-released NSTimer.

THEN I FELT STUPID, GUYS, REAL STUPID

Moral of the story? Read Greg Parker’s weblog.

[(August 23, 2010) .]