Okay, I am completely stumped and frustrated. I was working on an I/O Kit RAM Disk implementation and discovered that it wasn't unloading when my friend loaded it with kextload
and then tried to unload it with kextunload
. The reason for this was that none of the OSObject
s which the kext allocated were being freed. However, on both my computer (running Mac OS 10.8.5) and a VM (running Mac OS 10.7) everything worked as expected.
Eventually, I narrowed the problem down so much that I created a new Xcode project with a blank I/O Kit driver and tested it on my friend's machine. Lo and behold, I was unable to unload the module with kextunload
because it was claiming that my IOService
subclass was leaking. I would like to test another driver on my friend's machine where I don't override any IOService
methods (in the version I tested, I override a few to do IOLog
s before passing the call to super
). I will update this with any extra information I collect about his machine's configuration.
Here is my header (BrokenDriver.h):
#include <IOKit/IOService.h>
#include <IOKit/IOLib.h>
class BrokenDriver : IOService {
OSDeclareDefaultStructors(BrokenDriver)
public:
virtual bool init(OSDictionary * dictionary = NULL);
virtual void free();
virtual bool start(IOService * provider);
virtual void stop(IOService * provider);
};
And here is my implementation (BrokenDriver.cpp):
#define super IOService
OSDefineMetaClassAndStructors(BrokenDriver, IOService);
bool BrokenDriver::start(IOService * provider) {
bool success;
IOLog("%s[%p]::%s(%p)\n", getName(), this, __FUNCTION__, provider);
success = super::start(provider);
if (success) {
// Don't worry, the problem persists even if I don't call registerService()
registerService();
}
return success;
}
void BrokenDriver::stop(IOService * provider) {
IOLog("%s[%p]::%s(%p)\n", getName(), this, __FUNCTION__, provider);
super::stop(provider);
}
bool BrokenDriver::init(OSDictionary * dictionary) {
if (!super::init(dictionary)) {
return false;
}
IOLog("%s[%p]::%s(%p)\n", getName(), this, __FUNCTION__, dictionary);
return true;
}
void BrokenDriver::free(void) {
IOLog("%s[%p]::%s()\n", getName(), this, __FUNCTION__);
super::free();
}
Additionally, since I know this could be the source of problems, here is the XML of my BrokenDriver-Info.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.aqnichol.${PRODUCT_NAME:rfc1034identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2013 Alex Nichol. All rights reserved.</string>
<key>IOKitPersonalities</key>
<dict>
<key>BrokenDriver</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.aqnichol.BrokenDriver</string>
<key>IOClass</key>
<string>BrokenDriver</string>
<key>IOKitDebug</key>
<integer>65535</integer>
<key>IOMatchCategory</key>
<string>BrokenDriver</string>
<key>IOProbeScore</key>
<integer>1000</integer>
<key>IOProviderClass</key>
<string>IOResources</string>
<key>IOResourceMatch</key>
<string>IOKit</string>
</dict>
</dict>
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.kpi.iokit</key>
<string>9.0.0</string>
<key>com.apple.kpi.libkern</key>
<string>9.0.0</string>
<key>com.apple.kpi.mach</key>
<string>9.0.0</string>
</dict>
</dict>
</plist>
So, what's the verdict? Is my friend's kernel fried, or is it my brain? Is it even remotely possible that some other driver on his machine is attempting to persist my kext?
UPDATE: I tried again with an EMPTY implementation. That's right, I overrode exactly 0 methods of my own. The problem persists. Here is the message from kextunload
:
(kernel) Can't unload kext com.aqnichol.BrokenDriver; classes have instances:
(kernel) Kext com.aqnichol.BrokenDriver class BrokenDriver has 1 instance.
Failed to unload com.aqnichol.BrokenDriver - (libkern/kext) kext is in use or retained (cannot unload).