Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебн пособ по арх и прогр МП.doc
Скачиваний:
64
Добавлен:
10.12.2013
Размер:
1.59 Mб
Скачать

Тема 4. Создание программы в ехе-формате

4.1. Программы на языке транслятораMasm.

4.1.1. Определение сегментов и данных.

Программы на языке ASM состоят из строк двух типов: директив (псевдооператоров) и команд (операторов). Директива - это управляющая информация для программы-транслятора. Команда - управляющая информация для МП. Строка может начинаться с произвольной позиции и имеет следующий формат:

Для директивы

<присвоенное программистом имя> <мнемокод> <параметры > <;комментарии программиста>

Для команд:

< метка: > <мнемокод команды> <адреса операндов> <;комментарии программиста>

Поля (части) команд и директив разделяются не менее чем одним пробелом Комментарий не используется транслятором и не включается в ехе-файл.

Примеры директив уже приводились в п.2.1. методпособия (см. директивы DB и DW).

Ассемблерные программы для ехе-формата состоят, по крайней мере, из двух сегментов: кода и стека. Если операндов мало, и они являются константами, то их численные значения можно включать в команды.

Например, так:

В этом случае программа может не иметь сегмента данных.

Большинство программ содержит также сегмент данных, а некоторые - кроме трех перечисленных сегментов - и дополнительный сегмент данных.

Все сегменты определяются программистом с помощью двух директив языка ассемблер: SEGMENT и ENDS .Директива SEGMENT определяет начало, а директива ENDS конец сегмента. Директивы включаются в текст программы в такой последовательности:

<имя> SEGMENT [параметры]

<здесь указывается содержимое сегмента с помощью директив DB (DW)>

<имя> ENDS

Например, границы сегмента с именем DATS указываются так:

DATS SEGMENT

DATS ENDS

Имя сегмента должно обязательно присутствовать, быть уникальным и соответствовать правилам для имен в ассемблере, в том числе: нельзя начинать имя с цифры, использовать символы <.> и др. В качестве имен лучше использовать слова и сокращения, помогающие понять особенности или назначение именуемых объектов. Например, сегмент данных можно назвать DATS или _DATA или DAT. Обе директивы SEGMENT и ENDS должны иметь одинаковые имена. Директива SEGMENT может содержать три типа параметров, определяющих выравнивание, объединение и класс. Рассмотрим эти параметры, хотя большинство из них принимается по умолчанию (см. дальше).

1. Выравнивание. Данный параметр определяет границу начала сегмента. Обычным значением является PARA, по которому сегмент устанавливается на границу параграфа, т.е. блока ячеек в 16 байт. В этом случае начальный адрес делится на 16 без остатка, т.е. имеет адрес nnnn0h. Но при объединении сегментов STACK или PUBLIC может образоваться зазор между этими сегментами, что будет потерей памяти. Поэтому есть средство устранить такой зазор. Этим средством является параметр "выравнивание" директивы SEGMENT: он указывает, с какого адреса должен начинаться сегмент. Возможны следующие значения:

  • BYTE – ближайший свободный адрес;

  • WORD– ближайший адрес, кратный 2;

  • PARA – ближайший адрес, кратный 16 (параграф);

  • PAGE- ближайший адрес, кратный 256 (страница).

В случае отсутствия этого операнда ассемблер принимает по умолчанию PARA.

2. Объединение. Этот элемент определяет, объединяется ли данный сегмент с другими сегментами в процессе компоновки после ассемблирования. Возможны следующие типы объединений: STACK, COMMON, PUBLIC, AT <выражение>, MEMORY. Сегмент стека определяется следующим образом:

имя SEGMENT PARA STACK

В случае отсутствия этого операнда ассемблер принимает по умолчанию PUBLIC.

  • Значение PUBLIC:

Как уже было сказано, сегменты одного класса располагаются рядом, но сохраняют свою независимость. Т.е. при обращении к ним нужно было каждый раз загружать соответствующими значениями сегментные регистры. Но при использовании значения PUBLIC все сегменты этого класса объединяются в один сегмент. Более точно правило объединения следующее: компоновщик объединяет в один сегмент такие сегменты (из любых модулей), у которых одно и то же имя, которые относятся к одному и тому же классу и в директивах SEGMENT которых указан тип объединения PUBLIC.

  • Значение STACK

Если какие-то сегменты (из любых модулей) имеют одно и то же имя, относятся к одному и тому же классу и для каждого из них указан тип объединения STACK, то эти сегменты объединяются в один сегмент, который рассматривается как сегмент стека, и именно на него будут установлены регистры SS и SP перед выполнением программы.

  • Значение AT <константное выражение>

Значение выражения трактуется как номер некоторого сегмента памяти, т.е. как абсолютный адрес сегмента без последних четырех битов. Такой программный сегмент ни с кем не объединяется и располагается в памяти по указанному адресу.

Пример:

VIDEO SEGMENT AT 0B800h

DW 25*80 DUP(?)

VIDEO ENDS

С помощью AT-сегментов обычно вводятся ассемблерные обозначения для фиксированных участков памяти (вектор прерываний, видеопамять). Чтобы не менять их содержимое, в состав AT-сегментов не должны входить предложения, порождающие машинный код, т.е. запрещены любые команды, директивы DB, DW, DD с операндами, отличными от ?, и т.п. Если подобные предложения все-таки имеются, тогда компоновщик блокирует запись в память их машинного кода.

  • Значение COMMON

Все сегменты, которые имеют одно и то же имя, относятся к одному и тому же классу и в директиве SEGMENT которых указан параметр COMMON, компоновщик располагает в памяти с одного и того же адреса (с адреса, по которому размещен первый из этих сегментов), накладывая их содержимое друг на друга.

Когда отдельно ассемблированные программы должны объединяться компоновщиком, то можно использовать типы: PUBLIC, COMMON и STACK. В случае, если программа не должна объединяться с другими программами, то данная опция может быть опущена.

3. Класс. Данный элемент, заключенный в апострофы, используется для группирования относительных сегментов при компоновке:

<имя> SEGMENT PARA STACK 'Stack'

В этом параметре в апострофах указывается некоторое имя которое рассматривается как имя класса, к которому относится сегмент. Дело в том, что в многомодульной программе компоновщик LINK располагает сегменты в следующем порядке: сначала сегменты из первого модуля, затем из второго, и т.д. Но иногда желательно разместить родственные сегменты из разных модулей рядом. И тогда, если какие-то сегменты отнесены к одному классу, компоновщик расположит их рядом. Надо отметить, что все сегменты, для которых не указан класс, считаются относящимися к одному и тому же классу с "пустым" именем.

После директивы SEGMENT необходимо указать директиву ASSUME, определяющую соответствие между сегментным регистром и именем сегмента. Директива ASSUME имеет вид:

ASSUME <имя_сегментного_регистра>:<имя_сегмента>.

Например, ASSUME SS:< имя_стека>. Эта директива указывает, что транслятор должен связывать имя сегмента стека с именем сегментного регистра SS. Можно использовать одну директиву ASSUME для нескольких сегментов, например, директива:

ASSUME SS:<имя_сегм_стека>, DS:<имя_сегм_данных>, CS:<имя_сегм_кода> указывает, что ассемблер должен ассоциировать имя сегмента стека с регистром SS, имя сегмента данных – с именем регистра DS, а имя сегмента кода – с именем регистра CS. Параметры в директиве ASSUME могут записываться в любой последовательности. Регистр ES также может присутствовать в числе операндов. Если программа не использует регистр ES, то его можно опустить или указать ES:NOTHING. Нужно отметить, что директива ASSUME не загружает в сегментные регистры начальные адреса сегментов. Этой директивой автор программы лишь обещает, что в программе будет сделана такая загрузка. Директива ASSUME можно размещать в любом месте программы, но обычно ее размещают в начале сегмента команд. Каждая новая директива ASSUME для какого-то регистра отменяет все предыдущие определения.

Начальная загрузка сегментных регистров требуется, т.к. нужно, чтобы в сегментных регистрах находились правильные значения – номера соответствующих сегментов памяти. Но в начале выполнения программы в этих регистрах ничего нет. Поэтому выполнение надо начать с команд, которые загружают в сегментные регистры номера соответствующих сегментов памяти. Рассмотрим пример: пусть регистр DS надо установить на начало сегмента B. Однако, сделать это командой MOV DS, B нельзя. Поэтому пересылку надо делать через другой, несегментный регистр, например, AX:

MOV AX, B

MOV DS, AX ; DS:=B

Аналогично загружается и ES. А вот CS загружать не нужно, т.к. к началу выполнения программы этот регистр уже будет указывать на начало сегмента команд (это сделает операционная система, прежде чем передаст управление программе).

Что касается SS, то его можно загружать вручную (как ES и DS), или поручить операционной системе. В последнем случае в директиве SEGMENT, открывающей описание сегмента стека, надо указать специальный параметр STACK, например:

S SEGMENT STACK

Тогда загрузка S в регистр SS будет выполнена автоматически до начала выполнения программы.