Your FAR CALL to 0xF000:0x0000 doesn't work because it isn't the address for Int 0x13 in your interrupt table. Although 0xF000 looks like a reasonable segment in the BIOS area, the chance that Int 0x13's vector starts at offset 0x0000 isn't very high.
I spent considerable time on this question, but some persistence paid off. I found a specific Intel BIOS that exhibits this kind of odd behavior. If the physical address to write starts at 0x00000 the write returns success but the data is incorrect. On my system it seems to write part of BIOS memory rather than the IVT. BIOS bugs of this nature do occur, but fringe cases to find them are hard to find unless you are deliberately debugging the BIOS. It likely went unnoticed because writing the memory from 0x0000:0x0000 with Int 0x13 isn't something you see everyday. My BIOS information:
BIOS Manufacturer: Intel
Version: CR94510J.86a.0045.2007.0418.1532
Date:April 18th, 2007
Alternatively, you can use a different mechanism other than writing to disk. You could get the IVT entries by displaying them to the screen, outputting them on a serial port etc. You could write code to access the hard disk controller directly rather than using Int 0x13 to do it for you.
I didn't attempt to update the BIOS, although BIOS revision history doesn't say anything about this bug/feature.
To find workarounds I even went out of my way to turn off the A20 line so that I could try other segment:offset addresses that map to the same physical address 0x00000. 0xFF00:0x1000 = 0x10000, 0xFFFF:0x0010 = 0x10000, and 0x0000:0x0000 = 0x00000. When A20 is off physical address 0x10000 equals 0x00000. If I start reading from the second entry in the IVT at 0x0000:0x0004 there is absolutely no issue.
It very much smells like a bug. I couldn't find any work around except to copy the IVT table to another location in memory (I copied it right after the bootloader) and then write it to disk. I took your original code and enhanced it:
- Copy the 1024 byte IVT to 0x0000:0x7e00.
- Keep interrupts enabled with STI (although you can probably still use CLI).
- Use the boot drive number passed by BIOS in register DL. This allows us to write(or read) the disk that was booted on without having to hard code a value.
- Retry the disk operation 3 times before failing with an error message.
- Inform user when the disk operation says it is successful.
- Prompt user to hit any key before continuing the boot cycle.
- Since I was using USB and Hard Drive emulation I had to add a partition table with a bootable entry. The partition is self referential and makes the entire disk look like a partition with the MBR as the first sector. This can be removed if you are booting from real hard drives, but keeping it shouldn't hurt.
- Commented the code
boot.asm:
DISK_RETRIES EQU 3 ; Retry disk operation 3 times on error
bits 16
org 0x7c00
start:
xor ax, ax
mov es, ax ; Set ES=0
mov ds, ax ; Set DS=0
mov ss, ax
mov sp, 0x7c00 ; Place our stack below the bootloader @ 0x0000:0x7c00
sti ; Enable external interrupts
; Appears to be a bug with Int 0x13 on some BIOSes where an attempt
; to write to disk from the physical address 0x00000 fails. Copy the
; 1024 byte IVT from 0x0000:0x0000 to 0x0000:0x7e00 just after bootloader
mov di, 0x7e00 ; Destination ES:DI = 0x0000:0x7e00
mov si, 0x0000 ; Source DS;SI = 0x0000:0x0000
mov cx, 1024/ 2 ; IVT is 512 16-bit words
rep movsw ; Copy all 512 words (1024 bytes)
write_sector:
mov bp, DISK_RETRIES ; Set disk retry count
; Write CHS=(0,0,4)
mov es, ax
mov bx, 0x7e00 ; ES:BX=0x0000:0x7e00 where copy of the IVT resides
mov cx, 0x0004 ; ch=cylinder=0, cl=sector=4
xor dh, dh ; dh=heads=0
; Use DL passed by the BIOS
.retry:
mov ax, 0x0302 ; Call function 0x03 of int 13h (write sectors)
; AL = 2 = Sectors to write
int 0x13 ; Write 512 bytes from memory @ 0x0000:0x7e00
; to CHS=(0,0,4) LBA=3 on the boot drive
jc .disk_error ; If CF set then disk error
.success:
mov si, successMsg ; Display messageabout success
call print_string
mov si, anyKeyMsg ; Press any key
call print_string
xor ax,ax ; Wait for keystroke
int 0x16
xor ax,ax ; Tell BIOS to continue and disk non-bootable
int 0x18 ; Our bootloader has exited back to BIOS at this point
.disk_error:
xor ah, ah ; Int13h/AH=0 is drive reset
int 0x13
dec bp ; Decrease retry count
jge .retry ; If retry count not exceeded then try again
error_end:
; Unrecoverable error; print drive error; enter infinite loop
mov si, diskErrorMsg ; Display disk error message
call print_string
end_loop:
hlt
jmp end_loop ; Infinite loop to prevent processor from
; getting past this point.
; Function: print_string
; Display a string to the console on display page 0
;
; Inputs: SI = Offset of address to print
; Clobbers: AX, BX, SI
print_string:
mov ah, 0x0e ; BIOS tty Print
xor bx, bx ; Set display page to 0 (BL)
jmp .getch
.repeat:
int 0x10 ; print character
.getch:
lodsb ; Get character from string
test al,al ; Have we reached end of string?
jnz .repeat ; if not process next character
.end:
ret
diskErrorMsg: db "Unrecoverable disk error!", 0x0d, 0x0a, 0
successMsg: db "Successfully written data to disk!", 0x0d, 0x0a, 0
anyKeyMsg: db "Press any key to continue...", 0x0d, 0x0a, 0
times 446-($-$$) db 0 ; Pad remainder of boot sector up to first partition entry
part1_entry:
db 0x80 ; Bootable partiion
db 0x00, 0x01, 0x00 ; CHS of first absolute sector (MBR) of hard drive
; Head=0, Sector=1, Cylinder=0
db 0x0c ; Partition type (has to be non-zero)
; 0x0c = Win 95 FAT32 (LBA)
db 0x00, 0x01, 0x00 ; CHS of last absolute sector (MBR) of hard drive
; Head=0, Sector=1, Cylinder=0
; We are effectively saying Size of partition is 1 sector
dd 0x0 ; LBA of first absolute sector (0=MBR)
dd 0x1 ; Number of sectors in partition. We set it to 1 but if you
; wish you could set it to the number of sectors on the disk
times 510-($-$$) db 0 ; Pad remainder of boot sector up to boot signature. This zeroes
; partition entries 2,3,4 effectively making them inactive
dw 0xaa55
It can be built into a bootloader/MBR with:
nasm -f bin boot.asm -o boot.bin
On my real system with this bootloader, rather than getting memory I didn't request this code gave me a proper dump of the real mode IVT. The hex dump seems reasonable as most entries have 0xF000 as the segment and a non zero offset:
0000000 ff53 f000 ff53 f000 e2c3 f000 27b8 f000
0000010 ff53 f000 ff54 f000 27b8 f000 27b9 f000
0000020 fea5 f000 e987 f000 27b9 f000 27b9 f000
0000030 27b9 f000 27ac f000 ef57 f000 27b9 f000
0000040 0014 c000 f84d f000 f841 f000 4902 f000 <--- offset 0x4c is Int 0x13 0xf000:0x4902
0000050 e739 f000 ec6a f000 e82e f000 efd2 f000
0000060 53b5 f000 e6f2 f000 fe6e f000 ff53 f000
0000070 ff53 f000 f0a4 f000 efc7 f000 7011 c000
0000080 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000090 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00000a0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00000b0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00000c0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00000d0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00000e0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00000f0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000100 ec59 f000 00b0 0040 f065 f000 6c11 c000
0000110 27b9 f000 27b9 f000 00c0 0040 27b9 f000
0000120 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000130 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000140 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000150 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000160 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000170 27b9 f000 27b9 f000 27b9 f000 27b9 f000
0000180 0000 0000 0000 0000 0000 0000 0000 0000
0000190 0000 0000 0000 0000 0000 0000 27b9 f000
00001a0 27b9 f000 27b9 f000 27b9 f000 27b9 f000
00001b0 27b9 f000 0014 c000 27b9 f000 27b9 f000
00001c0 660b f000 27a3 f000 27b9 f000 c394 f000
00001d0 2c77 f000 2794 f000 3229 f000 3229 f000
00001e0 0000 0000 0000 0000 0000 0000 0000 0000
... The remainder up to 0x400 was all zero
mov dl, 0x80
and the value passed in DL by the BIOS will be used. The BIOS passes the boot drive number in DL for you already. This is preferred because this would work if booting off a hard drive other than 0x80 (or even removable media that might be 0x00 or 0x01 etc).Hard coding 0x80 just means this will only ever work from the first hard drive. – Michael Petchseg:0000
for your far call, which seemed unlikely, so sanity checking your IVT dump is important. Also, do the other IVT entries look plausible? Post some of them. – Peter Cordes