[SOLVED] GDT Load Causing Triple Fault

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
nortega
Posts: 6
Joined: Mon Mar 05, 2018 3:07 am
Libera.chat IRC: nortega
Contact:

[SOLVED] GDT Load Causing Triple Fault

Post 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.
Last edited by nortega on Thu Mar 08, 2018 9:10 am, edited 3 times in total.
hexcoder
Posts: 15
Joined: Thu Oct 20, 2016 12:26 pm

Re: GDT Load Causing Triple Fault

Post by hexcoder »

Looks like you're calling 'set_gdt(1, ...)' twice, overwriting your code descriptor with the data one.
nortega
Posts: 6
Joined: Mon Mar 05, 2018 3:07 am
Libera.chat IRC: nortega
Contact:

Re: GDT Load Causing Triple Fault

Post 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.
nortega
Posts: 6
Joined: Mon Mar 05, 2018 3:07 am
Libera.chat IRC: nortega
Contact:

Re: GDT Load Causing Triple Fault

Post 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
nortega
Posts: 6
Joined: Mon Mar 05, 2018 3:07 am
Libera.chat IRC: nortega
Contact:

Re: [SOLVED] GDT Load Causing Triple Fault

Post 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
nortega
Posts: 6
Joined: Mon Mar 05, 2018 3:07 am
Libera.chat IRC: nortega
Contact:

Re: [SOLVED] GDT Load Causing Triple Fault

Post 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
MichaelPetch
Member
Member
Posts: 799
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: [SOLVED] GDT Load Causing Triple Fault

Post 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
Post Reply