繁体   English   中英

使用“ORG 0x0000”时以 8086 实模式从内存中读取

[英]Reading from memory in 8086 real mode while using 'ORG 0x0000'

我一直在摆弄 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 组装了它,但遇到了同样的问题,这显然是我的错。

它打印出如下内容: 虚拟盒子

组织指令

当您在汇编程序顶部指定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

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


VirtualBox / CS:IP / Segment:Offset 对

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

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

由于 VirtualBox 使用 0x0000:0x7c00 的CS:IP ,它将在 (0x0000<<4)+0x7c00 = 20 位物理地址 0x07c00 的物理地址开始执行代码。 请注意,不能保证在所有环境中都是如此。 由于段:偏移对的性质,引用物理地址 0x07c00 的方法不止一种。 有关正确处理此问题的方法,请参阅此答案末尾的部分。


您的引导加载程序出了什么问题?

假设我们使用的是 VirtualBox 并且上一节中的上述信息被认为是正确的,那么在进入我们的引导加载程序时CS = 0x0000 和IP = 0x7c00。 如果我们使用示例代码(使用ORG 0x0000 )我写在这个答案的第一部分并查看反汇编信息(我将使用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

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

如果你在 VirtualBox 中运行这个程序,前 2 个字符将是乱码。 那是为什么呢? 首先回忆一下 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:0x24实际上和mov al,0x0000:0x24一样。 此语法无效,但我将DS替换为 0x0000 以表明观点。 0x0000:0x24是我们的代码在执行时将尝试从中读取字母P的位置。 可是等等。 即物理地址(0x0000<<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:0x24 0x07c0:0x24 ,这将转换为 (0x07c0<<4)+0x24 = 0x07c24 的物理地址。 这就是我们想要的,因为我们的引导加载程序由 BIOS 从该位置开始物理地放入内存中,因此它应该正确引用我们的msg变量。

故事的道德启示? 无论您对ORG使用什么,当我们启动程序时, DS寄存器中都应该有一个适用的值。我们应该明确设置它,而不是依赖于CS中的内容。


为什么要打印立即值?

使用原始代码,前 2 个字符打印出乱码,但后两个字符没有。 正如在上一节中讨论的那样,前 2 个字符无法打印是有原因的,但是最后 2 个字符呢?

让我们更仔细地检查第三个字符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<<4)+0x7c24 = 0x07c24。 这是加载引导加载程序的正确内存位置,也是我们的msg字符串的正确位置。 所以它有效。

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

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

在这种情况下DS = 0x07c0。 当程序实际运行时,我们现在有类似于mov al,0x07c0:0x7c24的东西。 呃,那看起来很糟糕。 这转化为物理地址是什么意思? (0x07c0<<4)+0x7c24 = 0x0F824。 它位于我们的引导加载程序之上的某个位置,它将包含计算机启动后出现的所有内容。 可能为零,但应假定为垃圾。 显然不是我们的味精字符串被加载的地方!

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

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中的什么值到达我们的引导加载程序并不重要,代码仍会为我们的数据引用适当的内存位置。


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

在我在之前的 StackOverflow 回答中写的一般引导加载程序技巧中,技巧 #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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM