; File: elevator.asm ; Stage 2 Bootloader - No size limit! ; Job: Load kernel, set up GDT, switch to 64-bit mode, jump to kernel [org 0x7e00] [bits 16] KERNEL_OFFSET equ 0x10000 ; Load kernel at 64KB (safer than 0x1000) BOOT_DRIVE: db 0 ; Store boot drive number ERROR_CODE: db 0 ; Store error code from disk read stage2_start: ; Print stage 2 message mov si, msg_stage2 call print_string ; Save boot drive number (passed from stage1 in DL) mov [BOOT_DRIVE], dl ; Load kernel from disk call load_kernel ; Verify kernel was loaded - check for signature ; Check if there's actual code at 0x10000 mov ax, 0x1000 mov es, ax xor bx, bx mov ax, [es:bx] ; Read first 2 bytes of kernel ; Show what we read (for debugging) push ax xor ax, ax mov es, ax mov si, msg_kernel_check call print_string pop ax call print_hex_word ; Print the word we read ; If it's 0x0000, kernel didn't load test ax, ax jz kernel_load_failed mov si, msg_kernel_ok call print_string ; Disable interrupts cli ; Load GDT lgdt [gdt_descriptor] ; Enable Protected Mode mov eax, cr0 or eax, 0x1 mov cr0, eax ; Far jump to 32-bit code jmp 0x08:protected_mode ; ============================================================ ; LOAD KERNEL FROM DISK ; ============================================================ load_kernel: push ax push bx push cx push dx ; Reset disk system first mov ah, 0x00 mov dl, [BOOT_DRIVE] int 0x13 ; Set up ES:BX for the destination ; KERNEL_OFFSET is 0x10000, so we need ES=0x1000, BX=0x0000 mov ax, 0x1000 mov es, ax xor bx, bx ; BX = 0, so ES:BX = 0x1000:0x0000 = 0x10000 ; Now try loading kernel mov ah, 0x02 ; Read function mov al, 10 ; Number of sectors mov ch, 0 ; Cylinder 0 mov cl, 7 ; Start at sector 7 mov dh, 0 ; Head 0 mov dl, [BOOT_DRIVE] ; Drive number int 0x13 jc kernel_error ; Restore ES to 0 xor ax, ax mov es, ax pop dx pop cx pop bx pop ax ret kernel_error: ; Restore ES xor ax, ax mov es, ax ; Save error code mov byte [ERROR_CODE], ah mov si, msg_kernel_error call print_string ; Show error code mov si, msg_error_code call print_string ; Print error code as hex mov al, [ERROR_CODE] call print_hex ; Show drive number we tried to use mov si, msg_drive call print_string mov al, [BOOT_DRIVE] call print_hex jmp $ kernel_load_failed: xor ax, ax mov es, ax mov si, msg_verify_failed call print_string jmp $ ; ============================================================ ; PRINT STRING (16-bit) ; ============================================================ print_string: pusha mov ah, 0x0e .loop: lodsb test al, al jz .done int 0x10 jmp .loop .done: popa ret ; ============================================================ ; PRINT HEX BYTE (for debugging) ; ============================================================ print_hex: push ax push bx mov bl, al shr al, 4 ; Get high nibble call print_nibble mov al, bl and al, 0x0F ; Get low nibble call print_nibble pop bx pop ax ret print_nibble: cmp al, 9 jle .digit add al, 7 ; Convert to A-F .digit: add al, '0' mov ah, 0x0e int 0x10 ret ; Print a 16-bit word as hex print_hex_word: push ax mov al, ah ; Print high byte first call print_hex pop ax call print_hex ; Print low byte ret ; ============================================================ ; MESSAGES ; ============================================================ msg_stage2: db '[STAGE2] Extended bootloader loaded', 0x0D, 0x0A, 0 msg_kernel_loaded: db '[STAGE2] Kernel loaded, switching to Protected Mode...', 0x0D, 0x0A, 0 msg_kernel_error: db '[ERROR] Kernel load failed!', 0x0D, 0x0A, 0 msg_kernel_check: db '[STAGE2] Verifying kernel load: Read word = 0x', 0 msg_kernel_ok: db '[STAGE2] Kernel load verified.', 0x0D, 0x0A, 0 msg_verify_failed: db '[ERROR] Kernel verification failed!', 0x0D, 0x0A, 0 msg_error_code: db ' Error code: 0x', 0 msg_drive: db ' Drive number: 0x', 0 ; ============================================================ ; GDT - Global Descriptor Table ; ============================================================ align 8 gdt_start: gdt_null: dq 0x0 gdt_code32: ; Selector 0x08 dw 0xffff dw 0x0000 db 0x00 db 10011010b db 11001111b db 0x00 gdt_data32: ; Selector 0x10 dw 0xffff dw 0x0000 db 0x00 db 10010010b db 11001111b db 0x00 gdt_code64: ; Selector 0x18 dw 0xffff dw 0x0000 db 0x00 db 10011010b db 10101111b db 0x00 gdt_data64: ; Selector 0x20 dw 0xffff dw 0x0000 db 0x00 db 10010010b db 10101111b db 0x00 gdt_end: gdt_descriptor: dw gdt_end - gdt_start - 1 ; Limit (size - 1) dd gdt_start ; Base address (org already adjusts this) ; ============================================================ ; 32-BIT PROTECTED MODE ; ============================================================ [bits 32] protected_mode: ; Set up segments mov ax, 0x10 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov esp, 0x90000 ; Print to VGA mov edi, 0xb8000 mov esi, pm_msg call print_pm ; Clear page table area (12KB starting at 0x1000) mov edi, 0x1000 mov ecx, 3072 ; 12KB / 4 bytes = 3072 dwords xor eax, eax rep stosd ; Set up page tables ; PML4[0] -> PDPT at 0x2000 mov dword [0x1000], 0x2003 ; PDPT[0] -> PD at 0x3000 mov dword [0x2000], 0x3003 ; Identity map first 4MB (two 2MB pages) ; PD[0] -> First 2MB mov dword [0x3000], 0x00000083 mov dword [0x3004], 0x00000000 ; PD[1] -> Second 2MB (covers 0x200000 - 0x3FFFFF) mov dword [0x3008], 0x00200083 mov dword [0x300C], 0x00000000 ; Load page table mov eax, 0x1000 mov cr3, eax ; Enable PAE mov eax, cr4 or eax, 0x20 mov cr4, eax ; Enable Long Mode mov ecx, 0xC0000080 rdmsr or eax, 0x100 wrmsr ; Enable Paging mov eax, cr0 or eax, 0x80000000 mov cr0, eax ; Jump to 64-bit mode jmp 0x18:long_mode ; Print in 32-bit protected mode print_pm: mov ah, 0x0f .loop: lodsb test al, al jz .done stosw jmp .loop .done: ret pm_msg: db '[PM32] Setting up Long Mode...', 0 ; ============================================================ ; 64-BIT LONG MODE ; ============================================================ [bits 64] long_mode: ; Set up segments mov ax, 0x20 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov rsp, 0x90000 ; Set up a minimal IDT to prevent triple faults ; Point all interrupts to a dummy handler call setup_idt ; Print message on line 3 mov rdi, 0xb8000 + (160 * 2) mov rsi, lm_msg call print_64 ; Write a marker before jumping to kernel (for debugging) mov rdi, 0xb8000 + (160 * 3) mov rsi, before_kernel_msg call print_64 ; JUMP TO KERNEL! call KERNEL_OFFSET ; If kernel returns, show error mov rdi, 0xb8000 + (160 * 4) mov rsi, kernel_return_msg call print_64 jmp $ ; Set up a minimal IDT setup_idt: ; Fill IDT with different handlers for different exceptions mov rdi, 0x4000 ; IDT location ; Set up first 32 entries (CPU exceptions) with specific handlers mov rcx, 32 mov rax, exception_handler .fill_exceptions: mov word [rdi], ax ; Offset low mov word [rdi + 2], 0x18 ; Code segment mov byte [rdi + 4], 0 ; IST mov byte [rdi + 5], 0x8E ; Present, interrupt gate shr rax, 16 mov word [rdi + 6], ax ; Offset middle shr rax, 16 mov dword [rdi + 8], eax ; Offset high mov dword [rdi + 12], 0 ; Reserved mov rax, exception_handler ; Restore for next iteration add rdi, 16 loop .fill_exceptions ; Fill remaining entries (32-255) with dummy handler mov rcx, 224 mov rax, dummy_handler .fill_rest: mov word [rdi], ax mov word [rdi + 2], 0x18 mov byte [rdi + 4], 0 mov byte [rdi + 5], 0x8E shr rax, 16 mov word [rdi + 6], ax shr rax, 16 mov dword [rdi + 8], eax mov dword [rdi + 12], 0 mov rax, dummy_handler add rdi, 16 loop .fill_rest ; Load IDT lidt [idt_descriptor] ret ; Exception handler - shows what went wrong exception_handler: ; Save registers push rax push rbx ; Get exception number from stack ; (not perfectly accurate but good enough for debugging) ; Write "EXCEPTION!" in red at line 5 mov rbx, 0xb8000 + (160 * 5) mov rax, 0x0C450C580C430C45 ; "ECXE" in red mov [rbx], rax mov rax, 0x0C4F0C490C540C50 ; "PTIO" in red mov [rbx + 8], rax mov word [rbx + 16], 0x0C21 ; "!" in red ; Show the instruction pointer where it failed mov rax, [rsp + 16] ; Get RIP from stack (after our 2 pushes) mov rbx, 0xb8000 + (160 * 6) ; Write "RIP: " mov word [rbx], 0x0F52 ; 'R' mov word [rbx + 2], 0x0F49 ; 'I' mov word [rbx + 4], 0x0F50 ; 'P' mov word [rbx + 6], 0x0F3A ; ':' mov word [rbx + 8], 0x0F20 ; ' ' ; Print RIP value as hex mov rcx, 16 mov rbx, 0xb8000 + (160 * 6) + 10 .print_rip: rol rax, 4 mov dl, al and dl, 0x0F cmp dl, 9 jle .digit add dl, 7 .digit: add dl, '0' mov byte [rbx], dl mov byte [rbx + 1], 0x0F add rbx, 2 dec rcx jnz .print_rip ; Restore and halt pop rbx pop rax ; Infinite loop instead of iretq cli hlt jmp $ ; Dummy interrupt handler for IRQs (not CPU exceptions) dummy_handler: iretq idt_descriptor: dw (256 * 16) - 1 ; Limit (256 entries * 16 bytes) dq 0x4000 ; Base address ; Print in 64-bit mode print_64: mov ah, 0x0f .loop: lodsb test al, al jz .done stosw jmp .loop .done: ret lm_msg: db '[64-BIT] Long mode active...', 0 before_kernel_msg: db '[64-BIT] Calling kernel...', 0 kernel_return_msg: db '[KERNEL] Returned unexpectedly!', 0