Page 1 of 1

[SOLVED] GDT Load Causing Triple Fault

Posted: Mon Mar 05, 2018 9:11 am
by nortega
Sorry to create another one of these, however I haven't found suitable answers in any of the others I've found on this forum.

I've been trying to setup a GDT from 32-bit protected mode, so far only with entries for kernel data and code (and, of course, the null entry). However, every time I try running it I get a triple fault. I've narrowed it down to the actual `lgdt' instruction, telling me that I've most likely set something up wrong either in the registry or in the entries themselves. So I double checked all the flags I was setting and so on and couldn't find anything that seemed out of the norm (`1001 1010' for kernel code sector, `1001 0010` for kernel data sector).

Here's what I've done so far (sorry for GAS syntax if that's not what you're used to):

Code: Select all

# boot.s
# declare constants for the multiboot header
.set ALIGN,      1 << 0            # align loaded modules on page boundaries
.set MEMINFO,    1 << 1            # provide memory map
.set FLAGS,      ALIGN | MEMINFO   # the multiboot `FLAG' field
.set MAGIC,      0x1BADB002        # 'magic number' letting the boot loader know we're here
.set CHECKSUM ,  -(MAGIC + FLAGS)  # checksum of the above to prove we're multiboot

/*
 * Declare the multiboot header marking this program as a kernel. The bootloader
 * will search for these values in the first 8 KiB of the kernel file aligned at
 * 32-bit boundaries (4 bytes). We put the signature in its own section to force
 * it within the first 8 KiB of the kernel file.
 */
.section .multiboot
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM

/*
 * Create a 16 byte aligned stack with 16 KiB of size. We create labels at the
 * bottom and top of the stack.
 */
.section .bss
.align 16
stack_bottom:
.skip 16384  # 16 KiB
stack_top:

/*
 * The linker script specifies the `_start' label as the entry point to the kernel
 * and the bootloader will jump to this position. That is, this is where the kernel
 * starts.
 */
.section .text
.global _start
.type _start, @function
_start:
	# set the position of `%esp' to the top of the stack
	mov $stack_top, %esp

	# GDT, paging, and other features
	call gdt_install

boot_kernel:
	# enter high-level kernel (C)
	call kernel_main

	# put computer into infinite loop
	cli                    # disable interrupts by clearing the interrupt flag in `eflags'
end_loop:
	hlt                    # wait for next interrupt
	jmp end_loop           # jump to the `hlt' instruction if it ever leaves it

.size _start, . - _start

.global gdt_flush
.type gdt_flush, @function
gdt_flush:
	mov -4(%esp), %eax
	lgdt (%eax)            # load GDT registry into processor
	movw $0x10, %ax        # 0x10 is the offset of the DATA segment in the GDT
	movw %ax, %ds
	movw %ax, %es
	movw %ax, %fs
	movw %ax, %gs
	movw %ax, %ss
	ljmp $0x0008, $flush_end
flush_end:
	ret

Code: Select all

/* gdt.h */
#pragma once

#include <stdint.h>
#include <stddef.h>

#define GDT_SIZE 3

// GDT registry
struct gdtr {
	uint16_t limit;
	uint32_t base;
} __attribute__((packed));

// a GDT entry
struct gdt_entry {
	uint16_t limit_low;    // bits 0-15
	uint16_t base_low;     // bits 0-15
	uint8_t base_mid;      // bits 16-23
	uint8_t access;        // access byte
	uint8_t flags;         // granularity and size flags
	uint8_t base_high;     // bits 24-31
} __attribute__((packed));

struct gdt_entry gdt[GDT_SIZE];
struct gdtr gdtr;

/*
 * Flush the GDT registry to the processor. This function is
 * defined in `boot.s'.
 */
extern void gdt_flush(struct gdtr *gdtr);
void gdt_set(size_t num, uint32_t base, uint32_t limit,
		uint8_t access, uint8_t flags);
void gdt_install();

Code: Select all

/* gdt.c */
#include "gdt.h"

#include <kernel/tty.h>
#include <kernel/system.h>

void gdt_set(size_t num, uint32_t base, uint32_t limit,
		uint8_t access, uint8_t flags) {
	if(num >= GDT_SIZE)
	{
		tty_init();
		tty_write_s("gdt_set(): num is too big. Aborting.\n");
		abort();
	}

	struct gdt_entry *entry = &gdt[num];
	// base
	entry->base_low = base & 0xFFFF;
	entry->base_mid = (base >> 16) & 0xFF;
	entry->base_high = (base >> 24) & 0xFF;

	// limit
	entry->limit_low = limit & 0xFFFF;
	entry->flags = (limit >> 16) & 0x0F;

	// access and granularity flags
	entry->flags |= flags & 0xF0;
	entry->access = access;
}

void gdt_install() {
	gdtr.limit = (sizeof(struct gdt_entry) * GDT_SIZE) - 1;
	gdtr.base = (uint32_t)&gdt;

	// null-descriptor
	gdt_set(0, 0, 0, 0, 0);

	/*
	 * Code segment entry: base address 0, limit of 4GiB,
	 * 4KiB granularity, 32-bit opcodes, code segment set.
	 */
	gdt_set(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);

	/*
	 * Data segment entry: base address 0, limit of 4GiB,
	 * 4KiB granularity, 32-bit opcodes, data segment set.
	 */
	gdt_set(2, 0, 0xFFFFFFFF, 0x92, 0xCF);

	gdt_flush(&gdtr);
}
EDITS:
- Fixed typo overwritting GDT code entry with data entry.

Re: GDT Load Causing Triple Fault

Posted: Mon Mar 05, 2018 5:30 pm
by hexcoder
Looks like you're calling 'set_gdt(1, ...)' twice, overwriting your code descriptor with the data one.

Re: GDT Load Causing Triple Fault

Posted: Tue Mar 06, 2018 3:10 am
by nortega
Thanks for the catch. However, it seems that sadly there was another issue as well. I just got done rerunning it in GDB and it continues to fail at `lgdt'. I've updated the code above with the fixes.

Re: GDT Load Causing Triple Fault

Posted: Thu Mar 08, 2018 9:09 am
by nortega
Alright, I've been fucking around a lot and finally I decided to code this directly in assembly in the `boot.s' file, and it seems to work now (probably because I wasn't setting flags correctly in C). Here's the updated code:

Code: Select all

# boot.s
# declare constants for the multiboot header
.set ALIGN,      1 << 0            # align loaded modules on page boundaries
.set MEMINFO,    1 << 1            # provide memory map
.set FLAGS,      ALIGN | MEMINFO   # the multiboot `FLAG' field
.set MAGIC,      0x1BADB002        # 'magic number' letting the boot loader know we're here
.set CHECKSUM ,  -(MAGIC + FLAGS)  # checksum of the above to prove we're multiboot

/*
 * Declare the multiboot header marking this program as a kernel. The bootloader
 * will search for these values in the first 8 KiB of the kernel file aligned at
 * 32-bit boundaries (4 bytes). We put the signature in its own section to force
 * it within the first 8 KiB of the kernel file.
 */
.section .multiboot
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM

/*
 * Create a 16 byte aligned stack with 16 KiB of size. We create labels at the
 * bottom and top of the stack.
 */
.section .bss
.align 16
stack_bottom:
.skip 16384  # 16 KiB
stack_top:

/*
 * Create the GDT
 */
.section .data
gdt_start:
gdt_null:
.long 0x0
.long 0x0

gdt_kcode:
.word 0xFFFF             # limit
.word 0x0                # base
.byte 0x0                # base
.byte 0b10011010         # 1st flags | type flags
.byte 0b11001111         # 2nd flags | limit
.byte 0x0                # base

gdt_kdata:
.word 0xFFFF             # limit
.word 0x0                # base
.byte 0x0                # base
.byte 0b10010010         # 1st flags | type flags
.byte 0b11001111         # 2nd flags | limit
.byte 0x0                # base
gdt_end:

gdtr:
.word (gdt_end - gdt_start - 1)
.long gdt_start

/*
 * The linker script specifies the `_start' label as the entry point to the kernel
 * and the bootloader will jump to this position. That is, this is where the kernel
 * starts.
 */
.section .text
.global _start
.type _start, @function
_start:
	# set the position of `%esp' to the top of the stack
	mov $stack_top, %esp

	# GDT, paging, and other features
flush_gdt:
	cli
	lgdt (gdtr)
	movw $0x10, %ax
	movw %ax, %ds
	movw %ax, %es
	movw %ax, %fs
	movw %ax, %gs
	movw %ax, %ss
	ljmp $0x08, $flush_end
flush_end:

boot_kernel:
	# enter high-level kernel (C)
	call kernel_main

	# put computer into infinite loop
	cli                    # disable interrupts by clearing the interrupt flag in `eflags'
end_loop:
	hlt                    # wait for next interrupt
	jmp end_loop           # jump to the `hlt' instruction if it ever leaves it

.size _start, . - _start

Re: [SOLVED] GDT Load Causing Triple Fault

Posted: Mon Apr 02, 2018 11:28 am
by nortega
Okay, I've been looking through it again (as well as looking through some MINIX code) and it seems one of my major mistakes was forgetting that an array is already a pointer, and therefore the line should have been:

Code: Select all

gdtr.base = (uint32_t)gdt;
//gdtr.base = (uint32_t)&gdt; //WRONG!!!!
Unfortunately this hasn't solved all my problems with writing this in C yet, but at least it's something. This also means that the following line can be simplified:

Code: Select all

gdtr.limit = sizeof(gdtr) - 1;
//gdtr.limit = (sizeof(struct gdt_entry) * GDT_SIZE) - 1; // OLD

Re: [SOLVED] GDT Load Causing Triple Fault

Posted: Wed Apr 04, 2018 10:04 am
by nortega
Okay, after a while of dealing with GDB I finally found out what the issue was with my initial C code, and now I feel rather stupid. The issue was how I was getting the parameter from the stack as well as a few other minor issues. Here's the `gdt_flush' function rewritten to work using proper calling conventions.

Code: Select all

.section .text
.global gdt_flush
.type gdt_flush, @function
gdt_flush:
	pushl %ebp
	movl %esp, %ebp
	movl 8(%ebp), %eax
	cli
	lgdt (%eax)
	movw $0x10, %ax
	movw %ax, %ds
	movw %ax, %es
	movw %ax, %fs
	movw %ax, %gs
	movw %ax, %ss
	ljmp $0x0008, $gdt_flush_end
gdt_flush_end:
	popl %ebp
	ret

Re: [SOLVED] GDT Load Causing Triple Fault

Posted: Mon Apr 23, 2018 3:41 am
by MichaelPetch
I would have believed the main problem was this:

Code: Select all

mov -4(%esp), %eax
. You don't need a frame pointer. I think you meant to have:

Code: Select all

mov 4(%esp), %eax