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
Loading a higher half long mode kernel help
-
- Posts: 22
- Joined: Mon Oct 29, 2007 10:49 am
- xenos
- Member
- Posts: 1121
- Joined: Thu Aug 11, 2005 11:00 pm
- Libera.chat IRC: xenos1984
- Location: Tartu, Estonia
- Contact:
Re: Loading a higher half long mode kernel help
There is a nice tutorial on setting up and entering long mode in the wiki:
http://wiki.osdev.org/User:Stephanvansc ... _Long_Mode
http://wiki.osdev.org/User:Stephanvansc ... _Long_Mode
-
- Posts: 22
- Joined: Mon Oct 29, 2007 10:49 am
Re: Loading a higher half long mode kernel help
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:
Where it says:
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?; Set the code segment and enter 64-bit long mode.
jmp GDT64.Code:Realm64
- Owen
- Member
- Posts: 1700
- Joined: Fri Jun 13, 2008 3:21 pm
- Location: Cambridge, United Kingdom
- Contact:
Re: Loading a higher half long mode kernel help
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
- xenos
- Member
- Posts: 1121
- Joined: Thu Aug 11, 2005 11:00 pm
- Libera.chat IRC: xenos1984
- Location: Tartu, Estonia
- Contact:
Re: Loading a higher half long mode kernel help
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
-
- Member
- Posts: 127
- Joined: Sat Sep 29, 2007 5:43 pm
- Location: Amsterdam, The Netherlands
Re: Loading a higher half long mode kernel help
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.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:
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?; Set the code segment and enter 64-bit long mode.
jmp GDT64.Code:Realm64
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
My solution to this problem is to use an extra loader, maybe even two.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?
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.
-
- Posts: 22
- Joined: Mon Oct 29, 2007 10:49 am
Re: Loading a higher half long mode kernel help
Excellent, thank you guys. I'm going to use the trampoline approach as suggested