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?
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.