August 28, 2003

Geek | Hard core debugging

Somebody taped this on their door at work:

Six Stages of Debugging

  1. That can't happen.
  2. That doesn't happen on my machine.
  3. That shouldn't happen.
  4. Why does that happen?
  5. Oh, I see.
  6. How did that ever work?

Today is debugging blog day. I spent the entire day trying to debug a crasher that nobody knows how to reproduce, although there are multiple bug reports with the same stack trace...

Here are some Cocoa debugging tricks that apparently are not common knowledge.

In GDB, use print-object (abbrev. po) to print the description of an NSObject. I thought this was common knowledge, until I saw an intern using call (void) NSLog(...) the other day, which is the hard way to do it.

Use thread apply all backtrace (abbrev. t a a bt) to print backtraces from all running threads.

If your app is hanging or too slow, use sample to see where it's spending its time. If your app is leaking memory, set the environment variable MallocStackLogging, launch it using open, and use leaks to find the source of the leaks.

If your app is crashing in obj_msgSend, chances are that you're trying to message an object that's already been released or autoreleased. To debug autorelease problems, see NSDebug.h in Foundation.framework/Headers and turn on NSZombieEnabled and NSAutoreleaseFreedObjectCheckEnabled. This will disable the autorelease mechanism and spit out messages on the console when you're trying to use a previously autoreleased object ("zombie").

It may help to understand the Objective-C runtime, especially if you're digging around without symbols (which is what I was doing today). An Objective-C message is translated to the C function call id objc_msgSend(id receiver, SEL selector, args ...). On the PowerPC, function arguments are passed in general registers 3 to 10. So if you break on objc_msgSend, $r3 is your receiver, and $r4 is your selector.

Say your backtrace looks like this:

(gdb) bt
#0 0x90811234 in objc_msgSend ()
#1 0x92dcc818 in -[NSApplication(NSWindowCache) _findWindowUsingCache:] ()
#2 0x92dc312c in _DPSNextEvent ()

You could type the following to discover the receiver's class:
(gdb) p (char *) object_getClassName($r3)
$4 = 0x90b04aa4 "NSConcreteMutableData"

or the following to discover the message selector:
(gdb) p (char *) sel_getName($r4)
$5 = 0x90861ae4 "length"

These functions are declared in /usr/include/objc/. Note that the former is equivalent to po [$r3 className], but since you set a breakpoint on objc_msgSend, you won't get very far.

Also, you can type info symbol to find the symbol for an address. This isn't really Cocoa related, but it also helps when you're debugging without symbols.

Say your backtrace looks like this:

Thread 0 Crashed:
#0 0x004146ac in 0x4146ac
#1 0x003c3bf0 in 0x3c3bf0
#2 0x001d6610 in -[MyObject foo:]

(gdb) info symbol 0x003c3bf0
_mh_execute_header + 3943408 in section LC_SEGMENT.__TEXT of /System/Library/Frameworks/OpenGL.framework/Versions/A/Resources/GLEngine.bundle/GLEngine
_mh_execute_header + 3943408 in section LC_SEGMENT.__TEXT.__text of /System/Library/Frameworks/OpenGL.framework/Versions/A/Resources/GLEngine.bundle/GLEngine

This tells you that -[MyObject foo:] crashed in an OpenGL call. You could dig deeper by disassembling the memory around that area, but that alone gives you a good hint on what to look for in your source code.

Use killall to kill processes by name. Use opendiff to compare two text files graphically. It's the command-line way of invoking

Do you have any debugging tricks for me?

[Update: Eric and Louis posted more great tips! I can't believe I left out breaking on raise.]

Posted by jrc at August 28, 2003 08:51 PM

I used fly paper with great success two days ago. The neighbor's garbage was quite infested with the bugs and made their way to my apartment.

This has happened twice now with the same neighbor. I think either he or I will have to move now...

What's up John? Sorry, I still don't have Bolo.

Posted by: CMPrince at August 29, 2003 05:54 AM

Great stuff! Thanks :-).

Posted by: Buzz Andersen at August 29, 2003 09:47 AM

What about Shark?

Posted by: An anonymous Coward at August 29, 2003 01:52 PM

I like conditional breakpoints. For example, I want to see why the file "barf.txt" is being opened. I can do:

break open
condition 1 (0 == strcmp("barf.txt", $r3))

Normally open is called a zillion times, but this way, it only breaks the one time I care.

Posted by: JAV at September 1, 2003 05:20 PM
Post a comment

Remember personal info?

(What's this?)
Enter the text as originally written.
soif youbreak on