[SOLVED] Loading the GDT causes random restarts
Posted: Sat Nov 21, 2020 8:44 pm
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 implementation of LGDT:
C code where I call the k_lgdt procedure defined above:
How I build my code and data segments:
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 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
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);
}
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;
}