Hi all, I have built a small Q&A space for people like you and me. Anyone could post a question related to anything they are interested about (both good and meaningful questions) and anyone could post an answer to them. Both asking and answering anonymously is also supported. The topic could be either live advice, or career, or spirituality, or almost anything that drives both good and useful answers from other users. Kindly checkout https://mindsply.com .
I would love some honest constructive feedback. Thanks.
1. Raam Raam ji (greeting with god's name :) ). This is our second blog post in the operating system development series. If you haven't, you can read the first blog post about creating our first UEFI boot loader in assembly here.
Today, we will code and run what the title of the post says.
struc UINT32 {
align 4 ; ensure 4-byte alignment
. dd ? ; reserve 4 bytes (32-bit) of uninitialized space
}
struc void {
align 8
. dq ? ; reserve 8 bytes (64-bit) of uninitialized space
}
macro struct name {
virtual at 0
name name
end virtual
}
struc EFI_TABLE_HEADER {
.Signature void
.Revision UINT32
.HeaderSize UINT32
.CRC32 UINT32
.Reserved UINT32
}
struct EFI_TABLE_HEADER
struc EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
.Reset void
.OutputString void
.TestString void
.QueryMode void
.SetMode void
.SetAttribute void
.ClearScreen void
.SetCursorPosition void
.EnableCursor void
.Mode void
}
struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
struc EFI_SYSTEM_TABLE {
.Hdr EFI_TABLE_HEADER
.FirmwareVendor void
.FirmwareRevision UINT32
.ConsoleInHandle void
.ConIn void
.ConsoleOutHandle void
.ConOut void
.StandardErrorHandle void
.StdErr void
.RuntimeServices void
.BootServices void
.NumberOfTableEntries void
.ConfigurationTable void
}
struct EFI_SYSTEM_TABLE
format pe64 efi
entry start
section '.text' code executable readable
start:
; store the image handle and the system table pointer passed by the
; firmware
mov [image_handle], rcx
mov [system_table], rdx
push rbp
sub rsp,0x20 ; allocate stack space
mov rax,[system_table]
mov rdx,test_ok_text
mov rcx,[rax+EFI_SYSTEM_TABLE.ConOut]
mov rcx,[rcx+EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString]
call rcx
add rsp,0x20 ; restore stack
; hang here
jmp $
section '.data' data readable writeable
image_handle dq ?
system_table dq ?
test_ok_text: du 'Test OK',0
Save the above program in a file named `boot.asm` and assemble it as:
`fasm boot.asm BOOTx64.EFI`
2. Now prepare a pen drive with the boot loader image and test it on your real machine by following the procedure given in the point #2 from the previous blog post.
Here is the output:
3. Now let us understand the above code a little bit:
struc UINT32 {
align 4 ; ensure 4-byte alignment
. dd ? ; reserve 4 bytes (32-bit) of uninitialized space
}
struc void {
align 8
. dq ? ; reserve 8 bytes (64-bit) of uninitialized space
}
These two structure definitions create standard data types with proper alignment. These structures are used when defining UEFI system tables and structures where proper alignment is necessary.
macro struct name {
virtual at 0
name name
end virtual
}
This macro defines a struct-like alias, allowing us to create named structures without allocating memory immediately. It allows a named structure to be used as a structure reference.
The next three structures above define key UEFI system tables used for interacting with the firmware. You can check the UEFI specification document for the structures of these tables.
EFI_TABLE_HEADER – A common header used in UEFI tables, containing metadata like a signature, version, and checksum.
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL – Defines function pointers for text output operations (e.g., printing text to the screen, clearing the screen, setting the cursor position).
EFI_SYSTEM_TABLE – The main UEFI system table, providing access to firmware services, console input/output, and runtime/boot services.
These structures allow the boot loader to interface with UEFI firmware, enabling operations like printing text on the screen.
Now beginning from our entry point, first we store the image handle and the system table pointer passed by the firmware in the `rcx` and `rdx` registers respectively.
The subsequent code prints a test message using UEFI’s text output protocol and goes into an infinite loop. First, we setup the stack by saving the base pointer (`rbp`) and allocating stack space (0x20 bytes) for function calls. Then we get UEFI conout (console output interface) interface and finally get `OutputString` function and print text by loading the string address into `rdx`.
Then we restore stack and execute the infinite loop.
Please try the above code and let me know if you have any comments or suggestions :)
Raam Raam ji _/\_ _/\_ _/\_ (greeting with god's name :) )
Raam is a 64-bit Unix-like kernel for x86-64 based PC.
I have been creating it since the beginning of this year. It is a proprietary software and I'm happy to share that I'm releasing its first version for free!
"Release early" is my mantra!
The aim of this release is to create the "core infrastructure" needed to enable the process management and the filesystem management in the kernel.
What have I implemented?
A UEFI boot loader
GDT (global descriptor table)
IDT (interrupt descriptor table)
interrupt handling
tty (output) driver initialization
printk implementation
acpi and pcie initializations for NVMe driver
NVMe driver
PIT timer driver
PIC driver
handler for timer interrupts
Please Note:
Running the kernel executable on your 64-bit intel machine may lead to data loss or corruption. It may also make your system unstable as I can't test the binary on all the possible machines. It works fine on my and my sister's machine though. Please continue at your own risk.
What do you need to test it?
1. The kernel executable file. Please download it from here and get the `BOOTx64.EFI` file.
2. A laptop or desktop PC with 64-bit intel cpu supporting UEFI booting, ACPI version 2, NVMe over PCIe SSD, PIT and PIC devices (available on most of the modern systems).
3. A pen drive formatted as GPT with FAT filesystem. Create two directories viz. `/EFI/BOOT`. Copy the kernel file `BOOTx64.EFI` to `/EFI/BOOT/`.
Reboot your machine and on boot, press the key required to get the boot menu (just google it for your model) and select your usb thumb drive from it. Voila! It should run and you will get a screen filled with various initializations and timer fires.
That's it.
If you like, please post the screenshot in the comments by uploading it somewhere.
It is the passion and the hobby :)
Note: The kernel works on my HP laptop and my elder sister's Dell laptop.
Raam Raam ji (greeting with god's name :) ). Hello world. This is our first blog post in the operating system development series.
Today, we will try to address this fundamental question - "How does an OS (Operating System) boot? What happens first?"
Let's get our hands dirty. Let us learn by doing. So it is the "firmware", the UEFI firmware on modern systems that is responsible for loading and calling your OS boot loader's entry point.
If you don't know what is UEFI, it stands for "Unified Extensible Firmware Interface". You don't need to know what is it. Just know that it is both different and newer than BIOS (Basic Input/Output System). It's a firmware that loads your OS's boot loader into RAM and calls its entry point. Simple!
Now what is a boot loader? It is a boot program that performs some initialization tasks, loads the kernel file into memory, and provides the kernel with the information it needs to work correctly.
So the first system program that runs after booting by the firmware is your boot loader which in turns loads and jumps to your kernel's entry point. A kernel is the core and central component of an OS. The userspace program like your shell runs later.
1. Enough theory! Now let's create a simple boot loader that does nothing but goes into an infinite loop and you will test it on your real machine:
Open your favorite text editor (I like `vim`) and paste the above code. Save it as boot.asm and then on a 64-bit Ubuntu like machine, install fasm assembler with apt install fasm. Then run the following command:
fasm boot.asm BOOTx64.EFI
On successful passes, you will get the output file `BOOTx64.EFI`.
2. Now you need to format a pen drive in order to make it bootable and then we will copy this boot loader program into it.
Open `Disks` program on your Ubuntu-like machine and select your pen drive from the leftmost column. Now click on Drive Options (upper three dots) in the right and then on Format Disk.... Now select the options as below:
click Format.... Now click on the plus button (+) below the Volumes label, click Next, choose FAT and then finally click Create. Now mount this partition and create two directories viz. EFI/BOOT on it. Now copy the above boot loader executable to <mount point>/EFI/BOOT/BOOTx64.EFI .
Reboot.
Now when your machine powers on, press the key to select the boot options (just google for it), select your USB thumb drive, and voila... we got a steady blank screen because of the infinite loop!
The code that you had just typed and assembled is now working!
3. Now let us understand the above code a bit:
`format pe64 efi` tells your assembler that it is going to be a 64-bit Portable Executable output format, with UEFI as the target subsystem.
`entry start` tells that the entry point for this loader application is the `start` label.
`section '.text' code executable readable` tells the assembler about the text section where the instructions of the loader program goes and this section is both executable and readable by the processor.
`start:` is our start label (a label in assembly is a mnemonic for an address). It can be any name.
`; infinite loop` is a comment telling that the following line will execute an infinite loop.
Finally, `jmp $` tells the program control to jump to the present address (which is this line itself) and so it goes into an infinite loop.