Tooprogram.ru

Компьютерный справочник
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Сегменты в ассемблере

Директивы сегмантации в ассемблере

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

Каждая программа содержит 3 типа сегментов:

  • Сегмент кодов – содержит машинные команды для выполнения. Обычно первая выполняемая команда находится в начале этого сегмента, и операционная система передает управление по адресу данного сегмента для выполнения программы. Регистр сегмента кодов (CS) адресует данный сегмент.
  • Сегмент данных – содержит данные, константы и рабочие области, необходимые программе. Регистр сегмента данных (DS) адресует данный сегмент.
  • Сегмент стека — содержит адреса возврата как для программы (для возврата в операционную систему), так и для вызовов подпрограмм (для возврата в главную программу), а также используется для передачи параметров в процедуры. Регистр сегмента стека (SS) адресует данный сегмент. Адрес текущей вершины стека задается регистрами SS:ESP.

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

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

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

  • .CODE — для указания начала сегмента кода;
  • .DATA — для указания начала сегмента данных;
  • .STACK — для указания начала сегмента стека.

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

Стандартные директивы сегментации

Наряду с упрощенными директивами сегментации может также использоваться стандартная директива SEGMENT , которая определяет начало любого сегмента. Синтаксис:

Директива ENDS определяет конец сегмента.

Атрибут выравнивания сегмента (тип выравнивания) align сообщает компоновщику о том, что нужно обеспечить размещение начала сегмента на заданной границе. Это важно, поскольку при правильном выравнивании доступ к данным в процессорах, совместимых с базовым i8086, выполняется быстрее. Допустимые значения этого атрибута следующие:

  • BYTE — выравнивание не выполняется. Сегмент может начинаться с любого адреса памяти;
  • WORD — сегмент начинается по адресу, кратному двум, то есть последний (младший) значащий бит физического адреса равен 0 (выравнивание на границу слова);
  • DWORD — сегмент начинается по адресу, кратному четырем, то есть два последних (младших) значащих бита равны 0 (выравнивание по границе двойного слова);
  • PARA — сегмент начинается по адресу, кратному 16, то есть последняя шестнадцатеричная цифра адреса должна быть 0h (выравнивание по границе параграфа);
  • PAGE — сегмент начинается по адресу, кратному 256, то есть две последние шестнадцатеричные цифры должны быть 00h (выравнивание по границе страницы размером 256 байт);
  • MEMPAGE — сегмент начинается по адресу, кратному 4 Кбайт, то есть три последние шестнадцатеричные цифры должны быть 000h (адрес следующей страницы памяти размером 4 Кбайт);

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

  • PRIVATE — сегмент не будет объединяться с другими сегментами с тем же именем вне данного модуля;
  • PUBLIC — заставляет компоновщик соединить все сегменты с одинаковым именем. Новый объединенный сегмент будет целым и непрерывным. Все адреса (смещения) объектов, а это могут быть, в зависимости от типа сегмента, команды или данные, будут вычисляться относительно начала этого нового сегмента;
  • COMMON — располагает все сегменты с одним и тем же именем по одному адресу. Все сегменты с данным именем будут перекрываться и совместно использовать память. Размер полученного в результате сегмента будет равен размеру самого большого сегмента;
  • AT xxxx — располагает сегмент по абсолютному адресу параграфа (параграф — объем памяти, кратный 16, поэтому последняя шестнадцатеричная цифра адреса параграфа равна 0). Абсолютный адрес параграфа задается выражением хххx . Компоновщик располагает сегмент по заданному адресу памяти (это можно использовать, например, для доступа к видеопамяти или области ПЗУ), учитывая атрибут комбинирования. Физически это означает, что сегмент при загрузке в память будет расположен, начиная с этого абсолютного адреса параграфа, но для доступа к нему в соответствующий сегментный регистр должно быть загружено заданное в атрибуте значение. Все метки и адреса в определенном таким образом сегменте отсчитываются относительно заданного абсолютного адреса;
  • STACK — определение сегмента стека. Заставляет компоновщик соединить все одноименные сегменты и вычислять адреса в этих сегментах относительно регистра SS . Комбинированный тип STACK (стек) аналогичен комбинированному типу PUBLIC , за исключением того, что регистр SS является стандартным сегментным регистром для сегментов стека. Регистр SP устанавливается на конец объединенного сегмента стека. Если не указано ни одного сегмента стека, компоновщик выдаст предупреждение, что стековый сегмент не найден. Если сегмент стека создан, а комбинированный тип STACK не используется, программист должен явно загрузить в регистр SS адрес сегмента (подобно тому, как это делается для регистра DS ).

Атрибут размера сегмента dim . Для процессоров i80386 и выше сегменты могут быть 16- или 32-разрядными. Это влияет прежде всего на размер сегмента и порядок формирования физического адреса внутри него. Атрибут может принимать следующие значения:

  • USE16 — это означает, что сегмент допускает 16-разрядную адресацию. При формировании физического адреса может использоваться только 16-разрядное смещение. Соответственно, такой сегмент может содержать до 64 Кбайт кода или данных;
  • USE32 — сегмент будет 32-разрядным. При формировании физического адреса может использоваться 32-разрядное смещение. Поэтому такой сегмент может содержать до 4 Гбайт кода или данных. В модели памяти FLAT используется по умолчанию именно это значение атрибута размера сегмента
Читать еще:  Язык си плюсы и минусы

Атрибут класса сегмента (тип класса) ‘class’ — это заключенная в кавычки строка, помогающая компоновщику определить соответствующий порядок следования сегментов при сборке программы из сегментов нескольких модулей. Компоновщик объединяет вместе в памяти все сегменты с одним и тем же именем класса (имя класса, в общем случае, может быть любым, но лучше, если оно будет отражать функциональное назначение сегмента). Типичным примером использования имени класса является объединение в группу всех сегментов кода программы (обычно для этого используется класс ‘code’ ). С помощью механизма типизации класса можно группировать также сегменты инициализированных и неинициализированных данных.

Все сегменты сами по себе равноправны, так как директивы SEGMENT и ENDS не содержат информации о функциональном назначении сегментов. Для того чтобы использовать их как сегменты кода, данных или стека, необходимо предварительно сообщить транслятору об этом, для чего используют специальную директиву ASSUME . Эта директива сообщает транслятору о том, какой сегмент к какому сегментному регистру привязан. В свою очередь, это позволит транслятору корректно связывать символические имена, определенные в сегментах. Привязка сегментов к сегментным регистрам осуществляется с помощью операндов этой директивы, в которых ИмяСегмента должно быть именем сегмента, определенным в исходном тексте программы директивой SEGMENT или ключевым словом nothing . Если в качестве операнда используется только ключевое слово nothing , то предшествующие назначения сегментных регистров аннулируются, причем сразу для всех шести сегментных регистров. Но ключевое слово nothing можно использовать вместо аргумента ИмяСегмента, в этом случае будет выборочно разрываться связь между сегментом с именем ИмяСегмента и соответствующим сегментным регистром.
Директива SEGMENT может применяться с любой моделью памяти. При использовании директивы SEGMENT с моделью flat требуется указать транслятору, что все сегментные регистры устанавливаются в соответствии с моделью памяти flat . Это можно сделать при помощи директивы ASSUME :

Регистры FS и GS программами не используются, поэтому для них указывается атрибут ERROR .

Упрощенный формат директивы MODEL

MODEL [ ] [др. параметры]

Обязательным параметром директивы MODEL является «модель памяти». Этот параметр определяет модель сегментации памяти для программного модуля.

Возможные значения параметра «модель памяти»:

TINY — Код и данные объединены в одну группу с именем DGROUP. Используется для создания программ формата com .

SMALL — Код занимает один сегмент, данные объединены в одну группу с именем DGROUP. Эту модель обычно используют для большинства программ на языке Assembler .

MEDIUM — Код занимает несколько сегментов, по одному на каждый объединяемый программный модуль. Все ссылки на передачу управления типа far. Данные объединены в одной группе, все ссылки на них типа near.

COMPACT — Код в одном сегменте; ссылка на данные типа far.

LARGE — Код в нескольких сегментах. Каждый объединяемый в одну программу модуль хранится в отдельном сегменте кода.

Параметр «модификатор» директивы MODEL уточняет особенности использования выбранной модели памяти.

Возможные значения параметра «модификатор модели памяти»:

use16 — сегменты выбранной модели используются как 16-битные

use32 — сегменты выбранной модели используются как 32-битные

dos — программа предназначена для работы в ОС MS-DOS

Другие параметры используются при написании программ на разных языках программирования (пока использовать не будем).

Для большинства программ на ассемблере используют директиву

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

Перечислим идентификаторы, создаваемые директивой MODEL:

@code — физический адрес сегмента кода

@data — физический адрес сегмента данных типа near

@fardata — физический адрес сегмента данных типа far

@fardata? — физический адрес сегмента неинициализированных данных типа far

@curseg — физический адрес сегмента неинициализированных данных типа far

@stack — физический адрес сегмента стека

Структура программы на языке Assembler

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

masm ;режим работы TASM: ideal или masm

model small ;модель памяти

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

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

BEGIN : .code ;сегмент кода

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

Текст программы с использованием упрощенных директив сегментации.

Описаны 3 сегмента программы: сегмент данных, сегмент стека и сегмент кода.

В сегменте данных задана строка для вывода на экран.

Размер стека равен 256 байт.

Сегмент кода содержит команды начала и завершения программы, а также комментарии к пропущенным командам.

masm ;режим работы TASM: ideal или masm

model small ;модель памяти

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

message db ‘Привет всем,$’

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

db 256 dup (‘?’) ;сегмент стека

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

main : ;начало программы

mov ax,@data ;заносим в сегментный регистр ds

mov ds,ax ; физический адрес сегмента данных

;здесь будут команды вывода строки на экран

mov ax,4c00h ;пересылка 4c00h в регистр ax

int 21h ;вызов прерывания с номером 21h

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

Сегменты

Дата добавления: 2013-12-23 ; просмотров: 3173 ; Нарушение авторских прав

Псевдокоманды определения переменных

Директивы сегментации. Описание типов данных ассемблера.

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

имя_переменной d* значение

где D* — одна из нижеприведенных псевдокоманд:

DB — определить байт;

DW — определить слово (2 байта);

DD — определить двойное слово (4 байта);

DF — определить 6 байт (адрес в формате 16-битный селектор: 32-битное смещение);

Читать еще:  Условные переходы ассемблер

DQ — определить учетверенное слово (8 байт);

DT — определить 10 байт (80-битные типы данных, используемые FPU).

Поле значения может содержать одно или несколько чисел, строк символов (взятых в одиночные или двойные кавычки), операторов ? и DUP, разделенных запятыми. Все установленные таким образом данные окажутся в выходном файле, а имя переменной будет соответствовать адресу первого из указанных значений. Например, набор директив

text_string db ‘Hello world!’number dw 7table db 1,2,3,4,5,6,7,8,9,0Ah,0Bh,0Ch,0Dh,0Eh,0Fhfloat_number dd 3.5e7

заполняет данными 33 байта. Первые 12 байт содержат ASCII-коды символов строки «Hello world!», и переменная text_string указывает на первую букву в этой строке, так что команда

считает в регистр AL число 48h (код латинской буквы H). Если вместо точного значения указан знак ?, переменная считается неинициализированной и ее значение на момент запуска программы может оказаться любым. Если нужно заполнить участок памяти повторяющимися данными, используется специальный оператор DUP, имеющий формат счетчик DUP (значение). Например, вот такое определение:

table_512w dw 512 dup(?)

создает массив из 512 неинициализированных слов, на первое из которых указывает переменная table_512w. В качестве аргумента в операторе DUP могут выступать несколько значений, разделенных запятыми, и даже дополнительные вложенные операторы DUP.

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

Сегмент программы описывается директивами SEGMENT и ENDS.

имя_сегмента segment readonly выравн. тип разряд ‘класс’ . имя_сегмента ends

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

Все пять операндов директивы SEGMENT необязательны.

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

Выравнивание. Указывает ассемблеру и компоновщику, с какого адреса может начинаться сегмент. Значения этого операнда:

BYTE — с любого адреса;

WORD — с четного адреса;

DWORD — с адреса, кратного 4;

PARA — с адреса, кратного 16 (граница параграфа);

PAGE — с адреса, кратного 256.

По умолчанию используется выравнивание по границе параграфа.

Тип. Выбирает один из возможных типов комбинирования сегментов:

тип PUBLIC (иногда используется синоним MEMORY) означает, что все такие сегменты с одинаковым именем, но разными классами будут объединены в один;

тип STACK — то же самое, что и PUBLIC, но должен использоваться для сегментов стека, потому что при загрузке программы сегмент, полученный объединением всех сегментов типа STACK, будет использоваться как стек;

сегменты типа COMMON с одинаковым именем также объединяются в один, но не последовательно, а по одному и тому же адресу, следовательно, длина суммарного сегмента будет равна не сумме длин объединяемых сегментов, как в случае PUBLIC и STACK, а длине максимального. Таким способом иногда можно формировать оверлейные программы;

тип AT — выражение указывает, что сегмент должен располагаться по фиксированному абсолютному адресу в памяти. Результат выражения, использующегося в качестве операнда для AT, равен этому адресу, деленному на 16. Например: segment at 40h — сегмент, начинающийся по абсолютному адресу 0400h. Такие сегменты обычно содержат только метки, указывающие на области памяти, которые могут потребоваться программе;

PRIVATE (значение по умолчанию) — сегмент такого типа не объединяется с другими сегментами.

Разрядность. Этот операнд может принимать значения USE16 и USE32. Размер сегмента, описанного как USE16, не может превышать 64 Кб, и все команды и адреса в этом сегменте считаются 16-битными. В этих сегментах все равно можно применять команды, использующие 32-битные регистры или ссылающиеся на данные в 32-битных сегментах, но они будут использовать префикс изменения разрядности операнда или адреса и окажутся длиннее и медленнее. Сегменты USE32 могут занимать до 4 Гб, и все команды и адреса в них по умолчанию 32-битные. Если разрядность сегмента не указана, по умолчанию используется USE16 при условии, что перед директивой .MODEL не применялась директива задания допустимого набора команд .386 или старше.

Класс сегмента — это любая метка, взятая в одинарные кавычки. Все сегменты с одинаковым классом, даже сегменты типа PRIVATE, будут расположены в исполняемом файле непосредственно друг за другом.

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

имя_группы group имя_сегмента.

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

Директива ASSUME указывает ассемблеру, с каким сегментом или группой сегментов связан тот или иной сегментный регистр. В качестве операнда «связь» могут использоваться имена сегментов, имена групп, выражения с оператором SEG или слово «NOTHING», означающее отмену действия предыдущей ASSUME для данного регистра. Эта директива не изменяет значений сегментных регистров, а только позволяет ассемблеру проверять допустимость ссылок и самостоятельно вставлять при необходимости префиксы переопределения сегментов, если они необходимы.

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

Читать еще:  Работа в ассемблере

Иллюстрированный самоучитель по Assembler

Сегментная структура программ

Сегменты вводятся в программу с помощью директив ассемблера segment и ends. Что такое директива ассемблера? В тексте программы встречаются ключевые слова двух типов: команды процессора (mov, int) и директивы транслятора (в данном случае термины «транслятор» и «ассемблер» являются синонимами, обозначая программу, преобразующую исходный текст, написанный на языке ассемблера, в коды, которые будут при выполнении программы восприниматься процессором). К директивам ассемблера относятся обозначения начала и конца сегментов segment и ends; ключевые слова, описывающие тип используемых данных (db, dup); специальные описатели сегментов вроде stack и т. д.

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

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

Здесь устанавливается соответствие сегмента code сегментному регистру CS и сегмента data сегментному регистру DS. Первое объявление говорит о том, что сегмент code является сегментом команд, и встречающиеся в этом сегменте метки принадлежат именно этому сегменту, что помогает ассемблеру правильно транслировать команды переходов. В нашей программе меток нет, и эту часть предложения можно было бы опустить, однако в более сложных программах она необходима (при использовании транслятора MASM эта часть объявления необходима в любой, даже самой простой программе).

Второе объявление помогает транслятору правильно обрабатывать предложения, в которых производится обращение к полям данных сегмента data. Выше уже отмечалось, что для обращения к памяти процессору необходимо иметь две составляющие адреса: сегментный адрес и смещение. Сегментный адрес всегда находится в сегментном регистре. Однако в процессоре два сегментных регистра данных, DS и ES, и для обращения к памяти можно использовать любой из них. Разумеется, процессор при выполнении команды должен знать, из какого именно регистра он должен извлечь сегментный адрес, поэтому команды обращения к памяти через регистры DS или ES кодируются по-разному. Объявляя соответствие сегмента data регистру DS, мы предлагаем транслятору использовать вариант кодирования через регистр DS.

Однако отсюда совсем не следует, что к моменту выполнения команды с обращением к памяти в регистре DS будет содержаться сегментный адрес требуемого сегмента. Более того, можно гарантировать, что нужного адреса в сегментном регистре не будет. Директива assume влияет только на кодирование команд, но отнюдь не на содержимое сегментных регистров. Поэтому практически любая программа должна начинаться с предложений, в которых в сегментный регистр, используемый для адресации к сегменту данных (как правило, это регистр DS) заносится сегментный адрес этого сегмента. Так сделано и в нашем примере с помощью двух команд, с которых начинается наша программа:

Сначала значение имени data (т.е. адрес сегмента data) загружается командой mov в регистр общего назначения процессора АХ, а затем из регистра АХ переносится в регистр DS. Такая двухступенчатая операция нужна потому, что процессор в силу некоторых особенностей своей архитектуры не может выполнить команду непосредственной загрузки адреса в сегментный регистр. Приходится пользоваться регистром АХ в качестве «перевалочного пункта».

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

Назначением Программы 1.1 предполагается вывод на экран текстовой строки «Программа работает!», описанной в сегменте данных. Следующие предложения программы как раз и выполняют эту операцию. Делается это не непосредственно, а путем обращения к служебным программам операционной системы MS-DOS, которую мы для краткости будем в дальнейшем называть просто DOS. Дело в том, что в составе команд процессора и, соответственно, операторов языка ассемблера нет команд вывода данных на экран (как и команд ввода с клавиатуры, записи в файл на диске и т.д.).

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

Для того, чтобы обратиться к DOS, надо загрузить в регистр общего назначения АН номер требуемой функции, в другие регистры – исходные данные для выполнения этой функции, после чего выполнить команду hit 21h (int – от interrupt, прерывание), которая передаст управление DOS. Вывод на экран строки текста можно осуществить функцией 09h, которая требует, чтобы в регистрах DS:DX содержался полный адрес выводимой строки. Регистр DS мы уже инициализировали, осталось поместить в регистр DX относительный адрес строки, который ассоциируется с именем поля данных msg. Длину выводимой строки указывать нет необходимости, так как функция 09h DOS выводит на экран строку от указанного адреса до символа доллара, который мы предусмотрительно включили в выводимую строку. Заполнив все требуемые для конкретной функции регистры, можно выполнить команду int 21h, которая осуществит вызов DOS.

Ссылка на основную публикацию
Adblock
detector