Search code examples
assemblyvisual-c++x86-16bootloaderreal-mode

Diagnosing boot loader code in QEMU?


Diagnosing boot loader code in QEMU?

I am trying to create a minimal 'boot loader code' that print the character 'A' and then halt.

I wrote the following C++ program for the purpose

#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;
}

First, I ran the code with the inline assembly uncommented. In the debugger, I derived the opcodes and is certain the opcode matches the ones in my source code. Next, I ran the code with the inline assembly commented, then I generated a img file. I used a binary editor to look at the content and make sure it looks the way I wanted it. Last, I ran qemu-system-i386.exe -fda disk.img, I am expecting the boot loader program to show a capital 'A', but instead, nothing is shown.

Now I have two questions:

1.) What's wrong with my code? 2.) How do I diagnose it?


Solution

  • The problem appears to be this instruction sequence:

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

    An operand prefix 0x66 in real mode decodes the instruction as the 32-bit register (in this case) and will then use 4 bytes to encode the immediate value. This of course would use the int 10h as part of the data for the mov. So what your instruction really does is:

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

    And that would be followed by the hlt and jmp instruction as they normally appear. In code targeting 16-bit real mode (and expected to running on an 8086/8088) you don't want to use such prefixes at all. Your code should simply exclude the prefix and look like:

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

    You could use a disassembler that understands 16-bit instructions to disassemble the disk.img file. One good disassembler that can handle 16-bit code is NDISASM which is part of the NASM program (Netwide Assembler). NASM is available for Windows. NDISASM will be installed and can be run this way:

    ndisasm -b16 disk.img
    

    This will try to decode disk.img as a 16-bit (-b16) binary. You'd probably discover what the translated instructions from your bytes are and what was wrong.

    You could also attempt to use the remote debugging feature in QEMU and use GDB debugger to step through your code. I don't run QEMU on Windows so I don't know if it is built with the remote debugging support.


    Instead of writing your bootloader the way you are, you might consider using an assembler (like NASM) that can be used to generate 16-bit 8086/8088 code that is suitable for use in bootloaders. There are many questions and answers on Stackoverflow that show how NASM can be used to create bootloaders.