Page 1 of 1

Why does this code fail to read a sector of a floppy?

Posted: Sun Oct 12, 2014 5:00 pm
by Stijn
I'm trying to read a sector of a floppy and jump to it. I'm mainly using http://stanislavs.org/helppc/int_13.html for information.
I've two problems: .read fails, and it fails a lot more than 3 times. In VMware I see the "Failed read attempt" message 23 times (perhaps even more, but my buffer is only 24 lines) and one "Fatal error!" message.

What are my mistakes?

boot.asm

Code: Select all

; +-------+
; | 512 B | bootloader
; +-------+
; | 4 KiB | stack
; +-------+

org 0x7c00 ; loaded at 0x0000:0x7c00 by the BIOS

xor ax, ax
mov ds, ax ; set ds pointer to zero because org is set to 0x7c00

mov ax, 32 ; set the stack pointer, 32 paragraphs (512 B)
mov ss, ax
mov sp, 4096 ; set the stack top pointer, 4 KiB

jmp main

boot_message db "OS bootloader\r\n===================\0"
loading_kernel db "Loading kernel...\0"
kernel_loaded db "Kernel loaded!\0"
hello_from_kernel db "Hello from kernel\0"

failed_reset_attempt db "Failed reset attempt\0"
failed_read_attempt db "Failed read attempt\0"
fatal_error db "Fatal error!\0"
number_of_failed_reset_attempts db 0
number_of_failed_read_attempts db 0
maximum_number_of_attempts db 3

main:
	mov si, boot_message
	call io.write_line

	mov si, loading_kernel
	call io.write_line

	jmp loader

loader:
	.reset:
		mov ah, 0x00
		mov dl, 0x00
		int 0x13
		
		jnc .read ; success, carry bit is not set
		
		inc byte [number_of_failed_reset_attempts]
		mov si, failed_reset_attempt
		call io.write_line
		cmp byte [number_of_failed_reset_attempts], maximum_number_of_attempts
		je .fatal_error
		jmp .reset
	
	.read:		
		mov ah, 0x02
		mov al, 0x01 ; one sector
		mov ch, 0x00 ; first track
		mov cl, 0x02 ; second sector (1-based)
		mov dh, 0x00 ; first head
		mov dl, 0x00 ; first floppy drive
		mov ax, 0x2000 ; load at 0x2000:0x0000
		mov es, ax
		mov bx, 0x0000
		int 0x13
		
		jnc .kernel_loaded ; success, carry bit is not set
		
		inc byte [number_of_failed_read_attempts]
		mov si, failed_read_attempt
		call io.write_line
		cmp byte [number_of_failed_read_attempts], maximum_number_of_attempts
		je .fatal_error
		jmp .read

	
	.fatal_error:
		mov si, fatal_error
		call io.write_line
		ret
	
	.kernel_loaded:
		mov si, kernel_loaded
		call io.write_line
		jmp 0x2000:0x0000


%include "io.asm"

times 512-($-$$) db 0 ; zerofill the boot sector

%include "kernel.asm"

times 1474560 - ($ - $$) db 0 ; zerofill the rest of the floppy
io.asm

Code: Select all

io.write:
	mov ah, 0x0e
	
	.loop:
		lodsb
		cmp al, "\"
			je .escape_sequence
		int 0x10
		jmp .loop
	
	.escape_sequence:
		lodsb
		cmp al, "0"
			je .escape_sequence_0
		cmp al, "n"
			je .escape_sequence_n
		cmp al, "r"
			je .escape_sequence_r
		jmp .loop ; Unrecognised escape sequence, skip the character.
	
	.escape_sequence_0:
		ret
	
	.escape_sequence_n:
		mov al, 0x0a
		int 0x10
		jmp .loop
	
	.escape_sequence_r:
		mov al, 0x0d
		int 0x10
		jmp .loop

new_line db "\r\n\0"
io.write_line:	
	call io.write
	mov si, new_line
	call io.write
	ret
kernel.asm

Code: Select all

kernel:
	mov si, hello_from_kernel
	call io.write_line
	ret

Re: Why does this code fail to read a sector of a floppy?

Posted: Sun Oct 12, 2014 6:01 pm
by Nable

Code: Select all

mov ax, 32 ; set the stack pointer, 32 paragraphs (512 B)
mov ss, ax
mov sp, 4096 ; set the stack top pointer, 4 KiB
Your stack is at the region 32*16...32*16+4096 == 0x0200...0x1200 - it looks like a really bad idea, as you can randomly overwrite vital BIOS structures (see http://wiki.osdev.org/BDA#BIOS_Data_Area_.28BDA.29 for details).

Second thing: I don't see anything like "use16" or "[bits 16]" in your code. You should tell your assembler to use right translation mode.

There are more questionable things in your code but I think that it's better to correct these points and then.. go to a good debugger (such as the Bochs's one).

Re: Why does this code fail to read a sector of a floppy?

Posted: Sun Oct 12, 2014 11:42 pm
by Brendan
Hi,
Stijn wrote:What are my mistakes?
I can't find a likely reason for the read to fail in VMware (unless you're doing something silly, like telling VMware it's a hard disk image). Here's things I did find (some important, some less important):

First, floppy disks should have a BPB. If you don't have one then the BIOS will be happy anyway (at least, as long as it's not USB flash emulating floppy), but most versions of Windows will complain that the disk is unformatted and/or corrupt and prompt the user to format it. Most users will make the mistake of assuming Microsoft is right, and therefore assume your disk image is bad.

The stack is "sort of OK"; in that the BIOS probably won't use more than about 2 KiB, so you'd mostly be expecting the stack to use RAM from 0xA00 to 0x11FF. However, there's no good reason to use "ss:0x0020:0x1000" instead of "ss:sp = 0x0000:0x1200" (which would've been clearer); and "ss:sp = 0x0000:0x7C00" would be much safer. I assume you were hoping that if SP ever reached zero the program would stop (e.g. crash with a stack fault) before it did much damage, but this isn't likely - for real mode it's very likely that SP would just roll over from 0x0020:0x0000 to 0x0020:0xFFFE and trash stuff instead. Because there's no stack protection, your only real option is to have more stack than you can realistically expect will ever be needed.

For resetting the disk system; the BIOS just successfully used the floppy to load your boot loader, so you know the disk system is working fine and there's no reason to reset the disk system before your first read. Instead of "reset then read", you want "read, then reset and retry if read failed".

For the device number; the BIOS tells you the correct device number in DL. You ignore this and assume "device number 0x00", which may not be the correct in several cases.

This instruction is a bug:

Code: Select all

cmp byte [number_of_failed_reset_attempts], maximum_number_of_attempts
Here, "maximum_number_of_attempts" is the address of the label (e.g. maybe something like 0x7C99) and not the value at that address. Also the address won't fit in a byte, so depending on which assembler and which warnings/errors are enabled, it either won't assemble cleanly or might be truncated (e.g. becoming 0x99 instead of 0x7C99). In any case; I'd suggest using the pre-processor instead - e.g. "%define MAX_ATTEMPTS 3" and "cmp byte [number_of_failed_reset_attempts], MAX_ATTEMPTS".

Also note that when you're working with hard-coded values, you can combine them to save space (and improve performance). For example:

Code: Select all

		mov ax, (0x02 << 8) | 0x01    ;ah = function 2, al = one sector
		mov cx, (0x00 << 8) | 0x02    ;ch = first track, cl = second sector (1-based)
For "io.asm", I think the escape sequences are silly and wasteful. Instead, you can do things like:

Code: Select all

kernel_loaded: db "Kernel loaded!",10,13,0
This is less bytes and less run-time branches, and easier for humans to read/maintain.


Cheers,

Brendan

Re: Why does this code fail to read a sector of a floppy?

Posted: Fri Oct 17, 2014 5:20 pm
by Stijn
Thank you both for the input.

I gave Bochs another go and got it running, it requires a boot signature. I had left it out because VMware didn't complain.

After fixing the "maximum_number_of_attempts" bug, Bochs told me "int13_diskette: unsupported ah=20" and then I realised the mistake: "mov ax, 0x2000 ; load at 0x2000:0x0000" had to be "mov bx, ..."

Now there's a nice "Kernel loaded!" message on the screen :) I'll also look into the other suggestions you gave me. Thanks again!