In my last entry I mentioned the private API
_LSSetStrongBindingForRef that Launch Services has to set the application that launches a particular file. I thought it might be interesting to post how I figured it out.
Firstly, I reasoned that Launch Services would probably have a private API that Finder uses to set it. I could have gone through all the symbols that Launch Services exports, including the private ones looking for likely candidates. To do that, you use the nm tool:
nm /System/Library/Frameworks/CoreServices.framework/ \
That will spew out a load of information. Any symbols that are tagged
U, are undefined which mean they're come from somewhere else--another framework or library. We're interested in symbols that are tagged
T since they're symbols that are exported by that framework. Symbols that have a lower case
t, are local to the module and aren't exported. Binaries that are stripped shouldn't have any symbols with a lower case codes. Remember that all C symbols will have an underscore at the beginning that you won't see in your code. C++ symbols are a different story that I won't go into here.
OK, so that's one way I could have figured it, but I got bored looking through them all, so...
Since I knew that Finder was able to set the binding, I figured I could just see what Finder does, but how to do that? What I did was attach the debugger (gdb) to Finder and set some breakpoints. To attach the debugger to Finder:
Apple's version of gdb supports tab completion for the attach command so when you press the tab command you should find it fills in the process id of Finder.
Now what to set the breakpoints on? Well, I first tried setting the breakpoint on write--obviously setting the binding is going to involve writing to the disk at some point (I already knew that the binding was set as part of the
usro resource in the resource fork) so my idea was that I'd set a breakpoint on write and then set a binding in Finder and see what we got. There aren't actually that many primitives for writing to the disk; all the Cocoa and Carbon frameworks eventually call BSD primitives to do what they want so you usually just need to set breakpoints on write/pwrite. For example, you'll probably find
-[NSData writeToFile:atomically:] calls a CoreFoundation function which then calls one of the BSD write functions.
To set a breakpoint:
Now on my system it sets breakpoints in two places, on
write$NOCANCEL. That's fine so I just continued. Type
c to continue.
OK, those of you playing along at home will find that whilst it might stop in a few places whilst you're trying to set the binding (e.g. when an open panel appears), it doesn't stop after you've actually set the binding. By the way, to continue after hitting a breakpoint, type
So then I set a breakpoint on
pwrite, and this time, just after setting the binding, the debugger stops. What now? Well, we want a backtrace. To get that, type
bt and this is what we get:
#0 0x00007fff82b8b03c in pwrite ()
#1 0x00007fff821b6b1a in BasicWrite ()
#2 0x00007fff821b6a49 in PBWriteForkSync ()
#3 0x00007fff821b69d6 in FSWriteFork ()
#4 0x00007fff822281c0 in WriteData ()
#5 0x00007fff822282d3 in WrResource ()
#6 0x00007fff821ca33a in UpdateTheFile ()
#7 0x00007fff821ca035 in UpdateResFileCommon ()
#8 0x00007fff8213a028 in _LSSetStrongBindingForRef ()
It goes on but I stopped at
_LSSetStrongBindingForRef. As you can see, there are a bunch of Carbon functions before we get to the
To get the prototype for the function I searched Google and found someone had already done it for me. ;-)
I should note that you'll probably want to detach from Finder: type detach, and then Finder will continue running. If you don't, you'll probably end up killing Finder which isn't actually such a big deal as it'll just be restarted for you.
Anyway, every programmer should get seriously familiar with gdb. In this case, I've used it to discover a private API which I personally wouldn't use and don't recommend others using either, but I have used similar techniques before to debug problems I've had with some of Apple's frameworks, not to mention my own code. I've mentioned the nm command which is useful, but there's also otool which will dump things like what libraries a binary is linked to; there's the awesome class-dump tool; there's fs_usage for monitoring what accesses the disk and lsof for tracking down open files, and then there's dtrace and probably many more that I've forgotten.