[SOLVED] Loading the GDT causes random restarts

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
peachsmith
Member
Member
Posts: 44
Joined: Tue Aug 26, 2014 5:07 pm

[SOLVED] Loading the GDT causes random restarts

Post by peachsmith »

Hiya.
So I'm trying to build my kernel as a 64-bit UEFI application, but it sometimes restarts when I load the GDT.
It appears to restart roughly 3 out of 10 times.
Given the random nature of the restarts, there's almost certainly some memory condition that I'm ignoring.
When I increased the RAM for qemu from the default of 128MB to 512MB, the problem still persisted.

I read that with UEFI, the kernel may be placed in some random location in memory when booting.
Could it be related to this?

Also, according to the Intel manual, the offsets of the segment registers are treated as 0 in 64-bit mode, so I just ignore them when loading my GDT.
Am I wrong to do that?

Information about my environment:
  • My compiler is a cross compiler built for the target x86_64-elf using the wiki's guide.
  • My kernel is a 64-bit UEFI application linked with GNU-EFI and running in QEMU 3.1.0 with OVMF.
  • I boot directly into my kernel by using the UEFI file path convention /EFI/BOOT/BOOTX64.EFI
  • I have not set up paging yet, which means my kernel should have the default paging that UEFI specifies (first 4GiB identity mapped).
  • I have not yet loaded my IDT or added a TSS.
  • My segment descriptors are 64-bit unsigned integers, and my GDT is an array of these values.
My code:

my implementation of LGDT:

Code: Select all

# Loads the GDT) into GDTR.
#
# Featured instructions: LGDT
#
# Params:
#   %rdi - a 16-bit number representing the number of bytes in the GDT - 1
#   %rsi - a 64-bit number representing the address of the GDT
k_lgdt:

  push %rbp
  mov %rsp, %rbp

  sub $0xA, %rsp # allocate 10 bytes on the stack

  mov %di, -0xA(%rbp)  # put the limit on the stack frame
  mov %rsi, -0x8(%rbp) # put the address of the GDT on the stack frame

  # Our stack frame now has the following structure:
  # 
  # base pointer
  # +-----------------+
  # | address of GDT  |
  # |                 |
  # |                 |
  # |                 |
  # |                 |
  # |                 |
  # |                 |
  # |                 |
  # +-----------------+
  # | size of GDT - 1 |
  # |                 |
  # +-----------------+
  # stack pointer

  lea -0xA(%rbp), %rax # load the address of our stack frame into %rax

  lgdt (%rax) # load the GDT into the GDTR

  # The author of the wiki article populated the segment selector registers with offsets of descriptors in the GDT.
  # Since this is 64-bit code, and the Intel manual says these offsets are treated as 0,
  # I'll just ignore the segment selector registers.
  
  leaveq
  retq
C code where I call the k_lgdt procedure defined above:

Code: Select all

// my type definition of a segment descriptor
typedef uint64_t seg_desc;

// the number of entries in the GDT
#define GDT_COUNT 3

// the GDT
seg_desc gdt[GDT_COUNT];

// let this compilation unit know about the assembly procedure
// that executes the LGDT instruction.
void k_lgdt(uint16_t, seg_desc*);

void k_load_gdt()
{
  gdt[0] = 0; // null descriptor

  // code segment descriptor
  // type: conforming, read, not yet accessed
  gdt[1] = build_cd_descriptor(0, 0x0FFFFF, 0x0E);

  // data segment descriptor
  // type: expand down, read/write, not yet accessed
  gdt[2] = build_cd_descriptor(0, 0x0FFFFF, 0x06);

  uint16_t limit = sizeof(gdt) - 1;

  // load the GDT
  k_lgdt(limit, gdt);
}
How I build my code and data segments:

Code: Select all

/**
 * Creates a segment descriptor which describes a 64-bit code or data
 * segment.
 *
 * Params:
 *   base - 32 bits containing the logical base of the segment
 *   limit - 20 bits containing the size of the segment in units of 4KB
 *   type - 4 bits containing the descriptor type field.
 *
 * Returns:
 *   seg_desc - a code or data segment descriptor
 */
static seg_desc build_cd_descriptor
(
  uint64_t base,
  uint64_t limit,
  uint64_t type
)
{
  // Create a segment descriptor.
  seg_desc desc = 0;

  // Set the base address.
  desc |= ((base & 0xFF000000) << 24);
  desc |= ((base & 0x00FFFFFF) << 16);

  // Set the limit.
  desc |= ((limit & 0x0F0000) << 32);
  desc |= (limit & 0x0FFFF);

  // Set the granularity to be 4KB.
  desc |= ((uint64_t)0x1 << 55);

  // Make sure the default operand size flag is 0
  // since we're in 64-bit mode.
  desc |= ((uint64_t)0x0 << 54);

  // Set the long mode flag.
  desc |= ((uint64_t)0x1 << 53);

  // Set the system use bit to 0.
  // We're not currently using this.
  desc |= ((uint64_t)0x0 << 52);

  // Set the presence flag.
  desc |= ((uint64_t)0x1 << 47);

  // Set the descriptor privilege level to 0.
  // Give us all the permissions!
  desc |= ((uint64_t)0x0 << 45);

  // Set the type flag to 1 since this descriptor
  // is describing a code or data segment.
  desc |= ((uint64_t)0x1 << 44);

  // Set the type field.
  desc |= ((type & 0x0F) << 40);

  return desc;
}
Last edited by peachsmith on Wed Nov 25, 2020 9:48 am, edited 1 time in total.
MichaelPetch
Member
Member
Posts: 797
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Loading the GDT causes random restarts

Post by MichaelPetch »

Did you disable interrupts before changing the GDT? If an interrupt came in after you load the GDT and before you set up the IDT it could fault if the selector in an IDT was no longer a 64-bit code segment.
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: Loading the GDT causes random restarts

Post by Octocontrabass »

The only thing that stands out to me is that you're temporarily misaligning the stack, but you should have interrupts disabled at this point so it shouldn't do anything bad.

Try adding "-no-reboot -d int,cpu_reset" to your QEMU command line to log the CPU state when it reboots. The log might give you some hints about what's wrong.

You're doing this after you call ExitBootServices(), right?
sj95126
Member
Member
Posts: 151
Joined: Tue Aug 11, 2020 12:14 pm

Re: Loading the GDT causes random restarts

Post by sj95126 »

This is an extremely generic answer, but if you're loading a new GDT (as opposed to reloading the same GDT with a new entry added) you would generally want to follow it with a jump into your new code segment. I don't use UEFI boot, so I don't know if you can assume that the GDT you're handed is similar to the one you create in the code you posted. If the code segment you're executing from isn't identical to the new one, you could end up with a reset.
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: Loading the GDT causes random restarts

Post by nullplan »

peachsmith wrote:

Code: Select all

# The author of the wiki article populated the segment selector registers with offsets of descriptors in the GDT.
  # Since this is 64-bit code, and the Intel manual says these offsets are treated as 0,
  # I'll just ignore the segment selector registers.
You misunderstood something there. The selector offsets are not treated as zero. The base addresses of DS, ES, and SS are treated as zero, and the length is ignored, yes, but the actual segments are not ignored. Then again, the only thing that still matters about data segments is the "present" bit. But you should load descriptors fitting your GDT into these registers anyway, or else there might currently be invalid values in there (fitting the GDT that the firmware had, but not necessarily yours), and those values might come back to haunt you in interrupt frames.
Carpe diem!
User avatar
peachsmith
Member
Member
Posts: 44
Joined: Tue Aug 26, 2014 5:07 pm

Re: Loading the GDT causes random restarts

Post by peachsmith »

So I was loading my GDT before calling ExitBootServices.
After moving the load to after calling ExitBootServices and allocating 16 bytes on my k_lgdt procedure's stack frame instead of 10, my kernel appears to boot consistently without restarting.

Here is what I saw when I added -no-reboot -d int,cpu_reset (this was written to the console before I loaded my GDT):

Code: Select all

ES =0030 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0038 0000000000000000 ffffffff 00af9a00 DPL=0 CS64 [-R-]
SS =0030 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0030 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0030 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0030 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT=     000000001f9eea98 00000047
IDT=     000000001f176018 00000fff
In regards to jumping to my code segment, the syntax for the long jmp (e.g. jmp $0x8, some_label) doesn't seem to work in 64-bit mode.
When trying that, I get "Error: number of operands mismatch for `jmp'"
MichaelPetch
Member
Member
Posts: 797
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Loading the GDT causes random restarts

Post by MichaelPetch »

peachsmith wrote:In regards to jumping to my code segment, the syntax for the long jmp (e.g. jmp $0x8, some_label) doesn't seem to work in 64-bit mode.
When trying that, I get "Error: number of operands mismatch for `jmp'"
There is no FAR JMP with 64-bit offsets (JMP ptr16:64 doesn't exist). You can do a FAR JMP indirectly through a memory operand or you can use a 64-bit FAR Return. The syntax in GNU assembler may look a bit unusual but these should work:

Code: Select all

push $0x08
push $some_label
lretq                # this is LRETQ not IRETQ
some_label:
or

Code: Select all

push $0x08
push $some_label
rex.W ljmp *(%rsp)
some_label:
add $16, %rsp
I use the stack to create the segment and offset pair but you could build it in a data segment or rodata etc like:

Code: Select all

.data
farptr64: .quad some_label, 0x08

.text
rex.W ljmp *farptr64(%rip)

# If the code and data are beyond the reaches of RIP relative addressing
# you could use absolute addressing if you are generating non-PIC code

# movabsq $farptr64, %rax
# rex.W ljmp *(%rax)

some_label:
Or you could put the FAR PTR in the code segment after the FAR JMP like this:

Code: Select all

rex.W ljmp *farptr64(%rip)
farptr64: .quad some_label, 0x08
some_label:
User avatar
peachsmith
Member
Member
Posts: 44
Joined: Tue Aug 26, 2014 5:07 pm

Re: Loading the GDT causes random restarts

Post by peachsmith »

So adding the following to my k_lgdt procedure resulted in the linker error "relocation R_X86_64_32S against `.text' can not be used when making a shared object; recompile with -fPIC"

Code: Select all

  # Put the offset of our code segment in CS?
  push $0x8
  push $end_gdt
  lretq

end_gdt:
  leaveq
  retq
Unfortunately, -fPIC doesn't appear to be an option for the assembler, and running $TARGET-as --help doesn't show any information about position-independent code.
MichaelPetch
Member
Member
Posts: 797
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Loading the GDT causes random restarts

Post by MichaelPetch »

I forgot you were using UEFI. This code may work though (since the label you are jumping to is in the code near the JMP):

Code: Select all

  push $0x08
  lea end_gdt(%rip), %rax
  push %rax
  lretq
end_gdt:
Using RIP relative addressing should work.
User avatar
peachsmith
Member
Member
Posts: 44
Joined: Tue Aug 26, 2014 5:07 pm

Re: Loading the GDT causes random restarts

Post by peachsmith »

lea end_gdt(%rip), %rax
Well, it worked, but I feel dirty for manipulating the instruction pointer.
But this experience forced me to learn about RIP relative addressing. Apparently that's a thing in 64-bit mode.

I don't know how to edit the title of the thread, but if I did, I would prepend [SOLVED] to it.
Thanks for your help.

Summary:
I was loading my GDT before calling ExitBootServices (moved to after ExitBootServices)
I created a stack frame that was not a multiple of 16 (probably not causing the problem, but better safe than sorry)
I was not setting the visible portion of the segment selector registers (set DS, ES, FS, GS, and SS with MOV, set CS with LRET)

My updated GDT loading procedure (with excess comments stripped out):

Code: Select all

k_lgdt:

  push %rbp
  mov %rsp, %rbp

  sub $0x10, %rsp       # allocate 16 bytes on the stack

  mov %di, -0x10(%rbp)  # limit (number of bytes in the GDT - 1)
  mov %rsi, -0xE(%rbp)  # address of the GDT

  lea -0x10(%rbp), %rax # address of the stack frame

  lgdt (%rax)           # load the GDT into the GDTR

  # Put the offset of the data segment in DS, ES, FS, GS, and SS
  mov $0x10, %ax
  mov %ax, %ds
  mov %ax, %es
  mov %ax, %fs
  mov %ax, %gs
  mov %ax, %ss
  
  # Put the offset of the code segment into CS.
  push $0x8
  lea end_gdt(%rip), %rax # RIP relative addressing
  push %rax
  lretq

end_gdt:
  leaveq
  retq
nexos
Member
Member
Posts: 1081
Joined: Tue Feb 18, 2020 3:29 pm
Libera.chat IRC: nexos

Re: Loading the GDT causes random restarts

Post by nexos »

peachsmith wrote:I don't know how to edit the title of the thread
Hit the edit button on the first post, and then change the title of that post :)
"How did you do this?"
"It's very simple — you read the protocol and write the code." - Bill Joy
Projects: NexNix | libnex | nnpkg
User avatar
peachsmith
Member
Member
Posts: 44
Joined: Tue Aug 26, 2014 5:07 pm

Re: Loading the GDT causes random restarts

Post by peachsmith »

nexos wrote:
peachsmith wrote:I don't know how to edit the title of the thread
Hit the edit button on the first post, and then change the title of that post :)
Ah, I should have noticed that.
Thanks.
Post Reply