Search code examples
xcodemacosiokitdriverkit

How to build a DEXT that works on both Big Sur and Monterey


We are facing a bit of a conundrum in our DriverKit extension development. We would like to build and debug on Monterey. This means that we need to use Xcode 13. We also need to support Big Sur. Unfortunately we haven't been able to build a DEXT with Xcode 13 that works on Big Sur.

We are setting the DRIVERKIT_DEPLOYMENT_TARGET to 19 (the lowest possible value). The DEXT loads fine on Big Sur but whenever a user client connects the DEXT process crashes with an assertion failure like this:

Crashed Thread:        0  Dispatch queue: Root

Exception Type:        EXC_CRASH (SIGABRT)
Exception Codes:       0x0000000000000000, 0x0000000000000000
Exception Note:        EXC_CORPSE_NOTIFY

Application Specific Information:
dyld2 mode
abort() called

Thread 0 Crashed:: Dispatch queue: Root
0   libsystem_kernel.dylib          0x0000000104bfb3a4 __pthread_kill + 8
1   libsystem_pthread.dylib         0x0000000104de6844 pthread_kill + 272
2   libsystem_c.dylib               0x0000000104b98f24 abort + 124
3   com.apple.DriverKit             0x00000001048b12b4 __assert_rtn + 92
4   com.apple.DriverKit             0x00000001048b151c OSMetaClassBase::QueueForObject(unsigned long long) (.cold.2) + 44
5   com.apple.DriverKit             0x0000000104893068 OSMetaClassBase::QueueForObject(unsigned long long) + 176
6   com.apple.DriverKit             0x0000000104893780 OSMetaClassBase::Invoke(IORPC) + 412
7   com.apple.DriverKit             0x000000010489425c Server(void*, mach_msg_header_t*, mach_msg_header_t*) + 512
8   com.apple.DriverKit             0x00000001048959c8 uiomachchannel(void*, dispatch_mach_reason_t, dispatch_mach_msg_s*, int) + 156
9   libdispatch.dylib               0x0000000104a43b90 _dispatch_mach_msg_invoke + 476
10  libdispatch.dylib               0x0000000104a313ec _dispatch_lane_serial_drain + 308
11  libdispatch.dylib               0x0000000104a448f4 _dispatch_mach_invoke + 464
12  libdispatch.dylib               0x0000000104a313ec _dispatch_lane_serial_drain + 308
13  libdispatch.dylib               0x0000000104a32154 _dispatch_lane_invoke + 456
14  libdispatch.dylib               0x0000000104a33408 _dispatch_workloop_invoke + 1680
15  libdispatch.dylib               0x0000000104a3c9f0 _dispatch_workloop_worker_thread + 764
16  libsystem_pthread.dylib         0x0000000104de75e0 _pthread_wqthread + 276
17  libsystem_pthread.dylib         0x0000000104dee7fc start_wqthread + 8

I've seen a similar problem on the Apple developer forums and the advice seems to be "upgrade to Monterey", which doesn't help much.

I have not been able to locate any meaningful assertion message. I tried digging in the XNU sources to find the failing assert, but did not have any luck.

Has anyone been able to build a DEXT with Xcode 13 that works on Big Sur? Any pointers on what to try are very welcome.


Solution

  • It's taken me a year+, but I've finally got around to finding the root cause and an acceptable solution for this behaviour.

    (Shipping two different dexts as mentioned in the other answer isn't helpful at all from my point of view, as a key reason for using a newer SDK and Xcode version in the first place is Apple's lack of backwards compatibility for Xcode itself - you'd still have to build the Big Sur version on Xcode 12, which won't run on Monterey let alone Ventura.)

    The apparent cause

    With the DriverKit 21 SDK, Apple annotated the IOUserClient::ExternalMethod function to run on a dedicated named dispatch queue. (QUEUENAME(IOUserClientQueueExternalMethod)) According to the documentation, this queue starts off as the object's default queue, but can of course be set to another queue.

    However, this is presumably only the case on macOS 12 and newer, as there is no reference to a queue with that name in older DriverKit SDKs and kernel sources. That would explain why the crash happens when an assertion fails that a queue lookup must result in a non-null queue pointer. (The OSMetaClassBase::QueueForObject in the crash stacktrace.)

    Workaround

    The workaround appears to actually be pretty straightforward! When building with an affected SDK, make sure the named queue is explicitly set, at least on affected macOS versions. You need to do this during init or Start on every instance of all IOUserClient subclasses in the dext:

    IODispatchQueue* default_queue = nullptr;
    kern_return_t res = uc->CopyDispatchQueue(kIOServiceDefaultQueueName, &default_queue);
    if (res == KERN_SUCCESS && default_queue != nullptr)
    {
        res = uc->SetDispatchQueue(kIOUserClientQueueNameExternalMethod, default_queue);
    }
    OSSafeReleaseNULL(default_queue);
    

    Note that kIOUserClientQueueNameExternalMethod is only defined on affected SDK versions, so you can use #ifdef kIOUserClientQueueNameExternalMethod to check at build time whether the fix is needed.

    I've added a helper function to my open source library of dext and kext helper functions that just needs to be called during init or Start and sorts it out for you.