NeuroAgent

How to Create an MBR Bootloader for QEMU on macOS/Linux

Step-by-step guide to creating an MBR-compatible bootloader for QEMU with FAT16 on macOS and Linux. Learn how to create a bootable image, copy the bootloader, and add files.

Question

How to create an MBR-compatible bootloader on macOS?

Or at least on Linux?

I have a file /Users/elisey/Downloads/OS/2/Bootloader, and I want to create a hard disk for QEMU with MBR and FAT16, so that it uses my bootloader and is mountable (so that I can copy files).

How can I do this?

NeuroAgent

Creating an MBR-Compatible Bootloader for QEMU with FAT16 Filesystem on macOS or Linux

Creating an MBR-compatible bootloader for QEMU with a FAT16 filesystem on macOS or Linux can be done using the command line. The main process involves creating an empty disk image, setting up an MBR with a partition table, formatting to FAT16, copying your bootloader to the boot sector, and adding files. On macOS, the built-in tools dd and diskutil are available, while Linux offers dd, fdisk, and mkfs.vfat.

Table of Contents

Necessary Tools for macOS

On macOS, you already have all the necessary tools for creating a bootable MBR image with FAT16:

  • dd - for creating disk images and writing MBR
  • diskutil - for formatting partitions to FAT16
  • hdiutil - for mounting images
  • fdisk (via fdisk_fdisk or gptfdisk from Homebrew)

If you need fdisk, install it via Homebrew:

bash
brew install gptfdisk

Step-by-Step Instructions for macOS

1. Creating an Empty Disk Image

First, create an empty image of the desired size. For this example, we’ll create a 32 MB image:

bash
dd if=/dev/zero of=mydisk.img bs=1M count=32

2. Creating MBR with Partition Table

Now, let’s create an MBR and partition table. In the MBR, we’ll define one active FAT16 partition:

bash
# Create MBR with active FAT16 partition
(
echo "g"          # Create GPT (for compatibility)
echo "n"          # New partition
echo "1"          # Partition number
echo ""          # Starting sector (default)
echo "+31M"      # Partition size
echo "t"          # Change partition type
echo "1"          # Select partition 1
echo "06"        # FAT16 type (0x06)
echo "a"          # Make partition active
echo "w"          # Write changes
echo "y"          # Confirm overwrite
) | fdisk mydisk.img

3. Copying Bootloader to MBR

Now, copy your bootloader to the first sector of the disk:

bash
dd if=/Users/elisey/Downloads/OS/2/Bootloader of=mydisk.img conv=notrunc bs=512 count=1

4. Formatting Partition to FAT16

Use diskutil to format the partition to FAT16:

bash
# Attach the image as a device
diskutil attach mydisk.img

# Get the device name (e.g., /dev/disk4)
# Format the partition to FAT16
diskutil partitiondisk /dev/disk4 1 MBR "MS-DOS FAT16" "MYDISK" 0B

# Detach the device
diskutil detach /dev/disk4

5. Mounting the Image to Add Files

Mount the image to copy files:

bash
hdiutil attach mydisk.img

After this, the FAT16 filesystem will be accessible as a regular volume where you can copy files.

Alternative Method for Linux

If you have access to Linux, the process can be simpler:

1. Creating the Image

bash
dd if=/dev/zero of=mydisk.img bs=1M count=32

2. Creating MBR and Partitions

bash
# Create MBR with fdisk
(
echo "o"          # Create empty MBR table
echo "n"          # New partition
echo "p"          # Primary partition
echo "1"          # Partition number
echo ""          # Starting sector
echo ""          # Ending sector (to end of disk)
echo "a"          # Make active
echo "w"          # Write
) | fdisk mydisk.img

3. Copying the Bootloader

bash
dd if=/Users/elisey/Downloads/OS/2/Bootloader of=mydisk.img conv=notrunc bs=512 count=1

4. Formatting to FAT16

bash
# Determine partition offset (usually 2048 sectors of 512 bytes)
OFFSET=1048576  # 2048 * 512

# Create loop device
sudo losetup -o $OFFSET --sizelimit $((31*1024*1024)) -f mydisk.img

# Format to FAT16
sudo mkfs.vfat -F 16 /dev/loop0

# Mount
sudo mount /dev/loop0 /mnt

# Copy files
sudo cp -r /path/to/your/files /mnt/

# Unmount
sudo umount /dev/loop0
sudo losetup -d /dev/loop0

QEMU Configuration for Booting

To use your image in QEMU, use the following command:

bash
qemu-system-x86_64 -drive file=mydisk.img,format=raw,if=ide -m 512M

Or for more precise simulation:

bash
qemu-system-i386 -drive file=mydisk.img,format=raw,if=floppy -m 512M

Adding Files to the Image

After creating the image, you can add files in several ways:

Through macOS:

bash
# Mount the image
hdiutil attach mydisk.img

# Copy files to the mounted volume
cp -r /path/to/files /Volumes/MYDISK/

# Unmount
hdiutil detach /Volumes/MYDISK

Through Linux:

bash
# Create loop device
sudo losetup -o 1048576 --sizelimit $((31*1024*1024)) -f mydisk.img

# Mount
sudo mount /dev/loop0 /mnt

# Copy files
sudo cp -r /path/to/files /mnt/

# Unmount
sudo umount /dev/loop0
sudo losetup -d /dev/loop0

Solving Common Problems

Problem: QEMU doesn’t boot from the image

Solution: Check that:

  • The bootloader is in the first 512 bytes of the image
  • The partition is marked as active (bootable flag)
  • There’s an MBR signature of 0xAA55 at the end

Check MBR:

bash
# On macOS
diskutil info mydisk.img

# Or manually
hexdump -C mydisk.img | tail -n 1
# Should see: 0003e000  aa 55 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |.U.............|

Problem: Filesystem doesn’t mount

Solution: There might be an issue with partition alignment. Ensure the partition starts at the correct sector (usually sector 2048 for 4K alignment):

bash
# Check partition structure
fdisk -l mydisk.img

Problem: Bootloader doesn’t work

Solution: Check that your bootloader is correctly copied and maintains the MBR structure. The bootloader should:

  • Be exactly 512 bytes
  • Contain the correct signature at the end
  • Properly handle BIOS interrupts

Detailed MBR and FAT16 Structure

MBR Structure (512 bytes):

Bytes 0-445: Bootloader code
Bytes 446-510: Partition table (4 entries of 16 bytes each)
Bytes 510-511: MBR signature (0xAA55)

Partition Table Structure (16 bytes):

Byte 0: Active flag (0x80 for active)
Bytes 1-3: Starting CHS
Byte 4: Partition type (0x06 for FAT16)
Bytes 5-7: Ending CHS
Bytes 8-11: Starting LBA
Bytes 12-15: Partition size in sectors

FAT16 Bootable Volume Structure:

  1. MBR (512 bytes) with your bootloader
  2. VBR (Volume Boot Record) - 512 bytes, including BPB (BIOS Parameter Block)
  3. Reserved sectors - usually 1 sector
  4. FAT tables (2 copies)
  5. Root directory
  6. Data area

To create a fully compatible image, ensure your bootloader maintains the MBR structure and properly handles the transition to VBR code when necessary.


Sources

  1. Creating bootable FreeDOS DOS floppy diskette IMG file for V86 on OSX
  2. How to create SD card .img and add files to it in linux (fat16)?
  3. Bootable FAT16 Image - OSDev.org
  4. How do I create an MBR on a USB stick using DD command line tool
  5. Creating and using disk images mini-howto
  6. How To Create Disk Image on Mac OS X With dd Command
  7. Disk Images — QEMU documentation
  8. Booting from real (UEFI) disk image on QEMU

Conclusion

Creating an MBR-compatible bootloader for QEMU with a FAT16 filesystem on macOS is a feasible task using standard command-line utilities. The key steps include:

  1. Creating an empty image using dd
  2. Setting up MBR and partition table via fdisk or diskutil
  3. Copying the bootloader to the first 512 bytes of the image
  4. Formatting the partition to FAT16 using mkfs.vfat or diskutil
  5. Mounting the image to add files

On macOS, the process is slightly more complex due to limited built-in tools, but it’s fully achievable. For more convenient work, it’s recommended to install gptfdisk via Homebrew. If you encounter booting issues, check the correctness of the MBR signature (0xAA55) and the active partition flag.

For experimenting with booting, you might also consider using simpler formats like hybrid images or ISOs, but MBR+FAT16 remains the most universal solution for QEMU bootable images.