How to produce a minimal BIOS hello world boot sector with GCC that works from a USB stick on real hardware?

As mentioned by @Jester, I had to zero DS with:

@@ -4,2 +4,4 @@ _start:
     cli
+    xor %ax, %ax
+    mov %ax, %ds
     mov $msg, %si

Note that it is not possible to mov immediates to ds: we must pass through ax: 8086- why can’t we move an immediate data into segment register?

So the root of the problem was difference between QEMU’s initial state and that of the real hardware.

I am now adding the following 16-bit initialization code to all my bootloaders to guarantee a cleaner initial state. Not all of those are mandatory as mentioned by Michael Petch on the comments.

 .code16
cli
/* This sets %cs to 0. TODO Is that really needed? */
ljmp $0, $1f
1:
xor %ax, %ax
/* We must zero %ds for any data access. */
mov %ax, %ds
/* The other segments are not mandatory. TODO source */
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
/*
TODO What to move into BP and SP? https://stackoverflow.com/questions/10598802/which-value-should-be-used-for-sp-for-booting-process
Setting BP does not seem mandatory for BIOS.
*/
mov %ax, %bp
/* Automatically disables interrupts until the end of the next instruction. */
mov %ax, %ss
/* We should set SP because BIOS calls may depend on that. TODO confirm. */
mov %bp, %sp

I have also found this closely related question: C Kernel – Works fine on VM but not actual computer?

The Intel Manual Volume 3 System Programming Guide – 325384-056US September 2015 9.10.2 “STARTUP.ASM Listing
” contains a large initialization example.

Leave a Comment