Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BIOS Disk - Read Sectors Into Memory (int 0x13, ah=0x02) blocking

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
like image 405
Stewart Smith Avatar asked Sep 17 '16 04:09

Stewart Smith


1 Answers

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:

  • Using the address of your variable rather than the value at that address
  • Doing the FAR JMP with the wrong segment value.

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.

like image 140
Michael Petch Avatar answered Sep 22 '22 23:09

Michael Petch