Setting Up Long Mode: Difference between revisions

[unchecked revision][unchecked revision]
Content deleted Content added
Orbital (talk | contribs)
Updated PML5 paragraph, it's 4 years since it started to be supported
m Bot: Replace deprecated source tag with syntaxhighlight
Line 57:
Now that CPUID is available we have to check whether long mode can be used or not. Long mode can only be detected using the extended functions of CPUID (> 0x80000000), so we have to check if the function that determines whether long mode is available or not is actually available:
 
<sourcesyntaxhighlight lang="asm">
mov eax, 0x80000000 ; Set the A-register to 0x80000000.
cpuid ; CPU identification.
cmp eax, 0x80000001 ; Compare the A-register with 0x80000001.
jb .NoLongMode ; It is less, there is no long mode.
</syntaxhighlight>
</source>
 
Now that we know that extended function is available we can use it to detect long mode:
Line 85:
Before we actually cover up the new paging used in x86-64 we should disable the old paging first (you can skip this if you never set up paging in protected mode) as this is required.
 
<sourcesyntaxhighlight lang="asm">
mov eax, cr0 ; Set the A-register to control register 0.
and eax, 01111111111111111111111111111111b ; Clear the PG-bit, which is bit 31.
mov cr0, eax ; Set control register 0 to the A-register.
</syntaxhighlight>
</source>
 
Now that paging is disabled, we can actually take a look at how paging is set up in x86-64 (It's recommended to read Chapter 5.3 of the AMD64 Architecture Programmer's Manual, Volume 2). First of all, long mode uses PAE paging and therefore you have the page-directory pointer table (PDPT), the page-directory table (PDT) and the page table (PT). There's also another table which now forms the root (instead of the PDPT or the PDT) and that is page-map level-4 table (PML4T). In protected mode a page table entry was only four bytes long, so you had 1024 entries per table. In long mode, however, you only have 512 entries per table as each entry is eight bytes long. This means that one entry in a PT can address 4kB, one entry in a PDT can address 2MB, one entry in a PDPT can address 1GB and one entry in a PML4T can address 512GB. This means that only 256TB can be addressed. The way these tables work is that each entry in the PML4T point to a PDPT, each entry in a PDPT to a PDT and each entry in a PDT to a PT. Each entry in a PT then points to the physical address, that is, if it is marked as present. So how does the MMU/processor know which physical address should be used with which virtual address? Basically each table has 512 entries ranging from 0 to 511. These entries each refer to a specific domain of memory. Like index 0 of the PML4T refers to the first 512GB in virtual memory, index 1 refers to the next 512GB and so on. The same applies to the PDPT, PDT and the PT (except with smaller sizes; 1GB, 2MB and 4kB as seen above). The last gigabyte of virtual memory would be described in the table referred to by 511th index of the PDPT which is referred to by the 511th index of the PML4T or in psuedo-C:
 
<sourcesyntaxhighlight lang="c">
pagedir_t* PDT = PML4[511]->PDPT[511];
</syntaxhighlight>
</source>
 
Basically, what pages you want to set up and how you want them to be set up is up to you, but I'd identity map the first megabyte and then map some high memory to the memory after the first megabyte, however, this may be pretty difficult to set up first. So let's identity map the first two megabytes. We'll set up four tables at 0x1000 (assuming that this is free to use): a PML4T, a PDPT, a PDT and a PT. Basically we want to identity map the first two megabytes so:
Line 105:
 
First we will clear the tables:
<sourcesyntaxhighlight lang="asm">
mov edi, 0x1000 ; Set the destination index to 0x1000.
mov cr3, edi ; Set control register 3 to the destination index.
Line 112:
rep stosd ; Clear the memory.
mov edi, cr3 ; Set the destination index to control register 3.
</syntaxhighlight>
</source>
 
Now that the page are clear we're going to set up the tables, the page tables are going to be located at these addresses:
Line 122:
 
So lets make PML4T[0] point to the PDPT and so on:
<sourcesyntaxhighlight lang="asm">
mov DWORD [edi], 0x2003 ; Set the uint32_t at the destination index to 0x2003.
add edi, 0x1000 ; Add 0x1000 to the destination index.
Line 129:
mov DWORD [edi], 0x4003 ; Set the uint32_t at the destination index to 0x4003.
add edi, 0x1000 ; Add 0x1000 to the destination index.
</syntaxhighlight>
</source>
 
If you haven't noticed already, I used a three. This simply means that the first two bits should be set. These bits indicate that the page is present and that it is readable as well as writable. Now all that's left to do is identity map the first two megabytes:
 
<sourcesyntaxhighlight lang="asm">
mov ebx, 0x00000003 ; Set the B-register to 0x00000003.
mov ecx, 512 ; Set the C-register to 512.
Line 142:
add edi, 8 ; Add eight to the destination index.
loop .SetEntry ; Set the next entry.
</syntaxhighlight>
</source>
 
Now we should enable PAE-paging by setting the PAE-bit in the fourth control register:
Line 257:
 
Notice that we set a 4gb limit for code. This is needed because the processor will make a last limit check before the jump, and having a limit of 0 will cause a #GP (tested in bochs). After that, the limit will be ignored. Now the only thing left to do is load it and make the jump to 64-bit:
<sourcesyntaxhighlight lang="asm">
lgdt [GDT64.Pointer] ; Load the 64-bit global descriptor table.
jmp GDT64.Code:Realm64 ; Set the code segment and enter 64-bit long mode.
</syntaxhighlight>
</source>
 
=== Sample ===
Line 266:
Now that we're in 64-bit, we want to do something that is actually 64-bit. In this sample I'm just going with an ordinary clear the screen:
 
<sourcesyntaxhighlight lang="asm">
; Use 64-bit.
[BITS 64]
Line 283:
rep stosq ; Clear the screen.
hlt ; Halt the processor.
</syntaxhighlight>
</source>
 
It is very important that you don't enable the interrupts (unless you have set up a 64-bit IDT of course).