It is very helpful to use an emulator with disk images to test code. We use bochs. It is also convenient to use a bootloader. We use grub. The osdever tutorial is useful, but doesn't really lay out how to do this. I've found a single harddisk image with both grub and my kernel image a useful route. I depended on a few sources, but this one was particularly helpful.

To get this started, I downloaded, compiled, and installed bochs. I leave this as an exercise for the reader. Next, I used the bochs image builder, bximage, to build a harddisk image:

wells@pulate the elusive .bochsrc. First, you'll have to find a sample. Because I built bochs, the sample was in /usr/local/share/doc/bochs/bochsrc-sample.txt: wells@local:~$ cp /usr/local/share/doc/bochs/bochsrc-sample.txt ~/.bochsrc sanctus:~kernel/images$ bximage ======================================================================== bximage Disk Image Creation Tool for Bochs $Id: bximage.c,v 1.32 2006/06/16 07:29:33 vruppert Exp $ ======================================================================== Do you want to create a floppy disk image or a hard disk image? Please type hd or fd. [hd] hd What kind of image should I create? Please type flat, sparse or growing. [flat] flat Enter the hard disk size in megabytes, between 1 and 129023 [10] 10 I will create a 'flat' hard disk image with cyl=20 heads=16 sectors per track=63 total sectors=20160 total size=9.84 megabytes What should I name the image? [c.img] hd.img Writing: [] Done. I wrote 10321920 bytes to my.img. The following line should appear in your bochsrc: ata0-master: type=disk, path="hd.img", mode=flat, cylinders=20, heads=16, spt=63

If you choose a different size, it is important to note the number of cylinders reported by bximage. Next, you need to "partition" the disk image. Happily, with a little help, fdisk will work on files:

wells@local:~/kernel/images$ fdisk -S 63 -H 16 -C 20 hd.img Command (m for help): n Command action e extended p primary partition (1-4) p Partition number (1-4): 1 First cylinder (1-20, default 1): 1 Last cylinder or +size or +sizeM or +sizeK (1-20, default 20): 20 Command (m for help): a Partition number (1-4): 1 Command (m for help): w The partition table has been altered! Calling ioctl() to re-read partition table. WARNING: Re-reading the partition table failed with error 25: Inappropriate ioctl for device. The kernel still uses the old table. The new table will be used at the next reboot. Syncing disks. wells@local:~/kernel/images$

Notice that we pass the disk geometry directly to fdisk on the command-line. Nobody seems to believe that setting the disk as bootable is necessary, but everybody seems to do it, so I do as well. We next use the Linux loopback facility to mount the image as a disk and create a filesystem. To do this, you must have kernel support for it, but many distributions seem to include it in their standard kernels by default. Also, I use losetup to attach the file to the loopback device. The '32256' is after the first track (512 bytes/sector * 63 sectors/track). According to some sources, this is necessary because fdisk want to leave the entire first track for the master boot record. Also, apparently, some bootloaders (grub included) use the first track for multi-stage loaders. Anyhow, using losetup:

wells@local:~/kernel/images$ sudo /sbin/losetup -o 32256 /dev/loop0 my.img

The -o flag attaches at an offset in the file. Now we can treat the image like a device. So, we build an ext2 filesystem on it:

wells@local:~/kernel/images$ sudo /sbin/mke2fs /dev/loop0 mke2fs 1.40-WIP (14-Nov-2006) Filesystem label= OS type: Linux Block size=1024 (log=0) Fragment size=1024 (log=0) 2512 inodes, 10048 blocks 502 blocks (5.00%) reserved for the super user First data block=1 Maximum filesystem blocks=10485760 2 block groups 8192 blocks per group, 8192 fragments per group 1256 inodes per group Superblock backups stored on blocks: 8193 Writing inode tables: done Writing superblocks and filesystem accounting information: done This filesystem will be automatically checked every 33 mounts or 180 days, whichever comes first. Use tune2fs -c or -i to override.

Notice that we are using root privilege (via sudo -- but you can do whatever floats your boat) for these operations. This seems necessary. We are next going to mount the device, copy over some necessary files for grub, then use the grub shell to configure the disk image for grub booting.

wells@local:~/kernel/images$ sudo mount /dev/loop0 mnt wells@local:~/kernel/images$ sudo mkdir -p mnt/boot/grub wells@local:~/kernel/images$ sudo cp /boot/grub/stage1 /boot/grub/stage2 /boot/grub/e2fs_stage1_5 mnt/boot/grub/ wells@local:~/kernel/images$ sudo grub --no-floppy grub> device (hd0) hd.img grub> geometry (hd0) 20 16 63 drive 0x80: C/H/S = 20/16/63, The number of sectors = 20160, my.img Partition num: 0, Filesystem type is ext2fs, partition type 0x83 grub> root (hd0,0) grub> setup (hd0) Checking if "/boot/grub/stage1" exists... yes Checking if "/boot/grub/stage2" exists... yes Checking if "/boot/grub/e2fs_stage1_5" exists... yes Running "embed /boot/grub/e2fs_stage1_5 (hd0)"... 15 sectors are embedded. succeeded Running "install /boot/grub/stage1 (hd0) (hd0)1+15 p (hd0,0)/boot/grub/stage2 /boot/grub/menu.lst"... succeeded Done. grub> quit

The --no-floppy parameter tells the grub shell not to probe the floppy disk. The (hd0) notation is grub nomenclature for disk identification. I doubt the root (hd0,0) is necessary in this context, but I haven't tried it without. Notice, too, that I use a relative mount directory, mnt/, but you can of course mount wherever your fancy is most tickled.

Now that we've added grub, we need only add the kernel image and unmount:

wells@local:~/kernel/images$ sudo cp ../kernel.bin mnt/ wells@local:~/kernel/images$ sudo umount mnt wells@local:~/kernel/images$ sudo losetup -d /dev/loop0

I wrote the image to the root of the disk image filesystem. The last step before starting bochs is to edit the .bochsrc and add the image. If you don't have a ~/.bochsrc, you'll probably want to copy one from your build source for bochs:

wells@local:~$ cp /usr/local/share/doc/bochs/bochsrc-sample.txt ~/.bochsrc

I didn't modify anything from the default, except that I added the line from our bochs image build in the section for ata:

ata0-master: type=disk, path="/path/to/your/hd.img", mode=flat, cylinders=20, heads=16, spt=63

Substitute a sensible path for /path/to/your/. You may also want to change the boot order, removing the floppy. Now you're ready to start bochs and execute your image:

wells@local:~/kernel/images$ bochs -q ======================================================================== Bochs x86 Emulator 2.3 Build from CVS snapshot on August 27, 2006 ======================================================================== 00000000000i[ ] LTDL_LIBRARY_PATH not set. using compile time default '/usr/local/lib/bochs/plugins' 00000000000i[ ] BXSHARE not set. using compile time default '/usr/local/share/bochs' 00000000000i[ ] reading configuration from /home/wells/.bochsrc 00000000000i[ ] lt_dlhandle is 0x81b4760 00000000000i[PLGIN] loaded plugin libbx_sdl.la 00000000000i[ ] installing sdl module as the Bochs GUI 00000000000i[ ] using log file bochsout.txt [ ... in another window ... ] grub> kernel (hd0,0)/kernel.bin , <0x100000:0x3000:0x3860>, shtab=0x107168 grub> boot

Something went wrong with the disk image, or your kernel image is wrong, if bochs can't find or won't load the kernel with the kernel directive. Otherwise, you should be good to go!

After a while, you'll tire of typing the same grub directive every time you test, so you'll want to create a grub menu.lst. Here is a minimalist menu, which you may place as /boot/grub/menu.lst on your image:

default 0 timeout 0 hiddenmenu groot=(hd0,0) title K-OS root (hd0,0) kernel (hd0,0)/kernel.bin