Friday, August 04, 2006

Tracing Objective-C Messages

Tools like strace, ltrace, truss, ktrace, etc, are very cool, and necessary if you really want to understand how things work. They allow you to watch what a process is doing by showing you when certain functions are called. It would also be really cool if we could see similar information as Objective-C messages are sent.

So, I read through the Objective-C runtime code and discovered a way. A few days later I found a good blog post by Dave Dribin here that outlines the basic idea that I had used. However, his solution requires you to recompile libobjc.dylib, which is undesirable as well as unrealistic in many cases.

Please take a few moments to read his post (again, here), then come back and read the rest of this...

...

OK, as he explains, the symbol that we want access to "_logObjcMessageSends" isn't exported (remember, nm showed it as a little "t") so he rebuilds the libobjc dylib in order to export the symbol. I'd like to propose an alternate solution that doesn't require touching libobjc.dylib.

Rather than looking up the symbol address using dlsym(), we should use the often overlooked nlist(3) function, which will return us the address of "private" symbols. So, in our dylib that we want to insert with DYLD_INSERT_LIBRARIES, we could have code like:

...
typedef int (*ObjCLogProc)(BOOL, const char *, const char *, SEL);
typedef int (*LogObjcMessageSendsFunc)(ObjCLogProc);

struct nlist nl[2];
bzero(&nl, sizeof(struct nlist) * 2);
nl[0].n_un.n_name = "_logObjcMessageSends";

if (nlist("/usr/lib/libobjc.dylib", nl) < 0 || nl[0].n_type == N_UNDF) {
fprintf(stderr, "nlist(%s, %s) failed\n",
"/usr/lib/libobjc.dylib",
nl[0].n_un.n_name);
return;
}

LogObjcMessageSendsFunc fcn = (LogObjcMessageSendsFunc) nl[0].n_value;
(fcn)(&MyLogObjCMessageSendFunction);
...

This code uses nlist() to look up the address of _logObjcMessageSends. The symbol it's looking up happens to be "private", but that's OK. Then once it has the address of the symbol, it casts it to a pointer to a function with the correct signature. Once that's done, the new function pointer is used just like any ol' function.

So, this solution works just like Dave Dribin's, but it doesn't require a recompile of the Objective-C runtime.

No comments: