Read a write a sector from hard drive with int 13h
Asked Answered
C

4

8

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.

Conservatoire answered 19/3, 2013 at 11:10 Comment(6)
first line of your question says "0 sector(mbr)" but you said MBR is at sector 1Totten
I don't understand why you zero out only ah and not the entire ax when zeroing ds. ds and ax are 16-bit, while ah and al are 8-bit.Hutchens
I zero out ah, cause it is the number of function (reset the hard drive)Conservatoire
Superset: load and jump to it: #2065870Stepha
@Vanzef, although your question is old it has been revived. At the time you asked your question, I'm curious why it was you were trying to read the second sector and overwrite the MBR with it? I'm asking because such an answer might clarify what you were really trying to achieve by doing so.Josselyn
@MichaelPetch Even I am copying sector 2 (random junk) to sector 1 to check if the copy really happened (if the copy was successful, the disk should become unbootable). This is of course a dumb way to check. Too lazy write better methods. I think Vanzef is trying the same.Nullity
A
25

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! :)

Agent answered 23/7, 2014 at 10:3 Comment(3)
Your answer is a thorough one. I wanted to comment about the CLI/STI section. What you do with CLI/STI is a good idea if targeting a wide variety of hardware. on 8086/8088/80286 CLI/STI wouldn't have been needed either, BUT there was a bug in some very early 8088's (but not all of them) where interrupts were not turned off (per specification) for the duration of the segment register update and following instruction. On the 8086/8088 it was suppose to this for all the segment registers (as of the 286 it only applied to SS and not other segment registers).Josselyn
If not for the bug on 8088 explicit CLI/STI would never have been needed. But if you want to target even buggy 8088s using CLI/STI is a good idea!Josselyn
This is exactly what I was looking for! With the [7:0] and [8:9] are these bit fields? So the cylinder number is defined in 10 bits?Episcopacy
H
8

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.

Hutchens answered 19/3, 2013 at 11:37 Comment(2)
I changed jnz to jc, writing position to 0x8c00. But it doesnt work. Also if I set ds=0 program crashes.Conservatoire
Add the new code to the question, so we can see it. But don't delete the original code, so the answer(r) remain(s) relevant.Hutchens
S
2

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.

Stepha answered 5/10, 2015 at 7:45 Comment(14)
If anyone can guess why the downvote, please do so I can learn and improve information. I never retaliate.Stepha
It wasn't me, but given that there is a perfectly good answer with 10 upvotes that does provide decent background and fundamentals; uses BIOS extended disk reads (which may be useful if the OP will be reading more of the hard drive later on);answers the question about DAP; initializes the register ES which should be set properly to have a better chance of running across VMs/hardware/emulators - I'd say your answer doesn't look like an improvement. I like the up voted one because it tries to answer the why a lot better. Yours also happens to give usage for QEMU and OP is using BochsJosselyn
I'd be curious why you feel the popular answer here wasn't sufficient? I'll leave comment to the popular answer though about the CLI/STI section of the answer to clarify a bit one point.Josselyn
@MichaelPetch thanks for the feedback! My problem with the top answer is format: it contains a lot of (useful) information, but what I wanted when I came here was a minimal example that I can copy paste and make the read work. I interpreted this as a X -> Y question, and answered the Y. Since the OP does not give emphasis to DAP, I felt it was not central to the question. For instance, this more Y-ish question is about to be closed as a dupe of this: #19381934Stepha
@MichaelPetch about ES you are right, and it is similar for other initializations like CS, DS and stack: I'm getting lazy of copying it around all the time, I think I will leave a comment. :-)Stepha
The OP says " I think it is connected with wrong DAP." His code uses extended BIOS read which requires a DAP. His code creates a DAP. So DAP is pertinent to his code. Both you and the OPs code require ES:BX to be set properly (int 13h disk reads use that segment register explicitly) so at a minimum your code is deficient if you want to target a wider variety of environments. The upvoted answer actually does it properly and explains it. Your code doesn't but requires you to go offsite to see code that does.Josselyn
Actually i think the existing answer provided is far superior (IMHO) because it actually EXPLAINS things (which I think it is has so many votes for the assembler tag). Yours doesn't. The upvoted answer can easily be taken and dropped in real code so doesn't need to provide everything. I won't be downvoting your answer. People will copy and paste your code and then come back to SO saying it may not work on a real machine or particular VM/emulator. If people copy and paste (which they do) I'd rather they do more accurate code.Josselyn
Part of the other answer also references off list material specific to the information provided (bibliography). It also mentions and provides code for the alternative non extended BIOS read (Yours only targets the one the OP wasn't using)Josselyn
One other important thing. The OP isn't asking to load a kernel and jump to it. He is asking how to load the second sector of the disk and write it to the first sector. Your code is answering a different question as well.Josselyn
@MichaelPetch I needed to have a side effect to the disk read so that my minimal example would do something observable. I could have put a value there and read it for example, but I though that jumping to it was more dramatic (and a common / interesting use case).Stepha
But that is not what this person is doing. His intent is to read a sector and then write it (you are trying to take your generic bootloader and load a kernel and splice it in here as an answer). The OP doesn't actually have his write sector code in his question (it is assumed he left it out, but he did show us his DAP2 entry as a reference point). None of the existing answers actually show code that writes the second sector out.Josselyn
I'd upvote a new answer that actually provides code using extended disk read and write (to stay inline with the OPs method), using his DAP entries that actually did a read AND write. None of the answers here actually does that. The answer would be more beneficial if it altered and fixed the existing code the OP is using rather than rewriting it. Such fixes to the code should be explained to show where the OP went wrong. The OPs code is almost workable as is. Would also be a good answer highlighting extended bios read and writes.Josselyn
@MichaelPetch I agree that would be a good, perhaps the best answer. Part of the problem is that the OP does not say "what" he is trying to do outside the code and people have to interpret a lot of code to understand that (kudos to you for doing that :-) ). So the title ends up taking a large weight, and lazy Googlers (like me) are likely to skip the question and read answers that answer the title directly. If I do make the answer you propose, I will ping you :-)Stepha
If you exclude the title of his question (which should be edited to say read AND write since his question does both): he has made it quite clear he wants to take the second sector and write it into the first. Why he wants to do it I don't know, but I have seen copying one MBR to another used over the years for unusual boot procedures that span multiple reboots. We can only really go by what the question asks, not by what we think his intentions were. It might have been a good idea when this question was first asked what the OP was using this code for (to get an understanding of context)Josselyn
D
-2
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
Dulcedulcea answered 22/11, 2013 at 8:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.