1. 40
    1. 17

      Isn’t the 24-bit truncation happening in this quoted portion of the code?

      add  word [gdt.dest], 0xfe00
      adc  byte [gdt.dest+2], 0
      

      To extend this to 32-bit sizes you’d need to add another line

      adc  byte [gdt.dest+5], 0
      

      See https://en.wikipedia.org/wiki/Segment_descriptor#/media/File:SegmentDescriptor.svg for why it is +5 and not +3 like you might otherwise expect.

      1. 7

        Oh, thank you very much for this suggestion!

        To verify, I removed a few instructions from the error function to make space in the MBR and added the adc command as you suggested, and this does indeed seem to fix the issue!

        I’ll try and get this properly submitted in the coming days.

        Edit: I updated the article to include your fix for the benefit of other interested readers: https://michael.stapelberg.ch/posts/2024-02-11-minimal-linux-bootloader-debugging-story/#update-a-fix — thanks again!

        1. 4

          Glad to help. This is one of those few times when understanding 16-bit 8086 assembly turns out to still have practical applications.

          When I was first reading the article and I got to the part where you showed the source for read_protected_mode_kernel, I thought that you had probably already tracked down the problem to somewhere within this block and were challenging the reader to try and spot the bug. When looking for a 24-bit limit, having add word followed by adc byte practically screams “look here!” so I thought I had likely found it. Then I was surprised when I read the rest of the article and there was no definitive answer for where the limit was coming from.

          It is always nice when the fix is a single line. And when you can get it correct on the first try. It is too bad there no extra space to insert this instruction without removing something else. You could maybe save a byte by using add byte [gdt.dest+1], 0xfe instead of add word [gdt.dest], 0xfe00 since everything is always 256-byte aligned? I’m sure there are other places to save a byte or two but nothing I can find right now while I’m on my phone.

          1. 1

            Yeah, my assembly skills are limited to reading and making educated guesses :)

            Unfortunately the extra adc instruction seems to take 5 bytes, so changing the error routine seems like the least-invasive change to me to get enough extra bytes :)

            Sebastian Plotz (the author of Minimal Linux Bootloader) also emailed me, confirming that your fix looks good to him as well, and saying he wants to publish a fixed version.

      2. 4

        Any reason you didn’t consider syslinux instead? It’s a lot lighter than GRUB, and fairly well supported for your use case (it’s common for CD boot images, and Alpine uses it for hard drive booting).

        1. 2

          Yeah, syslinux would be my next choice. I just am not very familiar with it, and, IIRC, could not easily figure out how to integrate syslinux for my case. I’m sure it can work, it just wasn’t trivial.

          1. 1

            I use Syslinux (with both FAT and Ext2 file-systems) exclusively for the last (perhaps?) 10+ years on everything, from laptop, to desktops, and USB-sticks. (Though with my latest desktop, booting in “legacy mode” doesn’t work with the built-in video card, thus I need to use UEFI…)

            However, getting back to Syslinux, in essence it’s very minimal and non-intrusive:

            • I always use the pre-built variant from kernel.org at https://mirrors.edge.kernel.org/pub/linux/utils/boot/syslinux/Testing/6.04/syslinux-6.04-pre1.tar.xz (it requires a 32bit Glibc library to be installed on the host); (this is needed only for initializing the partition;)
            • copy the first 440 bytes to the MBR or the partition; (Syslinux doesn’t care about MBR / GPT partitions, but I’ve seen many BIOS/UEFI implementations that when see a GPT partition, they asume UEFI only, and don’t even look at the MBR, even though in the BIOS it’s forced “legacy only”;)
            • copy a few modules in the syslinux folder on the target file-system; (in your case I think it’s enough to take linux.c32, libcom32.c32, libutil.c32 and ldlinux.c32;) (remove a few of them an see if any is not-needed; I also use menu.c32 and chain.c32 and don’t remember which depends on which;)
            • run the provided tool syslinux or extlinux that installs the stage2 loader in the pointed-to folder; (this file shouldn’t be moved;)
            • create a simple syslinux.cfg file where you state where the kernel and initrd lives, plus the arguments;

            If, as you describe in the article, you know they offset of each and every file in the file-system, then you don’t even need the Syslinux tooling, you only need to use their MBR code and their stage 2 file, lus the .c32 files I’ve mentioned.

        2. 4

          Fond memories of lilo printing just “LI”

          1. 3

            Very nice read :)

            For UEFI, there is systemd-boot, which comes as a single-file UEFI program, easy to include. That’s how gokrazy supports UEFI boot. Unfortunately, the PC Engines apu2c4 does not support UEFI, so I also needed an MBR solution.

            I think this is not entirely accurate, there’s official documentation on running coreboot + tioanocore on apus https://github.com/pcengines/apu2-documentation/blob/master/docs/tianocore_build.md I am using this myself without any complaints. Love(d) the APU platform, still sad that they are EOL https://pcengines.ch/eol.htm

            1. 2

              Thanks for the hint, I wasn’t aware!

              Maybe I’ll eventually upgrade my APUs to work with UEFI, but AFAIK other embedded boards (ODROID) that people run gokrazy on still only support MBR boot — it’s not just the APU for which we need an MBR bootloader.