Сообщений 0    Оценка 0        Оценить  
Система Orphus

Основные директивы IBM PC

Глава из книги “Искусство программирования на Ассемблере”

Автор: Н. Голубь
Источник: Искусство программирования на Ассемблере
Материал предоставил: Издательство "Питер"
Опубликовано: 24.06.2006
Версия текста: 1.0
Преимущества и недостатки изучения языка Ассемблера с использованием известных алгоритмических языков Pascal и C/C++
Разница между директивами и командами Ассемблера
Описание сегмента — директива SEGMENT
Директива группирования сегментов Group
Директива Assume
Стандартные модели памяти
Директива MODEL
Директивы упрощенного описания сегментов
Описание процедур
Описание внешних ссылок
Директива описания общих имен PUBLIC
Директива описания внешних имен EXTRN
Вопросы

Трудно по-своему выразить общеизвестные истины.
Квинт Гораций Флакк (65-8 гг. до н.э.)

Преимущества и недостатки изучения языка Ассемблера с использованием известных алгоритмических языков Pascal и C/C++

Язык Ассемблера можно изучать по-разному. Можно начинать изучать язык сам по себе, а за результатами следить через отладчик — это обычный, классический путь (см. п. 5.1.1.3).

Можно изучать Ассемблер, делая полноценные ассемблерные модули (но сначала БЕЗ ввода-вывода) и используя в качестве помощников уже известные алгоритмические языки, например, такие, как Pascal или C/C++. В данной книге в основном применен именно этот, ВТОРОЙ, подход. Многолетний и многоассемблерный опыт преподавания убедил автора этой книги в его целесообразности. Используя знакомый алгоритмический язык в качестве помощника и не вникая сначала в особенности ввода-вывода на Ассемблере и организации исполняемых COM- и EXE-файлов, можно начать с простых команд типа a+b (а в Ассемблере, да и в любом алгоритмическом языке ГРАМОТНОЕ решение этой на вид простой задачки дело совсем НЕПРОСТОЕ!). А освоившись с азами программирования на Ассемблере, мы закончим программами на чистом Ассемблере с использованием прерываний и прочих прибамбасов.

Достоинства этого подхода, по мнению автора, заключаются в следующем:

Недостатки:

Именно по этому пути мы и пойдем с самого начала (см. п. 5.1.1.2). Таким образом, различные тонкости языка Ассемблера будут раскрываться постепенно, по мере усвоения материала и появления опыта, а значит, и уверенности в своих силах.

Есть еще и ТРЕТИЙ путь — использование встроенного Ассемблера. Этот метод мы тоже будем использовать по мере необходимости и там, где это целесообразно. Но встроенный Ассемблер лишен прелести настоящего Ассемблера: он ОЧЕНЬ зависит от среды его реализации и МНОГИХ команд в нем может просто НЕ быть (особенно это касается Borland/Turbo Pascal). Кроме того, зачастую встроенный Ассемблер скрывает (маскирует) КРУПНЫЕ огрехи, которые немедленно возникают в настоящем Ассемблере.

Разница между директивами и командами Ассемблера

Итак, из п. 2.4.1 мы уже знаем, что в языке Ассемблера существуют команды и директивы, формат которых практически одинаков — см. рис. 2.12. В командах Имя интерпретируется как метка, поэтому за ней всегда ставится символ двоеточия ':'. Для Ассемблера в качестве имени (идентификатора) допускаются следующие символы:

Имя в Ассемблере может начинаться с любого допустимого символа, кроме цифры. Если имя содержит символ точки '.', то он должен быть ПЕРВЫМ символом. Имя НЕ может быть зарезервированным в Ассемблере словом (имя машинной команды или директивы).

С некоторыми директивами мы с вами уже успели и поработать — см. примеры 2.9 и 2.10.

При ассемблировании между директивами и командами существует одно принципиальное различие. Директивы (псевдооператоры, псевдокоманды) управляют работой компилятора или компоновщика, а НЕ микропроцессора. Они используются, например, для сообщения компилятору, какие константы и переменные применяются в программе и какие имена мы им дали, какой сегмент является кодовым, а какой — сегментом данных или стека, в каком формате выводить листинг исходного кода программы и прочее. Большинство директив НЕ генерирует машинных команд (объектный код).

Команда Ассемблера всегда генерирует машинный код.

Директивы имеют разный синтаксис в режимах MASM (поддерживается компиляторами Microsoft Assembler — masm и Borland (Turbo) Assembler — tasm) и Ideal (поддерживается компилятором tasm) — см. приложение 5.

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

Директив достаточно много. Практически каждая версия компилятора что-то из директив добавляет или немного изменяет синтаксис. Для определенности мы в данной главе остановимся на синтаксисе основных директив в более универсальном формате MASM для компиляторов masm-6.12 (или выше) и tasm-3.1 (или выше). Кроме того, известные компиляторы с языка программирования С/С++ (Borland C++, Visual C++) выдают ассемблерный листинг именно в формате MASM. И очень скоро мы научимся его читать…

Описание сегмента — директива SEGMENT

Из п. 2.4.3 мы знаем, что любые ассемблерные программы содержат, по крайней мере, один сегмент — сегмент кода. В некоторых программах используется сегмент для стековой памяти и сегмент данных (основной и дополнительный) для определения данных.

Универсальная директива для описания сегмента имеет следующий формат:
                 имяС       SEGMENT        [параметры] ; начало СЕГМЕНТА имяС
                                   .
                                   .
                                   .
                 имяС       ENDS     ; конец СЕГМЕНТА имяС 

Имя сегмента (имяС) должно обязательно присутствовать, быть уникальным и соответствовать соглашениям для имен в Ассемблере или в другом алгоритмическом языке, для стыковки с которым делается ассемблерный модуль. Например, при стыковке Ассемблера с Turbo/Borland Pascal имяС должно быть СТРОГО определенным:

Директива ENDS обозначает конец сегмента. Обе директивы SEGMENT и ENDS должны иметь одинаковые имена имяС.

В одном модуле можно открывать и закрывать сегмент с одним и тем же именем имяС несколько раз, но пересекаться (вкладываться друг в друга) разные сегменты НЕ должны. Компилятор просматривает ассемблерный модуль и объединяет вместе все части сегментов с одинаковым именем в том порядке, в каком он их обнаруживает (сверху-вниз).

Директива SEGMENT может содержать три основных типа необязательных параметров, определяющих выравнивание (align), объединение (combine) и класс ('class'), между которыми должен быть хотя бы один пробел в качестве разделителя. Параметры имеют смысл при разработке БОЛЬШИХ ассемблерных программ.

1. Выравнивание (align). Этот параметр сообщает компоновщику, чтобы он разместил данный сегмент, начиная с указанной границы. Это может быть ОЧЕНЬ важно, т.к. при правильном выравнивании данные загружаются процессором с большей скоростью. При попытке сосчитать невыравненные данные процессор сделает одно из двух: либо возбудит исключение, либо сосчитает их в несколько приемов (конечно, при этом быстродействие программы снизится) [Л7-8].

ПараметрЗначение
BYTEВыравнивание НЕ выполняется. Сегмент размещается, начиная со следующего байта.
WORDНачало сегмента выравнивается на границу слова (четный адрес, кратный 2)
DWORDНачало сегмента выравнивается на границу двойного слова (четный адрес, кратный 4)
PARAНачало сегмента выравнивается на границу параграфа (четный адрес, кратный 16, см.п.3.4.3). Это значение принято по умолчанию.
PAGEНачало сегмента выравнивается на границу страницы (четный адрес, кратный 256)
MEMPAGEНачало сегмента выравнивается на границу страницы памяти (четный адрес, кратный 4K)
Таблица 4.1. Параметр выравнивания (align)

2. Объединение (combine). Настоящий элемент предназначен для указания компоновщику, каким образом объединять сегменты, находящиеся в разных модулях и имеющие одинаковые имена, или как соединить данный сегмент с другими сегментами в процессе компоновки после ассемблирования.

3. Класс ('class'). Данный элемент, заключенный в апострофы, используется для группирования сегментов при компоновке. Компоновщик группирует вместе все сегменты с ОДИНАКОВЫМ классом.

ПараметрЗначение
PRIVATEСегмент НЕ будет объединяться с сегментами с тем же именем, находящимися в других модулях. Это значение принято по умолчанию.
PUBLICСегмент будет объединяться с сегментами с тем же именем, находящимися в других модулях или в том же модуле, в один сегмент.
MEMORYПараметр подобен PUBLIC, но для сегмента стека.
COMMONРазмещает данный сегмент и все сегменты с тем же именем по ОДНОМУ и тому же адресу. Получаются перекрывающиеся сегменты (overlay), занимающие разделяемую область памяти (shared memory).
VIRTUALОписывает сегмент специального вида, который должен объявляться ВНУТРИ другого сегмента (общая область памяти).
AT xxxУстанавливает сегмент по абсолютному адресу параграфа xxx для получения доступа по идентификаторам к фиксированным адресам памяти (например, видеобуфер, таблица векторов прерываний – см. прил.6)
Таблица 4.2. Параметр объединения (combine)

Например, сегмент стека, в котором зарезервировано 100*8 = 800 байтов (со словом My Stack — 8 символов) может быть описан следующим образом:

                         SStack	SEGMENT  	PARA   PUBLIC   'Stack'
	DB	100 dup ('My Stack')
                         SStack	ENDS

Директива группирования сегментов Group

Эта директива используется для объединения сегментов в группу. Она имеет следующий формат:

	имяG       GROUP        имяС1[, имяС2…] 

Группа позволяет осуществлять доступ к данным из всех сегментов, которые находятся в ней, с помощью одной загрузки адреса группы в сегментный регистр. Этим широко пользуются компиляторы С/С++.

Директива Assume

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

    ASSUME        сегм_регистр1:имя1[, сегм_регистр2: имя2…] 

В качестве сегментных регистров для базового Ассемблера принимаются уже известные нам регистры: CS, DS, ES или SS.

Для ОТМЕНЫ назначения для данного сегментного регистра используется ДРУГОЙ формат этой директивы:

    ASSUME        сегм_регистр1:NOTHING[,сегм_регистр2: NOTHING …] 

Чаще всего эта директива используется в начале модуля на Ассемблере.

Рассмотрим фрагмент листинга ассемблерного файла, который был получен компилятором Borland C++ 5.02:

_TEXT	segment byte public 'CODE'
_TEXT	ends
DGROUP	group	_DATA,_BSS
         			assume	cs:_TEXT,ds:DGROUP
_DATA	segment word public 'DATA'
               ………………………………………
_DATA	ends
_BSS	    segment word public 'BSS'
               ………………………………………
_BSS	    ends
_TEXT	segment byte public 'CODE'
	    	assume	cs:_TEXT,ds:DGROUP
               ………………………………………
_TEXT	ends
_DATA	segment word public 'DATA'
               ………………………………………
_DATA	ends
_TEXT	segment byte public 'CODE'
_TEXT	ends
 	    end

Ну, как? Все понятно? Вот мы и начинаем немного понимать, что же делают компиляторы с алгоритмических языков…

Стандартные модели памяти

Современные версии компиляторов с Ассемблера позволяют упростить описание сегментов с помощью использования стандартных моделей памяти — см. п. 3.4.2. В этом случае директивы SEGMENT, ENDS и ASSUME становятся НЕНУЖНЫМИ.

Директива MODEL

Директива MODEL позволяет задавать в ассемблерной программе одну из нескольких стандартных моделей сегментации памяти (модель_памяти) — см. табл. 3.3. Приведем ее упрощенный формат, которым мы будем пользоваться в дальнейшем:

                      .MODEL        модель_памяти [, язык]

Параметр язык позволяет немного упростить вопросы интерфейса (стыковки) модуля на Ассемблере с общепринятыми алгоритмическими языками. Если вы их используете, то параметр язык должен быть равен одному из терминальных элементов: C, CPP, BASIC, PASCAL, FORTRAN, PROLOG. Если этот параметр НЕ указан, он считается NOLANGUAGE. Например:

.MODEL        Large , C
.MODEL        Small , Pascal

Директивы упрощенного описания сегментов

Определившись с используемой моделью, можно использовать упрощенные директивы описания основных сегментов (регистр букв НЕСУЩЕСТВЕНЕН!):

.Code   ;    формат MASM
CODESEG ;    формат Ideal — см. Прил. 5.
.DATA   ;    формат MASM
DATASEG ;    формат Ideal
.STACK  ;    формат MASM
STACK   ;    формат Ideal

Описание процедур

Модуль на Ассемблере, как и модули на алгоритмических языках, обычно состоит из процедур. Для описания процедур используются две директивы. Визуально они похожи на соответствующие директивы описания сегмента — см. п. 4.2:

    имяP       PROC        [параметры] ; начало процедуры имяP
              .…………….…………….…………….…………….…………….……………
                    RET   ; КОМАНДА возврата в точку вызова процедуры
              .…………….…………….…………….…………….…………….…………….
    имяP       ENDP     ; конец процедуры имяP

У директивы PROC достаточно много параметров. С ними мы будем знакомиться постепенно, по мере необходимости.

Обратите внимание на ОБЯЗАТЕЛЬНУЮ команду RET. Она может быть в любом нужном месте процедуры и НЕ единственная. Если ее в процедуре НЕ будет, то ассемблерная программа НЕ сможет нормально работать — возникнет зависание.

Описание внешних ссылок

Как было заявлено в п. 4.1, мы будем использовать алгоритмические языки Pascal и C/C++ в качестве помощников при изучении Ассемблера. Таким образом, сразу начинаем работать с РАЗНЫМИ модулями, да еще и на разных языках! Поэтому нам не миновать ВНЕШНИХ ссылок. Что это такое? Это — использование в одном модуле имен, описанных в других модулях.

Директива описания общих имен PUBLIC

PUBLIC      [язык] имя1[,[язык] имя2…] 

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

Например, если мы хотим использовать в языке С/С++ функцию с именем Prim, реализованную в Ассемблере, то она в ассемблерном модуле должна быть описана следующим образом:

	Public	C  Prim
Prim	Proc
              ………………………………………
Prim	EndP

Обратите внимание

Язык С/С++ различает регистр букв в именах. Это же должен делать и Ассемблер, для чего служит специальный ключ, определяющий чувствительность Ассемблера к РЕГИСТРУ выбора символов: ml=all (все символы), mx=globals (только глобальные), mu=none (символы к регистру НЕ чувствительны — принято по умолчанию):

TASM имя.asm[ /ml]
TASM имя.asm[ /mx]
TASM имя.asm[ /mu]    

В нашем случае ассемблерный модуль должен быть откомпилирован ОБЯЗАТЕЛЬНО с ключом /ml или /mx. Иначе компоновщик С/С++ НЕ сможет подключить ассемблерный модуль. Если эти рассуждения вам пока НЕПОНЯТНЫ, НЕ зацикливайтесь, — мы к этому еще вернемся на КОНКРЕТНЫХ примерах.

Директива описания внешних имен EXTRN

EXTRN      имя1:тип1[, имя2:тип2…] 

Эта директива указывает компилятору и компоновщику, что данное имя имеет определенный тип, его предполагается использовать в данном ассемблерном модуле, но память для него выделена в другом модуле. Параметр тип может принимать следующие значения: ABS (для констант), BYTE, WORD, DWORD, QWORD, TBYTE (см. соответствующий столбец в табл. 2.2), FAR, NEAR.

Например, в модуле на алгоритмическом языке Pascal (Borland/Turbo Pascal-5.5/6.0/7.0х) мы описали следующие глобальные переменные:

Var a, b, c : Integer;
       X      : LongInt;

А использовать их собираемся в ассемблерном модуле. В этом случае директива будет иметь вид:

Extrn   a:Word, b:Word, c:Word, x:Dword

Обратите внимание

Язык Pascal НЕ различает регистр букв в именах. А директива EXTRN содержит только ОДНУ гласную букву — начинающие программисты часто на этом спотыкаются… Будьте ВНИМАТЕЛЬНЫ!

Вопросы

  1. Что изменится в директиве EXTRN, если мы опишем указанные выше переменные на алгоритмическом языке Object Pascal (Borland Delphi, Win32 Console)?
  2. Что изменится в директиве EXTRN, если мы опишем указанные выше переменные на алгоритмическом языке C++ (Borland C++ 4.5/5.02, EasyWin)?
  3. Что изменится в директиве EXTRN, если мы опишем указанные выше переменные на алгоритмическом языке C++ (Borland C++ 4.5/5.02, MS DOS Standard)?
  4. Что изменится в директиве EXTRN, если мы опишем указанные выше переменные на алгоритмическом языке C++ (Borland C++ 4.5/5.02, Win32 Console)?
  5. Что изменится в директиве EXTRN, если мы опишем указанные выше переменные на алгоритмическом языке C++ (Microsoft Visual C++ 5.0/6.0, Win32 Console)?
  6. Что изменится в директиве EXTRN, если мы опишем указанные выше переменные на алгоритмическом языке Object Pascal (Borland Delphi, Win32 Application)?
  7. Что изменится в директиве EXTRN, если мы опишем указанные выше переменные на алгоритмическом языке C++ (Microsoft Visual C++ 5.0/6.0, Win32 Application)?

Если вы правильно ответили на ВСЕ вопросы, значит, вы ХОРОШО УСВОИЛИ главу 2 (п. 2.2) — поздравляю!!! Если НЕТ, не беда — все у вас еще впереди (просто пока маловато опыта).


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