Copy Link
Add to Bookmark
Report

Bootstrap Idea for Linux

xbox's profile picture
Published in 
xbox
 · 20 Feb 2024

written by anonymous, 28 May 2002

Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved. Copyright (C) 2002 anonymous. (Thanks to the Free Software Foundation for that legalese.)

Windows NT and Xbox are registered trademarks of Microsoft Corporation. No affliation between the author and Microsoft Corporation and the author exists or is implied.

This document is an idea of how to get Linux started on the Xbox after the security measures have been broken. Think of it as a design specification for Phase 2 of the project, "Write Boot Loader". By no means is this intended to be the specification. It is simply a draft idea to give some thought to the problem of switching from a Windows NT environment to Linux without crashing the Xbox.

No ideas of how to get custom code running on the Xbox are listed in here. Nothing in this document matters until custom code is running - it assumes that that problem has already been solved.

These are the memory-management services provided to us by the Windows NT kernel on the Xbox. They are similar, but not identical, to the kernel-mode (device driver) support routines in the standard NT kernel, which are mostly documented in the DDK (Device Driver Kit). It's important to know these to make the switch from NT to Linux easier.

The prototypes listed below were derived from reverse engineering. It may be wrong.

Type definitions in Windows NT:

  • Pxxx = xxx *
  • VOID = void
  • NTAPI = __stdcall
  • NTSYSAPI = __declspec(dllimport)
  • ULONG = unsigned long (x86 NT/xbox has 32 bits)
  • ULONG_PTR = an unsigned integer at least as large as the larger of "void *" and "unsigned long" (x86 NT/xbox has 32 bits)
  • PHYSICAL_ADDRESS = an integer the size of the motherboard's address space (64 bits on PC NT to handle motherboards that address 2^36 bytes; 32 bits on Xbox since that's not needed)
  • IN = nothing (documentation marking); denotes an input parameter
  • OUT = nothing (documentation marking); denotes an output parameter
  • OPTIONAL = nothing (documentation marking); denotes an optional parameter (typically, 0 or NULL means you don't want to use it)
  • EXPORTNUM() = nothing (documentation marking); kernel export number

NTSYSAPI 
EXPORTNUM(166)
PVOID
NTAPI
MmAllocateContiguousMemoryEx(
IN ULONG NumberOfBytes,
IN PHYSICAL_ADDRESS LowestAcceptableAddress,
IN PHYSICAL_ADDRESS HighestAcceptableAddress,
IN ULONG Alignment OPTIONAL,
IN ULONG ProtectionType
);

Allocates a block of contiguous physical memory whose physical address is between LowestAcceptableAddress and HighestAcceptableAddress. The physical memory will be aligned to a 2^Alignment byte boundary. If that succeeds, a block of contiguous virtual address space is allocated that points to this physical memory. The page protection is set to the value specified in ProtectionType for this virtual address range. The return value is a pointer to the virtual address range allocated.

Note that any Alignment value less than 12 will be the same as 12 because of the page granularity of the x86. I am only guessing what this parameter is. I usually see 0 for it, which means align to 2^12.

The Ex name was guessed. I'm assuming that it's Ex simply because there exists another exported function that takes less parameters and simply calls this one with defaults for the missing parameters.

NTSYSAPI 
EXPORTNUM(173)
PHYSICAL_ADDRESS
NTAPI
MmGetPhysicalAddress(
IN PVOID BaseAddress
);

Returns the physical address to which the given virtual address corresponds. Certainly much easier than reading CR3 and traversing the page table. This function is identical to the standard NT MmGetPhysicalAddress except for the size of the PHYSICAL_ADDRESS typedef.

NTSYSAPI 
EXPORTNUM(177)
PVOID
NTAPI
MmMapIoSpace(
IN PHYSICAL_ADDRESS PhysicalAddress,
IN ULONG NumberOfBytes,
IN ULONG ProtectionType
);

Allocates a virtual address range that maps to the given physical memory. This mapping is done with no checking as to whether the mapping you are doing is "valid". The page protection is specified by ProtectionType. Because of the lack of validity checking, this call can be used to create mirrors of memory or to access unallocated memory. The return value is a virtual pointer to the physical memory. Using most API calls with the pointer returned will crash the system, since this function doesn't follow normal allocation rules.

The only differences between this and the standard NT MmMapIoSpace are that PHYSICAL_ADDRESS is 32 bits and Win32 protection types (PAGE_READWRITE for instance) are used instead of the MEMORY_CACHING_TYPE of standard NT.

NtAllocateVirtualMemory:

This function is considerably more complicated than the others, so I will not describe it in detail. Basically, this function allocates memory in the user-mode sense, where physical pages are allocated from who knows where (swap files don't exist in the Xbox kernel). The physical pages can be scattered anywhere in memory. However, it allocates contiguous virtual addresses to point to this block of memory.

What's nice about this function is it lets you choose the virtual address that it allocates, as long as it's free. This will be important, as I will show below.

Those 4 functions provide us with what I think is everything we need. Here is my bootstrap idea.

  1. Allocate 4096 bytes of physical memory between 00000000 and 000FFFFF (within the first meg) using MmAllocateContiguousMemoryEx. 4096 is enough to fit everything we need here. This memory shall be known as "boot loader 1". Keep this virtual address. Use MmGetPhysicalAddress to find the physical address allocated and store it.
  2. Allocate 4096 of standard memory using NtAllocateVirtualMemory. However, we do a little trick here. We can't control the physical address it uses, but we can control the virtual address. Choose the virtual address of this second 4096 such that the virtual address matches the physical address of boot loader 1. This is important for exiting protected mode. This second 4096 bytes of memory shall be known as "boot loader 2".
  3. Use NtAllocateVirtualMemory and NtReadFile to load the kernel and the kernel bootstrap from CD into RAM. The physical memory will not need to be contiguous. This memory shall be known as the "kernel block".
  4. Allocate (ceiling(sizeof(kernel block) / 4096)) * 4 bytes of contiguous physical memory with no limits on address (it doesn't need to be in the first meg). Use MmGetPhysicalAddress to retrieve the physical address. This memory shall be known as the "map table".
  5. For each 4096 byte page of the kernel block, call MmGetPhysicalAddress. This is the physical address of 1 page of the kernel. Store the address of each page in the "map table". Thus, this becomes a list of physical addresses at which we can find the kernel block.
  6. Copy our boot loader code to both boot loader 1 and boot loader 2. Jump to it within boot loader 2. The fact that both places have the exact same thing matters.
  7. Disable interrupts. Disable caching in CR0. Flush the cache using wbinvd. Use lgdt to load a temporary GDT. Create a 286 code selector (or rather, have one precreated in the new GDT) whose base is the virtual address of boot loader 2 and whose limit is 0000FFFF. Create a similar data selector. Jump far to this new CS:IP somewhere, which will switch the instruction set to 16 bits. We need that for our exit to real mode.
  8. Disable paging in CR0, then load CR3 with 00000000. This is why we made 2 copies of the boot loader. Whether the CPU uses the virtual address (with paging on) or the physical address (with paging off), it will read what seems like the same memory. Thus, with cache off, we do not have to worry about the sudden disabling of paging causing the instruction equivalent of screen shearing.
  9. Exit protected mode and jump to the real-mode CS:IP within boot loader 1. Since it was allocated within the first megabyte, this will work.
  10. Set up the GDT for our reentrance into protected mode, then enter protected mode. Do a far jump to switch back to 32 bit code. Most likely, you'll want to leave paging off for now and give CS a 4 gig limit. Enable caching here.
  11. "Defragment" the kernel. We stored the physical address of the map table, so we can go get it. Use this table to defragment the kernel's data into its final place in physical memory. It might be scattered to hell, and even overlap with its final destination - that's why we need to defragment.
  12. Create a page table that has the proper virtual address of the kernel mapped to its physical position and load CR3. If the kernel needs to know the size of RAM, check that now (we might as well support the 128 megs that a debug box has).
  13. Jump to the kernel.

I'm thinking that maybe this could be improved upon by never exiting protected mode. To do this, however, you would have to work carefully with the GDT, since that is somewhere in NT memory and will be deleted in the process of loading Linux.

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT