Как создать минимальный загрузочный сектор BIOS hello world с GCC, который работает с USB-накопителя на реальном оборудовании?

Мне удалось создать минимальный загрузочный сектор, который работает с QEMU 2.0.0 Ubuntu 14.04:

.code16
.global _start
_start:
    cli
    mov $msg, %si
    mov $0x0e, %ah
loop:
    lodsb
    or %al, %al
    jz halt
    int $0x10
    jmp loop
halt:
    hlt
msg:
    .asciz "hello world"
.org 510
.word 0xaa55

Скомпилировано с помощью

as -o main.o main.S
ld --oformat binary -o main.img -Ttext 0x7C00 main.o

Пример доступен для этого репо: https://github.com/cirosantilli/x86-bare-metal-examples/tree/2b79ac21df801fbf4619d009411be6b9cd10e6e0/no-ld-script

При:

qemu -hda main.img

он показывает hello world на экране эмулятора, как ожидалось.

Но если я пытаюсь записать на USB-порт:

sudo dd if=main.img of=/dev/sdb

затем подключите USB к ThinkPad T400 или T430, нажмите F12 и выберите USB, что я наблюдаю:

  • появляются некоторые загрузочные сообщения.
  • тогда экран будет пустым, только указатель подчеркивания вверху

Я также тестировал тот же USB-образ с Ubuntu 14.04, и он загрузился отлично, поэтому USB работает.

Как мне изменить этот пример, чтобы он загружался на аппаратном обеспечении и отображал сообщение hello world?

В чем разница между изображением Ubuntu и тем, что я создал?

Где это документировано?

Я загрузил вывод sudo dmidecode на T400: https://gist.github.com/cirosantilli/d47d35bacc9be588009f#file-lenovo-t400

Ответ 1

Как уже упоминалось в @Jester, мне пришлось 0 DS с:

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

Обратите внимание, что mov не может mov перейти к DS: мы должны пройти через ax: 8086- почему мы не можем перемещать немедленные данные в сегмент регистрация?

Таким образом, корень проблемы был разницей между начальным состоянием QEMU и реальным оборудованием.

Теперь я добавляю следующий 16-разрядный код инициализации ко всем моим загрузчикам, чтобы гарантировать более чистое начальное состояние. Не все из них являются обязательными, как упомянуто Майклом Петчем в комментариях.

 .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/info/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

Я также нашел этот близкий вопрос: C Ядро - отлично работает на VM, но не на самом компьютере?

Руководство по системному программированию Intel Manual Volume 3 - 325384-056US сентябрь 2015 г. 9.10.2 "STARTUP.ASM Listing" содержит большой пример инициализации.