assembly - 如何通过JMP在MBR中重定位代码?

标签 assembly x86 nasm bootloader osdev

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

我首先从jmp 0:continue开始,但似乎跳到0000:7c22(或者仅用nasm跳到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]

最佳答案

如您所知,您可以将整个引导程序的原点设置为ORG 0x7A00。那很好。将引导扇区复制到0x7A00的代码不依赖任何绝对的标签,而只依赖于亲戚标签。这个答案更多是一个思想实验,也是一种不同的解决方法。

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


NASM允许BIN格式(-f bin)具有包含virtual starting point(原点)和物理地址(开始)的段。对于引导加载程序的布局方式,此方法过于严格。
使用LD linker script定义引导加载程序的布局。
重新组织代码以使用0x0000的ORG(原点),并相应地设置段寄存器。请参阅我对这个问题的other answer


该答案集中在选项2上。对于Stackoverflow,解释LD Linker脚本的工作方式太广泛了。 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。重定位的代码显示成功字符串。

我修改了代码,以不使用像ESI这样的32位寄存器,因为您将在实模式下执行此代码。我还修改了无限循环以使用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


与第一种方法的区别在于,我们不对LD使用--oformat=binary选项。结果将是将生成ELF32图像并将其放置在输出文件boot.elf中。我们不能直接使用boot.elf作为启动映像,因此我们使用OBJCOPY将ELF32文件转换为名为boot.bin的二进制文件。如果我们使用这样的命令来转储内容并反汇编ELF文件,则可以看到以这种方式进行操作的有用性:

objdump boot.elf -Mintel -mi8086 -Dx



-D选项可全部反汇编
-x输出标题
-mi8086反汇编为16位8086代码
-Mintel反汇编应为INTEL语法,而不是默认的ATT语法

关于assembly - 如何通过JMP在MBR中重定位代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37308002/

相关文章:

linux - C 中意外的段错误

memory-management - x86 的物理内存地址到 DRAM 映射信息

c - 将内联汇编片段合并在一起

linux - 如何将 C 运行时库与 'ld' 链接起来?

assembly - 汇编代码中的段错误

assembly - 计算汇编 x86 中的 LCM

将 C 转换为 MIPS - 嵌套数组

assembly - 有没有办法检索 BIOS 在其中找到并加载引导扇区的设备 ID?

c - NASM 数组指针操作

ubuntu - 64 位汇编语言在 ubuntu 上使用 nasm