Как создать минимальный загрузочный сектор 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


person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 10.09.2015    source источник
comment
Как минимум, вы должны обнулить DS.   -  person Jester    schedule 10.09.2015
comment
Помимо настройки DS, вы также должны явно настроить сегмент стека (SS), особенно если вы начинаете использовать подпрограммы BIOS. Вам также немного повезло, потому что QEMU и другие эмуляторы намного приятнее к вам, когда они вызывают ваш загрузочный код. Сегмент CS не обязательно должен быть равен 0, когда биос вызывает ваш загрузчик. segment:offset будет эквивалентен 0x0000:0x7c00, но CS может быть и другим значением, например 0x07c0 (0x07c0:0x0000). Чтобы обойти это, вы должны сделать дальний переход к метке в вашем коде с CS, установленным на 0x0000. На реальном оборудовании это может быть проблемой.   -  person Michael Petch    schedule 10.09.2015
comment
@MichaelPetch спасибо за дополнительные советы! Мне скорее не повезло, что QEMU не очень точно эмулирует мое железо :-) Я изучу сегменты регистров, о которых вы упомянули, более внимательно. Раньше я никогда их не понимал, потому что ОС скрывает их от меня.   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 10.09.2015
comment
Несколько месяцев назад я взял загрузчик, который написал около 25 лет назад, и обновил его, чтобы использовать синтаксис at&t и компилировать с помощью GNU Assembler для #throwbackthursday. Вы можете найти его здесь. Он использует CLI для отключения прерываний перед выполнением дальнего перехода, а после этого настраивает стек и регистры ES, DS. Ваш код - это синтаксис Intel, но вы должны получить общее представление о том, что я сделал. Этот загрузчик был разработан для запуска с дискеты (этот код был для дискеты 3.5 объемом 2,88 МБ)   -  person Michael Petch    schedule 10.09.2015
comment
К сожалению, ваш код уже использовал синтаксис at&t ;-).   -  person Michael Petch    schedule 10.09.2015
comment
@MichaelPetch спасибо, я посмотрю код! Все в порядке, на данный момент я уже могу читать оба :-)   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 10.09.2015
comment
@MichaelPetch Я думаю, что вы можете зависеть от CS на нуле, но довольно просто сделать так, чтобы ваш загрузочный сектор не заботился о том, какое значение CS используется.   -  person Ross Ridge    schedule 11.09.2015
comment
@RossRidge К сожалению, вы не можете полагаться на 0 это на реальном оборудовании. Некоторые старые BIOS любили использовать, в частности, 0x07C0:0000. это то же место в физической памяти, что и 0x0000:0x7C00. Выполняя дальний переход к локальной метке, вы гарантируете, что используете известную CS. Конечно, он не обязательно должен быть равен нулю, но независимо от источника, в который был написан ваш код, он должен соответствовать CS. В старые времена дальний переход к локальной метке был простым способом убедиться, что у нас есть ожидаемый CS, а не тот, который мог использовать BIOS. Пример кода, который я предоставил, выполняет простой метод far jmp.   -  person Michael Petch    schedule 11.09.2015
comment
@MichaelPetch На практике не имеет значения, какое значение CS имеет в загрузочном секторе. Поскольку почти прямые инструкции JMP и CALL используют относительные смещения, тривиально написать, что загрузочный сектор может загружаться с любым смещением в сегменте кода и при этом работать.   -  person Ross Ridge    schedule 11.09.2015
comment
@RossRidge Да, если вы пишете свой загрузочный сектор так, чтобы избежать абсолютных переходов и вызовов, вам нужно только убедиться, что ваш DS (и другие применимые регистры сегментов) правильно сопоставляются с любой используемой точкой происхождения.   -  person Michael Petch    schedule 11.09.2015


Ответы (1)


Как упоминал @Jester, мне пришлось обнулить DS с помощью:

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

Обратите внимание, что невозможно 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/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

Я также нашел этот тесно связанный вопрос: C Kernel - Хорошо работает на виртуальной машине, но не на реальном компьютере?

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

person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 10.09.2015
comment
Подсказка: чтобы обнулить регистры, используйте xor %reg,%reg. Это уменьшает длину машинного кода с трех до одного байта и работает быстрее на современных устройствах, совместимых с 8086. Просто используйте xor %ax,%ax вместо mov $0,%ax. - person fuz; 11.09.2015
comment
@FUZxxl спасибо! Я уже видел это в stackoverflow.com/questions/1135679/, но это не вошло в мою привычку :-) Исправлено. - person Ciro Santilli 新疆再教育营六四事件ۍ 11.09.2015
comment
Подпрограммы BIOS не требуют установки FS и GS. ES не нужно устанавливать, если только конкретная процедура BIOS не требует этого как часть передаваемого параметра. - person Michael Petch; 30.10.2015
comment
mov 0x0000, %bp кажется подозрительным. Это переместит 16-битное значение в DS;0000 в BP. Возможно, вы имели в виду mov $0x0000, %bp? (Конечно, вы можете использовать XOR для обнуления BP) - person Michael Petch; 30.10.2015
comment
@MichaelPetch спасибо за всю информацию! Это определенно должно было быть mov $0x0000, %bp, это то, что вы получаете от копирования и вставки из синтаксиса Intel :-) - person Ciro Santilli 新疆再教育营六四事件ۍ 30.10.2015
comment
Без проблем. Кроме того, поскольку ax уже равен нулю, вы можете просто переместить ax в bp. - person Michael Petch; 30.10.2015
comment
@MichaelPetch true, экономит один байт. - person Ciro Santilli 新疆再教育营六四事件ۍ 30.10.2015
comment
Что касается БП, то его можно использовать для чего угодно. Его не нужно инициализировать, если у вас нет причин его использовать. Вызовы BIOS не используют кадры стека, и не требуется, чтобы кадры стека использовались в вашем собственном коде, если вы не пытаетесь быть совместимым с каким-либо соглашением. БИОСу все равно. Если вызов BIOS требует BP в качестве параметра, в документации будет указано, что это требование указано, и его нужно установить только перед вызовом. SS:SP — это отдельная история. Подпрограммы и прерывания BIOS нуждаются в пригодном для использования SS:SP. - person Michael Petch; 31.10.2015
comment
Когда я занимался программированием на ассемблере для DOS, я просто использовал BP в качестве регистра дополнительного значения для данных, к которым мне нужен был быстрый доступ. - person Brian Knoblauch; 05.11.2015
comment
Я предполагаю, что ваша ссылка на веб-архив на тот случай, если Intel переместит/удалит руководство? - person Michael Petch; 05.11.2015
comment
@MichaelPetch, да! И он также содержит исходный URL-адрес как подмножество URL-адреса. Вот как вы используете свой собственный скриншот: softwarerecs.stackexchange.com/questions/18651/ - person Ciro Santilli 新疆再教育营六四事件ۍ 05.11.2015
comment
У меня была точно такая же проблема, и я решил ее с помощью вашего решения: обнуление регистров DS и ES в начале моего кода загрузочного сектора: xor ax, ax и mov ds, ax и mov es, ax. - person user3405291; 30.11.2017