Почему мой загрузчик неправильно загружает байт из памяти?

У меня есть следующая программа x86:

mov ah, 0x0e          ; Set up call to BIOS routine to print character

mov al, [character]   ; Stick the byte at label "character"
int 0x10              ; Display character in al

jmp $                 ; Loop forever

character:
db 0x41               ; Put the byte "A" at this position

times 510-($-$$) db 0 ; Pad with zeros and end with the magic number for a bootloader
db 0x55
db 0xaa

Я запускаю его двумя разными способами:

  • В чему
  • Запись на USB-накопитель с помощью dd и загрузка на старом 64-битном ноутбуке.

Я использую следующие команды для запуска этого кода:

$ nasm -f bin -o boot.bin main.s
$ qemu-system-x86_64 boot.bin  # to test
$ dd if=boot.bin of=/dev/sda # to put it on a USB stick

Код, как написано выше, не работает ни в том, ни в другом случае. На железе показывает мигающий курсор, а на qemu печатает кириллическую букву, а не А. Поэтому вторую (непустую) строку я меняю на

mov al, [0x7c00 + character]

добавление смещения 0x7c00 к метке, так как согласно некоторым источникам x86 помещает ваш загрузчик в 0x7c00 в памяти. Это работает, как и ожидалось, в qemu, но продолжает давать мне мигающий курсор на оборудовании. Обратите внимание, что это имеет тот же эффект, что и размещение [org 0x7c00] вверху, под которым я подразумеваю, что бинарные файлы, созданные с использованием указанной выше строки или путем добавления директивы org, идентичны (я сравнил их md5).

Чтобы убедиться, что на моем оборудовании нет странного набора символов, где 0x41 не является буквой A, я попробовал

mov al, 0x41

и это работает как на qemu, так и на оборудовании.

Как я могу правильно сослаться на данные, хранящиеся в символе, чтобы мой ноутбук нашел значение, которое должно быть там? Обратите внимание, что поскольку это загрузчик, процессор (если я правильно понимаю) находится в 16-битном реальном режиме.


person Jack M    schedule 25.07.2020    source источник
comment
Предположительно, потому что вы пропустили org и установили ds, поэтому ваш ассемблер не знает правильных смещений для абсолютных операндов памяти, и нет гарантированного правильного смещения.   -  person Peter Cordes    schedule 25.07.2020
comment
Пожалуйста, опубликуйте минимально воспроизводимый пример, а не просто фрагменты. Другие пользователи должны иметь возможность собирать и запускать ваш код, чтобы помочь вам.   -  person fuz    schedule 25.07.2020
comment
@fuz Это не фрагмент, это полный код. Или я должен опубликовать команды оболочки, которые я использую для его компиляции и запуска?   -  person Jack M    schedule 25.07.2020
comment
@PeterCordes Так как же это сделает настоящий загрузчик? Или реальный код просто не будет использовать этот трюк с сохранением констант в исходном коде? PDF I был следующим, делает такие вещи и, казалось, подразумевал, что код, загружаемый в 0x7c00, был своего рода стандартом x86.   -  person Jack M    schedule 25.07.2020
comment
@JackM Если это ваш полный код, вам не хватает директивы org, из-за чего ваши операнды памяти отправляются по неправильным адресам. Публикация команд, используемых для сборки и запуска кода, также не помешает.   -  person fuz    schedule 25.07.2020
comment
@fuz я редактировал в своих командах. Я пробовал директиву org раньше, и она дала те же результаты, что и моя попытка с явным добавлением 0x7c00. На самом деле, поскольку я только что отредактировал вопрос, использование org создает двоичный файл, идентичный байту за байтом, с моим явным добавлением без org.   -  person Jack M    schedule 25.07.2020
comment
Убедитесь, что для регистра DS установлено значение 0x0000, если вы используете org 0x7c00. Каждая ссылка на память, которая не включает регистр BP, по умолчанию имеет значение DS:. Если вы не установите DS, вы можете ссылаться не на нужную память. См. мои советы по загрузчику. Это может быть реальной проблемой на реальном оборудовании. Если вы используете USB с эмуляцией флоппи-дисковода (USB FDD), вам может понадобиться Блок параметров BIOS.   -  person Michael Petch    schedule 25.07.2020
comment
@JackM В любом случае используйте директиву org вместо добавления смещений вручную. Тем не менее, да, вам может потребоваться настроить регистры сегментов в вашем загрузчике.   -  person fuz    schedule 25.07.2020
comment
@MichaelPetch Большое спасибо! Обнуление DS сработало (кажется, нет необходимости в блоке параметров на моем оборудовании). На самом деле я пробовал это раньше, заметив префикс ds: в дизассемблированном двоичном файле, но NASM отказался собирать строку mov ds, 0, поэтому я подумал, что, должно быть, просто что-то неправильно понял. Благодаря некоторому коду, размещенному в одной из ваших ссылок, я понял, что вам нужно сделать mov ax, 0 и mov ds, ax. Вы можете опубликовать это как ответ, если хотите - если вы хотите немного дополнить его, я также хотел бы объяснить, почему вы не можете просто написать константу в DS.   -  person Jack M    schedule 25.07.2020
comment
Что касается того, что константа не загружается напрямую в регистр сегмента, то это просто ограничение архитектуры. Нет MOV от непосредственного значения в регистр сегмента: felixcloutier.com/x86/mov   -  person Michael Petch    schedule 25.07.2020
comment
Разве вы не должны помещать страницу отображения текста в bh? И сегмент данных выглядит таким же, как сегмент кода, поэтому mov ax, cs и mov ds, ax.   -  person Weather Vane    schedule 25.07.2020
comment
@WeatherVane Мне известно о проблеме с кодовой страницей, но, похоже, она работает нормально, не касаясь BX, по крайней мере, на моей машине. Не уверен, что вы имеете в виду о реестре CS. Является ли CS -> DS более переносимым, чем 0 -> DS, как я?   -  person Jack M    schedule 25.07.2020
comment
Конечно, это является. То, что вы сделали, «работает» только в том случае, если регистр cs оказывается 0, и нет никакой гарантии, что в противном случае не было бы смысла иметь регистры сегментов. И то, что иногда bh случайно оказывается равным 0, не означает, что вам не нужно его устанавливать. С такими предположениями ваш код будет очень хрупким.   -  person Weather Vane    schedule 25.07.2020
comment
CS не обязательно является конкретным значением (хотя наиболее распространенными значениями являются 0x07c0 и 0x0000). Вы не должны копировать его в DS. Установите регистры сегмента на нужное вам значение и не полагайтесь на то, что они являются конкретным значением.   -  person Michael Petch    schedule 25.07.2020


Ответы (2)


В x86 есть несколько сегментных регистров, содержащих смещения памяти. В реальном режиме (и других режимах?) эти регистры неявно добавляются к любой ссылке на память, которую вы делаете. Какой сегментный регистр используется, зависит от контекста (другими словами, в какой инструкции используется адрес). В нашем случае, когда мы пытаемся получить данные из памяти с помощью

mov al, [character]

процессор неявно добавит содержимое регистра ds (для сегмента данных) (умноженное на 16) к смещению памяти character. Обратите внимание, что это происходит во время выполнения, а не во время компиляции, поэтому вы не увидите этого в своем двоичном файле, если будете его дизассемблировать.

Решение состоит в том, чтобы обнулить ds в верхней части ассемблерной программы. Однако обратите внимание, что на самом деле вы не можете просто сказать mov ds, 0, потому что x86 не поддерживает запись констант в сегментные регистры - вам нужно пройти через другой регистр, как в

mov ax, 0
mov ds, ax

Для полноты это полностью обновленный код, который работает как на моем ноутбуке, так и на QEMU. Отличия от кода в вопросе прокомментированы ниже.

mov ax, 0  ; Zero out the data segment register
mov ds, ax ;

mov ah, 0x0e

mov al, [0x7c00 + character] ; Add 0x7c00 to the offset
                             ; As mentioned in the question, putting ORG 0x7C00 at the top of the file
                             ; also works (and is better, but this is clearer for demonstration purposes)
                             ; and in fact produces an identical binary to this explicit addition.
int 0x10

jmp $

character:
db 0x41

times 510-($-$$) db 0
db 0x55
db 0xaa

Ясно, что здесь происходит то, что регистр ds по умолчанию равен нулю в QEMU, но не на моем оборудовании. Настоящий загрузчик, написанный профессионалом, всегда будет явно обнулять такие вещи, а не предполагать, что BIOS переводит регистры в какое-либо конкретное состояние перед загрузкой своего кода.

Если вы читали Напишите «Простая операционная система — с нуля» Ника Бланделла, как и я, он на самом деле говорит об этом чуть позже в разделе 3.6.1 (Расширенный доступ к памяти с использованием сегментов). К сожалению, я застрял на этом за несколько страниц до этого и не стал читать дальше.

person Jack M    schedule 26.07.2020
comment
Очень хорошо объяснил +1. Мне бы очень хотелось, чтобы вы добавили параметр BH DisplayPage для вызова BIOS.Teletype. - person Sep Roland; 27.07.2020
comment
@SepRoland Правда, наверное, лучше всего установить это (полагаю, на ноль). Установка его на моей машине, похоже, не имела никакого эффекта (будь то ноль или какое-то другое значение). - person Jack M; 27.07.2020
comment
В x86 есть несколько сегментных регистров, содержащих смещения памяти. В реальном режиме (и других режимах?), в режиме реального/виртуального 86 регистры сегмента содержат значения, которые используются для непосредственного вычисления баз сегмента (согласно исходному названию, режим реального адреса). В противном случае регистры сегментов содержат селекторы, которые являются индексами в GDT или LDT, которые содержат дескрипторы. Загрузка значения селектора в регистр сегмента приводит к тому, что база, предел, тип и т. д. устанавливаются из дескриптора в соответствии с выбором селектора. - person ecm; 26.08.2020

Возможно, вы потеряли какие-то параметры и команду ORG.

Попробуй это

org 0x7c00            ; Tell NASM ,This program begin at Address 0x7c00

mov ah, 0x0e          ; Set up call to BIOS routine to print character

mov al, [character]   ; Stick the byte at label "character"

mov bh,0              ; The PAGE 0

mov bl,0xff           ; White

int 0x10              ; Display character in al

jmp $                 ; Loop forever

character:

db 0x41               ; Put the byte "A" at this position

times 510-($-$$) db 0 ; Pad with zeros and end with the magic number for  a bootloader

db 0x55

db 0xaa
person AlanCui    schedule 26.07.2020
comment
Если вы не используете команду org (org означает происхождение), NASM будет думать, что программа начинается с 0x000000 И.... Вы знаете, адрес персонажа будет изношен. СОВЕТ: 0x7c00 является стандартным адресом, и НЕ изменяйте его - person AlanCui; 26.07.2020