I have managed to produce a minimal boot sector that works with QEMU 2.0.0 Ubuntu 14.04:
.code16
.global _start
_start:
cli
mov $msg, %si
mov $0x
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.