In this section we take a closer look at the few interesting parts of the code itself. The sources are located in my git repository1 and so I will describe here only the interesting parts. The git repository starts at point that corresponds to version 5.01b from the Debian upstream. I would prefer to clone the original repository, but after I contacted the maintainer he informed me that the main devel repo is private and works on making it public are ongoing.
I tested results of my work on the qemu emulator with KVM enabled. I wanted to boot the binary with multiboot strategy so I had two options for this:
-kernel
and -append
options that can be used to provide the kernel in the multiboot1 or bzImage format and the kernel command line respectively.-cdrom
option of qemu.I decided to go with the second option for two reasons.
First, it is the same way the memtest will be booted on a real hardware, which is great because possible bugs will be discovered early.
And second, the memtest claims to be both multiboot and bzImage at the same time and I was not sure which one would be used by the qemu if provided to the -kernel
.
This is nothing that could not be easily discovered but there was no reason to use this anyway.
To make things easier, I already had the required script to build the image from our school hypervisor.
Assume that the memtest is already built, then we create the file bootable.iso
as following:
# file:grub.cfg
set default=0
set timeout=30
insmod all_video
insmod efi_gop
menuentry "memtest" {
multiboot /boot/memtest console=ttyS0
set gfxpayload=auto
boot
}
mkdir -p iso/boot
cp memtest iso/boot/memtest
cp grub.cfg iso/
grub-mkrescue -o bootable.iso iso/
The grub.cfg
file instructs the GRUB2 to load proper modules and then start the multiboot file with given command-line.
I also wanted to test in UEFI conditions from the beginning, so I used the OVMF2 firmware to boot the emulator.
The whole qemu commad line then looked like this:
qemu-system-x86_64 -cpu host --accel kvm -m 1G -bios /usr/share/ovmf/OVMF.fd -cdrom bootable.iso -serial stdio -s -display vnc=:1
The first issue I encountered was pretty early one.
The existing multiboot patches already added the code that stored the pointer to the multiboot info structure as it was used to query the memory layout.
This seemed to work, but only by accident.
It turns out that the memory layout is on the beginning of the info structure, but then it overlapped the .bss
section and rest of the struct was zeroed out as part of the initialization.
To workaround this I just made it to skip the .bss
cleanup unconditionally.
/*
* Zero BSS
*/
/* The zeroing overwrites the multiboot structure and also multiboot loader
should clean the bss section for us */
jmp zerobss_done
cmpl $1, zerobss@GOTOFF(%ebx)
jnz zerobss_done
xorl %eax, %eax
leal _bss@GOTOFF(%ebx), %edi
leal _end@GOTOFF(%ebx), %ecx
subl %edi, %ecx
1: movl %eax, (%edi)
addl $4, %edi
subl $4, %ecx
jnz 1b
movl $0, zerobss@GOTOFF(%ebx)
zerobss_done:
This is certainly not a robust solution, as the C code expects that the .bss
will be cleaned up.
It works, but probably also only by accident.
I admit that after I investigated the memtests build system I wasn’t brave enough to dive in it. The linker script is pretty complex and I didn’t feel like I understand the purpose of everything it does and so I would probably break it.
Huge drawback of the build process is the fact that custom linker script is used very early and there is no mention about debug symbols sections in it. All my efforts to produce an intermediate binary that could at least be used with the great
addr2line
utility were doomed in the early stages. Even If I succeeded, the benefit of this would be very limited, because the memtest is linked as position independent executable and it even relocates itself in the memory. This sadly makes the whole debugging very unfriendly. I used qemu for testing so the best what I thought of was disassembling the memory around the instruction pointer and then look for the instruction sequence in the disassembled executable.
Later in the C code, the multiboot structure is examined for the valid framebuffer info and when found, the graphical output code will be initialized.
Luckily the memtest also provides an option that enables to print output on serial line.
This is achieved by passing console=ttyS0,115200
.
The ttyS0
is identifier of the first UART3 on the machine (at I/O port 0x3f8 and above) and the 115200
specifies the baudrate 4.
The baudrate is in fact not necessary in QEMU, but got used to always specify it because then it saves time when switching to a real hardware.
With the serial output enabled there is at least some reference information on how the output should look at certain moment.
The output itself was done via the cprint(int y, int x, const char *text)
function that accepts coordinates and text to be printed.
This function primarily prints to the text framebuffer located at 0xB8000
and optionally redirects its output to the serial line if configured.
I added another procedure call here that calls to my code and if this is initialized, prints character on the screen.
The font is provided in a file font_8x16.c
and dimensions of one glyph are (surprise!) 8x16 pixels.
The font is stored as array of bytes, each byte represents single row of pixels, with ones being active pixels an zeroes being inactive pixels.
Each 16 consecutive rows form a glyph.
Visual representation therefore begins at offset ASCII code of char * 16.
Below is representation of letter P
. Can you see it?
/* P */
0x00, /* ........ */
0x00, /* ........ */
0xfc, /* ******.. */
0xc6, /* **...**. */
0xc6, /* **...**. */
0xc6, /* **...**. */
0xc6, /* **...**. */
0xfc, /* ******.. */
0xc0, /* **...... */
0xc0, /* **...... */
0xc0, /* **...... */
0xc0, /* **...... */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
0x00, /* ........ */
Drawing a letter with this knowledge is matter of two nested for cycles.
When I sent the first working version to my colleague who gave me the inspiration for this project, the active pixels were printed white and background was black.
He pointed out that the text mode output always had a blue background. In my opinion black background is fine, but it was great opportunity to dive in the framebuffers colour format.
The framebuffer (or at least according to the multiboot) can have two colour modes. In the first one, there is provided a colour palette and one chooses colour by indexing to it. I don’t know exactly how that works, because the specification does not provide more details and in qemu is reported the second mode. My guess is that this option applies only to some legacy hardware, but I didn’t find more information on this.
The second mode is called direct RGB and it is what one would expect to see. There is a structure describing what bits in the pixel express amount of Red, Green and Blue. Interesting might be that qemu reports bpp 20 (which is 20 bits per pixel), but 20 doesn’t seem very multiple of three. There are either some unused bits, or the colors have different number of bits dedicated.
With my code I was able to mirror most of the console output to the QEMU emulated VGA.
The first image captures the output of GRUB2 bootloader.
On the left side is the output printed on serial console (qemu -serial stdio
) and the right window is output from the emulated VGA (qemu -display vnc=:1
).
The second image is finally output from the memtest86+ itself. Left side is again serial console output, right window is emulated VGA.
At this point, the patches provides working graphical output in qemu only. Real hardware has its own set of bugs and the debugging is harder because I do not own even the serial adapter. There are certainly ways of going around this, but this remains open for some future post.
In this project I implemented support for graphical framebuffers in the memtest86 binary. The work took me only about two days of work and it was my Christmas Holiday project for year 2021.
Although I had been involved in writing a little operating system from scratch, there was no support for graphical output. I therefore took this as an opportunity to put my hands on the graphical output.
I am also happy that I could experience the font rendering on my own so that now I much more appreciate this omnipresent piece of UI.