I am making My First OS.(x64)
So online resoures helped a lot but most of them were Simple like connect this and this and you have a OS or very outdated. So i also…
I am making My First OS.(x64)
So online resoures helped a lot but most of them were Simple like connect this and this and you have a OS or very outdated. So i also included AI in my research and heres what i learn.
Power button-> UEFI -> Bootloader -> Kernal -> Your Working screen
STEPS:
- I click on the Power button and the UEFI runs.
The UEFI is not something we make. Its already present in the hardware. When we turn our device on it loads its self. their are many function it does but for the simplicity you just remember it loads a .efi file. (Yes, the UEFI knows the loaction for the .efi file by default -> we can add multiple .efi files and set their perority in the UEFI menu. it also have a fall back .efi file to load)
- The .efi file loaded above is the bootloader file.
The bootloader is the one responsible to load our OS (kernal) through a file with extension .elf. its also known as GRUB which is used in linux system normally. Microsoft have one for it self also . We can make one of our own. Yes we can make a menu in this file which ask for which OS to load ( with OS is also kernal). Bootloader does other thing also but lets keep it simple for now.
- Now once our kernal is loaded from the .elf file . a function is called in the bootloader file which exits the bootloader and UEFI giving all the system access to the kernal.
Now in kernal think of it as a program file and we are in the main function. first some basic things take place in the kernal which are important like , Paging ( its a memory thing. think of it like you have a big land and you divide sections for each thing, paging does this but for programs so they dont write over each other) Intruupts ( hardware is slow so inturpts) , setup hardware (like basic drivers)
- once all this main functions things our done then other functions and services out called which make rest of the things like auth , apps , ui etc.
this it what making an OS need in a grand scheme of things. Remember many thing not discussed like shuduling , sequence etc they come later once you understand and make this.
NOW LETS START
Install WSL in windows with wsl — install and then add wsl to run it
Now download the tools you would need sudo apt update && sudo apt install gcc gnu-efi qemu-system-x86 ovmf
WE WILL MAKE THE BOOTLOADER(.efi)
Make a folder named os and inside it make a folder named boot and inside it make a file named main.c
mkdir -p os/boot && cd os/boot && gedit main.c
(these folder are to make things orgainsed.
Add this code to the main.c file
include <efi.h>
include <efilib.h>
EFI_STATUS EFIAPI efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { InitializeLib(ImageHandle, SystemTable); Print(L”Hello from the 64-bit UEFI World!\n”);
// Infinite loop so the screen doesn’t just disappear while(1);
return EFI_SUCCESS; }
Now we compile this file and link it to GUN_EFI and then convert it to EFI Binary (run below commands step by step)
gcc -I /usr/include/efi -I /usr/include/efi/x86_64 -I /usr/include/efi/protocol -fpic -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar -mno- red-zone -maccumulate-outgoing-args -c main.c -o main.o
ld -nostdlib -znocombreloc -T /usr/lib/elf_x86_64_efi.lds -shared -Bsymbolic - L /usr/lib /usr/lib/crt0-efi-x86_64.o main.o -o main.so -lgnuefi -lefi
objcopy -j .text -j .sdata -j .data -j .rodata -j .dynamic -j .dynsym -j .rel - j .rela -j .rel. -j .rela. -j .reloc — output-target efi-app-x86_64 — subsystem=10 main.so main.efi
File conversion main.c > main.o >main.so >main.efi (the bootfile)
Now we add this all to the BOOT dir. Go to the os folder first by cd ..
mkdir -p ../iso/EFI/BOOT
cp main.efi ../iso/EFI/BOOT/BOOTX64.EFI
Once our efi is added now we run by the following command.
qemu-system-x86_64 -bios /usr/share/ovmf/OVMF.fd -drive file=fat:rw:../ iso,format=raw

WITH ALL THE ABOVE > we made the bootloader file and shows its working , later will further modify this code to add more function and terminal (NOTE: this efi is automatically runned by hardware if we put it in the right file)
WE WILL MAKE THE KERNAL(.elf) & MAKING SURE OUR FULL CHAIN WORKS
Now for our .elf file (which will deal with the main os code) we will first main the kernal folder.
mkdir -p os/kernel
Now we will make the kernel.c file in it ( this c code will contain all the code which our kernel will run , like drivers , words m ui/ux everything”in future we would only need to modify it to make our os better”)
gedit kernel.c
Below is the code for it (right now this code print the Letter K through pixels) >> dont judge me i am still learning, maybe theirs a better way.
// Structure matching the bootloader layout to accept video metadata typedef struct { unsigned int* BaseAddress; unsigned long long BufferSize; unsigned int Width; unsigned int Height; unsigned int PixelsPerScanLine; } Framebuffer;
// An 8x8 bitmap representation for the letter ‘K’ // 1 represents a foreground pixel, 0 represents background const unsigned char font_K[8] = { 0b10000100, 0b10001000, 0b10010000, 0b11100000, 0b11100000, 0b10010000, 0b10001000, 0b10000100 };
// Safely plots a single pixel at (x, y) checking screen physical bounds void put_pixel(Framebuffer* fb, unsigned int x, unsigned int y, unsigned int color) { // Prevent out-of-bounds writes that could cause memory corruption panics if (x >= fb->Width || y >= fb->Height) return;
// Indexing calculation factoring in the hardware’s padding (PixelsPerScanLine) fb->BaseAddress[x + (y * fb->PixelsPerScanLine)] = color; }
// Clears the entire visible display to a solid color void clear_screen(Framebuffer* fb, unsigned int color) { for (unsigned int y = 0; y < fb->Height; y++) { for (unsigned int x = 0; x < fb->Width; x++) { put_pixel(fb, x, y, color); } } }
// Scans the 8-bit glyph bitmap to draw a pixel character on the screen void draw_char_K(Framebuffer* fb, unsigned int x_offset, unsigned int y_offset, unsigned int color) { for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { // Check each column bit tracking from left-to-right (MSB to LSB) if ((font_K[y] >> (7 — x)) & 1) { put_pixel(fb, x_offset + x, y_offset + y, color); } } } }
// Kernel Entry point conforming to System V AMD64 ABI convention attribute((sysv_abi)) void kernel_main(Framebuffer* fb) { // 1. Paint the entire workspace screen a deep navy blue hue (Hex: 0x00112233) clear_screen(fb, 0x00112233);
// 2. Render a solid white ‘K’ character at coordinate offset (X=150, Y=150) // Hex: 0x00FFFFFF (ARGB format) draw_char_K(fb, 150, 150, 0x00FFFFFF);
// 3. Put the CPU into an idle loop state while (1) { asm(“hlt”); } }
now we will make the linker.ld code(its function is that it store and call the location of the kernel.elf (code in kernel.c)
gedit kernel.ld
ENTRY(kernel_main)
SECTIONS { / Move the kernel up to a safer physical memory zone / . = 0x400000;
.text ALIGN(4096) : { (.text) }
.rodata ALIGN(4096) : { (.rodata) }
.data ALIGN(4096) : { (.data) }
.bss ALIGN(4096) : { (.bss) } }
Now we run the following commands in the given order to make the kernel.elf from this add it to the iso folder.
gcc -ffreestanding -mno-red-zone -mno-mmx -mno-sse -mno-sse2 -fno-stack-protector -c kernel.c -o kernel.o
ld -T linker.ld -static -nostdlib kernel.o -o kernel.elf
cp kernel.elf ../iso/kernel.elf
With this we have rap up our kernel code(the main os code) but remember we make the bootloader but it was very simple , now we need to modify it so that it do the following functions. call the kernal code and exit ( hence giving the full control to kernel) + some other important functions.
So we will go back to the boot folder and edit the main.c. below is the modified code.
include <efi.h>
include <efilib.h>
include <elf.h>
// Structure to pass the video configuration directly to the kernel typedef struct { unsigned int* BaseAddress; unsigned long long BufferSize; unsigned int Width; unsigned int Height; unsigned int PixelsPerScanLine; } Framebuffer;
EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { InitializeLib(ImageHandle, SystemTable); Print(L”UEFI Bootloader Started\n”);
EFI_LOADED_IMAGE LoadedImage; EFI_SIMPLE_FILE_SYSTEM_PROTOCOL FileSystem; EFI_FILE_PROTOCOL RootDir; EFI_FILE_PROTOCOL KernelFile;
EFI_STATUS status;
// Get Loaded Image Protocol status = uefi_call_wrapper( BS->HandleProtocol, 3, ImageHandle, &LoadedImageProtocol, (void**)&LoadedImage );
if (EFI_ERROR(status)) { Print(L”LoadedImage failed\n”); while (1); }
// Get filesystem status = uefi_call_wrapper( BS->HandleProtocol, 3, LoadedImage->DeviceHandle, &FileSystemProtocol, (void**)&FileSystem );
if (EFI_ERROR(status)) { Print(L”FS failed\n”); while (1); }
// Open volume status = uefi_call_wrapper( FileSystem->OpenVolume, 2, FileSystem, &RootDir );
if (EFI_ERROR(status)) { Print(L”OpenVolume failed\n”); while (1); }
// Open kernel status = uefi_call_wrapper( RootDir->Open, 5, RootDir, &KernelFile, L”kernel.elf”, EFI_FILE_MODE_READ, 0 );
if (EFI_ERROR(status)) { Print(L”kernel open failed\n”); while (1); }
Print(L”kernel.elf opened\n”);
// Read ELF header Elf64_Ehdr elfHeader; UINTN size = sizeof(Elf64_Ehdr);
status = uefi_call_wrapper( KernelFile->Read, 3, KernelFile, &size, &elfHeader );
if (EFI_ERROR(status)) { Print(L”ELF header read failed\n”); while (1); }
// Validate ELF if (!(elfHeader.e_ident[0] == 0x7F && elfHeader.e_ident[1] == ‘E’ && elfHeader.e_ident[2] == ‘L’ && elfHeader.e_ident[3] == ‘F’)) { Print(L”Invalid ELF\n”); while (1); }
Print(L”Valid ELF\n”);
// Read program headers Elf64_Phdr phdrs[16]; // small safe limit UINTN phdrSize = elfHeader.e_phnum * sizeof(Elf64_Phdr);
uefi_call_wrapper( KernelFile->SetPosition, 2, KernelFile, elfHeader.e_phoff );
status = uefi_call_wrapper( KernelFile->Read, 3, KernelFile, &phdrSize, phdrs );
if (EFI_ERROR(status)) { Print(L”PHDR read failed\n”); while (1); }
// Load segments for (UINTN i = 0; i < elfHeader.e_phnum; i++) {
Elf64_Phdr *phdr = &phdrs[i];
if (phdr->p_type != PT_LOAD) continue;
EFI_PHYSICAL_ADDRESS segment = phdr->p_vaddr; // ✅ CRITICAL FIX
UINTN pages = (phdr->p_memsz + 0xFFF) / 0x1000;
status = uefi_call_wrapper( BS->AllocatePages, 4, AllocateAddress, EfiLoaderData, pages, &segment );
if (EFI_ERROR(status)) { Print(L”Alloc failed\n”); while (1); }
// Read segment uefi_call_wrapper( KernelFile->SetPosition, 2, KernelFile, phdr->p_offset );
UINTN readSize = phdr->p_filesz;
status = uefi_call_wrapper( KernelFile->Read, 3, KernelFile, &readSize, (void*)segment );
if (EFI_ERROR(status)) { Print(L”Segment load failed\n”); while (1); }
// ZERO .BSS (CRITICAL FIX) if (phdr->p_memsz > phdr->p_filesz) { char bss = (char)segment + phdr->p_filesz; for (UINTN j = 0; j < phdr->p_memsz — phdr->p_filesz; j++) { bss[j] = 0; } } }
Print(L”Kernel loaded\n”);
// ========================================== // GRAPHICS INITIALIZATION (GOP) // ========================================== EFI_GUID gopGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
status = uefi_call_wrapper(BS->LocateProtocol, 3, &gopGuid, NULL, (void**)&gop); if (EFI_ERROR(status)) { Print(L”Failed to locate Graphics Output Protocol (GOP)\n”); while (1); }
// Pack essential hardware display settings into our config struct Framebuffer fb; fb.BaseAddress = (unsigned int*)gop->Mode->FrameBufferBase; fb.BufferSize = gop->Mode->FrameBufferSize; fb.Width = gop->Mode->Info->HorizontalResolution; fb.Height = gop->Mode->Info->VerticalResolution; fb.PixelsPerScanLine = gop->Mode->Info->PixelsPerScanLine;
Print(L”Graphics framebuffer configuration captured\n”);
// Update function pointer type to match the modern signature: void entry(Framebuffer) void (kernel_start)(Framebuffer) = (void ()(Framebuffer*))elfHeader.e_entry;
// ========================================== // SAFE EXIT BOOT SERVICES FLOW // ========================================== UINTN mapSize = 0; EFI_MEMORY_DESCRIPTOR *map = NULL; UINTN mapKey; UINTN descSize; UINT32 descVersion;
// 1. Get the required buffer size first uefi_call_wrapper(BS->GetMemoryMap, 5, &mapSize, NULL, &mapKey, &descSize, &descVersion);
// Add extra padding room safely mapSize += (2 * descSize);
// 2. Allocate the pool BEFORE doing the final key verification fetch status = uefi_call_wrapper(BS->AllocatePool, 3, EfiLoaderData, mapSize, (void**)&map); if (EFI_ERROR(status)) { Print(L”Failed to allocate memory map buffer\n”); while(1); }
// 3. Get the absolute latest, clean map and correct mapKey status = uefi_call_wrapper(BS->GetMemoryMap, 5, &mapSize, map, &mapKey, &descSize, &descVersion); if (EFI_ERROR(status)) { Print(L”Failed to get final memory map\n”); while(1); }
// 4. Exit boot services immediately without calling ANY other UEFI functions status = uefi_call_wrapper(BS->ExitBootServices, 2, ImageHandle, mapKey); if (EFI_ERROR(status)) { while(1); }
// 5. Absolute handoff to kernel space with the Framebuffer details passed as an argument kernel_start(&fb);
// Should never be reached while (1); }
After adding this code to main.c we will compile again using the above code.
And once main.efi is made we will run the second command from the image below (not mkdir because folder already made)

Now everything is done and we will run the code using the qemu command above.

which will run the code and give us the following output.

I know this is not a lot but as K is printed this shows that our kernel is in control now and our os is running. Now in future most of our changes will take place on the kernel.c file as we will make our OS functionality their. (maybe we do some change to bootloader .efi file >> will also discuss later on)
For now this will be our folder tree (use command tree -a in os folder)

Dont panic if you dont know what NvVars is
Remember when we start our pc/laptop and press F-12 we go to BIOS/UEFI menu wherewe change setting etc. Right?? Well what do you think how they store this information/setting you make.
In real system their is a hardware chip called NVRAM (its not your typical storage or RAM)it is non-volatile (stores data) small space , fast chip which store only BIOS/ motherboard data. When we do some changes or turn on our device normally it stores the last configured changes to this chip to remember before giving access to the kernel and shut down.
Here NvVars do the same but as right now no physical hardware present and we are doing this virtully hence it made a file.
So all in all this is what we have made uptill now, in future we will modify all of this to make our os better.
NOW WE WILL MAKE THE OS BETTER (to be continue…)
메타데이터
- post_id
- d5acee8ba8eb
- slug
- i-am-making-my-first-os-x64-d5acee8ba8eb
- url
- https://medium.com/@i221765/i-am-making-my-first-os-x64-d5acee8ba8eb
- canonical_url
- https://medium.com/@i221765/i-am-making-my-first-os-x64-d5acee8ba8eb
- author_url
- https://medium.com/@i221765
- status
- ok
- fetched_at
- 2026-06-10 08:17:25