Schemescape

Development log of a life-long coder

Project Euler in your BIOS

I'm still trying to write code in 100 different programming languages. See this repository for a summary table and links to code.

As always, none of this is groundbreaking, but it is fun to see my code running in places I hadn't considered before, like (this time) during the PC boot sequence or within a PostScript document.

Week 4

The calendar says this week isn't over yet, but I doubt I'll finish any more problems this week. Also, I'm compelled to share my first x86 assembly program.

After experiencing SectorLISP on day 2, I've been wanting to try my hand at developing for the (in)famous PC boot sector. Thus, I decided to solve Project Euler problem 26 using 16-bit ("real" mode) x86 assembly (with the BIOS API for text output).

x86 assembly

32-bit, user mode

My first stop was to learn about x86 assembly, and I used Assembly Nights on ratfactor.com to get started. By coincidence, like the Assembly Nights author, I was developing on an old netbook (because minimalism). I used NASM because it seemed popular, and I'm comfortable with "destination first" syntax.

I learned the bare minimum x86 assembly necessary to implement a solution in 32-bit ("protected" mode)--just mov, push, pop, cmp, call, ret, arithmetic, and conditional/unconditional jumps (along with Linux system calls for writing strings and terminating the process).

16-bit, "bare metal" (+ BIOS)

After that, it was just a simple* matter of porting the 32-bit code to 16-bit mode (removing e from register names and halving the word size). And then I got to experience the joy of learning about the PC BIOS.

The web is actually littered with examples of "bare metal" x86 programming (aside: does it count as "bare metal" if you're using the BIOS API?). Annoyingly, most of the examples I found didn't actually work for reasons I'll probably never understand. There are also many proclamations of required initialization rituals that I didn't (and still don't) understand. Some examples worked in QEMU but not in Hyper-V or Blinkenlights. Others inverted that sentence. And then there's real hardware, which is much more fickle.

Here's what actually worked for me (about which I'm 100% not confident):

Here's the code I used (in this case, I chose to have the stack grow down from where boot sector code must start):

    mov ax, 0
    mov ds, ax
    mov ss, ax
    mov sp, 0x7c00

Then you can set al to a character, ah to 0xe, and execute int 0x10 to output the character (and be moderately confident that it will actually appear).

The above worked for me on real hardware, QEMU, Blinkenlights, and v86.

*I will admit that the "simple" port to 16-bit actually took me a while because I forgot to update a line that implicitly depended on word size (add si, 4add si, 2).

Regardless, here's the final code.

You can run the code in your browser using v86 by clicking this link. Even better, download the assembled boot sector, copy the boot sector onto a USB stick (or floppy drive), and try it on real hardware!

Notes

I'll end with some notes I made while writing x86 assembly