I am writing a MBR and using QEMU for testing.
When using read sectors into memory (int 0x13, ah=0x02
), the int
instruction seems to block execution of my program, and it proceeds to hang. I have tested this with various print statements to confirm that it is this particular instruction blocking.
What could make the interrupt block? I thought this could only be done with the cli
instruction, and even then it does not block int
instructions.
For context, this is the code leading up to the blocking interrupt in read_sectors_16
:
[bits 16]
[ORG 0x7C00]
jmp 0x000:start_16 ; ensure cs == 0x0000
reset_failure_str db 'error: boot disk reset', 13, 10, 0
read_failure_str db 'error: boot disk read', 13, 10, 0
boot_segaddr dw 0x7E00
read_attempt_str db 'read attempt', 13, 10, 0
end_read_attempt_str db 'end read attempt', 13, 10, 0
start_16:
;; Initialize segment registers
mov ax, cs
mov ds, ax
mov es, ax
jmp load_bootsector_2
load_bootsector_2: ; Read program from disk
;; dl set by BIOS to the drive number MBR was loaded from
mov cx, 0x0002 ; cylinder 0, sector 2
xor dh, dh ; head 0
mov al, 0x01 ; load 1 sector
mov bx, boot_segaddr ; destination - load right after the boot loader
call read_sectors_16
jnc .success
mov si, read_failure_str
call print_string_16
jmp halt ; halt
.success:
jmp boot_segaddr:0000 ; jump to program
And here is the function with the blocking interrupt:
;;; read_sectors_16
;;;
;;; Read sectors from disk in 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:
pusha
mov di, 0x02 ; set attempts (max attempts - 1)
.attempt:
mov ah, 0x02 ; read sectors into memory (int 0x13, ah = 0x02)
int 0x13 ; TODO: this call is not returning!
jnc .end ; exit if read succeeded
dec di ; record attempt
test di, di
jz .end ; end if no more attempts
xor ah, ah ; reset disk (int 0x13, ah = 0x00)
int 0x13
jnc .attempt ; retry if reset succeeded, otherwise exit
jmp .end
.end:
popa
ret
The thing that stands out are your segments. First your code defines boot_segaddr
as:
boot_segaddr dw 0x7E00
This creates a 16-bit word in memory with the value 0x7E00. You then have these 2 lines:
mov bx, boot_segaddr
[snip]
jmp boot_segaddr:0000
In both these cases boot_segaddr
is being used as an immediate. You are using the address of boot_segaddr
, not the value boot_segaddr
points to.
I would alter boot_segaddr dw 0x7E00
to be a constant value (using EQU
) and rename it to look like:
BOOT_OFFSET EQU 0x7E00
You then can modify mov bx, boot_segaddr
to be:
mov bx, BOOT_OFFSET
This would have the effect of moving 0x7E00 to BX. The call to Int 13/AH=2 would read the sectors starting at ES:BX = 0x0000:0x7E00 which is what you want.
The next problem is if we reuse the same constant for the FAR JMP like this:
jmp BOOT_OFFSET:0000
This would result in a FAR JMP to 0x7E00:0x0000. Unfortunately this is physical address (0x7E00<<4)+0x0000 = 0x7E000 which is not where you want to be jumping. You want to jump to physical address 0x07E00. You really want a FAR JMP to 0x07E0:0x0000 which would be physical address (0x07E0<<4)+0x0000 = 0x7E00. To get the FAR JMP working properly we can shift BOOT_OFFSET
right 4 bits. You can change the line to be:
jmp (BOOT_OFFSET>>4):0000
Making these changes should get your bootloader working. As it was there were 2 bugs in your original code:
The apparent hang was likely caused by reading the sector starting at memory address boot_segaddr
which is in your bootloader. Likely you overwrote all the code in your bootloader making it work erratically when int 13h
eventually returned.
As Peter points out using an emulator like BOCHS and its internal debugger would allow you to single step through 16-bit real mode code. You would have probably discovered these issues.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With