Loading second stage of a bootloader
Asked Answered
F

1

5

I'm trying to create a small operating system for x86 machines and started writing the code for a fairly minimal bootloader. The bootloader I created is quite simple, it loads a small second bootloader from the sector located directly after the master boot record and jumps to that code. The bootloader code in the master boot record seems to run fine, the problem occurs when it tries to jump to the second stage bootloader. This second stage bootloader is supposed to output a letter indicating success (the letter S) so I can tell the code is being executed. The problem is nothing ever appears on the screen, so I suspect the second stage bootloader was never executed. The code I used was the following:

Bootloader in the master boot record:

[BITS 16] ; 16 bit mode
[ORG 0x7C00] ; Boot loader start address

Boot:
    ; Initial, dl contains drive number
    ; Set data segment to code segment
    mov ax, cs
    mov ds, ax
    mov es, ax
    ; Set the stack segment to 0xA000
    add ax, 0xA000
    mov ss, ax
    mov sp, 0x00
    ; Reset the drive, dl contains drive number
    mov ah, 0x00
    int 0x13
    ; Read from drive, dl contains drive number
    ;     Set up output location to 0x7E00: 0x00
    mov ax, 0x7E00
    mov es, ax ; Load to 0x7E00 : 0x00
    mov bx, 0x00
ReadDrive:
    mov ah, 0x02
    mov al, 0x01 ; Read 1 sector
    mov ch, 0x00 ; Read on cylinder 0
    mov cl, 0x02 ; Read sector 2
    mov dh, 0x00 ; Head number 0
    int 0x13

    jnc Success
    ; Print error (character F)
    mov al, 0x46
    call PrintChar
    jmp ReadDrive ; Retry

PrintChar: ; Prints a single character
    pusha
    mov ah, 0x09
    mov bh, 0x00
    mov bl, 0x0F
    mov cx, 0x01
    int 0x10
    popa
    ret

Success:
    jmp 0x7E00:0x00 ; Jump to 2nd stage bootloader

TIMES 510 - ($ - $$) db 0
DW 0xAA55 ; Boot signature

The code for the second stage bootloader:

[BITS 16]
[ORG 0x7E00]

Boot2:
    ; Prints the character S to the screen
    mov al, 0x53
    mov ah, 0x09
    mov bh, 0x00
    mov bl, 0x0F
    mov cx, 0x01
    int 0x10
    jmp $ ; Loop forever

TIMES 512 - ($ - $$) db 0 ; Fill rest of block

This code was compiled and written to a drive using the following code:

nasm boot.asm -o boot.bin -f bin
nasm boot2.asm -o boot2.bin -f bin
dd if=boot.bin of=/dev/sd3 bs=512
dd if=boot2.bin of=/dev/sd3 bs=512 seek=1

The device this code was written to was a 16GB USB drive. The computer I used to boot this code supports booting from USB and boots them like any other hard drive. What is the reason the code does not seem to execute?

Ferrous answered 16/12, 2015 at 17:9 Comment(8)
What happens if you use [ORG 0x0000] at the top of second stage and the first lines of code initialize DS with mov ax, cs mov ds, ax?Earley
How do you know the bootsector runs successfully?Airlie
Secondly and most important you jump to jmp 0x7E00:0x00 . This is physical address (0x7e00<<4)+0 or 0x7E000. I doubt that is what you intended. Maybe you meant jmp 0x07E0:0x00 which would be (0x07e0<<4)+0=0x7e00Earley
Are you sure the first stage is running okay? Maybe it should output a character to be sure. In this environment, are you sure that dl has the drive number?Redeemable
The importance of my first comment will become obvious when you start defining data variables and try to work with them in the second stage. Without a properly set up DS all the references to the variables will be at the wrong offset. Your code doesn't have variables at the moment so you don't notice the issue. If you far jmp to a second stage, the ORG directive should be equal to the offset that is jumped to. If you make suggested jmp change above (jmp 0x07E0:0x00) then the JMP will change CS:IP to CS=0x07E0, IP=0x0000 . You want ORG to equal IP. So ORG should be 0x0000.Earley
The first stage seemed to be running OK, I checked this by outputting characters at different stages in the code. I'll retry running the code using jmp 0x07E0:0x00 and the equivalent value for es. I'll also change the bug that might appear with the current stack setup (I completely didn't think about that one).Ferrous
Hence the change of the value for es. I'll change it. The code probably won't run the second stage without the change anyways. I'll compile the new code now to see if it works.Ferrous
That seemed to solve the problem indeed. It now successfully displays the letter S like it was supposed to. Thank you for all your help, too bad I can't mark a comment as the accepted answer though.Ferrous
E
6

There seem to be a number of issues in your code. I'll try to identify some of them. Some useful reference material can be found in some answers I have written for Stackoveflow.

  • General Boot Loader Tips which give general guidelines and assumptions you don't want to make in a bootloader
  • Information on the pitfalls of not setting up DS properly and getting garbage when accessing memory variables. This applies somewhat to your second stage
  • An answer for a question with similarities to yours could also provide some useful information.

Stack

You do set up a stack but it potentially overlaps video memory. Although this likely isn't related to your problems, it is a potential issue. With this code:

add ax, 0xA000
mov ss, ax
mov sp, 0x00

You set SS =0xa000, and SP =0x0000 . This sets up the stack, but unfortunately the first value pushed on the stack will be at 0xa000:(0x0000-2)= 0xa000:0xfffe . 0xa000:0xfffe happens to possibly fall within video memory. Maybe you intended to do ss=0x9000 so first value on stack would be at 0x9000:0xfffe . There is a snag there too. The Extended Bios Data Area (EBDA) can be in that region. Some BIOSes incorrectly return the wrong size for this area. In most cases it is 0k to 4k in size just below physical address 0xa0000. If you account for most worst case scenarios I'd go with a stack just below that.

add ax, 0x9000
mov ss, ax
mov sp, 0xF000  ; Bottom of stack at 0x9000:0xF000

Memory Address 0x7e00

There are 2 problems here. In your question you suggest you are trying to read the second stage into the area just above the bootloader. That would be at physical address 0x7e00. Your code does this:

; Read from drive, dl contains drive number
;     Set up output location to 0x7E00: 0x00
mov ax, 0x7E00
mov es, ax ; Load to 0x7E00 : 0x00
mov bx, 0x00

16-bit Segment:Offset pairs use this calculation to map to a physical memory address: (segment<<4)+offset (<<4 is the same as multiplying by 16). That means that 0x7E00:0x00 is physical memory address (0x7E00<<4)+0=0x7e000 . That clearly is wrong. I believe what you intended was this:

mov ax, 0x07E0
mov es, ax ; Load to 0x07E0:0x00
mov bx, 0x00

0x07E0:0x00 is physical memory address (0x07E0<<4)+0=0x7e00 . That is the area just above the bootloader loaded into memory at physical address 0x7c00. A similar issue arises when you the FAR JMP to the second stage with this code:

jmp 0x7E00:0x00 ; Jump to 2nd stage bootloader

Should be:

jmp 0x07E0:0x00 ; Jump to 2nd stage bootloader  

Potential Issues for Second Stage

If you make the suggested change (jmp 0x07E0:0x00) mentioned previously then the FAR JMP will change CS:IP to CS =0x07E0(segment), IP= 0x0000(offset) and continue execution there. You need your ORG directive to match the offset (IP) that you jump to from your first stage. Since the offset (IP) is 0x0000 your ORG directive should match:

[ORG 0x0000]

You need to also make sure that when your second stage starts loading that DS is also set up to match. One way of achieving this is to explicitly copy the Code Segment CS to the Data Segment DS . That can be done with code at the top of the second stage like this:

mov ax, cs 
mov ds, ax

Without a properly set up data segment DS all the references to the variables will use the wrong segment and likely won't point to where they really are in memory. Your code doesn't have variables at the moment so you don't notice the issue.


Don't Assume 1st Stage is Invoked by BIOS with CS:IP=0x0000:0x7c00

In my General Bootloader Tips mentioned in this answer's prolog, tip #1 is very important:

  • 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.

In your code your bootloader has this:

[BITS 16] ; 16 bit mode
[ORG 0x7C00] ; Boot loader start address

Boot:
    ; Initial, dl contains drive number
    ; Set data segment to code segment
    mov ax, cs
    mov ds, ax
    mov es, ax

[ORG 0x7C00] is fine, but there is an assumption being made that the CS segment is set to 0x0000 when it reached our bootloader. We then set DS=CS. Conventional wisdom for a naive bootloader is that the BIOS jumps to 0x0000:0x7c00 (CS:IP). ORG should match the offset (in this case IP). The problem is that in reality the BIOS jumps to physical address 0x00007c00 but it can do it with a variety of CS:IP pairs.

The BIOS could have FAR JMP'ed (or equivalent) to our code with jmp 0x07c0:0x0000, and some emulators and real hardware do it this way. 0x07c0:0x0000 is a physical address of (0x07c0<<4)+0=0x7c00 . This is perfectly fine, but notice that IP = 0x0000. We've set [ORG 0x7c00]. That would be a mismatch! How do we get around this if we don't actually know what CS:IP pair the BIOS calls us with? Simple - don't copy CS to DS in the first stage of a bootloader. Since we need an offset of 0x7c00, DS needs to be 0x0000 to work. We should explicitly place 0x0000 in our Data Segment (DS). The code could look like this:

[BITS 16] ; 16 bit mode
[ORG 0x7C00] ; Boot loader start address

Boot:
    ; Initial, dl contains drive number
    ; Set data segment to code segment
    xor ax, ax   ; AX=0
    mov ds, ax   ; DS=0  
    mov es, ax   ; ES=0
Earley answered 16/12, 2015 at 18:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.