Almost elegant cave man debugging

Recently, the Twitternets pointed me at Vincent Gable’s blog post, The Most Useful Objective-C Code I’ve Ever Written. It is indeed quite useful; given a semi-arbitrary expression, it prints out the value, using @encode() and macros to minimize drudgery.

However, it’s limited to a fixed set of types. As Vincent points out, this could be avoided by parsing the @encode() string rather than recognising specific strings. As it happens, I had a partially working parser. It is now wholly working and added a convenience macro similar to Vicent’s, and you can find it here. It handles most structs, unions and arrays automatically. It’s still not entirely general, though, in part because of compiler limitations:

  • Bitfields aren’t supported. This is basically because I haven’t bothered. I’m not clear on whether the encoding actually provides enough information to handle these properly, though.
  • Altivec/SSE/NEON vector types aren’t handled, because @encode() completely ignores them – if you have a struct { int a; __m128 v; int b; }, the encoding is {?=ii}, as if the vector wasn’t even there.
  • Aggregate types with non-standard alignment (#pragma pack) are not supported. Again, the compiler doesn’t encode the necessary information.

The latter two cases could easily lead to crashes if left to their own devices. My solution is to first parse the entire type without reading any data, to ensure that the amount of data the parser will consume matches the size according to sizeof(), then reparse if the size matches.

There are probably cases it doesn’t handle. Bug reports would be welcome. Also, it doesn’t handle PPC-64 and ARM, because I don’t have suitable machines to extract alignment information on and I can’t be bothered to read actual documentation.

Update (2010-08-22): Added support for _Complex types (encoded as 'j'; a complex double is encoded as jd and stored the obvious way, as two doubles in a row).

Update (2010-08-23): Fixed alignment calculations for nested structs when alignment is not determined by first member.

Update (2013-02-18): ARM/iOS is now supported.

This entry was posted in Cocoa, Code and tagged , . Bookmark the permalink.

8 Responses to Almost elegant cave man debugging

  1. Aw, I was hoping you were parsing the @encode expression into an object tree, and then making that into a string; I have wanted to transform the @encode string into something useful many times but never had the energy to parse strings :P This is probably a good start though, so thanks :)

    (for example, I want to make an ffi cif from an encoding string to make this thing work better: http://github.com/nevyn/NSObject-AddMethod/blob/master/NSObject%2BAddMethod.h )

  2. Jens Ayton says:

    It’s a recursive descent parser which finds the type and location of each piece of data. Making a tree out of that is trivial; just build a node in each handler and pass the right parent node to any sub-decoder calls.

  3. Jens Ayton says:

    As it turns out, generating a parse tree may be a good idea, because correct alignment calculations sometimes require look-ahead. For example, in 64-bit, struct { int16_t a, struct { int16_t b, int64_t c }} is interpreted as [short][short][pad-32][long long], when it should be [short][pad-48][short][pad-48][long long]. Getting this right requires finding the alignment of the inner struct, which is the same as the alignment of the biggest element in it. An easier fix would be to use two passes to read structs, but this will still require redesign to get the alignment information where it’s needed.

  4. Jens Ayton says:

    As it turns out, the multi-pass fix was easier than I thought, and the parse tree harder – in particular, since it follows pointers in some cases, representing data locations in an abstract way becomes problematic. Bad luck for Joachim. :-)

  5. Kaks says:

    Hmm, sounds like Oolite might be getting some more debugging stuff soon… Or am I reading too much into this? ;)

  6. Jens Ayton says:

    Kaks, that depends on whether the type decoding works in GNUstep. If so, it’s just a matter of adding the files. If not, well, it may be fiddly.

  7. shmim says:

    Hi,

    This is cool! However, I can’t get it to work with Objective-C properties:

    JA_ENCODE(self.window) will fail.

    I assume this is due to the fact that I can’t (or don’t know how to) get the actual address of self.window.

    I’ve tried all possible variants (except maybe the one that will work…):

    &self->window
    [&self window]
    &(self->window)

    and so on.

    In the header, you say: “Only lvalues can be printed, since it is necessary to take the address of the target.”

    Does this mean we’re out of luck for properties like self.window and the like?

  8. Jens Ayton says:

    Yes, to dump a property you’ll first have to assign it to a temporary variable. Vincent’s version doesn’t have this problem, but instead it can’t deal with C arrays. As far as I’m aware this is an unavoidable tradeoff, unless the compiler grows a __builtin_is_array_type() predicate.

Leave a Reply

Your email address will not be published. Required fields are marked *