Search code examples
assemblyx86x86-16bootloaderbios

Error while formatting disk with BIOS in real mode


I'm studying assembly at University. I am trying to write a personal format routine, but I'm having some problems. The routine starts at boot but it goes in error flow after the first interrupt (INT 13h/AH=7). Printing AH status value suggests the error is:

0Eh control data address mark detected (hard disk)

Here is my bootloader code:

[BITS 16]
[ORG 0x7C00]

init:
call main
ret


main:
xor ax, ax
mov ds, ax

mov si, string0
call print
call delay
mov si, string1
call print
call delay
mov si, string2
call print
call delay
mov si, string3
call print
call delay
mov si, string4
call print
call delay
mov si, string5
call print
call delay
mov si, string6
call print
call delay
mov si, string7
call print
call delay
mov si, string8
call print
call delay
mov si, string9
call print
call delay
mov si, string10
call print
call delay
mov si, string11
call print
call delay
mov si, string12
call print
call delay
mov si, string13
call print
call delay
mov si, string14
call print
call delay
mov si, string15
call print
call delay
mov si, string16
call print
call delay
mov si, string17
call print
call delay
mov si, string18
call print
call delay
mov si, string19
call print
call delay
mov si, string20
call print
call delay
mov si, string21
call print
call delay
mov si, string22
call print
call delay
mov si, string23
call print
call delay
mov si, string24
call print
call delay
mov si,string25
call print
call delay

call read_and_print
cmp al,'A'
jne error
call read_and_print
cmp al,'P'
jne error
call read_and_print
cmp al,'O'
jne error
call read_and_print
cmp al,'C'
jne error
call read_and_print
cmp al,'A'
jne error
call read_and_print
cmp al,'L'
jne error
call read_and_print
cmp al,'Y'
jne error
call read_and_print
cmp al,'P'
jne error
call read_and_print
cmp al,'S'
jne error
call read_and_print
cmp al,'E'
jne error


mov si,String27
call print
call menu
mov si,String35
call print
jmp $

menu:

mov si,String30
call print

call only_read
cmp al, 'A'
je zero_routine 
cmp al, 'B'
je exit_routine
jmp menu

exit_routine:
mov si, String29
call print
ret

zero_routine: ;Command Shell;

mov si,String31
call print

call only_read
cmp al, 'A'
je A_Command
cmp al, 'B'
je B_Command
cmp al, 'C'
je exit_routine


A_Command:

xor eax,eax

mov ah,0x00 ; Reset the disk ;
mov dl, 0x80; First Disk ;
int 13h
mov ah,0x01 ; Get Disk Status ;
mov dl, 0x80; First Disk ;
int 13h
cmp ah, 0x00 
jne f_error
mov si,String36
call print
;;;;;;;;;;;;;;;;;;;;;
; AH    07h         ;
; AL    Interleave  ;
; CH    Track       ;
; CL    Sector      ;
; DH    Head        ;
; DL    Drive       ;
;;;;;;;;;;;;;;;;;;;;; 



mov ah, 0x07
mov al, 0x00
mov ch, 0x00
mov dl, 0x80
mov dh, 0x00

call format
jmp zero_routine

B_Command:

xor eax,eax

mov ah,0x00 ; Reset the disk ;
mov dl, 0x81; Second Disk ;
int 13h
mov ah,0x01 ; Get Disk Status ;
mov dl, 0x81; Second Disk ;
int 13h
cmp ah, 0x00 
jne f_error
mov si,String36
call print

;;;;;;;;;;;;;;;;;;;;;
; AH    07h         ;
; AL    Interleave  ;
; CH    Track       ;
; CL    Sector      ;
; DH    Head        ;
; DL    Drive       ;
;;;;;;;;;;;;;;;;;;;;; 


mov ah, 0x07
mov al, 0x00
mov cl, 0x00
mov ch, 0x00
mov dl, 0x81
mov dh, 0x00

call format
jmp zero_routine

format:

mov si, String32
call print
int 13h ; Format Drive ;
cmp ah, 0x00 
jne f_error
mov si, String33
call print
ret

f_error:

mov si, String34
call print

xor al,al
add al,ah

mov ah, 0Ah ; Write Character
mov bh, 00h ; Page = 0
mov cx, 01h ; Times = 1
int 10h 
jmp $

error:
mov si,String26
call print
jmp $


print:

mov bl,0x0A
mov ah, 0x0E
mov bh, 0x00

nextchar:
lodsb
or al, al
jz return
int 0x10
jmp nextchar

return: 
ret

delay:

mov ah, 86h
mov cx, 0x00
mov dx, 0x8000
int 15h

ret

only_read:

mov ah, 00h ; Read Character
int 16h
ret

read_and_print:

mov ah, 00h ; Read Character
int 16h

push ax ; Save the current character

mov ah, 0Ah ; Write Character
mov bh, 00h ; Page = 0
mov cx, 01h ; Times = 1
int 10h 

cursor_forward:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;AX = 0, CH = Start scan line, CL = End scan line, DH = Row, DL = Column;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


mov ah, 03h ; Read cursor position
mov bh, 00h ; Page = 0;
int 10h     

mov ah,02h ; Set Cursor Position
mov bh,00h ; Page = 0
add dl,01h ; Column++
int 10h

pop ax

ret


    string0 db "MEMORY Check 0401B + 204R81B     OK",`\n`,`\r`,0
    string1 db "JA Hi-SYS BOOT!",`\n`,`\r`,0
    string2 db "Copyright (C) 2014,2015",`\n`,`\r`,0
    string3 db "CO-CPU            Check         256seg      OK",`\n`,`\r`,0
    string4 db "I/O VECTORS       Check                     OK",`\n`,`\r`,0
    string5 db "ROOTING TABLES    Check                     OK",`\n`,`\r`,0
    string6 db "STATUS ANALYZER   Check         SLAVE       OK",`\n`,`\r`,0
    string7 db "VIRUS PROTECTION  Check         GREEN       OK",`\n`,`\r`,0
    string8 db "-----  SYSTEM CONFIGURATION  -----",`\n`,`\r`,0
    string9 db "addr PSP  blks    size  owner/parameters",`\n`,`\r`,0
    string10 db "---- ---- ---- -------  ----------------------",`\n`,`\r`,0
    string11 db "D0E0 sys    1     5296  kozaic",`\n`,`\r`,0
    string12 db "D22C sys    1     2416  ersdrv",`\n`,`\r`,0    
    string13 db "D2C4 2081   1    16384  smalldrv",`\n`,`\r`,0
    string14 db "D6C5-DBFE   1    21392  <free>",`\n`,`\r`,0
    string15 db "DE02-E000   1     8160  <free>",`\n`,`\r`,0
    string16 db "--- UMB total:  53 TB ---",`\n`,`\r`,0
    string17 db "0586 sys    1     2144  shimem",`\n`,`\r`,0
    string18 db "060D sys    1     3968  hemm386",`\n`,`\r`,0
    string19 db "0706 sys    1     3312  smalldrv",`\n`,`\r`,0
    string20 db "07ED sys    1    13568  adam8b CON",`\n`,`\r`,0
    string21 db "0C18 sys    4    65424  <config>",`\n`,`\r`,0
    string22 db "1CD6 <--    1    15008  share 7L:500",`\n`,`\r`,0
    string23 db "2081 <--    1    13712  smalldrv",`\n`,`\r`,0
    string24 db "23DB-9FFF   1   508464  <free>",`\n`,`\n`,`\r`,0
    string25 db "---- Insert Password ----",`\n`,`\r`,0     
    String26 db "  ---- Error... Please Reboot----",`\n`,`\r`,0
    String27 db "  ---- OK ----",`\n`,`\r`,0
    String28 db "---- Error... No Such Subroutine ----",`\n`,`\r`,0
    String29 db `\n`,`\r`,"---- Exiting----",`\n`,`\r`,0
    String30 db "---- Menu: A = Command Shell, B = Exit ----",0
    String31 db `\n`,`\r`,"---- Menu: A = Format Disk 1, B = Format Disk 2, C = Exit ----",0
    String32 db `\n`,`\r`,"---- Formatting Disk - Please Wait... ---- ",`\n`,`\r`,0
    String33 db "---- Disk Formatted ----",`\n`,`\r`,0
    String34 db `\n`,`\r`,"---- Error While Formatting ---- ",0
    String35 db "---- You Can Now Reboot Your Computer ---- ",0
    String36 db `\n`,`\n`,`\r`,"---- Disk Resetted ----",`\n`,`\r`,0

Solution

  • This answer isn't an attempt to debug the code, but to suggest a mechanism to overcome a serious problem with this bootloader. I realize that this may not answer your specific question about INT 13h/AH=07h.


    Your original question mentioned nothing about boot media. If it was using floppy/hard drive then you'd have to be concerned about these problems:

    • The BIOS only reads the first 512 bytes of the boot disk into memory.
    • Your code lacks a boot sector signature (0xAA55) in the last 2 bytes. This may not be a problem with some virtual machines, but will generally cause the boot sector to not be seen as bootable by many BIOSes.
    • Setup the SS:SP stack pointer explicitly so you don't overwrite it in the bootloader code

    The code and data are well over 512 bytes. (Almost 2k I believe). Although the BIOS only reads the first 512 bytes (first sector), nothing prevents you from manually loading data from the sectors after the boot sector. The boot sector is #1, sector #2 is the sector right after. You can use INT 13/AH=02h to read disk sectors. From Ralf Brown's interrupt list we find that it is defined this way:

    DISK - READ SECTOR(S) INTO MEMORY

    AH = 02h
    AL = number of sectors to read (must be nonzero)
    CH = low eight bits of cylinder number
    CL = sector number 1-63 (bits 0-5)
    high two bits of cylinder (bits 6-7, hard disk only)
    DH = head number
    DL = drive number (bit 7 set for hard disk)
    ES:BX -> data buffer
    

    Return:

    CF set on error
    if AH = 11h (corrected ECC error), AL = burst length
    CF clear if successful
    AH = status (see #00234)
    AL = number of sectors transferred (only valid if CF set for some
    BIOSes)
    

    In your case we'll just read 8 sectors (4096 bytes) from the boot drive passed by the BIOS in the DL register. We'll read them into memory at 0x0000:0x7e00 right after the original 512 bytes loaded by the BIOS. It may be easier to place the main data and code after the first 512 bytes of the bootloader and the code that does initializing and disk reading in the first 512 bytes of the bootloader.

    We can pad out the first 512 bytes of the boot sector and add a disk signature with this:

    times 510 - ($-$$) db 0            ; Fill empty bytes to pad out first
                                       ;     sector with zeroes
    dw 0aa55h                          ; Last 16-bit word of boot sector should be 0xaa55
    

    The resulting code could look something like:

    [BITS 16]
    [ORG 0x7C00]
    
    init:
    xor ax, ax
    mov ds, ax        ; DS=0
    mov es, ax        ; ES=0
    mov ss, ax
    mov sp, 7c00h     ; Place SS:SP at 0000h:7c00h
    cld
    
    ;AH = 02h
    ;AL = number of sectors to read (must be nonzero). 
    ;CH = low eight bits of cylinder number
    ;CL = sector number 1-63 (bits 0-5)
    ;     Sector numbers start at 1, not 0!
    ;high two bits of cylinder (bits 6-7, hard disk only)
    ;DH = head number
    ;DL = drive number (bit 7 set for hard disk). Passed in by BIOS
    ;ES:BX -> data buffer. ES set to 0 earlier
    
    mov ax, 0208h     ; AH=2 disk read, AL = number sectors to read = 8 (4k)
    mov cx, 0002h     ; CH=Cylinder number 0, CL=sector to start reading = 2
                      ;    Sector 2 = sector right after boot sector
    xor dh, dh        ; DH=head number = 0
    mov bx, 7e00h     ; ES:BX = memory to read into. ES=0, BX=7e00h right 
                      ;    after the first 512 bytes read by BIOS
    int 13h           ; Int 13h/AH=02 disk read
                      ;    Should check carry flag for disk error. Leave
                      ;    as exercise for reader.
    call main
    ret
    
    times 510 - ($-$$) db 0            ; Fill empty bytes to pad out first
                                       ;     sector with zeroes
    dw 0aa55h                          ; Last 16-bit word of boot sector should be 0xaa55
    
    ; This label will start at the 513th byte of the file (first byte of sector 2)
    main:
    
    mov si, string0
    call print
    call delay
    mov si, string1
    call print
    call delay
    mov si, string2
    call print
    call delay
    mov si, string3
    call print
    call delay
    mov si, string4
    call print
    call delay
    mov si, string5
    call print
    call delay
    mov si, string6
    call print
    call delay
    mov si, string7
    call print
    call delay
    mov si, string8
    call print
    call delay
    mov si, string9
    call print
    call delay
    mov si, string10
    call print
    call delay
    mov si, string11
    call print
    call delay
    mov si, string12
    call print
    call delay
    mov si, string13
    call print
    call delay
    mov si, string14
    call print
    call delay
    mov si, string15
    call print
    call delay
    mov si, string16
    call print
    call delay
    mov si, string17
    call print
    call delay
    mov si, string18
    call print
    call delay
    mov si, string19
    call print
    call delay
    mov si, string20
    call print
    call delay
    mov si, string21
    call print
    call delay
    mov si, string22
    call print
    call delay
    mov si, string23
    call print
    call delay
    mov si, string24
    call print
    call delay
    mov si,string25
    call print
    call delay
    
    call read_and_print
    cmp al,'A'
    jne error
    call read_and_print
    cmp al,'P'
    jne error
    call read_and_print
    cmp al,'O'
    jne error
    call read_and_print
    cmp al,'C'
    jne error
    call read_and_print
    cmp al,'A'
    jne error
    call read_and_print
    cmp al,'L'
    jne error
    call read_and_print
    cmp al,'Y'
    jne error
    call read_and_print
    cmp al,'P'
    jne error
    call read_and_print
    cmp al,'S'
    jne error
    call read_and_print
    cmp al,'E'
    jne error
    
    
    mov si,String27
    call print
    call menu
    mov si,String35
    call print
    jmp $
    
    menu:
    
    mov si,String30
    call print
    
    call only_read
    cmp al, 'A'
    je zero_routine 
    cmp al, 'B'
    je exit_routine
    jmp menu
    
    exit_routine:
    mov si, String29
    call print
    ret
    
    zero_routine: ;Command Shell;
    
    mov si,String31
    call print
    
    call only_read
    cmp al, 'A'
    je A_Command
    cmp al, 'B'
    je B_Command
    cmp al, 'C'
    je exit_routine
    
    
    A_Command:
    
    xor eax,eax
    
    mov ah,0x00 ; Reset the disk ;
    mov dl, 0x80; First Disk ;
    int 13h
    mov ah,0x01 ; Get Disk Status ;
    mov dl, 0x80; First Disk ;
    int 13h
    cmp ah, 0x00 
    jne f_error
    mov si,String36
    call print
    ;;;;;;;;;;;;;;;;;;;;;
    ; AH    07h         ;
    ; AL    Interleave  ;
    ; CH    Track       ;
    ; CL    Sector      ;
    ; DH    Head        ;
    ; DL    Drive       ;
    ;;;;;;;;;;;;;;;;;;;;; 
    
    
    
    mov ah, 0x07
    mov al, 0x00
    mov ch, 0x00
    mov dl, 0x80
    mov dh, 0x00
    
    call format
    jmp zero_routine
    
    B_Command:
    
    xor eax,eax
    
    mov ah,0x00 ; Reset the disk ;
    mov dl, 0x81; Second Disk ;
    int 13h
    mov ah,0x01 ; Get Disk Status ;
    mov dl, 0x81; Second Disk ;
    int 13h
    cmp ah, 0x00 
    jne f_error
    mov si,String36
    call print
    
    ;;;;;;;;;;;;;;;;;;;;;
    ; AH    07h         ;
    ; AL    Interleave  ;
    ; CH    Track       ;
    ; CL    Sector      ;
    ; DH    Head        ;
    ; DL    Drive       ;
    ;;;;;;;;;;;;;;;;;;;;; 
    
    
    mov ah, 0x07
    mov al, 0x00
    mov cl, 0x00
    mov ch, 0x00
    mov dl, 0x81
    mov dh, 0x00
    
    call format
    jmp zero_routine
    
    format:
    
    mov si, String32
    call print
    int 13h ; Format Drive ;
    cmp ah, 0x00 
    jne f_error
    mov si, String33
    call print
    ret
    
    f_error:
    
    mov si, String34
    call print
    
    xor al,al
    add al,ah
    
    mov ah, 0Ah ; Write Character
    mov bh, 00h ; Page = 0
    mov cx, 01h ; Times = 1
    int 10h 
    jmp $
    
    error:
    mov si,String26
    call print
    jmp $
    
    
    print:
    
    mov bl,0x0A
    mov ah, 0x0E
    mov bh, 0x00
    
    nextchar:
    lodsb
    or al, al
    jz return
    int 0x10
    jmp nextchar
    
    return: 
    ret
    
    delay:
    
    mov ah, 86h
    mov cx, 0x00
    mov dx, 0x8000
    int 15h
    
    ret
    
    only_read:
    
    mov ah, 00h ; Read Character
    int 16h
    ret
    
    read_and_print:
    
    mov ah, 00h ; Read Character
    int 16h
    
    push ax ; Save the current character
    
    mov ah, 0Ah ; Write Character
    mov bh, 00h ; Page = 0
    mov cx, 01h ; Times = 1
    int 10h 
    
    cursor_forward:
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;AX = 0, CH = Start scan line, CL = End scan line, DH = Row, DL = Column;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
    
    mov ah, 03h ; Read cursor position
    mov bh, 00h ; Page = 0;
    int 10h     
    
    mov ah,02h ; Set Cursor Position
    mov bh,00h ; Page = 0
    add dl,01h ; Column++
    int 10h
    
    pop ax
    
    ret
    
    
    
        string0 db "MEMORY Check 0401B + 204R81B     OK",`\n`,`\r`,0
        string1 db "JA Hi-SYS BOOT!",`\n`,`\r`,0
        string2 db "Copyright (C) 2014,2015",`\n`,`\r`,0
        string3 db "CO-CPU            Check         256seg      OK",`\n`,`\r`,0
        string4 db "I/O VECTORS       Check                     OK",`\n`,`\r`,0
        string5 db "ROOTING TABLES    Check                     OK",`\n`,`\r`,0
        string6 db "STATUS ANALYZER   Check         SLAVE       OK",`\n`,`\r`,0
        string7 db "VIRUS PROTECTION  Check         GREEN       OK",`\n`,`\r`,0
        string8 db "-----  SYSTEM CONFIGURATION  -----",`\n`,`\r`,0
        string9 db "addr PSP  blks    size  owner/parameters",`\n`,`\r`,0
        string10 db "---- ---- ---- -------  ----------------------",`\n`,`\r`,0
        string11 db "D0E0 sys    1     5296  kozaic",`\n`,`\r`,0
        string12 db "D22C sys    1     2416  ersdrv",`\n`,`\r`,0    
        string13 db "D2C4 2081   1    16384  smalldrv",`\n`,`\r`,0
        string14 db "D6C5-DBFE   1    21392  <free>",`\n`,`\r`,0
        string15 db "DE02-E000   1     8160  <free>",`\n`,`\r`,0
        string16 db "--- UMB total:  53 TB ---",`\n`,`\r`,0
        string17 db "0586 sys    1     2144  shimem",`\n`,`\r`,0
        string18 db "060D sys    1     3968  hemm386",`\n`,`\r`,0
        string19 db "0706 sys    1     3312  smalldrv",`\n`,`\r`,0
        string20 db "07ED sys    1    13568  adam8b CON",`\n`,`\r`,0
        string21 db "0C18 sys    4    65424  <config>",`\n`,`\r`,0
        string22 db "1CD6 <--    1    15008  share 7L:500",`\n`,`\r`,0
        string23 db "2081 <--    1    13712  smalldrv",`\n`,`\r`,0
        string24 db "23DB-9FFF   1   508464  <free>",`\n`,`\n`,`\r`,0
        string25 db "---- Insert Password ----",`\n`,`\r`,0     
        String26 db "  ---- Error... Please Reboot----",`\n`,`\r`,0
        String27 db "  ---- OK ----",`\n`,`\r`,0
        String28 db "---- Error... No Such Subroutine ----",`\n`,`\r`,0
        String29 db `\n`,`\r`,"---- Exiting----",`\n`,`\r`,0
        String30 db "---- Menu: A = Command Shell, B = Exit ----",0
        String31 db `\n`,`\r`,"---- Menu: A = Format Disk 1, B = Format Disk 2, C = Exit ----",0
        String32 db `\n`,`\r`,"---- Formatting Disk - Please Wait... ---- ",`\n`,`\r`,0
        String33 db "---- Disk Formatted ----",`\n`,`\r`,0
        String34 db `\n`,`\r`,"---- Error While Formatting ---- ",0
        String35 db "---- You Can Now Reboot Your Computer ---- ",0
        String36 db `\n`,`\n`,`\r`,"---- Disk Resetted ----",`\n`,`\r`,0
    

    Special note : This wouldn't be required if you are booting from a CD in certain situations. As @RossRidge points out in the comments:

    The El Torito CD-ROM boot spec does allow bootloaders to be more than one (512 byte) sector long when using a "no emulation" boot. If fact often they're 4 sectors long since this is the same size of physical CD-ROM sector (2048 bytes). Some BIOSes apparently have trouble if the length isn't a multiple of the 2048 byte physical sector size

    Your assembled code in the question is very nearly 2048 bytes. If it gets any larger, then you'd potentially start running into your code/data not being entirely loaded by the BIOS.


    Potential Formatting Problems

    A quick look at your format code suggests a couple of problems:

    mov ah, 0x07
    mov al, 0x00
    mov cl, 0x00
    mov ch, 0x00
    mov dl, 0x81
    mov dh, 0x00
    
    call format
    jmp zero_routine
    
    format:
    
    mov si, String32
    call print
    int 13h ; Format Drive ;
    

    Problems:

    • You setup the registers for the Int 13h/AH=7h call but don't set ES:BX to point to a format buffer. BX hasn't been set to a valid offset, and your original code didn't zero ES. The documentation for that call is:

    FIXED DISK - FORMAT DRIVE STARTING AT GIVEN TRACK (XT,PORT)

    AH = 07h
    AL = interleave value (XT only)
    ES:BX = 512-byte format buffer (see AH=05h)
    CH = cylinder number (bits 8,9 in high bits of CL)
    CL = sector number
    DH = head
    DL = drive
    

    Return:

    AH = status code (see #00234)
    

    The format buffer is vaguely discussed in Int 13h/AH=5h:

    ES:BX -> 512-byte format buffer
    the first 2*(sectors/track) bytes contain F,N for each sector
    F = sector type
    00h for good sector
    20h to unassign from alternate location
    40h to assign to alternate location
    80h for bad sector
    N = sector number
    
    • Serious bug: you potentially overwrite all the registers because format does a call to print just before int 13h. An easy fix might be to print the formatting string before you set up the registers for the format.

    • Sector numbers in CL start at 1 not zero.

    The following code didn't even set CL:

    mov ah, 0x07
    mov al, 0x00
    mov ch, 0x00
    mov dl, 0x80
    mov dh, 0x00
    
    call format
    jmp zero_routine
    
    • I am not sure if INT 13h/AH=07h works on equipment other than XT/Older portable equipment and early drives. I have found some documentation that suggests this may be the case:

    INT 13h, 07h (7) Format Disk Starting at Cylinder fixed disk

    Initializes each sector on a specified cylinder and all subsequent cylinders with sector address and size information. Only XTs may use this service.

    You might have to use a lower level BIOS routine like INT 13h/AH=5h which is FIXED DISK - FORMAT CYLINDER. Even then, I'm not sure that such a low level format applies to hard disks these days. There may be no need to call this function. I do not know this for certain. I personally haven't tried such a low level format via BIOS since the 80s and early 90s.