Reputation: 309
I am currently writing a bootloader designed to load a program longer than the bootsector will allow. However, every time I run the program (I test it in both Virtualbox and QEMU), the disk read fails, and the disk reset also fails.
The bootloader is designed to load the sector immediately after it (this is to be a FAT16 volume, so I have made this a reserved sector in the disk description), and run the program immediately after. However, the disk read always fails (CF gets set to 1), and the disk reset does the same. This happens in both Virtualbox and QEMU.
This is my complete code for both the bootsector and second sector:
BITS 16
jmp strict short main ; Jump to main bootloader
nop ; Pad out remaining bytes until boot descriptor
; Disk descriptor
OEM_name db "HOUSE " ; Disk label
bytes_sector dw 0x0200 ; Bytes per sector
sectors_cluster db 0x01 ; Sectors per cluster
sectors_reserved dw 0x0002 ; Number of sectors reserved (in this case 1 for MARBLE, the rest for R)
fats db 0x02 ; Number of file allocation tables
max_root_entries dw 0x0200 ; Max number of root entries
sectors dw 0x1040 ; Number of sectors
medium_type db 0xF0 ; Type of medium (removable or fixed?)
sectors_fat dw 0x0010 ; Sectors per file allocation table
sectors_track dw 0x0020 ; Sectors per track
heads dw 0x0002 ; Number of heads
hidden_sectors dd 0x00000000 ; Number of sectors before partition
total_sectors dd 0x00000000 ; Number of sectors in medium (zero because 2B != 0)
drive_number db 0x00 ; Drive number (for BIOS int 0x13)
drive_signature db 0x00 ; NOT USED
ext_signature db 0x29 ; Extended boot signature
volume_serial dd 0x00000000 ; Volume's serial number
volume_label db "HOUSEHOUSE "; Volume label
fs_type db "FAT16 " ; Filesystem type
main:
mov sp, 0x1000 ; 4K of stack
mov ax, word [sectors_reserved] ; Read all reserved sectors
sub al, 0x01 ; Except this one
mov ah, 0x02 ; Read disk sectors
mov bx, r_buffer ; Read contents into our buffer
mov ch, 0x00 ; Cylinder 0
mov cl, 0x02 ; Sector 2
mov dh, 0x00 ; Head 0
jmp .read ; Read the disk
.reset:
pusha ; Push register states to stack
call lps
mov ah, 0x00 ; Reset disk
int 0x13 ; BIOS disk interrupt
jnc .read ; If successsul, read again
call lps
mov ah, 0x00 ; Otherwise, prepare to reboot
int 0x19 ; Reboot
.read:
call lps
int 0x13 ; BIOS disk interrupt
jc .reset ; If failed, reset disk
jmp r_buffer ; Otherwise, jump to R
lps: ; Debug message
pusha
mov ah, 0x0E
mov al, 0x52
mov bh, 0x00
int 0x10
mov ah, 0x00
int 0x16
popa
ret
times 510-($-$$) db 0x00 ; Pad remainder of boot sector with zeros
sig dw 0xAA55 ; Boot signature
r_buffer: ; Space in memory for loading R
r_start: ; Beginning of second sector
mov ax, 0x07C0
add ax, 0x0220
mov ss, ax
mov ax, 0x07C0
mov ds, ax
mov si, success ; Successful
call print_str ; Print!
hlt ; Halt here
print_str: ; Prints string pointed to by REGISTER SI to cursor location (si=str)
pusha ; Push register states to stack
mov ah, 0x0E ; Print in teletype mode
mov bh, 0x00 ; Page 0
.repeat:
lodsb ; Load next character from si
cmp al, 0x00 ; Is this a null character?
je .ret ; If it is, return to caller
int 0x10 ; Otherwise, BIOS interrupt
jmp .repeat ; Do the same thing
.ret:
popa ; Restore register states
ret ; Return to caller
.data:
success db "!", 0x00 ; Success!
times 1024-($-$$) db 0x00 ; Pad remainder of reserved sectors with zeros
As I said, the code in the second sector is supposed to be run, but this doesn't happen as the disk reset fails.
Upvotes: 3
Views: 120
Reputation: 47553
@ecm picked up on most things I saw. Before accessing any data you need to set up the segment registers and ensure you have an ORG
(origin point) that is proper for the values you load in the segment registers (especially DS). By default when there is no ORG
directive (or equivalent) the default is 0x0000.
In real-mode every logical address is made up of 2 components - a segment and an offset. The physical address that is computed by the CPU is based on the formula (segment*16)+offset. Think of ORG
as the starting offset. If you use a segment of 0x0000 then you need an offset of 0x7c00. (0x0000*16)+0x7c00=0x7c00. 0x7c00 is where the bootloader starts. Since you are writing sectors to memory above 0x7e00 it would be simpler to set the stack to 0x0000:0x7c00 to grow down from just under the bootloader towards the beginning of memory.
Since you are using string instructions like LODSB
you should ensure the direction flag (DF) is cleared with the CLD
instruction so that string operations advance forward in memory. You can't rely on DF being clear when your bootloader starts executing.
When reading the disk Int 13h/AH=2 clobbers AX. If there is an error you will need to reload AH with 2 and AL with the number of sectors to read. If you rework the code you can use SI to store the number of sectors to read temporarily. Usually you don't have to check if a disk reset fails, just redo the read operation again. Try the operation a few times and then go into a failure state / reboot. I modified your code to put a retry count in DI. Each time a reset is done the retry count is reduced by 1. If the retry count >= 0 then the read is attempted. If the retry count is <= 0 then it reboots.
When jumping to the code at memory address 0x7e00 where the reserved sectors are read to you can take the opportunity to ensure CS is set to 0x0000 as well by using a FAR JMP.
If you set up the segment to 0x0000 in the boot sector you can reuse them in the second stage. If you intend to enter protected mode from your bootloader I do not recommend using segment values other than 0x0000 so that logical addresses and physical addresses in the first 64KiB are the same. This is very useful when loading a GDT and making the jump into protected mode.
When using HLT
it is a good idea to make sure interrupts are off (use CLI
) otherwise the HLT
will exit on the next interrupt and the processor will continue executing what happens to be in memory after that. On real hardware it is possible that a Non-Maskable Interrupt (NMI) can occur even with interrupts off so it is a good idea to put the HLT
in a loop.
With these changes in mind, this code should work:
BITS 16
org 0x7c00
jmp strict short main ; Jump to main bootloader
nop ; Pad out remaining bytes until boot descriptor
; Disk descriptor
OEM_name db "HOUSE " ; Disk label
bytes_sector dw 0x0200 ; Bytes per sector
sectors_cluster db 0x01 ; Sectors per cluster
sectors_reserved dw 0x0002 ; Number of sectors reserved (in this case
; 1 for MARBLE, the rest for R)
fats db 0x02 ; Number of file allocation tables
max_root_entries dw 0x0200 ; Max number of root entries
sectors dw 0x1040 ; Number of sectors
medium_type db 0xF0 ; Type of medium (removable or fixed?)
sectors_fat dw 0x0010 ; Sectors per file allocation table
sectors_track dw 0x0020 ; Sectors per track
heads dw 0x0002 ; Number of heads
hidden_sectors dd 0x00000000 ; Number of sectors before partition
total_sectors dd 0x00000000 ; Number of sectors in medium (zero because 2B != 0)
drive_number db 0x00 ; Drive number (for BIOS int 0x13)
drive_signature db 0x00 ; NOT USED
ext_signature db 0x29 ; Extended boot signature
volume_serial dd 0x00000000 ; Volume's serial number
volume_label db "HOUSEHOUSE "; Volume label
fs_type db "FAT16 " ; Filesystem type
main:
xor ax, ax ; AX = 0
mov ds, ax ; DS = ES = 0
mov es, ax
mov ss, ax ; SS:SP = 0x0000:0x7c00 (grows down below bootloader)
mov sp, 0x7c00
cld ; Set forward direction for string instructions
mov si, word [sectors_reserved] ; Read all reserved sectors
dec si ; Except this one. SI = sectors to read
mov di, 3 ; retry count of 3 and then give up
mov bx, r_buffer ; Read contents into our buffer
mov ch, 0x00 ; Cylinder 0
mov cl, 0x02 ; Sector 2
mov dh, 0x00 ; Head 0
jmp .read ; Read the disk
.reset:
call lps
dec di ; Reduce retry count
jge .read ; If retry >= 0 then try again
; Otherwise retry count exceeded - reboot
mov ah, 0x00
int 0x19 ; Reboot
.read:
mov ax, si ; Transfer sector read count to AX
mov ah, 0x02 ; Read disk sectors
call lps
int 0x13 ; BIOS disk interrupt
jc .reset ; If failed, reset disk
jmp 0x0000:r_start ; Otherwise, jump to r_start. Set CS=0
lps: ; Debug message
pusha
mov ah, 0x0E
mov al, 0x52
mov bh, 0x00
int 0x10
mov ah, 0x00
int 0x16
popa
ret
times 510-($-$$) db 0x00 ; Pad remainder of boot sector with zeros
sig dw 0xAA55 ; Boot signature
r_buffer: ; Space in memory for loading R
r_start: ; Beginning of second sector
mov si, success ; Successful
call print_str ; Print!
cli
.endloop:
hlt ; Halt here
jmp .endloop
print_str: ; Prints string pointed to by REGISTER SI
; to cursor location (si=str)
pusha ; Push register states to stack
mov ah, 0x0E ; Print in teletype mode
mov bh, 0x00 ; Page 0
.repeat:
lodsb ; Load next character from si
cmp al, 0x00 ; Is this a null character?
je .ret ; If it is, return to caller
int 0x10 ; Otherwise, BIOS interrupt
jmp .repeat ; Do the same thing
.ret:
popa ; Restore register states
ret ; Return to caller
.data:
success db "!", 0x00 ; Success!
times 1024-($-$$) db 0x00 ; Pad remainder of reserved sectors with zeros
Upvotes: 2