Search code examples
assemblyx86nasmbootloaderbochs

fail to read sectors on boot code


I wrote a boot code that print some stuff on the screen then relocate itself and load the next boot code (VBR). I tested the code on virtual machine using vmware and it's working as it should, I see the right output from the vbr code. Click here to see the output of the code.

I want to be able to debug my code and since vmware doesn't have this feature, I want to use bochs which can be used with IDA. After running the code on bochs, I got the error:

>>PANIC<< IO write(0x01f0): current command is 20h

The error occurred after calling the interrupt that handles extended reading (int 13h, function 42). I checked for extensions and they are supported so I wrote another real mode program which only load 1 sector using the same bios function, and for some reason it worked. There is only small difference between the codes. In the boot code I wrapped the code that loads the sector with function that gets the parameters, and in the second code it wasn't wrapped with a function. I will post here the codes.

Boot code (only the relevant function, for a full version click here):

%define AdjustAddress(addr) addr - BASE_ADDRESS + NEW_ADDRESS
%define DAP(obj, member) [obj + DiskAddressPacket. %+ member]

NEW_ADDRESS             equ         0x0600
BASE_ADDRESS            equ         0x7C00
DAP_SIZE                equ         DiskAddressPacket_size

struc DiskAddressPacket
    .size           resb 1
    .resv           resb 1
    .num_of_sectors resb 2
    .offset         resb 2
    .segment        resb 2
    .lba_start      resb 8
endstruc

main:
    ; code
    ; ....

    ; Read the VBR
    push 0x01
    mov  si, [AdjustAddress(parti_pointer)]
    push dword PE(si, starting_lba)
    push BASE_ADDRESS
    push 0x00
    call ReadSectors

    ; code
    ; ....

; void __stdcall ReadSectors(WORD segment, WORD offset, DWORD lba, WORD count)
ReadSectors: 
    push bp           ; Save bp register value
    mov  bp, sp       ; Setting up stack frame
    sub  sp, DAP_SIZE ; Allocate a buffer for the DAP data

    ; Zero out DAP buffer
    std ; Set direction flag (decrease di)
    mov   di, bp 
    xor   al, al
    mov   cx, DAP_SIZE
    repnz stosb ; di = DAP buffer at the end of this operation

    ; Initialize DAP with correct data
    mov byte DAP(di, size), DAP_SIZE
    mov bx, word [bp + 0x0C]    ; count parameter 
    mov word DAP(di, num_of_sectors), bx
    mov bx, word [bp + 0x04]    ; segment parameter 
    mov word DAP(di, segment), bx 
    mov bx, word [bp + 0x06]    ; offset parameter 
    mov word  DAP(di, offset), bx
    mov bx, word [bp + 0x08]    ; Low word of LBA parameter
    mov word DAP(di, lba_start), bx
    mov bx, word [bp + 0x0A]    ; High word of LBA parameter
    mov word DAP(di, lba_start + 0x02), bx

    ; Prepare parameters
    mov ax, 0x4200  ; Extended read sectors function of int 0x13
    mov dx, word [AdjustAddress(drive_index)] ; Drive index
    mov si, di      ; si = DAP buffer
    int 0x13        ; Read the sectors from the disk
    jc  CheckLBASupport.no_lba_support

    mov sp, bp  ; Clear the allocated memory
    pop bp      ; Restore bp register value

    ret 0x0A ; Clean the stack and return to the calling code

Second code:

cli ; Cancel interrupts 

; Set up segments registers
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax

mov sp, 0x7c00

mov ax, 0x4100
mov bx, 0x55aa
int 0x13
; Checking returned value with the debugger

sub sp, DiskAddressPacket_size
mov bp, sp
mov byte [bp], DiskAddressPacket_size
mov byte [bp + 0x01], 0x00
mov word [bp + 0x02], 0x01
mov word [bp + 0x04], 0x7C00
mov word [bp + 0x06], 0x00
mov dword [bp + 0x08], 0x80
mov dword [bp + 0x0c], 0x00

mov ax, 0x4200
mov si, bp
int 0x13

I checked the DAP buffer in the memory for both code, and it is the same. I cant figure out why on the boot code I get the error and on the second code I dont. Is there something I miss?


Solution

  • A couple of issues in your code, and a bug in BOCHS are causing issues.


    There is an off by one error in your ReadSectors function:

    You have:

    push bp           ; Save bp register value
    mov  bp, sp       ; Setting up stack frame
    sub  sp, DAP_SIZE ; Allocate a buffer for the DAP data
    
    ; Zero out DAP buffer
    std ; Set direction flag (decrease di)
    mov   di, bp
    xor   al, al
    mov   cx, DAP_SIZE
    repnz stosb ; di = DAP buffer at the end of this operation
    

    The problem is that mov di, bp is pointing at the least significant byte of BP that was pushed on the stack with push bp. The last byte in the disk access packet (DAP) is actually bp-1. As well after the last stosb instruction executes DI will actually be one byte below where your buffer starts. To fix these problems you can modify your code to look something like:

    push bp           ; Save bp register value
    mov  bp, sp       ; Setting up stack frame
    sub  sp, DAP_SIZE ; Allocate a buffer for the DAP data
    
    ; Zero out DAP buffer
    std ; Set direction flag (decrease di)
    lea   di, [bp - 1]
    xor   al, al
    mov   cx, DAP_SIZE
    repnz stosb ; di = DAP buffer at the end of this operation
    inc   di
    

    In some places after you relocate the code to the memory area at 0x600 you fail to adjust the addresses. For example in CheckBootSignature this code:

     .error:
        mov  si, boot_sig_error
        call PrintString
        int  0x18
    

    Should be:

     .error:
        mov  si, AdjustAddress(boot_sig_error)
        call PrintString
        int  0x18
    

    There is a similar issue in FindBootablePartition


    BOCHS BIOS bug

    In a comment I suggested a possible stack issue:

    My best guess is the issue is stack related. It could be possible the extended int 13h/ah=42 requires more stack than is available between the end of your code relocated around 0x600 and 0x7c00. What happens if you move your stack elsewhere. As an experiment you could try setting SS:SP to something like 0x8c00:0x0000 which would place the stack between 0x8c00:0x0000 (0x8c000) and 0x9c00:0000 (0x9c000) below the video memory and the EBDA.

    I discovered this morning this is partially true. The issue is stack related, but not because of the amount of space available. When the stack is beneath 0x7c00 a BIOS bug ends up clobbering the stack almost immediately. Int 0x13 in BOCHS default BIOS implementation is actually not setting the direction flag when it does processing. It relies on the code calling Int 0x13 to set the direction forward. In your code the direction is backward (You used STD to set the direction flag bit).

    With the direction flag set (backward) BOCHS BIOS Int 0x13/AH=0x42 starts writing DWORDs it reads from the disk into 0x0000:0x7c00 and proceeds to write future data below that offset. Since the stack was placed just below 0x0000:0x7c00 the first sector that gets read pretty much obliterates what is on the stack including the disk access packet (DAP). When this happens any number of bizarre behaviors may occur.

    To fix this make sure that when you call Int 0x13 that you set the direction flag forward with CLD.

    ; Zero out DAP buffer
    std ; Set direction flag (decrease di)
    lea   di, [bp - 1] 
    xor   al, al
    mov   cx, DAP_SIZE
    repnz stosb ; di = DAP buffer at the end of this operation
    inc   di
    cld         ; Forward direction for Int 0x13 and future calls to PrintString
    

    By doing this you get around this BOCHS bug, and you ensure that future calls to PrintString also process characters in the forward direction.