Page 1 of 1

Problem Setting Up GDT

Posted: Sat May 06, 2017 11:43 pm
by MuchLearning
I'm trying to set up my GDT table, but upon doing so the system crashes and goes into an infinite loop as it infinitely reboots and tries again. I based my implementation on the OSDev wiki tutorial and MINIX's implementation.

I have a header that defines some packed structs in the same way as MINIX, a source file that sets up the data structures, and an assembly function that loads the GDT and sets up the special registers. I'm pretty sure the problem is with the assembly function that actually loads the GDT, but I've included everything just in case.

How do we know that the correct segment selector to call flush_segments from is 0x08, and why are we setting all other segments to 0x10 now?

Code: Select all

        .intel_syntax noprefix

        .global x86_lgdt
        .type x86_lgdt, @function
x86_lgdt:
        lgdt [esp+4]
        jmp 0x08:flush_segments
flush_segments:
        mov ax, 0x10
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov ss, ax
        ret

Code: Select all

#ifndef _GDT_KERNEL_H
#define _GDT_KERNEL_H

#include <stdint.h>

struct GdtEntry {
  uint16_t limit_low;
  uint16_t base_low;
  uint8_t base_middle;
  uint8_t access;      /* |P|Pr|1|X|D|W|A|  1 indicates that this is code/data */
  uint8_t gran;        /* |G|S|0|0|LMIT|  */
  uint8_t base_high;
}__attribute__((packed));

/*
  P   - Present bit
  Pr  - Privilege bits, 0-3
  X   - Executable bit
  D   - Direction bit for data, 0 grows up, 1 grows down
      - Conforming bit for code, 0 can only be executed by priv set in Pr.
  W   - Read/Write. 1 enables reading for code segments and write access for data.
  A   - Access bit used by CPU. Set it to 0.

  G   - Granularity bit. 0 for limit in 1B blocks. 1 if limit in 4KB blocks (pages)
  S   - Size bit. 0 for 16-bit mode, 1 for 32-bit mode
*/

struct GdtR {
  uint16_t limit;
  uint32_t base;
}__attribute__((packed));

void setup_gdt(void);

#endif

Code: Select all

#include <kernel/gdt.h>
#include <kernel/gdt_asm.h>

#include <string.h>
#include <stdio.h>

#define TYPE_CODE 14
#define TYPE_DATA 6
#define PRIV_KERN 0
#define PRIV_USER 3

struct GdtEntry gdt_entries[5];
struct GdtR gdt;

uint8_t createAccess(uint8_t privilege, uint8_t type) {
  /* Entry will always be present. 0x90 is 1001 0000b */
  return (uint8_t) ((0x90) | (privilege << 5) | (type));
}

void setEntry(uint32_t base, uint32_t limit, uint8_t access, struct GdtEntry* entry) {
  entry->base_low = base & 0xFFFF; //lower 16 bits
  entry->base_middle = (base << 8) >> 24;
  entry->base_high = base >> 24;

  entry->limit_low = limit & 0xFFFF;
  entry->gran = (12 << 4) | (uint8_t)(limit << 12 >> 28);
  /* 12 in highest 4 bits sets granularity bit (setting 4KB blocks)
       and Size bit (set 32-bit mode) */

  entry->access = access;
}

void setup_gdt(void) {
  printf("BEGIN GDT SETUP\n");

  memset(gdt_entries, 0, sizeof(gdt_entries));

  gdt.base = (uint32_t) &gdt_entries;
  gdt.limit = sizeof(gdt_entries) - 1;

  printf("Addr: 0x%x\nBase: 0x%x\nLim: 0x%x\n", &gdt, gdt.base, gdt.limit);

  uint8_t kern_code = createAccess(PRIV_KERN, TYPE_CODE);
  uint8_t kern_data = createAccess(PRIV_KERN, TYPE_DATA);
  uint8_t user_code = createAccess(PRIV_USER, TYPE_CODE);
  uint8_t user_data = createAccess(PRIV_USER, TYPE_DATA);

  //setEntry(0, 0, 0, &gdt_entries[0]);//<--this should be zero'd from memset
  setEntry(0, 0x000FFFFF, kern_code, &gdt_entries[1]);
  setEntry(0, 0x000FFFFF, kern_data, &gdt_entries[2]);
  setEntry(0, 0x000FFFFF, user_code, &gdt_entries[3]);
  setEntry(0, 0x000FFFFF, user_data, &gdt_entries[4]);


  printf("---Entry---\n");
  printf("base: 0x%x\n",(uint32_t)(
         (uint32_t)gdt_entries[1].base_high << 24 |
         (uint32_t)gdt_entries[1].base_middle) << 16 |
         (uint32_t)gdt_entries[1].base_low);
  printf("limit: 0x%x\n", (uint32_t)( (uint32_t)gdt_entries[1].gran << 16
                                       | (uint32_t)gdt_entries[1].limit_low) & 0xFFFFF );

  for (int i = 1; i < 5; i++) {
  struct GdtEntry debugp = gdt_entries[i];
  printf("----Entry%d---\n", i);
  printf("access: 0x%x\n", (uint32_t)debugp.access);
  printf("gran: 0x%x\n", (uint32_t)(debugp.gran >> 4));
  }

  // x86_lgdt((uint32_t) &gdt);
}

Code: Select all

BEGIN GDT SETUP
Addr: 0x102000
Base: 0x102020
Lim: 0x27
---Entry---
base: 0x0
limit: 0xfffff
----Entry1---
access: 0x9e
gran: 0xc
----Entry2---
access: 0x96
gran: 0xc
----Entry3---
access: 0xfe
gran: 0xc
----Entry4---
access: 0xf6
gran: 0xc

Re: Problem Setting Up GDT

Posted: Sat May 06, 2017 11:58 pm
by MuchLearning
OSDev forums are you my rubber duck?

Should

Code: Select all

lgdt [esp+4]
be

Code: Select all

mov eax, [esp+4]
lgdt [eax]
Even then there is a weird stutter that makes me think things aren't quite right...

Re: Problem Setting Up GDT

Posted: Sun May 07, 2017 12:15 am
by eryjus
MuchLearning wrote:infinitely reboots and tries again
You are getting a General Protection Fault, which is causing a Double Fault, and then a Triple Fault (and reboot).

I just happen to have my Intel Software Developer's Guide -- System Programmer's Guide vol3A open to figure 5-1... And Section 5-10 handles the discussion of what the Confirming bit means. I just happened to be refreshing my memory on this today, and I set my Kernel code to 0x9a and my Kernel data to 0x92.
MuchLearning wrote:How do we know that the correct segment selector to call flush_segments from is 0x08, and why are we setting all other segments to 0x10 now?
You should know because that is how you are setting them up. Code selector 0x08 (think of it as a byte offset into the GDT table since each entry is 8 bytes) is exactly what you are defining, and the same holds for 0x10 for kernel data.

Now as an aside, I would also recommend that you refresh your esp right after setting ss. The reason for this is that the CPU will prevent an interrupt for 1 additional instruction when you mov to ss to allow for the stack pointer to be set as well. (And, I just realized I did this backwards today... :oops: ) For me, I found it better to handle the GDT at compile time and set it all properly as soon as grub handed control to my loader -- literally the first thing done.

Re: Problem Setting Up GDT

Posted: Sun May 07, 2017 3:56 am
by lkurusa
Any particular reason you are not using inline assembly to load the GDTR ?
GCC will calculate the correct stack offset automatically for you, whereas your assembly code doesn't even seem to follow the correct calling convention (frame pointer?).

Also, while it is clear that GDT offset 0x0 will be full zero (i.e. the NULL segment), but for clarity maybe it is better to include the code that sets it to zero, instead of relying on memset.

Re: Problem Setting Up GDT

Posted: Fri May 12, 2017 3:07 am
by MuchLearning
lev wrote:Any particular reason you are not using inline assembly to load the GDTR ?
Does incompetence count as a reason? I'm looking at chaning it to inline assembly now.

Re: Problem Setting Up GDT

Posted: Fri May 12, 2017 3:37 am
by iansjack
MuchLearning wrote:Does incompetence count as a reason?
It would certainly be a good reason for trying to live up to your username.

I'm not sure that it is the best reason for posting here.