2

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

我正在尝试创建一个最小的“引导加载程序代码”,打印字符“A”然后停止。

为此,我编写了以下 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.) 我该如何诊断?

4

1 回答 1

5

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

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

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

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

然后是通常出现的hltand指令。jmp在针对 16 位实模式的代码中(并希望在 8086/8088 上运行),您根本不想使用这样的前缀。您的代码应该简单地排除前缀,如下所示:

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

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

ndisasm -b16 disk.img

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

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


您可以考虑使用可用于生成适用于引导加载程序的 16 位 8086/8088 代码的汇编程序(如NASM ),而不是按照您的方式编写引导加载程序。Stackoverflow 上有许多问题和答案展示了如何使用NASM创建引导加载程序。

于 2015-12-21T03:53:42.247 回答