my bootloader consists of two 512 byte stages. stage 1 gets loaded by the bios into the MBR area. stage1 then proceeds to load stage2 from the drive and jumps to it.
i confirmed with a hex editor that the size of final binary "program.bin" is exactly 1024 byte long and contains both "signatures" (last two bytes of each stage, 0xAA55 for stage1 (MBR signature) and 0xCC77 for stage2).
expected outputs are:
1 // stage1 started
0000 or 0080 // drive# in hex
CC77 // stage2 "signature" in hex
2 // stage2 started
this works fine in QEMU but fails in virtualbox and on hardware. it looks to me like the stage2 load fails silently (error branch doesn't get called) and i am looking to solve this for two weeks now without success.
stage1.asm:
global _start
extern _stage2
extern _stage2data
BITS 16
_start:
; init registers
xor ax, ax
mov es, ax
mov gs, ax
mov ss, ax
mov sp, 0x7C00 ; right before MBR, counting upwards
mov ax, 0x7C0 ; set DS to 0x7c0 so pointing at 0x0 resolves to 0x7C0:x0000 = 0x7C00
mov ds, ax
cld ; set direction flag to make string operations count forward
; mark start of stage 1 by printing "1"
mov al, '1'
call real_mode_print_char
call real_mode_new_line
print_drive_number:
; drive# is put into DL by BIOS
mov dh, 0x0
mov bx, dx
call real_mode_print_hex
load_sector2:
mov al, 0x01 ; load 1 sector
mov bx, 0x7E00 ; destination, right after your bootloader
mov cx, 0x0002 ; cylinder 0, sector 2
; mov dl, [BootDrv] ; boot drive
xor dh, dh ; head 0
call read_sectors_16
jnc execute_stage2 ; if carry flag is set, disk read failed
jmp error
execute_stage2:
mov bx, [_stage2data] ; print data at _stage2data to confirm stage 2 was loaded
call real_mode_print_hex
jmp _stage2 ; start execude instructions of _stage2
error:
; print "E" if an error occurs
mov al, 'E'
call real_mode_print_char
; infinite loop
loop:
jmp loop
; read_sectors_16
;
; Reads sectors from disk into memory using BIOS services
;
; input: dl = drive
; ch = cylinder[7:0]
; cl[7:6] = cylinder[9:8]
; dh = head
; cl[5:0] = sector (1-63)
; es:bx -> destination
; al = number of sectors
;
; output: cf (0 = success, 1 = failure)
read_sectors_16:
push ax
mov si, 0x02 ; maximum attempts - 1
.top:
mov ah, 0x02 ; read sectors into memory (int 0x13, ah = 0x02)
int 0x13
jnc .end ; exit if read succeeded
dec si ; decrement remaining attempts
jc .end ; exit if maximum attempts exceeded
xor ah, ah ; reset disk system (int 0x13, ah = 0x00)
int 0x13
jnc .top ; retry if reset succeeded, otherwise exit
.end:
pop ax
retn
# print a number in hex
# IN
# bx: the number
# CLOBBER
# al, cx
real_mode_print_hex:
mov cx, 4
.lp:
mov al, bh
shr al, 4
cmp al, 0xA
jb .below_0xA
add al, 'A' - 0xA - '0'
.below_0xA:
add al, '0'
call real_mode_print_char
shl bx, 4
loop .lp
call real_mode_new_line
ret
real_mode_new_line:
mov al, 0x0D
call real_mode_print_char
mov al, 0x0A
call real_mode_print_char
ret
real_mode_print_char:
push bx
xor bx, bx ; Attribute=0/Current Video Page=0
mov ah, 0x0e
int 0x10 ; Display character
pop bx
ret
; boot signature
TIMES 510-($-$$) db 0
mbr_id:
dw 0xAA55
stage2.asm:
global _stage2
global _stage2data
BITS 16
_stage2:
mov al, '2'
call bios_print_char
loop:
jmp loop
bios_print_char:
push bx
xor bx, bx ; Attribute=0/Current Video Page=0
mov ah, 0x0e
int 0x10 ; Display character
pop bx
ret
; boot signature
TIMES 510-($-$$) db 0
_stage2data:
dw 0xCC77
linker script "linker.ld":
ENTRY(_start)
OUTPUT_FORMAT(binary)
SECTIONS
{
output :
{
stage1.elf(.text)
stage2.elf(.text)
}
}
i use the following commands to compile and link everything together:
nasm -f elf64 stage1.asm -o stage1.elf
nasm -f elf64 stage2.asm -o stage2.elf
ld -m elf_x86_64 -o program.bin stage2.elf stage1.elf -nostdlib -T linker.ld
i run the binary on QEMU with:
qemu-system-x86_64 -drive format=raw,file=program.bin
to run it on hardware i write the binary to a USB with:
dd if=program.bin of=/dev/sdb1 && sync
Your bootloader actually looks pretty good. As @jester pointed out on real hardware if you are booting USB using Floppy Disk Emulation (FDD) then you will most likely need a BPB. There are indications in your screenshot that you are booting USB as Hard Disk Emulation (HDD) since the drive number appears to be 0x0080. If that is the case a BPB isn't necessary.
When using USB HDD emulation you may need a partition table with one partition marked active/bootable for some BIOSes to recognize the drive as bootable. Without it some BIOSes may refuse to recognize the drive as something it should boot even if it has the proper disk signature (0xaa55
) in the last 2 bytes.
I believe the real problem lies in how you are writing to the USB drive. You are using:
dd if=program.bin of=/dev/sdb1 && sync
/dev/sdb1
is actually the first partition, not the beginning of the drive. What it appears you want is to write to the beginning of the drive with:
dd if=program.bin of=/dev/sdb && sync
You may ask: how is it that the bootloader you wrote was actually running if it wasn't written to the beginning of the drive? My suspicion is that your USB drive was formatted with a Master Boot Record (MBR) that chain loaded the Volume Boot Record (VBR) bootloader in partition 1 and then started executing it. Such a chainloading MBR is very possible if the USB stick was formatted and partitioned in Windows. Windows usually formats USB as one large partition and places an MBR in the first sector of the drive that chain loads the VBR from the first sector of the first partition.
Since you appear to be booting everything as Hard Disk media, you may wish to consider using the Extended disk function like Int 13h/AH=42h rather than Int 13h/AH=2h. Int 13/AH=2 is very limited in what it can load with CHS addressing rather than LBA addressing when dealing with larger media (usually greater than about 8GiB).