Page 1 of 1

Loading from disk works on qemu but not on hardware

Posted: Thu Apr 09, 2020 11:29 pm
by digit113
I've got a simple bootloader which uses int 13h, ah=2 to load the 2nd sector of the boot drive into memory right after where the boot sector is loaded- at 0x7e00. Then I check that the disk read didn't report an error by checking the carry flag, then print out some of the loaded data, and finally show the status after the disk read. On qemu and bochs, this gives the expected result:

Code: Select all

No Error.
A1B2C3
0100
Where No Error is based on the carry flag value, A1B2C3 are the first three bytes of the sector that was loaded, and 0100 are the number of sectors loaded (just sector 2) and the status code, both of which were stored in ax after the interrupt and pushed to the stack for safekeeping.
On real hardware however, (Specifically on a 32-bit Intel from the Celeron family and a 64-bit AMD FX-8320) it outputs:

Code: Select all

No Error.
000000
0100
Indicating by all accounts that the code ran smoothly, but for some reason failing to load and print the actual values. I know that these values are being loading onto my USB because I copied the compiled code on and off again and the copy works fine. I also made sure to properly setup the stack and segment registers, Checked the compiled code to ensure that the read and write locations were in fact identical, and tried it with both the x86_64 and i386 emulators. I also tried removing the org statement and setting all the segment registers to 0x7c0 instead. Not sure why that would help, but I was running out of ideas. Here's the code:

Code: Select all

[org 0x7c00]
[bits 16]

; Init segment registers
mov bx, 0x0
mov ds, bx
mov ss, bx
mov es, bx
mov fs, bx
mov gs, bx

; Setup Stack
mov bp, 0x9000
mov sp, bp

; Save this number, it's our boot drive by default
mov [BOOT_DRIVE], dl

; After the boot sector, some data is included which must be loaded from the disk manually.
mov ah, 0x02 ; Read sectors from disk

mov ch, 0 ; Cylinder 0
mov dh, 0 ; Head 0
mov cl, 2 ; Sector 2

mov al, 1 ; Read 1 sector

; Data will be read to es:bx
mov bx, data

int 0x13
push ax ; Save the status

jc disk_error
	mov bx, nermsg
	call prn_str 
	mov bx, data
	mov cx, 3
	call prn_hex
	jmp post_disk_error_check
	
disk_error:
	mov bx, errmsg
	call prn_str 

post_disk_error_check:

; Move the cursor to the next line
mov ah, 0x0e
mov al, 10
int 0x10
mov al, 13
int 0x10

; Prints out two bytes: al (Number of sectors read), and ah (status). These values were saved to the stack immediately after the interrupt.
mov bx, sp
mov cx, 2
call prn_hex
pop ax

jmp $

%include "lib/prn_str.asm"
%include "lib/prn_hex.asm"

errmsg: db "General disk read error.", 10, 13, 0

nermsg: db "No error.", 10, 13, 0

BOOT_DRIVE: db 0

times 510-($-$$) db 0
dw 0xaa55

data:
	db 0xA1, 0xB2, 0xC3

times 1024-($-$$) db 0
And the debug functions. They both preserve all registers and appear to work just fine.

Code: Select all

; Prints a null terminated ASCII string using the int 10h teletype routine.
; Takes: BX as pointer to start of string
prn_str:
	push ax
	push bx
	mov ah, 0x0e
	
	prn_str_loop:
	
	; Get value at pointer
	mov al, [bx]
	
	; Check for null value
	cmp al, 0
	je prn_str_ret
	
	int 0x10
	
	inc bx
	jmp prn_str_loop
	
	prn_str_ret:
	pop bx
	pop ax
	ret

Code: Select all

; Prints the 16-bit hexadecimal value pointed to using the int 10h teletype routine.
; Takes: BX as pointer to first value to print, CX as number of values to print
prn_hex:
	push ax
	push bx
	push cx
	
	mov ah, 0x0e
	
	prn_hex_loop:
		cmp cx, 0
		je prn_hex_end ; If number of bytes remaining is zero, quit
		
		mov al, [bx] ; Isolate the current significant byte, at bx
		shr al, 0x04 ; Isolate the most significant 4 bits
		cmp al, 0xA  ; Test if this value is represented by a number (<10) or a letter (>=10)
		jl prn_hex_high_is_number
			; high is a letter
			add al, 55
			int 0x10
			jmp prn_hex_high
		prn_hex_high_is_number:
			; high is a number
			add al, 48
			int 0x10
		prn_hex_high:
		
		mov al, [bx] ; Isolate the current significant byte, at bx
		and al, 0x0F ; Isolate the least significant 4 bits
		cmp al, 0xA  ; Test if this value is represented by a number (<10) or a letter (>=10)
		jl prn_hex_low_is_number
			; low is a letter
			add al, 55
			int 0x10
			jmp prn_hex_low
		prn_hex_low_is_number:
			; low is a number
			add al, 48
			int 0x10
		prn_hex_low:
		
		; Now that this byte is printed, get to next byte and decrement the number of bytes remaining counter (cx)
		inc bx
		dec cx
		
		jmp prn_hex_loop
	
	prn_hex_end:
		pop cx
		pop bx
		pop ax
		ret