Связь с администрацией сайта:       

demo

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

Глава 5 - ассемблер(директивы и операторы)

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

 

Структура программы

Программа на ассемблере состоит из строк вида:
метка[:] команда/директива операнды ;коментарий
Все данные поля необязательны т.е. строка может быть и пустой! Метка может быть любой комбинацией символов но не должна начинаться с цифры, кроме того не стоит использовать знаки $ и ?. Коментарием может быть всё что угодно. В том случае когда метка располагается перед командой процессора то после неё обязательно надо поставить двоеточие, что указывает ассемблеру создать переменную с именем метки и значением равным адресу команды. Вот например забегая вперёд
loop: lodsw ;читаем слово в еаx
cmp eax,78;если 78 то прекратим
loopne loop

 


Если метка ставится перед директивой то двоеточние не нужно. Директивы работающие с метками это label и equ. Первая определяет метку и задаёт её тип. А вторая присваивает метке значение (то есть имя метки это синоним выражения в правой части, в общем аналог #define d с/с++). Вообще на практике их используют не часто.
метка label тип
метка equ выражение
Типом может быть BYTE(байт), WORD(слово), DWORD(двойное слово), FWORD(6 байт),QWORD(учетверённое слово),TBYTE(10 байт),NEAR(ближняя метка),FAR(дальня метка).

Есть одна очень полезная предопределённая метка: $.  Она определяет текущий адрес. Вот как лекго организуется вечный цикл:
jmp $

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

А куда-же без этого? Перед использованием данные надо определять. Это хороший тон. Вот что для этого есть.

  • DB - определить одни байт
  • DW - определить два байта(слово)
  • DD - определить двойное слово
  • DF - определить 6 байт
  • DQ - определить учетверённое слово (8 баёт)
  • DT - определить 10 байт (80 битные типы данных для FPU)

Вот примеры определения данных
text db 'Hi! i'm string!'
number dw 7878
tbl db 1,2,3,4,5,6,7,8,9,10,11,12
float dd 3.5e7
empty dw ? 

Здесь видно что после директив идут значения, причём их может быть несколько! В этом случае имя переменной определяет адрес первого элемента
Это важно понять.

Вот обращение к первому символу: mov al,text.Пустое значение задается знаком вопроса. Кроме этого для массива можно использовать оператор DUP.
Его формат 
счётчик DUP (значение). 
tbl dw 1024 dup (?) задаёт массив неинициализированных слов размеров 1Кб.

В ассемблере также можно определять структуры данных аналогично языкам высокого уровня! Кроме того структуры могут быть вложенными. Рассмотрим на примере:
point struct ;вводим новый тип данных
x dw 0 ;здесь два поля но их количество 
y dw 0 ;на самом деле произвольно, как и тип данных!
point ends ;закончили описание нового типа данных
cur_point point <10,244> ; объявляем переменную нового типа и указываем значения
............................
mov ax,cur_point.x; а так можно обратиться к элементу структуры. 

 

Записи

Запись представляет собой набор полей бит, объединенных одним именем. Каждое поле записи имеет собственную длину, исчисляемую в битах, и не обязано занимает целое число байтов. Объявление записи в программе на языке ассемблера включает в себя 2 действия:
1) объявление шаблона или типа записи директивой RECORD;
2) объявление собственно записи.
Формат директивы RECORD:
имя_записи RECORD имя_поля:длина[[=выражение]],...
Директива RECORD определяет вид 8- или 16-битовой записи, содержащей одно или несколько полей. Имя_записи представляет собой имя типа записи, которое будет использоваться при объявлении записи. Имя_поля и длина (в битах) описывает конкретное поле записи. Выражение, если оно указано задает начальное (умалчиваемое) значение поля. Описания полей записи в директиве RECORD, если их несколько, должны разделяться запятыми. Для одной записи может быть задано любое число полей, но их суммарная длина не должна превышать 16 бит.

Длина каждого поля задается константой в пределах от 1 до 16. Если общая длина полей превышает 8 бит, ассемблер выделяет под запись 2 байта, в противном случае – 1 байт. Если задано выражение, оно определяет начальное значение поля. Если длина поля не меньше 7 бит, в качестве выражения может быть использован символ в коде ASCII. Выражение не должно содержать ссылок вперед. Пример:
item RECORD char:7='Q',weight:4=2
Запись item будет иметь следующий вид:
char weight
0000 1010001 0010
При обработке директивы RECORD формируется шаблон записи, а сами данные создаются при объявлении записи, которое имеет следующий вид:
[[имя]] имя_записи <[[значение,...]]>

По такому объявлению создается переменная типа записи с 8- или 16-битовым значением и структурой полей, соответствующей шаблону, заданному директивой RECORD с именем имя_записи. Имя задает имя переменной типа записи. Если имя опущено, ассемблер распределяет память, но не создает переменную, которую можно было бы использовать для доступа к записи.

В скобках <> указывается список значений полей записи. Значения в списке, если их несколько, должны разделяться запятыми. Каждое значение может быть целым числом, строковой константой или выражением и должно соответствовать длине данного поля. Для каждого поля может быть задано одно значение. Скобки <> обязательны, даже если начальные значения не заданы. Пример:
table item 10 DUP(<'A',2>)

Если для описания шаблона записи использовалась директива RECORD из предыдущего примера, то по этому объявлению создается 10 записей, объединенных именем table.

Организация программы - сегменты

Любая программа на самом деле состоит из сегментов. В общем случае команды расположены в сегменте кода, данные в сегменте данных, стек в сегменте стека. Разумеется можно разносить сегменты на отдельные части, помещать данные в сегмент кода или стека, или использовать один сегмент для всего.

Вот как описывают сегмент на ассемблере
имя сегмента segment readonly выравнивание тип разрядность класс
...............................
имя сегмента ends

Не все операнды обязаны присутствовать. Если написано readonly то в сегмент нельзя ничего писать, выравнивание может быть BYTE (с любого адреса), WORD(с адреса кратного двум),DWORD(c кратного четырём),PARA (с кратного 16), PAGE(с кратного 256). Тип определяет комбинирование сегментов:

  • PUBLIC - все сегменты с одинаковым именем но разными классами объединяются в один
  • STACK тоже самое но используется для стэка
  • COMMON - сегменты объединяются в одни но не последовательно а с одинакового адреса, для формирования оверлеев
  • AT - указывает фиксированый абсолютный адрес в памяти
  • PRIVATE - такие сегменты не объединяются ни с какими другими

Разрядность может быть USE16 или USE32. Шестнадцатиразрядные сегменты не могут превышать 64Кб, в них всё равно можно применять 32-х разрядные регистры и команды, но они будут работать медленне из-за префикса переопределения разрядности! Класс сегмента это любая метка, взятая в кавычки. Для обращения к сегменту его сначала надо загрузить в какой нибудь сегментный регистр. Если в программе много сегентов то надо создать их группу:
name_group group seg1,seg2,.....
Имя группы можно применять для получения адреса и для директивы ASSUME
assume регистр:связь.....
Эта директива просто подсказывает аассемблеру что вы делаете с сегментами.

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

Модели памяти задаются директивой .MODEL модель,язык,модификатор 
Модель это одно из слов

  • TINY - код данные и стек размещаются в одном и томже сегменте размером до 64Кб.
  • SMALL - код в одном сегменте а данные и стек в другом.
  • COMPACT - код в одном сегменте а под данные выделяется несколько сегментов
  • MEDIUM - код в нескольких сегментах а данные в одном
  • LARGE и HUGE - и код и данные в нескольких сегментах
  • FLAT - то же что и TINY но 32-х разрядный

Язык определяет что процедуры рассчитаны на вызов из подпрограмм на языке высокого уровня. Модификатор определяет будет ли сегмент объединяться с сегментом стэка: NEARSTAK и FARSTACK. После установления модели памяти сегменты можно определить упрощенно!
Сегмент кода: .code имя_сегмента
Сегмент стэка: .stack размер
Сегмент данных: .data
Сегмент неинициализированых данных: .data? Сегмент констант: .const
Вообще говоря мы так и будем определять модели памяти и сегменты, так компактнее и нагляднее!

 

Другие директивы

Директива INCLUDE.
Встречая директиву INCLUDE, ассемблер весь текст, хранящийся в указанном файле, подставит в программу вместо этой директивы. В общем случае обращение к директиве INCLUDE (включить) имеет следующий вид: INCLUDE <имя файла> Директиву INCLUDE можно указывать любое число раз и в любых местах программы. В ней можно указать любой файл, причем название файла записыва¬ется по правилам операционной системы. Директива полезна, когда в разных программах используется один и тот же фрагмент текста; чтобы не выписывать этот фрагмент в каждой программе заново, его запи¬сывают в какой-то файл, а затем подключают к программам с помощью данной директивы.

Директивы задания допустимого набора команд. 
По умолчанию разрешено использовать набор команд только процессора 80x86. Для использования новых команд, разрешение нужно явно указать:

  • .186
  • .286 и .286c
  • .286p
  • .386 и .386c  - разрешены непривелигированые команды 80286
  • .386p  - разрешены все команды 80286
  • .MMX

И так можно указывать вплоть до .686. Конечно если версия ассемблера более ранняя чем выход соответствующего процесора то директиву указывать нельзя. На практике достаточно указать .386

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

org выражение
Забегая вперёд, можно сказать что эта директива с параметром 100h используется при написании COM программ которые загружаются в память после блока параметров размеров 256 байт.

Rate this item
(0 votes)
Login to post comments