Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading second stage of a bootloader

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?

like image 540
Shadowwolf Avatar asked Dec 16 '15 17:12

Shadowwolf


People also ask

What is 2 stage boot loading?

Second-stage boot loaders, such as GNU GRUB, rEFInd, BOOTMGR, Syslinux, NTLDR or iBoot, are not themselves operating systems, but are able to load an operating system properly and transfer execution to it; the operating system subsequently initializes itself and may load extra device drivers.

What file is loaded into memory by the second stage boot loader?

The second stage boot loader reads a configuration file (/boot/grub/grub. conf when using the grub boot loader) to determine what kernel should be started. The kernel is located in /boot and loaded into memory.

Is Uboot a second stage bootloader?

U-Boot is both a first-stage and second-stage bootloader. It is loaded by the system's ROM (e.g. onchip ROM of the ARM CPU) from a supported boot device, such as an SD card, SATA drive, NOR flash (e.g. using SPI or I²C), or NAND flash.

What is primary and secondary boot loader?

The primary bootloader is used for application reprogramming and the secondary bootloader is used for bootloader reprogramming. Both the codes are stored in the program memory of the microcontroller.

What is a two stage bootloader?

Two-Stage Bootloader. A two-stage bootloader actually consists of two bootloaders after each other. The first being small with the sole purpose of loading the second one. The second one can then contain all the code needed for loading the kernel.

What is the 2nd stage of the booting process?

The next decision is to choose a mode. Once the individual components in the motherboard start working together and give us a “computer system”, the second stage of the bootloader starts. In this 2nd stage of the booting process, a decision needs to be made by the user of the system.

How do I set up a bootloader?

In the first-stage bootloader, we must do the following: Setup the memory segments and stack used by the bootloader code Display a string saying “Loading OS…” Find the second-stage boot loader in the FAT directory Read the second-stage boot loader image into memory at 1000:0000

What are the components of bootloader?

The bootloader is usually composed of three programs: 1 A boot sector program directly loaded by the BIOS at boot time 2 A second stage program loaded by the boot sector program to complete the booting process 3 A bootloader installer to install the bootloader and the second stage program in the boot disk.


1 Answers

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
like image 79
Michael Petch Avatar answered Oct 19 '22 23:10

Michael Petch