x86_64 kernel page table misalignment
Posted: Mon Sep 06, 2010 4:04 pm
I get the feeling I'm missing something blatantly obvious here. The problem I would appreciate help with is that when I specify an ALIGN 0x1000 directive in front of a page table declaration in NASM, the address I end up with isn't aligned. It's always precisely 0x30 bytes too high an address. This crops up every time, and looking in the linker map confirms that the PML4 base is at the address I see in Bochs debugger. For these reasons, I suspect that my linker script is at fault; however, comparing it to the one used by Xomb and the one provided as a base in Creating a 64-bit kernel doesn't appear to yield any obvious faults. All three of them seem to do so same thing, with the only practical difference being that my linker script shuffles around with sections, not files.
I've cut out a great deal of my code which performs the checks for long mode, and ended up with a set of 3 files which I've included below. As you can probably tell, I create a new section at the 1 MiB mark which contains the long mode and kernel bootstrap; everything else gets linked at 0xFFFF800000100000, which my code will probably jump to (assuming I've not messed up with the paging structures; it's a little difficult for me to verify in Bochs at the moment)
LinkScript.ld:
LongMode.s:
Multitasking/Paging.s:
In case it helps, I compile my assembly using 'nasm -f elf64 -I ./Source/' and link using 'x86_64-elf-ld -T "LinkScript.ld" -Map "LinkerMap.txt" -o kernel.elf $(OBJ_FILES)'.
I've cut out a great deal of my code which performs the checks for long mode, and ended up with a set of 3 files which I've included below. As you can probably tell, I create a new section at the 1 MiB mark which contains the long mode and kernel bootstrap; everything else gets linked at 0xFFFF800000100000, which my code will probably jump to (assuming I've not messed up with the paging structures; it's a little difficult for me to verify in Bochs at the moment)
LinkScript.ld:
Code: Select all
OUTPUT_FORMAT(elf64-x86-64)
ENTRY(asmEntry)
LINEAR_ADDRESS = 0x100000;
VIRTUAL_ADDRESS = 0xFFFF800000000000;
SECTIONS
{
. = LINEAR_ADDRESS;
textStart = .;
.pmode ALIGN(0x1000) :
{
*(.multiboot*)
*(.pmode.gdt*)
*(.pmode.text*)
}
. += VIRTUAL_ADDRESS;
.text ALIGN(0x1000) : AT(ADDR(.text) - VIRTUAL_ADDRESS)
{
*(.text)
*(.rodata*)
}
textEnd = .;
dataStart = .;
.data ALIGN(0x1000) : AT(ADDR(.data) - VIRTUAL_ADDRESS)
{
constructorStart = .;
*(.ctor*)
constructorEnd = .;
*(.data)
}
dataEnd = .;
bssStart = .;
.bss ALIGN(0x1000) : AT(ADDR(.bss) - VIRTUAL_ADDRESS)
{
startBss = .;
*(COMMON)
*(.bss)
endBss = .;
}
bssEnd = .;
/DISCARD/ :
{
*(.eh_frame)
}
end = .;
}
Code: Select all
GLOBAL asmEntry
EXTERN textStart
EXTERN bssStart
EXTERN bssEnd
EXTERN HigherHalfEntry
%include "Multitasking/Paging.s"
; Basic GrUB information
ModuleAlign equ 1
MemoryMap equ 2
AOUTKludge equ (1 << 16)
Flags equ ModuleAlign | MemoryMap | AOUTKludge
Magic equ 0x1BADB002
Checksum equ -(Magic + Flags)
[BITS 32]
section .multiboot
align 0x4
MultibootHeader:
dd Magic
dd Flags
dd Checksum
dd (MultibootHeader) ; header_addr
dd (textStart) ; load_addr
dd (bssStart - VirtualAddressBase) ; load_end_addr
dd (bssEnd - VirtualAddressBase) ; bss_end_addr
dd (asmEntry) ; entry_addr
section .pmode.gdt
align 0x8
gdt:
dq 0x0000000000000000 ; Standard NULL entry
dq 0x00AF9A000000FFFF ; 64-bit kernel code
dq 0x00AF93000000FFFF ; 64-bit kernel data
gdtEnd:
align 0x8
gdtPtr:
dw gdtEnd - gdt - 1
dq gdt
section .pmode.text
; GrUB will jump here, so it needs to be 32-bit code
asmEntry:
; First, make certain that an interrupt won't arrive and mess up the boot code
cli
mov ecx, cr0
and ecx, 0x7FFFFFFF
mov cr0, ecx
; Enable bit 5 (PAE) in CR4
mov ecx, cr4
bts ecx, 5
mov cr4, ecx
; Switch on long mode in the necessary MSR
mov ecx, 0xC0000080
rdmsr
bts eax, 8
wrmsr
xchg bx, bx
mov ecx, (pml4Base - VirtualAddressBase) ; Load ECX with the physical address of the boot page directory
mov cr3, ecx
mov ecx, cr0
bts ecx, 31
mov cr0, ecx
lgdt [gdtPtr]
; After this jump, the CPU is in long mode
jmp 0x8:(HigherHalfEntry - VirtualAddressBase)
Code: Select all
%ifndef Paging_S
%define Paging_S
; This file just contains the basic structures necessary to get the higher half operating
; It also stores the first page directory entries (including recursive page mapping)
VirtualAddressBase equ 0xFFFF800000000000
VirtualAddressBasePML4 equ (VirtualAddressBase >> 40) & 0x1FF
VirtualAddressBasePDPT equ (VirtualAddressBase >> 31) & 0x1FF
VirtualAddressBasePageDirectory equ (VirtualAddressBase >> 22) & 0x1FF
VirtualAddressBasePageTable equ (VirtualAddressBase >> 13) & 0x1FF
section .data
; All this just creates a simple mapping for the bootstrap code
; pageTable gets reused so that it can create another mapping later
align 0x1000
; Using this page table allows me to map any virtual address to 0
; 512 entries in the page table, resulting in each page table representing 2 MiB
pageTable:
%assign i 0
%rep 512
dq i | 11b
%assign i i+0x1000
%endrep
align 0x1000
pageDirectory:
dq pageTable - VirtualAddressBase + 11b
%rep 511
dq 0
%endrep
; 512 entries in the PDPT
align 0x1000
pdpt:
dq pageDirectory - VirtualAddressBase + 11b
%assign i 0
%rep 511
dq 0
%endrep
align 0x1000
higherHalfPageDirectory:
times (VirtualAddressBasePageTable) dq 0
dq (pageTable - VirtualAddressBase + 11b)
times (512 - VirtualAddressBasePageTable - 1) dq 0
; 512 entries in the PDPT
align 0x1000
higherHalfPDPT:
times (VirtualAddressBasePageDirectory) dq 0
dq (higherHalfPageDirectory - VirtualAddressBase + 11b)
times (512 - VirtualAddressBasePageDirectory) dq 0
; There are 512 entries in the PML4
align 0x1000
pml4Base:
dq pdpt - VirtualAddressBase + 11b
times 254 dq 0
dq (higherHalfPDPT - VirtualAddressBase + 11b)
times 255 dq 0
dq pdpt - VirtualAddressBase + 11b
%endif