Page 1 of 1

Switch to long mode with seperate loader

Posted: Sat Apr 01, 2017 11:02 am
by nilres
Hey guys,

after some time I started again on working on some kernel experiments. Currently I am trying to implement a loader that sets up long mode and then jumps to an 64 elf module loaded by GRUB (just like it is described here: http://wiki.osdev.org/Creating_a_64-bit ... ate_loader)

After my loader was put into memory by Grub I setup a temporary GDT and start loading the elf. After that I check if long mode is supported (cpuid...).
Then I setup paging for long mode (not enabling it yet), enable PAE, switch to compatibility mode and then enable paging. Then I call my enter64BitKernel method (which sets a new Long Mode GDT and then jumps to the kernel).

Everything works fine (no faults). When I put only 32bit code in my 64bit kernel module it executes just fine (so I assume my elf loading is working probably). But as soon as I put any 64bit instructions in my 64 bit kernel it doesn't work and eventually triple faults.

So here is some code:

loader.c:

Code: Select all

        disablePaging();

        cout<<"Setup 64 bit paging\n";
        setupPaging64();
        cout<<"Paging setup completed - not enabled\n";

        enablePAE();
        cout<<"PAE enabled\n";

        switchIA32e();
        cout<<"IA32 compatibltiy mode enabled\n";

        enablePaging64();
        cout<<"Paging enabled\n";

        gdt64[0].set(0x0);
        gdt64[1].set(0x0, 0xFFFFFFFF, 0x9A);
        gdt64[2].set(0x0, 0xFFFFFFFF, 0x92);

        if(gdt64[1].isLongMode() && gdt64[2].isLongMode()) {
            cout<<"long mode set\n";
        }

        GDTDescriptor gdtDescriptor64(GDT_ENTRIES_NUM, &gdt64[0]);

        cout<<"Loader finished, entering 64 bit kernel\n";
        enter64BitKernel(&gdtDescriptor64, kernelEntry);

        cout<<"We never should have reached this\n";
loader.s:

Code: Select all

.global setupPaging64
.type setupPaging64, @function
setupPaging64:
        movl $0x1000, %edi
        movl %edi, %cr3
	xorl %eax, %eax
	movl $4096, %ecx
	rep stosl
	movl %cr3, %edi
	
	movl $0x2003, (%edi)
	addl $0x1000, %edi
	movl $0x3003, (%edi)
	addl $0x1000, %edi
	movl $0x4003, (%edi)
	addl $0x1000, %edi

	movl $0x00000003, %ebx
	movl $512, %ecx

.SetEntry:
	movl %ebx, (%edi)
	addl $0x1000, %ebx
	addl $8, %edi
	loop .SetEntry

	ret

.global enablePAE
.type enablePAE, @function
enablePAE:
	movl %cr4, %eax
	orl $0x20, %eax # 1 << 5
	movl %eax, %cr4
	ret

.global switchIA32e
.type switchIA32e, @function
switchIA32e:
	movl $0xC0000080, %ecx
	rdmsr
	orl $0x100, %eax # 1 << 8
	wrmsr
	ret

.global enablePaging64
.type enablePaging64, @function
enablePaging64:
	movl %cr0, %eax
	orl $0x80000000, %eax # 1<<31
	movl %eax, %cr0
	ret

.global enter64BitKernel
.type enter64BitKernel, @function
enter64BitKernel:
	movl 8(%esp), %eax
	movl %eax, (kernel_entry)

	movl 4(%esp), %eax
	lgdt (%eax)

reloadSegmentsLong:			# jump to code segement
        jmp $0x08, $.reloadCSLong
.reloadCSLong:
	mov $0x10, %ax			# load data segment
	mov %ax, %ds
	mov %ax, %es
	mov %ax, %fs
	mov %ax, %gs
	mov %ax, %ss

	movl (kernel_entry), %eax
	#movl $stack_top, %esp
	#.long 0
	jmp *%eax			# jump to new kernel

	ret
kernel64.s:

Code: Select all

.section .text
.global _start64
.type _start64, @function
_start64:
        movl $0x00b8000, %edi
        movq $500, %rcx
        movq $0x1F201F201F201F20, %rax
        rep stosq
        movl $0x00b8000, %edi
    
# if I uncomment those lines it tripple faults

        #movq $0x1F6C1F6C1F651F48, %rax
        #movq %rax, (%edi)

        #movq $0x1F6F1F571F201F6F, %rax
        #movq %rax, 8(%edi)

        #movq $0x1F211F641F6C1F72, %rax
        #movq %rax, 16(%edi)

        call halt
    
.global halt
.type halt, @function
halt:
         cli
1:
         hlt
         jmp 1b
As you can see the code should clear the screen with a nice blue colour but what it actually does is just setting the first "pixel" blue and puts an arrow symbol in it.

What makes me curious is the following: When I look at the memory with qemu it shows the following (0x509 is the entry point of the elf):
(qemu) xp /10i 0x509
0x0000000000000509: mov $0xb8000,%edi
0x000000000000050e: dec %eax
0x000000000000050f: mov $0x1f4,%ecx
0x0000000000000515: dec %eax
0x0000000000000516: mov $0x1f201f20,%eax
0x000000000000051b: and %bl,(%edi)
0x000000000000051d: and %bl,(%edi)
0x000000000000051f: repz dec %eax
0x0000000000000521: stos %eax,%es:(%edi)
0x0000000000000522: mov $0xb8000,%edi
(qemu)
it doesn't recognize it as 64Bit instructions. Is this normal behaviour of qemu or does this only mean that I'am not in long mode?!

Any ideas are appreciated :)

Greets
Nils

Re: Switch to long mode with seperate loader

Posted: Sat Apr 01, 2017 5:03 pm
by LtG
What does your gdt64[].set() do? The code segment doesn't seem to have the "long attribute" bit set?

You may want to check the GDT64 from here:
http://wiki.osdev.org/Setting_Up_Long_M ... it_Submode

Also, can't remember if there was any restrictions with having to have the 32-bit and 64-bit GDT entries when you transition.. not sure if you are recycling the same GDT table or if you have a separate 32-bit GDT.

Re: Switch to long mode with seperate loader

Posted: Sun Apr 02, 2017 2:27 am
by nilres
Thanks man. I was able to figure it out. I ruled that out a long time ago because I explicitly wrote a method that checked if the long mode bit was set. Unfortunately I miscounted the bits and checked the 22th bit not the 21th... Now it is working. Thanks a lot!