我如何在我的MBR中重新定位代码?

时间:2016-05-18 18:57:06

标签: assembly x86 nasm osdev mbr

我正在尝试编写一个非常简单的MBR来开始学习如何编写MBR /内核。这是我到目前为止(从其他MBR的部分创建)。我从使用nasm然后使用ld到链接的二进制文件与仅使用nasm两者有点不同,但这似乎不是问题。

我首先使用jmp 0:continue开始,但似乎只使用nasm跳转到0000:7c22(或001d ...我相信我没有指定它从7c00开始)但我希望跳转到:7a22:7a1d,重定位代码的地址。我尝试使用jmp continue,然后在下面看到未注释,将堆栈指针添加到继续指针,推送它并返回。当我读到第一个扇区时,我得到的只是一个闪烁的光标。任何帮助表示赞赏。

                            ; nasm+ld       nasm            comment
global _start
_start:
    xor    cx, cx           ; 6631c9        31c9            Set segment registers to zero
    mov    es, cx           ; 8ec1          8ec1
    mov    ds, cx           ; 8ed9          8ed9
    mov    ss, cx           ; 8ed1          8ed1
    mov    sp, 0x7A00       ; 66bc007a      bc007a          Stack
    mov    di, sp           ; 6689e7        89e7            Bottom of relocation point
    mov    esi, _start      ; be007c0000    66be00000000
    cld                     ; fc            fc
    mov    ch, 1            ; b501          b501            cx = 256
    rep movsw               ; f366a5        f3a5            Copy self to 0:7A00

;----------------------------------------------------------------------------------------------------------------------
    xor    eax,eax
    mov    ax, sp
    add    ax, continue

    ;jmp    0:continue      ; ea227c00000000    ea1d000000      near JMP to copy of self
                            ; or
    ;jmp    continue        ; (eb00)
    push eax
    ret
;----------------------------------------------------------------------------------------------------------------------

continue:
    sti                     ; fb            fb

ERROR:
    mov esi, errormsg       ; be3b7c0000 (be36) 66be36000000        Error Message loc
    mov ah, 0x0E            ; b40e          b40e
    mov bx, 7               ; 66bb          bb0700
disp:
    lodsb                   ; ac            ac
    cmp ah, 0x00            ; 80fc00        80fc00
    je end                  ; 7404          7404
    int 10h                 ; cd10          cd10
    jmp disp                ; ebf6          ebf6

end:
    nop                     ; 90            90
    jmp end                 ; ebfd          ebfd            infinte loop

errormsg db 10,'YOU MESSED UP.',13,0

times (0x1b8 - ($-$$)) nop  ; 90            90          Padding

UID db 0xf5,0xbf,0x0f,0x18                                         ;Unique Disk ID

BLANK times 2 db 0

PT1 db 0x80,0x20,0x21,0x00,0x0C,0x50,0x7F,0x01,0x00,0x08,0x00,0x00,0xb0,0x43,0xF9,0x0D ;First Partition Entry
PT2 times 16 db 0                                      ;Second Partition Entry
PT3 times 16 db 0                                      ;Third Partition Entry
PT4 times 16 db 0                                      ;Fourth Partition Entry

BOOTSIG dw 0xAA55                                      ;Boot Signature[/code]

谢谢!

3 个答案:

答案 0 :(得分:8)

正如您所发现的,您可以为整个引导加载程序将原点设置为ORG 0x7A00。这非常有效。将引导扇区复制到0x7A00的代码并不依赖于任何绝对标签,只是相对标签。这个答案更像是一个思想实验和一种不同的接近方式。

如果我们想在复制之前显示字符串作为示例,会发生什么?有哪些可能的选择?

  1. NASM 允许BIN格式(-f bin)包含带有virtual starting point(原点)和物理地址(开始)的部分。这种方法对引导程序的布局方式限制太多。
  2. 使用LD linker script定义引导加载程序的布局。
  3. 重新组织代码以使用0x0000的 ORG (原点)并相应地设置段寄存器。请参阅此问题other answer
  4. 这个答案主要关注选项2.解释 LD 链接器脚本如何工作对于Stackoverflow而言过于宽泛。 LD manual是最好的信息来源,它确实有例子。我们的想法是允许引导加载程序在链接描述文件中布局。我们可以设置LMA(加载内存地址)来指定将该部分加载到内存中的内存地址。 VMA是一个部分的原点。部分中的所有标签和地址将相对于其VMA进行解析。

    方便的是,我们可以使用具有特定LMA的部分将引导签名直接放入输出文件中,而不是在汇编代码中指定它。我们还可以使用 NASM extern指令从链接描述文件中定义符号,这些符号可以从汇编代码中访问。

    所有这一切的一个优点是,您可以按照您想要的任何顺序定义汇编代码中的节,并且链接器脚本将重新排序。您还可以将多个目标文件链接在一起。首先应列出包含要首先显示的引导代码的目标文件。

    此链接描述文件的布局大致如下所示:

    Non-relocatable portion of boot code (boot.text) Relative to an origin of 0x7c00
    Non-relocatable portion of boot data (boot.data)
    --------------------------------------- Word aligned
    Relocatable portion of boot code (rel.text) - Relative to an origin of 0x7a00
    Relocatable portion of boot data (rel.data)
    Relocatable portion of partition data at offset 0x1b8 (partition.data)
    ---------------------------------------
    Boot signature at offset 0x1fe
    

    布局此引导加载程序的链接描述文件可能类似于:

    ENTRY(_start);
    OUTPUT(elf_i386);
    
    SECTIONS
    {
        /* Set the base of the main bootloader offsets */
        _bootbase = 0x7c00; /* Where bootloader initially is loaded in memory */
        _relbase  = 0x7a00; /* Address entire bootsector will be copied to
                               This linker script expects it to be word aligned */
        _partoffset = 0x1b8; /* Offset of UID and Partition data */
        _sigoffset  = 0x1fe; /* Offset of the boot signature word */
    
    
        /* SUBALIGN(n) in an output section will override the alignment
         * of any input section that is encontered */
    
        /* This is the boot loader code and data that is expected to run from 0x7c00 */
        .bootinit _bootbase : SUBALIGN(2)
        {
            *(boot.text);
            *(boot.data);
        }
    
        /* Note that referencing any data in the partition table will
         * only be usable from the code that is in the .bootrel section */
    
        /* Partition data */
        .partdata _relbase + _partoffset :
            AT(_bootbase + _partoffset) SUBALIGN(0)
        {
            *(partition.data);
        }
    
        /* Boot signature */
        .bootsig :
            AT(_bootbase + _sigoffset) SUBALIGN(0)
        {
            SHORT(0xaa55);
        }
        /* Length of region to copy in 16-bit words */
        _rel_length = 256;
        /* Address to copy to */
        _rel_start = _relbase; /* Word aligned start address */
    
        /* Code and data that will expect to run once relocated
         * is placed in this section. Aligned to word boundary.
         * This relocateable code and data will be placed right
         * after the .bootinit section in the output file */
        .bootrel _relbase + SIZEOF(.bootinit) :
            AT(_bootbase + SIZEOF(.bootinit)) SUBALIGN(2)
        {
            *(rel.text);
            *(rel.data);
        }
    }
    

    使用此链接描述文件及其中定义的符号修改后的代码副本可能如下所示:

    BITS 16
    
    extern _bootbase
    extern _relbase
    extern _rel_length
    extern _rel_start
    
    section boot.text
                                ; comment
    global _start
    _start:
        xor    cx, cx           ; Set segment registers to zero
        mov    es, cx
        mov    ds, cx
        mov    ss, cx
        mov    sp, 0x7A00       ; Stack
        cld
    
    .copymsg:
        mov si, copymsg         ; Copy message
        mov ah, 0x0E            ; 0E TTY Output
        mov bx, 7               ; Page number
    .dispcopy:
        lodsb                   ; Load next char
        test al, al             ; Compare to zero
        jz .end                 ; If so, end
        int 10h                 ; Display char
        jmp .dispcopy           ; Loop
    .end:
        mov    di, _rel_start   ; Beginning of relocation point
        mov    si, _bootbase    ; Original location to copy from
        mov    cx, _rel_length  ; CX = words to copy
        rep movsw               ; Copy self to destination
    
        jmp    0:rel_entry      ; far JMP to copy of self
    
    section rel.text
    rel_entry:
        sti                     ; Enable interrupts
    
        mov si, successmsg      ; Error Message location
        mov ah, 0x0E            ; 0E TTY Output
        mov bx, 7               ; Page number
    .disp:
        lodsb                   ; Load next char
        test al, al             ; Compare to zero
        je .end                 ; If so, end
        int 10h                 ; Display char
        jmp .disp               ; Loop
    
        cli                     ; Disable interrupts
    .end:
        hlt                     ; CPU hlt
        jmp .end                ; infinte loop
    
    section rel.data
    successmsg db 10,'Success!',13,0
    
    section boot.data
    copymsg db 10,'Before copy!',13,0
    
    section partition.data
    UID db 0xf5,0xbf,0x0f,0x18  ;Unique Disk ID
    
    BLANK times 2 db 0
    
    PT1 db 0x80,0x20,0x21,0x00,0x0C,0x50,0x7F,0x01
        db 0x00,0x08,0x00,0x00,0xb0,0x43,0xF9,0x0D
    PT2 times 16 db 0
    PT3 times 16 db 0
    PT4 times 16 db 0
    

    作为确保boot.text部分中的代码可以访问boot.data中的数据的实验,我在复制之前显示字符串。然后,我对重定位的代码执行 FAR JMP 。重定位的代码显示成功字符串。

    我将代码修改为不使用32位寄存器,如 ESI ,因为您将在实模式下执行此代码。我还修改了你的无限循环以使用HLT指令。

    可以修改代码和链接描述文件,使其仅从重定位数据的开头复制到第512个字节,但超出了本答案的范围。

    查看反汇编

    下面提供了原点为0x7c00的.bootinit部分。这是该部分的 OBJDUMP 片段(为简洁起见没有数据):

    Disassembly of section .bootinit:
    
    00007c00 <_start>:
        7c00:       31 c9                   xor    cx,cx
        7c02:       8e c1                   mov    es,cx
        7c04:       8e d9                   mov    ds,cx
        7c06:       8e d1                   mov    ss,cx
        7c08:       bc 00 7a                mov    sp,0x7a00
        7c0b:       fc                      cld
    
    00007c0c <_start.copymsg>:
        7c0c:       be 2e 7c                mov    si,0x7c2e
        7c0f:       b4 0e                   mov    ah,0xe
        7c11:       bb 07 00                mov    bx,0x7
    
    00007c14 <_start.dispcopy>:
        7c14:       ac                      lods   al,BYTE PTR ds:[si]
        7c15:       84 c0                   test   al,al
        7c17:       74 04                   je     7c1d <_start.end>
        7c19:       cd 10                   int    0x10
        7c1b:       eb f7                   jmp    7c14 <_start.dispcopy>
    
    00007c1d <_start.end>:
        7c1d:       bf 00 7a                mov    di,0x7a00
        7c20:       be 00 7c                mov    si,0x7c00
        7c23:       b9 00 01                mov    cx,0x100
        7c26:       f3 a5                   rep movs WORD PTR es:[di],WORD PTR ds:[si]
        7c28:       ea 3e 7a 00 00          jmp    0x0:0x7a3e
    

    左列上的所有VMA地址似乎都相对于原点0x7c00正确设置。 FAR JUMP jmp 0x0:0x7a3e)也跳转到重新定位(复制)所有内容的位置。 .bootrel部分的类似缩写转储显示为:

    Disassembly of section .bootrel:
    
    00007a3d <rel_entry-0x1>:
            ...
    
    00007a3e <rel_entry>:
        7a3e:       fb                      sti
        7a3f:       be 54 7a                mov    si,0x7a54
        7a42:       b4 0e                   mov    ah,0xe
        7a44:       bb 07 00                mov    bx,0x7
    
    00007a47 <rel_entry.disp>:
        7a47:       ac                      lods   al,BYTE PTR ds:[si]
        7a48:       3c 00                   cmp    al,0x0
        7a4a:       74 05                   je     7a51 <rel_entry.end>
        7a4c:       cd 10                   int    0x10
        7a4e:       eb f7                   jmp    7a47 <rel_entry.disp>
        7a50:       fa                      cli
    
    00007a51 <rel_entry.end>:
        7a51:       f4                      hlt
        7a52:       eb fd                   jmp    7a51 <rel_entry.end>
    

    左列中的VMA相对于0x7A00的开头是正确的。指令mov si,0x7a54是一个绝对的近存储器地址,并且它被正确编码以引用successmsg地址(为了简洁,我将数据剪切掉,因此它不会出现)。

    参赛作品:

    00007a3d <rel_entry-0x1>:
            ...
    

    是否将.bootrel部分与偶数字边界对齐相关的信息。使用此链接器脚本rel_entry将始终具有偶数地址。

    编译和链接此Bootloader

    最简单的方法是使用以下命令:

    nasm -f elf32 -o boot.o boot.asm
    ld -melf_i386 -Tlinker.ld -o boot.bin --oformat=binary boot.o
    

    应该指出的是,我们将 ELF32 格式与 NASM 一起使用,而不是 BIN 。然后使用 LD 创建二进制文件boot.bin,该文件应该是引导扇区的512字节图像。 linker.ld是链接描述文件的名称。

    如果您希望能够获得对象转储的便利性,那么您可以使用这些命令进行汇编和链接:

    nasm -f elf32 -o boot.o boot.asm
    ld -melf_i386 -Tlinker.ld -o boot.elf boot.o
    objcopy -O binary boot.elf boot.bin
    

    与第一种方法的不同之处在于我们不会将--oformat=binary选项与 LD 一起使用。结果将生成 ELF32 图像并将其放置在输出文件boot.elf中。我们无法直接使用boot.elf作为启动映像,因此我们使用 OBJCOPY ELF32 文件转换为名为{{1}的二进制文件}。如果我们使用这样的命令来转储 ELF 文件的内容和反汇编,就可以看到以这种方式这样做的有用性:

    boot.bin
    • objdump boot.elf -Mintel -mi8086 -Dx 选项是全部反汇编
    • -D输出标题
    • -x反汇编为16位8086代码
    • -mi8086反汇编应为 INTEL 语法而非默认 ATT 语法

答案 1 :(得分:3)

使用以下代码进行编译和关联:nasm -f bin -o mbr.bin mbr.asm

[BITS 16]
ORG 0x00007a00
                            ; opcodes       comment
global _start
_start:
    xor    cx, cx           ; 31c9          Set segment registers to zero
    mov    es, cx           ; 8ec1
    mov    ds, cx           ; 8ed9
    mov    ss, cx           ; 8ed1
    mov    sp, 0x7A00       ; bc007a        Stack
    mov    di, sp           ; 89e7          Bottom of relocation point
    mov    esi, 0x00007C00  ; 66be007c0000  Original location
    cld                     ; fc
    mov    ch, 1            ; b501          CX = 256
    rep movsw               ; f3a5          Copy self to 0:7A00
    jmp    0:continue       ; ea1d7a0000    near JMP to copy of self

continue:
    sti                     ; fb

ERROR:
    mov esi, errormsg       ; 66be357a0000  Error Message location
    mov ah, 0x0E            ; b40e          0E TTY Output
    mov bx, 7               ; bb0700        Page number
disp:
    lodsb                   ; ac            Load next char
    cmp al, 0x00            ; 3c00          Compare to zero
    je end                  ; 7404          If so, end
    int 10h                 ; cd10          Display char
    jmp disp                ; ebf6          Loop

end:
    nop                     ; 90            Do Nothing
    jmp end                 ; ebfd          infinte loop

errormsg db 10,'YOU MESSED UP!',13,0

times (0x1b8 - ($-$$)) nop  ; 90909090...   Padding

UID db 0xf5,0xbf,0x0f,0x18  ;Unique Disk ID

BLANK times 2 db 0

PT1 db 0x80,0x20,0x21,0x00,0x0C,0x50,0x7F,0x01
PT1more db 0x00,0x08,0x00,0x00,0xb0,0x43,0xF9,0x0D
PT2 times 16 db 0
PT3 times 16 db 0
PT4 times 16 db 0

BOOTSIG dw 0xAA55           ;Boot Signature

hexdump -C mbr.bin的输出:

00000000  31 c9 8e c1 8e d9 8e d1  bc 00 7a 89 e7 66 be 00  |1.........z..f..|
00000010  7c 00 00 fc b5 01 f3 a5  ea 1d 7a 00 00 fb 66 be  ||.........z...f.|
00000020  35 7a 00 00 b4 0e bb 07  00 ac 3c 00 74 04 cd 10  |5z........<.t...|
00000030  eb f7 90 eb fd 0a 59 4f  55 20 4d 45 53 53 45 44  |......YOU MESSED|
00000040  20 55 50 21 0d 00 90 90  90 90 90 90 90 90 90 90  | UP!............|
00000050  90 90 90 90 90 90 90 90  90 90 90 90 90 90 90 90  |................|
*
000001b0  90 90 90 90 90 90 90 90  f5 bf 0f 18 00 00 80 20  |............... |
000001c0  21 00 0c 50 7f 01 00 08  00 00 b0 43 f9 0d 00 00  |!..P.......C....|
000001d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
00000200

答案 2 :(得分:2)

重新定位MBR / bootloader的另一种方法,它不涉及链接器脚本,但允许 ALL 将引导加载程序中的代码和数据重新定位到segment:offset pair = segment的任何位置:0×0000。这并不限制引导加载程序在64kb内存中复制。这涉及使用一些段数学和对代码的一些小改动。我将使用您的原始代码进行一些清理以及我在第一个答案中添加的说明,该说明在 JMP 之前打印一个字符串。

请记住,在x86 real mode segmentation中,物理地址由段寄存器的内容和偏移量决定。计算如下:

Physical address = (segment << 4) + offset

对于引导加载程序,许多人将0x0000放入 DS ES ,这是您在代码中所做的。您的原始代码使用ORG 0x7c00(实际上是偏移量)。当您获取分段和偏移并计算您获得的物理地址时:

Physical address = (0x0000 << 4) + 0x7c00 = 0x07c00

0x07c00是BIOS中放入内存的引导加载程序的物理地址。但是,通常有多种方法可以将单个物理地址表示为一个段:偏移对。

作为一个例子,如果我们使用一段0x07c0和一个ORG(偏移)为0x0000,我们发现该等式产生:

Physical address = (0x07c0 << 4) + 0x0000 = 0x07c00

如果将原始段寄存器设置为0x07c0并在引导加载程序中使用ORG 0x0000指令,该怎么办?那会有用吗?是的,它会的。链接器和 NASM 实际上没有关于用户如何设置段寄存器的概念。当 NASM 创建对 NEAR 绝对地址的引用时,它假定开发人员在运行时将正确设置这些段。

如果我们使用ORG 0x0000,然后从0x7c00复制到0x7a00,我们可以将0x07c0放入 DS ,将 SI 放到0(0x07c0:0x0000) )。我们可以将 ES 设置为0x07a0,将 DI 设置为0(0x07a0:0x0000)。 CX 将设置为256(我们将复制单词)。这会将512字节的内存从物理地址0x07c00复制到0x07a00。复制整个引导加载程序后,我们使用 FAR JMP CS 设置为跳转中指定的段,并将指令指针(IP)设置为标签的偏移量。 / p>

一旦我们跳转到复制引导加载程序的内存(从物理地址0x7a00开始的区域),我们就相应地设置了段寄存器。因此,在我们的情况下,我们在跳转到0x07a0之后设置寄存器 DS ES 。这应该允许所有引导加载程序代码和数据在新位置按预期运行。

以下是与上述建议类似的代码:

BITS 16
ORG 0x0000

global _start
_start:
    xor    bp, bp           
    mov    ss, bp           ; Set SS segment register to zero
    mov    sp, 0x7A00       ; Set stack to SS:SP=0x0000:0x7A00
    mov    dx, 0x07C0
    mov    ds, dx           ; DS = 0x07C0 - Segment:Offset = 0x07c0:0x0000 = 0x07C00
    sub    dx, 0x20         ; DX = 0x07C0-0x20=0x07A0
    mov    es, dx           ; ES = 0x07A0 - Segment:Offset = 0x07a0:0x0000 = 0x07A00
    cld

    ; Print message before the copy
    mov si, copymsg         ; Copy message (DS:[copymsg])
    call outputstr

    ; Copy 256 words of memory from 0x07c00 to 0x07a00
    mov    di, bp           ; Destination is ES:DI = 0x07a0:0x0000 = 0x07a00
    mov    si, bp           ; Source is DS:SI = 0x07c0:0x0000 = 0x07c00
    mov    cx, 256          ; CX = 256 words to copy
    rep movsw               ; Copy self to destination

    jmp    0x07A0:rel_entry
                            ; far JMP jumps to phys address 0x07a00, sets
                            ; CS = 0x07A0 and IP = rel_entry.

rel_entry:
    sti                     ; Enable interrupts
    mov    ds, dx           ; DS=ES=0x07A0
                            ; ES already 0x07A0 before the jump, no need to set again
.success:
    ; Print the "Success!" message
    mov si, successmsg      ; Success Message location (DS:[successmsg])
    call outputstr

    cli                     ; Disable interrupts
.end:
    hlt                     ; CPU hlt
    jmp .end                ; infinite loop

; Function: outputstr
; Inputs:
;    SI = address of string
; Outputs:
;    None
; Registers destroyed:
;    AX, BX, SI
;
outputstr:
    mov ah, 0x0E            ; 0E TTY Output
    mov bx, 7               ; Page number
.disp:
    lodsb                   ; Load next char from DS:[SI], SI+=2
    test al, al             ; Compare to zero
    je .end                 ; If so, end
    int 0x10                ; Display char
    jmp .disp               ; Loop
.end:
    ret

successmsg db 10,'Success!',13,0
copymsg db 10,'Before copy!',13,0

times (0x1b8 - ($-$$)) db 0x00 ; Padding

UID     db 0xf5,0xbf,0x0f,0x18 ; Unique Disk ID
BLANK   times 2 db 0
PT1     db 0x80,0x20,0x21,0x00,0x0C,0x50,0x7F,0x01
PT1more db 0x00,0x08,0x00,0x00,0xb0,0x43,0xF9,0x0D
PT2     times 16 db 0
PT3     times 16 db 0
PT4     times 16 db 0

BOOTSIG dw 0xAA55              ;Boot Signature

此引导加载程序调用函数outputstr以在复制之前的代码中以及跳转到重定位代码之后向控制台显示消息。这是有效的,因为在两个实例中,所有标签仍然处于相同的偏移量,只有段更改。

上面的引导程序可以使用如下命令组装:

nasm -f bin boot.bin boot.asm