Better BSP

Embedded systems don't have to suck

Anatomy of a Bootable ISO

I recently tried to make a bootable ISO using Buildroot, and encountered some difficulty. In that process, I realized that I don’t actually know what all goes into a bootable Linux ISO. So this is my collection of notes that I made while trying to answer that question.

Starting out, the ISOLINUX documentation is going to be my guide, along with a Core Linux ISO. I was originally going to use an Ubuntu ISO, but I had a hard time getting the Ubuntu ISO to boot in qemu without having a graphic display attached to it (the box I’m running this all on is headless).

Loopback mount of the ISO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
aja042@tellurium:~$ sudo mkdir /mnt/corelinux
[sudo] password for aja042:

aja042@tellurium:~$ sudo mount -o loop ~/Downloads/Core-current.iso /mnt/corelinux/
mount: /dev/loop1 is write-protected, mounting read-only

aja042@tellurium:~$ ls /mnt/corelinux/
boot

aja042@tellurium:~$ ls /mnt/corelinux/boot/
core.gz  isolinux  vmlinuz

aja042@tellurium:~$ ls /mnt/corelinux/boot/isolinux/
boot.cat  boot.msg  f2  f3  f4  isolinux.bin  isolinux.cfg

This is a nice simple looking ISO.

What’s in the isolinux.cfg?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
aja042@tellurium:~$ cat /mnt/corelinux/boot/isolinux/isolinux.cfg
display boot.msg
default microcore
label microcore
        kernel /boot/vmlinuz
        initrd /boot/core.gz
        append loglevel=3
                        
label mc
        kernel /boot/vmlinuz
        append initrd=/boot/core.gz loglevel=3
implicit 0
prompt 1
timeout 300
F1 boot.msg
F2 f2
F3 f3
F4 f4

And booting it is pretty simple too:

1
$ qemu-system-x86_64 -curses -boot d -cdrom ~/Downloads/Core-current.iso -m 512

Note that I’m using the -curses option because I don’t have a display on this machine. Drop that if you’re running from desktop Linux. If you do run with -curses, note that you can press ESC 2 to get into the QEMU monitor mode and type quit to exit.

What else is on here?

1
2
aja042@tellurium:/mnt/corelinux/boot$ file vmlinuz
vmlinuz: Linux kernel x86 boot executable bzImage, version 4.2.9-tinycore (tc@box) #1999 SMP Mon Jan 18 19:42:12 UTC 2016, RO-rootFS, swap_dev 0x3, Normal VGA

So that’s a standard bzImage kernel, renamed to vmlinuz for some reason. Good enough!

1
2
aja042@tellurium:/mnt/corelinux/boot$ file core.gz
core.gz: gzip compressed data, was "core.cpio", last modified: Mon Jul  4 08:08:02 2016, max compression, from Unix

That’s the initrd referenced above. Together, those are the only real files that the isolinux.cfg references.

Can I repack this?

A good test to see if I get how an image like this works is to figure out how to recreate it from the files. One important thing here is that this is a bootable ISO:

1
2
aja042@tellurium:/mnt/corelinux/boot/isolinux$ file ~/Downloads/Core-current.iso
/home/aja042/Downloads/Core-current.iso: ISO 9660 CD-ROM filesystem data 'Core' (bootable)

Which is likely an extra wrinkle.

Going off the instructions, you can get a binary version of syslinux from kernel.org. I’m going to use 6.03.

For a little bit of an added challenge, I’m going to make the directory layout different - I’m just going to stick all the files in the root of the new ISO.

1
2
3
aja042@tellurium:~/workspace/bootable-iso$ mkdir cd_root
aja042@tellurium:~/workspace/bootable-iso$ cd cd_root/
aja042@tellurium:~/workspace/bootable-iso/cd_root$ cp /mnt/corelinux/boot/{vmlinuz,core.gz} .

Next, the instructions say to copy isolinux.bin and ldlinux.c32:

1
2
aja042@tellurium:~/workspace/bootable-iso/cd_root$ cp ../syslinux-6.03/bios/core/isolinux.bin .
aja042@tellurium:~/workspace/bootable-iso/cd_root$ cp ../syslinux-6.03/bios/com32/elflink/ldlinux/ldlinux.c32 .

And now, I’m supposed to create isolinux.cfg. Here’s what I put in it:

1
2
3
4
5
6
7
aja042@tellurium:~/workspace/bootable-iso$ cat cd_root/isolinux.cfg
default microcore
label microcore
        kernel /vmlinuz
        initrd /core.gz
        append loglevel=3
                       

One thing that isn’t clear to me in all of this is how isolinux knows where to find these files. Anyway, I’ll keep following the instructions for now. Next up is creating the actual ISO:

1
2
3
4
5
6
7
8
9
10
aja042@tellurium:~/workspace/bootable-iso$ mkisofs -o output.iso -b isolinux.bin -c boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table cd_root/
I: -input-charset not specified, using utf-8 (detected in locale settings)
Size of boot image is 4 sectors -> No emulation
 91.43% done, estimate finish Fri Mar  3 20:22:49 2017
 Total translation table size: 2048
 Total rockridge attributes bytes: 0
 Total directory bytes: 0
 Path table size(bytes): 10
 Max brk space used 0
 5483 extents written (10 MB)

Alright, that seemed to work. Let’s try booting it!

1
aja042@tellurium:~/workspace/bootable-iso$ qemu-system-x86_64 -curses -boot d -cdrom output.iso -m 512

This boots it successfully!

What do the -b and -c flags do in mkisofs?

The man page for mkisofs is actually called genisoimage, for some reason. Doing mkisofs --help reveals this.

-b eltorito_boot_image 

  Specifies the path and filename of the boot image to be used when
  making an El Torito bootable CD for x86 PCs. The pathname must be
  relative to the source path specified to genisoimage.  This option is
  required to make an El Torito bootable CD.  The boot image must be
  exactly 1200 kB, 1440 kB or 2880 kB, and genisoimage will use this
  size when creating the output ISO9660 filesystem.  The PC BIOS will
  use the image to emulate a floppy disk, so the first 512-byte sector
  should contain PC boot code.  This will work, for example, if the boot
  image is a LILO-based boot floppy.

  If the boot image is not an image of a floppy, you need to add either
  -hard-disk-boot or -no-emul-boot.  If the system should not boot off
  the emulated disk, use -no-boot.

So this is useful! It says that the argument passed to -b is going to be used as the boot image. So what the heck is El Torito? It is, apparently, a standard for how PCs should search for boot code on a CD-ROM. If you’re really curious, you can have a look at the specification from Intel and Phoenix. Section 5.3 describes “No Emulation Booting”, which goes into some awesome x86 history describing interrupts that you can use to retrieve more boot information.

One thing that the standard mentions is the “boot catalog”. The man page confirms that -c is used to specify the filename to be used for the boot catalog. What is the boot catalog? According to section 2.0, it’s a “collection of [0x]20-byte entries, packed [0x]40 entries to the sector”. Section 2.5 explains that these contain a Header ID, a platform ID (x86/PowerPC/Mac), an ID string, a checksum, and two key bytes. These also have a bit more information about what kind of boot media it is, how many sectors should be loaded, etc. I’m personally quite happy that mkisofs makes this for me and I don’t really have to think much about what’s going on here.

So how does isolinux know where to find isolinux.cfg?

The file txt/syslinux.cfg.txt in the syslinux source directory explains the answer to this:

*ISOLINUX* (before 4.02) used the configuration filename of
isolinux.cfg, searching  /boot/isolinux (starting 2.00), then /isolinux
and /.  As of 4.02, *ISOLINUX* will search for isolinux.cfg then
syslinux.cfg in /boot/isolinux before searching for the same files in
/isolinux, /boot/syslinux, /syslinux, and /.

So, basically, I got lucky that it searches for it in the root, since I didn’t read those docs ahead of time.

Conclusion

Looks like the process of making a bootable Linux ISO is pretty straight-forward. You take the isolinux.bin and make an isolinux.cfg, put them in the right place, and tell mkisofs where to find them. Awesome! Now I’m going to go back to figuring out why the ISO I’m making using buildroot isn’t booting right.

Comments