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

时间:2015-12-22 08:21:40

标签: assembly nasm x86-16 bootloader fasm

我一直在搞乱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

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

它打印的内容如下: VirtualBox

1 个答案:

答案 0 :(得分:9)

ORG指令

当您在汇编程序顶部指定{em> ORG 指令(如ORG 0x0000)并使用BITS 16时,您将通知 NASM 将标签解析为代码和数据,将生成的绝对偏移量将基于 ORG 中指定的起始偏移量(16位代码将限制为偏移量为 WORD < / em> / 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

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

VirtualBox / CS:IP /段:偏移对

对于Virtual Box,在物理地址0x00007c00加载引导扇区后,它将有效地将 FAR JMP 等效设置为0x0000:0x7c00。 FAR JMP (或等效的)不仅会跳转到给定的地址,还会将 CS IP 设置为指定的值。 FAR JMP 到0x0000:0x7c00将设置 CS = 0x0000和 IP = 0x7c00。

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

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

您的Bootloader会出现什么问题?

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

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

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

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

   0:   0e                      push   cs
   1:   1f                      pop    ds

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

   8:   a0 24 00                mov    al,ds:0x24

ds:0x24实际上是数据部分中 msg 变量的编码地址。偏移量0x24处的字节中包含值P(0x25具有A)。你可能会看到事情可能出错的地方。我们的 DS = 0x0000,因此mov al,ds:0x24mov al,0x0000:0x24实际上相同。此语法无效,但我将 DS 替换为0x0000以获取一个点。 0x0000:0x24是我们的代码在执行时将尝试从中读取我们的字母P。可是等等!这是物理地址(0x0000 <&lt; 4)+ 0x24 = 0x00024。该存储器地址恰好位于中断向量表中间的存储器底部。显然这不是我们想要的!

有几种方法可以解决这个问题。最简单(也是首选的方法)是将正确的段放入 DS ,而不是依赖于程序运行时 CS 的内容。由于我们设置了 ORG 0x0000,我们需要一个数据段( DS )= 0x07c0。段:偏移对0x07c0:0x0000 =物理地址0x07c00。这是我们的引导加载程序的地址。所以我们要做的就是通过替换:

来修改代码
    push cs
    pop  ds      ; DS=CS

使用:

    push 0x07c0
    pop  ds      ; DS=0x07c0 

VirtualBox 中运行时,此更改应提供正确的输出。现在让我们看看为什么。这段代码没有改变:

   8:   a0 24 00                mov    al,ds:0x24

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

故事的道德?当你启动我们的程序时,你在 ORG 中使用的是 DS 寄存器中应该有一个适用的值。我们应该明确地设置它,而不是依赖于< EM> CS

为什么立即值打印?

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

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

  16:   b0 4f                   mov    al,0x4f        ; 0x4f = 'O'

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

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

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

使用我的第一个示例并将ORG 0x0000修改为ORG 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

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

    7c08:       a0 24 7c                mov    al,ds:0x7c24

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

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

    7c08:       a0 24 7c                mov    al,ds:0x7c24
在这种情况下,

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

那么我们如何解决这个问题呢?修改Ross Ridge建议的内容,并注意我之前给出的关于明确将 DS 设置为我们真正想要的段的建议(不要假设 CS 是正确的,然后盲目地复制到 DS )如果我们使用ORG 0x7c00,我们应该在启动引导程序时将0x0000放入 DS 。所以我们可以改变这段代码:

ORG 0x7c00

start:
    push cs
    pop  ds      ; DS=CS

为:

ORG 0x7c00

start:
    xor ax, ax   ; ax=0x0000
    mov ds, ax   ; DS=0x0000

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

通过采用这种方法, CS 中可能用于访问引导加载程序的值并不重要,代码仍然会引用我们数据的相应内存位置。

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

在我之前的StackOverflow回答中写的General Bootloader Tips中,提示#1非常重要:

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

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

我们应该通过明确地将 DS 设置为我们需要的内容来解决这个问题,并将其设置为我们在 ORG 指令中使用的值。

摘要

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

如前所述,如果我们将 DS 正确设置为0x07c0,则可以修复您的代码以使用ORG 0x0000原来的代码。这可能看起来像:

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

或者我们可以像这样使用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