OS - Entering Protected Mode
Now that we have discussed the A20 line and the Global Descriptor Table, we’re finally ready to enter protected mode. Protected mode adds a level of abstraction so that our kernel will not be able to access hardware directly. It also allows us to use up to 4GB of RAM and many other features that won’t be covered. So first, the code.
; Craig Milby ; Bootloader Stage 2 ; May 2nd, 2015 ; boot2.asm [bits 16] [org 0x500] _start: jmp _main_16 %include "A20.asm" %include "gdt.asm" %include "io_16.asm" loaded_32_msg: db "Loaded 32-Bit Protected Mode", 0 loaded_boot2_msg: db "Loaded Bootloader Stage 2", 13, 10, 0 _main_16: mov bp, 0x9000 mov sp, bp mov si, loaded_boot2_msg call _print_16 call _enable_A20_BIOS cli lgdt [_gdt_descriptor] mov eax, cr0 or eax, 1 mov cr0, eax jmp CODE_SEGMENT:_init_32 [bits 32] %include "io_32.asm" _init_32: mov ax, DATA_SEGMENT mov ds, ax mov ss, ax mov es, ax mov fs, ax mov gs, ax mov ebp, 0x90000 mov esp, ebp _main_32: call _clear_screen_32 mov ebx, loaded_32_msg call _print_32 jmp $
We are not going to be looking at the Stage 1 bootloader code because it hasn't changed. This will all be in our second stage loader. As usual, much of this code is familiar so only select parts will be discussed.
cli lgdt [_gdt_descriptor]
We know what the first instruction does, disables all interrupts. In protected mode, we cannot use BIOS interrupts because these are hardware access which we no longer have. If one is used, the OS will triple-fault and crash. The next line is how we load our Global Descriptor Table. Assembly provides a simple way of loading it with the lgdt instruction. Then we just have to specify where in memory it actually is, easy enough.
mov eax, cr0 or eax, 1 mov cr0, eax jmp CODE_SEGMENT:_init_32
Here we have new registers we haven't seen before. cr0 is a control register. We have to set its flag to the correct value to jump it into 32-bit mode like so. After that we define where our code is (remember we did this in our GDT file) and jump to it.
[bits 32] %include "io_32.asm"
Up next, we use an assembler directive to define that we are now using 32-bits. The assembly will now know to start usign 32-bit registers. After, we include another file. This is a basic input/output file for protected mode. Our 16-bit real mode printing required BIOS interrupts. This one does not. I will most likely not show this code however.
_init_32: mov ax, DATA_SEGMENT mov ds, ax mov ss, ax mov es, ax mov fs, ax mov gs, ax mov ebp, 0x90000 mov esp, ebp
This is where our 32-bit code enters. It starts by setting all the registers to the value of DATA_SEGMENT which is defined in our GDT file. We then set up our 32-bit stack. This stack is much larger due to memory limitations being expanded. ebp is the bottom of the stack and esp is the top but they both start at the same value.
_main_32: call _clear_screen_32 mov ebx, loaded_32_msg call _print_32 jmp $
This is our last bit of code. Our main protected mode code. In future version this is where the kernel would be loaded, but in our case, we are just going to pritn stuff to the screen. First we clear the screen with the clear_screen call, then we move our message to the dbx ebx register and print it. Lastly we repeatedly jump to the last line halting the CPU. Let's see what this looks like!
Wow! Colors! We can define lots of colors now that we have access to video memory when we are in protected mode. Here's more.
From here, lots of things can be done. We can load a kernel and start programming in C. This is done a lot because C is a lot easier than Asseembly. We could stay usign Assembly and go into 64-bit long mode. This will give us access to more memory and loads of other features. As for this blog, this is the end. Hope you learned something and enjoyed it.













