2

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?

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Andrew Au
  • 812
  • 7
  • 18
  • 1
    I find it amusing that you ended up writing a program just to put bytes into a disk image. On Linux you'd just use `dd` for this. (Michael's answer is correct BTW: you compiled/assembled your code for 32bit mode, but are then running those instructions in 16bit mode. So you have operand-size prefixes where there shouldn't be any.) – Peter Cordes Dec 21 '15 at 04:24

1 Answers1

5

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.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • 1
    `objdump -D -Mintel,i8086 a.out` is 16-bit capable, too; for raw binaries use: `objdump -D -Mintel,i8086 -b binary -m i386 foo.bin` – mirabilos Dec 22 '15 at 20:22
  • @mirabilos : you are correct. The reason I didn't mention it in this particular question is that the user is on the Windows platform, and I don't know if he has a GNU Binutil tool chain on his system (ie MinGW/Cygwin/MSYS/MSYS2). I used NDISASM because I suggested _NASM_ be downloaded and the tools can run standalone without a lot of overhead. – Michael Petch Dec 22 '15 at 20:34
  • 2
    Ah right, that’s true. In the meantime, I [extended a bit on GNU binutils’ version](http://stackoverflow.com/a/34424194/2171120) elsewhere, which also contains a few pointers to `ndisasm`; the “comes with the OS” argument is moot there of course because either needs to be downloaded. (On DOS, I use `debug.com` or Borland’s Turbo Debugger for such purposes myself, the former coming with MS-DOS 3.30A.) – mirabilos Dec 22 '15 at 20:52