How computers start up! Part 2: Real Mode(16-bit) in X-86 assembly
Deep dive into the process of bringing up the system through various CPU modes of execution.
One can be lucky if BIOS is kind enough to put the CPU in Protected Mode(32-bit) before executing the boot-loader, however cannot be sure. We are going to assume that the CPU is in Real Mode(16-bit) and boot-loader is about to get executed by the BIOS after confirming the presence of the Magic Number(0xAA55) at the end of the boot sector(First 512 Bytes of the drive).
Memory Operations in Real Mode
By now, system have been surviving interrupts using the Real Mode Interrupt Vector Table(IVT) placed at the start of the memory(RAM) by BIOS for us to use. At a certain point, when we will leave Real Mode for Protected Mode, we will have to replace it with our own interrupt vector table. It will be good to have a look at the memory(RAM) map used while working in Real Mode X-86.
Boot-Sector starts @ 0x7C00 in the memory, so we need to place an offset inside our assembly code to address locations appropriately.
;placing this at the top of our assembly code sets a global offset! [org 0x7c00]
To create a variable inside the boot-loader.
[org 0x7c00] ; global offset of BootSector private_data: db "A"
Now, to access it
[org 0x7c00] ; global offset of BootSector mov ah, 0x0e; tty mode mov al, [private_data] int 0x10 ; this is the interrup for printing system output
Setting up Stack
Remember that the bp
register stores the base address (i.e. bottom) of the stack,
and sp
stores the top, and that the stack grows downwards from bp
(i.e. sp
gets
decremented)
So in order for our stack to work all we have to do is initialize these registers with values far away from where boot sector is loaded(0x7C00), lets say 0x8000,
mov bp, 0x8000 ; this is an address far away from 0x7c00 mov sp, bp ; if the stack is empty then sp points to bp
After this we can Push and Pop from the stack
push 'A' push 'B' push 'C' pop bx mov al, bl int 0x10 ; prints C pop bx mov al, bl int 0x10 ; prints B pop bx mov al, bl int 0x10 ; prints A
Functions and File Includes in assembly
We are going to write PRINT function in a new file print_assembly.asm
. It will take one parameter, a String. Essentially we loop until we find \0
at the end of the string.
Overall Algorithm:
while (string[i] != 0) { print string[i]; i++ }
; Print function in assembly print: pusha start: mov al, [bx] ; 'bx' is the base address for the string cmp al, 0 je done ; the part where we print with the BIOS help mov ah, 0x0e int 0x10 ; 'al' already contains the char ; increment pointer and do next loop add bx, 1 jmp start done: popa ret
Let's also create function for printing new line, \n
as 0x0a
and carriage return, \r
as 0x0d
; Print new Line print_nl: pusha mov ah, 0x0e mov al, 0x0a ; newline char int 0x10 mov al, 0x0d ; carriage return int 0x10 popa ret
Including and calling Print function from Main.asm
code
[org 0x7c00] ; tell the assembler that our offset is bootsector code ; Prepare parameters in the bx register mov bx, HELLO call print call print_nl mov bx, GOODBYE call print call print_nl ; now loop forever jmp $ ; $: represents current address ; This is where the code from print_assembly.asm file will be copied %include "print_assembly.asm" ; data HELLO: db 'Hello, World', 0 GOODBYE: db 'Goodbye', 0 ; padding and magic number times 510-($-$$) db 0 dw 0xaa55
Segmentation
Segmentation is a way to manage memory. In other words it is the layout in which programs and data are placed inside the RAM. This feature is in-built in 32-bit x-86 processors. For this to work we need to setup global descriptor table
Initialization
We set up stack by initializing registers sp
and bp
. Similarly, in order to start segmenting memory we need to initialize segment registers cs
, ds
, ss
and es
.
Note: Once you set some value for, say, ds
, then all your memory access will be offset by ds
Access
To compute the real address we don't just join the two
addresses, but we overlap them: (segment << 4) + address
. For example,
if ds
is 0x4d
, then [0x20]
actually refers to 0x4d0 + 0x20 = 0x4f0
mov bx, 0x7c0 ; segment is automatically shifted left 4 bits for you mov ds, bx ; now cpu uses ds as all data operations
This article is to be continued in the next issue where we prepare and move the CPU to 32-bit Protected mode after setting up segmentation at a global level using GDT(Global Descriptor Table ), Part 3: Protected Mode(32-bit) & Global Descriptor Table
Visit OS Dev for detailed information about the topic discussed above.