I have a simple program. It must read first sector from hard drive (not mbr), and write it to the 0 sector (mbr). But it doesnt work. I think it is connected with wrong DAP. Thanks.
[bits 16]
[org 0x7c00]
;clear screen
start:
mov ax, 0x3
int 0x10
;reset the hard drive
xor ah, ah
mov dl, 0x80
int 0x13
jnz error
;read the second sector
mov si, DAP
mov ah, 0x42
int 0x13
mov si, data
call print_string
jmp $
DAP:
db 0x10 ;size of DAP
db 0x0 ;zero
db 0x1 ;number of sectors to read
db 0x0 ;zero
;point to memory
dw 0x0 ;offset
dw 0x0 ;segment
dq 0x1 ;disk address
DAP2:
db 0x10
db 0x0
db 0x1
db 0x0
dw 0x0
dw 0x0
dd 0x0
dd 0x0
print_string:
mov ax, 0xb800
mov es, ax
xor di, di
mov cx, 8
rep movsw
ret
data: db 'H',2,'e',2,'l',2,'l',2
error:db 'E',2,'r',2,'r',2
times 510 - ($ - $$) db 0
dw 0xaa55
UPD: new code
[bits 16]
[org 0x7c00]
;clear screen
start:
; mov ah, 0
; push ax
; pop ds
mov ax, 0x3
int 0x10
;reset the hard drive
xor ah, ah
mov dl, 0x80
int 0x13
jc error
;read the second sector
mov si, DAP
mov ah, 0x42
int 0x13
mov si, data
call print_string
jmp $
DAP:
db 0x10 ;size of DAP
db 0x0 ;zero
db 0x1 ;number of sectors to read
db 0x0 ;zero
;point to memory
dw 0x0 ;offset
dw 0x8c00 ;segment
dq 0x1 ;disk address
DAP2:
db 0x10
db 0x0
db 0x1
db 0x0
dw 0x0
dw 0x8c00
dq 0x2
print_string:
mov ax, 0xb800
mov es, ax
xor di, di
mov si, 0x8c00
mov cx, 8
rep movsw
ret
data: db 'H',2,'e',2,'l',2,'l',2
error:db 'E',2,'r',2,'r',2
endp:
times 510 - ($ - $$) db 0
dw 0xaa55
P.S. I'm using Bochs.
A bit of necrophilia; hopefully your assembly skills have improved in the meantime. But just in case...
To quote @Alexey Frunze, "You need to pay attention to what you're doing". In addition to the mistakes detailed in the other answers, here are some of my observations:
Your emulator is too kind
Your code appears to be a bootloader. You assume the BIOS will load your code at 0x0000:0x7C00
, but you cannot be sure it doesn't in fact load it at 0x07C0:0000
, or any other equivalent address. Read up on segmentation.
You fail to initialise any segment registers. You probably get away with it because your emulator is kind, and correctly initialises cs
, ds
, and es
to 0x0000
.
You can solve both of these problems like this:
[bits 16]
[org 0x7C00]
jmp 0x0000:start_16 ; ensure cs == 0x0000
start_16:
; initialise essential segment registers
xor ax, ax
mov ds, ax
mov es, ax
Fundamental misunderstandings
In the event of an error, you jump directly to a string, rather than executable code. Lord only knows what the computer will do if that happens.
You check the return value (CF) of the drive reset, but not the read itself. In the event of a read fail, you should reset the drive and attempt the read again. Do this in a loop for a few attempts (say, 3) in case the drive is hiccuping. If the drive reset fails, it's likely something more serious is wrong, and you should bail.
A safer approach
I would suggest using int 0x13, ah = 0x02
. You are using an extended BIOS function that may not be supported on all systems (emulator support might be flakey, not to mention the lazy BIOS implementations found on some modern-day hardware). You're in real mode - you don't need to do anything fancy. It would be best to get into protected mode with the long-term goal of writing a PM driver to handle disk I/O.
As long as you stay in real mode, here is a standalone function that will read one or more sectors from disk using simple BIOS functions. If you don't know in advance which sector(s) you need, you will have to add additional checks to take care of multitrack reads.
; read_sectors_16
;
; Reads sectors from disk into memory using BIOS services
;
; input: dl = drive
; ch = cylinder[7:0]
; cl[7:6] = cylinder[9:8]
; dh = head
; cl[5:0] = sector (1-63)
; es:bx -> destination
; al = number of sectors
;
; output: cf (0 = success, 1 = failure)
read_sectors_16:
pusha
mov si, 0x02 ; maximum attempts - 1
.top:
mov ah, 0x02 ; read sectors into memory (int 0x13, ah = 0x02)
int 0x13
jnc .end ; exit if read succeeded
dec si ; decrement remaining attempts
jc .end ; exit if maximum attempts exceeded
xor ah, ah ; reset disk system (int 0x13, ah = 0x00)
int 0x13
jnc .top ; retry if reset succeeded, otherwise exit
.end:
popa
retn
Your print function assumes a colour monitor (by writing to video memory at 0xB8000). Again, you're in real mode. Keep it simple. Use a BIOS service:
; print_string_16
;
; Prints a string using BIOS services
;
; input: ds:si -> string
print_string_16:
pusha
mov ah, 0x0E ; teletype output (int 0x10, ah = 0x0E)
mov bx, 0x0007 ; bh = page number (0), bl = foreground colour (light grey)
.print_char:
lodsb ; al = [ds:si]++
test al, al
jz .end ; exit if null-terminator found
int 0x10 ; print character
jmp .print_char ; repeat for next character
.end:
popa
retn
Example usage
load_sector_2:
mov al, 0x01 ; load 1 sector
mov bx, 0x7E00 ; destination (might as well load it right after your bootloader)
mov cx, 0x0002 ; cylinder 0, sector 2
mov dl, [BootDrv] ; boot drive
xor dh, dh ; head 0
call read_sectors_16
jnc .success ; if carry flag is set, either the disk system wouldn't reset, or we exceeded our maximum attempts and the disk is probably shagged
mov si, read_failure_str
call print_string_16
jmp halt ; jump to a hang routine to prevent further execution
.success:
; do whatever (maybe jmp 0x7E00?)
read_failure_str db 'Boot disk read failure!', 13, 10, 0
halt:
cli
hlt
jmp halt
Last but not least...
Your bootloader doesn't set up a stack. The code I provided uses the stack to prevent register trashing. There is almost 30KiB available before the bootloader (< 0x7C00), so you can simply do this somewhere near the start of your bootloader:
xor ax, ax
cli ; disable interrupts to update ss:sp atomically (AFAICT, only required for <= 286)
mov ss, ax
mov sp, 0x7C00
sti
Phew! A lot to digest. Notice I've tried to keep the standalone functions flexible, so you can re-use them in other 16-bit real mode programs. I'd suggest you try to write more modular code, and stick to this approach until you're more experienced.
For example, if you're dead set on using the extended read function, perhaps you should write a function that accepts a DAP, or a pointer to one, on the stack. Sure, you'll waste code space pushing the data there in the first place, but once it's there you can simply adjust the necessary fields for subsequent reads, rather than having lots of DAPs taking up memory. The stack space can be reclaimed later.
Don't be disheartened, assembly takes time, and monstrous attention to detail... not easy when bashing this stuff out at work, so there might be errors in my code! :)
First of all, you need to check cf
and not zf
to see if the BIOS call succeeded. Correct your jnz error
.
Secondly, you seem to be relying on ds
being equal to 0. It's not guaranteed to be 0. Set it to 0.
Ditto for flags.df
, it's not guaranteed to be 0. Set it to 0. Check the documentation on rep
, movs*
and cld
.
Third, you ask BIOS to read the sector and write it to physical address 0 in memory. By doing so you overwrite the interrupt vector table (that starts there and occupies 1KB) and damage the system, needing a reboot. Choose a better address. The best would be right after the end of the bootsector in memory. But you'd also need to make sure the stack isn't there, so you need to set the stack to a known location as well.
You need to pay attention to what you're doing.
Minimal NASM BIOS example that loads a sector with code and jumps to it, without error checking and DAP:
use16
org 0x7C00
; For greater portability you should
; do further initializations here like setup the stack and segments.
; Load stage 2 to memory.
mov ah, 0x02
mov al, 1
; This may not be necessary as many BIOS setup is as an initial state.
mov dl, 0x80
mov ch, 0
mov dh, 0
mov cl, 2
mov bx, stage2
int 0x13
jmp stage2
; Magic bytes.
times ((0x200 - 2) - ($ - $$)) db 0x00
dw 0xAA55
stage2:
; Print 'a'.
mov ax, 0x0E61
int 0x10
cli
hlt
; Pad image to multiple of 512 bytes.
times ((0x400) - ($ - $$)) db 0x00
Compile and run:
nasm -f bin -o main.img main.asm
qemu-system-i386 main.img
Expected outcome: a
gets printed to the screen, and then the program halts.
Tested on Ubuntu 14.04.
Saner GAS example using a linker script and more correct initialization (segment registers, stack) on my GitHub.
load:
; Load sectors routine : bootdrv-drive , snum-sectors to load
;ES:BX where to load ex: mov ax,0000h mov es,ax mov bx, 7c00h
push bx
push ds
mov verify,0h
.reset:
cmp verify,5h
je Err1
add verify,1h
mov ax, 0 ; Reset Disk
mov dl, [BootDrv] ; Drive to reset
int 13h ;
jc .reset ; Failed -> Try again
pop ds
pop bx
mov verify,0h
.read:
cmp verify,5h
je Err1
add verify,1h
mov ah, 2 ; Interrupt 13h,2 => Read disk sectors
mov al, snum ; how many sectors to read
mov cx, fsect ; cl-first sector to be r/w ch-track containing sector
mov dh, head ; head=0
mov dl, [BootDrv] ; Drive=boot drive
int 13h ; Read! ES:BX = data from disk
jc .read ; failed -> Try again
retn