For those that are interested, here are the details behind this error.
First, I acknowledge the excellent work of kby, who I think is the first person to figure out what is going on.
Second, please note that Mac (and C) programming is not my normal area of expertise, so I Could Be Wrong.
In the ever popular Q&A format:
Q. What does SheepShaver mean by "ERROR: Cannot map RAM"?
A. SheepShaver is attempting to allocate a block of virtual memory, for the size specified in the preferences. This allocation attempt is failing.
Q. Why does the error say "File already exists"?
A. This is a bug in the code. It is displaying the value of the C error variable "errno". The problem is, errno isn't set by the calls it is making. Errno has a value (17) left over from
before it tries to allocate the memory. 17 is the value for "File already exists".
I changed it so that it uses an error code of ENOMEM, which changes the message to "ERROR: Cannot map RAM: Cannot allocate memory", which is closer to what is going on.
Q. Why can't it allocate the memory it wants?
A. The address block it wants is already in use.
SheepShaver is coded to allocate its memory blocks at
fixed addresses. This seems strange, but it sort of makes sense.
SheepShaver can be compiled to run in two different memory access modes: DIRECT and REAL. REAL means that the virtual memory addresses as allocated in the host machine are
the same as the virtual address seen inside the emulated machine. For example, if the ROM is loaded into the host space at 0x40000000, then the emulated Mac sees the ROM at the same address.
This improves performance (especially when run natively on PowerPC hosts), since it doesn't have to do address translation (aside from byte swapping).
SheepShaver wants to allocate the following blocks on Mac OS or Unix/Linux hosts running in REAL mode:
Code: Select all
RAM area ???M 0x20000000 to ?
ROM area: 5M 0x40800000 to 0x40D00000
Kernel Data 2: 8K 0x5fffe000 to 0x60000000
SS Globals: 512K 0x60000000 to 0x60080000
Sig stack: 64K 0x60080000 to 0x60090000
DR Emulator: 64K 0x68070000 to 0x68080000
Kernal Data 1: 8K 0x68ffe000 to 0x69000000
DR Cache: 512K 0x69000000 to 0x69080000
Load mod: 0x78048000
The first thing we notice is that if the RAM starts at 0x20000000 and the ROM starts at 0x40800000, then there is no way to have a RAM area greater than 512 MB. (Larger RAM areas work on Windows because on Windows SheepShaver allocates the RAM starting at 0x0.)
On certain Leopard machines, it can't get the RAM area because there already is something allocated at 0x20000000.
Q. What is already allocated at 0x20000000?
A. I have an Intel iMac with 512 MB of graphics memory. On my machine, there is a 512MB memory allocation by IOKit, running from 0x01f4d000 to 0x21f4d000.
There are some other blocks allocated past that point. To succeed in allocating the RAM, the RAM area has to start at x25811000.
Q. Why does the problem only occur on some machines?
A. I think kby had the answer: it is the IOKit allocation. I wouldn't be surprised if most of the people that get the error have 512 MB of graphics memory in their machines.
It also depends on some other factors:
- Do you have any Input Managers (Saft, 1Password, PithHelmet, etc.)? Input managers inject code into every process, including SheepShaver. This will slightly shift the memory allocations.
- Did the Mac load IOKit in low memory or high memory? On my Intel Mac running Leopard, it loaded it low. On my PowerPC Mac running Tiger, it loads it high, which is out of the way of SheepShaver
Q. Even so, why does it vary by machine? And why would running a OS update combo updater fix it? And what does this have to do with Leopard?
A. My theory is: Leopard introduced "Library Randomization".
Library Randomization means that the OS doesn't always load the system libraries at the same point. This is to make it more difficult to hack the machine.
I suspect that on Tiger the IOKit always loads high, and on Leopard it doesn't. (This is a wild guess.)
The key is that Library Randomization is not per launch or per boot. It is
per machine. And when do the library load points get randomized? It is when pre-binding occurs.
Guess what happens when you install an OS update? The last thing it does is update the pre-binding!
So my theory is that some machines redoing the pre-binding shifted the library load points just enough so that the block at 0x20000000 was left free, so SheepShaver could load.
(We could test this theory by using a utility to just update the pre-binding. But don't try this on a working machine; it could shift it so SheepShaver no longer runs!)
Q. Why does it work when SheepShaver is compiled for running under X, rather than SDL?
A. The problem is that SDL is a Cocoa program. The way it works is there is an SDL starter program that performs all the Cocoa initialization and setup, then it turns control over to the main SheepShaver program.
At the very start, there is nothing much loaded in memory at all. As soon as it does the Cocoa init, that's when all the libraries, fonts, and the IOKit areas get allocated. By the time the SheepShaver code tries to allocate, it is too late.
When you run under X, there is no SDL cocoa wrapper before the SheepShaver main program.
Q. Is there a simple fix?
A. We could simply change the RAM load point, such as to 0x30000000. But that would decrease the maximum RAM size for the emulated machine. At 0x30000000, it can only be 256 MB.
Q. How did you fix it?
A. I changed it so that the RAM block is no longer acquired at a fixed address; it will be allocated at any address that has enough contiguous free memory.
This works because even though on a real classic Mac the RAM starts at zero, it can actually start at other addresses. The Mac defines the bottom of memory as "starting where the RAM is".
Q. But the SheepShaver code will error if the RAM is higher than the ROM. Why does it do that?
A. Apparently on a real classic Mac, the start of the ROM has to be at a higher address than the start of the RAM. Otherwise it crashes.
Also, the ROM address has to be aligned to a 1 MB boundary; it can't just be any address.
Q. So how did you get the ROM address to be higher than the RAM?
A. I changed it to allocate a contiguous block: RAM size + ROM size + 1 MB. I set the RAM to start at the beginning of this block, at the ROM starts after the RAM. The ROM is aligned to a 1 MB boundary.
Note: It only does this code when running in REAL mode. This code won't work in DIRECT mode, due to the way SheepShaver's configuration files are set up.
Q. So how much memory can we give the emulated machine now? More than 512 MB? 1 GB? 1.5 GB (the maximum memory of a PowerMac 9600)?
A. Yes, the 512 MB limit is lifted, but not necessarily all the way to 1 GB.
If your machine didn't have the "Cannot map RAM" problem before, then you should be able to go to 1024 MB. Otherwise you probably can't go all the way. On my machine I can give it 929 MB, but anything higher crashes.
The problem is that with a larger allocation, it can't find it down in the 0x20000000 range, so it allocates it high (above 0xa0000000). This causes a crash; probably because the RAM/ROM is higher than one of the other allocations (globals, sig stack, kernel, etc.). Or maybe it is just that the classic Mac OS code doesn't expect it to be that high. Or maybe there is a bug in SheepShaver.
Update: It will now write an error if the RAM/ROM area loads too high to be usable, and suggest lowering the requested RAM amount.
Q. Is your solution the "right" solution? Or is there some other way we could fix it? How can we get 1 GB of memory on the emulated machine, running on an OS X host?
A. I don't know. See the disclaimer above; this is not my normal type of programming.
The way I see it, the alternatives are:
- Do the simple fix where we just change the RAM block to start at a higher address.
The disadvantage is that it reduces the memory limit, and will probably fail in the future.
- Do the SheepShaver memory allocations before the SDL Cocoa initialization.
The problem here is that the SDL code comes from the Basilisk II source, not SheepShaver. And Basilisk II is not modifying the SDL main program at all; it is identical to what you download from SDL.
- Run in DIRECT mode rather than REAL mode, and change the program so it can have different memory offsets for RAM and ROM.
This is non-trivial, and may not even work. SheepShaver may be depending on the fact that the distance between memory addresses in the host is always the same as the distance between the emulated machine addresses.
- Include the other data areas in the same contiguous block, so they are allocated higher than the ROM.
Don't know if this would work.
- Change to run in 64-bit mode.
I suspect this is very non-trivial, especially since the emulated machine still has to see addresses in the 32-bit address space.
Q. Are there any known drawbacks to your fix?
A. One is that now it is trying to allocate one larger block (which apparently has to come from below 0x60000000), rather than two smaller blocks (in fixed positions).
It is conceivable that there is some scenario where it worked before and fails now, but that would involve a very strange memory map.
Q. What types of hosts are affected by your change? How many programs did you change?
A. SheepShaver's code had the ROM area at a hard-coded address across many programs. This had to change, so the code changes are in the base SheepShaver code, which affects everything.
In theory the only real impact should be to Unix and Mac OS platforms. In other platforms (such as Windows), it should be doing exactly what it was doing before.
I changed 14 programs.