2.1. EXE- и COM-программы
DOS может загружать и выполнять программные файлы двух типов – COM и EXE.
Ввиду сегментации адресного пространства процессора 8086 и того факта, что переходы (JMP) и вызовы (CALL) используют относительную адресацию, оба типа программ могут выполняться в любом месте памяти. Программы никогда не пишутся в предположении, что они будут загружаться с определенного адреса (за исключением некоторых самозагружающихся, защищенных от копирования программ).
Файл COM-формата – это двоичный образ кода и данных программы. Такой файл должен занимать менее 64K и не содержать перемещаемых адресов сегментов.
Файл EXE-формата содержит специальный заголовок, при помощи которого загрузчик выполняет настройку ссылок на сегменты в загруженном модуле.
Перед загрузкой COM- или EXE-программы DOS определяет сегментный адрес, называемый префиксом программного сегмента (PSP), как базовый для программы. Затем DOS выполняет следующие шаги:
создает копию текущего окружения DOS (область памяти, содержащая ряд строк в формате ASCIIZ, которые могут использоваться приложениями для получения некоторой системной информации и для передачи данных между программами) для программы;
помещает путь, откуда загружена программа, в конец окружения;
заполняет поля PSP информацией, полезной для загружаемой программы (количество памяти, доступное программе; сегментный адрес окружения DOS; текущие векторы прерываний INT 22H INT 23H и INT 24H и т.д).
EXE-программы. EXE-программы содержат несколько программных сегментов, включая сегмент кода, данных и стека. EXE-файл загружается, начиная с адреса PSP:0100h. В процессе загрузки считывается информация заголовка EXE в начале файла и выполняется перемещение адресов сегментов. Это означает, что ссылки типа
mov ax,data_seg
mov ds,ax
и
call my_far_proc
должны быть приведены (пересчитаны), чтобы учесть тот факт, что программа была загружена в произвольно выбранный сегмент.
После перемещения управление передается загрузочному модулю посредством инструкции далекого перехода (FAR JMP) к адресу CS:IP, извлеченному из заголовка EXE.
В момент получения управления программой EXE -формата:
DS и ES указывают на начало PSP
CS, IP, SS и SP инициализированы значениями, указанными в заголовке EXE
поле PSP MemTop (вершина доступной памяти системы в параграфах) содержит значение, указанное в заголовке EXE. Обычно вся доступная память распределена программе.
COM-программы. COM-программы содержат единственный сегмент (или, во всяком случае, не содержат явных ссылок на другие сегменты). Образ COM-файла считывается с диска и помещается в память, начиная с PSP:0100h. В общем случае, COM-программа может использовать множественные сегменты, но она должна сама вычислять сегментные адреса, используя PSP как базу.
COM-программы предпочтительнее EXE-программ, когда дело касается небольших ассемблерных утилит. Они быстрее загружаются, ибо не требуется перемещения сегментов, и занимают меньше места на диске, поскольку заголовок EXE и сегмент стека отсутствуют в загрузочном модуле.
После загрузки двоичного образа COM-программы:
CS, DS, ES и SS указывают на PSP;
SP указывает на конец сегмента PSP (обычно 0FFFEH, но может быть и меньше, если полный 64K сегмент недоступен);
слово по смещению 06H в PSP (доступные байты в программном сегменте) указывает, какая часть программного сегмента доступна;
вся память системы за программным сегментом распределена программе;
слово 00H помещено (PUSH) в стек.
IP содержит 100H (первый байт модуля) в результате команды JMP PSP:100H.
2.2. Выход из программы
Завершить программу можно следующими способами:
через функцию 4CH (EXIT) прерывания 21H в любой момент, независимо от значений регистров;
через функцию 00H прерывания 21H или прерывание INT 20H, когда CS указывает на PSP.
Функция DOS 4CH позволяет возвращать родительскому процессу код выхода, который может быть проверен вызывающей программой или командой COMMAND.COM "IF ERRORLEVEL".
Можно также завершить программу и оставить ее постоянно резидентной (TSR), используя либо INT 27H , либо функцию 31H (KEEP) прерывания 21H. Последний способ имеет те преимущества, что резидентный код может быть длиннее 64K, и что в этом случае можно сформировать код выхода для родительского процесса.
3. Ассемблер, макроассемблер, редактор связей
Существует несколько версий программы ассемблер. Одним из наиболее часто используемых является пакет Turbo Assembler, водящий в состав комплекса программ Borland Pascal 7.0. Рассмотрим работу с этим пакетом более подробно.
Входной информацией для ассемблера (TASM.EXE) является исходный файл — текст программы на языке ассемблера в кодах ASCII. В результате работы ассемблера может получиться до 3-х выходных файлов:
объектный файл – представляет собой вариант исходной программы, записанный в машинных командах;
листинговый файл – является текстовым файлом в кодах ASCII, включающим как исходную информацию, так и результат работы программы ассемблера;
файл перекрестных ссылок – содержит информацию об использовании символов и меток в ассемблерной программе (перед использованием этого файла необходима его обработка программой CREF).
Существует много способов указывать ассемблеру имена файлов. Первый и самый простой способ — это вызов команды без аргументов. В этом случае ассемблер сам поочередно запрашивает имена файлов: входной (достаточно ввести имя файла без расширения ASM), объектный, листинговый и файл перекрестных ссылок. Для всех запросов имеются режимы, применяемые по умолчанию, если в ответ на запрос нажать клавишу Enter:
объектному файлу ассемблер присваивает то же имя, что и у исходного, но с расширением OBJ;
для листингового файла и файла перекрестных ссылок принимается значение NUL — специальный тип файла, в котором все, что записывается, недоступно и не может быть восстановлено.
Если ассемблер во время ассемблирования обнаруживает ошибки, он записывает сообщения о них в листинговый файл. Кроме того, он выводит их на экран дисплея.
Другой способ указать ассемблеру имена файлов — это задать их прямо в командной строке через запятую при вызове соответствующей программы, например:
TASM Test, Otest, Ltest, Ctest
При этом первым задается имя исходного файла, затем объектного, листингового и, наконец, файла перекрестных ссылок. Если какое-либо имя пропущено, то это служит указанием ассемблеру сгенерировать соответствующий файл по стандартному соглашению об именах.
Программа, полученная в результате ассемблирования (объектный файл), еще не готова к выполнению. Ее необходимо обработать командой редактирования связей TLINK, которая может связать несколько различных объектных модулей в одну программу и на основе объектного модуля формирует исполняемый загрузочный модуль.
Входной информацией для программы TLINK являются имена объектных модулей (файлы указываются без расширение OBJ). Если файлов больше одного, то их имена вводятся через разделитель «+». Модули связываются в том же порядке, в каком их имена передаются программе TLINK. Кроме того, TLINK требует указания имени выходного исполняемого модуля. По умолчанию ему присваивается имя первого из объектных модулей, но с расширением ЕХЕ. Вводя другое имя, можно изменять имя файла, но не расширение. Далее можно указать имя файла, для хранения карты связей (по умолчанию формирование карты не производится). Последнее, что указывается программе TLINK – это библиотеки программ, которые могут быть включены в полученный при связывании модуль. По умолчанию такие библиотеки отсутствуют.
Информацию обо всех этих файлах программа TLINK запрашивает у пользователя после ее вызова.
Графически процесс создания программы на языке Ассемблера можно представить как это показано на рис 3.1.
4. Язык Ассемблера. Начальные сведения
Все ассемблерные программы состоят из одного или более предложений и комментариев. Предложение и комментарии представляют собой комбинацию знаков, входящих в алфавит языка, а также чисел и идентификаторов, которые тоже формируются из знаков алфавита. Алфавит языка составляют цифры, строчные и прописные буквы латинского алфавита, а также следующие символы:
? @ _ $ : . [ ] ( ) < > { } + / * & % ! ' ~ | \ = # ^ ; , ` "
4.1. Идентификаторы, переменные, метки, имена, ключевые слова
Конструкции языка ассемблера формируются из идентификаторов и ограничителей. Идентификатор представляет собой набор букв, цифр и символов «_», «.», «?», «$» или «@» (символ «.» может быть только первым символом идентификатора), не начинающийся с цифры. Идентификатор должен полностью размещаться на одной строке и может содержать от 1 до 31 символа (точнее, значимым является только первый 31 символ идентификатора, остальные игнорируются). Друг от друга идентификаторы отделяются пробелом или ограничителем, которым считается любой недопустимый в идентификаторе символ. Посредством идентификаторов представляются следующие объекты программы:
переменные;
метки;
имена.
Переменные идентифицируют хранящиеся в памяти данные. Все переменные имеют три атрибута:
СЕГМЕНТ, соответствующий тому сегменту, который ассемблировался, когда была определена переменная;
СМЕЩЕНИЕ, являющееся смещением данного поля памяти относительно начала сегмента;
ТИП, определяющий число байтов, подвергающихся манипуляциям при работе с переменной.
Метка является частным случаем переменной, когда известно, что определяемая ею память содержит машинный код. На нее можно ссылаться посредством переходов или вызовов. Метка имеет два атрибута: СЕГМЕНТ и СМЕЩЕНИЕ.
Именами считаются символы, определенные директивой EQU и имеющие значением символ или число. Значения имен не фиксированы в процессе ассемблирования, но при выполнении программы именам соответствуют константы.
Некоторые идентификаторы, называемые ключевыми словами, имеют фиксированный смысл и должны употребляться только в соответствии с этим. Ключевыми словами являются:
директивы ассемблера;
инструкции процессора;
имена регистров;
операторы выражений.
В идентификаторах одноименные строчные и заглавные буквы считаются эквивалентными. Например, идентификаторы AbS и abS считаются совпадающими.
4.2. Типы данных
Ниже описаны типы и формы представления данных, которые могут быть использованы в выражениях, директивах и инструкциях языка ассемблера.
Целые числа имеют следующий синтаксис (xxxx – цифры):
[+|-]xxxx
[+|-]xxxxB
[+|-]xxxxQ
[+|-]xxxxO
[+|-]xxxxD
[+|-]xxxxH
Латинский символ (в конце числа), который может кодироваться на обоих регистрах, задает основание системы счисления числа: B – двоичное, Q и O – восьмеричное, D – десятичное, H – шестнадцатеричное. Шестнадцатеричные числа не должны начинаться с буквенных цифр (например, вместо некорректного ABh следует употреблять 0ABh). Шестнадцатеричные цифры от A до F могут кодироваться на обоих регистрах. Первая форма целого числа использует умалчиваемое основание (обычно десятичное).
Символьные и строковые константы имеют следующий синтаксис:
'символы'
"символы"
Символьная константа состоит из одного символа алфавита языка. Строковая константа включает в себя 2 или более символа. В отличие от других компонент языка, строковые константы чувствительны к регистру. Символы «'» и «"» в теле константы должны кодироваться дважды.
Кроме целых и символьных типов ассемблер содержит еще ряд типов (например, вещественные числа, двоично-десятичные числа), однако их рассмотрение выходит за рамки данного пособия.
4.3. Предложения
Программа на языке Ассемблера – это последовательность предложений, каждое из которых записывается в отдельной строке:
<�Предложение>
<�Предложение>
...
<�Предложение>
Предложения определяют структуру и функции программы, они могут начинаться с любой позиции и содержать не более 128 символов. При записи предложений действуют следующие правила расстановки пробелов:
пробел обязателен между рядом стоящими идентификаторами и/или числами (чтобы отделить их друг от друга);
внутри идентификаторов и чисел пробелы недопустимы;
в остальных местах пробелы можно ставить или не ставить;
там, где допустим один пробел, можно ставить любое число пробелов.
Все предложения языка ассемблера делятся на директивы ассемблера и инструкции (команды) процессора.
Директивы ассемблера действуют лишь в период компиляции программы и позволяют устанавливать режимы компиляции, задавать структуру сегментации программы, определять содержимое полей данных, управлять печатью листинга программы, а также обеспечивают условную компиляцию и некоторые другие функции. В результате обработки директив компилятором объектный код не генерируется.
Инструкции процессора представляют собой мнемоническую форму записи машинных команд, непосредственно выполняемых микропроцессором. Все инструкции в соответствии с выполняемыми ими функциями делятся на 5 групп:
инструкции пересылки данных;
арифметические, логические и операции сдвига;
операции со строками;
инструкции передачи управления;
инструкции управления процессором.
4.4. Выражения
В языке ассемблера выражения могут быть использованы в инструкциях или директивах и состоят из операндов и операторов.
Операнды представляют значения, регистры или адреса ячеек памяти, используемых определенным образом по контексту программы.
Операторы выполняют арифметические, логические, побитовые и другие операции над операндами выражений.
Ниже даны описания наиболее часто используемых в выражениях операторов.
Арифметические операторы.
выражение_1 * выражение_2
выражение_1 / выражение_2
выражение_1 MOD выражение_2
выражение_1 + выражение_2
выражение_1 – выражение_2
+ выражение
– выражение
Эти операторы обеспечивают выполнение основных арифметических действий (здесь MOD - остаток от деления выражения_1 на выражение_2, а знаком / обозначается деление нацело). Результатом арифметического оператора является абсолютное значение.
Операторы сдвига.
выражение SHR счетчик
выражение SHL счетчик
Операторы SHR и SHL сдвигают значение выражения соответственно вправо и влево на число разрядов, определяемое счетчиком. Биты, выдвигаемые за пределы выражения, теряются. Замечание: не следует путать операторы SHR и SHL с одноименными инструкциями процессора.
Операторы отношений.
выражение_1 EQ выражение_2
выражение_1 NE выражение_2
выражение_1 LT выражение_2
выражение_1 LE выражение_2
выражение_1 GT выражение_2
выражение_1 GE выражение_2
Мнемонические коды отношений расшифровываются следующим образом:
EQ – равно;
NE – не равно;
LT – меньше;
LE – меньше или равно;
GT – больше;
GE – больше или равно.
Операторы отношений формируют значение 0FFFFh при выполнении условия и 0000h в противном случае. Выражения должны иметь абсолютные значения. Операторы отношений обычно используются в директивах условного ассемблирования и инструкциях условного перехода.
Операции с битами.
NOT выражение
выражение_1 AND выражение-2
выражение_1 OR выражение-2
выражение_1 XOR выражение-2
Мнемоники операций расшифровываются следующим образом:
NOT – инверсия;
AND – логическое И;
OR – логическое ИЛИ;
XOR – исключающее логическое ИЛИ.
Операции выполняются над каждыми соответствующими битами выражений. Выражения должны иметь абсолютные значения.
Оператор индекса.
[[выражение_1]] [выражение_2]
Оператор индекса [] складывает указанные выражения подобно тому, как это делает оператор +, с той разницей, что первое выражение необязательно, при его отсутствии предполагается 0 (двойные квадратные скобки указывают на то, что операнд не обязателен).
Оператор PTR
тип PTR выражение
При помощи оператора PTR переменная или метка, задаваемая выражением, может трактоваться как переменная или метка указанного типа. Тип может быть задан одним из следующих имен или значений:
Таблица 4.1. Типы оператора PTR
Имя типа
|
Значение
|
BYTE
|
1
|
WORD
|
2
|
DWORD
|
4
|
QWORD
|
8
|
TBYTE
|
10
|
NEAR
|
0FFFFh
|
FAR
|
0FFFEh
|
Оператор PTR обычно используется для точного определения размера, или расстояния, ссылки. Если PTR не используется, ассемблер подразумевает умалчиваемый тип ссылки. Кроме того, оператор PTR используется для организации доступа к объекту, который при другом способе вызвал бы генерацию сообщения об ошибке (например, для доступа к старшему байту переменной размера WORD).
Операторы HIGH и LOW
HIGH выражение
LOW выражение
Операторы HIGH и LOW вычисляют соответственно старшие и младшие 8 битов значения выражения. Выражение может иметь любое значение.
Оператор SEG
SEG выражение
Этот оператор вычисляет значение атрибута СЕГМЕНТ выражения. Выражение может быть меткой, переменной, именем сегмента, именем группы или другим символом.
Оператор OFFSET
OFFSET выражение
Этот оператор вычисляет значение атрибута СМЕЩЕНИЕ выражения. Выражение может быть меткой, переменной, именем сегмента или другим символом. Для имени сегмента вычисляется смещение от начала этого сегмента до последнего сгенерированного в этом сегменте байта.
Оператор SIZE
SIZE переменная
Оператор SIZE определяет число байтов памяти, выделенных переменной.
|