Page 1 of 2

long mode GDT reload

Posted: Tue May 04, 2010 1:41 pm
by proxy
So i've gotten my kernel to boot into 64-bit mode, higher half, everything is going smooth. And I want to assemble a new GDT and use that.

traditionally in protected mode, after my lgdt, I would do something like (from memory, might be not exactly right)

Code: Select all

ljmp $0x08, 1f; 1:
. Unfortunately, this is not available in long mode. So what is the correct way to do that? I've tried something like this with no success (bochs shows an exception on the ljmp instruction).

Code: Select all

set_cs:
	movabsq $jump_buffer, %rax // make rax a pointer to out little structure below
	ljmp *(%rax)

// ljmp "descriptor" target is right below it
jump_buffer:
	.word 0x08
	.quad target
	
// we should end up here, with the new CS loaded and simply return
target:
	ret
What am I missing? Are there some alignment requirements I've missed?

proxy

Re: long mode GDT reload

Posted: Tue May 04, 2010 2:02 pm
by Combuster
Nothing other than this:
You wrote:Unfortunately, this is not available in long mode.
If you are already in long mode, and you need a different value in CS, use iretq

Re: long mode GDT reload

Posted: Tue May 04, 2010 3:01 pm
by Owen
far return also works to my knowledge (Checking the AMD manuals, it doesn't say anywhere it shouldn't)

Of course, there is pretty much no reason to reload the GDT in long mode.

Re: long mode GDT reload

Posted: Tue May 04, 2010 8:37 pm
by lemonyii
Intel® 64 and IA-32 Architectures
Software Developer’s Manual
Volume 3A:
8.8.5 Initializing IA-32e Mode

Operating systems should follow this sequence to initialize IA-32e mode:
1. Starting from protected mode, disable paging by setting CR0.PG = 0. Use the
MOV CR0 instruction to disable paging (the instruction must be located in an
identity-mapped page).
2. Enable physical-address extensions (PAE) by setting CR4.PAE = 1. Failure to
enable PAE will result in a #GP fault when an attempt is made to initialize IA-32e
mode.
3. Load CR3 with the physical base address of the Level 4 page map table (PML4).
4. Enable IA-32e mode by setting IA32_EFER.LME = 1.
5. Enable paging by setting CR0.PG = 1. This causes the processor to set the
IA32_EFER.LMA bit to 1. The MOV CR0 instruction that enables paging and the
following instructions must be located in an identity-mapped page (until such
time that a branch to non-identity mapped pages can be effected).

64-bit mode consistency checks fail in the following circumstances:
• An attempt is made to enable or disable IA-32e mode while paging is enabled.
• IA-32e mode is enabled and an attempt is made to enable paging prior to
enabling physical-address extensions (PAE).
• IA-32e mode is active and an attempt is made to disable physical-address
extensions (PAE).
• If the current CS has the L-bit set on an attempt to activate IA-32e mode.
• If the TR contains a 16-bit TSS.

check the manual for details
good luck

Re: long mode GDT reload

Posted: Tue May 04, 2010 9:35 pm
by gerryg400

Code: Select all

set_cs:
   movabsq $jump_buffer, %rax // make rax a pointer to out little structure below
   REX.W ljmp *(%rax)

// ljmp "descriptor" target is right below it
jump_buffer:
   .word 0x08
   .quad target
   
// we should end up here, with the new CS loaded and simply return
target:
   ret
It should work. But you'll need a rex.w on the front of the ljmp. If that doesn't work, are you sure the 0x8 goes before the target. It might be (just guessing no books with me to check)
.quad target
.word 0x08

- gerryg400

Re: long mode GDT reload

Posted: Tue May 04, 2010 9:41 pm
by gerryg400

Code: Select all

set_cs:
   movabsq $jump_buffer, %rax // make rax a pointer to out little structure below
   rex.w ljmp *(%rax)

// ljmp "descriptor" target is right below it
jump_buffer:
   .quad target
   .word 0x08
   
// we should end up here, with the new CS loaded and simply return
target:
   ret
Just did a quick test. Above code seems to work. Added REX.W and swapped the order of the contents of jump_buffer.

- gerryg400

Re: long mode GDT reload

Posted: Wed May 05, 2010 9:06 am
by Owen
According to AMD64 Vol 3, "If the operand size is 32 or 64 bits, the operand is a 16-bit selector followed by a 32-bit offset". In other words, there is no 64:16 indirect far jump. The REX prefix does nothing.

From this I can ascertain that either of the following is the case:
  • Intel added a REX-based 64-bit jump for EM64T. This is technically incompatible, but unlikely to have any effect (Who would code a 32-bit far jump with a REX prefix?) (Edit: This is the case)
  • Intel didn't and your emulator is broken
In either case, your code is non-portable.

Re: long mode GDT reload

Posted: Wed May 05, 2010 10:20 am
by Owen
I did a bit of experimentation to check this was not a documentation oversight on either's part. First, the code:

Code: Select all

.global main
main:
        mov %cs, %bx
        mov %bx, jcs
        movabs $jmpbuf, %rax
        rex.w ljmp  *(%rax)

.data
jmpbuf: .quad after
jcs:    .word 0
        

.text
after:
        mov $0, %rax
        ret
Experimental results:
  • On AMD64, this code crashes with a SIGSEGV (The processor loads the CS from part way through the offset and therefore gets 0000h and crashes)
  • On Intel64, this code works
  • On AMD64, this code works if the .quad offset is changed to a .long, obviously with limited addressing
  • On both, this code works if the .quad offset is changed to a .long and the rex.w prefix removed
This is consistent with both's documentation

Re: long mode GDT reload

Posted: Wed May 05, 2010 10:23 am
by StephanvanSchaik
Owen wrote:far return also works to my knowledge (Checking the AMD manuals, it doesn't say anywhere it shouldn't)
gerryg400 wrote: ...

- gerryg400
According to AMD64 Architecture Programmer's Manual Volume 2: System Programming, section 2.5.10, it is illegal to use the far jump instruction in 64-bit mode.
proxy wrote:traditionally in protected mode, after my lgdt, I would do something like (from memory, might be not exactly right)

Code: Select all

ljmp $0x08, 1f; 1:
. Unfortunately, this is not available in long mode. So what is the correct way to do that? I've tried something like this with no success (bochs shows an exception on the ljmp instruction).
The only correct way to reload the segment registers after you executed the LGDT-instruction in 64-bit mode is to use IRETQ for the code segment register (and perhaps stack segment register, although the latter is useless) and MOV for the other segment registers (DS, ES, FS and GS, although useless as well).

This is some code coming from my 64-bit kernel which works properly (Note: I also modify the RFLAGS, so you might want to look up what bits I'm changing so you get why I am doing this, since this is required in some cases):

Code: Select all

    lgdt [GDT.Pointer]
    mov rbp, rsp
    push QWORD GDT.KData
    push rbp
    pushfq
    pushfq
    pop rax
    and rax, 1111111111111111111111111111111111111111111111101011111011111111b
    push rax
    popfq
    push QWORD GDT.KCode
    push QWORD .Flush
    iretq
    
.Flush:
    mov ax, GDT.KData
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
Owen wrote:Of course, there is pretty much no reason to reload the GDT in long mode.
There are reasons to reload the GDT. First of all, the GDT you initially get from boot loaders isn't reliable, since you usually don't know where it is and what descriptors it has. Second of all, after enabling long mode your GDT will not be the most-ideal setup at all so you will want to reload the GDT with one that has a supervisor code entry, a supervisor data entry, a user code entry, a user data entry and a TSS entry or whatever entries you want to have in your GDT (in which case the two different data segments are actually not playing a role and are only there to remain "compatible" with 32-bit mode, since the data descriptors are almost entirely ignored).


Regards,
Stephan J.R. van Schaik.

Re: long mode GDT reload

Posted: Wed May 05, 2010 10:45 am
by Owen
StephanVanSchaik wrote:
Owen wrote:Of course, there is pretty much no reason to reload the GDT in long mode.
There are reasons to reload the GDT. First of all, the GDT you initially get from boot loaders isn't reliable, since you usually don't know where it is and what descriptors it has. Second of all, after enabling long mode your GDT will not be the most-ideal setup at all so you will want to reload the GDT with one that has a supervisor code entry, a supervisor data entry, a user code entry, a user data entry and a TSS entry or whatever entries you want to have in your GDT (in which case the two different data segments are actually not playing a role and are only there to remain "compatible" with 32-bit mode, since the data descriptors are almost entirely ignored).


Regards,
Stephan J.R. van Schaik.
After taking control from GRUB, I switch to my GDT. This is the same GDT as I use in long mode; it is laid out appropriately. I then enter long mode, and jump to the higher half. At this point I re-execute LGDT with the higher half (rather than identity mapped) address, but again pointed at the same physical GDT.

I never reload the GDT following that. At no point do I need to reload CS except for when I do my initial entry to long mode.

By the way: far returns work to switch segments, at least according to my limited (userspace) testing:

Code: Select all

.global main
main:
        mov %cs, %ax
        movzx %ax, %rax
        pushq %ax
        pushq $next
        lretq

next:   mov $str, %rdi
        jmp puts

.section .rodata
str:    .asciz "Hello world!"
Runs successfully. One could create the following:

Code: Select all

// Switch code segment
// void switchseg(short newseg)
.global switchseg
switchseg:
    pop %rsi
    push %rdi
    push %rsi
    lretq

Re: long mode GDT reload

Posted: Wed May 05, 2010 10:50 am
by StephanvanSchaik
Owen wrote:By the way: far returns work to switch segments, at least according to my limited (userspace) testing:

Code: Select all

.global main
main:
        mov %cs, %ax
        movzx %ax, %rax
        pushq %ax
        pushq $next
        lretq

next:   mov $str, %rdi
        jmp puts

.section .rodata
str:    .asciz "Hello world!"
Runs successfully. One could create the following:

Code: Select all

// Switch code segment
// void switchseg(short newseg)
.global switchseg
switchseg:
    pop %rsi
    push %rdi
    push %rsi
    lretq
The AMD64 manuals seem to recommend IRETQ over RETQ though. That's probably due the fact that the switch to user mode uses IRETQ as well.


Regards,
Stephan J.R. van Schaik.

Re: long mode GDT reload

Posted: Wed May 05, 2010 12:51 pm
by gerryg400
According to AMD64 Architecture Programmer's Manual Volume 2: System Programming, section 2.5.10, it is illegal to use the far jump instruction in 64-bit mode.
In that case they are referring to the ea opcode. I used the ff opcode (Jump far, absolute indirect, address given in m16:64.)

- gerryg400

Re: long mode GDT reload

Posted: Wed May 05, 2010 12:58 pm
by gerryg400
I did a bit of experimentation to check this was not a documentation oversight on either's part. First, the code:

Code: Select all

Code:
.global main
main:
        mov %cs, %bx
        mov %bx, jcs
        movabs $jmpbuf, %rax
        rex.w ljmp  *(%rax)

.data
jmpbuf: .quad after
jcs:    .word 0
        

.text
after:
        mov $0, %rax
        ret
Experimental results:
On AMD64, this code crashes with a SIGSEGV (The processor loads the CS from part way through the offset and therefore gets 0000h and crashes)
On Intel64, this code works
On AMD64, this code works if the .quad offset is changed to a .long, obviously with limited addressing
On both, this code works if the .quad offset is changed to a .long and the rex.w prefix removed

This is consistent with both's documentation
Owen, did you have the NULL selector ? Or a valid selector in jcs?

- gerryg400

Re: long mode GDT reload

Posted: Wed May 05, 2010 1:19 pm
by Owen
gerryg400 wrote: Owen, did you have the NULL selector ? Or a valid selector in jcs?

- gerryg400
Note the following:

Code: Select all

        mov %cs, %bx
        mov %bx, jcs
I grab the program's CS ;)

Re: long mode GDT reload

Posted: Wed May 05, 2010 1:48 pm
by gerryg400
oops. It's very early here :)