16+
ComputerPrice
НА ГЛАВНУЮ СТАТЬИ НОВОСТИ О НАС




Яндекс цитирования


Версия для печати

Модуль поиска не установлен.

Вы и ваш компьютер (часть 2)

09.03.2009

Рустам Галеев

Сегментная адресация

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

Когда-то первые релейные вычислительные машины строились по подобному же принципу. Чтобы перепрограммировать этот "компьютер", инженеры были вынуждены вручную переставлять штырьки и переключатели. Жизнь инженеров облегчил фон Нейман, предложив записывать в память не только данные, промежуточные и окончательные результаты, но и саму программу. Теперь программу можно было написать один раз и потом исполнять, когда нужно, быстро загрузив в память и не тратя времени на настройку проводов. Появилась возможность автоматизировать дальше, т.е. выстраивать своего рода "цепочки программ", когда результаты одной программы после ее отработки передаются другой и т.д.

Но здесь возникает одна проблема. Если в программе есть ветвления, т.е. места, когда в зависимости от значения той или иной переменной необходимо переходить к тому или иному участку кода, команда перехода должна содержать адрес перехода. Например, если x<0, переходим к исполнению по адресу 120, иначе по адресу 150. Но это предполагает, что по адресам 120 и 150 находятся нужные нам команды, а не какие-то другие, а это, в свою очередь, требует, чтобы сама программа каждый раз загружалась в память, начиная с одного и того же адреса, допустим, начиная с адреса 0. Это означает также, что вы не можете использовать цепочечные вызовы - чтобы запустить новую программу, приходится записывать на какой-нибудь носитель результаты ее работы, затем загружать в память, начиная с нулевого адреса, другую программу, вводить в нее сохраненные результаты работы предыдущей и запускать - преимущества автоматизации сводятся на нет.

Выходом из этой проблемы явилась концепция сегментов. Было решено, что программа записывается в сегмент - определенную область памяти, рассматриваемую как целое, а команды переходов содержат не абсолютные адреса байтов памяти, а смещения относительно начала сегментов. При исполнении такой программы нужный адрес памяти находится сложением смещения с адресом начала сегмента, причем все это реализуется на аппаратном уровне и никаких дополнительных вычислений программисту проделывать не надо. Чтобы запустить программу, необходимо было только указать ее сегментный адрес, т.е. начиная с какого адреса программа размещена в памяти. Это значение хранилось в специально отведенном для этой цели регистре, который стали называть сегментным регистром.

Эта концепция оказалась довольно удобной, и она стала использоваться гораздо шире. Так, очень неудобно программировать, когда код и данные перемешаны, по ошибке управление может быть передано по адресу, в котором содержатся данные. Поэтому для данных стали отводить самостоятельный сегмент, который так и называется - сегмент данных, в противоположность кодовому сегменту. Всякого рода промежуточные результаты, вообще-то, тоже являются данными, но оказалось, что их удобнее хранить в особой форме, называемой стеком, а для самого стека также выделили отдельный сегмент - сегмент стека. Так появились классические три сегмента - кода, данных и стека, их начальные адреса для исполняющейся в настоящий момент времени программы хранятся соответственно в регистрах CS (Code Segment), DS (Data Segment) и SS (Stack Segment).

При перемещении данных из одной области памяти в другую часто используется еще один (дополнительный) сегмент данных - ES. Текущие адреса для байта или слова данных определяются обычно сочетанием двух регистров: сегментного регистра, содержащего адрес начала сегмента, и индексного регистра, содержащего текущее смещение от начала соответствующего сегмента. Для указания адреса "места отправки" часто используется пара регистров DS:SI, для адреса "места назначения" - ES:DI.

Итак, мы выяснили, что теперь нам нужно настроить соответствующие сегменты данных. "Местом назначения" является у нас видеопамять, поэтому сегментный регистр ES должен содержать адрес начала видеопамяти. Для текствого режима © 3 VGA видеопамять отображается, начинается с адреса b800h (шестнадцатиричное значение, о чем напоминает h в конце числа). "Местом отправки" является область, в которой находится записанный нами заранее текст. О том, как и куда мы его запишем, я скажу позднее; пока же предположим, что значение сегмента данных DS уже установлено, а в качестве смещения возьмем 120h (об этом тоже позже):

mov ax,0b800h ;(3)

mov es,ax ;(4)

mov si,120h ;(5)

Пояснения требует только, почему пересылка значения b800 осуществляется с помощью двух команд. Дело в том, что команды пересылки непосредственного значения в сегментный регистр ES не существует, поэтому приходится прибегать к помощи "мальчика на побегушках", в качестве которого выступает, как правило, регистр AX (вообще, это, пожалуй, наиболее часто использующийся регистр из всех).

Переходим непосредственно к пересылке данных:

mov ah,7 ;(6)

beg: lodsb ;(7)

and al,al ;(8)

jz end ;(9)

stosw ;(10)

jmp beg ;(11)

end: ; выход из цикла

Сначала пара слов о "новых лицах" - регистрах AH и AL. На самом деле, никакие они не новые, а два лица нашего старого знакомца - регистра AX (помните, что я говорил о нескольких именах?). Иногда приходится использовать не все 16 битов регистра, а лишь их часть, поэтому регистр был поделен пополам, и половинки получили собственные имена: старший байт назвали "AH" (Ax-High), младший байт - "AL" (Ax-Low). "Многоличье" регистра AX на этом не заканчивается: он сам, будучи 16-разрядным, является частью (младшим, словом) 32-разрядного регистра EAX. На этом, к счастью, разнообразие регистра завершается; но у других регистров общего назначения (EBX, ECX, EDX) аналогичная картина.

Перейдем к нашему коду. О шестой строке чуть позже; разбор начнем с седьмой строки. beg - это метка; в "настоящем" ассемблере можно так писать прямо в программе - компилятор впоследствии вычислит и подставит вместо ссылок на метки конкретные адреса. Нам же придется это делать вручную; здесь она приведена просто для удобства. В частности, код 11-й строки ссылается на эту метку; потом мы подставим в 11-й строке конкретное значение адреса.

Команда lodsb загружает в регистр AL один байт, взяв его по адресу DS:SI (т.е. сумме адреса сегмента, взятого из регистра DS, и смещения, взятого из регистра SI). Там находится первая буква нашего текста. После выполнения данной команды значение регистра SI увеличивается на единицу, и DS:SI, таким образом, указывает на следующий байт. Так сделано специально, чтобы удобнее было копировать последовательности байтов.

Получив байт, мы должны проверить, не является ли он сигналом конца текста. В качестве такового обычно используется 0; однако, это не цифра "0", поскольку в коде ASCII и ANSI цифре "0" соответствует шестнадцатиричное значение "30h", это "чистое" значение 0. Проверка осуществляется в строке (8): к содержимому регистра AL, содержащему наш байт, применяется логическая операция AND по отношению к этому же регистру. В результате мы получаем опять то же значение, но при этом, если AL=0 (конец текста), будет установлен признак нулевого результата (флаг нуля) специального флагового регистра. Значение этого флага проверяется следующей командой (jz): если 0, осуществляется выход из цикла - переход по адресу метки end (сюда нам потом также придется подставить значение адреса), если не 0, выполняется следующая команда (10 строка).

Команда stosw пересылает слово, т.е. 2 смежных байта, из регистра AX по адресу, указываемому регистрами ES:DI (помните, что у нас там? Правильно, видеопамять). Постойте-ка, можете воскликнуть вы, как же так, из текста мы взяли один байт, а в видеопамять посылаем два? Именно так. Вспомните, что после кода каждого символа в текстовом режиме в видеопамяти помещается байт-атрибут. Именно это осуществляет первая строка нашего кода - в старший байт регистра AX помещается 7 (означающий светло-серый цвет символов на черном фоне). Поскольку для всего текста мы используем один и тот же цвет фона и текста, нет смысла каждый раз записывать это значение в регистр AX; это значение сохраняется в старшем байте, поскольку мы записываем лишь в младший байт, т.е. AL. При выводе целого слова из AX выводится не только младший, но и старший байт.

И, наконец, строка (11) организует цикл: если ранее (в строке 9) не был осуществлен переход по адресу метки end, то теперь переход осуществляется снова на первую строку. И так до тех пор, пока среди вводимых символов не встретится 0, означающий конец наших данных.

Итак, мы вывели наш текст, что дальше? Неплохо бы зафиксировать его, хотя бы на время, чтобы все смогли убедиться, что мы это действительно сделали. (Как в спорте, да?) Обычно для этого дают компьютеру команду осуществить ввод данных с клавиатуры, причем вводимый символ значения не имеет. При этом выполнение программы приостанавливается до тех пор, пока действительно не будет введен какой-нибудь символ, т.е. не будет нажата какая-нибудь клавиша (вы наверняка помните эту знаменитую фразу: "Нажмите любую клавишу..."). Воспользуемся опять сервисами BIOS, на этот раз используем прерывание int 16h и передадим команду осуществить ввод, что выглядит следующим образом:

end: mov ax,0 ;(12)

int 16h ;(13)

Теперь нам осталось только сообщить операционной системе, что программа работу завершила и занимаемую ею память можно использовать для других целей. Воспользуемся на этот раз сервисом DOS, а именно - функцией 4ch прерывания 21h:

mov ah,4ch ;(14)

int 21h ;(15)

Кодовая часть нашей программы успешно завершена. Настало время подумать о том самом тексте, который нам предстоит выводить на экран. Здесь мы прибегнем к небольшой хитрости: не будем сразу записывать весь нужный текст, а поместим лишь в качестве первого символа пробел (шестнадцатиричное значение ASCII-кода этого символа равно 20h):

db 20h ;(16)

Вот и вся программа. Хитрость заключается в том, что к получившемуся файлу впоследствии мы сможем добавлять любой нужный нам текст. Но все по порядку. Введем написанную программу, подставляя по ходу дела вместо символических ссылок действительные смещения адреса. Учтите, что отладчик debug, с помощью которого мы будем вводить и ассемблировать нашу програму, воспринимает все вводимые числа как шестнадцатиричные, поэтому нет необходимости указывать 'h' после чисел или 0 перед числом, если оно начинается с букв a-f. Выводимые числа также имеют шестнадцатиричный формат. Регистр вводимых символов значения не имеет.

Итак: запустите сеанс MS-DOS. С помощью команды DOS cd перейдите заранее в тот каталог, в котором вы хотите сохранить написанную программу (сменив при необходимости диск). Например, у меня было так:

d:

cd \temp

Теперь набираем debug. Появляется черточка - приглашение отладчика. Сначала вводим команду:

a 100

Это означает, что мы собираемся вводить программу на ассемблере, начиная с относительного адреса (т.е. смещения) 100h. В ответ отладчик выдаст группы цифр, разделенные двоеточием. Это адрес команды, которую мы будем вводить, записанный в виде сегмент:смещение - но сегментая часть адреса у вас будет другой (у меня появилось значение 172Е:0100). Набираем программу (все команды мы уже разбирали):

172E:0100 mov ax,3

172Е:0103

Появляется новый адрес следующей вводимой команды. Обратите внимание, что смещение изменилось не на 1, а на 3 - 0103 - так как команда (mov ax,3) занимает три байта. Набираем дальше:

172Е:0103 int 10

172Е:0105 mov ax,b800

172Е:0108 mov es,ax

172Е:010А mov si,120

172Е:010D mov ah,7

172Е:010F

На секунду прервемся. Вспомним, что в этом месте у нас должна находится метка beg: - запишем ее относительный адрес (10F), чтобы подставить впоследствии вместо самой метки. Продолжаем:

172Е:010F lodsb

172Е:0110 and al,al

172Е:0112

А как быть здесь? Нам попалось то, что называется "ссылкой вперед" - в команде 'jz end' указана метка (end), которая будет определена позже. Как же узнать ее адрес? Нам не обойтись без того, что называется "вторым проходом". Т.е. пока можно вставить произвольное значение, но потом, после определения метки (узнав ее адрес), придется вернуться к этой команде и подставить настоящий адрес. В "настоящем" ассемблере всей этой канителью занимается компилятор, и нам не пришлось бы беспокоиться по этому поводу. В данном конкретном случае за вас побеспокоился ваш покорный слуга и вычислил этот адрес - он равен 117. Продолжим:

172Е:0112 jz 117

172Е:0114 stosw

172Е:0115 jmp 10f

172Е:0117

Понятно, откуда адрес 10F - это сохраненное нами ранее значение для метки beg. А вот следующей должна идти метка end: - можете убедиться, что ее относительный адрес действительно равен 117.

172Е:0117 mov ax,0

172Е:011A int 16

172Е:011C mov ah,4c

172Е:011E int 21

172Е:0120 db 20

172Е:0121

Программа набрана. Чтобы сообщить об этом отладчику, просто нажмите еще раз Enter. Теперь понятно, откуда взялось число 120, которое мы записали в регистр SI - это относительный адрес "начала" нашего текста (хотя у нас там пока всего лишь один пробел). Нам надо придумать название для нашего файла - можете воспользоваться своей фантазией, я же использовал простое имя prog.com. Строго говоря, для нашей последующей работы неважно, с каким расширением мы сохраним файл - но если вы хотите попробовать запустить именно его (а я полагаю, что все-таки вы хотите этого), то расширение должно быть .com. Набираем:

n prog.com

Однако, хотя наша программа и получила имя, она еще не записана на диск, как нам того нужно. Чтобы записать ее, надо явно указать отладчику, сколько именно байт содержится в нашей программе. Здесь вы можете посетовать на то, что отладчик "тупой" - ведь, когда мы нажимали 'Enter', ничего не вводя, было "понятно", что это конец программы. На самом деле это не совсем так. Нажатие 'Enter' без вводимого значения говорит лишь о том, что вы завершили ввод текущего фрагмента. После этого вы могли бы набрать, например, "a 240" и начать вводить новый фрагмент программы, начиная уже со смещения 240h.

Кстати, таким же образом вы можете исправлять "предварительные" адреса (можно называть их "адресами-заглушками") при ссылках вперед - в директиве отладчику 'a' вы указываете смещение команды, которую нужно исправить, и просто вводите новую команду. Например, в нашем случае такое можно было сделать для команды 'jz end' - набрав при "первом проходе" 'jz eee', при "втором проходе", узнав адрес метки 'end' (117h) и зная адрес данной команды (172Е:0112), вы могли набрать: 'a 112' и далее 'jz 117', и два раза нажать 'Enter'.

Вернемся к нашей программе и вычислим ее размер: 121 (смещение конца программы) - 100 (смещение начала программы) = 21 (при вычислениях не забудьте, что мы имеем дело с шестнадцатиричными числами). Для сообщения этого значения отладчику, надо поместить это значение в регистр СХ. Делается это следующим образом:

r cx

На экране появляется текущее значение регистра СХ и мигающее двоеточие в качестве приглашения для ввода нового значения. Набираем:

21

w

Последняя команда и выполняет собственно запись. После нее появляется сообщение:

Writing 00021 bytes

Вот и все. Набираем q, чтобы выйти из отладчика. Теперь, кому очень не терпится, можно прямо тут же, не выходя из окна DOS, набрать имя программы, как вы ее сохранили (расширение указывать не обязательно). И если вы все набрали правильно и не сделали ошибок (будем на это надеяться), то увидите, скорее всего, какой-то набор символов (вполне возможно, весьма причудливых) и мигающий курсор в самом начале экрана. Мы, однако, как будто ничего такого не записывали?

Все правильно, но вспомните, что сигналом конца текста у нас является 0, а ведь его в программу мы не помещали (и это было сделано умышленно)! Поэтому после запуска нашей программы она начинает копировать в видеопамять все, что находится после ее относительного адреса (смещения) 120, а там наш пробел, потом программа кончается и идет то, что называют "мусором" - случайные данные, оставшиеся в оперативной памяти от предыдущей работы. Этот мусор и выводится на экран, пока, опять-таки случайно, не встретится среди него 0. Поэтому эту программу, как она есть на данный момент, можно использовать для "изучения" мусора на вашем компьютере (ведь бывают люди, которые любят копаться на свалках, и это относится не только к археологам).

Однако, у самых дотошных читателей после всего этого может остаться неудовлетворенность. "А что такое с регистром DS, о котором нам обещали рассказать? - могут сказать они. - И почему нужно записывать программу, начиная со смещения 100h, а не 0? И почему расширение файла .com, а не .exe?" Тут нам придется сделать еще одно отступление, хотя скорее "технологическое", чем "лирическое", о форматах исполняемых файлов.

Кое-что об исполнении программ и исполняемых файлах.

Когда операционная система загружает исполняемые файлы для запуска в оперативную память (а это прямая и основная обязанность операционной системы), она предпринимает, помимо простого копирования файла в память, ряд дополнительных действий. Программе может потребоваться передать некоторые системные сведения, в частности, запуск программы может сопровождаться передачей ей в командной строке некоторых параметров и т.п. Для размещения всей этой информации, а также использования в качестве буфера передачи данных между программой и другими устройствами операционная система создает перед каждой загружаемой в память программой так называемый префикс программного сегмента размером в 256 байтов (100h), содержащий все нужные сведения.

Теперь вспомните, что в программах содержатся по крайней мере 3 различных сегмента - кодовый, данных и стека, а в соответствующие сегментные регистры необходимо загрузить начальные адреса этих сегментов. В исполняемом же файле все эти сегменты находятся вместе, причем размеры сегментов у различных программ могут сильно различаться - понятно, что, чтобы разобраться со всем этим, приходится создавать дополнительную структуру данных со всеми необходимыми сведениями и записывать их в один файл с программой. Exe-файл действительно содержит такую структуру, которая называется заголовком exe-файла, минимальный размер его составляет 512 байтов (размер заголовка может быть различным в зависимости от сложности, структуры и размера exe-программы).

Представьте, что у нас несколько программ, подобных той, которую мы только что написали. Программа сама по себе занимает 33 байта (шестнадцатиричное 21), а заголовок - 512 байтов. Это было бы непростительным расточительством, поэтому придумали упрощенный формат исполняемых файлов, назвав их com-файлами ("командными"). "Упрощенный" формат, ввиду отсутствия загловка файла, предполагает выполнение определенных условий, в частности, предполагается, что при загрузке com-файла на исполнение в 4 сегментных регистра помещается адрес, с которого начинается префикс программного сегмента (размером 100h), а далее следует сама программа. Т.е. вместо нескольких сегментов используется фактически один, выступающий в роли и семента кода, и сегмента данных, и даже сегмента стека (правда, временные данные записываются в этот сегмент "с другого конца"). Таким образом, получается, что регистр DS содержит у нас тот же адрес, что и регистр CS - поэтому смещение 120h конца нашего кода было одновременно смещением начала наших данных (мы просто поместили все наши данные в конец программы, после кода).

С другой стороны, поскольку, в отличие от exe-файлов, регистр CS в com-файлах настраивается на начало не кодового сегмента, а префикса программного сегмента, мы должны учесть это - начало нашей программы будет сразу после этого префикса, т.е. начиная со смещения 100h. Отладчик debug имеет это в виду и при записи по команде "w" он записывает указанное в регистре СХ количество байтов, начиная со смещения 100h - следовательно, туда же следует записывать и программу.

По причине необходимости сложного заголовка для exe-файла, который автоматически создается компоновщиком при работе с компиляторами, а также наличия нескольких сегментов и сложной внутренней структуры, создавать exe-файл средствами отладчика debug крайне затруднительно (хотя теоретически, может быть, и возможно). Это может представлять лишь "спортивный интерес"; для написания же простых программ на ассемблере достаточно возможностей com-файлов.

Ну что ж, с отступлениями покончено, можно, наконец, переходить к содержательной стороне нашего дела - заняться выводимым на экран текстом. Поскольку мы "перехитрили" com-формат, поместив начало данных в конец файла, у нас появилась возможность неограниченно его расширять, и сейчас мы этим и займемся. Но сначала давайте все-таки создадим для наших данных "конец" - как вы знаете, это "чистый" 0. Хотя я и думал в начале, что удастся как-нибудь исхитриться и записать этот 0 в файл средствами "штатных" текстовых редакторов, у меня это не получилось (если кто знает, как это сделать, сообщите мне); поэтому пришлось создавать специальный вспомогательный файл, содержащий один этот 0, средствами все того же debug.

Работаем по той же схеме: запускаем debug, в качестве кода вводим одну строку 'db 0' (со смещения 100h - помните? Т.е. 'а 100'). Даем имя (например, 'n zero' - расширение можно вообще не указывать), в регистр СХ ('r cx') вводим '1' и сохраняем - 'w'. Выходим из отладчика ('q') - больше он уже не понадобится.

Осталось написать собственно текст. Для этого можно использовать любой стандартный редактор, например, встроенные редакторы FAR или Norton Commander. Можно использовать и MS Word, только сохранять файл надо в формате "Текст DOS". Годится и Блокнот Windows; но с ним нельзя набирать русский текст, т.к. он использует кодировку Windows, а не DOS. Создайте пустой файл с любым именем, расширение лучше использовать .txt, чтобы не запутаться, и наберите там, что захотите - хоть классическое "Hello, World!", хоть что-нибудь еще - можно и на русском языке. Единственное требование - нельзя в конце строки нажимать 'Enter' (собственно, даже это на самом деле можно, просто при прямом выводе в видеопамять это не даст ожидаемого эффекта - в конец строки просто будут добавлены символы перевода строки и возврата каретки в виде символов ASCII-кода).

Теперь приступим к "склеиванию" всего этого. Необходимо соединить вместе три наших файла: собственно программу, файл с текстом и вспомогательный файл с нулем. Для этого воспользуемся стандартной командой DOS copy: если передать этой команде в качестве параметров имена нескольких файлов, между которыми поставлены '+' (между '+' и именами файлов должны быть пробелы), и указать еще одно имя файла, то в него последовательно, один за другим, будут скопированы все указанные нами файлы. Сейчас должно быть понятно, почему не имели значения расширения наших "предварительных" файлов - они были лишь заготовками для получения окончательного. Но у нашего результирующего файла обязательно должно быть расширение .com.

Приступим. У меня файлы были такие:

copy prog.com + message.txt + zero hello.com

Получилось? Запускайте!

hello (впрочем, можете воспользоваться возможностями FAR или NC, или вообще запустить из Проводника Windows) - теперь все можно! - и хлопайте в ладоши: если не было ошибок, вы увидите свое послание.

Возможности нашей программы не ограничиваются выводом одной строки текста. Можно подготовить и красиво отформатированный текст - но всю работу по форматированию придется проделать нам самим. Чтобы "перейти" на новую строку, надо заполнить текущую строку до конца пробелами ("строки" содержат по 80 символов - именно такой видеорежим мы устанавливали). То же относится к отступам в начале строки и табуляции - "штатными средствами" стандартных редакторов воспользоваться не удастся. Не нужно также нажимать в конце строки (или даже абзаца) 'Enter' - это лишь добавит два лишних символа. Весь наш текст, с точки зрения текстового редактора, будет представлять собой одну длинную строку.

До сих пор наш текст выводился в стандартном для DOS виде - светлые символы на черном фоне, но это легко можно изменить. Для этого надо изменить байты-атрибуты, записываемые в видеопамять после кодов символов. Шестая строка кода нашей программы выглядела так: 'mov ah,7'. Чтобы изменить цвета фона или текста, нужно изменить значение, передаваемое в регистр AH.

Рассмотрим структуру байта атрибута. Биты этого байта нумеруются справа налево, начиная с 0, т.е. если записать его в двоичной форме, биты будут идти в следующем порядке: 76543210. Бит 7 указывает на мерцание символов: 0 - обычный текст, 1 - мигающий текст. Биты 4-6 указывают цвет фона, причем единичное значение бита указывает на наличие в цвете фона одного из трех цветов - синего, зеленого и красного - соответственно для битов 4, 5, 6. Комбинации различных битов дают промежуточные цвета, например, красный и зеленый дают коричневый цвет и т.д. Биты 2-0 аналогичным образом указывают наличие трех основных цветов в цвете символа; но для цвета символа имеется еще и так называемый бит яркости 3: если он установлен в 1, цвета символа будут яркими, если 0 - тусклыми. Например, установленные биты красного, зеленого и синего цветов при нулевом бите яркости дадут светло-серый цвет, а при установленном в единицу - ярко-белый.

Пример: байт-атрибут содержит значение 00000111. Это означает отсутствие мигания (нормальный текст), фон не содержит ни одной цветовой компоненты (черный), цвет текста содержит все компоненты цвета, но бит яркости сброшен - светло-серый цвет. Мы получаем комбинацию, использованную нами - светлый текст на черном фоне; и действительно, двоичное значение 00000111 равно десятичному (и шестнадцатиричному, в данном случае) 7. Напротив, байт-атрибут 01110000 (шестнадцатиричное 70h) означал бы черный текст на белом фоне. Давайте изменим нашу программу соответствующим образом.

Открываем окно DOS, переходим в тот каталог, где у нас находится наша программа. Далее набираем: 'debug <имя программы>' (у меня: 'debug prog.com'). В этом случае отладчик загрузит нашу программу, начиная с относительного адреса 100h. Проверим: 'u 100' - на экране появляется наша программа в кодах ассемблера. Мы видим, что команда 'mov ah,7' имеет смещение 010D, поэтому изменим одну эту команду:

a 10d

mov ah,70

'Enter'

Чтобы проверить, что все остальное не изменилось, снова набираем 'u 100' - как видите, действительно изменилась лишь одна команда. Запишем изменения: 'w' (поскольку мы указали файл программы при запуске отладчика, вводить команду 'n' с именем программы не нужно; также не нужно указывать число записываемых байтов - поскольку размер программы у нас не изменился, сохранится вся программа). Выходим из отладчика: 'q'. Теперь нужно склеить из новой программы и "текстового" и "нулевого" файлов новый com-файл. Впрочем, поскольку сама программа размещается у нас в самом начале файла, аналогичного результата можно было добиться, загрузив вместе с debug окончательный файл (у меня - hello.com) и исправив ту же команду.

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

Вы и ваш компьютер (часть 1)
Вы и ваш компьютер (часть 3)



статьи
статьи
 / 
новости
новости
 / 
контакты
контакты