0

While writing the below bootloader (with no prior experience in the field), I've encountered many completely different implementations for x86; Now I've been trying to cover all the cases for it to become a "complete" example.

The code below is running as expected (by simply running qemu-systems-x86_64 kern.img), but there's one main thing I still can't completely wrap my head around, and it's what about the stack

  • do I need to explicitly define the stack? what happens if I don't? (it's working as is)
  • how call / ret instructions work without defining the stack explicitly, I'm aware that both modify the stack (push/pop IP/BP)

Any tips, corrections, or suggestions are welcomed.

org 0x7C00
bits 16

start:
    mov ax, cs
    mov ds, ax

    mov si, title_str
    call print_str

    mov si, loading_str
    call print_str

    call load_kernel_to_mem
    jmp 0x0900:0x00

load_kernel_to_mem:
    mov ax, 0x0900
    mov es, ax
    mov ah, 0x02                                ; read sectors from disk into mem
    mov al, 0x01                                ; sectors to read count
    mov ch, 0x00                                ; track
    mov cl, 0x02                                ; sector
    mov dh, 0x00                                ; head
    mov dl, 0x80                                ; disk type 1st hard disk
    mov bx, 0x00                                ; ES:BX buffer address pointer (bx=offset)
    int 0x13                                    ; CF=0 on success, otherwise CF=1 AX=errno
    jc handle_load_err                          ; handle error if CF=1
    ret

handle_load_err:
    mov si, load_error_str
    call print_str
    hlt                                         ; stop cpu from executing further instruction (breaks on interrupts)

print_str:
    mov ah, 0x0E                                ; teletype output
.loop lodsb                                     ; load byte from si to al then advance si
    cmp al, 0                                   ; cmp loaded byte to 0
    je finished_printing                        ; jmp if curr byte is 0
    int 0x10                                    ; print byte to screen
    jmp .loop

finished_printing:
    mov al, 10d                                 ; print new line
    int 0x10
    mov ah, 0x03                                ; get cursor position and shape
    mov bh, 0                                   ; page number
    int 0x10
    mov ah, 0x02                                ; set cursor position
    mov dl, 0                                   ; col 0
    int 0x10
    ret

title_str           db "Bootloader", 0
loading_str         db "Loading...", 0
load_error_str      db "Failed to load", 0

times 510 - ($ - $$) db 0
dw 0xAA55
Elia
  • 762
  • 1
  • 12
  • 28
  • 2
    An MBR bootloader starts with SS:SP pointing to usable stack space, with at least enough space for interrupts or bios calls, and some call/ret. But you don't know where if you don't set it yourself. [How should the stack be initialized to in an x86 real mode bootloader to prevent conflicts with BIOS?](https://stackoverflow.com/q/61296865) / [Which value should be used for SP for booting process?](https://stackoverflow.com/q/10598802) / [Is reading stack at bootloader start safe?](https://stackoverflow.com/q/62313028) – Peter Cordes Dec 02 '22 at 14:29
  • 2
    Yes, you should define your stack so that you know where it is. Otherwise you may be overwriting stuff. You can rely on it not directly overlapping your boot sector but for example it could be at `0x9000` where you load your kernel, causing corruption. – Jester Dec 02 '22 at 14:30
  • @PeterCordes In the BIOS realm, you cannot really rely on anything. You should always define the stack yourself. BIOSes are not known for being reliable. – DarkAtom Dec 02 '22 at 14:33
  • @DarkAtom: If SS:SP didn't point somewhere with at least a few bytes of stack space, even `int` instructions wouldn't work so a normal bootloader couldn't boot, including mainstream commercially important bootloaders like DOS or Windows. So we can pretty much assume that any BIOS will have pointed SS:SP somewhere that won't overwrite BIOS data or parts of the 512-byte MBR itself. That's all I claimed, no more but no less; a toy Hello World MBR can use it. (I did edit my first comment to point out that you don't know where, so it could conflict with anything you load from disk.) – Peter Cordes Dec 02 '22 at 14:37
  • You are completely right, but that is assuming that you have a sane system. What if during initialization the BIOS used a stack located at 0x7D00? According to this [OSDev page](https://wiki.osdev.org/Boot_Sequence), some BIOSes even give you control in Protected Mode. Unless you have UEFI (and even then...it's questionable), assuming a sane environment might not be the safest option :)) – DarkAtom Dec 02 '22 at 14:49
  • @DarkAtom: I don't know how stupid it's possible for the initial stack to be and still not break DOS or Windows bootloaders. Since they do load more stuff, they probably do set SS:SP fairly early on, but if timer or keyboard interrupts could lead to overwriting bytes at the end of the 512B that might break some. I keep mentioning DOS/Windows bootloaders because they're commercially relevant, so much so that my understanding is that having them work is just about the only limiting factor in how much a BIOS's MBR handling can deviate from any standards, that they're the only test-case for some – Peter Cordes Dec 02 '22 at 15:42
  • 1
    @DarkAtom: I guess part of my reason for not wanting to make too big a deal out of it is that in 2022, legacy BIOS MBRs are less and less relevant for new development other than toy projects for people who thought that a partly software-emulated IBM-PC environment (via SMI interrupts and stuff) was more low level or useful to learn than 64-bit mode. There are retrocomputing software projects like ELKS, but they mostly already have bootloaders. – Peter Cordes Dec 02 '22 at 15:48
  • 1
    Classic Microsoft MBR would set the stack pointer to 0:7c00h, then copy the code loaded at 0:7c00h to 0:0600h and jump there. The code at 0:0600h would then load the active partition boot sector code into 0:7c00h and jump there. – rcgldr Dec 02 '22 at 16:51

0 Answers0