Making a bootable image

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
PeterX
Member
Member
Posts: 590
Joined: Fri Nov 22, 2019 5:46 am

Re: Making a bootable image

Post by PeterX »

The first sector of a FAT disk/partition is a mix of code and file system data.
1.) In the code you must skip over the data (the BIOS Parameter block) and continue after the BPB.

2.) In the script/makefile you must write only the code to the disk image. Otherwise you erase the FAT file system data.
User avatar
Bonfra
Member
Member
Posts: 270
Joined: Wed Feb 19, 2020 1:08 pm
Libera.chat IRC: Bonfra
Location: Italy

Re: Making a bootable image

Post by Bonfra »

PeterX wrote:In the code you must skip over the data (the BIOS Parameter block) and continue after the BPB
How large is the BPB? does it depends on the file system? From where should I should start adding code?
PeterX wrote:Otherwise you erase the FAT file system data.
Is this also in the BPB or is some other part of the disk to not override?
Regards, Bonfra.
PeterX
Member
Member
Posts: 590
Joined: Fri Nov 22, 2019 5:46 am

Re: Making a bootable image

Post by PeterX »

Bonfra wrote:
PeterX wrote:In the code you must skip over the data (the BIOS Parameter block) and continue after the BPB
How large is the BPB? does it depends on the file system? From where should I should start adding code?
PeterX wrote:Otherwise you erase the FAT file system data.
Is this also in the BPB or is some other part of the disk to not override?
Yes, it depends on the file system. Here is a list of the different DOS versions and their BPB:
https://en.wikipedia.org/wiki/BIOS_parameter_block
I suggest you take the DOS 7.1 FAT32 structure which goes from offset 3 to offset 95. So from offset 96 on it's ok to write code.
No, there is no other data that should not be overwritten. When I talk about FAT file system data and BPB I mean the same.
User avatar
Bonfra
Member
Member
Posts: 270
Joined: Wed Feb 19, 2020 1:08 pm
Libera.chat IRC: Bonfra
Location: Italy

Re: Making a bootable image

Post by Bonfra »

PeterX wrote:I suggest you take the DOS 7.1 FAT32 structure which goes from offset 3 to offset 95. So from offset 96 on it's ok to write code.
7.1 Is for FAT32, by now I'm using FAT12. Do you think I should just switch to FAT32 or use a FAT12 BPB for now?

Or I just ignore that part of the disk to leave compatibiliy for FAT32 and continue using FAT12?
Regards, Bonfra.
PeterX
Member
Member
Posts: 590
Joined: Fri Nov 22, 2019 5:46 am

Re: Making a bootable image

Post by PeterX »

Bonfra wrote:Or I just ignore that part of the disk to leave compatibiliy for FAT32 and continue using FAT12?
I suggest yes. But it's up to you. All three options should work (somehow).
User avatar
Bonfra
Member
Member
Posts: 270
Joined: Wed Feb 19, 2020 1:08 pm
Libera.chat IRC: Bonfra
Location: Italy

Re: Making a bootable image

Post by Bonfra »

So in the bootloader file I do something like this:

Code: Select all

jmp 0x7C00 : boot

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

boot:
  ; Actual boot code
And I need to copy the jump and the boot in the partition.dd excluding the biosParameterBlock.
The problem is that even if I copy only the first byte the partition is no more recognized:

Code: Select all

dd if=bin/boot/boot.bin of=partition.dd seek=0 count=1 conv=notrunc bs=1
This is the partition before copy:

Code: Select all

EB 3C 90 6D 6B 66 73 2E-66 61 74 00 02 04 04 00 [...]
This is the partition after copy:

Code: Select all

EA 3C 90 6D 6B 66 73 2E-66 61 74 00 02 04 04 00 [...]
Regards, Bonfra.
PeterX
Member
Member
Posts: 590
Joined: Fri Nov 22, 2019 5:46 am

Re: Making a bootable image

Post by PeterX »

Code: Select all

jmp 0x7C00 : boot
No, that's a far jump. It takes 5 bytes. But you have only room of 3 bytes. So use a near jump

Code: Select all

jmp boot
or a short jump

Code: Select all

jmp short boot
nop
I recommend the short JMP.
User avatar
Bonfra
Member
Member
Posts: 270
Joined: Wed Feb 19, 2020 1:08 pm
Libera.chat IRC: Bonfra
Location: Italy

Re: Making a bootable image

Post by Bonfra »

In the bootloader:

Code: Select all

jmp short boot
In the terminal:

Code: Select all

dd if=bin/boot/boot.bin of=partition.dd seek=0 count=1 conv=notrunc bs=3
And the image results in:

Code: Select all

EB 5E 00 6D 6B 66 73 2E-66 61 74 00 02 04 04 00 [...]
But the image is still marked as unknown
Regards, Bonfra.
PeterX
Member
Member
Posts: 590
Joined: Fri Nov 22, 2019 5:46 am

Re: Making a bootable image

Post by PeterX »

Bonfra wrote:In the bootloader:

Code: Select all

jmp short boot
In the terminal:

Code: Select all

dd if=bin/boot/boot.bin of=partition.dd seek=0 count=1 conv=notrunc bs=3
And the image results in:

Code: Select all

EB 5E 00 6D 6B 66 73 2E-66 61 74 00 02 04 04 00 [...]
Add the "nop". Some BIOS check for that NOP.
User avatar
Bonfra
Member
Member
Posts: 270
Joined: Wed Feb 19, 2020 1:08 pm
Libera.chat IRC: Bonfra
Location: Italy

Re: Making a bootable image

Post by Bonfra »

PeterX wrote:Add the "nop". Some BIOS check for that NOP.
It was this! Now the image is valid. I'll do some testing to load the bootloader and I'll let you know. Thanks
Regards, Bonfra.
User avatar
Bonfra
Member
Member
Posts: 270
Joined: Wed Feb 19, 2020 1:08 pm
Libera.chat IRC: Bonfra
Location: Italy

Re: Making a bootable image

Post by Bonfra »

Ok the partition works perfectly. It is created with this script:

Code: Select all

#Create the partition
dd if=/dev/zero of=partition.dd bs=512 count=65536 # count = [ K = megabyte; K*(1024)^2/512 ]
mkfs.vfat -F 16 partition.dd

#Add file to the partition
mcopy -i partition.dd path/to/file.eg ::/

#Add the bootloader to the partition
dd if=path/to/boot.bin of=partition.dd seek=0 count=1 conv=notrunc bs=3
dd if=path/to/boot.bin of=partition.dd seek=83 seek=83 skip=83 count=$[512-83] conv=notrunc bs=1
Then I Create the disk image and copy the partition in it:

Code: Select all

#Create the Disk image
dd if=/dev/zero of=disk.dd bs=512 count=$[2048+65536]
echo -e "n \n p \n \n \n \n t \n 6\n a \n w" | fdisk disk.dd

#Load the MBR 
dd if=bin/boot/mbr.bin of=disk.dd seek=0 count=1 conv=notrunc bs=436

#Add the partition to the disk
dd if=partition.dd of=disk.dd conv=notrunc bs=512 seek=2048
If I boot just the partition.dd with:

Code: Select all

qemu-system-x86_64 partition.dd
It prints "Hello, World!" but if i boot the disk.dd it prints nothing, instead it clears the screen (which I think is not a good thing).

Is it something with the disk reading part?
This is the code for the MBR:

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:
        mov EBX, DWORD [bx]       ; Start LBA of Active Partition
        mov di, 0x7C00            ; We Are Loading VBR to 0x07C0:0x0000
        mov cx, 1                 ; Only one sector
        call ReadSectors          ; Read Sector
    
    .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

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

hang:
    cli
    hlt
    jmp hang

absoluteSector  db 0x00
absoluteHead    db 0x00
absoluteTrack   db 0x00

datasector  dw 0x0000
cluster     dw 0x0000

bpbSectorsPerCluster:   DB 1
bpbSectorsPerTrack:     DW 18
bpbHeadsPerCylinder:    DW 2
bpbBytesPerSector:      DW 512


;********************;
; Convert CHS to LBA ;
; Parameters:        ;
;   ax => CHS        ;
; Returns:           ;
;   ax => LBA        ;
;********************;
CHSLBA:
    sub ax, 0x0002                          ; zero base cluster number
    xor cx, cx
    mov cl, BYTE [bpbSectorsPerCluster]     ; convert byte to word
    mul cx
    add ax, WORD [datasector]               ; base data sector
    ret

;********************;
; Convert LBA to CHS ;
; Parameters:        ;
;   ax => LBA        ;
; Returns:           ;
;   ax => CHS        ;
;********************;
LBACHS:
    xor dx, dx                              ; prepare dx:ax for operation
    div WORD [bpbSectorsPerTrack]           ; calculate
    inc dl                                  ; adjust for sector 0
    mov BYTE [absoluteSector], dl
    xor dx, dx                              ; prepare dx:ax for operation
    div WORD [bpbHeadsPerCylinder]          ; calculate
    mov BYTE [absoluteHead], dl
    mov BYTE [absoluteTrack], al
    ret

;***********************************;
; Reads a series of sectors         ;
; Parameters:                       ;
;   cx => Number of sectors to read ;
;   ax => Starting sector           ;
;   es:ebx => Buffer to read to     ;
;***********************************;
ReadSectors:
    pusha
    .start:
        mov di, 0x0005                          ; five retries for error
    .sectorloop:
        call LBACHS                             ; convert starting sector to CHS
        mov ah, 0x02                            ; BIOS read sector
        mov al, 0x01                            ; read one sector
        mov ch, BYTE [absoluteTrack]            ; track
        mov cl, BYTE [absoluteSector]           ; sector
        mov dh, BYTE [absoluteHead]             ; head
        mov dl, BYTE [bootDrive]                ; drive
        int 0x13                                ; invoke BIOS
        jnc .success                            ; test for read error
        xor ax, ax                              ; BIOS reset disk
        int 0x13                                ; invoke BIOS
        dec di                                  ; decrement error counter
        jnz .sectorloop                         ; attempt to read again
    .success:
        push cx                                  ; queue next segment
        mov cx, WORD [bpbBytesPerSector]
        shr cx, 4
        mov dx, es
        add dx, cx
        mov es, dx
        pop cx

        inc ax                                  ; queue next sector
        loop .start                             ; read next sector
        popa
        ret

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

Message.Error.NoBootablePartition db "No bootable partition found.", 13, 10, 0
Message.cazzo db "CAZZO", 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
Regards, Bonfra.
PeterX
Member
Member
Posts: 590
Joined: Fri Nov 22, 2019 5:46 am

Re: Making a bootable image

Post by PeterX »

I don't know if this is the reason, but it is wrong:

Code: Select all

       int 0x13                                ; invoke BIOS
        jnc .success                            ; test for read error
        xor ax, ax                              ; BIOS reset disk
        int 0x13                                ; invoke BIOS
        dec di                                  ; decrement error counter
        jnz .sectorloop                         ; attempt to read again
    .success:
This means, it counts down (via DI) and if it still fails, it continues with success.
There should be some error output and halt between "jnz .sectorloop" and ".success"

Also I have the impression you take too advanced code. You kind of copy code from somewhere without truely understanding it. It would be better for your coding success if you try something "smaller" first.
For example you can assume that partition bootsector is at sector 2048 (because FDISK does so). This is not good for a real OS, but you can concentrate more on the disk read subroutine.
Have you looked at Ralph Brown's Interrupt List for disk read routines?

Greetings
Peter
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: Making a bootable image

Post by BenLunt »

PeterX wrote:Also I have the impression you take too advanced code. You kind of copy code from somewhere without truly understanding it.
I am going to agree with Peter here.

The FAT file system occupies certain parts of the partition. This is a must. Therefore, you *must* preserve these areas when writing boot code to the partition.

The first sector of the partition, sometimes called a Logical Sector Number of zero, must contain a valid BPB for the FAT type it uses. The FAT-12 and FAT-16 use the same data region, while FAT-32 occupies a little more of the first sector. As long as you preserve these areas, you are free to add code to the rest.

Please note that the utility that is writing the code to LSN 0 will already know if it is a FAT-12, -16, or -32 file system and will act accordingly.

For FAT-12 and -16 file systems, as long as you use a short jump plus a NOP as the first three bytes of LSN 0, and skip over the BPB, you can use the remaining part of the sector as code, minus the last two bytes, which must be the 0xAA55 signature. In fact, the FAT-12 and -16 file systems allow you to use additional sectors for code starting at LSN 1. However, since the MBR code only loaded 1 sector, your code within the LSN 0 *must* load any remaining sectors to memory.

For FAT-32, LSN 1 through x are used for the file system and must be preserved. This includes a INFO sector.

However, with all three types, as long as you preserved these areas, you can have many sectors worth of code for your boot code.

As with Peter's suggestion, I suggest that you study how the FAT file system works *before* you try to write boot code for it.

As for the code you posted, it makes a lot of assumptions that a newbie will completely miss. One huge example will be the ORG 0x0600 line. Yes, this is correct, given the code doesn't make any memory references until after it moves to 0x00600, but a quick glance over and one might assume too much about this assumption.

Ben
- http://www.fysnet.net/osdesign_book_series.htm
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: Making a bootable image

Post by bzt »

Bonfra wrote:It prints "Hello, World!" but if i boot the disk.dd it prints nothing, instead it clears the screen (which I think is not a good thing).

Is it something with the disk reading part?
This is the code for the MBR
PeterX and BenLunt gave good advice. However I would also recommend to try it with a known to work MBR code first, before you write your own MBR code.

Under Windows, just use "FDISK.EXE /MBR". Under Linux, use "ms-sys --mbrdos". The code for this MBR sector can be found here (sadly just a byte array), but there's a detailed disassembly with fully commented source code here.

In theory the MBR code does nothing else than chainloading the VBR sector to 0:7C00h. If your image works with the standard code, then the problem is with your MBR code. If it still doesn't work, then the problem is with your disk image creation. FYI this site also contains all MS boot sectors (MBR and VBR alike). You can find a FAT boot sector for floppies as well as for exFAT disks.

Cheers,
bzt
User avatar
Bonfra
Member
Member
Posts: 270
Joined: Wed Feb 19, 2020 1:08 pm
Libera.chat IRC: Bonfra
Location: Italy

Re: Making a bootable image

Post by Bonfra »

Yea you are right, I should start easy implementing my own stuffs.
I tried implementing this ReadSector function:

Code: Select all

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

    mov ah, 0x02
    int 0x13    ; Invoke BIOS

    ret
Which relies on this datas:
Image
But the carry flag is setted by the BIOS function so it fails and I don't understand the reason
Regards, Bonfra.
Post Reply