Программирование 16-разрядных микропроцессоров на Ассемблере

Допустимые символы языка ассемблера состоят из прописных и строчных букв (латинских), цифр, специальных знаков +, -, *, /, =, (), [], ’, ’’, ., ;, @, &, ?, <, >, % и символов: перевод строки ПС (ОАН), возврат каретки ВК (ОDН), табуляции (О9Н). Любой другой символ воспринимается как пробел. Наименьшей конструкцией модуля является идентификатор – последовательность букв и цифр (не более 31), начинающийся с буквы. К зарезервированным именам относятся имена регистров, операций, псевдокоманд. Модуль представляет собой последовательность операторов языка, записанных в одной строке и заканчивающихся возвратом каретки и переводом строки. Если в первой позиции оператора стоит символ &, то оператор является продолжением предыдущего.

Операторы разделяются на командные и директивы. Команды порождают одну машинную команду. Директивы (псевдокоманды) содержат управляющую информацию для ассемблера. Оператор команды имеет вид:

{метка:} {префикс} {мнемоника} {операнд(ы)}; { комментарий}.

Значения метки являются текущим значением счетчика в данном сегменте кода, то есть, представляет собой адрес команды. Префикс позволяет формировать байты блокировки LOOK или повторения REP. Мнемоника идентифицирует тип генерируемой команды. В зависимости от функции команды может быть один операнд, два или ни одного. Более двух операндов указывается в макрокоманде. Комментарии поясняют смысл команды.

Директивы ассемблера имеют несколько другой формат:

{имя} директива {операнд(ы)} {; комментарий}

Имя директивы имеет другой смысл по сравнению с меткой и не заканчивается двоеточием. В ряде директив имя отсутствует.

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

MACROCODE имя {операнд(ы)}; {комментарии}

Переменная – это единица данных, имеющая имя. Она имеет три атрибута: сегмент, смещениеитип. Сегмент SEG определяет сегмент, содержащий переменную. СмещениеOFFSET, расстояние от начала сегмента до переменной, тип – число байтов переменной (1,2 или 4).

Метка, представляющая имя ячейки памяти, имеет атрибутами сегмент, смещение, расстояние. Константа отличается от переменной и метки тем, что она определяет только число. Символьные цепочки заключаются в апострофы и обычно имеют длину до 255 знаков.

Для определения и инициализации данных предназначены директивы:

DB - определить байт (Define Byte)

DW - определить слово (Define Word)

DD - определить двойное слово (Define Double Word).

Формат директивы:

имя DX <начальное значение>, [< начальное значение>]

Пример определения переменных:

Z1 DB 0ABH ; один байт, равный AB

Z2 DW 1000H ; одно слово, равное 1000

Z3 DD 1235H; 1000H ; младшее слово 1000, старшее 1235.

Для указания произвольного значения ячеек памяти используется символ ? (значение переменной не оговаривается, резервирование ячейки). Для распределения и инициализации нескольких ячеек памяти используется конструкция DUP:

DB 100 DUP(0) ; сто нулевых байтов (Dublicate)

DW 20 DUP(?) ; 20 слов без значения

ADR DD 10 DUP(ADR) ; десять полных адресов

DB 10 DUP(80DUP) ; десять слов, содержащих 80 пробелов

В директивах DW, DD допускаются символьные цепочки длиной 1 и 2 символа, например:

DW ’K1’; DD ’G’.

В выражениях, где запрашивается тип используемой информации, применяются операторы TYPE, LENGTH, SIZE. TYPE (тип) сообщает число байт, отведенных для переменной (1,2,4). LENGTH (длина) определяет число единиц памяти переменной (байт, слов, двойных слов). SIZE (размер) сообщает размер переменной в байтах, причем размер переменной равен длине, умноженной на тип.

В ассемблере вводится понятие логического сегмента, под которым понимается часть программы, которая может включать сегменты для машинного кода, данных и стека. Каждый логический сегмент должен начинаться с директивы SEGMENT и заканчиваться директивой ENDS. Логическому сегменту присваивается имя, данное программистом, и список параметров (атрибутов), которые не обязательны, но необходимы в случае программы, включающие несколько модулей.

Пример определения простого сегмента:

DATA SEGMENT [<список атрибутов>]

F1 DB ?

F2 DW ?

F3 DD ?

DATA ENDS

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

Пример:

MOV AX, DATA

MOV DS, AX

До использования стека необходимо инициализировать регистры SS и SP. В общем случае в программе первыми командами являются команды инициализации регистров DS, SS, SP. В языке ассемблера К1810 допускаются вложенные сегменты. Это означает, что если определен некоторый сегмент S1 (парой директив SEGMENT и ENDS), затем определяется сегмент S2, а потом в новой директиве SEGMENT снова появляется сегмент с именем S1, то содержащиеся в новом сегменте данные и (или) команды будут автоматически размещаться за операторами, которые находятся в старом сегменте с именем S2. Вложенный сегмент должен заканчиваться раньше внешнего сегмента.

Параметры директивы SEGMENT.

1. Выравнивание. Определяет границу начала сегмента. Обычное значение - PARA, по которому сегмент устанавливается на границу параграфа. В этом случае адрес кратен 16. При отсутствии этого операнда ассемблер по умолчанию принимает PARA; адрес сегмента ХХХ0. Бывает: PAGE=ХХ00; WORD=ХХХЕ (четная граница); BYTE=ХХХХ – любая шестнадцатеричная цифра.

2. Объединение. Определяет, объединяется ли данный сегмент с другими сегментами в процессе компоновки после ассемблирования. Возможны следующие типы объединений:

STACK, COMMON (общий), PUBLIC (общедоступный), AT- и MEMORY.

Все PUBLIC - сегменты, имеющие одинаковое имя или класс, загружаются компоновщиком в смежные области. Все такие сегменты имеют один общий базовый адрес.

Для сегментов COMMON с одинаковыми именами и классами компоновщик устанавливает один общий базовый адрес. При выполнении происходит наложение одного сегмента на другой. Размер общей области определяется самым длинным сегментом.

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

VIDEO_RAM SEGMENT AT 0B800h.

Для сегмента стека используется объединение STACK.

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

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

Типичными примерами являются классы ‘STACK’ и ‘CODE’. (См. П. Абель с. 55 и 396).

Директива ASSUME(присвоить). Необходимая ассемблеру информация о значении сегментных регистров сообщается в директиве ASSUME, которая имеет формат:

ASSUME <SR: базовое значение>, [<SR: базовое значение>]…

Директива ASSUME DS: data1 требуется для транслятора, указывая ему, что сегмент сопоставляется с регистром DS. Описание в директиве ASSUME соответствия сегментного регистра DS сегменту данных избавляет нас от необходимости указывать в каждой строке, содержащей ссылку на имя данных, в каком сегментных регистров находится сегментный адрес этих данных. По умолчанию будет подразумеваться регистр DS. Если регистр ES в директиве ASSUME не описан, его следует в явной форме указывать во всех строках программы, где выполняется адресация к дополнительному сегменту данных:

Mov BX, ES: mas[DI]

Однако, и в этом случае занесение в регистр ES требуемого сегментного адреса должно быть сделано в явном виде:

mov ax, data1

mov ex, ax

Поле SR содержит имя одного из сегментных регистров CS, DS, SS, ES, а базовое значение указывает на начало области памяти, адресуемой через этот регистр. Одним из наиболее часто используемых типов базового значения является имя сегмента, например

ASSUME DS: DATA

Такая директива сообщает ассемблеру, что к определенным в сегменте DATA переменным можно обратиться через сегментный регистр DS. Директива ASSUME необходима перед использованием сегментных регистров и перед каждой точкой в программе, в которой может модифицироваться содержимое сегментных регистров. Если какой-либо сегмент не будет использован в модуле, то директива будет иметь вид ASSUME NOTHING.

Директива ORG (origin - начало). Основной внутренней переменной ассемблера является счетчик адресов, который при ассемблировании выполняет функцию, аналогичную функции программного счетчика при выполнении программ. Этот счетчик сообщает ассемблеру адрес следующей ячейки памяти (имеется в виду смещение в сегменте), которая предназначена для размещения следующего байта команды или данных.

Первое появление директивы <имя> SEGMENT определяет начало сегмента с заданным именем. При этом организуется новый счетчик ячеек, в который загружается нулевое значение, так как первый байт в сегменте имеет нулевое смещение. При распределении каждого следующего байта производится инкремент счетчика ячеек.

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

Текущее значение счетчика адресов может быть принудительно изменено программистом с помощью директивы ORG <выражение>. При выполнении директивы ORG вычисляется значение выражение и полученный результат загружается в счетчик ячеек (адресов).

Ассемблирование следующих команд или элемента данных производится по полученному адресу (смещению в текущем сегменте), который изменяется в диапазоне 0-65535 (вычисление осуществляется по модулю 64К).

Директива EQU. Директива позволяет определить символические имена для часто используемых выражений. Формат директивы:

<имя> EQU <выражение>

Поля выражения может определять константы, адреса, регистры и даже мнемокоды макрокоманд. Именам целесообразно придавать содержательный смысл. Допустим, в программе необходимо многократно применять размер таблицы. Тогда его можно определить:

SIZE TABL EQU 100; 100 – размер таблицы.

Затем можно использовать имя SIZE TABL в любой команде, где требуется использовать этот размер. Такой прием улучшает понимание программы, а при расширении таблицы достаточно заменить в директиве EQU число 100 другим размером и произвести повторное ассемблирование программы. Ассемблер автоматически заменит каждое появление в новой команде SIZE TABL новым размером.

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

PURGE <имя>, [<имя>]…

Если какое-то имя удалено, то с помощью директивы PURGE, его можно переопределить. Пример использования директивы EQU:

CR EQU ODH ; численная константа

LF EQU OAH

BET EQU ALPHA [SI]+3 ; адресное выражение

COUNT EQU CX ; регистр

Имена, присвоенные директивами EQU, можно использовать в любых операторах исходного модуля, например:

MOV CL, LF ; загрузить в CL значение OA

INC COUNT ; инкремент содержания регистра CX,

; используемого в качестве счетчика.

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

Команда CALL содержит метку одной из команд процедуры. Метка в процедуре, которой передает управление команда CALL, называется точкой входа процедуры. Процедура выполняется до тех пор, пока не встретится команда возврата RET.

Процедура обычно разрабатывается как функциональный блок, который формирует набор выходных данных путем преобразования четко определенных входных данных, называемых параметрами. Поэтому суть действий с процедурами сводится к передаче им параметров и получении из них результатов. Для организации процедур в языке ассемблера предназначены директивы PROC и ENDP. Директива PROC отмечает точку входа процедуры, а директива ENDP – окончание процедуры.

Формат этих директив имеет вид:

[имя] PROC [тип]

.

. тело процедуры

.

[имя] ENDP

Имя представляет точку входа в процедуру. Тип процедуры (NEAR или FAR) (по умолчанию NEAR) используется ассемблером для определения вида генерируемой команды CALL для вызова процедуры. Если указан тип NEAR, то процедура находится в том же сегменте кода, в котором находятся все команды CALL, вызывающие эту процедуру. В этом случае ассемблер генерирует команду внутрисегментного вызова, то есть машинная команда CALL содержит только смещение точки входа.

Если указан тип FAR, процедура находится в сегменте, отличающимся от того сегмента кода, в котором находятся команды вызова CALL. То есть ассемблер должен генерировать длинную команду межсегментного вызова, которая содержит базу и смещение точки входа. При передаче управления в стеке приходится запоминать, а затем восстанавливать содержимое регистров CS и PC.

В соответствии с двумя форматами команд вызова необходимы и две разновидности команды возврата RET. Ассемблер определяет генерируемую разновидность команды RET на основе типа, указанного в директиве PROC.

Пример определения процедуры:

SRED PROC NEAR

MOV CX, AX; передать <AX> в <CX>

ADD CX, DX; прибавить <DX> к <CX>

RCR CX,1; сдвинуть <CX> вправо (разделить на 2)

RET

SRED ENDP

Эта процедура определяет среднее значение содержимого регистров DX и AX помещает его в CX. Для вызова процедуры используется команда CALL SRED.

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

Наиболее употребляемые директивы ассемблера МП 8086 приведены в табл.6.1.

Таблица 6.1 – Директивы ассемблера

Название (назначение) Запись/Пример Комментарии
Определение данных [имя] Dn операнд; [комментарий] X1 DB 25 Y2 DB ? Z DB 11, 12, 13 DD ? DT ? MON DB 01, ‘JAN’, 02, ‘FEB’, 03, ‘MAR’   Х1:=25 У2 не определен Z:=11; Z+1:=12; Z+2:=13  
Определение повторяющихся констант   [имя] Dn число повтор. UP(выражение) DW 10 DUP(?) DB 5 DUP(14) DB 3 DUP (4DUP(8))   10 неопредел. слов 5 байт=ОЕН 12 восьмерок
Задание сегмента   Завершение сегмента [имя] SEGMENT [параметры]   STACKSG SEGMENT PARA STACK ‘Stack’ DATASG SEGMENT PARA ‘Data’ CODESG SEGMENT PARA ‘Code’ . имя ENDS  
Процедура   Завершение процедуры [имя процедуры] PROC FAR или NEAR . .. RET Имя процедуры ENDP    
Присвоение ASSUME операнды ASSUME SS::имя_стека,CS::имя_сегмента, DS::имя_ сегмента, ES::имя_сегмента
Установка текущего значения IP ORG выражение ORG100H Программа размещается с адреса 100Н.  
Управление листингом PAGE кол. строк, кол. символов PAGE 60,132 TITLE текст (напр., имя программы) По умолчанию PAGE 66,80 Вверху каждого листа будет напечатан текст.
Присвоение имени програмы NAME имя  
       

Оформление и размещение в памяти ассемблерных программ

Тексты исполняемых программ могут быть представлены в ЕХЕ- или СОМ форматах. Различие между программами в ЕХЕ- и СОМ-файлах заключается в следующем.

Размер программы. Программа в ЕХЕ может иметь любой размер, в то время как СОМ-файл ограничен размером одного сегмента (≤64к). Размер СОМ-файла всегда меньше, чем размер соответствующего ЕХЕ-файла, так как в СОМ-файле отсутствует заголовок (512 байт) ЕХЕ-файла. Заголовок хранится на диске в начале ехе-файла. В заголовке содержится информация о размере выполняемого модуля, области загрузки в памяти, адрес стека и относительное смещение. В нем также указывается число байтов в последнем блоке ехе-файла, число настраиваемых параметров, количество параграфов в заголовке и некоторые другие данные.

Сегмент стека. В ЕХЕ следует задавать сегмент стека, в то время как СОМ-программа генерирует стек автоматически.

Сегмент данных. В ЕХЕ-программах обычно определяется сегмент данных, а регистр DS инициализируется адресом этого сегмента. В СОМ-программах все данные должны быть определены в сегменте кода.

Инициализация. В ЕХЕ-программе следует записать 0-слово в стек и инициализировать регистр DS. Так как в СОМ стек и сегмент данных не определены, то эти шаги отсутствуют.

При запуске СОМ-программы сегментные регистры содержат адрес префикса программного сегмента (256 байт=100Н), который резервируется DОS непосредственно перед СОМ- или ЕХЕ –программой в памяти. Так как адресация начинается со смещения 100Н от начала префикса, то в программе после директивы SEGMENT следует вносить директиву ORG 100H.

Для преобразования ЕХЕ-файла в СОМ-файл используется программа ЕХЕ2BIN.

Если в программе отсутствует явное объявление стека, то система сама создает стек по умолчанию в конце сегмента команд. Эот объясняется следующим образом. Если запустить программу, то можно увидеть (с помощью отладчика), что в SS находится тот же адрес, что и в CS, а в SP=0. Первая же команда PUSH уменьшает содержание SP на 2, то есть поместит в SP-2 которое равно FFFEh (0-2). При дальнейшей записи в стек его указатель будет смещаться в сторону меньших адресов. При интенсивном использовании стека в программе может получиться, что стек дорастет до последних команд сегмента команд и начнет затирать эти команды. В связи с этим следует использовать отдельный сегмент стека SS.

При размещение .ЕХЕ-программы в памяти регистры CS и SS указывает на начало сегмента команд (DOS делает автоматически). Поскольку сегмент данных оказывается не адресуемым, то необходимо инициализировать регистр DS (и ES).

begin: mov ax, data; data – начало сегмента данных

mov ds, ax

push DS

pop ES

.

mov ax, 4c00h; функция завершения программы

int 21h; вызов DOS

Префикс программного сегмента PSP: первые два байта префикса программного сегмента содержат команду INT20h завершение программы (DOS восстанавливает векторы критических прерываний по значениям, хранящимся в PSP; очищает буфер файлов и передает управление процедуре завершения). Затем указывается общий размер доступной памяти XXXXOh.

Программа в памяти начинается с префикса программного сегмента (PSP), который образуется и заполняется DOS в процессе загрузки программы в память. Затем в памяти располагаются сегменты в том порядке, как они объявлены в тексте программы (рисунок 5.11а).

В процессе загрузки в память сегментные регистры автоматически инициализируется следующим образом: ES и DS указывают на начало PSP. Поэтому перед началом выполнения программы нужно сохранить их значения, чтобы после окончания можно было обратиться к PSP. Потом размещается адрес подпрограммы завершения, а также адреса п/п реакции на Ctrl/Break, на фатальную ошибку.

В ячейках с 80 по FF располагается буфер передачи данных, который используется как буферная область ввода/вывода для текущего дисковода

  Программирование 16-разрядных микропроцессоров на Ассемблере - student2.ru  
а) б)
Рисунок 6.11 – Схема размещения программ ЕХЕ (а) и СОМ (б) в памяти  

Схема размещение СОМ-программы в памяти показано на рисунке 5.11б. Под команды, данные и стек выделяется только один сегмент. В начале сегмента размещается префикс программного сегмента PSP. Заполняет PSP операционная система DOS, но место под него в начале программы должен зарезервировать программист. В программе нет необходимости инициализировать регистр DS, поскольку его, как и другие сегментные регистры инициализирует ОС.

Пример 1 программы на ассемблере МП 8086.

NAME EXAMPLE

ASSUME CS: CODE, SS: STACK, DS: DATA

DATA SEGMENT

VAR-1 DW 0; определить и инициализировать

VAR-2 DW 0; две переменные

DATA ENDS

STACK SEGMENT

DW 10DUP(?); зарезервировать 10 слов

STK_TOP LABEL WORD; вершина стека

STACK ENDS

CODE SEGMENT

START: MOV AX, DATA; инициализация регистров DS и SS

MOV DS,AX

MOV AX, STACK

MOV SS, AX

MOV SP, OFSET STK_TOP

PROG: PUSH AX; заполнить (AX)

MOV AX, VAR-1; увеличить на 5 значение VAR-1

ADD AX,5

MOV VAR-2, AX; присвоить VAR-2

POP AX; восстановить AX

CODE ENDS

END START; конец исходного модуля

В первой строке программы находится директива NAME (наименовать), которая присваивает внутреннее имя объектному модулю, генерируемому ассемблером. Имя модуля EXAMPLE нельзя путать с именем файла – оно хранится внутри объектного файла.

Программа состоит из трех различных логических сегментов DATA, STACK, CODE. Каждый сегмент начинается с директивы SEGMENT и заканчивается директивой ENDS, причем обе директивы для одного и того же сегмента имеют одинаковые имена. Логические сегменты соответствуют физическим сегментам в памяти, но привязки логических сегментов к физическим адресам памяти в этом исходном модуле нет.

Директива ASSUME сообщает ассемблеру, что регистр CS будет содержать базовый адрес сегмента CODE, а регистр DS – базовый адрес сегмента DATA. Регистр ES не используется, поэтому указание о нем в директиве отсутствует.

Сегмент данных DATA содержит всего две переменные, которые инициализированы с помощью директив DW. В сегменте STACK резервируются 10 слов памяти, т.е. для стека в программе резервируется 10 слов (глубина стека 10 слов). Директива LABEL (отметить) определяет имя STK_TOP, которое идентифицирует вершину пока пустого стека.

Сегмент кода начинается с пяти команд, обеспечивающих инициализацию сегментных регистров и указателя стека SP. Выражение OFFSET STK_TOP представляет собой смещение метки STK_TOP от начала содержащего ее сегмента STACK. Собственно программа начинается с метки PROG. Директива END с операндом START сообщает ассемблеру о достижении конца исходного модуля и необходимости начать выполнение программы с команды, отмеченной меткой START.

Пример 2 ассемблерной программы:

Page 60, 132

TITLE EXAMPLE (EXE)

;--------------------------------------------

STACKSG SEGMENT PARA STACK ‘stack’; Указывать обязательно, т.к. по ней осуществляется. ; автомати ческая инициализация SS и SP

; Если тип объединения STACK, то при компоновке ; одноименные сегменты располагаются рядом, при ;COMMON – накладываются друг на друга

DW 10 DUP(?);

STACKSG ENDS

;--------------------------------------------------------------------------------------

DATASG SEGMENT PARA ‘Data’

NAME1 DB ‘ASSEMBLERS’;

NAME2 DB 10DUP(‘ ‘) ; зарезервировать 10 слов

DATASG ENDS

;--------------------------------------------------------------------------------

; --------------------------------------------------------------------------------------

CODESG SEGMENT PARA ‘Code’ ; для правильной работы LINK необходимо указать ‘Code’

BEGIN PROC FAR

ASSUME CS:CODESG; DS:DATASG, SS:STACKSG, ES:DATASG

; Показывает транслятору, какие регистры закрепляются за

; сег ментами, которые затем используются по умолчанию

PUSH DS ; записать DS в стек

SUB AX, AX ; записать 0 в стек

PUSH AX

MOV AX, DATASG ; регистры CS и SS инициализируются автоматически

MOV DS, AX

MOV ES, AX

; --------------------------------------------------------------------------------------------------------

MOV AX, 0123H

ADD AX, 0025H

MOV BX, AX

ADD BX, AX

SUB AX, AX

NOP

; ---------------------------------------------------------------------------------------------------------

CLD

LEA SI, NAME1

LEA DI, NAME2

MOV CX, 10

REP MOUSB ; переслать 10 байтов из NAME1 NAME2

; ----------------------------------------------------------------------------------------------------------

RET ; возврат в DOS

BEGIN ENDP

CODESG ENDS

END [BEGIN]; операнд может быть опущен, если эта программа должна быть скомпонована

;с другим (главным) модулем. Для обычных программ операнд содеожит имя, указанное в ;директивве PROG, (то есть в точку входа в главную процедуру). Этим адресом

; загружается IP.

Программа в формате ЕХЕ, созданная компоновщиком, состоит из следующих двух частей: 1) заголовка – записи, содержащей информацию по управлению и настройке программы, и 2) собственно загрузочного модуля.

В заголовке находится информация о размере выполняемого модуля, области загрузки в памяти, адресе стека и относительных смещениях, которые должны заполнить машинные адреса в соответствии с относительными 16-ричными позициями.

Заголовок имеет минимальный размер 512 байт и может быть больше, если программа содержит большое число настраиваемых элементов. Позиция 06 в заголовке указывает число элементов в выполняемом модуле, нуждающихся в настройке. Каждый элемент настройки в таблице, начинающейся в позиции 1С заголовка, состоит из двухбайтовых величин смещений и двухбайтовых сегментных значений.

Операционная система строит префикс программного сегмента (PSP) следом за резидентной частью COMMAND.COM, которая выполняет операцию загрузки. Затем COMMAND.COM осуществляет следующее:

§ Считывает форматированную часть заголовка в память;

§ Вычисляет размер выполнимого модуля (общий размер файла в позиции 04 минус размер заголовка в позиции 08) и загружает модуль в память с начала сегмента;

§ Считывает элементы таблицы настройки в рабочую область и прибавляет адрес начала сегмента;

§ Устанавливает в регистрах SS и SP значения из заголовка и прибавляет адрес начала сегмента;

§ Устанавливает в регистрах DS и ES сегментный адрес префикса программного сегмента;

§ Устанавливает в регистре CS адрес PSP и прибавляет величину смещения в заголовке (позиция 16) к регистру CS. Если сегмент кода непосредственно следует за PSP, то смещение в заголовке равно 256 (100h). Регистровая пара CS:IP содержит стартовый адрес в кодовом сегменте, т.е. начальный адрес программы.

После инициализации регистры CS и SS содержат правильные адреса сегментов, а регистр DS (и ES) должен быть настроен в программе на собственный сегмент данных.

При завершении программы команда RET заносит в регистр IP нулевое значение, которое было помещено в стек в начале выполнения программы. В регистровой паре CS:IP в этом случае получается адрес, который является адресом первого байта PSP, где расположена команда INT 20h. Когда эта команда будет выполнена, управление перейдет в DOS.

Пример 3 оформления СОМ-программы

Page 60,80

TITLE EXAMPLE_COM для пересылки и сложения

Codes SEGMENT PARA ‘Code’

ASSUME CS:CODESG, DS:CODESG, SS:CODESG, ES:CODESG

ORG 100H ; начало в конце PSP

BEGIN: JMP MAIN ; обход через данные

;---------------------------------------------------------------------------------

ALPHA DW 250

BETA DW 125

GAMMA DW ?

;---------------------------------------------------------------------------------

MAIN PROC NEAR

MOV AX, ALPHA

ADD AX, BETA

MOV GAMMA, AX

RET ; вернуться в DOS

; можно вместо RET INT20H

MAIN ENDP

Codes ENDS

END BEGIN

В отличие от ЕХЕ-файла СОМ-файл не содержит заголовка на диске. Так как организация СОМ-файла намного проще, то для DOS необходимо знать только то, что тип файла СОМ. Как уже упоминалось выше, загруженным в память СОМ- и ЕХЕ-файлам предшествует префикс программного сегмента. Первые два байта этого префикса содержат команду INT20h (возврат в DOS).

При загрузке СОМ-программы DOS устанавливает в четырех сегментных регистрах адрес первого байта PSP. Затем устанавливается указатель стека на конец сегмента объемом 64 Кбайт (FFFE), т.е. на конец памяти. В вершину стека заносится нулевое слово. В командный указатель помещается 100Н (размер PSP). После этого управление передается по адресу регистровой пары CS:IP, т. е. на адрес непосредственно после PSP. Этот адрес является началом выполняемой СОМ-программы и должен содержать выполнимую команду.

При выходе из программы команда RET заносит в регистр IP нулевое слово, которое было записано в вершину стека при инициализации. В этом случае в регистровой паре CS:IP получается адрес первого байта PSP, где находится команда INT20h. При выполнении этой команды управление передается в резидентную часть COMMAND.COM. Если программа завершается по команде INT20h (Завершение программы, управление передается DOS) вместо RET, то управление непосредственно передается в COMMAND.COM.

Примечание.

Коды команд, данные и стек в СОМ-программах располагаются в одном сегменте. Вследствие этого СОМ-программа занимает в памяти 64 Кбайт, независимо от ее размера на диске. Этот факт является одной из причин предпочтительного использования ЕХЕ-формата.

Хотя ЕХЕ-файл может занимать больше дискового пространства (т.к. в него включена дополнительная информация), небольшие ЕХЕ-программы занимают при исполнении в памяти значительно меньше места, чем эквивалентные им СОМ-программы.

СОМ-формат файла наследуется из ОС СР/М. Полезный в ряде случаев, формат не рекомендуется использовать для РС-программ.

Команды СОМ-программ загружаются в память с адреса 100h относительно начала кодового сегмента программы. По такому же адресу загружаются программы в ОС СР/М, на которой во многом основана DOS и которая используется в компьютерах, имеющих 64К общей памяти. Под управлением DOS СОМ-программы работают в режиме псевдо-СР/М адресного пространства, несмотря на то, что современные компиляторы имеют объем памяти в десятки раз больший. Т.о. сегодня уже нет разумных причин для этого устаревшего формата файла.

Компания Microsoft объявила о своем намерении отказаться от использования СОМ-фор­мата, хотя эти попытки не являются успешными.


Наши рекомендации