Nat! bio photo


Senior Mull

Twitter Github Twitch

_NSI3, CFExecutableLinkedOnOrAfter, NSInvocation trouble

I can't quite put my finger on it, but I think there is a bug in PPC NSInvocation, when the method to be invoked is performSelector:withObject: as I had sporadic crashes in _NSI3(). I suspect a register is clobbered there somewhere.

I had a hellish time getting a particular piece of software to run.Wading knee-deep through disassembled code stumbled upon the following in -[NSInvocation invoke]

0x90157310 <+144>: li      r3,5
0x90157314 <+148>: bl      0x901383ec <_CFExecutableLinkedOnOrAfter>
0x90157318 <+152>: cmpwi   cr7,r3,0
0x9015731c <+156>: bne-    cr7,0x90157358 <+216>

0x90157320 <+160>: lwz     r0,24(r27)
0x90157324 <+164>: andi.   r2,r0,512
0x90157328 <+168>: bne-    0x90157334 <+180>
0x9015732c <+172>: lwz     r4,8(r30)
0x90157330 <+176>: b       0x90157338 <+184>
0x90157334 <+180>: lwz     r4,4(r30)
0x90157338 <+184>: addi    r1,r1,96
0x9015733c <+188>: lwz     r5,16(r30)
0x90157340 <+192>: mr      r3,r27
0x90157344 <+196>: lwz     r0,8(r1)
0x90157348 <+200>: li      r6,1
0x9015734c <+204>: lmw     r26,-24(r1)
0x90157350 <+208>: mtlr    r0
0x90157354 <+212>: b       0x901565e8 <__NSI3>

0x90157358 <+216>: addi    r1,r1,96
0x9015735c <+220>: lwz     r0,8(r1)
0x90157360 <+224>: lmw     r26,-24(r1)
0x90157364 <+228>: mtlr    r0
0x90157368 <+232>: blr

The mysterious _CFExecutableLinkedOnOrAfter

Now my problem definitely happened in _NSI3 and this code path is selected based on _CFExecutableLinkedOnOrAfter on which Google finds for example this entry on an Ars Techica board:

Most frameworks don't have such huge binary compatibility issues that they need new versions. Then again, Mac OS X has _CFExecutableLinkedOnOrAfter() that system APIs use to determine if an incorrect 10.4 behaviour should be maintained on 10.5. As soon as they compile/link on 10.5 using the 10.5SDK, they are saying they no longer want the 10.4 legacy behaviour.

Here's where it gets strange (i.e. complicated). My test application works when it is compiled as a 10.5 tool, that links to a 10.4 framework.
But when I compile the tool against the 10.4 SDK and set Deployment target to 10.4, it crashes. Basically the other way as I would expect.

What _CFExecutableLinkedOnOrAfter does

Which begs the question, what is _CFExecutableLinkedOnOrAfter doing after all ? Searching with Google yields this a little outdated version

#define checkLibrary(LIBNAME, VERSIONFIELD) \
    {int vers = CFGetExecutableLinkedLibraryVersion(LIBNAME).primaryVersion; \
     if ((vers != 0xFFFF) && (versionInfo[version].VERSIONFIELD != 0xFFFF) && ((version == 0) || \
(versionInfo[version-1].VERSIONFIELD < versionInfo[version].VERSIONFIELD))) \
return (results[version] = ((vers < versionInfo[version].VERSIONFIELD) ? false : true)); }

CF_EXPORT Boolean _CFExecutableLinkedOnOrAfter(CFSystemVersion version) {
    // The numbers in the below table should be the numbers for any version of the framework
    // in the release.
    // When adding new entries to this table for a new build train, it's simplest to use the 
    // versions of the first new versions of projects submitted to the new train. These can 
    // later be updated. One thing to watch for is that software updates
    // for the previous release do not increase numbers beyond the number used for the next release!
    // For a given train, don't ever use the last versions submitted to the previous train! 
    // (This to assure room for software updates.)
    // If versions are the same as previous release, use 0xFFFF; this will assure the answer 
    // is a conservative NO.
    // NOTE: Also update the CFM check below, perhaps to the previous release... (???)
    static const struct {
        uint16_t libSystemVersion;
        uint16_t cocoaVersion;
        uint16_t appkitVersion;
        uint16_t fouVersion;
        uint16_t cfVersion;
        uint16_t carbonVersion;
        uint16_t applicationServicesVersion;
        uint16_t coreServicesVersion;
        uint16_t iokitVersion;
    } versionInfo[] = {
        {50, 5, 577, 397, 196, 113, 16, 9, 52},         /* CFSystemVersionCheetah (used the last versions) */
        {55, 7, 620, 425, 226, 122, 16, 10, 67},        /* CFSystemVersionPuma (used the last versions) */
        {56, 8, 631, 431, 232, 122, 17, 11, 73},        /* CFSystemVersionJaguar */
        {67, 9, 704, 481, 281, 126, 19, 16, 159},       /* CFSystemVersionPanther */
        {73, 10, 750, 505, 305, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF},        /* CFSystemVersionTiger */
        {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF},       /* CFSystemVersionChablis */
    static char results[CFSystemVersionMax] = {-2, -2, -2, -2, -2, -2}; /* We cache the results per-release; there are only a few of these... */
    if (version >= CFSystemVersionMax) return false; /* Actually, we don't know the answer, and something scary is going on */
    if (results[version] != -2) return results[version];

    if (_CFIsCFM()) {
        results[version] = (version <= CFSystemVersionJaguar) ? true : false;
        return results[version];
    checkLibrary("System", libSystemVersion);   // Pretty much everyone links with this
    checkLibrary("Cocoa", cocoaVersion);
    checkLibrary("AppKit", appkitVersion);
    checkLibrary("Foundation", fouVersion);
    checkLibrary("CoreFoundation", cfVersion);
    checkLibrary("Carbon", carbonVersion);
    checkLibrary("ApplicationServices", applicationServicesVersion);
    checkLibrary("CoreServices", coreServicesVersion);
    checkLibrary("IOKit", iokitVersion);
    /* If not found, then simply return NO to indicate earlier --- compatibility by default, unfortunately */
    return false;

_CFExecutableLinkedOnOrAfter scans the linked shared libraries by name until one matches. It extracts the version compares it with it's internal table and then it's all set.

Apart from my problem, It's obvious, that this can only work in the very simplest circumstances, where you can recompile everything in your app, that is not from Apple and you don't expect to load third-party NSBundles. Technically I think _CFExecutableLinkedOnOrAfter is a pretty poor idea.

No solution but a workaround

So it looks like it _CFExecutableLinkedOnOrAfter is not really my problem. I fixed my problem, my moving performSelector:withObject: "outside" of the NSInvocation. In case _NSI3() is doing some stuff, that should only be done in 10.5, as I suspect it does, then -[NSInvocation invoke] should rather read:

0x9015731c <+156>: beq-    cr7,0x90157358 <+216>