MechMK1
MechMK1

Reputation: 3378

Load segment from floppy with int13h

I am currently trying to write 16 bit real mode boot code which prints a letter, then loads a second segment from floppy and jumps to it, which then also prints a letter.

However, I am a little confused about how the Read Sectors From Drive call works. Here is my code so far:

[BITS 16]
org 0x7B00

start:
        mov ax, 0xB800 ; Video buffer
        mov es, ax     ; Copy address of video buffer to extra segment

        mov byte [es:0], 'A'    ; Move character A to first address
        mov byte [es:1], 0x17   ; Format for blue background, white foreground

        mov ah, 0x02 ; Read sectors from drive
        mov al, 1    ; Read 1 sector
        mov ch, 0    ; Cylinder 0
        mov cl, 0    ; Sector 0
        mov dh, 0    ; Head 0
        mov dl, 0    ; Drive 0 (Floppy)
        mov word [es:bx], sect2dest ; <- Completely unsure about this.

        int 0x13

        jmp sect2dest:0

data:
        sect2dest equ 0x0500

The second assembler file, which I copy to the second sector of the floppy, looks like this:

[BITS 16]
org 0x5000

sect2:

        mov ax, 0xB800
        mov es, ax

        mov byte [es:2], 'B'
        mov byte [es:3], 0x17

        jmp $

However, instead of printing anything, my cursor just turns purple. I know, this is a strange question which could likely easily be solved with better ASM skills, so please excuse me

Upvotes: 2

Views: 1882

Answers (1)

Michael Petch
Michael Petch

Reputation: 47573

I highly recommend you take a look at my General Bootloader Tips that I wrote in a previous StackOverflow answer. You should consider setting up a stack in your bootloader code, set the DS (Data Segment) and you should use the boot drive supplied by the BIOS in the DL register when your bootloader is executed. This would allow you to boot from a drive that may not be the first floppy. My first tip outlines that:

When the BIOS jumps to your code you can't rely on CS,DS,ES,SS,SP registers having valid or expected values. They should be set up appropriately when your bootloader starts. You can only be guaranteed that your bootloader will be loaded and run from physical address 0x00007c00 and that the boot drive number is loaded into the DL register.


Bootloader Problems


The master boot record (first sector of a disk) is loaded by the BIOS to 0x7c00. Your ORG directive uses 0x7b00. You should change:

org 0x7B00

to:

org 0x7C00

I'm not sure if you cut your code off, but at the very bottom of your boot loader you didn't place the 0xAA55 magic value in the last word of the sector to flag your boot sector as bootable. You should add something like this to the bottom of your boot laoder:

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

If you were using a linker script (there are indications you aren't) then you could use that to insert 0xaa55 at the end of the first sector. It is possible you are doing this in some other way that is not shown in your question. If you are inserting 0xAA55 in some other manner, then you can ignore this change.


Ralph Brown's Interrupt List is probably the best source of interrupt documentation (including BIOS interrupts). In particular the int 13h/ah=02 read sector is documented as:

AH = 02h
AL = number of sectors to read (must be nonzero)
CH = low eight bits of cylinder number
**CL** = sector number 1-63 (bits 0-5)
high two bits of cylinder (bits 6-7, hard disk only)
DH = head number
**DL** = drive number (bit 7 set for hard disk)
**ES:BX** -> data buffer

Because DL contains the boot drive number passed by the BIOS, you can remove this line since DL is already the value we want:

mov dl, 0    ; Drive 0 (Floppy)

This change allows us to read sectors from the boot drive actually used by the BIOS, which means the code would work for booting from Floppy B (DL =0x01) or a hard drive (DL =0x80 etc)

The sector numbers in CL to start reading at is a value between 1 and 63. Sector numbers (in CHS format) are unusual in that they are 1 based instead of 0. Sector 1 on Cylinder 0 Head 0 is the boot sector. Sector 2 on Cylinder 0 Head 0 is the sector right after the boot record. Given your question it seems you meant for this line:

mov cl, 0    ; Sector 0

To be:

mov cl, 2    ; Sector 2

You marked this code as a possible problem, and you are correct:

mov word [es:bx], sect2dest

This moves the constant sect2dest to the word in memory starting at ES:BX. BX likely has zero in it (it is never initialized so could be anything), and since ES still points to the video memory you likely wrote 0x0500 to the first cell of the video display. What ES:BX means in the documentation is that ES and BX should be the segment and offset you want to read disk sectors into memory at. Since it appears you intend to load your second stage at 0x0500:0x0000 you need to set ES=0x0500 and BX=0x0000 . Remove this code:

mov word [es:bx], sect2dest ; <- Completely unsure about this.

And replace it with:

mov bx, sect2dest
mov es, bx          ; ES = 0x0500
xor bx, bx          ; BX = 0. So ES:BX=0x0500:0x0000

Second Stage Problems


In your second stage org 0x5000 should be org 0x0000. The reason for this is that jmp 0x0500:0x0000 will set segment:offset CS:IP to CS=0x0500 and IP=0x0000. You need your ORG directive to match the offset that you jump to. You should use ORG 0x0000 because you'll be getting an IP (offset)=0x0000 with the far jump jmp sect2dest:0.

Change:

org 0x5000

To

org 0x0000

You should set up the DS data segment in your second stage by copying CS (0x0500) to DS. You can add this to the top of your code in the second stage:

mov ax, cs 
mov ds, ax

This effectively makes DS=CS. You don't need to do this for the sample code shown, but if you add a .data section you'll need it to properly access your variables.


Code After Changes

bootloader assembly code:

[BITS 16]
org 0x7C00

start:
        ; This section of code is added based on Michael Petch's bootloader tips
        xor ax,ax      ; We want a segment of 0 for DS for this question
        mov ds,ax      ;     Set AX to appropriate segment value for your situation
        mov bx,0x8000  ; Stack segment can be any usable memory

        cli            ; Disable interrupts to circumvent bug on early 8088 CPUs
        mov ss,bx      ; Top of the stack @ 0x80000.
        mov sp,ax      ; Set SP=0 so the bottom of stack will be just below 0x90000
        sti            ; Re-enable interrupts
        cld            ; Set the direction flag to be positive direction

        mov ax, 0xB800 ; Video buffer
        mov es, ax     ; Copy address of video buffer to extra segment

        mov byte [es:0], 'A'    ; Move character A to first address
        mov byte [es:1], 0x17   ; Format for blue background, white foreground

        mov ah, 0x02 ; Read sectors from drive
        mov al, 1    ; Read 1 sector
        mov ch, 0    ; Cylinder 0
        mov cl, 2    ; Sector 2
        mov dh, 0    ; Head 0
        mov bx, sect2dest 
        mov es, bx   ; ES = sect2dest
        xor bx, bx   ; BX = 0
        int 0x13

        jmp sect2dest:0

data:
        sect2dest equ 0x0500

times   510-($-$$) db 0     ; Create padding to fill out to 510 bytes
dw      0xaa55              ; Magic number in the trailer of a boot sector

Second stage assembly code:

[BITS 16]
org 0x0000

sect2:
        mov ax, cs
        mov ds, ax    ; Set CS=DS. CS=0x0500, therefore DS=0x500
                      ; If variables are added to this code then this
                      ; will be required to properly reference them
                      ; in memory

        mov ax, 0xB800
        mov es, ax

        mov byte [es:2], 'B'
        mov byte [es:3], 0x17

        jmp $

Upvotes: 13

Related Questions