Implementing User Mode and Kernel Mode Switching in 64 bit UEFI OS

625 views Asked by At

I am writing a 64 Bit UEFI OS ( GNU-EFI - Bootloader ). I am wondered about User Mode and Kernel Mode in OS, I have to Implement User Mode and Kernel Mode in My OS, I found some on the Internet but It won't works for me ( I thinks it is because of 64 Bit ), So How can I do it?

I am used this:

OSDEV - Ring3

But When I implement it in my gdt, My kernel Hangs out!

My Kernel Code :

Commented are not my plain code that works these are copied from OSDEV - These Hangs Out the Kernel, SO I am Commented those.

gdt.h

// gdt.h

#pragma once
#include <stdint.h>

struct GDTDescriptor {
    uint16_t Size;
    uint64_t Offset;
} __attribute__((packed));

struct GDTEntry {
    uint16_t Limit0;
    uint16_t Base0;
    uint8_t Base1;
    uint8_t AccessByte;
    uint8_t Limit1_Flags;
    uint8_t Base2;
    
    // These are from OSDEV, if I enable these, Kernel Hangs Out

    // unsigned int Read_Write;
    // unsigned int Confirming_Expand_Down;
    // unsigned int Code;
    // unsigned int Code_data_segment;
    // unsigned int DPL;
    // unsigned int present;
    // unsigned int available;
    // unsigned int long_mode;
    // unsigned int big;
    // unsigned int gran;

}__attribute__((packed));

struct GDT {
    GDTEntry Null; //0x00
    GDTEntry KernelCode; //0x08
    GDTEntry KernelData; //0x10
    GDTEntry UserNull;
    GDTEntry UserCode;
    GDTEntry UserData;
} __attribute__((packed)) 
__attribute((aligned(0x1000)));

extern GDT DefaultGDT;

extern "C" void LoadGDT(GDTDescriptor* gdtDescriptor);

gdt.cpp

// gdt.cpp
#include "gdt.h"


__attribute__((aligned(0x1000)))
GDT DefaultGDT = {
    {0, 0, 0, 0x00, 0x00, 0}, // null
    {0, 0, 0, 0x9a, 0xa0, 0}, // kernel code segment
    {0, 0, 0, 0x92, 0xa0, 0}, // kernel data segment
    {0, 0, 0, 0x00, 0x00, 0}, // user null
    {0, 0, 0, 0x9a, 0xa0, 0}, // user code segment    
    {0, 0, 0, 0x92, 0xa0, 0}, // user data segment
};


// This is for commented vars of GDTEntry Struct

// __attribute__((aligned(0x1000)))
// GDT DefaultGDT = {
//     {0, 0, 0, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // null
//     {0, 0, 0, 0x9a, 0xa0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // kernel code segment
//     {0, 0, 0, 0x92, 0xa0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // kernel data segment

//     {0, 0, 0, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // user null
    
//     {0xFFFF, 0, 0, 0x00, 0xF, 0, 1, 0, 1, 1, 3, 1, 1, 0, 1, 1}, // user code segment

//     {0xFFFF, 0, 0, 0x00, 0xF, 0, 1, 0, 0, 1, 3, 1, 1, 0, 1, 1}, // user code segment
// };

gdt.asm

[bits 64]
LoadGDT:   
    lgdt [rdi]
    mov ax, 0x10 
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    pop rdi
    mov rax, 0x08
    push rax
    push rdi
    retfq
GLOBAL LoadGDT

How can I enter Ring3 In 64 bit UEFI OS, Then How to Switch User Mode to Kernel Mode?

1

There are 1 answers

0
Mac O'Brien On

The GDT entry struct you reference from OSDev looks like this:

struct gdt_entry_bits {
    unsigned int limit_low              : 16;
    unsigned int base_low               : 24;
    unsigned int accessed               :  1;
    unsigned int read_write             :  1; // readable for code, writable for data
    unsigned int conforming_expand_down :  1; // conforming for code, expand down for data
    unsigned int code                   :  1; // 1 for code, 0 for data
    unsigned int code_data_segment      :  1; // should be 1 for everything but TSS and LDT
    unsigned int DPL                    :  2; // privilege level
    unsigned int present                :  1;
    unsigned int limit_high             :  4;
    unsigned int available              :  1; // only used in software; has no effect on hardware
    unsigned int long_mode              :  1;
    unsigned int big                    :  1; // 32-bit opcodes for code, uint32_t stack for data
    unsigned int gran                   :  1; // 1 to use 4k page addressing, 0 for byte addressing
    unsigned int base_high              :  8;
} __packed; // or `__attribute__((packed))` depending on compiler

This struct definition makes use of bit fields; note the end of each field declaration, where there is a colon followed by the number of bits occupied by the field. Despite each field having its own type declaration, fields will be packed together according to their bit width; the sum of these bit widths for the OSDev example is 64.

Your definition as written (if you include the commented fields) is 48 bytes wide. A GDT entry should be 8 bytes (64 bits) wide: see section 3.4.5 of the Intel Software Developer's Manual. I would strongly recommend keeping the Intel manuals handy; much of the information on the OSDev wiki is out of date for x86_64, while the Intel (and AMD) manuals are continually kept up-to-date.

Regarding switching between kernel and user mode, x86_64 has dedicated instructions for system calls in SYSCALL and SYSRET. I would suggest using these instructions for switches between kernel and user mode, as e.g. the Linux kernel typically does (though it also uses IRET under some circumstances). You can jump into user code with SYSRET even if the process has not made a SYSCALL by setting up the environment correctly (see the linked instruction reference).