So many pixels, so few bytes
Remember back in 1990? No multitasking, DOS applications running in 16 bit mode with all privileges, setting VESA Mode X was two machine instructions, and putting a pixel on the screen was a single write to a memory location?
Remember 2016? Big, fat operating systems with big, fat drivers, no hardware control, and putting a pixel on the screen requires talking to a graphics server and lots of graphics API calls?
- Booted directly by GRUB
- Runs at least in 32 bit mode
- Activates a high-res graphics mode (at least 1024×768 and 32 bit depth)
- Puts at least one pixel on the screen
The answer is: 66 bytes.
This is the assembly code (GNU syntax):
This is the necessary GRUB2 configuration:
Build it with:
How it works
I’m totally cheating here, but that’s what we did back then too: You didn’t really set a graphics mode with just two machine instructions, the VESA BIOS did it for you. You didn’t initialize the hardware, DOS did it for you (to some degree).
The biggest space saver is GRUB2, because it supports the Multiboot. Multiboot will set up a lot of things for you if you provide a Multiboot header. I am using the following things:
- GRUB2 loads my binary, so no need for my own bootloader.
- GRUB2 sets up 32 bit protected mode, so no need to switch manually.
- GRUB2 sets up the VESA graphics mode (this is a little known feature) using the VESA BIOS Extensions and, if the right bits are set, hands over a linear framebuffer address. Writing pixel data to the framebuffer immediately puts pixels on the screen.
Let’s take a step-by-step tour through the code:
The Multiboot header is a data structure at the beginning of the binary. If GRUB2 sees the right
magic value, it will interpret the data structure. The
flags field needs to be set according to the specification, in my case e.g. I set bit 11 to tell GRUB that I want a graphics mode and the corresponding header fields have been set. Additional fields hold various important pointers, e.g. the start address of the actual machine code.
The most important fields for me are the four last ones. Graphics mode 0 requests a linear framebuffer instead of bank switching, width, height and depth are self-explanatory. Note that GRUB2 will only set the mode correctly if the vbe module is loaded, other graphics modules will interfere and the buffer is not set up.
This is the start of the actual machine code. The Multiboot specification will set up the environment so that the EBX register points to a special data structure with additional system information:
vbe_mode_info field is a pointer to VESA BIOS Extension data structure, which has another field that holds a pointer to the linear framebuffer allocated by GRUB2. So
mov 0x4c(%ebx),%eax dereferences right field in EBX pointer and
mov 0x28(%eax),%eax the second one, with the linear framebuffer address ending up in the EAX register.
This line draws a red pixel (bits 16 to 23 are the bits for the red color channel in 32 bit color depth) to the first entry of the linear framebuffer. And now to the most important part:
Nothing left to do, loop forever.
For the demo I wanted a bit more than just a single pixel, so the following piece of code fades from a full, black screen to a full, blue screen in a loop:
If you want to try it for yourself, download the pre-built ISO image. Run with: