Filesystem on Loop Device not Recognized by Linux when Bootloader is Written to it
Asked Answered
G

1

2

I am currently writing a bootloader in x86 NASM assembly designed to load a kernel (R.BIN) from a FAT16 filesystem and jump to it. I have been writing the bootloader to a blank image that I have mounted by using sudo losetup loop21 image.img. I would write the image using sudo dd if=LOADER.BIN of=/dev/loop21. Of course, the bootloader didn't work immediately (I was basing it off of a FAT12 bootloader and forgot to change a few things). After making dozens of edits, at some point, Linux stopped recognizing the loop device I was writing it to, saying the contents were "Unknown".

When I delete the entire contents of the bootloader itself (not including the disk descriptor etc.), Linux recognizes the drive as FAT12. However, as I said, with the code to load R.BIN, Linux will not recognize the filesystem.

This is the code I have:

BITS 16

jmp main                            ; Jump to main bootloader
nop                                 ; Pad out remaining bytes until boot descriptor

; Disk descriptor

OEM_name            db "HOUSE   "   ; OEM label
bytes_sector        dw 0x0200       ; Bytes per sector
sectors_cluster     db 0x01         ; Sectors per cluster
sectors_record      dw 0x0001       ; Sectors reserved for boot record
fats                db 0x02         ; Number of file allocation tables
max_root_entries    dw 0x0200       ; Max number of root entries
sectors             dw 0x0FF5       ; Number of sectors
medium_type         db 0xF0         ; Type of medium (removable or fixed?)
sectors_fat         dw 0x0010       ; Sectors per file allocation table
sectors_track       dw 0x0012       ; Sectors per track
heads               dw 0x0002       ; Number of heads
hidden_sectors      dd 0x00000000   ; Number of sectors before partition
total_sectors       dd 0x00000000   ; Number of sectors in medium (zero because it was already set in the allocated word above)
drive_number        db 0x00         ; Drive number (for BIOS int 0x13)
drive_signature     db 0x00         ; NOT USED
ext_signature       db 0x29         ; Extended boot signature
volume_serial       dd 0x00000000   ; Volume's serial number
volume_label        db "HOUSEHOUSES"; Volume label
fs_type             db "FAT16   "   ; Filesystem type

main:
    mov ax, 0x07C0
    add ax, 0x0220
    mov ss, ax
    mov sp, 0x1000                  ; 4K of stack
    mov ax, 0x07C0
    mov ds, ax
    mov byte [drive_num], dl        ; Save boot drive number

    mov bx, ds
    mov es, bx                      ; Set ES to Data Segment
    mov bx, disk_buffer             ; Set BX to disk buffer
    mov ax, 0x21                    ; Start of root = sectors_record + fats * sectors_fat = 1 + 2 * 16 = logical 33
    call ls_chs                     ; Convert logical 33 to head, track and sector
    mov al, 0x20                    ; Number of sectors in root = max_root_entries * 32 / bytes_sector = 512 * 32 / 512 = 32
    mov si, a                       ; Read root dir message*
    call print_str                  ; Print!*

.read_disk:
    int 0x13                        ; BIOS disk interrupt
    jnc .search_init                ; If successful, get ready to search the disk
    call reset_disk                 ; Otherwise, reset the disk
    jmp .read_disk                  ; And retry

.search_init:
    mov si, success                 ; Success message*
    call print_str                  ; Print!*
    mov ax, ds
    mov es, ax                      ; Move data segment to extra segment
    mov di, disk_buffer             ; Location of disk buffer (ES:DI will be the location of the root entry we will be checking)
    mov si, r_name                  ; Location of filename of R (DS:SI will be the location of the string to compare to the root entry)
    mov bx, 0x00                    ; Start at root entry 0
    push si                         ; Push*
    mov si, b                       ; Search message*
    call print_str                  ; Print!
    pop si                          ; Pop*

.check_entry:
    mov cx, 0x0B                    ; Compare the first 11 bytes
    push si                         ; Push filename location to stack
    push di                         ; Push entry location to stack
    repe cmpsb                      ; Compare the two strings
    pop di                          ; Restore entry location
    pop si                          ; Restore filename location
    je .found_entry                 ; If equal, we found the root entry!
    add di, 0x20                    ; Otherwise, move to next entry
    inc bx                          ; Number of next entry
    cmp bx, [max_root_entries]      ; Have we gone through all root entries?
    jg .missing                     ; If so, R is missing
    jmp .check_entry                ; Otherwise, look at this next entry

.found_entry:
    mov si, success                 ; Success message*
    call print_str                  ; Print!*
    mov ax, word [es:di+0x1A]
    mov word [cluster], ax          ; Move starting cluster number to our spot in memory

    mov bx, disk_buffer             ; ES:BX points to disk buffer
    mov ax, 0x01                    ; 1st FAT begins at logical sector 1
    call ls_chs                     ; Convert to head, track and sector
    mov al, [sectors_fat]           ; Read all sectors in FAT
    mov si, c                       ; Read FAT message*
    call print_str                  ; Print!*

.read_fat:
    int 0x13                        ; BIOS disk interrupt
    jnc .read_cluster               ; If successful, load the first cluster of the file
    call reset_disk                 ; Otherwise, reset the disk
    jmp .read_fat                   ; And try again

.read_cluster:
    mov si, d                       ; Attempt to read cluster message*
    call print_str                  ; Print!*
    mov ax, 0x4200          ; End of disk_buffer = (sectors_record + max_root_entries * 32 / bytes_sector) * bytes_sector = 16,896
    mov es, ax                      ; Segment into which we will load R
    mov bx, word [buffer_pointer]   ; Spot into which we will load this cluster
    mov ax, word [cluster]          ; Cluster to read
    add ax, 0x1F                    ; Convert to logical sector
    call ls_chs                     ; Convert to head, track and sector
    mov al, [sectors_cluster]       ; Read the number of sectors in 1 cluster
    int 0x13                        ; BIOS disk interrupt
    jnc .find_next_cluster          ; If successful, find the next cluster
    call reset_disk                 ; Otherwise, reset the disk
    jmp .read_cluster               ; And try again

.find_next_cluster:
    mov si, success                 ; Success message*
    call print_str                  ; Print!*
    mov ax, word [cluster]          ; Location of current cluster
    mov bx, 0x02                    ; There are two bytes per entry in FAT16
    mul bx                          ; The memory location of CLUSTER should fit in AL
    mov si, disk_buffer             ; Location of start of FAT
    add si, ax                      ; Add the number of bytes until current cluster
    mov ax, word [ds:si]            ; Number of next cluster
    mov word [cluster], ax          ; Store this
    cmp ax, 0xFFF8                  ; Check whether this next cluster is an end-of-file marker
    jae .jump                       ; If it is, we have fully loaded the kernel
    add word [buffer_pointer], 0x0200 ; Otherwise, increment the buffer pointer a sector length
    jmp .read_cluster               ; And load it into memory

.jump:
    mov si, loaded                  ; Print a J
    call print_str                  ; Print!
    mov ah, 0x00                    ; Read keyboard buffer
    int 0x16                        ; BIOS keyboard interrupt
    mov dl, byte [drive_num]        ; Make the boot drive number accessible to R
    jmp 0x4200:0x0000               ; Jump to R's location!

.missing:
    mov si, m_r_missing             ; Display the missing message
    call rsod                       ; Display it in a Red Screen of Death

reset_disk:
    pusha                           ; Push register states to stack
    mov ax, 0x00                    ; RESET disk
    mov dl, byte [drive_num]        ; Boot drive number
    int 0x13                        ; BIOS disk interrupt
    jc .disk_fail                   ; If failed, fatal error and reboot
    popa                            ; Restore register states
    ret                             ; And retry

.disk_fail:
    mov si, m_disk_error            ; Display the disk error message
    call rsod                       ; Display it in a Red Screen of Death

print_str:                          ; Prints string pointed to by REGISTER SI to cursor location (si=str)
    pusha                           ; Push register states to stack
    mov ah, 0x0E                    ; BIOS will PRINT

.repeat:
    lodsb                           ; Load next character from SI
    cmp al, 0x00                    ; Is this a null character?
    je .ret                         ; If it is, return to caller
    int 0x10                        ; Otherwise, BIOS interrupt
    jmp .repeat                     ; Do this again

.ret:
    mov ah, 0x00                    ; Read keyboard buffer
    int 0x16                        ; BIOS keyboard interrupt      
    popa                            ; Restore register states
    ret                             ; Return to caller

ls_chs:                             ; Convert logical sector to head, track, and sector configuration for int 0x13 (AX = logical sector)
    mov dx, 0x00                    ; Upper word of dividend is 0
    div word [sectors_track]        ; Divide to find the number of tracks before this
    mov cl, dl                      ; The remainder is the number of the sector within the track
    add cl, 0x01                    ; Sectors start at 1, not 0
    mov dx, 0x00                    ; Upper word of dividend is 0
    div word [heads]                ; Divide by number of heads/sides
    mov dh, dl                      ; The remainder is the head number (it should only take up the lower half of DX)
    mov ch, al                      ; The quotient is the track number (it should only take up the lower half of CX)
    mov dl, byte [drive_num]        ; Boot drive number
    mov ah, 0x02                    ; READ disk sectors
    ret                             ; Return to caller

rsod:                               ; Red Screen of Death (SI = line to print)
    mov al, 0x20                    ; SPACE
    mov bh, 0x00                    ; Page 0
    mov bl, 0x40                    ; Red background
    mov cx, 0x50                    ; Enough to fit the screen width

.repeat:
    mov ah, 0x09                    ; Write character and attribute
    int 0x10                        ; BIOS VGA interrupt
    mov ah, 0x03                    ; Get cursor position
    int 0x10                        ; BIOS VGA interrupt
    cmp dh, 0x1A                    ; Have we gone all the way down the screen?
    jge .write                      ; If we have, return to caller
    inc dh                          ; Otherwise, next row down
    mov ah, 0x02                    ; Set cursor position
    int 0x10                        ; BIOS VGA interrupt
    jmp .repeat                     ; Do this again for the next line

.write:
    mov ah, 0x02                    ; Set cursor position
    mov dh, 0x01                    ; Row 1
    mov dl, 0x03                    ; Col 3
    int 0x10                        ; BIOS VGA interrupt
    push si                         ; Push line to stack
    mov si, fatal                   ; Prepare to display "FATAL" message
    call print_str                  ; Print!
    pop si                          ; Restore line and prepare to print it
    call print_str                  ; Print!
    mov si, press_a_key             ; Prepare to display prompt
    call print_str                  ; Print!
    mov ah, 0x00                    ; Wait for keyboard input
    int 0x16                        ; BIOS keyboard input
    int 0x19                        ; Reboot

data:
    r_name          db "R       BIN"        ; Filename of R
    cluster         dw 0x0000               ; Cluster that we are working with
    buffer_pointer  dw 0x0000               ; Pointer to offset of buffer
    drive_num       db 0x00                 ; Boot drive number
    fatal           db "FATAL: ", 0x00      ; Fatal error message
    press_a_key     db "! Press a key", 0x00; Instruct the user to press a key and reboot
    m_r_missing     db "R missing", 0x00    ; Missing message
    m_disk_error    db "Disk failed", 0x00  ; Disk error message
    a               db "A", 0x00            ; About to read root dir*
    b               db "B", 0x00            ; About to search root dir*
    c               db "C", 0x00            ; About to read FAT*
    d               db "D", 0x00            ; About to attempt cluster read*
    success         db "!", 0x00            ; Success!*
    loaded          db "J", 0x00            ; Loaded R message*

    times 510-($-$$) db 0x00        ; Pad remainder of boot sector
    sig             dw 0xAA55       ; Boot signature

disk_buffer:                        ; Space in memory for loading disk contents

I expect Linux to recognize the filesystem as FAT16 with this written to the boot sector, but it is not recognizing it at all and therefore not allowing me to copy R.BIN.

Gar answered 22/7, 2019 at 23:39 Comment(11)
That's not the code you have. Please always make sure you post exact code. I assume you have jmp marble_main not jmp main.Abandon
Also, how does linux not recognize it? When you try to mount? Works fine here.Abandon
After I write the bootloader, when I view the loop volume in the "Disks" application, it lists the contents as "Unknown".Gar
No idea what that application is, or why you care. What I have tried is to simply mount it and it works. Also I checked with mtools and that does produce a warning as follows: "Total number of sectors (4085) not a multiple of sectors per track (18)!" so maybe fix that.Abandon
What exactly are you mounting?Gar
Furthermore note that simply putting a boot sector on top of a blank image will not produce a valid file system. It would be best to create the file system first with standard tools, copy your kernel and then just the code portion of your boot sector, leaving the BPB intact.Abandon
With DD? It worked before.Gar
How would I write the code of my bootloader itself to the boot sector of a filesystem?Gar
You can use mformat -B. Or you can create a file system using whatever means, figure out the code offset for it, drop everything from your assembly before main, add org with the offset and use dd with the same offset.Abandon
As Jester says you could use mformat -B to do it. If you don't want to use mformat You could use DD to extract the BIOS Parameter Block out of the formatted disk and save it to a file. Then write your bootloader to the boot sector with DD and then you write the saved BPB into the boot sector.Tortosa
sectors dw 0x0FF5 is inapt for a FAT16 file system. (It results in a max cluster below 0FF0h, which is detected as FAT12.) Use my answer's method to preserve the BPB when installing the loader.Oxbridge
O
1

Here's how I just installed a custom boot sector loader into an image and have it work afterwards. This is a FAT12 image that is 1440 KiB long in my case, but the size of the BPB + BPB new fields is the same for FAT16. Please report whether this solves your problem.

$ dd if=bldbg12.bin of=floppy.img bs=1 count=11 conv=notrunc
11+0 records in
11+0 records out
11 bytes copied, 0.000161134 s, 68.3 kB/s
$ dd if=bldbg12.bin of=floppy.img bs=1 count=$((512 - 0x3e)) seek=$((0x3e)) skip=$((0x3e)) conv=notrunc
450+0 records in
450+0 records out
450 bytes copied, 0.00150799 s, 298 kB/s
$

A minor issue that most likely does not cause your error is that jmp main should be jmp strict short main.

Oxbridge answered 23/7, 2019 at 10:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.