StrikerX3 / OpenXBOX

An experimental (Original) Xbox emulator
79 stars 6 forks source link

Rework the Memory Manager #8

Closed StrikerX3 closed 6 years ago

StrikerX3 commented 6 years ago

The current implementation is mostly a placeholder that gets the job done. The memory manager in the kernel does much more work.

The goal is to implement all Mm functions (including any internal Mi functions and data structures), and go further and implement all Nt functions related to virtual memory and Ex functions related to pools.

Note that in the real kernel, the addresses produced by the internal memory allocation functions are in the virtual memory range reserved for the kernel -- 0x80000000 and above. These addresses map 1:1 to the physical memory area at 0x00000000, but we still need to map addresses to pointers and back again safely.

StrikerX3 commented 6 years ago

Mapping virtual addresses into physical addresses is easily done with MmGetPhysicalAddress. On the other hand, converting physical addresses into virtual addresses is not cheap. So, in order to avoid performance penalties, all kernel emulation code will have to use guest addresses and convert them into pointers when needed. However, using pointers has caveats as explained below.

With the above function, we could make pointers into physical memory which we can use to manipulate structs, but if the data happens to cross the boundary between two virtual pages that map to non-contiguous physical addresses, writing data via the pointer will corrupt memory. This is a problem I'm trying to solve in an elegant manner and so far there have been three failed attempts.

It would be great if C++ offered a way to create a custom pointer with the same pointer syntax we're familiarized with while letting us do custom memory addressing. Unfortunately, that is not possible. The best we can do as far as my limited knowledge of C++ goes is to create a template class that stores the virtual address and overloads the pointer and arrow operators to provide direct access to the underlying data. In fact, this was my first attempt. I gave up on it when I realized pointers would have the issues mentioned above.

My second attempt involved pointers-to-members. It looked ugly as sin and suffered from the same problem with pointers, not to mention the verbose syntax for accessing deeply nested struct members and array indices.

My third attempt (in which I was aware of the pointer problems) involved a slightly modified version of the first design, where it would provide a pointer to either the actual data in the memory array if it would fit in a single physical page or a contiguous set of pages, or a pointer to a copy of the data which would have to be manually copied back into or refreshed from the guest memory. It provided methods that helped with those two operations, which was quite annoying to deal with since you had to manually invoke the methods after every change that had to be visible. I stopped working on it when it became too cumbersome in some cases (especially when dealing with LIST_ENTRY). Perhaps this could be partially automated by using copy/move constructors, destructors and assignment operators? I will explore this further.

StrikerX3 commented 6 years ago

Commit 91e1c522 implements all Mm* functions exported on a retail Xbox kernel, along with all required internal functions and variables. The game executable image is being loaded on top of the kernel image, which obviously causes problems during emulation. The XBE should be loaded in virtual memory, which means the actual physical pages it uses don't overlap with the existing kernel image.

Next up are the Nt functions related to virtual memory management followed by a refactor of the XBE loader, which will likely include the XeLoadSection and XeUnloadSection functions. The Ex pool management functions will be converted after that. File system cache functions might be added to the list too.

If the pointer issues mentioned above crop up during this work, I'll have to figure out a definitive solution. Issue #9 was created for this.

StrikerX3 commented 6 years ago

Virtual memory and pool functions are now implemented but not tested.

StrikerX3 commented 6 years ago

There are some issues with PDE/PTE pointer mapping that are preventing progress. More specifically, ToPointer is having trouble mapping addresses in the ranges 0xC0000000 - 0xC03FFFFF (PTE and PDE addresses) and 0xD0000000 - 0xEFFFFFFF (system PTEs). The current implementation is clearly incorrect, as the MiGetPteAddressPtr macro is returning an address into the PFN database instead of a PTE page.

StrikerX3 commented 6 years ago

I managed to fix these issues, but it seems that the Cpu::MemRead and Cpu::MemWrite methods are not mapping virtual addresses correctly, causing the emulation to crash.

StrikerX3 commented 6 years ago

A few more issues were resolved in the latest commit. I need to figure out how to make the Unicorn CPU emulator actually use the PDE/PTE tables when executing code.

UPDATE: It seems that there is a bug with Unicorn. I opened an issue on their project. In the meantime, I'll look into other CPU emulators/hypervisors (#10) or a kernel rewrite (#11) for a dynamic recompiler.

UPDATE 2: HAXM executes code in virtual memory correctly, as expected. This means that the virtual memory management functions implemented as part of this issue are working. But before I close this issue, I'll fix the issues that cropped up with HAXM until we get to the point we were before with Unicorn.

StrikerX3 commented 6 years ago

After fixing the major roadblocks with the HAXM CPU plugin, the emulator managed to run a Microsoft XDK software up to the point where we halted before the memory manager rework. Of course, more testing is needed and new issues may crop up later, but I consider this a success and the rework is confirmed to be working so far, therefore I say it is complete. File system cache will be done later, when we actually read files.