Change GDT and update CS while in long mode

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
lodo1995
Posts: 16
Joined: Thu Nov 12, 2015 6:31 am

Change GDT and update CS while in long mode

Post by lodo1995 »

Hi,
I'm trying to write a small 64-bit OS, booting it via UEFI.
This means that when my code takes full control (after ExitBootServices), the processor is already in 64-bit long mode, with paging enabled.

What I want to do is substitute all UEFI generated structures (paging structures, GDT, IDT) with my own.

After successfully loading a new value in CR3 (paging structures) and in GDTR (with lgdt), to use my new GDT, I need to update the CS register. On the OSDev wiki there are tutorials to load a new GDT from 32-bit to 64-bit mode, but none on how to do it when already in 64 bit long mode.

I suppose I should use a far jump, but this code does not work (AT&T syntax):

Code: Select all

mov %rax, %cr3   # load paging structures (it works)
lgdt 6(%rcx)     # load gdt (it works)
mov $100, %rsp   # update stack pointer (it works)

# now what I tried unsuccessfully:
pushw $8         # new code segment selector
pushq fun        # function to execute next
retfq            # far return (pops address and code segment)
Not having any IDT in place, this code triple faults while executing retfq.

Thanks in advance for your help.
FallenAvatar
Member
Member
Posts: 283
Joined: Mon Jan 03, 2011 6:58 pm

Re: Change GDT and update CS while in long mode

Post by FallenAvatar »

You are not doing a far jump. A far jump in long mode is the same as entering protected mode. You can see here for code: http://wiki.osdev.org/Protected_mode

Basically, you need to do a:

Code: Select all

jmp {segment selector}:{address}
- Monk
User avatar
iansjack
Member
Member
Posts: 4706
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Change GDT and update CS while in long mode

Post by iansjack »

A far return should change the selector as required; it doesn't have to be a far jump.

Anyway, there is no such thing as a word push in long mode; all pushes are 64-bit. I would suggest that you run the code under a debugger and inspect the stack just before you do the far return. Then single-step the far return and see what values it loads.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: Change GDT and update CS while in long mode

Post by Combuster »

Is RCX really pointing to six bytes before the GDTR structure? lgdt happily fills the gdt register with garbage.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
lodo1995
Posts: 16
Joined: Thu Nov 12, 2015 6:31 am

Re: Change GDT and update CS while in long mode

Post by lodo1995 »

Combuster wrote:Is RCX really pointing to six bytes before the GDTR structure? lgdt happily fills the gdt register with garbage.
Yes, because RCX points to a structure like this:

Code: Select all

struct gdtr
{
    unsigned padding: 48;
    unsigned size: 16;
    unsigned ptr: 64;
};
This way the GDT pointer has natural alignment.
Last edited by lodo1995 on Mon Dec 14, 2015 6:38 am, edited 2 times in total.
lodo1995
Posts: 16
Joined: Thu Nov 12, 2015 6:31 am

Re: Change GDT and update CS while in long mode

Post by lodo1995 »

tjmonk15 wrote:You are not doing a far jump. A far jump in long mode is the same as entering protected mode. You can see here for code: http://wiki.osdev.org/Protected_mode

Basically, you need to do a:

Code: Select all

jmp {segment selector}:{address}
- Monk
Whenever I try that, the GNU assembler complains with an error like this (where func is a label):

Code: Select all

junk `:func' after expression
User avatar
iansjack
Member
Member
Posts: 4706
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Change GDT and update CS while in long mode

Post by iansjack »

lodo1995 wrote:
Combuster wrote:Is RCX really pointing to six bytes before the GDTR structure? lgdt happily fills the gdt register with garbage.
Yes, because RCX points to a structure like this:

Code: Select all

struct gdtr
{
    unsigned padding: 48;
    unsigned size: 16;
    unsigned ptr: 64;
};
This way the GDT pointer has natural alignment.
The best way to be sure would be to inspect the memory area in your debugger. You're sure that your structure is packed? If not this can lead to all sort of errors. The debugger should tell you the value of CS at the time of the crash; that way you know whether you have an invalid selector or a GDT problem.
lodo1995
Posts: 16
Joined: Thu Nov 12, 2015 6:31 am

Re: Change GDT and update CS while in long mode

Post by lodo1995 »

iansjack wrote: The best way to be sure would be to inspect the memory area in your debugger. You're sure that your structure is packed? If not this can lead to all sort of errors. The debugger should tell you the value of CS at the time of the crash; that way you know whether you have an invalid selector or a GDT problem.
Thank you. The structure is correctly packed. The crash happens when RIP points to retfq and CS still points to the code segment of the old GDT set up by UEFI. The problem is that I don't know how to change CS to the correct value for the new GDT that I loaded.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: Change GDT and update CS while in long mode

Post by Combuster »

lgdt happily fills the gdt register with garbage.
lodo1995 wrote:The structure is correctly packed. The crash happens when RIP points to retfq and CS still points to the code segment of the old GDT set up by UEFI.
So that you know, the latter does not at all prove the former.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
lodo1995
Posts: 16
Joined: Thu Nov 12, 2015 6:31 am

Re: Change GDT and update CS while in long mode

Post by lodo1995 »

The kernel's binary, loaded at 0xFFFF800000000000:

Code: Select all

00: .. a structure ..  (size = 0x48)
48: 01 00 00 00 01 00 27 00 (27 00 is the 16-bit size of the GDT)
50: 58 00 00 00 00 80 FF FF (0xFFFF800000000058 is the GDT address)
58: 00 00 00 00 00 00 00 00 (first GDT entry: null descriptor)
.. other descriptors ..
.. other things ..
Some registers at crash time:

Code: Select all

rcx=FFFF800000000048 (RCX points 6 bytes before GDTR structure)
gdtr=FFFF800000000058:0027 (GDT correctly loaded)
cs={0028 base=0000000000000000 limit=ffffffff flags=0000a09b} (CS points to segment 28, which in the old UEFI GDT was the code segment)
User avatar
iansjack
Member
Member
Posts: 4706
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Change GDT and update CS while in long mode

Post by iansjack »

lodo1995 wrote: The structure is correctly packed.
I only asked because there was no directive to do so in the code that you quoted. Presumably you use a command-line option to pack structs.
The crash happens when RIP points to retfq and CS still points to the code segment of the old GDT set up by UEFI. The problem is that I don't know how to change CS to the correct value for the new GDT that I loaded.
As far as I can see that should work, presuming that you have pre-loaded the correct values onto the stack, your stack pointer is pointing to the correct location, and your GDT is valid. The best way to check each of these possibilities is by inspecting the stack and the GDT in your debugger. In practice a long jump is easier, and less error prone, than faking a function return which is why this is normally done that way.
User avatar
iansjack
Member
Member
Posts: 4706
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Change GDT and update CS while in long mode

Post by iansjack »

lodo1995 wrote:27 00 is the 16-bit size of the GDT
Is it just me, or is there something wrong there? How can the table be an odd number of bytes in size?
lodo1995
Posts: 16
Joined: Thu Nov 12, 2015 6:31 am

Re: Change GDT and update CS while in long mode

Post by lodo1995 »

iansjack wrote:
lodo1995 wrote:27 00 is the 16-bit size of the GDT
Is it just me, or is there something wrong there? How can the table be an odd number of bytes in size?
If I interpreted correctly the specification, that field (that I called size) is in fact the offset of the last valid byte of the GDT, that is, the size of the GDT minus one.
User avatar
iansjack
Member
Member
Posts: 4706
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Change GDT and update CS while in long mode

Post by iansjack »

lodo1995 wrote:
iansjack wrote:
lodo1995 wrote:27 00 is the 16-bit size of the GDT
Is it just me, or is there something wrong there? How can the table be an odd number of bytes in size?
If I interpreted correctly the specification, that field (that I called size) is in fact the offset of the last valid byte of the GDT, that is, the size of the GDT minus one.
Let's assume that you are correct (though I'm not convinced that you are). If you have an 8-byte selector starting at byte 0x28 how can the size of the table be 0x27?
lodo1995
Posts: 16
Joined: Thu Nov 12, 2015 6:31 am

Re: Change GDT and update CS while in long mode

Post by lodo1995 »

Before continuing, I'd like to thank everyone for the feedback so far. It's not that common to find someone willing to spend so much time helping you.
iansjack wrote:
lodo1995 wrote:
iansjack wrote: Is it just me, or is there something wrong there? How can the table be an odd number of bytes in size?
If I interpreted correctly the specification, that field (that I called size) is in fact the offset of the last valid byte of the GDT, that is, the size of the GDT minus one.
Let's assume that you are correct (though I'm not convinced that you are). If you have an 8-byte selector starting at byte 0x28 how can the size of the table be 0x27?
Regarding the offset of the last valid byte, this is a quote from the Intel Developer Manual:
The limit value for the GDT is expressed in bytes. As with segments, the limit value is added to the base address to
get the address of the last valid byte. A limit value of 0 results in exactly one valid byte. Because segment descriptors
are always 8 bytes long, the GDT limit should always be one less than an integral multiple of eight (that is, 8N
– 1).
Regarding the selector starting at 0x28, as I said, it is a selector referencing the old GDT made up by UEFI. I started this thread because I can't manage to substitute that selector with one referencing my new GDT. On the OSDev wiki (http://wiki.osdev.org/Setting_Up_Long_M ... it_Submode) it seems like a far jump should do it, when coming from 32-bit mode. But I'm already in 64-bit mode, and that instruction doesn't work. In fact, GAS refuses to compile every kind of far jump I tried.

So, apart from my code, any way of setting CS to 0x08 (a valid code selector in the new GDT) would be appreciated.

Thank you very much.
Post Reply