Loading second stage of a bootloader

前端 未结 1 668
渐次进展
渐次进展 2021-01-05 20:52

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 lo

相关标签:
1条回答
  • 2021-01-05 21:17

    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
    
    0 讨论(0)
提交回复
热议问题