Until now our kernel is 16-Bit, and in the last chapter we entered into protective land (32-Bit Protected Mode) but our kernel was 16-Bit so we were not able to call the kernel from the 32 bit mode. In order to call the kernel we have to convert it into 32-Bit. Let's do it.
Convert Kernel from 16 to 32 Bit
As our kernel is in assembly language, in order to switch it to 32 bit, we have just got to replace the BITS 16
to BITS 32
. It is easy isn't it. However, we have to replace our printing function dependency, which only works for 16 bit real mode, because those function uses interrupt to print on the screen and in protected mode real mode interrupts is unavailable.
kernel/kernel_entry.asm
:
org 0xb000
BITS 32 ; Specify that the cod is 32-bit
This is the basic template for the 32 bit flat binary kernel.
Now we have to create the screen clearing and printing functions that will works in protected mode (32-bit) without using interrupts.
boot/stage2/includes/print32.inc
:
This file will have all the printing logic for the 32 bit protected mode. These functions write directly to the memory address of the VGA which is mode 0x3
(80*25).
%ifndef _PRINT_32_INC_
%define _PRINT_32_INC_
BITS 32
%define VGA_DEFAULT_MEM_ADDR 0xb8000
%define VGA_ROW 80
%define VGA_COLOUMN 25
; 80*25 Colors
%define BLACK 0x0
%define BLUE 0x1
%define GREEN 0x2
%define CYAN 0x3
%define RED 0x4
%define MAGENTA 0x5
%define BROWN 0x6
%define LGRAY 0x7
%define DGRAY 0x8
%define LBLUE 0x9
%define LGREEN 0xA
%define LCYAN 0xB
%define LRED 0xC
%define LMAGENTA 0xD
%define YELLOW 0xE
%define WHITE 0xF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; **************************
; ClearScreen32
; IN:
; BL: Foreground color
; BH; Background color
; Nothing
; Registers:
; - Conserves all the Registers
; **************************
ClearScreen32:
pushad ; Push the complete state
mov esi, VGA_DEFAULT_MEM_ADDR
mov ecx, VGA_ROW * VGA_COLOUMN
.ClearScreen32Loop:
mov byte [esi], ' ' ; Space
mov byte [esi + 1], 0x07 ; default color - lightgrey on black
add esi, 2 ; Increment VGA memory pointer by 2 bytes.
loop .ClearScreen32Loop ; Loop until ecx is 0
popad ; Pop all the saved state
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; **************************
; PrintString32
; IN:
; - ESI: Null-Terminated String
; Registers:
; - Conserves all the registers
; **************************
PrintString32:
pushad ; push all 32 bit registers onto the stack
mov ecx, VGA_ROW * VGA_COLOUMN
mov edi, VGA_DEFAULT_MEM_ADDR
shl bh, 4
or bl, bh
.PrintString32Loop:
lodsb ; it reads character from the esi to al
test al, al
je .PrintString32Done
;; The first byte in 80*25 ie. mode 0x3 has to be the character
;; followed by the color,
;; which is lower 4 bits for foreground color
;; upper 4 bits for background color
mov [edi], al ; Write the character
mov byte [edi + 1], bl;0x07 ; color
add edi, 2
loop .PrintString32Loop
.PrintString32Done:
popad ; pop all saved 32-bit register from the stack
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%endif
stage2.asm
:
Let's modify the stage2.asm to print the message after entering in the protected land and jump to the loaded kernel.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Load Flat Kernel of Size 1 KB at location 0xB000
xor eax, eax ; Clear out the eax
mov esi, eax ; Clear ou the esi
mov ax, MEMLOCATION_KERNEL_LOAD_SEGMENT ; 0x0000
mov es, ax ; Set es to 0x0000
mov bx, MEMLOCATION_KERNEL_LOAD_OFFSET ; 0xB000
mov eax, 59 ; Starting sector low 32 bit (0-indexed LBA)
mov esi, 0 ; Starting sector high 32 bit
mov ecx, 2 ; Sector count
mov edx, 512 ; Sector sizes in bytes
call ReadFromDiskUsingExtendedBIOSFunction
;; Stop Jumping of Kernel, first we have to be in Protected Mode
;; and then shift the kernel to 32 bit mode.
; jmp MEMLOCATION_KERNEL_LOAD_SEGMENT:MEMLOCATION_KERNEL_LOAD_OFFSET ; Jump to the Kernel
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Entering Protective Land
;; Enable PE (Protection Enable) Bit
; Enable Protected Mode
mov eax, cr0 ; Move the current value of CR0 register into eax
or eax, 0x1 ; Set PE bit (bit 0) to 1
mov cr0, eax ; Write the modified value back to the CR0 register,
; enabling Protected Mode
; Perform a far jump to clear the prefetch queue
jmp 0x08:ProtectedMode ; Segment selector 0x08 (code segment)
;; Entry of 32-Bit world
BITS 32
; 32 Bit Includes
%include 'print32.inc' ; For printing
ProtectedMode:
; Update segment registers
xor eax, eax
mov ax, 0x10 ; Data segment selector 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 0x7BFF ; set the stack top to the 0x7BFF
; The stack grows from high memory to lower memory
; --------
; |______| 0x7BFF
; |______|
; |......| it is growing downward as we pushes
; |______| data
; |______|
; | | 0x7AFF
; Disable the all irq lines
mov al, 0xff
out 0xa1, al
out 0x21, al
cli ; Disable the interrupt as they are no more available
; in real mode.
; Now we are in protective land
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; clear the screen
call ClearScreen32
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Print welcome sentence for protected mode
mov esi, sProtectedModeWelcomeSentence
mov bl, LMAGENTA ; Foreground = Light Magenta
mov bh, BLACK ; Background = Black
call PrintString32
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; jmp to the kernel
jmp MEMLOCATION_KERNEL_LOAD_OFFSET
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
jmp $
; **********
; Variables
; **********
bPhysicalDriveNum db 0
WelcomeToStage2 db 'Welcome to the Stage2', 0
sReceivedDriveNumber db 'Received Drive Number in Stage 2: ', 0
sProtectedModeWelcomeSentence db 'Entered the Protective Land', 0
times STAGE_2_SIZE - ($-$$) db 0 ; Fill up the remaining space with zeroes
Now we have the functions for printing to the kernel, let's modify the kernel to clear the screen and print the welcome message.
kernel/kernel_entry.asm
:
org 0xb000 ; Set the origin address for the code. This tells the assembler
; that the code should be loaded at memory address 0x0B00.
; So all the jmp statement and string declaration offset is
; calculated based on it.
BITS 32 ; Specify that the code is 32-bit.
kernel_entry: ; Label for the kernel entry point.
jmp start ; jmp after the includes
;; Include files
%include "print32.inc"
start:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Clear the Screen
call ClearScreen32
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Print Welcome Message
mov esi, sKernelWelcomeStatement
mov bl, YELLOW ; Foreground color = Yellow
mov bh, BLACK ; Background color = Black
call PrintString32
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
jmp $ ; Infinite loop to halt execution after printing the message.
;; Put data in second sector
times 512 - ($ - $$) db 0
sKernelWelcomeStatement: db 'Welcome to Flat Binary 32-Bit Kernel Land.', 0
; Define the welcome message string, terminated by a null byte (0).
times 1024 - ($ - $$) db 0 ; Fill the rest of the 1 KB (1024 bytes) space with zeros.
Makefile:
Add the stage2 includes directory for the kernel to use, because print32.inc
is available at the stage2/includes directory:
BOOT_STAGE2_INCLUDE = boot/stage2/includes
kernel_entry.bin: kernel/kernel_entry.asm
nasm -f bin -I $(BOOT_STAGE2_INCLUDE) -o build/$@ $<
Output

Source Code
The complete source code is at this commit: https://github.com/The-Jat/TheTaaJ/tree/boot