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?
The GDT entry struct you reference from OSDev looks like this:
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
andSYSRET
. I would suggest using these instructions for switches between kernel and user mode, as e.g. the Linux kernel typically does (though it also usesIRET
under some circumstances). You can jump into user code withSYSRET
even if the process has not made aSYSCALL
by setting up the environment correctly (see the linked instruction reference).