LD37 - Zombie Room Mac OS
Free download Zombie Area! Zombie Area!puts you in the role of one man alone against the raving hordes of zombies. Apple fans shouldn't get too smug about this because the firewall which shipped with Mac OS X Tiger is just as bad. Jay Beale discovered that even if you enable the “ Block UDP Traffic ” box in the firewall GUI, packets from port 67 (DHCP) and 5,353 (Zeroconf) pass right through. Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. Testing conducted by Apple in October 2020 using preproduction Mac mini systems with Apple M1 chip, and production 3.6GHz quad‑core Intel Core i3‑based Mac mini systems, all configured with 16GB of RAM and 2TB SSD. Open source project built with prerelease Xcode 12.2 with Apple Clang 12.0.0, Ninja 1.10.0.git, and CMake 3.16.5. Business chats, one-click conference calls and shared documents – all protected with end-to-end encryption. Also available for personal use.
Next article: Friday Q&A 2015-01-23: Let's Build Swift Notifications
Previous article: A Brief Pause
Tags: braaaiiiinnnssssfridayqnamemoryzombie
Zombies are a valuable tool for debugging memory management problems. I previously discussed the implementation of zombies, and today I'm going to go one step further and build them from scratch, a topic suggested by Шпирко Алексей.
Review
Zombies detect memory management errors. Specifically, they detect the scenario where an Objective-C object is deallocated and then a message is sent using a pointer to where that object used to be. This is a specific case of the general 'use after free' error.
In normal operation, this results in a message being sent to memory that may have been overwritten or returned to the kernel. This results in a crash if the memory has been returned to the kernel, and can result in a crash if the memory has been overwritten. In the case where the memory was overwritten with a new Objective-C object, then the message is sent to that new object which is probably completely unrelated to the original one, which can cause exceptions thrown due to unrecognized selectors or can even cause bizarre misbehaviors if the message is one the object actually responds to.
It's also possible that the memory hasn't been touched and still contains the original object, in a post-dealloc
state. This can lead to other interesting and bizarre failures. For example, if the object contains a UNIX file handle, it may call close
on a file descriptor twice, which can end up closing a file descriptor owned by some other part of the program, causing a failure far away from the bug.
ARC has greatly reduced the frequency of these errors, but it hasn't eliminated them altogether. These problems can still occur due to problems with multithreading, interactions with non-ARC code, mismatched method declarations, or type system abuse that strips or changes ARC storage modifiers.
Zombies hook into object deallocation. Instead of freeing the underlying memory as the last step in object deallocation, zombies change the object to a new zombie class which intercepts all messages sent to it. Any message sent to a zombie object results in a diagnostic error message instead of the bizarre behavior you get in normal operation. There is also a mode where it rewrites the class and then frees the memory anyway, but this is typically much less useful since the memory will typically get reused quickly, and I'll ignore that option here.
To write our own zombies implementation, we need to hook object deallocation and build the appropriate zombie classes. Let's get started!
Catching All Messages
If we make a root class without any methods, then any message sent to an instance of that class will go into the runtime's forwarding machinery. This would seem to make forwardInvocation:
a natural point to catch messages. However, that one happens a bit too late. Before forwardInvocation:
can run, the runtime needs a method signature to construct an NSInvocation
object, and that means that methodSignatureForSelector:
runs first. This, then, is the override point for catching messages sent to a zombie object.
Dynamically Allocated Classes
In addition to the selector that was sent, zombies also remember the original class of the object. However, there may not be any room in the object's memory to store a reference to that original class. If the original class had no additional instance variables, then there's no space that can be repurposed for storage. The original class must therefore be stored in the zombie class rather than in the zombie object, and that means the zombie class needs to be dynamically allocated. Each class which has an instance that becomes a zombie will get its own zombie class.
The next question is where to store the reference to the original class. It's possible to allocate a class with some extra storage for things like this, but it's somewhat inconvenient to use. An easier way is to simply use the class name. Since Objective-C classes all live in one big namespace, the class name is sufficient to uniquely identify it within a process. By sticking a prefix on the original class name to generate the zombie class name, we end up with something that's both descriptive on its own and can be used to recover the original class name. We'll use MAZombie_
as the prefix.
Method Implementations
Note that all of the code here is built without ARC, since ARC memory management calls really get in the way here.
Let's start off with a simple method implementation, which is an empty one:
It turns out that the Objective-C runtime assumes that every class implements +initialize
. This is sent to a class before the first message sent to the class to allow it to do any setup it needs. If it's not implemented, the runtime sends it anyway and hits the forwarding machinery instead, which isn't helpful here. Adding an empty implementation of +initialize
avoids that problem. EmptyIMP
will be used as the implementation of +initialize
on zombie classes.
The implementation of -methodSignatureForSelector:
is a bit more interesting:
It retrieves the class of the object and that class's name. This is the name of the zombie class:
The original class name can be retrieved by stripping off the prefix:
Then it logs the error and calls abort()
to make sure you're paying attention:
Creating the Classes
The ZombifyClass
function takes a normal class and returns a zombie class, creating it if necessary:
The zombie class name is useful both for checking to see if a zombie class exists and for creating it if it doesn't:
The existence of the zombie class can be checked using NSClassFromString
. This also provides the zombie class so it can be returned immediately if it exists:
Note that there's a race condition here: if two instances of the same class are zombified from two threads simultaneously, they'll both try to create the zombie class. In real code, you'd need to wrap this whole chunk of code in a lock to ensure that doesn't happen.
A call to the objc_allocateClassPair
function allocates the zombie class:
We add the implementation of -methodSignatureForSelector:
using the class_addMethod
function. The signature of '@@::'
means that it returns an object, and takes three parameters: an object (self
), a selector (_cmd
), and another selector (the explicit selector parameter):
The empty method is also added as the implementation of +initialize
. There's no separate function for adding class methods. Instead, we add a method to the class's class, which is the metaclass:
Now that the class is set up, it can be registered with the runtime and returned:
Ld37 - Zombie Room Mac Os Catalina
Zombifying Objects
In order to turn objects into zombies, we'll replace the implementation of NSObject
's dealloc
method. Subclasses' dealloc
methods will still run, but once they go up the chain to NSObject
, the zombie code will run. This will prevent the object from being destroyed, and provides a place to set the object's class to the zombie class. This operation gets wrapped up into a function to enable zombies:
We can then put a call to EnableZombies
at the top of main()
or similar, and the rest takes care of itself. The implementation of ZombieDealloc
is straightforward. It calls ZombifyClass
to obtain the zombie class for the object being deallocated, then uses object_setClass
to change the class of the object to the zombie class:
Testing
Let's make sure it works:
I chose NSIndexSet
semi-arbitrarily, as a convenient class that doesn't hit CoreFoundation bridging weirdness. Running this code with zombies enabled produces:
Success!
Conclusion
Zombies are fairly simple to implement in the end. By dynamically allocating classes, we can easily keep track of the original class without needing to rely on storage within the zombie object. methodSignatureForSelector:
provides a convenient choke point for intercepting messages sent to the zombie object. A quick hook on -[NSObject dealloc]
lets us turn objects into zombies instead of destroying them when their retain count goes to zero.
That's it for today. Come back next time for more frightening tales. Until then, keep sending in your suggestions for topics.
Also, this article is a week late. October is for zombies... Now we need articles on NSTurkey. Or NSPumpkinSpiceLatte, or something... :)
https://chromium.googlesource.com/chromium/src/+/master/chrome/common/mac/objc_zombie.mm
Disclosure: I'm on the Chrome team, though I never worked on the zombie implementation.
Technically speaking, the granularity of malloc() on Darwin is 16 bytes, which is a minimum space occupied by an Obj-C object (barring evil custom +alloc implementations, of course). The isa variable on a 64-bit system occupies 8 bytes, which still leaves 8 bytes for a pointer to the original class. In other words, that's a great opportunity for optimization for 99.9% cases on all currently supported Apple systems. Just my 2 cents.
Konstantin Anoshkin: You're right, we could safely assume there's enough storage for another pointer in the real world. But I didn't like to make that assumption, partly just because it's messy, and partly because there's nothing that says Apple couldn't decide to use a more fine-grained allocator for objects that don't have any extra ivars. In a real implementation, assuming you're only running it for debugging and you check with malloc_size before you start overwriting stuff, it would be fine.
Also, I believe this implementation of zombies won't call -.cxx_destruct, which is used by ARC. Would it be a good idea to call that in ZombieDealloc?
Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.
Ld37 - Zombie Room Mac Os Pro
JavaScript is required to submit comments due to anti-spam measures. Please enable JavaScript and reload the page.
Click here to return to the 'How to disable an optical drive' hint |
I wonder if just using the kext unload command in the terminal would do it. ?? Hmm. interesting hint, thanks, this could also be good to secure your computer in lab setups.
Maybe I'm just being Mr. Obvious here, but wouldn't it be a heck of a lot easier to shut the machine down, open it up, and disconnect the ATA cable from the drive? Beats messing around with the OS.
How obvious, how could he have missed that taking apart an iBook or PowerBook is so much simpler than dragging and dropping a file into a folder.
Oh wait... 'and I didn't want to take apart the laptop and disconnect it'. Thank you Captain Obvious.
vThat's a powerbook,that's not use to disconnect the CABLE
thx for the author's tips
similar problem ever come to me,
but i didn't solve it,
now 3q for your guide
It Did not work for my MacBook Pro, any more Ideas?
sudo chown -R root:wheel IODVDStorageFamily.kext
sudo chmod -R 755 root:wheel IODVDStorageFamily.kext
sudo kextload IODVDStorageFamily.kext
Then I rebooted, and my system still can't see the optical drive. I think I need to somehow load the kext before login every time?
Nevermind, I found the solution. I simply had to plug a flash drive in and the optical drive was immediately recognized.
sudo kextunload IOSCSIArchitectureModelFamily.kext/Contents/PlugIns/IOSCSIMultimediaCommandsDevice.kext
sudo kextunload IOBDStorageFamily.kext
sudo kextunload IODVDStorageFamily.kext
sudo kextunload IOCDStorageFamily.kext
You can actually see all the dependancies by just looking at the command 'kextstat'. G4 Titanium PB 1Ghz DVI Superdrive. Superdrive failed, eating my hardware test disk. I opened it up and took the disk out. Upon boot, the newly-empty Superdrive started cycling endlessly, annoyingly. Looked online, saw this post. Tried deleting IODVDStorageFamily.kext to no avail; upon restart, drive kept up the annoying cycling. Dragged and dropped IODVDStorageFamily.kext back into System > Library> Extensions. Upon restart, my external Firewire DVD drive is no longer recognized; upon opening DVD Player, I get an error message that IODVDStorageFamily.kext was installed improperly, and I should contact the vendor. Also, no DVD drive recognized.
Oh, boy. So, my Superdrive is cycling endlessly, and I'd like to stop that. I'm fine with opening the PB up and pulling a plug. Which one? Can I remove the Superdrive entirely? I'm assuming not, as the modem and inverter mount on it, but how can I disable it or stop it from spazzing out?
Worse, I cannot now play a DVD from my (recently) working FW DVD drive.
A little help, please! And thanks in advance...
Okay, I went back in and unplugged the Superdrive, so that's that. And now when I insert a DVD in the FW drive, it shows up in System Profiler, DVD appears on the desktop with the right name... But when I try to play it with DVD player, I get 'There was an initialization error. A valid DVD drive could not be found. [-70012]. Help!
Ironically enough, I appear to have disabled my optical drive, all right...
So DVD Player will not run if the Superdrive is unplugged, despite the fact that you can read the DVD from a Firewire external and open it. So it's a DVD Player problem, at this point. My 'Super' drive is physically broken and cycling really annoyingly, so I'd like to unplug it, but can't watch DVD's in DVD Player like that. Maybe another player? Otherwise, issues are solved, sort of.
I tried this, but when I wanted to un-do it I just moved the files back (I did the CD and the DVD one). But now it just comes up with an error:
System extension cannot be used
The system extension '/System/Library/Extensions/IOCDStorageFamily.kext' was installed improperly and cannot be used. Please try reinstalling it, or contact the product's vendor for an update.
Is there something I can do?
In reply to my previous comment, I got my cd drive working again by loading Disk Utility, choosing my hard drive and selecting 'Repair permissions.' It popped up one more time saying it was still not working, but then I restarted and it is all working great ;).
P.S. In the original disabling, you do have to restart before anything really notices that the drive is gone.