Debug cocoa objective-c autorelease

http://www.cocoadev.com/index.pl?DebuggingAutorelease


Extra keywords (for lil' Google): debug, reference-counting, release.

Update: For Leopard users, I recommend using Instruments: http://www.corbinstreehouse.com/blog/index.php/2007/10/instruments-on-leopard-how-to-debug-those-random-crashes-in-your-cocoa-app/

One of the most opaque bugs I've had to deal with in Cocoa is leaving a released object in the autorelease pool, causing an EXC_BAD_ACCESS in NSPopAutoreleasePool?(). When this happens, it's pretty much impossible to tell what the doubly-released object was and where it was instantiated.

Fear no more! Using Cocoa's NSZombie debugging class and the command-line malloc_history tool, we can nail this bug in a pinch.

Suppose you have the following (obviously incorrect) code:


  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

  NSData* data = [NSData dataWithBytes:"asklaskdxjgr" length:12];


  [data release];

  [pool release];

The dataWithBytes: method sends an autorelease message to the created object, so we don't need to release it ourselves. When the autorelease pool is tossed the freed data object gets another release message, our app crashes, and we have no idea why.

Here's what we do:

Click on the "Targets" tab, open "Executables" and select the app (In XCode 2.0, double-click the executable in the file tree and select the arguments tab to enter environment variables). In the executable settings, add the following environment variables and set their values to "YES" (without the quotes):


  NSDebugEnabled

  NSZombieEnabled

  MallocStackLogging

You may also want the following environment variable set to YES:


  MallocStackLoggingNoCompact

With NSZombieEnabled, Cocoa sets an object's isa pointer to the NSZombie class when its retain count drops to zero instead of deallocating it. Then when you send a message to an NSZombie object (i.e., you're accessing freed data), it raises an exception and tells you where the object lives:


  2003-03-18 13:01:38.644 autoreleasebug[3939] *** *** Selector 'release'

  sent to dealloced instance 0xa4e10 of class NSConcreteData.

Since you have MallocStackLogging turned on, you can now run "malloc_history <pid> <address>" to see the stack trace when the object was allocated:


  [dave@host193 Frameworks]$ malloc_history 3939 0xa4e10


  Call [2] [arg=32]: thread_a0000dec |0x1000 | start | _start | main |

  +[NSData dataWithBytes:length:] | NSAllocateObject | object_getIndexedIvars |

  malloc_zone_calloc 

if you run under gdb, you may enter:

 (gdb) shell malloc_history 3939 0xa4e10

And there it is: the double-released object was allocated with [NSData dataWithBytes:length:] in the function main()!

I love you, Cocoa!



Another useful breakpoint is "szone_error"- this stops the debugger where you get the "Incorrect checksum for freed object" message



Also note that NSZombieEnabled keeps objects from being freed, so if you use it with MallocStackLogging you won't see premature releases. Turn off NSZombieEnabled and wait for the segfault.. Hopefully your debugger will still be awake and can show you the line you're crashing on.



What would a malloc_debug like this mean?


Call [2] [arg=24]: thread_a000a1ec |0x0 | _dyld_start | _start | main | NSApplicationMain

 | -[NSApplication run] | -[NSApplication sendEvent:] | -[NSWindow sendEvent:]

 | -[NSControl mouseDown:] | -[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:]

 | -[NSCell trackMouse:inRect:ofView:untilMouseUp:] | -[NSCell _sendActionFrom:]

 | -[NSControl sendAction:to:] | -[NSApplication sendAction:to:from:] | -[MEController 

newCity:] | -[MECityEditor editCity:otherCities:] | -[NSApplication runModalForWindow:]

 | -[NSApplication _realDoModalLoop:peek:] | -[NSApplication nextEventMatchingMask:

untilDate:inMode:dequeue:] | _DPSNextEvent | BlockUntilNextEventMatchingListInMode

 | ReceiveNextEventCommon | RunCurrentEventLoopInMode | CFRunLoopRunSpecific

 | __CFRunLoopRun | __CFRunLoopDoObservers | _handleWindowNeedsDisplay | -[NSWindow 

displayIfNeeded] | -[NSView displayIfNeeded] | -[NSView _displayRectIgnoringOpacity:

isVisibleRect:rectIsVisibleRectForView:] | -[NSThemeFrame 

_recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:

topView:] | -[NSFrameView _recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:

rectIsVisibleRectForView:topView:] | -[NSView _recursiveDisplayRectIfNeededIgnoringOpacity:

isVisibleRect:rectIsVisibleRectForView:topView:] | -[NSView

_recursiveDisplayRectIfNeededIgnoringOpacity:isVisibleRect:rectIsVisibleRectForView:topView:]

 | -[NSView(NSInternal) _getDirtyRects:clippedToRect:count:boundingBox:] | -[NSRegion 

mutableCopy] | NSAllocateObject | _internal_class_createInstanceFromZone | malloc_zone_calloc

My program does this when I select an object in a popupmenu. When I break at malloc_printf the program breaks inside NSPopAutoreleasePool?, so I know I have an autorelease bug. This is one of several "double free" bugs I've inheritted with code that I'm taking over.

Thanks for any help, Joe



Just wanted to say thanks to those who wrote this page up - just saved me hours of sleuthing... Using this tip I pinpointed my autorelease bug in a few seconds - great stuff. However, it also shows one situation where the "rules" for autoreleasing need to be modified, possibly. In my app, I create a small custom window instance that is used to provide feedback info, a bit like a tooltip. Its class includes a class factory method for returning a shared window instance, and following the usual rules, this method was autoreleasing the window object it returned. Users of the window would retain it as normal. However, a window seems to require a - close method when you're done with it, not a direct - release. At app quit time, all windows are sent the -close method, which in this case releases this window, which means that when the original autorelease pool does release this now deallocated object, it crashes. My fix (for now) is to just return the object from the factory method without autoreleasing it, which stops the crash at quit time. I'm a little uneasy about making a special case of not autoreleasing from a factory method, but when the object in question is an NSWindow subclass, that seems to be what you have to do. Unless someone knows otherwise, of course! --GrahamCox


"How to do otherwise" would be to uncheck the "Release when closed" box in IB, or [window setReleasedWhenClosed:NO] in code.


Ah yes, that also does the trick - and more cleanly. Thanks! --GC




One thing I find very useful is being able to find an object's retain count in the debugger. One easy way to do it is to define this macro in .gdbinit:


define rc

call (int)[$arg0 retainCount]

end

You can then type rc object to see an object's retain count.



<Foundation/NSDebug.h> has some very useful routines on NSAutoreleasePool that might help someone debugging this stuff. e.g., [NSAutoreleasePool showPools], and related.

peace - hsi


From http://lists.apple.com/archives/Cocoa-dev/2006/Mar/msg00058.html


This procedure works well, though it's not fully automatic: 1. Run `x/s $ecx`. This should print the name and address of the selector. If it doesn't, you're too far inside objc_msgSend() to get an easy answer. 3. Run `x/8x $esp`. This is the top 8 words of the stack. 4. Look for the selector address (from step 1) in the stack contents. The word just before the selector address is the receiver object's address. The method's other arguments, if any, start after the selector.

If step #1 doesn't work, check whether you're stopped at the very first instruction of objc_msgSend(). If so, step forward one instruction (`si`) and try again. That first instruction is the one that moves the selector into $ecx.

Example: 0x9ff57eef in objc_msgSend () (gdb) x/s $ecx 0x9ffcb230 <_errNotSuper+412640>: "sharedSpellChecker" // The selector is "sharedSpellChecker", and its address is 0x9ffcb230 (gdb) x/8x $esp 0xbfffee34: 0xbfffee88 0x003539b0 0x93624629 0xa34ab0c0 0xbfffee44: 0x9ffcb230 0xbfffef18 0x9ff57f36 0xa34ac480 // The selector address is the 5th word on the stack, so the receiver is 0xa34ab0c0 (gdb) p (char *)object_getClassName(0xa34ab0c0) $1 = 0x932aa1750 "NSSpellChecker?" // The receiver is either class NSSpellChecker? or one of its instances. This case happens to be the class itself, for + [NSSpellChecker? sharedSpellChecker]




This is also very handy: http://www.sealiesoftware.com/blog/archive/2008/09/22/objc_explain_So_you_crashed_in_objc_msgSend.html



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值