finished gdt mechanisms

This commit is contained in:
Chris
2026-04-10 21:05:46 +00:00
parent c4bed9000d
commit d3d8b973c0
14 changed files with 256 additions and 2 deletions

1
.gitignore vendored
View File

@@ -18,3 +18,4 @@ src/group_name/src/kernel.c
*.pdf
devcontainer.json
.devcontainer/devcontainer.json
docs/

View File

@@ -4,11 +4,21 @@
"command": "/usr/bin/nasm -I/workspaces/AdvOpSys/src/OSDev_18/include -f elf32 -o CMakeFiles/uiaos-kernel.dir/src/multiboot2.asm.o /workspaces/AdvOpSys/src/OSDev_18/src/multiboot2.asm",
"file": "/workspaces/AdvOpSys/src/OSDev_18/src/multiboot2.asm"
},
{
"directory": "/workspaces/AdvOpSys/build/OSDev_18",
"command": "/usr/bin/nasm -I/workspaces/AdvOpSys/src/OSDev_18/include -f elf32 -o CMakeFiles/uiaos-kernel.dir/src/arch/i386/gdt_flush.asm.o /workspaces/AdvOpSys/src/OSDev_18/src/arch/i386/gdt_flush.asm",
"file": "/workspaces/AdvOpSys/src/OSDev_18/src/arch/i386/gdt_flush.asm"
},
{
"directory": "/workspaces/AdvOpSys/build/OSDev_18",
"command": "/usr/local/bin/i686-elf-gcc -I/workspaces/AdvOpSys/src/OSDev_18/include -g -Wall -Wextra -nostdinc -nostdlib -fno-builtin -fno-stack-protector -fno-stack-check -fno-lto -fPIE -m32 -march=i386 -mno-mmx -mno-sse -mno-sse2 -mno-red-zone -Wno-main -g -Wno-unused-variable -Wno-unused-parameter -std=gnu99 -o CMakeFiles/uiaos-kernel.dir/src/kernel.c.o -c /workspaces/AdvOpSys/src/OSDev_18/src/kernel.c",
"file": "/workspaces/AdvOpSys/src/OSDev_18/src/kernel.c"
},
{
"directory": "/workspaces/AdvOpSys/build/OSDev_18",
"command": "/usr/local/bin/i686-elf-gcc -I/workspaces/AdvOpSys/src/OSDev_18/include -g -Wall -Wextra -nostdinc -nostdlib -fno-builtin -fno-stack-protector -fno-stack-check -fno-lto -fPIE -m32 -march=i386 -mno-mmx -mno-sse -mno-sse2 -mno-red-zone -Wno-main -g -Wno-unused-variable -Wno-unused-parameter -std=gnu99 -o CMakeFiles/uiaos-kernel.dir/src/gdt.c.o -c /workspaces/AdvOpSys/src/OSDev_18/src/gdt.c",
"file": "/workspaces/AdvOpSys/src/OSDev_18/src/gdt.c"
},
{
"directory": "/workspaces/AdvOpSys/build/OSDev_18",
"command": "/usr/local/bin/i686-elf-gcc -I/workspaces/AdvOpSys/src/OSDev_18/include -g -Wall -Wextra -nostdinc -nostdlib -fno-builtin -fno-stack-protector -fno-stack-check -fno-lto -fPIE -m32 -march=i386 -mno-mmx -mno-sse -mno-sse2 -mno-red-zone -Wno-main -g -Wno-unused-variable -Wno-unused-parameter -std=gnu99 -o CMakeFiles/uiaos-kernel.dir/src/terminal.c.o -c /workspaces/AdvOpSys/src/OSDev_18/src/terminal.c",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,97 @@
# Summary of Work Done on April 10, 2026
## Overview
Today, work was done on the `OSDev_18` project to improve the kernel startup sequence and to make sure the kernel still boots correctly and prints output to the screen. The main focus was to add a minimal Global Descriptor Table (GDT) setup for the i386 version of the kernel and verify that it works as expected.
The project is set up to use a dev container, so the build and test steps were carried out in that environment. This was necessary because the container already includes the required cross-compilation and debugging tools.
## GDT Implementation
A minimal GDT was added for 32-bit protected mode. The implementation uses three entries:
1. A null descriptor
2. A kernel code segment descriptor
3. A kernel data segment descriptor
The code and data segments were configured with:
- base address `0x00000000`
- effective 4 GiB address space
- 4 KiB granularity
- ring 0 privilege level
The GDT setup code builds these descriptors in memory and prepares a GDT descriptor that can be loaded by the CPU.
After that, an assembly routine loads the new GDT with `lgdt`, performs a far jump to reload the code segment register, and then reloads the remaining segment registers for data access. This step is important because loading the GDT alone does not automatically update the segment registers already cached by the processor.
## Kernel Startup Changes
The kernel startup code was updated so that the GDT is initialized before the terminal is set up. After the GDT setup is finished, the terminal initialization still runs and the kernel prints `Hello, World!` to the VGA text buffer.
This means the boot flow now works roughly like this:
1. The bootloader transfers control to the kernel entry point.
2. The kernel starts execution in C code.
3. The GDT is initialized and loaded.
4. Segment registers are reloaded.
5. The VGA terminal is initialized.
6. `Hello, World!` is printed.
7. The kernel enters an infinite halt loop.
## Build System Changes
The build configuration was updated so the kernel now includes both the new C source for the GDT logic and the new assembly source for the reload routine. This ensures that the GDT implementation is compiled and linked into the kernel binary together with the rest of the startup code.
New build artifacts were generated today, including an updated kernel binary and bootable ISO image.
## Documentation Work
The project documentation was also improved today. Short explanations were added to describe:
- the general kernel boot flow
- where the GDT setup happens
- why `lgdt` is not enough on its own
- why a far jump and segment-register reload are required after loading the GDT
This was done to make the code easier to follow and to connect the implementation more clearly to the assignment requirements.
## Verification and Testing
The GDT implementation was tested in two ways.
### 1. Static inspection of the kernel binary
The built kernel binary was inspected to confirm that the expected GDT-related symbols were present and that the generated machine code actually contained:
- a call to load the GDT
- a far jump to selector `0x08`
- reloads of the data segment registers using selector `0x10`
This showed that the expected GDT setup sequence had been compiled into the kernel.
### 2. Runtime debugging in QEMU with GDB
The kernel was then started in QEMU and examined with `gdb-multiarch`. During this session, execution was stopped inside the GDT initialization code and inside the assembly reload routine.
The following results were observed:
- the GDT descriptor size was `0x17`, which matches three 8-byte entries minus 1
- the code segment entry was populated with the expected values
- after the far jump, the `cs` register changed to `0x08`
- after stepping through the segment reload instructions, `ds`, `es`, `fs`, `gs`, and `ss` all became `0x10`
These checks show that the processor did not just compile the code correctly, but also used the new GDT correctly at runtime.
## Current State at the End of the Day
At the end of todays work, the `OSDev_18` kernel:
- builds inside the dev container
- includes a minimal i386 GDT implementation
- loads the GDT during kernel startup
- reloads the code and data segment registers correctly
- still initializes the VGA terminal
- still prints `Hello, World!` after boot
Based on the testing that was done today, the GDT implementation appears to be working correctly for this minimal protected-mode kernel setup.

View File

@@ -58,11 +58,14 @@ set(OS_KERNEL_LINKER "${CMAKE_CURRENT_SOURCE_DIR}/src/arch/${OS_ARCH_TARGET}/lin
# Add executable target for the kernel
add_executable(uiaos-kernel
src/multiboot2.asm # TODO: Add multiboot2 support
src/multiboot2.asm
src/arch/i386/gdt_flush.asm
src/kernel.c
src/terminal.c
src/gdt.c
src/terminal.c
)
# Include directories for the kernel target
target_include_directories(uiaos-kernel PUBLIC include)

View File

@@ -0,0 +1,23 @@
# Kernel Source Overview
This directory contains the core source files for the 32-bit kernel.
## Boot And Execution Flow
1. The bootloader transfers control to `_start` in `multiboot2.asm`.
2. `_start` sets up a stack and calls `main` in `kernel.c`.
3. `main` initializes the Global Descriptor Table (GDT).
4. After the GDT is loaded and segment registers are reloaded, the kernel initializes the VGA text terminal.
5. The kernel prints `Hello, World!` and then halts in an infinite loop.
## GDT Files
- `gdt.c` creates the three required GDT entries:
- null descriptor
- kernel code descriptor
- kernel data descriptor
- `arch/i386/gdt_flush.asm` loads the GDT with `lgdt` and reloads the segment registers.
## Terminal Output
`terminal.c` writes directly to VGA text memory at `0xB8000`, which is why the kernel can print text without a standard library or drivers.

View File

@@ -0,0 +1,26 @@
#ifndef KERNEL_GDT_H
#define KERNEL_GDT_H
#include <libc/stdint.h>
#define GDT_NULL_SELECTOR 0x00
#define GDT_CODE_SELECTOR 0x08
#define GDT_DATA_SELECTOR 0x10
struct GdtEntry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_middle;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
} __attribute__((packed));
struct GdtDescriptor {
uint16_t size;
uint32_t offset;
} __attribute__((packed));
void gdtInitialize(void);
#endif

View File

@@ -0,0 +1,30 @@
# i386 Architecture Notes
This kernel runs in 32-bit protected mode on i386.
## Global Descriptor Table
The kernel installs a minimal Global Descriptor Table (GDT) with three entries:
1. A null descriptor
2. A kernel code descriptor
3. A kernel data descriptor
The code and data descriptors both use:
- base address `0x00000000`
- effective 4 GiB address space
- 4 KiB granularity
- ring 0 privilege level
## Why `lgdt` Is Not Enough
`lgdt` only loads the processor's GDT register. It does not automatically refresh the segment registers that are already cached by the CPU.
Because of that, the kernel must:
1. Execute `lgdt`
2. Perform a far jump to reload `cs`
3. Reload `ds`, `es`, `fs`, `gs`, and `ss` with the new data selector
That reload sequence is implemented in `gdt_flush.asm`.

View File

@@ -0,0 +1,22 @@
%define GDT_CODE_SELECTOR 0x08
%define GDT_DATA_SELECTOR 0x10
global gdtFlush
section .text
bits 32
gdtFlush:
mov eax, [esp + 4]
lgdt [eax]
jmp GDT_CODE_SELECTOR:.reload_cs
.reload_cs:
mov ax, GDT_DATA_SELECTOR
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
ret

38
src/OSDev_18/src/gdt.c Normal file
View File

@@ -0,0 +1,38 @@
#include <kernel/gdt.h>
static struct GdtEntry gdtEntries[3];
static struct GdtDescriptor gdtDescriptor;
extern void gdtFlush(uint32_t gdtDescriptorAddress);
static void gdtSetEntry(
uint32_t index,
uint32_t base,
uint32_t limit,
uint8_t access,
uint8_t granularity
) {
gdtEntries[index].base_low = (uint16_t)(base & 0xFFFF);
gdtEntries[index].base_middle = (uint8_t)((base >> 16) & 0xFF);
gdtEntries[index].base_high = (uint8_t)((base >> 24) & 0xFF);
gdtEntries[index].limit_low = (uint16_t)(limit & 0xFFFF);
gdtEntries[index].granularity = (uint8_t)((limit >> 16) & 0x0F);
gdtEntries[index].granularity |= (uint8_t)(granularity & 0xF0);
gdtEntries[index].access = access;
}
void gdtInitialize(void) {
gdtDescriptor.size = sizeof(gdtEntries) - 1;
gdtDescriptor.offset = (uint32_t)&gdtEntries;
gdtSetEntry(0, 0, 0, 0, 0);
// Kernel code segment: base 0, 4 GiB span, ring 0, executable, readable
gdtSetEntry(1, 0, 0x000FFFFF, 0x9A, 0xCF);
// Kernel data segment: base 0, 4 GiB span, ring 0, writable
gdtSetEntry(2, 0, 0x000FFFFF, 0x92, 0xCF);
gdtFlush((uint32_t)&gdtDescriptor);
}

View File

@@ -1,7 +1,10 @@
#include <libc/stdint.h>
#include <kernel/terminal.h>
#include <kernel/gdt.h>
void main(void) {
gdtInitialize();
terminalInitialize();
terminalWriteString("Hello, World!\n");

View File

@@ -1,3 +1,4 @@
#include <kernel/gdt.h>
#include <kernel/terminal.h>
const size_t VGA_HEIGHT = 25;