Page 1 of 1

Loading a higher half long mode kernel help

Posted: Fri Mar 26, 2010 12:20 pm
by PinkyNoBrain
Hello, i have ben away from OS development for a while and have decided to come back and write a 64bit long mode kernel(just for fun :-) ). I have read the AMD systems programmers manual and a few other sources and have just found the http://wiki.osdev.org/Creating_a_64-bit_kernel tutorial as well (which reasuringly suggested the plan i had been formulating for loading my kernel). Unfortuantly i am still a little unclear as to how to go about some of the technical details involved in initialising longmode and getting my code running in the highre half so am turning to the good people at this site for advice before spending hours running down blind alleys. My current plan is as follows(but if there is an easyer way to get an elf64 kernel running in the higher half pls let me know):

Ultimately i want a 64bit kernel running in the higher half of its VAS. To achieve this goal my plan is to use GRUB Legacy as my initial boot loader(since im already familiar with it), GRUB will load my own 32bit loader as a kernel and load my 64bit kernel as a module(along with other modules for drivers and the init process) . My 32 bit loader will do the initial prep work for long mode (Make a GDT,IDT and the initial long mode page table structures), process the boot modules by unpacking them into physical memory and mapping them appropriatly into the 64bit vas, Enable long mode by enabeling pageing and then activating long mode by doing a far jump to the 64bit code segment with the entry point of my 64 bit kernel (passing it the addresses of some systems information datastrucuters in registers).

I have almost got the above process working with two 32bit kernels and a 4GB address space and im now planning on making the necesary changes to maek it load the 64 bit kernel. The first thing i dont understand however is once ive doneall the long mode enabling stuff with the page tabels how i actually jump into my 64bit kernel? I know that to actually activate long mode i must do a far jump to a 64bit code segment but since the jump is being executed in protected mode i only have a 32bit offset and so cannot use the offset of my kernel in the higher half. Also since segment based addressing is ignored i cant do any tricks with the base address to overcome this problem. So is the only way to do this to include a tiny 64bit code stub in my 32bit loader that repeats the jump with a 64bit address or am i missing a trick?

Thanks for your time and apologies since this may be the first in a series of questions while i get my head round this :-(

Pinky

Re: Loading a higher half long mode kernel help

Posted: Sat Mar 27, 2010 3:01 am
by xenos
There is a nice tutorial on setting up and entering long mode in the wiki:

http://wiki.osdev.org/User:Stephanvansc ... _Long_Mode

Re: Loading a higher half long mode kernel help

Posted: Sat Mar 27, 2010 7:53 am
by PinkyNoBrain
Thanks XenOS, it is a very nice tutorial and it was helpfull when i was writing the long mode datastructures and doing the CPU detection. Unfortunatly it isnt doing a higher half kernel and so it doesnt hit the same problem i have.

Where it says:
; Set the code segment and enter 64-bit long mode.
jmp GDT64.Code:Realm64
I dont think that will work for me because this instruction is executed in 32-bit compatability mode and so Realm64 is a 32bit offset and so cant adress the entry point of my kernel in the upper half of its 64Bit VAS. (It makes no difference to him however because his kernel is in the bottom 4GB of the 64bit VAS). The only way i see of overcoming this is to include an intermediary bit of 64bit code that is loaded bellow 4GB and simply repeats the jump but with a 64bit offset. My question was about whether this is the normal solution or if i am missing a trick?

Re: Loading a higher half long mode kernel help

Posted: Sat Mar 27, 2010 9:36 am
by Owen
My 64-bit kernel entry code does this:
  • Load the kernel's GDT using a 32-bit GDTR
  • Stash away the Multiboot parameters so we can get to them later
  • Check for long mode support
  • Enable PAE
  • Set LME to enter long mode
  • Enable paging
  • ljmp to the 64-bit code segment, to a function called KStub64 in the bottom half of the address space, which is a trampoline
  • Does an absolute jump to KEntry64, the 64-bit assembler stub in the upper half

    Code: Select all

    // AT&T syntax
        movabs $KEntry64, %rax
        jmp *%rax
    
  • KEntry64 then reloads the GDT using a 64-bit GDTR pointed at the higher half
  • Reloads the data segment (This is shared with 32-bit code - remember, the processor doesn't care in long mode)
  • Set the stack pointer
  • Call the C main function
Quite a bit of work. Yes, you need some code initially mapped in the bottom half; I map the bottom 2MB of address space in both halves initially using 2MB PAE pages. The actual kernel code unmaps the bottom half map when its initializing the VMM (And at that point does an INVLPG on every 4kb page which would have made it up - remember, the bottom 1MB of the address space is treated specially by the processor)

Re: Loading a higher half long mode kernel help

Posted: Sat Mar 27, 2010 12:21 pm
by xenos
I guess now I get the point. I also have a 64 bit higher half kernel and I use an identity mapped 64 bit stub. So the jump from 32 bit compatibility mode to 64 bit mode ends up at an address < 4 gig, so it can be done from 32 bit mode. The 64 bit stub finally calls the actual kernel entry point:

Code: Select all

.code32
	// enable paging (activate long mode), enable floating point exception
	movl %cr0, %ecx
	orl $0x8000000a, %ecx
	movl %ecx, %cr0

	// jump into long mode
	ljmp $0x20, $(long_mode - KERNEL_OFFSET)

.code64
long_mode:
	movabsq $(stack + STACK_SIZE), %rsp

	movabsq $(KernelEntry), %rax
	callq *%rax // Run the boot module by calling its C++ entry point

Re: Loading a higher half long mode kernel help

Posted: Sat Mar 27, 2010 1:30 pm
by StephanvanSchaik
PinkyNoBrain wrote:Thanks XenOS, it is a very nice tutorial and it was helpfull when i was writing the long mode datastructures and doing the CPU detection. Unfortunatly it isnt doing a higher half kernel and so it doesnt hit the same problem i have.

Where it says:
; Set the code segment and enter 64-bit long mode.
jmp GDT64.Code:Realm64
I dont think that will work for me because this instruction is executed in 32-bit compatability mode and so Realm64 is a 32bit offset and so cant adress the entry point of my kernel in the upper half of its 64Bit VAS. (It makes no difference to him however because his kernel is in the bottom 4GB of the 64bit VAS). The only way i see of overcoming this is to include an intermediary bit of 64bit code that is loaded bellow 4GB and simply repeats the jump but with a 64bit offset. My question was about whether this is the normal solution or if i am missing a trick?
Actually the article was based on my very own code with some differences here and there. One of them is that my boot loader actually loads the kernel to 1MB but maps it to 0xFFFFFFFFC0000000. I attached the code of my boot loader to the thread.

Code: Select all

[BITS 32]

ProtectedRealm:
    cli
    mov ax, GDT32.Data
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    cmp WORD [Kernel + 6], '64'
    je .LongMode
    jmp GDT32.Code:0xC0000000

[CPU X64]

.LongMode:
    mov eax, cr0
    and eax, 01111111111111111111111111111111b
    mov cr0, eax
    mov edi, DWORD [FreeMemory]
    add edi, 0x1000
    and edi, 11111111111111111111000000000000b
    mov cr3, edi
    xor eax, eax
    mov ecx, 7168
    rep stosd
    mov edi, cr3
    mov DWORD [edi], edi
    add DWORD [edi], 0x1000
    or DWORD [edi], 00000000000000000000000000000011b
    mov DWORD [edi + 511 * 8], edi
    add DWORD [edi + 511 * 8], 0x2000
    or DWORD [edi + 511 * 8], 00000000000000000000000000000011b
    push edi
    add edi, 0x1000
    mov DWORD [edi], edi
    add DWORD [edi], 0x2000
    or DWORD [edi], 00000000000000000000000000000011b
    add edi, 0x2000
    mov DWORD [edi], edi
    add DWORD [edi], 0x2000
    or DWORD [edi], 00000000000000000000000000000011b
    add edi, 0x2000
    mov ebx, 0x00000003
    mov ecx, 256
    
.SetEntryPT1:
    mov DWORD [edi], ebx
    add ebx, 0x1000
    add edi, 8
    loop .SetEntryPT1
    pop edi
    add edi, 0x2000
    mov DWORD [edi+511*8], edi
    add DWORD [edi+511*8], 0x2000
    or DWORD [edi+511*8], 00000000000000000000000000000011b
    add edi, 0x2000
    mov DWORD [edi], edi
    add DWORD [edi], 0x2000
    or DWORD [edi], 00000000000000000000000000000011b
    add edi, 0x2000
    mov ebx, 0x00100003
    mov ecx, 256
    
.SetEntryPT2:
    mov DWORD [edi], ebx
    add ebx, 0x1000
    add edi, 8
    loop .SetEntryPT2
    mov eax, cr4
    or eax, 00000000000000000000000000100000b
    mov cr4, eax
    mov ecx, 0xC0000080
    rdmsr
    or eax, 00000000000000000000000100000000b
    wrmsr
    mov eax, cr0
    or eax, 10000000000000000000000000000000b
    mov cr0, eax
    lgdt [GDT64.Pointer]
    jmp GDT64.Code:LongRealm

[BITS 64]

LongRealm:
    cli
    mov ax, GDT64.Data
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov rdi, 0xFFFFFFFFC0000000
    push rdi
    ret

Code: Select all

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; File: main.asm                                                             ;;
;; Author: Stephan J.R. van Schaik.                                           ;;
;; Description: the 64-bit Synkhronix kernel.                                 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

[ORG 0xFFFFFFFFC0000000]
[BITS 64]
[CPU X64]
[SECTION .text]

Main:
    cli
    ...

Regards,
Stephan J.R. van Schaik.

Re: Loading a higher half long mode kernel help

Posted: Sun Mar 28, 2010 6:51 am
by torshie
PinkyNoBrain wrote: I have almost got the above process working with two 32bit kernels and a 4GB address space and im now planning on making the necesary changes to maek it load the 64 bit kernel. The first thing i dont understand however is once ive doneall the long mode enabling stuff with the page tabels how i actually jump into my 64bit kernel? I know that to actually activate long mode i must do a far jump to a 64bit code segment but since the jump is being executed in protected mode i only have a 32bit offset and so cannot use the offset of my kernel in the higher half. Also since segment based addressing is ignored i cant do any tricks with the base address to overcome this problem. So is the only way to do this to include a tiny 64bit code stub in my 32bit loader that repeats the jump with a 64bit address or am i missing a trick?
My solution to this problem is to use an extra loader, maybe even two.
Use grub to load your 32-bit identity mapped loader. Switch the CPU to 64-bit mode(still with identity map), set up higher half page map in 64-bit mode, then load kernel into higher half, finally a simple jmp will bring you a higher half 64-bit kernel.
Yesterday, I just splited my loader into to two parts. The first part runs in 32-bit mode without map, the second loader runs in 64-bit mode with identity map setup by the 32-bit loader. The page map also maps the low memory into higher half.
so the initialization of my 64-bit higher half kernel is like this:
grub => 32-bit loader (no map) => 64-bit loader (identity map + higher half map) => 64-bit kernel (higher half map only).
btw, no need to add filesystem support in the loader. just use the .incbin gas directive to include the kernel binary file directly into the loader.

Re: Loading a higher half long mode kernel help

Posted: Sun Mar 28, 2010 8:45 am
by PinkyNoBrain
Excellent, thank you guys. I'm going to use the trampoline approach as suggested :-)