План выполнения ЛР2 Работу по выполнению ЛР содержит две последовательных операции: 1. Ввод, отладка и исследование программ (В текстах программ (Вариант 1, Вариант 2) в место фразы: «Наука умеет много гитик» конкретный исполнитель данной ЛР должен ввести (возможно с использованием латинского алфавита) свои Фамилию И. О. И номер группы); 2. Защита ЛР. Операция 1: Ввод, отладка и исследование программ. Каждому студенту необходимо ввести, отладить и исследовать с использованием отладчика оба варианта программ. Созданные в ходе ЛР файлы программ и текстовый файл с отчётом по исследованию программы с точки зрения работы оборудования ЭВМ необходимо сформировать в папке “Исполнение Операции2 У6” (см. проводник). Вариант 1: Программа с двумя сегментами. Программа в ЛР1 содержала лишь один сегмент, в котором располагались и команды, и данные. Такая конструкция программы вполне законна, но не очень наглядна. Кроме того, предусмотрев в программе лишь один сегмент, мы ограничили суммарный объем команд и данных величиной 64К. Разумнее разнести команды и данные по отдельным сегментам операция продемонстрирована в следующем примере 2.1. Пример 2.1. Программа с двумя сегментами. text segment 'code' ;(1)Начало сегмента команд assume assume CS:text, DS:data ;(2)Сегментный регистр CS будет ;указывать на сегмент команд, ;а регистр DS - на сегмент данных begin mov AX, data ;(З)Адрес сегмента данных загрузи mov DS,AX ;(4) сначала в AX, затем в DS mov АН,9 ; (5)Функция DOS вывода на экран mov DX,offset message ;(6)Адрес выводимого сообщения int 21h ;(7)Вызов DOS mov AX,4C00h ;(8)функция DOS завершения ;программы вместе с кодой 0 ;успешного завершения int 21h ;(9)Вызов DOS text ends ;(10)Конец сегмента команд data segment ;(11)Начало сегмента данных message db 'Наука умеет много гитик$' ;(12)Выводимый текст data ends ;(13)Конец сегмента данных end begin ;(14)Конец текста программы ;с указанием точки входа Приведенная программа отличается от примера 1.1 лишь несколькими деталями (хотя ее структура, можно сказать, с отличается радикально). Вслед за сегментом команд введен отдельный сегмент данных с произвольным именем data. Этот сегмент открывается в предложении 11 и закрывается в предложении 13. Изменилось предложение 2 в нём указано, что сегментный регистр CS будет указывать на сегмент команд text, а регистр DS - на сегмент данных data. Соответственно изменилось и предложений 3. Теперь в регистр DS загружается адрес сегмента данных data, а не сегмента команд text, как это было в примере 1.1. ЛР1. Оттранслировав, скомпоновав и выполнив программу примера 2.1, можно заметить, что результат работы программы в точности тот же, что и для односегментной программы. Действительно, введение отдельного сегмента данных повысило наглядность программы и дало возможность (которой мы пока не воспользовались) увеличить общий размер программы до 128 Кбайт (64 Кбайт команд и 64 Кбайт данных). Однако существо программы сохранилось. Теперь у нас есть две простых программы, одна с одним сегментом, а другая - с двумя. Запустите первую программу (пример 1.1 ЛР1) под управлением отладчика, выясните, чему равен сегментный адрес сегмента команд программы и определите, в какое место оперативной памяти загружена программа. Проследите за процессом настройки сегментного регистра DS. Обратите внимание на то, что перед началом выполнения программы содержимое регистров DS и ES оказывается в точности на10h меньше содержимого регистра CS. В дальнейшем этот важный факт получит свое объяснение. Запустите под управлением отладчика вторую программу (пример 2.1 ЛР2), изучите расположение сегментов программы в памяти, сопоставьте полученные результаты с размерами сегментов, полученными с помощью листинга трансляции. Вариант 2: Программа, работающая со стеком. В примерах 1.1(ЛР1) и 2.1(ЛР2) мы не заботились о стеке, поскольку на первый взгляд, нашей программе стек был не нужен. Однако на самом деле это не так. Стек автоматически используется системой в раде случаев, в частности, при переходе на подпрограммы и при выполнении команд прерывания int. И в том, и в другом случае процессор заносит в стек адрес возврата, чтобы после завершения выполнения подпрограммы или программы обработки прерывания можно было вернуться в ту точку вызывающей программы, откуда произошел переход. Поскольку в нашей программе есть две команды int 21h, система при выполнении программы дважды обращалась к стеку. Где же был стек программы, если мы его иным образом не создали? Чтобы разобраться в этом вопросе, изменим пример 1.1(ЛР1), введя, в него строки работы со стеком. Пример 2.2. Программа, работающая со стеком. text segment 'code' ;(1)Начало сегмента команд assume assume CS:text,DS:data ;(2)Сегментный регистр CS будет ;указывать на сегмент команд, ;а регистр DS - на сегмент данных begin mov AX,data ;(З)Адрес сегмента данных загрузи mov DS,AX ;(4) сначала в AX, затем в DS push DS ;(5) загрузим в стек содержимое DS pop ES ;(6) выгрузим его из стека в ES mov АН,9 ; (7)Функция DOS вывода на экран mov DX,offset message ;(8)Адрес выводимого сообщения int 21h ;(9)Вызов DOS mov AX,4C00h ;(10)функция DOS завершения ;программы вместе с кодой 0 ;успешного завершения int 21h ;(11)Вызов DOS text data message data end ends ;(12)Конец сегмента команд segment ;(13)Начало сегмента данных db 'Наука умеет много гитик$' ;(14)Выводимый текст ends ;(15)Конец сегмента данных begin ;(16)Конец текста программы ;с указанием точки входа В предложении 5 содержимое регистра DS сохраняется в стеке, а в следующем предложении выгружается из стека в регистр ES. После этой операции оба сегментных регистра, и DS, и ES, будут указывать на один и тот же сегмент данных. В нашей программе эти строки не имеют смысла, но вообще здесь продемонстрирован удобный прием переноса содержимого одного сегментного регистра в другой. Выше уже отмечалось, что в силу особенности архитектуры микропроцессора для ceгментных регистров действуют некоторые ограничения. Так, в сегментный регистр нельзя непосредственно загрузить адрес ceгмента; нельзя также перенести число из одного сегментного регистра в другой. При необходимости выполнить последнюю операцию, в качестве "перевалочного пункта" часто используют стек. Запустите под управлением отладчика программу 2.2. Посмотрите, чему равно содержимое регистров SS и SP. Вы увидите, что в SS находится тот же адрес памяти, что ив CS; отсюда можно сделать вывод, что сегменты команд и стека совпадают. Однако содержимое SP равно 0. Первая же команда PUSH уменьшит содержимое SP на 2, т.е. поместит в SP -2. Значит ли это, что стек будет расти, как ему и положено, вверх, но не внутри сегмента команд, а над ним, по адресам -2, -3, 4, -6 и т.д. относительно верхней границы сегмента команд. Оказывается, совсем нет. Если взять 16-разрядный двоичный счетчик, в котором записан 0, и послать в него два вычитающих импульсы, то после первого импульса в нем окажется число FFFFh, а после второго - FFFEh. IIpи желании мы можем рассматривать число FFFEh, как -2 (что и имеет место при работе со знаковыми числами, о которых (будет идти речь позже), однако процессор при вычислении адресов рассматривает содержимое регистров, как целые числа без знака, и число FFFEh оказывается эквивалентным не -2, а 65534. В результата первая же команда занесения данного в стек поместит это число не над сегментом команд, а в самый его конец, по адресу CS:FFFEh. При дальнейшем использовании стека его указатель будет смещаться в сторону меньших адресов, проходя значения FFFCh, FFFAh и т.д. Таким образом, если в программе отсутствует явное объявление стека, система сама создает стек по умолчанию в конце сегмента команд. Рассмотренное явление, когда при уменьшении адреса после адреса 0 у нас получился адрес FFFFh, т.е. от начала сегмента мы прыгнули сразу в его конец, носит название циклического возврата или оборачивания адреса. С этим явлением приходится сталкиваться довольно часто. Расположение стека в конце сегмента команд не приводит к каким-либо неприятностям, пока размер программы далек от граничной величины 64К. В этом случае начало сегмента команд занимают коды команд, а конец - стек. Если, однако, размер программы приближается к 64К, то для стека остается все меньше места. При интенсивном использовании стека в программе может получиться, что по мере занесения а стек новых данных, стек дорастет до последних команд сегмента команд и начнет затирать эти команды. Очевидно, что этого нельзя допускать. В то же время система не проверяет, что происходит со стеком и никак не реагирует на затирание команд или данных. Таким образом, оценка размеров собственно программы, данных и стека является важным этапом разработки программы. Современные программы часто имеют значительна размеры (даже не помещаясь в один сегмент команд), а стек иногда используется для хранения больших по объему массив»в данных. Поэтому целесообразно ввести в программу отдельный сегмент стека, определив его размер, всходя из требований конкретной программы. Эти и сделано в следующем примере. Выполняя программу 2.2 по шагам, пронаблюдайте, как команды push и pop изменяют cодержимое регистров SP и ES. Выведите на экран дамп памяти начиная с адреса SS:FFFOh. Убедитесь, что содержимое DS действительно записано в память по адресу SS:FFFEh, и так и осталось там после извлечения содержимого стека и восстановления его указателя. Что еще имеется в нашей двyxceгмeнтнoй программе, кроме сегментов команд и данных? При загрузке программы в память она будет выглядеть так, как это показано на рис. 2.1 Образ программы в памяти начинается с очень важной структуры данных, которую мы будем называть префиксом программы. В оригинальной литературе эта структура носит не очень удачное название Program Segment Prefics (или сокращенно PSP), т.е. "префикс программного сегмета". PSP образуется и заполняется системой в процессе загрузки программы в память; он всегда имеет размер 256 байт и одержит поля данных, используемые системой (а часто и самой программой) в процессе выполнения программы. К составу полей PSP мы еще не раз будем возвращаться в дальнейшем. Вслед за PSP располагаются сегменты программы. Поскольку объявления сегментов сделаны нами наипростейшим образом (операторы segment не сопровождаются операндами-описателями) порядок размещения сегментов в памяти совпадает с порядком их объявления в программе, что упрощает исследование и отладку программы. Для большинства программ не имеет значения, в каком порядке вы будете объявлять сегменты, хотя встречаются программы, для которых порядок сегментов важен. Для таких программ предложения с операторами segment будут выглядеть сложнее. В процессе загрузки программы в память сегментные регистры автоматически инициализируются следующим образом: ES и DS указывают на начало PSP (что дает возможность, сохранив их содержимое, обращаться затем в программе к PSP), CS -на начало сегмента команд. SS, как мы экспериментально убедились, также в нашем случае указывает на начало сегмента команд. Как мы увидим позже, верхняя половина PSP занята важной для системы и самой программы информацией, а нижняя половина (128 байт) практически свободна. Поскольку после загрузки программы в память оба сегментных регистра данных указывают на PSP, сегмент данных программы оказывается не адресуемым. Не забывайте об этом! Если вы позабудете инициализировать регистр DS так, как это сделано в предложениях 3-4 нашей программе, вы не сможете обращаться к своим данным. При этом транслятор не выдаст никаких ошибок, но программа будет выполняться неправильно. Поставьте поучительный эксперимент: уберите из текста программы 2.2 строки инициализации регистра DS (проще всего не стирать эти строки, а поставить в их начале знак комментария - символ ";"). Оттранслируйте, скомпонуйте и выполните такой вариант программы. Ничего ужасного не произойдет, но на экран будет выведена какая-то ерунда. Возможно, в конце этой ерунды будет и строка "Наука умеет много гитик". Почему так получилось? Когда начинает выполняться функция DOS 09h, она предполагает, что полный двухсловный адрес выводимой на экран строки находится в регистрах DS:DX (в DS -сегментный адрес, в DX - относительный). У нас же сегментный регистр DS указывает на PSP. В результате на экран будет выводиться содержимое PSP, который заполнен адресами, кодами команд и другой числовой (а не символьной) информацией. Рассмотрим теперь программу с тремя сегментами: команд, данных и стека. Такая структура широко используется для относительно несложных программ. Пример 2.3. Программа с тремя сегментами. text segment 'code' ;(1)Начало сегмента команд assume CS:text,DS:data ;(2)Сегментный регистр CS будет ;указывать на сегмент команд, ;а регистр DS - на сегмент данных begin: mov AX,data ;(3)Адрес сегмента данных загрузим mov DS,AX ;(4)сначала в АХ, затем в DS push DS ;(5)3агрузим в стек содержимое DS pop ES ;(6)Выгрузим его из стека в ES mov АН,9 ;(7 )Функция DOS вывода на экран mov DX,offset message ;(8)Адрес выводимого сообщения int 21h ;(9)Вызов DOS mov AX,4C00h ;(10)Функция DOS завершения ;программы с кодом 0 int 21h ;(11)Вызов DOS text ends ;(12)Конец сегмента команд data segment ;(13)Начало сегмента данных message db 'Наука умеет много гитик$' ;(14) Выводимый текст data ends ;(15)Конец сегмента данных stack segment stack 'stack' ;(16)Начало сегмента стека dw 128 dup (0) ;(17)Под стек зарезервировано 128 ; слов stack ends ;(18)Конец сегмента стека end begin ;(19)Конец текста программы ;с указанием точки входа В программе 2.3. вслед за сегментом данных объявлен еще один сегмент, которому мы дали имя stack. Так же, как и для других сегментов, сегмент стека можно назвать как угодно, в частности, дать ему естественное имя stack. Строка описания сегмента стека (предложение 16) должна содержать так называемый тип объединения, в данном случае описатель stack. Тип объединения указывает компоновщику, каким образом должны объединяться одноименные сегменты разных программных модулей. Тип объединения используется главным образом в тех случаях, когда отдельные части программы располагаются в разных исходных файлах (например, пишутся несколькими программистами) и объединяются на этапе компоновки. Хотя для одномодульных программ тип объединения обычно не имеет значения, для сегмента стека обязательно указание типа stack, поскольку в этом случае при загрузке программы выполняется автоматическая инициализация регистров SS (адресом начала сегмента стека) и SP (смещением конца сегмента стека). В предложении 16, объявляющем сегмент стека, имеется еще один описатель. Слово 'stack', стоящее в апострофах после оператора segment, указывает класс сегмента. Классы сегментов анализируются компоновщиком и используются им при компоновке загрузочного модуля: сегменты, принадлежащие одному классу, загружаются в память рядом. Для простых программ, входящих в единственный файл с исходным текстом и включающих по одному сегменту команд, данных и стека, указание класса не обязательно, однако для правильной работы компоновщиков и отладчиков желательно, а в некоторых случаях и необходимо указание классов сегментов: 'code' для сегмента команд и 'stack' для сегмента стека. Так, отладчик CodeView не сможет реализовать некоторые из своих режимов вывода на экран текста программы без указания класса 'code', а компоновщик TLINK не будет инициализировать сегмент стека, если не объявлен его класс 'stack'. В приведенном примере для стека зарезервировано 128 слов памяти, что более чем достаточно для несложной программы. Заметим, что получившаяся у нас программа является типичной и аналогичная структура будет использоваться в большинстве последующих примеров. Подготовьте программу 2.3 к выполнению. Запустите ее под управлением отладчика, изучите расположение сегментов программы в памяти, обратив особое внимание на содержимое регистров SS и SP. Убедитесь, что в программе образовался отдельный сегмент стека размером 100h байт (128 слов = 256 байт = 100h байт). Поинтересуйтесь, где сохраняется значение DS при выполнении предложения 5. Операция 2: Защита ЛР. Защита ЛР включает четыре этапа: 1. Предъявление и объяснение принципов работы программы (с точки зрения оборудования) в среде отладчика; Программы по Операции 1 должны быть сохранены в папке «Программы». 2. Предъявление отчёта по работе содержащихся в папках и распечатке краткого содержания (основных принципов, доступных для запоминания) результатов обучения, полученных в ходе выполнения ЛР в части Реферата; 3. Предъявление совокупность файлов, полученных по ходу ЛР. 4. Ответы на теоретические вопросы преподавателя по теме ЛР. См. папку.