What can go wrong on long mode activation ?

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.
Post Reply
User avatar
Neolander
Member
Member
Posts: 228
Joined: Tue Mar 23, 2010 3:01 pm
Location: Uppsala, Sweden
Contact:

What can go wrong on long mode activation ?

Post by Neolander »

Hello everyone !

I run an assembly snippet heavily inspired from AMD's code which theoretically
1/Checks long mode availability
2/Sets PAE bit
3/Loads the right CR3 value
4/Sets LME bit
5/Grabs CR0's value, sets bit 31 ("PG"), puts it back in CR0, then long jumps at 64-bit code entry point.

And which practically
1/Checks long mode availability
2/Sets PAE bit
3/Loads the right CR3 value
4/Sets LME bit
5/Triple faults

I'm not ready to give up and ask if somebody may have a look at my code yet, I just want to know in exactly which circumstances an exception (most likely #GP) may be triggered at step 5.

AMD mentions the following :
If ((EFER.LME=1) & (CR4.PAE=0) then #GP(0)
Not likely, since LME & PAE are both set.
If ((EFER.LME=1) & (CS.L=1)) then #GP(0)
Not likely either. I did not check the GRUB-provided segments, but why should a dirty old bootloader from the 32-bit era set CS.L ?

Apart from that, other options could be...
1/I jump in an NX section
-> No. The section of memory is not NX

2/My page table is not properly identity-mapped or there's something else fundamentally wrong in the way I create it
-> No, at least not as far as I can tell

...and ?

Could someone provide me with a more extensive list of everything which can go wrong during long mode activation ?
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: What can go wrong on long mode activation ?

Post by Combuster »

I suggest you begin with posting a crash log from bochs.
"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 ]
User avatar
Neolander
Member
Member
Posts: 228
Joined: Tue Mar 23, 2010 3:01 pm
Location: Uppsala, Sweden
Contact:

Re: What can go wrong on long mode activation ?

Post by Neolander »

Bochs says this :

Code: Select all

00052120042e[CPU0 ] interrupt(long mode): IDT entry extended attributes DWORD4 TYPE != 0
00052120042e[CPU0 ] interrupt(long mode): IDT entry extended attributes DWORD4 TYPE != 0
00052120042i[CPU0 ] CPU is in compatibility mode (active)
00052120042i[CPU0 ] CS.d_b = 32 bit
00052120042i[CPU0 ] SS.d_b = 32 bit
00052120042i[CPU0 ] EFER   = 0x00000d00
00052120042i[CPU0 ] | RAX=00000000e0000011  RBX=00000000010010e0
00052120042i[CPU0 ] | RCX=00000000c0000080  RDX=0000000000000000
00052120042i[CPU0 ] | RSP=00000000001100d6  RBP=00000000001100fe
00052120042i[CPU0 ] | RSI=000000000012c000  RDI=0000000000123ca0
00052120042i[CPU0 ] |  R8=0000000000000000   R9=0000000000000000
00052120042i[CPU0 ] | R10=0000000000000000  R11=0000000000000000
00052120042i[CPU0 ] | R12=0000000000000000  R13=0000000000000000
00052120042i[CPU0 ] | R14=0000000000000000  R15=0000000000000000
00052120042i[CPU0 ] | IOPL=0 ID vip vif ac vm RF nt of df if tf sf zf af pf cf
00052120042i[CPU0 ] | SEG selector     base    limit G D
00052120042i[CPU0 ] | SEG sltr(index|ti|rpl)     base    limit G D
00052120042i[CPU0 ] |  CS:0008( 0001| 0|  0) 00000000 ffffffff 1 1
00052120042i[CPU0 ] |  DS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00052120042i[CPU0 ] |  SS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00052120042i[CPU0 ] |  ES:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00052120042i[CPU0 ] |  FS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00052120042i[CPU0 ] |  GS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00052120042i[CPU0 ] |  MSR_FS_BASE:0000000000000000
00052120042i[CPU0 ] |  MSR_GS_BASE:0000000000000000
00052120042i[CPU0 ] | RIP=00000000001012b6 (00000000001012b6)
00052120042i[CPU0 ] | CR0=0xe0000011 CR2=0x00000000001012b6
00052120042i[CPU0 ] | CR3=0x01085000 CR4=0x00000020
(0).[52120042] [0x001012b6] 0008:00000000001012b6 (unk. ctxt): jmp far ds:[ebx]          ; ff2b
00052120042e[CPU0 ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting
gerryg400
Member
Member
Posts: 1801
Joined: Thu Mar 25, 2010 11:26 pm
Location: Melbourne, Australia

Re: What can go wrong on long mode activation ?

Post by gerryg400 »

Not likely either. I did not check the GRUB-provided segments, but why should a dirty old bootloader from the 32-bit age set CS.L ?
Note that the Grub documentation warns against using its GDT entries.
If a trainstation is where trains stop, what is a workstation ?
User avatar
Neolander
Member
Member
Posts: 228
Joined: Tue Mar 23, 2010 3:01 pm
Location: Uppsala, Sweden
Contact:

Re: What can go wrong on long mode activation ?

Post by Neolander »

gerryg400 wrote:Note that the Grub documentation warns against using its GDT entries.
That's right. Maybe I should make a new GDT myself, then, though this one worked conveniently well until now, especially taking the mess that x86 segmentation is into account...
User avatar
Neolander
Member
Member
Posts: 228
Joined: Tue Mar 23, 2010 3:01 pm
Location: Uppsala, Sweden
Contact:

Re: What can go wrong on long mode activation ?

Post by Neolander »

FINAL EDIT : Well, could someone tell me why this code crashes at the ljmp step with a triple fault ? I don't understand what's wrong with my GDT...

Code :

Code: Select all

void replace_gdt() {
  unsigned int tss_entry;
  segment_descriptor gdt[4];
  uint32_t tss[26];
  uint64_t gdtr;
  
  //Fill in the GDT
  //Null segment
  gdt[0] = 0;
  //Code segment : setting all base bits to 0 and all limit bits to 1, then setting appropriate bits
  gdt[1] = 0x000f00000000ffff;
  gdt[1] += DBIT_READABLE + DBIT_CODEDATA + DBIT_SBIT + DPL_USERMODE + DBIT_PRESENT + DBIT_DEFAULT_32OPSZ + DBIT_GRANULARITY;
  //Data segment
  gdt[2] = 0x000f00000000ffff;
  gdt[2] += DBIT_WRITABLE + DBIT_SBIT + DPL_USERMODE + DBIT_PRESENT + DBIT_DEFAULT_32OPSZ + DBIT_GRANULARITY;
  
  //Make a TSS with SS0 = 16 (Kernel data is 2nd GDT entry), ESP0 = 0 (bad practice, but system calls won't occur), and IOPB = 104
  //(because we don't need this io bitmap)
  for(tss_entry=0; tss_entry<26; ++tss_entry) {
    if(tss_entry==2) tss[tss_entry]=16;
    else if(tss_entry==25) tss[tss_entry]=104 << 16;
    else tss[tss_entry]=0;
  }
  //Add a TSS descriptor. We assume that the TSS is located in the first 2^24 bytes of the address space, which is reasonable here.
  gdt[3] = ((uint32_t) tss) * (1 << LIMIT_CHUNK1_SIZE) + 104;
  gdt[3] += IS_A_TSS + DPL_USERMODE + DBIT_PRESENT;
  
  //Now that the GDT is complete, setup GDTR value and load it
  gdtr = (uint32_t) gdt;
  gdtr <<= 16;
  gdtr += 32;
  __asm__ volatile ("lgdt (%0);\
                     ljmp $8, $bp;\
                     bp:\
                       xchg %%bx, %%bx;\
                     mov $16, %%ax;\
                     mov %%ax, %%ds;\
                     mov %%ax, %%es;\
                     mov %%ax, %%fs;\
                     mov %%ax, %%gs;\
                     mov %%ax, %%ss;\
                     mov $24, %%ax;\
                     ltr %%ax"
             :
             : "r" (&gdtr)
             : "%ax", "%bx"
             );

}
Crash log :

Code: Select all

00048819962e[CPU0 ] fetch_raw_descriptor: GDT: index (f) 1 > limit (0)
00048819962e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
00048819962e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)
00048819962i[CPU0 ] CPU is in protected mode (active)
00048819962i[CPU0 ] CS.d_b = 32 bit
00048819962i[CPU0 ] SS.d_b = 32 bit
00048819962i[CPU0 ] EFER   = 0x00000000
00048819962i[CPU0 ] | RAX=000000000011dd80  RBX=000000000012b844
00048819962i[CPU0 ] | RCX=000000000011e004  RDX=00000000001100c6
00048819962i[CPU0 ] | RSP=00000000001100be  RBP=00000000001100d6
00048819962i[CPU0 ] | RSI=0000000000124ca0  RDI=000000000002bebb
00048819962i[CPU0 ] |  R8=0000000000000000   R9=0000000000000000
00048819962i[CPU0 ] | R10=0000000000000000  R11=0000000000000000
00048819962i[CPU0 ] | R12=0000000000000000  R13=0000000000000000
00048819962i[CPU0 ] | R14=0000000000000000  R15=0000000000000000
00048819962i[CPU0 ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf zf AF PF cf
00048819962i[CPU0 ] | SEG selector     base    limit G D
00048819962i[CPU0 ] | SEG sltr(index|ti|rpl)     base    limit G D
00048819962i[CPU0 ] |  CS:0008( 0001| 0|  0) 00000000 ffffffff 1 1
00048819962i[CPU0 ] |  DS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00048819962i[CPU0 ] |  SS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00048819962i[CPU0 ] |  ES:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00048819962i[CPU0 ] |  FS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00048819962i[CPU0 ] |  GS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00048819962i[CPU0 ] |  MSR_FS_BASE:0000000000000000
00048819962i[CPU0 ] |  MSR_GS_BASE:0000000000000000
00048819962i[CPU0 ] | RIP=00000000001012ad (00000000001012ad)
00048819962i[CPU0 ] | CR0=0x60000011 CR2=0x0000000000000000
00048819962i[CPU0 ] | CR3=0x00000000 CR4=0x00000000
(0).[48819962] [0x001012ad] 0008:00000000001012ad (unk. ctxt): jmp far 0008:001012b4     ; eab41210000800
00048819962e[CPU0 ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting
pcmattman
Member
Member
Posts: 2566
Joined: Sun Jan 14, 2007 9:15 pm
Libera.chat IRC: miselin
Location: Sydney, Australia (I come from a land down under!)
Contact:

Re: What can go wrong on long mode activation ?

Post by pcmattman »

Code: Select all

00048819962e[CPU0 ] fetch_raw_descriptor: GDT: index (f) 1 > limit (0)
00048819962e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
00048819962e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)
Looks like your GDT limit is zero, according to the processor. I'd be checking your GDTR. The fact that the index it's trying to find is 0xF is also probably indicative of a problem...

You may also want to set your data segments after you load the GDT and before you long jump to load CS, instead of after the load of CS.
gerryg400
Member
Member
Posts: 1801
Joined: Thu Mar 25, 2010 11:26 pm
Location: Melbourne, Australia

Re: What can go wrong on long mode activation ?

Post by gerryg400 »

Should your gdt and tss really be local variables ?

and

Code: Select all

__asm__ volatile ("lgdt (%0);\
I'm no expert in inline assembler but shouldn't that be

Code: Select all

__asm__ volatile ("lgdt %0;\
If a trainstation is where trains stop, what is a workstation ?
User avatar
Neolander
Member
Member
Posts: 228
Joined: Tue Mar 23, 2010 3:01 pm
Location: Uppsala, Sweden
Contact:

Re: What can go wrong on long mode activation ?

Post by Neolander »

pcmattman wrote:

Code: Select all

00048819962e[CPU0 ] fetch_raw_descriptor: GDT: index (f) 1 > limit (0)
00048819962e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
00048819962e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)
Looks like your GDT limit is zero, according to the processor. I'd be checking your GDTR. The fact that the index it's trying to find is 0xF is also probably indicative of a problem...
Well, I rode that the two low-order bytes are the limit and the 4 high-higher bytes are the base so I just created an uint64 and put (limit + base << 16) in it before LGDTing it. Is this wrong ?

You may also want to set your data segments after you load the GDT and before you long jump to load CS, instead of after the load of CS.
Done. New inline asm chunk :

Code: Select all

  __asm__ volatile ("lgdt (%0);\
                     mov $16, %%ax;\
                     mov %%ax, %%ds;\
                     mov %%ax, %%es;\
                     mov %%ax, %%fs;\
                     mov %%ax, %%gs;\
                     mov %%ax, %%ss;\
                     ljmp $8, $bp;\
                     bp:\
                       xchg %%bx, %%bx;\
                     mov $24, %%ax;\
                     ltr %%ax"
             :
             : "r" (&gdtr)
             : "%ax", "%bx"
             );
I now encounter exactly the same crash, but with the data segment (which is in fact reassuring since I made it pretty much the same way as the code segment) :

Code: Select all

00048819962e[CPU0 ] fetch_raw_descriptor: GDT: index (17) 2 > limit (0)
gerryg400 wrote:Should your gdt and tss really be local variables ?
Well, they probably shouldn't, but I won't ever be modifying them. My initial plan was "Go in long mode first and care about those things later, no use creating pmode structures with care if it's to replace them with long mode one a few cycles later". Apparently, GRUB's GDT could be not enough to achieve that goal, so I quickly hacked another one which should last just long enough to make the long mode switch before being nuked. That's why I set ESP0 = 0 in the TSS, by the way : I really, *really* don't plan to use it. It's just because I need one, apparently, according to the GDT tutorial.

Code: Select all

__asm__ volatile ("lgdt (%0);\
I'm no expert in inline assembler but shouldn't that be

Code: Select all

__asm__ volatile ("lgdt %0;\
Aaah, I'd love that if it worked... However note that the GDTR is 48-bit data. So except using some black magic which I don't know about, I think I can't just load it in a 32-bit register and LGDT it.
I hence tried the following :
1/Get some uint64 (gdtr) around.
2/Fill it with the appropriate value (which is base << 16 + limit if I understood the paragraph about the two low-order bytes being a limit and the four high-order bytes being the base)

Code: Select all

  gdtr = (uint32_t) gdt;
  gdtr <<= 16;
  gdtr += 32;
3/Put its address in a register (see the definition of %0 in the inline assembly macro
4/Run lgdt [register]'s att assembly equivalent. (which is lgdt (%0), I think)
Is that wrong ?
gerryg400
Member
Member
Posts: 1801
Joined: Thu Mar 25, 2010 11:26 pm
Location: Melbourne, Australia

Re: What can go wrong on long mode activation ?

Post by gerryg400 »

Mine looks like this.

Code: Select all

static inline void lgdt(struct descref_t *pgdt) {

	__asm__ __volatile__ ( \
		"lgdt %0\n\t"
		:
		: "m" (*pgdt));
}
Maybe you can adapt it.
If a trainstation is where trains stop, what is a workstation ?
pcmattman
Member
Member
Posts: 2566
Joined: Sun Jan 14, 2007 9:15 pm
Libera.chat IRC: miselin
Location: Sydney, Australia (I come from a land down under!)
Contact:

Re: What can go wrong on long mode activation ?

Post by pcmattman »

Well, they probably shouldn't, but I won't ever be modifying them.
In their current state they'll be sitting on the stack, which will end up trashed fairly quickly if you call another function with multiple local variables later on.
My initial plan was "Go in long mode first and care about those things later, no use creating pmode structures with care if it's to replace them with long mode one a few cycles later"
Sounds like you're trying to get it done, and then do it right later. I'd highly suggest for this really low-level initialisation stuff that you do it right first, and then optimise/upgrade it later.

For the GDT, "doing it right" usually means it works :)
gerryg400
Member
Member
Posts: 1801
Joined: Thu Mar 25, 2010 11:26 pm
Location: Melbourne, Australia

Re: What can go wrong on long mode activation ?

Post by gerryg400 »

Neolander, I'm not sure whether you realise, but whenever you load a segment register, the processor will access your GDT in memory. Therefore, you must not load any segment registers after leaving this function. The GDT *REALLY MUST* be a global variable. Same applies to TSS.
If a trainstation is where trains stop, what is a workstation ?
User avatar
Neolander
Member
Member
Posts: 228
Joined: Tue Mar 23, 2010 3:01 pm
Location: Uppsala, Sweden
Contact:

Re: What can go wrong on long mode activation ?

Post by Neolander »

pcmattman wrote:
Well, they probably shouldn't, but I won't ever be modifying them.
In their current state they'll be sitting on the stack, which will end up trashed fairly quickly if you call another function with multiple local variables later on.
*facepalm* Wow ! You both are right, I completely forgot about this aspect of the situation, shame on me... I'll make it static or global this evening...
My initial plan was "Go in long mode first and care about those things later, no use creating pmode structures with care if it's to replace them with long mode one a few cycles later"
Sounds like you're trying to get it done, and then do it right later. I'd highly suggest for this really low-level initialisation stuff that you do it right first, and then optimise/upgrade it later.

For the GDT, "doing it right" usually means it works :)
That's exactly the result which I'm trying to achieve ;) This function is a hack, but I'd like it to be a working hack ^^
User avatar
Neolander
Member
Member
Posts: 228
Joined: Tue Mar 23, 2010 3:01 pm
Location: Uppsala, Sweden
Contact:

Re: What can go wrong on long mode activation ?

Post by Neolander »

Okay, I used gerryg400's code, made variables global where appropriate, changed the DPL of my segments, and now segmentation seems to work. Thanks everyone !!! :mrgreen:

The thing is, long mode activation still does not seem to work. Same error messages as before :

Code: Select all

00052128127e[CPU0 ] interrupt(long mode): IDT entry extended attributes DWORD4 TYPE != 0
00052128127e[CPU0 ] interrupt(long mode): IDT entry extended attributes DWORD4 TYPE != 0
00052128127i[CPU0 ] CPU is in compatibility mode (active)
00052128127i[CPU0 ] CS.d_b = 32 bit
00052128127i[CPU0 ] SS.d_b = 32 bit
00052128127i[CPU0 ] EFER   = 0x00000d00
00052128127i[CPU0 ] | RAX=00000000e0000011  RBX=00000000010010e0
00052128127i[CPU0 ] | RCX=00000000c0000080  RDX=0000000000000000
00052128127i[CPU0 ] | RSP=00000000001100d6  RBP=00000000001100fe
00052128127i[CPU0 ] | RSI=000000000012d000  RDI=0000000000124d40
00052128127i[CPU0 ] |  R8=0000000000000000   R9=0000000000000000
00052128127i[CPU0 ] | R10=0000000000000000  R11=0000000000000000
00052128127i[CPU0 ] | R12=0000000000000000  R13=0000000000000000
00052128127i[CPU0 ] | R14=0000000000000000  R15=0000000000000000
00052128127i[CPU0 ] | IOPL=0 ID vip vif ac vm RF nt of df if tf sf zf af pf cf
00052128127i[CPU0 ] | SEG selector     base    limit G D
00052128127i[CPU0 ] | SEG sltr(index|ti|rpl)     base    limit G D
00052128127i[CPU0 ] |  CS:0008( 0001| 0|  0) 00000000 ffffffff 1 1
00052128127i[CPU0 ] |  DS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00052128127i[CPU0 ] |  SS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00052128127i[CPU0 ] |  ES:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00052128127i[CPU0 ] |  FS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00052128127i[CPU0 ] |  GS:0010( 0002| 0|  0) 00000000 ffffffff 1 1
00052128127i[CPU0 ] |  MSR_FS_BASE:0000000000000000
00052128127i[CPU0 ] |  MSR_GS_BASE:0000000000000000
00052128127i[CPU0 ] | RIP=00000000001012b6 (00000000001012b6)
00052128127i[CPU0 ] | CR0=0xe0000011 CR2=0x00000000001012b6
00052128127i[CPU0 ] | CR3=0x01085000 CR4=0x00000020
(0).[52128127] [0x001012b6] 0008:00000000001012b6 (unk. ctxt): jmp far ds:[ebx]          ; ff2b
00052128127e[CPU0 ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting
I thought that an IDT wasn't necessary for getting in long mode...?

EDIT : (However, it seems that Brendan did a thing which I forgot to do : load a 64-bit GDT... Will fix it soon and see if the bug remains)
Post Reply