在 QEMU 中诊断引导加载程序代码?

Diagnosing boot loader code in QEMU?

正在诊断 QEMU 中的引导加载程序代码?

我正在尝试创建一个打印字符 'A' 然后停止的最小 'boot loader code'。

我为此写了下面的C++程序

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
  /*
   * If I run these code directly in Windows, it crashes, for I believe Windows 
   * do not allow accessing the BIOS directly, that make sense
   * 
   * But I can compile a binary, then I can use the debugger to view what machine code
   * does these correspond to, and build to boot program!
   */
  /*
  __asm
  {
    mov ah, 0x0e
    mov al, 0x41
    mov bx, 15
    int 0x10
    hlt
lp: jmp lp   
  }
   */
  int disk_length = 80 * 18 * 512 * 2;
  char* disk = (char*)calloc(disk_length, sizeof(char));

  const char program[] =
  {
    0xb4, 0x0e,             //     mov ah, 0EH
    0xb0, 0x41,             //     mov al, 41H
    0x66, 0xbb, 0x0f, 0x00, //     mov bx, 0FH
    0xcd, 0x10,             //     int 10H
    0xf4,                   //     hlt
    0xeb, 0xfe              // lp: jmp lp
  };
  const char boot_signature[] = {0x55, 0xAA};

  const int program_length = _countof(program);
  const int boot_signature_length = _countof(boot_signature);

  // Be careful with file mode
  FILE* imgFile = fopen("disk.img", "wb");

  memcpy(disk, program, program_length);
  memcpy(disk + 510, boot_signature, boot_signature_length);

  int written = 0;
  while (written < disk_length)
  {
    written += fwrite(disk + written, sizeof(char), disk_length - written, imgFile);
  }
  fclose(imgFile);

  return 0;
}

首先,我运行 内联汇编的代码没有注释。在调试器中,我导出了操作码并确定操作码与我的源代码中的操作码相匹配。 接下来,我运行注释了内联汇编的代码,然后我生成了一个img文件。我使用二进制编辑器查看内容并确保它看起来像我想要的那样。 最后,我 运行 qemu-system-i386.exe -fda disk.img,我希望引导加载程序显示大写字母 'A',但没有显示任何内容。

现在我有两个问题:

1.) 我的代码有什么问题? 2.) 如何诊断?

问题似乎是这个指令序列:

0x66, 0xbb, 0x0f, 0x00, //     mov bx, 0FH
0xcd, 0x10,             //     int 10H

实模式下的 operand prefix 0x66 将指令解码为 32 位寄存器(在本例中),然后将使用 4 个字节对立即值进行编码。这当然会使用 int 10h 作为 mov 数据的一部分。所以你的指令真正做的是:

0x66, 0xbb, 0x0f, 0x00, 0xcd, 0x10,  // mov ebx,0x10cd000f

然后是通常出现的 hltjmp 指令。在以 16 位实模式为目标的代码中(预计在 8086/8088 上 运行ning),您根本不想使用此类前缀。您的代码应该简单地排除前缀并且看起来像:

0xbb, 0x0f, 0x00,       //     mov bx, 0FH
0xcd, 0x10,             //     int 10H

您可以使用理解 16 位指令的反汇编程序来反汇编 disk.img 文件。 NDISASMNASM 程序(Netwide Assembler)的一部分,它是一个可以处理 16 位代码的好反汇编程序。 NASM 可用于 WindowsNDISASM 将被安装并且可以 运行 这样:

ndisasm -b16 disk.img

这将尝试将 disk.img 解码为 16 位 (-b16) 二进制文件。您可能会发现从您的字节中翻译的指令是什么以及哪里出了问题。

您还可以尝试使用 QEMU 中的远程调试功能,并使用 GDB 调试器单步调试您的代码。我在 Windows 上没有 运行 QEMU 所以我不知道它是否内置了远程调试支持。


您可以考虑使用可用于生成 16 位 8086/适合在引导加载程序中使用的 8088 代码。 Whosebug 上有许多问题和答案,说明如何使用 NASM 创建引导加载程序。