Skip to content

Commit

Permalink
chore: adds comments for a lot of the asm files
Browse files Browse the repository at this point in the history
  • Loading branch information
albertarielw committed Sep 9, 2023
1 parent f557fc6 commit e7bd277
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 106 deletions.
132 changes: 50 additions & 82 deletions bootstrap.asm
Original file line number Diff line number Diff line change
@@ -1,55 +1,35 @@
; start indicates starting point of the execution
start:
; When a the boot sector is loaded by BIOS. It will be loaded on the location 07C0h in the memory.
; What we do here is setting the register "ds" to the value "07C0" which is the same of the memory address we are on now.
; Our variable "hello_string" will be loaded as a part of the bootloader, when we use it in the line "mov si, hello_string"
; we're actually loading the memory address of the first character, let's assume that this memory address is (1000). Because
; it is considered as a "data" by the processor. This offset (the memory address of "hello_string") will be added to the data segment
; selector which resides in the register "ds". According to "Protected Mode Software Architecture", the initial value of "ds" is 0000h,
; so, if we keep "ds" as it is, the CPU is going to load the data from the memory 00001000, BUT we are actually on 07C0, so the data
; segment selector should be 07C0h so we can get the correct address of "hello_string" which is 07C01000 and not 00001000.
; For backward compatibility, when the bootloader starts, the execution environment will be in "Real Mode", 16-bit.

; we want to move 07C0h to ds (why? it will be explained in later segment)
; we cannot mov ds, 0x08C0 directly because ds (segment register) cannot be loaded with value directly in x86
; further details: https://stackoverflow.com/questions/19074666/8086-why-cant-we-move-an-immediate-data-into-segment-register
mov ax, 07C0h
mov ds, ax
; ... ;
; == Printing == ;
; define si to be title_string or message_string
; call print_string which prints to screen the values saved in si
mov si, title_string
call print_string ; "CALL" is used instead of "JMP" because we would like to return to the next instruction after "print_string" finishes.
mov si, message_string
call print_string
; ... ;
; === Loading the Kernel === ;
call load_kernel_from_disk
; If the loading has been performed correctly. Jump to the kernel's code which resides on 0900h:0000 according
; to ES:BX values before calling (int 13h).
;
; There is a difference between "JMP" and "CALL" inctructions. The first one doesn't store returning information
; in the stack while the later does. Because we are not going to return from kernel to the bootloader, we don't
; need to store return information.
; gives control to kernel by jumping to the start of the kernel
; 0900h is the same value we have loaded in the register es in the beginning of load_kernel_from_disk
; 0000 is the offset we have specified in the register bx in load_kernel_from_disk before calling 02h:13h
jmp 0900h:0000

; ... ;
; ... ;

; BIOS service 13h:02h loads the required sector into the memory address es:bx
; So, the value 0900h which has been set to es in the code above will be the starting memory address of the kernel
; Previously, it was enough to set bx = 0 since we only load 1 sector -> the code will reside from offset 0 to 511
; Now we are loading > 1 sector so we need to increment bx
load_kernel_from_disk:
; BIOS provides disk services through intrrupt 13h. The value of the register "ah" decides which disk service that we are requesting from BIOS.
; The Disk Service "02h" loads sectors to the memory. The memory location that we would like to load our kernel in should be decided
; before calling "int 13h". There are two parts of this location. The first part (segment selector) resides in the register "ES" while
; the other part (offset) resides in the register "BX".
;
; x86 Segment Registers: CS: for code segment.
; DS, ES, FS, and GS: for 4 data segments.
; SS: for stack segment.
; They are 16-bit registers that hold "segment selectors"
; increment bx by 1 sector to load the next sector
mov ax, [curr_sector_to_load]
sub ax, 2
mov bx, 512d
Expand All @@ -58,24 +38,34 @@ load_kernel_from_disk:
mov ax, 0900h
mov es, ax
mov ah, 02h ; Requesting the service of reading disk sectors
mov al, 1h ; Number of sectors to read (How many sectors to read?)
mov ch, 0h ; Track number
mov cl, [curr_sector_to_load] ; Sector number
mov dh, 0h ; Head number

; service number 02h is specified to register ah
; it reads sectors from the hard disk and loads them into the memory
mov ah, 02h

; 01h is the number of sector we ants to read (since simple_kernel.asm is < 512 bytes, 1 sector is enough)
mov al, 1h

; 0h is the track number we would like to read (track 0)
mov ch, 0h

; 02h is the sector number we would like to read (sector 2)
mov cl, [curr_sector_to_load]

; 0h is the head number (of the disk)
; specifies the type of disk
; 0h means floppy disk, 80h means hard disk #0, 81h means hard disk #1, etc.
; the value of bx is the memory address that the content will be loaded into
; we are reading one sector and the content will be stored on memory address 0h
mov dh, 0h
mov dl, 80h ; Drive to read from. (0 = Floppy. 80h = Drive #0. 81h = Drive #1)
int 13h ; BIOS Disk Services
; The instruction "jc" jumps to a memory location when CF = 1 (jc = jump if carry).
; CF (or carry flag) is the first bit of EFLAG register in x86. The BIOS service (13h,02h)
; clear CF (that is, put 0 in CF) when everything is fine. But if there is some error
; it's going to set CF (that is, put 1 in CF). Error code will be in "ah".
;
; If any error happens in loading our kernel. The bootloader is going to jump to the label "kernel_load_error".
; when the content is loaded successfully, BIOS service 13h:02h set carry flag to 0, otherwise 1 and stores error code in ax
; if cf is 1 jc will jump
jc kernel_load_error

; increment curr_sector_to_load, decrement number_of_sectors_to_load
sub byte [number_of_sectors_to_load], 1
add byte [curr_sector_to_load], 1
cmp byte [number_of_sectors_to_load], 0
Expand Down Expand Up @@ -103,17 +93,12 @@ kernel_load_error:
; For an example of using service "0Eh" to print "Hello" character by character, please refer to "../examples/bootstrap/1/".
print_string:
; specify function for printing character
mov ah, 0Eh

print_char:
; [MQH] 26 Nov 2019
; It's known that a byte = 8 bits. In x86 there are more two units: a "word" = 16 bits and "doubleword" = 32 bits. Some x86 instructions have multiple variants that work
; with a specific aforementioned unit. "lods" is an example of these instructions, there is "lodsb" which works with a byte, "lodsw" which works with a word and "lodsd" which
; works with doubleword.
;
; The instruction "lods" uses the value which is stored in register "si" (when using lodsb or loadsw) or register "esi" (when using lodsw) and assume that this
; value as memory location. Then according to the used unit a (b)yte (8-bits), (w)ord (16-bits) or (d)oubleword (32-bit) will be read from the memory location DS:(E)SI
; and store these bits in al, ax or eax according to the size of read data.
; char to print is passed via si
; lodsb will transfer first character of the string to the register al and increase value of si by 1 byte
lodsb
cmp al, 0
Expand All @@ -124,47 +109,30 @@ print_char:
jmp print_char

printing_finished:
mov al, 10d ; Print new line
mov al, 10d ; print new line 10d = ENDL
int 10h
; Move the cursor to the beginning
mov ah, 03h
; reading current cursor position
mov ah, 03h ; specify service for reading current position of cursor
mov bh, 0
int 10h
mov ah, 02h
mov dl, 0
; move the cursor to the beginning
mov ah, 02h ; specify service for setting cursor to position 0
mov dl, 0 ; position to be set is passed to dl
int 10h
; print_string is a procedure (or function), therefore we should return. It is called by using "CALL" instead of "JMP".
ret

; ... ;

; "DB" is one of pseudo-instructions which is provided by NASM. A source line in NASM follows the following format
; label: instruction operands ; comment
; The label is optional and even the colon after the label is optional.
; "DB" is used to declare initialized data. The "B" in "DB" means byte.
; the part ", 0" means that the last byte will be 0. In some way resembles
; nul character in C strings.
;
; Put the string in the output of this assembly file. Byte by byte, and the address of
; this string will be in the label, so we can reach the string inside the assembly source code.
title_string db 'The Bootloader of 539kernel.', 0
message_string db 'The kernel is loading...', 0
load_error_string db 'The kernel cannot be loaded', 0
number_of_sectors_to_load db 10d ; 255 sectors = 127.5KB ; [MQH] NEW 4 July 2021
curr_sector_to_load db 2d

; "TIMES" is an NASM pseudo-instruction which repeats an instruction a number of specific times.
; The first operand of "TIMES" is the number of repetitions.
; As we mentioned before "$" means the starting address (or assembly position?) of current instruction.
; "$$" is another special expression which means the starting address (or assembly position?) of current section.
; So, we know that the size of bootstrap should be 512 byte. Two bytes are represent the magic code in the last line
; so 510 bytes remains for us. Our code starts from position $$ and we reached position $. Therefore, $ - $$ gives
; us the size of our code. So, 510 - ( $ - $$ ) gives us the remaining size of the 512 bytes and fills it with zeros
; by using "DB".
; write the magic signature 0xAA55
times 510-($-$$) db 0

; Put the magic code of bootloader in the end of assembly file's output. "W" means word.
; pad with 0 so magic signature is at loc 510-511 (0-based)
dw 0xAA55
9 changes: 8 additions & 1 deletion gdt.asm
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
; The values of the decriptors from Basekernel (kernelcode.S) (https://github.com/dthain/basekernel)
gdt:

; in x86, first entry is null descriptor
null_descriptor : dw 0, 0, 0, 0

; code and data segment of kernel
kernel_code_descriptor : dw 0xffff, 0x0000, 9a00h, 0x00cf
kernel_data_descriptor : dw 0xffff, 0x0000, 0x9200, 0x00cf

; code and data segment of user application
userspace_code_descriptor : dw 0xffff, 0x0000, 0xfa00, 0x00cf
userspace_data_descriptor : dw 0xffff, 0x0000, 0xf200, 0x00cf
; DON'T FORGET TO CHANGE THE SIZE OF THE GDT
; TODO: I think the limit isn't correct
tss_descriptor : dw tss + 3, tss, 0x8900, 0x0000

gdtr:
; size: 6 entries, each 8 byte
gdt_size_in_bytes : dw ( 6 * 8 ) ;= 28h
gdt_base_address : dd gdt

5 changes: 4 additions & 1 deletion idt.asm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
isr_0:
cli
; push number of current interrupt into the stack
push 0
jmp isr_basic

Expand Down Expand Up @@ -294,9 +295,10 @@ isr_48:
jmp irq_basic

isr_basic:
; cli
; call C function interrput_handler
call interrupt_handler
; clean stack frame: pop stack frame and save popped value in eax (chosen arbitrarily)
pop eax
sti
iret
Expand All @@ -321,6 +323,7 @@ irq_basic:
; The value of the flags from Basekernel (kernelcode.S) (https://github.com/dthain/basekernel)
idt:
; handler_name, segment_selector, present, privilege_level, descriptor_size, gate_type (in this case interrupt)
dw isr_0, 8, 0x8e00, 0x0000
dw isr_1, 8, 0x8e00, 0x0000
dw isr_2, 8, 0x8e00, 0x0000
Expand Down
6 changes: 0 additions & 6 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,12 @@ void kernel_main()
{
process_t p1, p2, p3, p4;

// ... //

heap_init();
paging_init();
screen_init();
process_init();
scheduler_init();

// ... //

// video[0] = 'A';
// video[2] = 'B' why not video[1]? this is because the byte after char contains color information
// Each memory location corresponds to Cartesian coordinate [k * 2] = (k % max_x, k / max_x) e.g. [0] = (0, 0), [2] = (1, 0)
Expand All @@ -37,8 +33,6 @@ void kernel_main()
print( "We are now in Protected-mode" );
println();

// ... //

process_create( &processA );
process_create( &processB );
process_create( &processC );
Expand Down
2 changes: 0 additions & 2 deletions paging.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#include "paging.h"

unsigned int *page_directory;

int create_page_entry( int base_address, char present, char writable, char privilege_level, char cache_enabled, char
write_through_cache, char accessed, char page_size, char dirty )
{
Expand Down
46 changes: 32 additions & 14 deletions starter.asm
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
; runs in 16 bits
; to prepare proper environment for main kernel by:
; - init and load GDT
; - set interrupts
; - set video mode
; - change 16 bit real mode to 32 bit protected mode

; tell NASM to generate 16 bit code since we will be using 16 bit real mode
; in the bootloader don't need to do this because: for flat binary the default is 16 bit but we use ELF32
bits 16
; extern tells NASM that there is a function/ variable define outside this code, it will be figured out by the linker
extern kernel_main
extern interrupt_handler
extern scheduler
Expand All @@ -9,48 +19,55 @@ global load_page_directory
global enable_paging

start:
mov ax, cs
mov ax, cs ; set proper memory address of ds depending on the value of cs
mov ds, ax
; --- ;
call load_gdt
call init_video_mode
call enter_protected_mode
call setup_interrupts
call load_task_register
; --- ;
call 08h:start_kernel
load_gdt:
cli
lgdt [gdtr - start]
ret
; turn off interrupts (recommended by x86)
cli
; substract address of start from gdtr, take this resulting address and load the content there
; why gdtr - start instead of gdtr?
; in real mode, gdtr -> is seen as offset from segment register instead of full address
lgdt [gdtr - start]

ret

setup_interrupts:
call remap_pic
call load_idt
ret

; cannot use BIOS to print to screen anymore, so we need to use vga
init_video_mode:
;; Set Video Mode
; Set Video Mode
; call BIOS service 10h:0h to set video mode to number passed in register al
mov ah, 0h
mov al, 03h ; 03h For Text Mode. 13h For Graphics Mode.

; 03h -> text mode with 16 colors, 13h is graphic mode with 256 colors
mov al, 03h
int 10h
;; Disable Text Cursor
; call BIOS service 10h:01h to disable text cursor
mov ah, 01h
mov cx, 2000h
int 10h
ret

enter_protected_mode:
; x86 has cr0-cr7 control register
mov eax, cr0

; last bit of cr0 -> 0: disable protected mode, 1: enable protected mode
or eax, 1
mov cr0, eax
Expand All @@ -59,6 +76,7 @@ enter_protected_mode:
remap_pic:
mov al, 11h
; out port_number, value
send_init_cmd_to_pic_master:
out 0x20, al
Expand Down

0 comments on commit e7bd277

Please sign in to comment.