Программные сегменты. Директива ASSUME

Структура программы на ассемблере

Рассмотрим, как правильно оформлять последовательность команд, чтобы транслятор мог их обработать, а микропроцессор выполнить.

 

 

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

DT1 SEGMENT PARA PUBLIC ‘DATA’ ; сегмент данных с именем DT1

A DB 0

B DW ?

DT1 ENDS

CODE SEGMENT ; кодовый сегмент CODE

ASSUME CS:CODE, DS:DT1

MAIN PROC

MOV AX,DT1 ;инициализация сегмента

MOV DS,AX ;данных

MOV AX,A

...

MOV AX,4C00H ; выход из

INT 21H ; программы

MAIN ENDP ; конец процедуры MAIN

CODE ENDS ; конец сегмента кода

END MAIN ;конец программы

Важно отметить, что функциональное назначение сегмента несколько шире, чем просто разбиение программ на блоки кода, данных и стека. Сегментация является частью более общего механизма, связанного с концепцией модульного программирования, она предполагает унификацию оформления объектных модулей, создаваемых компилятором, в том числе с разных языков программирования. Это позволяет объединять программы, написанные на разных языках. Именно для реализации различных вариантов такого объединения и предназначены операнды в директиве SEGMENT:

SEGMENT <тип выравнивания><тип комбинирования><класс><тип размера сегмента>

Первый операнд в директиве – атрибут выравнивания сегмента (тип выравнивания) сообщает компановщику о том, что нужно обеспечить размещение начала сегмента на заданной границе. Допустимые значения атрибута:

BYTE – выравнивание не выполняется. Сегмент может начинаться с любого адреса.

WORD – сегмент начинается по адресу, кратному двум.

DWORD – сегмент начинается по адресу кратному четырем.

PARA - сегмент начинается по адресу, кратному 16. Принимается по умолчанию.

PAGE -сегмент начинается по адресу кратному 256.

NEMPAGE – сегмент начинается по адресу, кратному 4 Кбайт.

Следующий атрибут комбинирования сегментов(комбинаторный тип) сообщает компоновщику, как нужно комбинировать сегменты различных модулей, имеющих одно и то же имя. По умолчанию атрибут комбинирования принимает значение PRIVATE. Атрибуты комбинирования могут быть следующими:

PRIVATE – сегмент не будет объединяться с другими сегментами с тем же именем вне данного модуля.

PUBLIC – компоновщик соединит все сегменты с одинаковыми именами.

COMMON – располагает все сегменты с одним и тем же именем по одному адресу.

AT xxxx – располагает сегмент по абсолютному адресу параграфа. Абсолютный адрес параграфа задается выражением xxxx.

STACK – определение сегмента стека. Заставляет компоновщик соединять все одноименные сегменты и вычислять адреса этих сегментов относительно регистра SS.

Атрибут класса сегмента (тип класса) – это заключенная в кавычки строка, помогающая компоновщику определить соответствующий порядок следования сегментов при объединении программ.

Все ссылки на предложения одного программного сегмента ассемблер сегментирует по умолчанию по одному и тому же сегментному регистру. По какому именно - устанавливается специальной директивой ASSUME. В нашем примере эта директива определяет, что все ссылки на сегмент CODE должны, если явно не указан сегментный регистр, сегментироваться по регистру CS, все ссылки на DT1 - по регистру DS, а все ссылки на DT2 - по регистру ES.

Встретив в тексте программы ссылку на какое-либо имя (например, на имя C в команде MOV AX,A), ассемблер определяет, в каком программном сегменте оно описано (у нас - в DT2). Затем по информации из директивы ASSUME узнает, какой сегментный регистр поставлен в соответствие этому сегменту (у нас - это ES), и далее образует адресную пару из данного регистра и смещения имени (у нас - ES:0), которую и записывает в формируемую машинную команду.

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

Директива ASSUME должна быть указана перед первой командой программы. В директиве ASSUME следует каждому сегменту ставить в соответствие сегментный регистр.

 

3.2.2 Начальная загрузка сегментных регистров

 

Директива ASSUME сообщает ассемблеру о том, по каким регистрам он должен сегментировать имена из каких сегментов, и "обещает", что в этих регистрах будут находиться начальные адреса этих сегментов. Однако загрузку этих адресов в регистры сама директива не осуществляет.

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

Поскольку в ассемблере нет команды пересылки непосредственного операнда в сегментный регистр (а имя, т.е. начало, сегмента - это непосредственный операнд), то такую загрузку приходится делать через какой-то другой, несегментный, регистр (например, AX):

MOV AX,DT1 ;AX:=начало сегмента DT1

MOV DS,AX ;DS:=AX

Аналогично загружается и регистр ES.

Загружать регистр CS в начале программы не надо: он, как и счетчик команд IP, загружается операционной системой перед тем, как начинается выполнение программы (иначе нельзя было бы начать ее выполнение). Что же касается регистра SS, используемого для работы со стеком, то он может быть загружен так же, как и регистры DS и ES.

Это называется стандартными директивами сегментации.

 

3.2.3 Упрощенная директива сегментации

 

Для простых программ, содержащих по одному сегменту кода, данных и стека возможно использовать упрощенную директиву сегментации. Совместно с упрощенными директивами сегментации используется директива указания модели памяти MODEL, которая управляет размещением сегментов и выполняет функцию директивы ASSUME. Эта директива связывает сегменты с предопределенными именами.

Существуют модели памяти:

 

tiny и код и данные программы должны помещаться внутри

одного 64 Кбайтного сегмента. И код и данные -

ближние.

 

small код программы должен помещаться внутри одного

сегмента в 64 К, и данные должны помещаться внутри

другого 64 К сегмента. И код и данные - ближние.

 

medium код программы может быть больше 64 К, а данные

должны помещаться внутри 64 К сегмента. Код -

дальний, а данные - ближние.

 

compact программный код должен помещаться внутри 64 К сегмента, а данные могут быть больше 64 К. Код - ближний, а данные - дальние. Любой массив данных не может быть больше 64 К.

large и код и данные могут быть больше 64 К, но ни один массив данных не может быть больше 64 К. И код и данные - дальние.

huge и код и данные могут быть больше 64 К, и массив данных может быть больше 64 К. И код и данные -дальние. Указатели на элементы внутри массива - дальние.

Заметим, что с точки зрения ассемблера, large и huge идентичны. Модель huge не поддерживает автоматически массивы данных больше 64 К.

Немногие ассемблерные программы требуют более 64 К кода или данных, поэтому модель smALl оптимальна для большинства программ. Вы должны использовать модель smALl, где это возможно, т.к. дальний код (модели medium, large и huge) делают выполнение программы более медленным; дальние данные (модели compact, large и huge) значительно труднее обрабатывать на ассемблере.

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

Директива MODEL требуется, если Вы используете упрощенные директивы сегментации, иначе Турбо-Ассемблер не будет знать, как установить сегменты, определенные с .CODE и .DATA. MODEL должна предшествовать директивам .CODE, .DATA, .STACK.

Приведем заготовку программы, использующей упрощенные директивы сегментации:

Пример:

MASM ; режим работы TASM

MODEL SMALL ;модель памяти

.DATA ;сегмент данных

A DB 0

B DW ?

.STACK ;сегмент стека

DB 256 DUP(?)

.CODE ; сегмент кода

MAIN PROC

MOV AX,@DATA ; инициализация сегмента

MOV DS,AX ; данных

. . .

MOV AX,4C00H ;выход

INT 21H ;из программы

MAIN ENDP ;конец процедуры

ENDMAIN ;конец программы с точкой выхода main

 

 

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

.DATA? используется так же как .DATA, за исключением того, что она определяет, какая часть сегмента данных содержит неинициализированные данные. Она обычно используется в ассемблерных модулях, связанных с языком высокого уровня.

.FARDATA позволяет Вам определить дальний сегмент данных, т.е. сегмент данных, отличный от стандартного сегмента @data, используемого всеми модулями. .FARDATA позволяет ассемблерному модулю определить его собственный сегмент данных размером более 64 К. Если .FARDATA была использована, @fardata - это имя для дальнего сегмента данных, указанного этой директивой, так же как @data - это имя сегмента данных, указанного .DATA.

.FARDATA? во многом подобна .FARDATA за исключением того, что она определяет неинициализированный дальний сегмент. Как для .FARDATA и @fardata, если была задана директива .FARDATA?, @fardata? - это имя для дальнего сегмента данных, указанного этой директивой.

.CONST определяет, что часть сегмента данных содержит константные данные. Она используется, когда ассемблерный код связывается с языком высокого уровня.

Когда используются упрощенные директивы сегментации, доступны некоторые предопределенные метки. @FileName - это имя ассемблируемого файла. @curseg - это имя сегмента, который сейчас ассемблируется Турбо-Ассемблером. @CodeSIze - 0 для моделей памяти с ближними кодовыми сегментами (tiny, smALl и compact) и единица для моделей памяти с дальними кодовыми сегментами (medium, large и huge). Аналогично @DataSIze - 0 для моделей памяти с ближними сементами данных (tiny, smALl и medium), 1 в compact и large и 2 в huge модели.

 

4. Ассемблирование и компоновка программы.

 

Программа на ассемблере вводится с помощью текстового редактора. После ввода текста программы сохраните ее с расширением .ASM. Для выполнения программы, ее необходимо преобразовать в выполнимую форму. Это требует двух дополнительных шагов, ассемблирование и компановку.

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

Чтобы ассемблировать программу PROG.ASM наберите

 

TASM PROG

 

Если не указать другого имени файла, PROG.ASM будет ассемблироваться в PROG.OBJ. (Заметим, что не требуется набирать расширение имени файла; Турбо-Ассемблер добавляет .ASM сам). На экране появится

 

Turbo Assembler version 1.0 Copyright (C) 1988 by Borland

International, Inc.

 

Assembling file: prog.asm

Error messages: none

Warning messages: none

 

Если в программе содержатся ошибки, они появятся на экране вместе с номером строки, в которой найдена эта ошибка. После исправления ошибок отассемблируйте программу снова.

Если в результате ассемблирования не обнаружено ошибок, то следующий шаг – компоновка объектного модуля. Для компоновки программы используйте TLINK, набрав

 

TLINK PROG

 

Не нужно вводить расширение имени, TLINK добавит .OBJ сам. Когда компоновка завершится редактор автоматически создаст .EXE файл с тем же именем, что и имя объектного файла, если Вы не укажете другого. На экране появится сообщение:

 

Turbo Linker version 2.0 Copyright (C) 1987, 1988 by Borland

International Inc.

Для отладки программы можно использовать программу TD – турбо-debuger, которая обеспечивает пошаговую трассировку и просмотр значений регистров, стека и оперативной памяти.