assemblynasmx86-16bootloaderposition-independent-code

Position independent code in 16-bit real mode, bootloading/floppy read


The following source files are assembled separately (into raw binaries) and loaded onto sectors 1 and 2, respectively, of a virtual floppy. This floppy then serves as the boot medium for a qemu-system-i386 VM.

The "bootloader" reads the "first program" from sector 2 of the floppy, and then it jumps to the memory containing the code just read. The following code works as desired (i.e. the "first program" welcome message is printed), but I had to specify ORG 0x001E in the source of the "first program" (obtained by examining the bootloader's code in a hex editor). 0x001E is the offset of the temp buffer, which holds the code read from the floppy.

"bootloader":

BITS 16

bootloader_main:
    mov bx, 0x07C0      ; Set data segment to bootloader's default segment
    mov ds, bx


    mov ah, 0x02        ; BIOS int13h "read sector" function
    mov al, 1           ; Number of sectors to read
    mov cl, 2           ; Sector to read
    mov ch, 0           ; Cylinder/track
    mov dh, 0           ; Head
    mov dl, 0           ; Disk number (here, the floppy disk)
    mov bx, 0x07C0      ; Segment containing the destination buffer
    mov es, bx
    mov bx, temp        ; Destination buffer offset
    int 0x13

    jmp temp

    ret
;end bootloader_main




temp: times 60 db 17

times 510-($-$$) db 0       ; Pad rest of sector and add bootloader   
dw 0xAA55                      signature

"first program":

BITS 16
ORG 0x001E       ; Assume that this code will be located 0x001E bytes     
                   after start of bootloader (in RAM)

mov bx, string      ; Print a welcome string
mov ah, 0x0E
print_loop:
    mov al, byte [bx]
    int 0x10
    inc bx
    cmp byte [bx], 0
    jne print_loop
;end print_loop


string: db "This is the first program.", 0

Alternatively, I could use ORG 0x200 and 0x200 for the buffer instead of temp (i.e. load the program into RAM just after the bootloader), but neither of these hacks seems sustainable when it comes to creating useful operating systems. How do I avoid this kind of hardcoding of addresses?


Solution

  • You can avoid hard coding of addresses by using segments. Load the "first program" at an address that's a multiple of 16 and load DS with corresponding segment (address / 16) and then far jump to segment:0 where segment is where you loaded the program. Use ORG 0 in the loaded program.

    For example:

    BITS 16
    
    bootloader_main:
        mov ax, 0x07C0      ; Set data segment to bootloader's default segment
        mov ds, ax
    
        mov ah, 0x02        ; BIOS int13h "read sector" function
        mov al, 1           ; Number of sectors to read
        mov cl, 2           ; Sector to read
        mov ch, 0           ; Cylinder/track
        mov dh, 0           ; Head
        mov bx, program_seg ; load program at program_seg:0
        mov es, bx
        xor bx, bx
        int 0x13
    
        mov ax, program_seg
        mov ds, ax
        mov ss, ax          ; set stack to end of program_seg
        mov sp, 0
        jmp program_seg:0
    
    bootloader_end:
    program_seg equ (bootloader_end - bootloader_main + 0x7c00 + 15) / 16
    
    times 510-($-$$) db 0       ; Pad rest of sector and add bootloader   
    dw 0xAA55                   ;   signature
    
    BITS 16
    ORG 0
    
    mov bx, string      ; Print a welcome string
    mov ah, 0x0E
    print_loop:
        mov al, byte [bx]
        int 0x10
        inc bx
        cmp byte [bx], 0
        jne print_loop
    ;end print_loop
    
    
    string: db "This is the first program.", 0
    

    I've removed the mov dl, 0 instruction because you shouldn't hard code this value. The BIOS will pass the drive number of the boot device in DL, so you don't need to change it.