Home > Blockchain >  QEMU triple faulting on sti instruction
QEMU triple faulting on sti instruction

Time:12-15

Before I begin: No, this question is not a duplicate of this one.

Hello! I've been doing a lot of operating system development and recently encountered a large problem. When I do the instruction sti in C (asm volatile ("sti")), QEMU triple faults and reboots. I've done a lot of troubleshooting, and I don't know why. Can you please help me out?

Here is the code:

Main kernel code:

#include "include/kernel.h" // Kernel header file


// updateBottomText() - A kernel function to make handling the beginning graphics easier.
void updateBottomText(char *bottomText) {
    if (strlen(bottomText) > INT_MAX) return -1; // Overflow
    
    updateTerminalColor(vgaColorEntry(COLOR_BLACK, COLOR_LIGHT_GRAY));

    for (int i = 0; i < SCREEN_WIDTH - 1; i  ) terminalPutcharXY(' ', vgaColorEntry(COLOR_BLACK, COLOR_LIGHT_GRAY), i, SCREEN_HEIGHT - 1);
    terminalWriteStringXY(bottomText, 0, SCREEN_HEIGHT - 1);


    updateTerminalColor(vgaColorEntry(COLOR_WHITE, COLOR_CYAN));
    return;
}







void kmain() {
    initTerminal(); // Initialize the terminal and clear the screen
    updateTerminalColor(vgaColorEntry(COLOR_BLACK, COLOR_LIGHT_GRAY)); // Update terminal color

    printf("reduceOS v1.0 - Development Build");
    for (int i = 0; i < (SCREEN_WIDTH - strlen("Test OS v1.0 - Development Build")); i  ) printf(" ");

    updateTerminalColor(vgaColorEntry(COLOR_WHITE, COLOR_CYAN));
    printf("Test OS is loading, please wait...\n");

    updateBottomText("Initializing GDT...");
    gdtInitialize();


    updateBottomText("Initializing IDT...");
    idtInit(0x8);
    

    uint32_t vendor[32];
    
    memset(vendor, 0, sizeof(vendor));
    
    __cpuid(0x80000002, (uint32_t *)vendor 0x0, (uint32_t *)vendor 0x1, (uint32_t *)vendor 0x2, (uint32_t *)vendor 0x3);
    __cpuid(0x80000003, (uint32_t *)vendor 0x4, (uint32_t *)vendor 0x5, (uint32_t *)vendor 0x6, (uint32_t *)vendor 0x7);
    __cpuid(0x80000004, (uint32_t *)vendor 0x8, (uint32_t *)vendor 0x9, (uint32_t *)vendor 0xa, (uint32_t *)vendor 0xb);
    
    printf("CPU Vendor: %s\n", vendor);
    
    
    
    updateBottomText("Initializing PIT...");
    i86_pitInit();
    i86_pitStartCounter(100, I86_PIT_OCW_COUNTER_0, I86_PIT_OCW_MODE_SQUAREWAVEGEN);

    updateBottomText("Initializing PIC...");
    i86_picInit(0x20, 0x28);
    asm volatile ("sti")
    
}

GDT code:

// =====================================================================
// gdt.c - Global Descriptor Table
// This file handles setting up the Global Descriptor Table (GDT)
// =====================================================================

#include "include/gdt.h" // The GDT header file


// The global descriptor table is an array of descriptors.
gdt_descriptor _gdt [MAX_DESCRIPTORS];

// GDTR data (this will be passed to the ASM lgdt call)
gdtr _gdtr;




static void installGDT() {
    __asm__ ("lgdt %0" :: "m"(_gdtr));
}

// gdtSetDescriptor(uint32_t i, uint32_t base, uint32_t limit, uint8_t access, uint8_t grand) - Sets up a descriptor in the GDT
void gdtSetDescriptor(uint32_t i, uint32_t base, uint32_t limit, uint8_t access, uint8_t grand) {
    //if (i > MAX_DESCRIPTORS) return; // Can't have more than 3 descriptors.
    // memset((void*)&_gdt[i], 0, sizeof(gdt_descriptor)); // We need to null out the descriptor to set proper values.

    gdt_descriptor *this = &_gdt[i];

    this->baseLow = base & 0xFFFF; // Setting up baseLow, baseMid, baseHigh, and limit (all the addresses).
    this->baseMid = (base >> 16) & 0xFF;
    this->baseHigh = (base >> 24 & 0xFF);
    this->limit = limit & 0xFFFF;

    this->flags = access; // Setting up access flags and the grandularity.
    this->grand = (limit >> 16) & 0x0F;
    this->grand = this->grand | (grand & 0xF0);

}


// i86_gdtGetDescriptor(int i) - returns a descriptor from GDT
gdt_descriptor* i86_gdtGetDescriptor(int i) {
    if (i > MAX_DESCRIPTORS) return 0;

    return &_gdt[i];
}

// gdtInitialize() - Initalize GDT
int gdtInitialize() {
    // First, we need to setup _gdtr
    _gdtr.m_limit = sizeof(_gdt) - 1;
    _gdtr.m_base = (uint32_t) _gdt;

    // Next, we set null descriptor.
    gdtSetDescriptor(0, 0, 0, 0, 0);

    // Now, we set the default code descriptor.
    gdtSetDescriptor(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);

    // Set the data segment
    gdtSetDescriptor(2, 0, 0xFFFFFFFF, 0x92, 0xCF);

    gdtSetDescriptor(3, 0, 0xFFFFFFFF, 0xFA, 0xCF);
    gdtSetDescriptor(4, 0, 0xFFFFFFFF, 0xF2, 0xCF);
    
    // Finally, install _gdtr into the CPU's gdtr register.
    installGDT();

    printf("GDT initialized.\n");
    return 0;
}

GDT header file

// gdt.h - header file for gdt.c
// Sets up & handles the Global Descriptor Table (GDT) 

// gdt.h is slightly similar to idt.h in the structures and misc. stuff


#ifndef GDT_H
#define GDT_H


// Includes
#include "include/libc/stdint.h"
#include "include/terminal.h"

// Definitions
#define MAX_DESCRIPTORS 8 // The maximum amount of descriptors allowed

// GDT Descriptor access bit flags
#define I86_GDT_DESC_ADDRESS        0x0001 // Set access bit - 00000001
#define I86_GDT_DESC_READWRITE      0x0002 // Descriptor is RW. Default is read only. - 00000010
#define I86_GDT_DESC_EXPANSION      0x0004 // Set expansion director bit - 00000100
#define I86_GDT_DESC_EXEC_CODE      0x0008 // Executable code segment - 00001000
#define I86_GDT_DESC_CODEDATA       0x0010 // Set code or data descriptor - 00010000
#define I86_GDT_DESC_DPL            0x0060 // Set DPL bits - 01100000
#define I86_GDT_DESC_MEMORY         0x0080 // Set in memory bit

// GDT descriptor granularity bit flags

#define I86_GDT_GRAND_LIMITHI_MASK      0x0f // Masks out limitHi (High 4 bits of limit) - 00001111
#define I86_GDT_GRAND_OS                0x10 // Set OS defined bit - 00010000
#define I86_GDT_GRAND_32BIT             0x40 // Set if 32-bit, default is 16-bit - 01000000
#define I86_GDT_GRAND_4K                0x80 // 4k granularity. Default: none - 10000000


// Typedefs


// GDT descriptor - defines the properties of a specific memory block and permissions
typedef struct {
    uint16_t limit; // Bits 0-15 of the segment limit
    uint16_t baseLow; // Bits 0-15 of the base address
    uint8_t baseMid; // Bits 16-23 of the base address
    uint16_t baseHigh; // Bits 24-32 of the base address
    uint8_t flags; // Descriptor access flags
    uint8_t grand;
} gdt_descriptor;

// GDTR - This will go into the processor's GDTR register.
typedef struct {
    uint16_t m_limit; // Size of GDT
    uint32_t m_base; // Base address of GDT.
} gdtr;


// Functions

extern void gdtSetDescriptor(uint32_t i, uint32_t base, uint32_t limit, uint8_t access, uint8_t grand); // Setup a descriptor in the GDT
extern gdt_descriptor* i86_gdtGetDescriptor(int i); // Returns a descriptor.
extern int gdtInitialize(); // Initialize the GDT


#endif

IDT code

// =====================================================================
// idt.c - Interrupt Descriptor Table
// This file handles setting up the Interrupt Descriptor Table (IDT)
// =====================================================================

#include "include/idt.h" // IDT header file

typedef struct {
    uint16_t limit; // size of the IDT
    uint32_t base_addr; // base address of the IDT
} __attribute__((packed)) IDT_PTR; 




IDT _idt[I86_MAX_INTERRUPTS];
IDT_PTR _idtptr;


static void installIDT(); // Installs IDT_PTR into the CPU's idtr register
static void i86DefaultHandler(); // Default interrupt handler used to catch unregistered interrupts

// Installs IDT_PTR into the CPU's idtr register
static void installIDT() {
    __asm__ ("lidt %0" :: "m"(_idtptr));
}

// i86DefaultHandler() - Handles all interrupts
static void i86DefaultHandler() {
    panic("i86", "i86DefaultHandler", "Unhandled exception"); // Kernel panic
    for(;;); // Infinite loop. Unreachable.
}


IDT* idtGetIR(uint32_t i) {
    if (i < I86_MAX_INTERRUPTS) return 0;
    return &_idt[i]; // Return the IR function
}


int idtInstallIR(int i, uint8_t flags, uint16_t segmentSelector, uint32_t base) {
    if (i < 0 || i >= I86_MAX_INTERRUPTS) return -1; // This would throw an out of bounds exception if it was.

    IDT *idtTemp = &_idt[i];


    idtTemp->baseLow = base & 0xFFFF; // Set base low
    idtTemp->baseHigh = (base >> 16) & 0xFFFF; // Set base highidtInstallIR
    idtTemp->reserved = 0; // Reserved should always be 0!
    idtTemp->flags = flags; // Set the flags
    idtTemp->segmentSelector = segmentSelector; // Set the segment selector

    return 0;
}



int idtInit(uint16_t segmentSelector) {

    // Setup the idtr (_idtptr) for the processor.
    _idtptr.limit = sizeof(_idt) - 1;
    _idtptr.base_addr = (uint32_t)&_idt[0];



    // Register the default handlers
    for (int i = 0; i <= I86_MAX_INTERRUPTS; i  ) {
        idtInstallIR(i, I86_IDT_DESC_PRESENT | I86_IDT_DESC_BIT32, segmentSelector, (IDT_IRQ_HANDLER)i86DefaultHandler);
    }

    installIDT(); // Install IDT

    printf("IDT initialized.\n");
    return 0;
}

IDT header file

// idt.h - IDT header file, contains includes/declarations for the idt.c file.

#ifndef IDT_H
#define IDT_H

// Includes
#include "include/libc/stddef.h" // size_t declaration
#include "include/libc/stdint.h" // Integer type declarations
#include "include/libc/stdbool.h" // Boolean declarations
#include "include/libc/string.h" // String functions
#include "include/libc/limits.h" // Limits on integers and more.
#include "include/libc/stdarg.h" // va argument handling (for ... on printf)
#include "include/libc/va_list.h" // va_list declared here.
#include "include/panic.h" // panic function for unregistered interrupt handler
#include "include/terminal.h" // printf() and other terminal functions
#include "include/isr.h" // Interrupt Service Routines


// Definitions

#define I86_MAX_INTERRUPTS 256 // 256 maximum interrupts

// These must be in the format 0D110 where D is the descriptor type
#define I86_IDT_DESC_BIT16 0x06 // 00000110
#define I86_IDT_DESC_BIT32 0x0E // 00001110
#define I86_IDT_DESC_RING1 0x40 // 01000000
#define I86_IDT_DESC_RING2 0x20 // 00100000
#define I86_IDT_DESC_RING3 0x60 // 01100000
#define I86_IDT_DESC_PRESENT 0x80 // 10000000


// Typedefs

typedef void (*IDT_IRQ_HANDLER)(void); // Interrupt handler without an error code. Interrupt handlers are called by the processor. Since the stack setup may change, we leave it up to the interrupts' implementation to handle it and properly return.

typedef struct {
    uint16_t baseLow; // Lower 16 bits (0-15) of the interrupt routine address (address to jump when interrupt fires.)
    uint16_t segmentSelector; // Code segment selector in GDT
    uint8_t reserved; // Reserved. Should be 0.
    uint8_t flags; // Bit flags.
    uint16_t baseHigh; // Higher 16 bits (16-31) of the interrupt routine address (see baseLow)
} __attribute__((packed)) IDT;



// Functions

extern IDT* idtGetIR(uint32_t i); // Returns interrupt descriptor
extern int idtInstallIR(int i, uint8_t flags, uint16_t segmentSelector, uint32_t base); // Installs interrupt handler. When INT is fired, it calls this
extern int idtInit(uint16_t segmentSelector); // Initializes basic IDT.



#endif

Thank you for all help! Anything is appreciated.

Edit 1: Output from QEMU -d cpu

EAX=00000000 EBX=00000820 ECX=000001e5 EDX=000021c4
ESI=00000692 EDI=00000000 EBP=00000000 ESP=0008fffc
EIP=00001005 EFL=00000216 [----AP-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     4c400000 0000004f
IDT=     00004420 000007ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000094 CCD=0008fff0 CCO=ADDL
EFER=0000000000000000

CodePudding user response:

I managed to fix it. I'm pretty new with this stuff and I wasn't sure how to do it.

Basically I was loading GDT twice. Once in the boot loader and once in the kernel. I don’t know why I thought I needed to, but it’s okay.

Thank you to everyone who tried to help me!

  • Related