cebix / macemu

Basilisk II and SheepShaver Macintosh emulators
1.38k stars 289 forks source link

BasiliskII uses a dangerously fragile method to allocate the framebuffer #171

Closed beholdnec closed 6 years ago

beholdnec commented 6 years ago

The method that BasiliskII uses to allocate the framebuffer in video_sdl.cpp is fragile, and easily leads to breakage on 64-bit builds.

The framebuffer is allocated in the function vm_allocate_framebuffer. This is fine. The problem arises when the framebuffer's address is passed to the emulated Macintosh. Basically, to tell the Mac the address of the framebuffer, it is given the 32-bit difference between the address of the framebuffer in host RAM and the address of the Mac's main memory in host RAM.

This only works correctly if the framebuffer is located within 4 gigs (32-bit max.) of main memory. This is always true on a 32-bit build. However, in a 64-bit build, it is entirely possible that the framebuffer will be allocated more than 4 gigs away from main memory and things will break. On Windows, there is no way to prevent this.

A better method is to allocate main memory, ROM, the framebuffer, and any other Mac-accessible buffers all in one contiguous chunk.

rickyzhang82 commented 6 years ago

There are three different memory addressing in BII. See the wiki I wrote.

I didn't experience the problem you described in 64 bit Intel Mac OS X and 64 bit Intel Fedora. For sure, I'm not a Windows guy. Please be specific what's your concern and what addressing mode you use in Windows.

beholdnec commented 6 years ago

BasiliskII always crashes on my 64-bit Windows build. The framebuffer gets allocated far out of range of the Mac address space.

The problem is that Windows doesn't have an equivalent of mmap's MAP_32BIT flag which BasiliskII uses on Unix and Mac to allocate the framebuffer.

rickyzhang82 commented 6 years ago

Reading the Microsoft doc, user space can be mapped from 0 up to 8TB.

Each process has its own virtual address space that goes from 0x000'0000000 through 0x7FF'FFFFFFFF

Your comment in the first post is incorrect.

I'd advise you:

  1. Figure out what addressing mode of BII in your Windows.
  2. See how frame buffer is allocated. It is more likely that the address mapping has bug. Thus, it has access violation.
beholdnec commented 6 years ago

Ah, I was compiling with DIRECT_ADDRESSING enabled, but I probably should have been using virtual addressing.

I will close this issue.

If anyone is interested in another approach, I have implemented "arena addressing" in my fork at https://github.com/beholdnec/macemu . This works by allocating a contiguous 4G address space and carving out blocks for RAM, ROM and the framebuffer. It is experimental and only works on Windows.

rickyzhang82 commented 6 years ago

Instead of hacking, you can add your logic into vm_acquire_framebuffer

https://github.com/beholdnec/macemu/commit/7e1e8ee225b3e5eb15bd94987778a3f194419c44#diff-17c3cebf9cddab61a386bb1d7d5b8e09L683

-       the_buffer = (uint8 *)vm_acquire_framebuffer(the_buffer_size);
 +      //the_buffer = (uint8 *)vm_acquire_framebuffer(the_buffer_size);
 +        the_buffer = (uint8 *)fb;

I don't investigate what change you made. Could you elaborate 'arena addressing'? It doesn't seem to introduce a new addressing at all at the first sight.

beholdnec commented 6 years ago

Sure. Perhaps "arena addressing" isn't the right word.

Most of the logic is in main_windows.cpp .

To begin with, I reserve a contiguous block of 0xFFFFFFFF+1 bytes. Remember, this is virtual address space, so no physical memory is actually being allocated. This block represents the entire addressable space of the Macintosh. I call this block the "arena".

Then, I create pages within the arena to hold RAM, ROM, scratch space, and the framebuffer.

The emulator uses this arena for memory accesses. It's as simple as that. One major advantage is that there is no way for software running in the emulator to corrupt the host by writing memory outside its range.

Ordinarily, the framebuffer is allocated in video_sdl.cpp. I changed it so the framebuffer is created in main_windows.cpp and passed to VideoInit.

rickyzhang82 commented 6 years ago

Your approach is simpler than the old way. But keep in mind that in 32bit Windows time, you can't allocate the whole 4GB from 0 up to 0xFFFFFFFF. That's what BII inherits from.

Again, as I commented before, I believe there is a bug in address mapping. If you want, spend some time to figure this out. I'm pretty sure you can learn more than work around it.

beholdnec commented 6 years ago

Building with virtual addressing enabled fails because the function get_virtual_address is not defined. A comment in uae_cpu/memory.h says:

/* gb-- deliberately not implemented since it shall not be used... */
extern uae_u32 get_virtual_address(uae_u8 *addr);

However, get_virtual_address is still used. I'm not sure how to proceed.

rickyzhang82 commented 6 years ago

I found the allocation part for Windows:

src/CrossPlatform/vm_alloc.cpp

0263 #elif defined(HAVE_WIN32_VM)
0264     int alloc_type = MEM_RESERVE | MEM_COMMIT;
0265     if (options & VM_MAP_WRITE_WATCH)
0266       alloc_type |= MEM_WRITE_WATCH;
0267 
0268     if ((addr = VirtualAlloc(NULL, size, alloc_type, PAGE_EXECUTE_READWRITE)) == NULL)
0269         return VM_MAP_FAILED;
0270 #else

According to M$ doc,

C++

LPVOID WINAPI VirtualAlloc(
  _In_opt_ LPVOID lpAddress,
  _In_     SIZE_T dwSize,
  _In_     DWORD  flAllocationType,
  _In_     DWORD  flProtect
);

Parameters

lpAddress [in, optional]
The starting address of the region to allocate. If the memory is being reserved, the specified address is rounded down to the nearest multiple of the allocation granularity. If the memory is already reserved and is being committed, the address is rounded down to the next page boundary. To determine the size of a page and the allocation granularity on the host computer, use the GetSystemInfo function. If this parameter is NULL, the system determines where to allocate the region.
If this address is within an enclave that you have not initialized by calling InitializeEnclave, VirtualAlloc allocates a page of zeros for the enclave at that address. The page must be previously uncommitted, and will not be measured with the EEXTEND instruction of the Intel Software Guard Extensions programming model.

Because it is NULL in the 1st parm, the starting address in host OS is determined by Windows OS. In theory, you can't/shouldn't use real addressing mapping. But you can use direct addressing.

Direct addressing is to apply a fixed offset between host OS and guest OS, while real addressing is a one to one mapping.

In any case, the address mapping between host OS and guest OS should be follow the wiki I wrote. I haven't documented frame buffer part yet. I remembered it must be in very strictly location in guest OS.

If you are interested in it, you can post more information so that we can discuss it here. Last time, we fix 24 bit ROM for black and white frame buffer. It is fun.