Нет. DI подразумевает сегментный регистр ES (SI - DS, BX - DS, BP - SS), поэтому надо писать mov al, [ds:di]
KIV Ты выводишь не черед ту функцию. Через которую ты выводишь, нельзя задать цвета. Я решил чуть-чуть упростить задачу (на текущий момент времени). Пока не использую переборы по всей строке, лишь пытаюсь вывести один символ, но указанный по индексу. Вот делают так: Code (Text): use16 ; Используем 16-битный код org 0x7C00 ; Смещение ; -------------------------------------------------------------------------- ; Начало 16-битного кода ; -------------------------------------------------------------------------- code16: mov AX,[data16] mov DS,AX mov ES,AX mov FS,AX mov GS,AX mov SS,AX sti call InitScreen ; Инициализация экрана call ClearScreen ; Очищаем весь экран mov DL,4 mov DH,4 call SetCursorPos mov DS,word [text1] mov BL,00000100b call DrawText call LoopProcessor ; Подвешиваем процессор ; -------------------------------------------------------------------------- ; Подвешивание процессора LoopProcessor: jmp LoopProcessor ; Переходим на эту же инструкцию ret ; -------------------------------------------------------------------------- include "BIOS.asm" ; -------------------------------------------------------------------------- ; Начало 16-битных данных ; -------------------------------------------------------------------------- data16: text1 db "Hello my friend!",0x00 А вот теперь функция вывода строки: Code (Text): ; -------------------------------------------------------------------------- ; Вывод текста ; BL - атрибуты текста ; -------------------------------------------------------------------------- DrawText: mov AH,0x09 ; Код функции mov BH,0x00 ; Номер страницы mov CX,1 ; Количество повторений mov SI,0 mov AL,[DS:SI] int 0x10 ; Вызываем прерывание BIOS ret У меня выводится почему-то символ S. Хотя его нет в начале. А при изменении SI в 1, вообще не выводится символ, только чёрный фон на месте символа. Что я не так делаю-то?
это определённо не та инструкция. надо бы так: Code (Text): mov si, text1 кстати, вместо mov al, [ds:si] лучше бы написать lodsb. она делает тоже самое но занимает один байт. Поскольку у тебя и оперативки не много, да и загрузочный сектор мал оптимизация по размеру имеет смысл. А вот вариант моей функции с использованием вызова BIOS с заданием цвета: Code (Text): write_str: mov ah, 0x09 ; Номер вызова xor bh, bh ; Номер видео-страницы mov cx, 1 ; КОличество вывода каждого символа @@: lodsb ; Загрузим очередной символ test al, al ; Проверим не ноль ли это jz @f ; Если ноль, то выходим int 0x10 ; Выводим символ. При этом BIOS не изменит регистры с параметрами - проверенно jmp @b ; Переходим к выводу следующего символа @@: ret ; Возврат из процедуры Code (Text): mov SI,0 Твои данные начинаются с адреса 0x7C00,а в первом килобайте (0 - 0x3FF) располагается таблица прерываний, а ни как не твоя строка. UPD: Я понял, что вы хотели сделать командой mov ds, [text1]. Эта команда работает не правильно по трём причинам: 1) Значение сег. регистра умножается на 16, а потом к нему прибавляется смещение. Так что надо: mov ds, text1 / 16 2) Вам нужен адрес строки - text1, а не значение двух первых байт строки - [text1]. 3) Ваша строка может и не быть выровнена на 16 байт, поэтому адрес после деления будет неверным.
SII Это не ошибка. При необходимости можно биос настроить так, чтобы он переключался в режим 80*25, но если на компе установлена не текстовая ос, а графическая, то это сделали для подавления лишних мерцаний при переключении режимов. все конечно прекрасно и замечательно, но так вы загрузочный сектор никогда не напишите. вам попросту не хватит места. mov dl,4;2 bytes/mov dh, 4;2 bytes вместо mov dx, 0x0404;3 bytes Да и в загрузочном секторе мало кто работает с экраном. Если вы в нем включите текстовый режим, очистите экран и т.д. то это лишь приведет к тем самым лишним переключениям экрана, от которых пытается избавиться биос. P.S. Несколько советов 1) если будете писать загрузочный сектор то самое оптимальное делать так (offset'ы внутри команд где возможно станут однобайтными) Code (Text): org 0 ... cli xor bx, bx mov ss, bx mov sp, 0x7C00 mov bp, sp далее для адресации внетри 512 байт можно использовать регистр bp с сегментом ss add bp, 256;а благодаря такой фишке область в которой смещения будут генерироваться однообайтными увеличится вдвое, если не используете переменные в стеке jmp 0x07C0:@f;после этой настройки можно использовать имена переменных, они будут рассчитаны компилятьром правильно @@: push cs pop ds; теперь можно использовать bx для адресации в сегменте данных 2) для каких либо переменных необходимых при рассчетах удобно использовать стековую память. тем самым вы исключите большую часть ненужных прикидок, где бы расположить ту или иную переменную и уменьшите размеры команд обращения к ним 3) не стоит создавать много процедут. их оформление занимает много места. если код выделенный в процедуру используется в программе 1 раз, то его проще заинлайнить. это сэкономит как минимум 4 байта
KIV Ошибаетесь. DI подразумевает использование ES только при строковых операциях. Во всех остальных случаях используется сегмент данных. Кстати говоря, в строковых операциях префикс замены сегмента может подменить сегмент DS на любой другой, но сегмент ES он изменить не может.
Проверил - вы правы. Просто я где-то читал, что DI - ES. То ли автор ошибся, то ли я не так понял. Правда я на такие грабли бы не наступил всё равно - у меня почти всегда DS = ES.
KIV Ну, в ИА-32 полно всяких загибонов, и все их упомнить проблематично... Мне просто во время оно довольно много пришлось писать на асме для 8086, потому и запомнилось...
Спасибо всем! Много для меня полезного написали, однако у меня пока не получается. А чем вообще пользоваться удобнее: VMware, VirtualBox, VirtualPC, Bochs или QEMU?
Только два последних, насколько знаю, поддерживают отладку, первая троица предназначена не для создания новых ОС и прочих низкоуровневых программ, а для эксплуатации существующих.
Так, с текстом разобрался! Вот теперь решил параметры передавать через стек. Но что-то не получается. Пишу так: Code (Text): use16 org 0x7C00 code16: mov AX,CS mov DS,AX mov ES,AX mov FS,AX mov GS,AX mov AX,steck16 mov SS,AX mov SP,steckMiddle sti call InitScreen call ClearScreen push 0x0101 call SetCursorPos mov SI,text1 mov BL,00000100b call DrawText call LoopProcessor LoopProcessor: jmp LoopProcessor ret include "BIOS.asm" data16: text1 db "Hello my friend!",0x00 steck16: steckEnd db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 steckMiddle db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 steckStart db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 И потом вот так: Code (Text): SetCursorPos: mov AH,0x02 ; Код функции mov BH,0x00 ; Номер страницы pop DX int 0x10 ; Вызываем прерывание BIOS ret Эмулятор зацикливается, текст (всего один символ) не выводит. Понимаю что не так что-то со стеком сделал. Как правильно? Я просто внизу программы зарезервировал 64 байта для стека.
Потому что в стеке данные располагаются так: параметры адрес возврата то есть pop dx извлекает адрес возврата, а не параметр. Надо писать так: Code (Text): push bp mov bp, sp mov dx, [bp + 4] ... pop bp Однако это требует генерации кучи дополнительного кода. В начальном загрузчике лучше ограничится регистрами - сэкономите не один десяток байт. И вообще передача параметров через регистры оптимальнее, но требует думать о том, чтобы не затирать регистр до последнего использования параметра из него или запихивать его в стек, когда это неизбежно и восстанавливать позднее. Но в итоге программа получается намного меньше и быстрее. В качестве эмулятора рекомендую Bochs - в нём есть очень хороший отладчик. Чтобы получить представление о его возможностях взгляните хотя бы это:
Это я для Linux такое долго искал (пришлось собирать из исходников с особенными параметрами). В Windows достаточно написать в bxrc строчку: Code (Text): display_library: win32, options="gui_debug" И открывать файл bxrc с помощью bochsdbg.exe, а не просто bochs.exe (пункт в контекстном меню файла в проводнике "Debugger", если бокс поставил ассоциацию файлов, иначе - C:\Program Files\Bochs-2.4.5\bochsdbg.exe). Ещё могу посоветовать опцию magic_break: enabled=1. Заставляет отладчик останавливаться на инструкциях xchg bx, bx - удобно, когда не знаешь адрес где надо встать. Только файлы с этими двумя опциями обычный bochs не dbg не откроет - напишет неизвестная опция. И наконец эта фича появилась начиная с 2.4.2 - в предыдущих её нет.
Всё, сделал. Вот код файла main.asm: Code (Text): use16 org 0x7C00 code16: mov AX,CS mov DS,AX mov ES,AX mov FS,AX mov GS,AX mov SS,AX sti call InitScreen call ClearScreen mov SI,text1 mov BL,00000100b mov DH,1 mov DL,1 call DrawText mov SI,text2 mov BL,00011010b mov DH,3 mov DL,1 call DrawText call LoopProcessor LoopProcessor: jmp LoopProcessor ret include "BIOS.asm" data16: text1 db "Hello my friend!",0x00 text2 db "You in R-Mode.",0x00 А вот код файла BIOS.asm: Code (Text): ; Инициализация экрана InitScreen: mov AH,0x00 ; Код функции mov AL,0x03 ; Код режима (80x25x16) int 0x10 ; Вызываем прерывание BIOS ret ; Очистка экрана ClearScreen: mov AH,0x09 ; Код функции mov AL,' ' ; Выводимый символ mov BH,0x00 ; Номер страницы mov BL,00010000b ; Атрибуты mov CX,2000 ; Количество повторений int 0x10 ; Вызываем прерывание BIOS ret ; Установка положения курсора ; DH - номер строки ; DL - номер столбца SetCursorPos: mov AH,0x02 ; Код функции mov BH,0x00 ; Номер страницы int 0x10 ; Вызываем прерывание BIOS ret ; Вывод текста ; BL - атрибуты текста ; DH - номер строки ; DL - номер столбца DrawText: mov CX,1 ; Количество повторений DrawTextLoop: call SetCursorPos mov AH,0x09 ; Код функции mov BH,0x00 ; Номер страницы mov AL,byte [SI] cmp AL,0x00 jz DrawTextExit int 0x10 ; Вызываем прерывание BIOS inc SI inc DL jmp DrawTextLoop DrawTextExit: ret А вот результат работы: Большое всем спасибо! Буду признателен любым советам и комментариям. Я всё таки хочу написать свой мини-ассемблер на C++, который включал бы только то, что мне нужно, с понятным для меня (своим) синтаксисом. Как считаете, стоит ли это делать (не для глобального использования везде и всеми, а только для себя, для успокоения своей души)?
Не советую изобретать велосипед. Всё что только можно (очень мощный макроязык, поддержка практически всех инструкций процессора - даже самых новых) сделано в FASM. Тем кому не нравится немного отличающийся от Intel синтаксиса FASM могут пользоваться TASM, MASM, YASM, NSAM или для любителей AT&T синтаксиса - gas. Ничего нового вы не придумаете всё равно, а получиться лишь урезанный fasm или masm. Code (Text): mov FS,AX mov GS,AX Вы не используете эти регистры. Зачем их инициализировать? Этим вы теряете совместимость с 8086, хотя найти подобный комп сегодня сложновато... Code (Text): mov SS,AX А вот это делать совсем не надо, если не устанавливаете свой SP. Ведь при переходе на загрузчик стек был настроен, а вы вполне можете его испортить. Советы насчёт оптимизации: не mov al, [si]; inc si, а lodsb не cmp al, 0, а test al, al не mov bh, 0, а xor bh, bh. и так во всех подобных случаях (обнуление регистра) больше замечаний нет. Так вы планируете переключаться в PM/long mode?
нет. только править исходники. но если встала ассоциация, то очень даже удобно дважды кликнуть по файлу bxrc. Даже в Linux я так сделал.
KIV Вот я заменил строку: Code (Text): mov AL,byte [SI] На: Code (Text): lodsb И у меня текст стал выводится перепрыгивая через буквы (возможно через одну, я просто не разглядывал).
KIV Спасибо, сейчас сделаю. Да, у меня получилось, я правой кнопкой нажимаю и там есть Debugger. А если просто два раза нажать на файл, то открывается окно и там только завершение.