Page 4 of 7

Re: Making a bootable image

Posted: Mon Nov 02, 2020 4:23 am
by PeterX
Bonfra wrote:But the carry flag is set by the BIOS function so it fails and I don't understand the reason
I'm not sure about the CHS values. But your code looks good to me. (Except that you comment "head 0" but you set it really to 0x20.)
Maybe you first must init the drive? (int 0x13 ah=0)
Or maybe your bootdrive value gots corrupted (because of wrong DS value or something else).
You could do a hexdump of the AH register after the disk read interrupt.

Greetings
Peter

Re: Making a bootable image

Posted: Mon Nov 02, 2020 6:55 am
by Bonfra
PeterX wrote: Maybe you first must init the drive? (int 0x13 ah=0)
Just tried with this other version of the function:

Code: Select all

ReadSectors:
    mov ah, 0x00
    int 0x13

    mov bx, 0x7C00  ; address
    mov al, 1       ; Number of Sectors
    
    mov dl, [bootDrive]
    mov ch, 0x00   ; cylinder 0x00
    mov dh, 0x20   ; head 0x20
    mov cl, 0x21   ; disk sector number

    clc ; clear the flag to be sure
    mov ah, 0x02
    int 0x13    ; Invoke BIOS

    ret

PeterX wrote: Or maybe your bootdrive value gots corrupted (because of wrong DS value or something else).
I add the bootloader to the disk with this command:

Code: Select all

dd if=bin/boot/mbr.bin of=disk.dd seek=0 count=1 conv=notrunc bs=436
Just to be sure that the problem is actually in the read sectors and not somewhere else I used this template from the wiki and implemented the ReadSector function (and the error label), removing the parameter part since they are hard coded in the function:

Code: Select all

.ReadVBR:
        call ReadSectors          ; Read Sector
        jc error.diskReadFailed

Still nothing the readdisk interrupt return an error.

Re: Making a bootable image

Posted: Mon Nov 02, 2020 7:41 am
by PeterX
Try something like this:

Code: Select all

ReadSectors:
.init:
    mov dl, [bootDrive]  ;!!!
    mov ah, 0x00
    int 0x13
    jc .init


.readSec:
    mov bx, 0x7C00  ; address
    mov al, 1       ; Number of Sectors
    mov dl, [bootDrive]
    mov ch, 0x00   ; cylinder 0x00
    mov dh, 0x20   ; head 0x20
    mov cl, 0x21   ; disk sector number

    clc ; clear the flag to be sure -> not neccessary
    mov ah, 0x02
    int 0x13    ; Invoke BIOS
    jc .readSec

    ret
EDIT:
For the disk interrupts:
http://www.ctyme.com/intr/int-13.htm
https://wiki.osdev.org/BIOS

Re: Making a bootable image

Posted: Mon Nov 02, 2020 10:09 am
by Bonfra
The interrupt keeps throwing the error and the function never returns.
I tried burning the image on a USB to run it on a real device and the function returns, the partition bootloader still does not run but we'll think about it later.
Maybe is the way I launch QEMU? I use this line:

Code: Select all

qemu-system-x86_64 disk.dd -m 512M -no-reboot -no-shutdown
Also tried with -hda but it doesn't change.

Re: Making a bootable image

Posted: Mon Nov 02, 2020 10:55 am
by PeterX
Maybe you debug a bit.

I debug usually by printing characters or registers.
For example printing an 'A' after the disk reset. So you know if it is hanging in the reset or in the read code.
And directly after the read int you could print the content of ah which is a status info.

Others prefer using a debugger. Perhaps this helps:
https://wiki.osdev.org/How_Do_I_Use_A_D ... With_My_OS

Greetings
Peter

Re: Making a bootable image

Posted: Mon Nov 02, 2020 3:59 pm
by MichaelPetch
The reason it will be failing is because QEMU is likely using a disk geometry that is 16 heads maximum and your partitioning tool used a drive geometry with heads > 16. This is a big reason why using Int 13h/AH=42h is preferred because you can then use LBA addressing rather than CHS. But if you wish to use Int 13h/AH=2 to read using CHS then you will have to query the BIOS to determine the Sectors per Track and the Max number of Heads. With those 2 values you can compute CHS from any given LBA. You will then have to write some kind of conversion function that can take an LBA and convert it to CHS. I have some sample code here that can do just that: https://stackoverflow.com/a/47127909/3857942 . Using CHS is far more complex than using LBA and CHS can't access large drives. Int 13/AH=42h LBA disk reads were meant to deal with this. Unless you are on some ancient equipment that doesn't have support for MS Disk Extensions, using LBA address for hard disk is preferred.

Your disk image is 2048+65536 sectors and it is likely that QEMU used 16 heads (0-15), 63 sectors per track (1-63) and 68 cylinders (0-67) to cover all those sectors. If you read a head, sector, or cylinder starting beyond any of those limits it will likely fail. What happens will be dependent on the BIOS and may differ from what you see on real hardware.

I have a GCC program that also converts LBA to CHS that you can find here: https://stackoverflow.com/a/59416266/3857942 . You specify the Sectors Per Track (SPT) and the number of heads and the LBA and it will compute CHS. If I have a drive presenting as media with an SPT of 63 and 16 heads, and want to know the CHS of the LBA 2048 I'd run it this way:

Code: Select all

lba2chs 16 63 2048
and the output would be:

Code: Select all

SPT = 63, Heads = 16
LBA = 2048 is CHS = (2, 0, 33)

DH = 0x00, CH = 0x02, CL = 0x21

Re: Making a bootable image

Posted: Mon Nov 02, 2020 6:29 pm
by BenLunt
Bonfra wrote:Image
I believe you are still getting bad results with your writes to the file.

The above ending CHS values are wrong. Using a starting LBA of 2048 with a sector count of 65536, the ending CHS values should be 0x43/0x00/0x31 respectively.

Why does your partition entry have 0x04/0x34/0x30? 0x34 for a head value is, as MechaelPetch has stated, a wrong value.

When using my utility, click on the button to the right of these values, and it will fill the CHS fields with correct values per your Starting LBA and Sector Count values.

Same goes for your Starting CHS values. They should be 0x02/0x00/0x21 respectively.

Ben

Re: Making a bootable image

Posted: Mon Nov 02, 2020 6:51 pm
by MichaelPetch
Ben, I did notice that fdisk which he is using is computing these values does in fact populate the partition table with those values (I see the same behaviour here). Based on these numbers I suspect 255 heads and 63 SPT is being used by FDISK in this case and it is assuming BIOS translation:

Code: Select all

Disk disk.dd: 33 MiB, 34603008 bytes, 67584 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xfe110584

Device     Boot Start   End Sectors Size Id Type
disk.dd1   *     2048 67583   65536  32M  6 FAT16

Command (m for help): i
Selected partition 1
         Device: disk.dd1
           Boot: *
          Start: 2048
            End: 67583
        Sectors: 65536
      Cylinders: 26
           Size: 32M
             Id: 6
           Type: FAT16
    Start-C/H/S: 0/32/33
      End-C/H/S: 4/52/48
          Attrs: 80
Some of the values that I think apply:

Code: Select all

lba2chs 255 63 2048
SPT = 63, Heads = 255
LBA = 2048 is CHS = (0, 32, 33)

DH = 0x20, CH = 0x00, CL = 0x21

lba2chs 255 63 67583
SPT = 63, Heads = 255
LBA = 67583 is CHS = (4, 52, 48)

DH = 0x34, CH = 0x04, CL = 0x30

lba2chs 16 63 2048
SPT = 63, Heads = 16
LBA = 2048 is CHS = (2, 0, 33)

DH = 0x00, CH = 0x02, CL = 0x21

lba2chs 16 63 67583
SPT = 63, Heads = 16
LBA = 67583 is CHS = (67, 0, 48)

DH = 0x00, CH = 0x43, CL = 0x30
It appears one can use

Code: Select all

fdisk -H 16 -S 63
when running FDISK to get it to use 16 heads and 63 SPT.

Re: Making a bootable image

Posted: Tue Nov 03, 2020 1:57 am
by Bonfra
Ok finally it works and the partition bootloader is loaded correctly and runs. I'd like to summarize the whole process in one post for any future readers.

First I created a partition image and add any needed file with this lines:

Code: Select all

dd if=/dev/zero of=partition.dd bs=512 count=65536 # count = [ K = megabyte; K*(1024)^2/512 ]
mkfs.vfat -F 16 -n "MyOS" partition.dd
#mcopy -i partition.dd path/to/file.eg ::/
Then I loaded the bootloader in the partition paying attention to not override the BPB:

Code: Select all

dd if=bin/boot/boot.bin of=partition.dd seek=0 count=1 conv=notrunc bs=3
dd if=bin/boot/boot.bin of=partition.dd seek=83 seek=83 skip=83 count=$[512-83] conv=notrunc bs=1 # 83 is the end address of the 7.1 FAT32 BPB
Then I create the actual disk image:

Code: Select all

dd if=/dev/zero of=disk.dd bs=512 count=$[2048+65536] #65536 is the K value from above
echo -e "n \n p \n \n \n \n t \n 6\n a \n w" | fdisk -H 16 -S 63 disk.dd
Then I added the MBR to the image paying attention to not override the partition table:

Code: Select all

dd if=bin/boot/mbr.bin of=disk.dd seek=0 count=1 conv=notrunc bs=436
And finally add the partition to the disk:

Code: Select all

dd if=partition.dd of=disk.dd conv=notrunc bs=512 seek=2048
I'll also post the minimal code for the MBR and the partition bootloader:

MBR code: (This code make some assumption in the ReadSectors function so it should be changed for a normal use)

Code: Select all

bits 16
org 0x0600
 
start:
    cli                         ; We do not want to be interrupted
    xor ax, ax                  ; 0 AX
    mov ds, ax                  ; Set Data Segment to 0
    mov es, ax                  ; Set Extra Segment to 0
    mov ss, ax                  ; Set Stack Segment to 0
    mov sp, ax                  ; Set Stack Pointer to 0
    .CopyLower:
        mov cx, 0x0100            ; 256 WORDs in MBR
        mov si, 0x7C00            ; Current MBR Address
        mov di, 0x0600            ; New MBR Address
        rep movsw                 ; Copy MBR
    jmp 0:LowStart              ; Jump to new Address

LowStart:
    sti                         ; Start interrupts

    mov BYTE [bootDrive], dl    ; Save BootDrive
    .CheckPartitions:           ; Check Partition Table For Bootable Partition
        mov bx, PT1               ; Base = Partition Table Entry 1
        mov cx, 4                 ; There are 4 Partition Table Entries
        .CKPTloop:
            mov al, BYTE [bx]       ; Get Boot indicator bit flag
            test al, 0x80           ; Check For Active Bit
            jnz .CKPTFound          ; We Found an Active Partition
            add bx, 0x10            ; Partition Table Entry is 16 Bytes
            dec cx                  ; Decrement Counter
            jnz .CKPTloop           ; Loop
            jmp error.noBootablePartition ; ERROR!
        .CKPTFound:
            mov WORD [PToff], bx    ; Save Offset
            add bx, 8               ; Increment Base to LBA Address

    .ReadVBR:
        call ReadSectors          ; Read Sector
        jc error.diskReadFailed
        mov si, Message.Status.VBRLoaded
        call BiosPrint

    .jumpToVBR:
        cmp WORD [0x7DFE], 0xAA55 ; Check Boot Signature
        jne error                 ; Error if not Boot Signature

        mov si, WORD [PToff]      ; Set DS:SI to Partition Table Entry
        mov dl, BYTE [bootDrive]  ; Set DL to Drive Number
        jmp 0x7C00                ; Jump To VBR
error:
    jmp hang

    .diskReadFailed:
        mov si, Message.Error.DiskReadFailed
        call BiosPrint
        jmp hang

    .noBootablePartition:
        mov si, Message.Error.NoBootablePartition
        call BiosPrint
        jmp hang

hang:
    cli
    hlt
    jmp hang

;***********************************;
; Reads a series of sectors         ;
; Returns:                          ;
;   cf => set if error              ;
;***********************************;
ReadSectors:
.init:
    mov dl, [bootDrive]
    mov ah, 0x00
    int 0x13
    jc .init

.readSec:
    mov bx, 0x7C00  ; address
    mov al, 1       ; Number of Sectors
    mov dl, [bootDrive]
    mov ch, 0x02   ; cylinder 0x02
    mov dh, 0x00   ; head 0x00
    mov cl, 0x21   ; disk sector number

    mov ah, 0x02
    int 0x13    ; Invoke BIOS

    jc .readSec

    ret

;************************;
; Parameters:            ;
;   si => string pointer ;
;************************;
BiosPrint:
    pusha
    .loop:
        lodsb
        or al, al
        jz .done
        mov ah, 0x0E
        int 0x10
        jmp .loop
    .done:
    popa
    ret

times (218 - ($-$$)) nop      ; Pad for disk time stamp
 
DiskTimeStamp times 8 db 0    ; Disk Time Stamp

Message.Error.NoBootablePartition db "No bootable partition found.", 13, 10, 0
Message.Error.DiskReadFailed db "Disk read failed", 13, 10, 0
Message.Status.VBRLoaded db "VBR loaded!", 13, 10, 0
 
bootDrive db 0                ; Our Drive Number Variable
PToff dw 0                    ; Our Partition Table Entry Offset

times (0x1b4 - ($-$$)) nop    ; Pad For MBR Partition Table

UID times 10 db 0             ; Unique Disk ID
PT1 times 16 db 0             ; First Partition Entry
PT2 times 16 db 0             ; Second Partition Entry
PT3 times 16 db 0             ; Third Partition Entry
PT4 times 16 db 0             ; Fourth Partition Entry
 
dw 0xAA55                     ; Boot Signature
Partition bootloader code:

Code: Select all

org 0x7C00
bits 16

jmp short boot
nop

biosParameterBlock:
times 0x52-($-$$) db 0 ; BPB 7.1 FAT32 ends at 0x52.

boot:
.init:

cli

; adjust segment registers
mov ax, cs
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax

; create stack
xor ax, ax
mov ss, ax
mov sp, 0x7C00

sti

mov si, String.HelloWorld
call BiosPrint

hang:
    cli
    hlt
    jmp hang

String.HelloWorld db "Hello, World!", 13, 10, 0
 
BiosPrint:
    pusha
    .loop:
        lodsb
        or al, al
        jz .done
        mov ah, 0x0E
        int 0x10
        jmp .loop
    .done:
    popa
    ret

times 510-($-$$) db 0
dw 0xAA55
Finally I'd like to thank PeterX, MichaelPetch, BenLunt (and his tool) and bzt for helping me out.

Re: Making a bootable image

Posted: Tue Nov 03, 2020 4:58 am
by Bonfra
Actually... It boots from QEMU and prints "Hello, World!" from the partition bootloader, if I boot the image on a real device via USB stick it does not print it.
I think that the problem is the geometry of the usb stick.
This can be a solution:
MichaelPetch wrote:using Int 13h/AH=42h is preferred
I read this page about it but I'm not sure about the disk address packet content. It is indicated as so:

Code: Select all

00h    BYTE    size of packet (10h or 18h)
01h    BYTE    reserved (0)
02h    WORD    number of blocks to transfer (max 007Fh for Phoenix EDD)
04h    DWORD   -> transfer buffer
08h    QWORD   starting absolute block number
10h    QWORD   (EDD-3.0, optional) 64-bit flat address of transfer buffer;
I've setted this values as so:

Code: Select all

LBA_Packet:
    .packet_size     db   0x10   ; use_transfer_64 ? 10h : 18h
    .reserved        db   0x00   ; always zero    
    .block_cout      dw   0x01   ; number of sectors to read
    .transfer_buffer dd  0x7C00 ; address to load in ram
    .lba_value       dq  0x0    ; LBA addres value   
Then moving LBA_Packet in SI before calling int 13h/AH=42h.
In QEMU this works and I get the "Hello, World!" printed but on the real machine it does not. What have I done wrong?

Re: Making a bootable image

Posted: Tue Nov 03, 2020 9:36 am
by MichaelPetch
When it fails what is the status code in AH from Int 13h/AH=42h? I'd also like to know what the value in DL is (The drive number) on the real hardware.

I assume your bootloader was still relocated to 0x600. Make sure that DS is set to 0x0000 so that DS:SI points to the DAP in memory. I believe you did do this in your code already, just making sure since I can't see the code you are currently testing as you didn't seem to update GitHub repository with it.

Some people have said that there are BIOSes out there where ES must be set to the value of the segment in the Disk Address Packet (DAP). In your case that is zero. Make sure ES has zero in before calling int 13h/ah=42h. You don't show the code where you modify the LBA value in the disk packet either. Can't tell if there is a problem there.

What type of computer are you using (real hardware)? How old? It may be possible the Extended BIOS functions aren't available on the BIOS/Drive but that wouldn't be very typical these days, although it is usually a reason people check that Extended BIOS drive functions are available and also that the drive supports them and then fall back to CHS if they aren't supported.

Are you booting the USB stick as Floppy Drive Emulation (FDD) or Hard Drive Emulation (HDD)? This is usually setting in the BIOS that you can adjust. If you have it set to FDD it is possible that Int 13h/Ah=42h and the other extended disk functions are not available for what appears to be floppy media. The extended disk functions are generally not available on floppy devices (or on devices appearing as a floppy device). Knowing the drive letter in DL might give us a hint if it is being seen as a floppy or hard drive device.

There are some BIOSes out there that when booting with partitioned USB media will look in the MBR for an active partition and load the VBR directly. Did you print anything out in your MBR so you know your MBR was actually loaded and executed before loading the VBR?

Re: Making a bootable image

Posted: Tue Nov 03, 2020 10:24 am
by Bonfra
MichaelPetch wrote:When it fails what is the status code in AH from Int 13h/AH=42h?
It doesn't properly fail, the MBR print that the reading was succesfull but when it jumps to the bootloader does nothing (QEMU insteads print "Hello, World!")
MichaelPetch wrote:Make sure that DS is set to 0x0000 so that DS:SI [...] ES must be set to the value of the segment. In your case that is zero.
It is (code from mbr.asm):

Code: Select all

xor ax, ax                      ; 0 AX
mov ds, ax                      ; Set Data Segment to 0
mov es, ax                      ; Set Extra Segment to 0
mov ss, ax                      ; Set Stack Segment to 0
mov sp, ax                      ; Set Stack Pointer to 0
MichaelPetch wrote:I believe you did do this in your code already, just making sure since I can't see the code you are currently testing as you didn't seem to update GitHub repository with it
Yes, I pushed to the wrong branch, now the code is in the one I've linked to you (testing branch).
MichaelPetch wrote: What type of computer are you using (real hardware)? How old?
It's an Acer from 2009.
MichaelPetch wrote: There are some BIOSes out there that when booting with partitioned USB media will look in the MBR for an active partition and load the VBR directly. Did you print anything out in your MBR so you know your MBR was actually loaded and executed before loading the VBR?
Yes, the MBR after loading from the disk the bootloader prints "VBR loaded!", and then "Hello, World!" should appear.
MichaelPetch wrote: Are you booting the USB stick as Floppy Drive Emulation (FDD) or Hard Drive Emulation (HDD)
The USB stick is booted as HDD, anyway I'm posting some images of the BIOS to be sure about the version of it and that it supports LBA.
Image Image Image

Re: Making a bootable image

Posted: Tue Nov 03, 2020 10:50 am
by MichaelPetch
What does this command give as output:

Code: Select all

file disk.dd

Re: Making a bootable image

Posted: Tue Nov 03, 2020 10:53 am
by Bonfra
MichaelPetch wrote:What does this command give as output:

Code: Select all

file disk.dd
The file is now called BonsOS.img, anyway this is the output:

Code: Select all

file BonsOS.img
BonsOS.img: DOS/MBR boot sector; partition 1 : ID=0x6, active, start-CHS (0x0,32,33), end-CHS (0x4,52,48), startsector 2048, 65536 sectors

Re: Making a bootable image

Posted: Tue Nov 03, 2020 11:25 am
by MichaelPetch
You don't actually print out an error message if you load a sector successfully but don't find the disk signature. You do this:

Code: Select all

    .jumpToVBR:
        cmp WORD [0x7DFE], 0xAA55   ; Check Boot Signature
        jne error                   ; Error if not Boot Signature

        mov si, WORD [PToff]        ; Set DS:SI to Partition Table Entry
        mov dl, BYTE [bootDrive]    ; Set DL to Drive Number
        jmp 0x7C00                  ; Jump To VBR
error:
    jmp hang
My guess is that for whatever reason you are not reading data/code from the VBR you expect and it gets stuck in the `hang` loop. It might help if you print an error message.

If this in fact what is going on then the only thing I can think of is that the process you are using to write the image to the USB stick is the problem and somehow the VBR isn't in the place on disk that is expected; it somehow got clobbered; or wasn't written at all. This is just a guess, I don't actually see anything wrong except it might be a good idea to use `CLD` instruction before you start using string instructions in the MBR (This isn't a problem for you currently), The direction flag isn't guaranteed to be cleared as your code expect. Just giving you a heads-up.

How do you write your image to the USB stick?