13

I am writing a Mac OS 9 "compatibility layer" for Mac OS X because I was struck with nostalgia recently, and because all current solutions require you to run Classic inside a virtual machine that doesn't support everything it should to run the stuff I want to use.

To achieve that goal, I implemented a PEF executable loader, a PowerPC interpreter that can bridge to native code, and some Mac OS 9 libraries. The whole thing is kind of working: there are some bugs in the interpreter, but that was expected and I'm working on that.

My biggest issue so far is that PowerPC programs are 32-bits, so pointers need to be 32-bits. To satisfy that constraint, currently I'm compiling for i386 only. However, as I'm trying to build around that core, it's becoming more and more constraining (for instance, you can't use ARC with 32-bits Cocoa applications). Not to mention that it's not super-safe to let the PowerPC code access everything the host process can.

I've designed the "platform" with an eventual switch to 64-bits in mind: the interpreter expects a base address and all PowerPC pointers are offset by that base address. (Having a map that translates PowerPC addresses to native addresses is out of question, for performance and design reasons.)

Since a 64-bits address space has enough room to host four billions independent 32-bits address spaces, this sounds like a good way to go for me.

But I still have a problem: I need to be sure that I'll be able to allocate memory inside that address range, because it would be impossible for the PPC interpreter to access anything outside of it.

There are two solutions I am thinking of right now:

  • Find out if I can ask Mac OS to allocate something inside a specific address range. I know I can use mmap, but mmap will ask for allocations in page multiples, which seems very wasteful to me since I'd need a full page for each allocation, no matter how small it is (though that could actually be okay considering that Classic-era Macs had very little memory compared to modern computers);
  • Use mmap to reserve a full 0x100000000 bytes with PROT_NONE, then mprotect pages on demand to actually allocate memory when it's needed, and put them back to PROT_NONE when they're not useful anymore. It looks good on paper, but it means I have to implement a malloc replacement.

So, what should I do? Is there a built-in mechanism that will let me try to allocate memory, malloc-style, inside a specific address range? Otherwise, is there a good, readable, open-source implementation of malloc I could base myself on?

phuclv
  • 37,963
  • 15
  • 156
  • 475
zneak
  • 134,922
  • 42
  • 253
  • 328
  • 2
    Holy crapola. If you've got this even partway working, I'd love to see it! Can you throw it up on Github or something? –  Nov 22 '12 at 17:22
  • @duskwuff, sure will! I'm currently able to run a hello world program. My next goal is the Unmangle executable from the MPW tools (and this is where things get ugly with the interpreter). I'm working on a graphical debugger, and when I'll have something that can at least show disassembly in a Cocoa window, I'll push it to github (that might be today). My github handle is [zneak](https://github.com/zneak). – zneak Nov 22 '12 at 17:33
  • (I just don't want my first push to be something blatantly incomplete.) – zneak Nov 22 '12 at 17:33
  • To anyone interested, I [just published the project to GitHub](https://github.com/zneak/classix). – zneak Nov 23 '12 at 02:32
  • would it be possible to create the space by including a defined variable? You would want to have it load from the disk with something like a dictionary of text (it would be huge) and then just alloc within that space? – nycynik Nov 30 '12 at 21:47
  • @nycynik, in essence, this is the same as the `mmap` solution (though you don't need to have a huge file on drive to use it). I don't like it because while it gives me a chunk of memory, I have to write the code that splits it in decent-sized chunks to distribute. For instance, I wouldn't want to return one megabyte of memory if the caller only needs 16 bytes. – zneak Nov 30 '12 at 22:25

2 Answers2

4

I'm not aware of a built-in way to do this, but it is doable with a bit of work. One approach would be to create a custom malloc zone, and then use malloc_zone_* versions of the usual malloc functions when allocating any memory that needs to be visible to your PPC code. Your custom zone would need a malloc implementation, but you can pick from any number of open-source ones (e.g. tcmalloc). It would also need to be wired up to use something like vm_allocate with a hint address, to ensure you get allocations in the exact range you need.

Wade Tregaskis
  • 1,996
  • 11
  • 15
  • I don't know the `malloc_zone` function family, but this look promising. Do you think you could come up with an example of using it? Just looking at the manpages doesn't tell a lot about how to actually use the functions. Also, can it respect the requirement that all allocations are placed inside the same virtual address range? – zneak Nov 26 '12 at 22:14
  • You'd start by defining your own malloc zone, a `malloc_zone_t` (see /usr/include/malloc/malloc.h). You can register that when it's ready by calling `malloc_zone_register`. You'd fill it in with pointers to your custom malloc implementation - you could use the one Apple uses, which is part of the dyld project (available from opensource.apple.com, for example), or a 3rd party one (e.g. tcmalloc). Either way you'd have to tweak your custom malloc implementation to use `vm_allocate` with a hint, as I noted, rather than whatever it uses by default (possibly vm_allocate, or mmap, or brk). – Wade Tregaskis Nov 27 '12 at 07:24
  • (it's too involved for me to whip up an example, sorry. I'm not aware of any ready-made examples out there on the interwebs) – Wade Tregaskis Nov 27 '12 at 07:25
  • It looks like this would behave like a "normal" malloc except that I would have to give the implementations myself. In that case, it only provides a uniform interface for different allocators; it doesn't make implementing allocators easier. That's pretty cool, but not exactly what I need. – zneak Dec 02 '12 at 20:24
  • Yeah, it's a pretty involved DIY kind of solution. As I said, I don't know a better way. It seems like you're going to have to implement or find a custom malloc implementation regardless, since this functionality's definitely not built in to the system one. Whether or not you plug it into malloc or whatever is up to you. I think it's wise though as the malloc API provides some convenient abstractions. You might also want to make a custom CFAllocator, if that's convenient to your use, and it might be possible to reuse the malloc zone for that. – Wade Tregaskis Dec 02 '12 at 21:06
  • `CFAllocator`s can work natively with `malloc` zones, just like you can make a `NSZone` from any `malloc` zone and use `allocWithZone:` to allocate Objective-C objects. I already have a whole allocator abstraction, though, so I'm probably gonna use that (and if it would become handy to make a `CFAllocator` too, I'll probably just make a malloc zone over that allocation mechanism). – zneak Dec 03 '12 at 23:41
  • You certainly deserve some rep for pointing that out, but I don't feel comfortable awarding the full bounty since that didn't really solve the bigger issue (I'm now confident that there's no mechanism to natively allocate memory inside a specific range, but `malloc` zones won't help me reach that goal). The bounty system should award you a good +150 within a few hours. – zneak Dec 03 '12 at 23:47
0

So, I've considered this issue myself for a very similar (unpublished) project, and trying to use this 64-bit approach (or, indeed any sort of big-endian memory space on x86!) rapidly runs into a few nasty issues.

  1. While you can point native APIs at emulated structures, you can't force them to make all their own allocations within that arena. You can provide your own implementations of all the most obvious ones, like _NewPointer, but internal allocations by other functions (like, say, _NewWindow with nil wStorage) are largely out of your control.

  2. System structures (like a Rect, for instance) allocated by emulated code will be big-endian, but the OS will want them little-endian. There's no easy way to get around this besides maybe byte-swapping everything on the way in and out of native functions, and that seems hugely error-prone. (Another approach I considered here was to just make the emulated core run as little-endian, but some programs are, unfortunately, sensitive to this change.)

To be clear, I love the hell out of the idea of a Classic-to-OS-X API translator. But I'm pretty sure that it's impractical -- given that Apple ended using a VM for Classic on PPC, I suspect that they determined that a translation approach wasn't going to work.

  • I'm pretty sure Apple used a VM because they aimed for 100% identical behavior, and they almost got it (it only missed virtual memory as far as I can remember). I can live without the identical behavior. For "external" allocations, as long as they're wrapping an opaque type, this shouldn't be an issue, since I can just return a pointer to the actual pointer that a thin wrapper around the library would use. Maybe that will be error-prone, but I don't see anything strictly impossible. – zneak Nov 23 '12 at 21:28
  • Besides, I'm not really asking what will be hard with this project. I don't think there will be any surprise about what's going to be hard. I'm asking how I can allocate things inside a contiguous 0x100000000 address range. – zneak Nov 23 '12 at 21:31
  • I considered the pointer-wrapping approach -- it'dwork if pointers were always treated as opaque on classic Mac OS, but they typically aren't. Reading/writing system structures directly is a pretty common practice. :( –  Nov 24 '12 at 03:10
  • If you're still curious about [the whole thing](https://github.com/zneak/classix/), I got the [SillyBalls example program](http://pastebin.com/gYUtCwrq) essentially working this evening. – zneak Apr 18 '13 at 05:18