How exactly did 32 bit support get removed in OSX? (read carefully please)

I've seen a lot of people asking how to get 32 bit apps running again on newer versions of OSX, and mostly these just get answered with "sorry cant be done". Why? There must be a reason why it can't be done. From an OS perspective, its just libraries right? Why can't I make a kernel extension to enable 32-bit and get some 32-bit libraries?

And what about the M1 processor -- did intel-based 32bit support work via some capabilities of the chip, which I presume the M1 would not include? Are there any references on whether the M1 on another OS could run 32-bit applications (like linux or BSD)? I've heard reports that Windows for ARM on M1 will run 32 bit apps (I haven't independently verified this).

I'm really tired of seeing closed threads ending with "can't be done". Please put more effort in than that. I want a why and how.

I'm really tired of seeing closed threads ending with "can't be done".

Well, everything can be done with enough engineering effort but there are a number of things that make this infeasible:

  • macOS KEXTs are built on top of KPIs (kernel programming interfaces). These KPIs allow the KEXT to interact with the kernel in a specific way. For example, the I/O Kit KPI lets the KEXT implement a driver and the VFS plug-in KPI lets the KEXT implement a file system. There is no KPI for doing the major surgery required to bring back 32-bit support.

  • You could get around this by bypassing the KPIs but modern kernel’s are hardened to prevent that. And the only way around that is to disable SIP, which isn’t something I’d do on a production machine.

  • You could try replacing the kernel with one built from the Darwin source. The problem here is that the Darwin kernel is slightly different from the macOS kernel — small amounts of code don’t show up in Darwin for various reasons — so your kernel won’t run all of macOS.

  • On the libraries front, you wrote “get some 32-bit libraries”. Where do you propose to get these from? Writing them yourself would be a massive undertaking. Grabbing them from a previous version of macOS won’t work because these libraries don’t work in a vacuum. Many, perhaps even most, Apple frameworks work in coöperation with a helper process: a daemon, agent, XPC Service, or whatever. These are built together, so there’s no incentive to avoid rev-lock. That is, the helper process assumes that the client framework is speaking the same IPC protocol as it, and vice versa.

  • And you can’t just get some 32-bit helper processes because in many cases those helper processes assume that they have exclusive access to a resource. For example, cfprefsd, the helper process behind CFPreferences and NSUserDefaults, assumes that it ‘owns’ the preferences files and would fail badly if there was another instance running.

Finally, I’ll note that none of this stuff is officially documented, and for good reason. The goal of macOS is to provide services to users and developers, and neither of those audiences require that we provide in-depth documentation about the internal architecture of the platform. If you want to learn more about this, you’ll need to look at unofficial resources.

My go-to resource for this is Mac OS X Internals: A Systems Approach, by Amit Singh. It’s way out of date, and many of the details have changed, but it’s still worth a read just to get the lay of the land.

So, in summmary: sorry cant be done (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

The technical aspects have been well-answered above, and—for books—Levin's three-volume new OS X book replaces the book that was mentioned above, and is a good resource.

Now consider this at a higher level...

Compatibility is something all users and developers want, as we're otherwise building apps on a shifting foundation of mud and sticks, and that gets tedious and expensive.

Conversely, rote rigidity is not desirable, however. We don't get updates and enhancements, or they're slower, or more complex.

As valuable as API and ABI compatibility is, rote compatibility means the platform is locked into older designs and older limits and older bugs, which can constrain or even prevent desirable changes, and increases the costs of current and future work.

More generally, removing the older and limited and insecure APIs and ABIs means the old code supporting all that can also be ripped out, and the bug reports around issues and exploits that might be present closed, and which means you're writing more code in your hypothetical replacement project to retrofit those older interfaces into the newer environment.

Using something else that you're probably familiar with (this based on your nick) as an example of the strategy that Apple follows here, transport layer security (TLS) is a wrapper that is intended to provide a range of different choices for a secure key exchange, for the encryption used, and the handshake itself. This design allows a range of choices that can be used, and allows newer and more secure choices to be made available, while also allowing older and insecure key exchanges, ciphers, and handshakes to be deprecated and eventually to be removed as and if necessary.

What happens with TLS is also what happens with APIs and ABIs within most any platform that is being actively updated, though not necessarily as "cleanly" as the TLS design. ABIs and APIs can need to be retired and replaced, though—to avoid churning apps and developers—that deprecation and retirement and (where applicable) replacement preferably happens at timeframes longer than the LTS for the platform, or longer. In the case of Apple, the 64-bit transition started with OS X 10.5 and 10.6; a while ago. The 64-bit transition also removed the Carbon APIs, which were deprecated at OS X 10.8. Among other older and 32-bit code.

Could you craft a completely compatible design, given sufficient time and money? Probably. But parts of it will—like older TLS versions—be insecure or busted or otherwise limited. And now your platform code is that much more complex and convoluted and unstable and limited. And that much more insecure. And can block some of the changes you might want to implement—such as a hypothetical ABI that previously specified an eight byte user-allocated buffer for a cryptographic hash being replaced with a now-secure cryptographic hash requiring sixteen or thirty-two bytes. Keeping that old ABI means you're inherently dragging around fodder for exploits. Or for your retrofit, now you're reimplementing the bug.

Even ill-used code has to be built and maintained and tested and secured.

The other wrinkle here is that all this app compatibility work and all this investment is intended for apps that aren't being maintained or invested by their vendors, or by users wishing to avoid purchasing newer versions of those apps. Or put differently, folks that aren't spending or aren't buying.

In short, the "can't be done" is pretty close. Not at a price and with a development schedule and with the inherent limitations to changes that most of us would be willing to pay. Boot a hypervisor guest on Intel platforms. Maybe boot an emulator and a guest (QEMU?) on Apple silicon. Or maybe update the apps once a decade, or purchase newer versions or alternatives of those same apps occasionally.

thank you for your answers, I'm really satisfied with the great explanations provded by both @eskimo and @Hoffman. I can see now the implications of attempting to run 32 bit stuff on my 64bit machine is a bad idea because of the security and development requirements now. I'll rest easy knowing that the best way to run my 32-bit apps is in a VM. I'd really rather not reimplement a ton of kernel stuff and fix a million libraries to work on the newest OSX, I had assumed I could just grab the old library source and get that running, but I can see thats not how it really works.

"can't be done" I can see why that's an answer :) Thank you both for clearing this up for me.

Maybe you could write a program that will dlopen() and 32bit app, the intercept any interlibrary calls, libc, syscall, with glue to translate 32bit to 64bit and back. It would be a massive undertaking, with no guarantee that you wouldn't hit a brick wall at somepoint..

.. when all "they'd" have to do is compile it for 64 bit :)

Might even be easier to go translate the 32bit assembler to 64bit in each segment. But that does nothing to help you with the "old" API calls, you'd still need to interpose them with glue.

How exactly did 32 bit support get removed in OSX? (read carefully please)
 
 
Q