Wednesday, March 14, 2012

Using gdb to debug Objective-C code

I've been using gdb for many years, initially for debugging code for embedded software, written in C/C++ and running on specialized hardware, and more recently for software meant to run on iOS devices, written in Objective-C. Knowing the way to use it for C/C++ served me well, as most of that stuff is applicable to Objective-C too, including the most commonly used commands. I was aware of, and had been using some commands specific to Objective-C, like print-object, but not much beyond that.

At some point I came across a presentation that informed me about the ability to evaluate and print the value of complex expressions, including method calls. Of course you have to be mindful of possible side-effects, but that's a pretty powerful capability to have in the middle of a debug session.

So recently I had to inspect the number of entries in a dictionary. Even though I could have used the Xcode's ability to hover over variable's name and "inspect" some of its content, which for NSDictionary works really well, in that it shows how many key/value pairs it contains (in my case 10), I thought I should use the command-line gdb to find that out. So I did:

(gdb) po [existentMetadataForPrefix count]
0xa does not appear to point to a valid object.

Hmm, not what I expected. The 0xa mentioned there in hex is 10 in decimal, so the value appears OK. It looks like po (shorthand for print-object) tries to send a message to the object pointed by 0x10. In fact that message is "description", so what I get is fair enough. Obviously I shouldn't be using print-object on an integer value (NSUInteger) returned by count.

To print primitive values in C/C++ one would use straight print. Doing so yields:

(gdb) p [existentMetadataForPrefix count]
Unable to call function "objc_msgSend" at 0x1b1e08c: no return type information available.
To call this function anyway, you can cast the return type explicitly (e.g. 'print (float) fabs (3.0)')

What the ... ? This doesn't make any sense. That 0x1b1e08c address is not the address of existentMetadataForPrefix, as shown by:

(gdb) p existentMetadataForPrefix
$2 = (NSMutableDictionary *) 0xf520c10

Then, what's at that address ? Can find out, like so:

(gdb) x 0x1b1e08c
0x1b1e08c : 0x08244c8b

Huh, looks like that might be the actual address of the function objc_msgSend. Can that be ? Let's double-check:

(gdb) x objc_msgSend
0x1b1e08c : 0x08244c8b

It is indeed, so what's going on here ? AFAIK that objc_msgSend function is called when a message like count is sent to an object like existentMetadataForPrefix.  Let's set a breakpoint:

(gdb) b objc_msgSend
Breakpoint 3 at 0x1b1e08c

And try again:

(gdb) p [existentMetadataForPrefix count]
Unable to call function "objc_msgSend" at 0x1b1e08c: no return type information available.
To call this function anyway, you can cast the return type explicitly (e.g. 'print (float) fabs (3.0)')

Nope, the breakpoint doesn't get hit. Ok, at this point it's pretty clear that I need to learn more about how to use gdb with Objective-C. In doing so I discovered this excellent blog post that explains that:
We can also print the result of messages which return primitive values. However, gdb is not smart enough to figure out the return type in this case, so we have to tell it by adding a cast to the expression:
So all I had to do was this:

(gdb) p (int)[existentMetadataForPrefix count]
$1 = 10

Which, in all fairness was in fact suggested by gdb, but was shrouded in confusion, at least for me, by the mention of objc_msgSend.

I hope this would help you in case you run into a similar problem, and I can definitely recommend checking out that blog post. It's full of very interesting gotchas on this topic.

No comments: