使用'ORG 0x0000'时,在8086实模式下从内存中读取

Mat*_*hew 1 assembly nasm bootloader fasm x86-16

我一直在搞乱x86-16程序集并使用VirtualBox运行它.出于某种原因,当我从内存中读取并尝试将其作为角色打印时,我得到的结果与我期望的完全不同.但是,当我将字符硬编码为指令的一部分时,它工作正常.这是代码:

ORG 0
BITS 16

push word 0xB800        ; Address of text screen video memory in real mode for colored monitors
push cs
pop ds                  ; ds = cs
pop es                  ; es = 0xB800
jmp start

; input = di (position*2), ax (character and attributes)
putchar:
    stosw
    ret

; input = si (NUL-terminated string)
print:
    cli
    cld
    .nextChar:
        lodsb   ; mov al, [ds:si] ; si += 1
        test al, al
        jz .finish
        call putchar
        jmp .nextChar
    .finish:
        sti
        ret

start:
    mov ah, 0x0E
    mov di, 8

    ; should print P
    mov al, byte [msg]
    call putchar

    ; should print A
    mov al, byte [msg + 1]
    call putchar

    ; should print O
    mov al, byte [msg + 2]
    call putchar

    ; should print !
    mov al, byte [msg + 3]
    call putchar

    ; should print X
    mov al, 'X'
    call putchar

    ; should print Y
    mov al, 'Y'
    call putchar

    cli
    hlt

msg: db 'PAO!', 0

; Fill the rest of the bytes upto byte 510 with 0s
times 510 - ($ - $$) db 0

; Header
db 0x55
db 0xAA
Run Code Online (Sandbox Code Playgroud)

打印标签及其中的说明可以忽略,因为我还没有使用它,因为我一直试图打印存储在内存中的字符.我已经用FASM和NASM组装了它,并且遇到了同样的问题,这显然是我的错.

它打印的内容如下: VirtualBox的

Mic*_*tch 9

ORG指令

如果在汇编程序的顶部指定ORG指令ORG 0x0000,并且使用BITS 16,则通知NASM在解析代码和数据的标签时,将生成的绝对偏移量将基于ORG中指定的起始偏移量(16位代码将限制为WORD/2字节的偏移量.

如果您ORG 0x0000在开始处并start:在代码的开头放置一个标签,start则其绝对偏移量为0x0000.如果使用,ORG 0x7C00则标签start的绝对偏移量为0x7c00.这适用于任何数据标签和代码标签.

我们可以简化您的示例,以便在处理数据变量和硬编码字符时查看生成的代码中发生了什么.虽然此代码并不完全执行与代码相同的操作,但它足够接近以显示哪些有效,哪些无效.

使用ORG 0x0000的示例:

BITS 16
ORG 0x0000

start:
    push cs
    pop  ds      ; DS=CS
    push 0xb800
    pop es       ; ES = 0xB800 (video memory)
    mov ah, 0x0E ; AH = Attribute (yellow on black)

    mov al, byte [msg]
    mov [es:0x00], ax   ; This should print letter 'P'
    mov al, byte [msg+1]
    mov [es:0x02], ax   ; This should print letter 'A'
    mov al, 'O'
    mov [es:0x04], ax   ; This should print letter 'O'
    mov al, '!'
    mov [es:0x06], ax   ; This should print letter '!'

    cli
    hlt

msg: db "PA"

; Bootsector padding
times 510-($-$$) db 0
dw 0xAA55
Run Code Online (Sandbox Code Playgroud)

如果你在VirtualBox上运行它,前两个字符将是垃圾,同时O!应该正确显示.我将通过本答案的其余部分使用此示例.


VirtualBox/CS:IP /段:偏移对

在Virtual Box的情况下,在物理地址0x00007c00加载引导扇区后,它将有效地将FAR JMP 等效为0x0000:0x7c00.一个FAR JMP(或同等学历)不仅会跳转到指定的地址,它集CSIP到指定的值.一个FAR JMP为0x0000:0x7c00将设置CS = 0×0000和IP = 0x7c00.

如果不熟悉16位段后面的计算:偏移对以及它们如何映射到物理地址,那么本文档是理解该概念的一个相当好的起点.从16位段获得物理内存地址的一般公式:偏移对是(segment<<4)+offset = 20-bit physical address.

由于VirtualBox使用CS:IP 0x0000:0x7c00,因此它将开始在物理地址(0x0000 << 4)+ 0x7c00 = 20位物理地址0x07c00处执行代码.请注意,并非所有环境都保证不会出现这种情况.由于segment:offset对的性质,有多种方法可以引用物理地址0x07c00.请参阅本答案末尾的部分,了解如何正确处理此问题.


您的Bootloader会出现什么问题?

假设我们使用VirtualBox并且上一节中的信息被认为是正确的,那么在进入我们的引导加载程序时CS = 0x0000和IP = 0x7c00.如果我们采用ORG 0x0000我在本答案的第一部分写的示例代码(Using )并查看反汇编信息(我将使用objdump输出),我们会看到:

objdump -Mintel -mi8086 -D -b binary --adjust-vma=0x0000 boot.bin

00000000 <.data>:
   0:   0e                      push   cs
   1:   1f                      pop    ds
   2:   68 00 b8                push   0xb800
   5:   07                      pop    es
   6:   b4 0e                   mov    ah,0xe
   8:   a0 24 00                mov    al,ds:0x24
   b:   26 a3 00 00             mov    es:0x0,ax
   f:   a0 25 00                mov    al,ds:0x25
  12:   26 a3 02 00             mov    es:0x2,ax
  16:   b0 4f                   mov    al,0x4f
  18:   26 a3 04 00             mov    es:0x4,ax
  1c:   b0 21                   mov    al,0x21
  1e:   26 a3 06 00             mov    es:0x6,ax
  22:   fa                      cli
  23:   f4                      hlt
  24:   50                      push   ax          ; Letter 'P'
  25:   41                      inc    cx          ; Letter 'A'
        ...
 1fe:   55                      push   bp
 1ff:   aa                      stos   BYTE PTR es:[di],al
Run Code Online (Sandbox Code Playgroud)

由于组装到二进制文件时ORG信息丢失,--adjust-vma=0x0000因此我使用第一列值(内存地址)从0x0000开始.我想这样做是因为我ORG 0x0000在原始的汇编代码中使用过.我还在代码中添加了一些注释,以显示我们的数据部分的位置(以及代码后面的字母PA位置).

如果你在VirtualBox中运行这个程序,那么前两个字符就会变成乱码.那为什么呢?首先回想起VirtualBox通过将CS设置为0x0000并将IP设置为0x7c00 来达到我们的代码.此代码然后将CS复制到DS:

   0:   0e                      push   cs
   1:   1f                      pop    ds
Run Code Online (Sandbox Code Playgroud)

由于CS为零,因此DS为零.现在让我们来看看这一行:

   8:   a0 24 00                mov    al,ds:0x24
Run Code Online (Sandbox Code Playgroud)

ds:0x24实际上是数据部分中msg变量的编码地址.偏移量0x24处的字节具有其中的值P(0x25具有A).你可能会看到事情可能出错的地方.我们的DS = 0x0000所以mov al,ds:0x24真的是一样的mov al,0x0000:0x24.这种语法无效,但我用DS0000 替换DS来表示观点.0x0000:0x24是执行时我们的代码将尝试读取我们的信件的P地方.可是等等!那是物理地址(0x0000 << 4)+ 0x24 = 0x00024.该存储器地址恰好位于中断向量表中间的存储器底部.显然这不是我们想要的!

有几种方法可以解决这个问题.最简单(也是首选的方法)是将实际的段放入DS中,而不是依赖于程序运行时CS可能是什么.由于我们将ORG设置为0x0000,因此我们需要一个数据段(DS)= 0x07c0.段:偏移对0x07c0:0x0000 =物理地址0x07c00.这是我们的引导加载程序的地址.所以我们要做的就是通过替换来修改代码:

    push cs
    pop  ds      ; DS=CS
Run Code Online (Sandbox Code Playgroud)

附:

    push 0x07c0
    pop  ds      ; DS=0x07c0 
Run Code Online (Sandbox Code Playgroud)

VirtualBox中运行时,此更改应提供正确的输出.现在让我们看看为什么.此代码未更改:

   8:   a0 24 00                mov    al,ds:0x24
Run Code Online (Sandbox Code Playgroud)

现在执行时DS = 0x07c0.这就像说 mov al,0x07c0:0x24.0x07c0:0x24,它将转换为(0x07c0 << 4)+ 0x24 = 0x07c24的物理地址.这是我们想要的,因为我们的引导加载程序是从该位置开始由BIOS物理地放入内存中的,所以它应该正确引用我们的msg变量.

故事的道德启示?当你开始我们的程序时,你在ORG中使用什么应该在DS寄存器中有一个适用的值.我们应该明确地设置它,而不是依赖于CS中的内容.


为什么立即值打印?

用原始代码,前两个字符打印乱码,但最后两个没有.正如前一节所讨论的那样,有一个原因是前2个字符不能打印,但最后2个字符的作用是什么呢?

让我们O更仔细地检查第三个字符的反汇编:

  16:   b0 4f                   mov    al,0x4f        ; 0x4f = 'O'
Run Code Online (Sandbox Code Playgroud)

由于我们使用了立即(常量)值并将其移动到寄存器AL中,因此字符本身被编码为指令的一部分.它不依赖于通过DS寄存器访问存储器.因此,最后2个字符正确显示.


Ross Ridge的建议及其在VirtualBox中的工作原理

罗斯里奇建议我们使用ORG 0x7c00,你发现它有效.为什么会这样?这个解决方案是理想的吗?

使用我的第一个例子,修改ORG 0x0000ORG 0x7c00,然后再组装起来.objdump会提供这个反汇编:

objdump -Mintel -mi8086 -D -b binary  --adjust-vma=0x7c00 boot.bin

boot.bin:     file format binary   
Disassembly of section .data:

00007c00 <.data>:
    7c00:       0e                      push   cs
    7c01:       1f                      pop    ds
    7c02:       68 00 b8                push   0xb800
    7c05:       07                      pop    es
    7c06:       b4 0e                   mov    ah,0xe
    7c08:       a0 24 7c                mov    al,ds:0x7c24
    7c0b:       26 a3 00 00             mov    es:0x0,ax
    7c0f:       a0 25 7c                mov    al,ds:0x7c25
    7c12:       26 a3 02 00             mov    es:0x2,ax
    7c16:       b0 4f                   mov    al,0x4f
    7c18:       26 a3 04 00             mov    es:0x4,ax
    7c1c:       b0 21                   mov    al,0x21
    7c1e:       26 a3 06 00             mov    es:0x6,ax
    7c22:       fa                      cli
    7c23:       f4                      hlt
    7c24:       50                      push   ax          ; Letter 'P'
    7c25:       41                      inc    cx          ; Letter 'A'
        ...
    7dfe:       55                      push   bp
    7dff:       aa                      stos   BYTE PTR es:[di],al
Run Code Online (Sandbox Code Playgroud)

当VirtualBox 跳转到我们的引导加载程序时,它将CS设置为0x0000.然后我们的原始代码将CS复制到DS,因此DS = 0x0000.现在观察ORG 0x7c00指令对我们生成的代码的作用:

    7c08:       a0 24 7c                mov    al,ds:0x7c24
Run Code Online (Sandbox Code Playgroud)

请注意我们现在如何使用0x7c24的偏移量!这就像mov al,0x0000:0x7c24物理地址(0x0000 << 4)+ 0x7c24 = 0x07c24.这是加载引导加载程序的正确内存位置,是我们的msg字符串的正确位置.所以它有效.

使用ORG 0x7c00一个坏主意?不,没关系.但我们有一个微妙的问题需要应对.如果另一个Virtual PC环境或真实硬件没有使用CS:IP 0x0000:0x7c000 对我们的引导加载程序进行FAR JMP会发生什么?这个有可能.有许多具有BIOS的物理PC实际上相当于远程跳转.这也是我们已经看到的物理地址.在那种环境中,当我们的代码运行CS = 0x07c0时.如果我们使用将CS复制到DS的原始代码,DS现在也有0x07c0.现在观察在这种情况下该代码会发生什么:0x07c0:0x00000x07c00

    7c08:       a0 24 7c                mov    al,ds:0x7c24
Run Code Online (Sandbox Code Playgroud)

在这种情况下DS = 0x07c0.我们现在有一些类似于mov al,0x07c0:0x7c24程序实际运行的东西.哦,哦,看起来很糟糕.这转化为物理地址是什么?(0x07c0 << 4)+ 0x7c24 = 0x0F824.这是我们的引导加载程序之上的某个地方,它将包含计算机启动后发生的任何事情.可能是0,但应该假设它是垃圾.显然不是我们的msg字符串被加载的地方!

那么我们如何解决这个问题呢?要修改什么罗斯岭建议,并听取我以前给了有关设置明确的建议DS到我们真正想要的(不要以为段CS是正确的,然后盲目地复制到DS),我们应该把为0x0000到DS,当我们的bootloader启动如果我们使用ORG 0x7c00.所以我们可以改变这段代码:

ORG 0x7c00

start:
    push cs
    pop  ds      ; DS=CS
Run Code Online (Sandbox Code Playgroud)

至:

ORG 0x7c00

start:
    xor ax, ax   ; ax=0x0000
    mov ds, ax   ; DS=0x0000
Run Code Online (Sandbox Code Playgroud)

在这里,我们不依赖于CS中的不受信任的值.我们只是根据我们使用的ORGDS设置为有意义的段值.您可以推送0x0000并将其弹出到DS中,就像您一直在做的那样.我更习惯于将寄存器归零并将其移至DS.

通过采用这种方法,CS可能用于访问我们的引导加载程序无关紧要,代码仍然会引用我们数据的相应内存位置.


不要假设BIOS使用CS调用第一阶段:IP = 0x0000:0x7c00

在我在之前的StackOverflow答案中写的一般Bootloader提示中,提示#1非常重要:

  • 当BIOS跳转到您的代码时,您不能依赖具有有效或预期值的CS,DS,ES,SS,SP寄存器.应在引导加载程序启动时正确设置它们.您只能保证将从物理地址0x07c00加载并运行引导加载程序,并将引导驱动器号加载到DL寄存器中.

BIOS可以使用FAR JMP(或等效)代码jmp 0x07c0:0x0000,并且一些仿真器和真实硬件就是这样做的.其他jmp 0x0000:0x7c00人像VirtualBox一样使用.

我们应该通过明确地将DS设置为我们需要的内容并将其设置为对我们在ORG指令中使用的值有意义的内容来解释这一点.


摘要

不要假设CS是我们期望的值,并且不要盲目地将CS复制到DS.明确设置DS.

如前所述ORG 0x0000,如果我们将DS适当地设置为0x07c0 ,那么您的代码可以被修复为使用您最初使用的代码.这可能看起来像:

ORG 0
BITS 16

push word 0xB800        ; Address of text screen video memory in real mode for colored monitors
push 0x07c0
pop ds                  ; DS=0x07c0 since we use ORG 0x0000
pop es
Run Code Online (Sandbox Code Playgroud)

或者我们可以ORG 0x7c00像这样使用:

ORG 0x7c00
BITS 16

push word 0xB800        ; Address of text screen video memory in real mode for colored monitors
push 0x0000
pop ds                  ; DS=0x0000 since we use ORG 0x7c00
pop es
Run Code Online (Sandbox Code Playgroud)