План выполнения ЛР2

advertisement
План выполнения ЛР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.
Ответы на теоретические вопросы преподавателя по теме ЛР. См. папку.
Download