Model small ассемблер что это
Перейти к содержимому

Model small ассемблер что это

  • автор:

Model small ассемблер что это

Модели памяти задаются директивой .MODEL

где модель — одно из следующих слов:

TINY — код, данные и стек размещаются в одном и том же сегменте размером до 64 Кб. Эта модель памяти чаще всего используется при написании на ассемблере небольших программ;

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

COMPACT — код размещается в одном сегменте, а для хранения данных могут использоваться несколько сегментов, так что для обращения к данным требуется указывать сегмент и смещение (данные дальнего типа);

MEDIUM — код размещается в нескольких сегментах, а все данные — в одном, поэтому для доступа к данным используется только смещение, а вызовы подпрограмм применяют команды дальнего вызова процедуры;

LARGE и HUGE — и код, и данные могут занимать несколько сегментов;

FLAT — то же, что и TINY, но используются 32-битные сегменты, так что максимальный размер сегмента, содержащего и данные, и код, и стек, — 4 Мб.

Язык — необязательный операнд, принимающий значения C, PASCAL, BASIC, FORTRAN, SYSCALL и STDCALL. Если он указан, подразумевается, что процедуры рассчитаны на вызов из программ на соответствующем языке высокого уровня, следовательно, если указан язык C, все имена ассемблерных процедур, объявленных как PUBLIC, будут изменены так, чтобы начинаться с символа подчеркивания, как это принято в C.

Модификатор — необязательный операнд, принимающий значения NEARSTACK (по умолчанию) или FARSTACK. Во втором случае сегмент стека не будет объединяться в одну группу с сегментами данных.

После того как модель памяти установлена, вступают в силу упрощенные директивы определения сегментов, объединяющие действия директив SEGMENT и ASSUME. Кроме того, сегменты, объявленные упрощенными директивами, не требуется закрывать директивой ENDS — они закрываются автоматически, как только ассемблер обнаруживает новую директиву определения сегмента или конец программы.

Директива .CODE описывает основной сегмент кода

для моделей TINY, SMALL и COMPACT и

для моделей MEDIUM, HUGE и LARGE (name — имя модуля, в котором описан данный сегмент). В этих моделях директива .CODE также допускает необязательный операнд — имя определяемого сегмента, но все сегменты кода, описанные так в одном и том же модуле, объединяются в один сегмент с именем NAME_TEXT.

Директива .STACK описывает сегмент стека и эквивалентна директиве

Необязательный параметр указывает размер стека. По умолчанию он равен 1 Кб.

Описывает обычный сегмент данных и соответствует директиве

Описывает сегмент неинициализированных данных:

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

Описывает сегмент неизменяемых данных:

В некоторых операционных системах этот сегмент будет загружен так, что попытка записи в него может привести к ошибке.

Сегмент дальних данных:

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

Сегмент дальних неинициализированных данных:

Как и в случае с FARDATA, доступ к данным из этого сегмента потребует загрузки сегментного регистра. Если имя сегмента не указано, используется FAR_BSS.

Во всех моделях памяти сегменты, представленные директивами .DATA, .DATA?, .CONST, .FARDATA и .FARDATA?, а также сегмент, описанный директивой .STACK, если не был указан модификатор FARSTACK, и сегмент .CODE в модели TINY автоматически объединяются в группу с именем FLAT — для модели памяти FLAT или DGROUP — для всех остальных моделей. При этом сегментный регистр DS (и SS, если не было FARSTACK, и CS в модели TINY) настраивается на всю эту группу, как если бы была выполнена команда ASSUME.

Model small ассемблер что это

Рис. 1. Формат предложения ассемблера

Рис. 2. Формат директив

Рис. 3. Формат команд и макрокоманд

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

  1. все латинские буквы: A—Z , a—z . При этом заглавные и строчные буквы считаются эквивалентными;
  2. цифры от 0 до 9 ;
  3. знаки ? , @ , $ , _ , & ;
  4. разделители , . [ ] ( ) < >< >+ / * % ! ‘ » ? \ = # ^ .

  • идентификаторы — последовательности допустимых символов, использующиеся для обозначения таких объектов программы, как коды операций, имена переменных и названия меток. Правило записи идентификаторов заключается в следующем: идентификатор может состоять из одного или нескольких символов. В качестве символов можно использовать буквы латинского алфавита, цифры и некоторые специальные знаки — _, ?, $, @. Идентификатор не может начинаться символом цифры. Длина идентификатора может быть до 255 символов, хотя транслятор воспринимает лишь первые 32, а остальные игнорирует. Регулировать длину возможных идентификаторов можно с использованием опции командной строки mv . Кроме этого существует возможность указать транслятору на то, чтобы он различал прописные и строчные буквы либо игнорировал их различие (что и делается по умолчанию). Для этого применяются опции командной строки /mu, /ml, /mx ;
  • цепочки символов — последовательности символов, заключенные в одинарные или двойные кавычки;
  • целые числа в одной из следующих систем счисления: двоичной, десятичной, шестнадцатеричной . Отождествление чисел при записи их в программах на ассемблере производится по определенным правилам:
    • Десятичные числа не требуют для своего отождествления указания каких-либо дополнительных символов, например 25 или 139.
    • Для отождествления в исходном тексте программы двоичных чисел необходимо после записи нулей и единиц, входящих в их состав, поставить латинское “ b ”, например 10010101 b .
    • Шестнадцатеричные числа имеют больше условностей при своей записи:
      • Во-первых , они состоят из цифр 0. 9 , строчных и прописных букв латинского алфавита a , b , c , d , e , f или A , B , C , D , E , F .
      • Во-вторых , у транслятора могут возникнуть трудности с распознаванием шестнадцатеричных чисел из-за того, что они могут состоять как из одних цифр 0. 9 (например 190845), так и начинаться с буквы латинского алфавита (например ef15 ). Для того чтобы «объяснить» транслятору, что данная лексема не является десятичным числом или идентификатором, программист должен специальным образом выделять шестнадцатеричное число. Для этого на конце последовательности шестнадцатеричных цифр, составляющих шестнадцатеричное число, записывают латинскую букву “ h ”. Это обязательное условие. Если шестнадцатеричное число начинается с буквы, то перед ним записывается ведущий ноль: 0 ef15 h .

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

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

      • постоянные, или непосредственные, операнды
      • адресные операнды
      • перемещаемые операнды
      • счетчик адреса
      • регистровый операнд
      • базовый и индексный операнды
      • структурные операнды
      • Записи

      Рис. 4. Синтаксис описания адресных операндов

      Результатом вычисления выражения может быть адрес некоторой ячейки памяти или некоторое константное (абсолютное) значение.

      • Арифметические операторы
      • Операторы сдвига
      • Операторы сравнения
      • Логические операторы
      • Индексный оператор
      • Оператор переопределения типа
      • Оператор переопределения сегмента
      • Оператор именования типа структуры
      • Оператор получения сегментной составляющей адреса выражения
      • Оператор получения смещения выражения

      • Арифметические операторы . К ним относятся:
        • унарные “ + ” и “ – ”;
        • бинарные “ + ” и “ – ”;
        • умножения “ * ”;
        • целочисленного деления “ / ”;
        • получения остатка от деления “ mod ”.

        Рис. 7. Синтаксис операторов сравнения

        Таблица 1. Операторы сравнения

        Рис. 9. Синтаксис индексного оператора

        Заметим, что в литературе по ассемблеру принято следующее обозначение: когда в тексте речь идет о содержимом регистра, то его название берут в круглые скобки. Мы также будем придерживаться этого обозначения.
        К примеру, в нашем случае запись в комментариях последнего фрагмента программы mas + (si) означает вычисление следующего выражения: значение смещения символического имени mas плюс содержимое регистра si.

        Рис. 13. Синтаксис оператора получения смещения

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

        Таблица 2. Операторы и их приоритет

        Оператор Приоритет
        length, size, width, mask, (, ), [, ], <, > 1
        . 2
        : 3
        ptr, offset, seg, type, this 4
        high, low 5
        +, — (унарные) 6
        *, /, mod, shl, shr 7
        +, -, (бинарные) 8
        eq, ne, lt, le, gt, ge 9
        not 10
        and 11
        or, xor 12
        short, type 13

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

        • с одним сегментом кода;
        • с одним сегментом стека;
        • с одним сегментом данных;
        • с тремя дополнительными сегментами данных.

        Синтаксическое описание сегмента на ассемблере представляет собой конструкцию, изображенную на рис. 14:

        Рис. 14. Синтаксис описания сегмента

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


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

          Рис. 15. Директива ASSUME

          На уроке 3 мы рассматривали пример программы с директивами сегментации. Эти директивы изначально использовались для оформления программы в трансляторах MASM и TASM. Поэтому их называют стандартными директивами сегментации .

          Для простых программ, содержащих по одному сегменту для кода, данных и стека, хотелось бы упростить ее описание. Для этого в трансляторы MASM и TASM ввели возможность использования упрощенных директив сегментации . Но здесь возникла проблема, связанная с тем, что необходимо было как-то компенсировать невозможность напрямую управлять размещением и комбинированием сегментов. Для этого совместно с упрощенными директивами сегментации стали использовать директиву указания модели памяти MODEL , которая частично стала управлять размещением сегментов и выполнять функции директивы ASSUME (поэтому при использовании упрощенных директив сегментации директиву ASSUME можно не использовать). Эта директива связывает сегменты, которые в случае использования упрощенных директив сегментации имеют предопределенные имена, с сегментными регистрами (хотя явно инициализировать ds все равно придется).

          В листинге 1 приведен пример программы с использованием упрощенных директив сегментации:
          Синтаксис директивы MODEL показан на рис. 16.

          Рис. 16. Синтаксис директивы MODEL

          Обязательным параметром директивы MODEL является модель памяти . Этот параметр определяет модель сегментации памяти для программного модуля. Предполагается, что программный модуль может иметь только определенные типы сегментов, которые определяются упомянутыми нами ранее упрощенными директивами описания сегментов . Эти директивы приведены в табл. 3.

          Таблица 3. Упрощенные директивы определения сегмента

          Формат директивы
          (режим MASM)
          Формат директивы
          (режим IDEAL)
          Назначение
          .CODE [имя] CODESEG[имя] Начало или продолжение сегмента кода
          .DATA DATASEG Начало или продолжение сегмента инициализированных данных. Также используется для определения данных типа near
          .CONST CONST Начало или продолжение сегмента постоянных данных (констант) модуля
          .DATA? UDATASEG Начало или продолжение сегмента неинициализированных данных. Также используется для определения данных типа near
          .STACK [размер] STACK [размер] Начало или продолжение сегмента стека модуля. Параметр [размер] задает размер стека
          .FARDATA [имя] FARDATA [имя] Начало или продолжение сегмента инициализированных данных типа far
          .FARDATA? [имя] UFARDATA [имя] Начало или продолжение сегмента неинициализированных данных типа far
          Наличие в некоторых директивах параметра [имя] говорит о том, что возможно определение нескольких сегментов этого типа. С другой стороны, наличие нескольких видов сегментов данных обусловлено требованием обеспечить совместимость с некоторыми компиляторами языков высокого уровня, которые создают разные сегменты данных для инициализированных и неинициализированных данных, а также констант.

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

          Таблица 4. Идентификаторы, создаваемые директивой MODEL

          Имя идентификатора Значение переменной
          @code Физический адрес сегмента кода
          @data Физический адрес сегмента данных типа near
          @fardata Физический адрес сегмента данных типа far
          @fardata? Физический адрес сегмента неинициализированных данных типа far
          @curseg Физический адрес сегмента неинициализированных данных типа far
          @stack Физический адрес сегмента стека
          Если вы посмотрите на текст листинга 1 , то увидите пример использования одного из этих идентификаторов. Это @data ; с его помощью мы получили значение физического адреса сегмента данных нашей программы.

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

          Таблица 5. Модели памяти

          Модель Тип кода Тип данных Назначение модели
          TINY near near Код и данные объединены в одну группу с именем DGROUP.
          Используется для создания программ формата .com.
          SMALL near near Код занимает один сегмент, данные объединены в одну группу с именем DGROUP.
          Эту модель обычно используют для большинства программ на ассемблере
          MEDIUM far near Код занимает несколько сегментов, по одному на каждый объединяемый программный модуль.
          Все ссылки на передачу управления — типа far.
          Данные объединены в одной группе; все ссылки на них — типа near
          COMPACT near far Код в одном сегменте;
          ссылка на данные — типа far
          LARGE far far Код в нескольких сегментах, по одному на каждый объединяемый программный модуль
          Параметр модификатор директивы MODEL позволяет уточнить некоторые особенности использования выбранной модели памяти (табл. 6).

          Таблица 6. Модификаторы модели памяти

          Значение модификатора Назначение
          use16 Сегменты выбранной модели используются как 16-битные (если соответствующей директивой указан процессор i80386 или i80486)
          use32 Сегменты выбранной модели используются как 32-битные (если соответствующей директивой указан процессор i80386 или i80486)
          dos Программа будет работать в MS-DOS
          Необязательные параметры язык и модификатор языка определяют некоторые особенности вызова процедур. Необходимость в использовании этих параметров появляется при написании и связывании программ на различных языках программирования.

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

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

          Что означает .model small в программах 8086?

          Я новичок в ассемблере 8086. Я могу понять логику, используемую в программе, и сам писать небольшие программы. Но я просто хочу знать, что это делает:

          Какое объяснение для .model small?

          Я использую masm .

          2 ответа

          С .model tiny вы получаете программу, в которой CS , DS и SS все указывают на одни и те же 64 КБ памяти. Стек размещается в самой высокой области этого сегмента размером 64 КБ.

          С помощью .model small вы получаете программу, в которой CS указывает на собственный сегмент, за которым следует сегмент, на который указывают DS и SS . Стек помещается в самую верхнюю часть сегмента SS .

          Директива .stack 300h сообщает MASM размер стека, поэтому MASM может предупредить вас, когда остальная часть программы (данные, bss, куча) столкнется со стеком.

          В обеих этих моделях весь доступ к элементам данных осуществляется с помощью ближайших указателей.

          Поскольку 8086 является 16-разрядной архитектурой, у него возникают трудности с доступом к более чем 64 КБ памяти.

          Самый эффективный способ использования указателей — использовать выделенные 16-битные регистры (например, bx ). Однако, когда ваша программа хочет получить доступ к более чем 64 КБ, она также должна использовать регистры сегментов (например, es ). Чтобы разрешить оба способа адресации, были изобретены модели памяти.

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

          Он имеет следующие эффекты:

          Вам разрешено писать инструкцию retn (возврат из подпрограммы near ) как ret . Поскольку ассемблер знает, что весь ваш код находится в одном сегменте, все ваши подпрограммы будут иметь формат near (т.е. иметь 16-битный адрес), а все инструкции ret означают retn .

          Звучит глупо и незначительно? Читай дальше.

          Если ваш код разбросан по нескольким исходным файлам, у вас будут инструкции call , которые вызывают подпрограммы, о которых ассемблер ничего не знает. Когда вы используете небольшую модель памяти, она знает, по крайней мере, что каждая подпрограмма имеет 16-битный адрес, и можно использовать код операции вызова near .

          Вы можете написать код, не объявляя свою модель памяти, но тогда вам придется call near вместо просто call .

          Если все ваши исходные файлы объявляют .model small , компоновщик возьмет все сегменты кода и попытается уместить их все в 64 КБ (то же самое для сегментов данных). Это может потерпеть неудачу, если материал слишком большой.

          Небольшие программы на ассемблере обычно не заботятся о модели памяти — кода размером 64 КБ более чем достаточно для написания сложных программ, если вы не используете большую внешнюю библиотеку. В таком случае .model small может означать «Меня не волнует эта модель памяти, просто используйте значение по умолчанию».

          Model small ассемблер что это

          .286 ;Разрешает ассемблирование непривилегированных инструкций
          ;процессора 80286 (реальный режим) и инструкций арифметического
          ;сопроцессора 80287.

          CSEG segment ;Даём имя сегменту, а точнее определяем абсолютный
          ;сегмент в памяти программ по определённому адресу.
          ;Имя нашего сегмента будет CSEG.

          assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG ;Задаём сегментные регистры, которые будем использовать для
          ;вычисления действующего адреса для всех меток и переменных, опре-
          ;делённых для сегмента или группы сегментов с указанным именем.
          ;У нас их четыре, — CS, DS, ES, SS и они будут указывать на наш
          ;единственный сегмент (мы его назвали CSEG).

          org 100h ;Устанавливаем счётчик инструкций в текущем сегменте в соот-
          ;ветствии с адресом, задаваемым "выражением".
          ;Сейчас этот счётчик равен 100h — используется для всех программ
          ;типа .com

          begin: ;Метка начала программы.

          ;Выводим фразу, используя функцию 9 прерывания 21h.
          mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
          mov dx,offset helloworld ;Указываем, что за фразу мы будем выводить.
          int 21h ;Выводим фразу.

          int 20h ;Выходим в DOS.

          helloworld db ‘Hello, world!$’ ;Определяем переменную helloworld, доступную побайтно, с фразой
          ;"Hello, world!". В одинарных кавычках, после знака "!" ставим
          ;знак "$".

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

          Давайте попробуем вывести на экран DOS две фразы вида "Hello, world!" и "Hello, world! (2)". Как это сделать привычным нам способом вы, думаю, знаете:

          .286 ;Разрешает ассемблирование непривилегированных инструкций
          ;процессора 80286 (реальный режим) и инструкций арифметического
          ;сопроцессора 80287.

          CSEG segment ;Даём имя сегменту, а точнее определяем абсолютный
          ;сегмент в памяти программ по определённому адресу.
          ;Имя нашего сегмента будет CSEG.

          assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG ;Задаём сегментные регистры, которые будем использовать для
          ;вычисления действующего адреса для всех меток и переменных, опре-
          ;делённых для сегмента или группы сегментов с указанным именем.
          ;У нас их четыре, — CS, DS, ES, SS и они будут указывать на наш
          ;единственный сегмент (мы его назвали CSEG).

          org 100h ;Устанавливаем счётчик инструкций в текущем сегменте в соот-
          ;ветствии с адресом, задаваемым "выражением".
          ;Сейчас этот счётчик равен 100h — используется для всех программ
          ;типа .com

          begin: ;Метка начала программы.

          ;Выводим фразу, используя функцию 9 прерывания 21h.
          mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
          mov dx,offset helloworld1 ;Указываем, что за фразу мы будем выводить.
          int 21h ;Выводим фразу.

          ;Выводим фразу, используя функцию 9 прерывания 21h.
          mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
          mov dx,offset helloworld2 ;Указываем, что за фразу мы будем выводить.
          int 21h ;Выводим фразу.

          int 20h ;Выходим в DOS.

          helloworld1 db ‘Hello, world!$’ ;Определяем переменную helloworld1, доступную побайтно, с фразой
          ;"Hello, world!". В одинарных кавычках, после знака "!" ставим
          ;знак "$".
          helloworld2 db ‘Hello, world! (2)$’ ;Определяем переменную helloworld2, доступную побайтно, с фразой
          ;"Hello, world!". В одинарных кавычках, после знака "!" ставим
          ;знак "$".

          А теперь поместим данные об этих фразах в два разных сегмента. Внимание! До нынешнего момента мы создавали программы .com, работающие только с одним сегментом! Пора перейти к созданию *.exe файлов. Создаём нижеследующий файл test.asm в кодировке 866 и вводим нашу команду без атрибутов: ml test.asm:

          .286 ;Разрешает ассемблирование непривилегированных инструкций
          ;процессора 80286 (реальный режим) и инструкций арифметического
          ;сопроцессора 80287.

          MESSAGE1 segment USE16 ;Начало сегмента MESSAGE1
          ;Сообщаем ассемблеру, что внутрисегментная адресация является
          ;16-ти разрядной — use16

          helloworld1 db ‘Hello, world!$’ ;Определяем переменную helloworld1, доступную побайтно, с фразой
          ;"Hello, world!". В одинарных кавычках, после знака "!" ставим
          ;знак "$".
          MESSAGE1 ends ;Указываем на завершение сегмента MESSAGE1.

          MESSAGE2 segment USE16 ;Начало сегмента MESSAGE2
          ;Сообщаем ассемблеру, что внутрисегментная адресация является
          ;16-ти разрядной — use16

          helloworld2 db ‘Hello, world! (2)$’ ;Определяем переменную helloworld2, доступную побайтно, с фразой
          ;"Hello, world!". В одинарных кавычках, после знака "!" ставим
          ;знак "$".
          MESSAGE2 ends ;Указываем на завершение сегмента MESSAGE2.

          CSEG segment USE16 ;Начало сегмента CSEG — в котором идёт наш код программы.
          ;Сообщаем ассемблеру, что внутрисегментная адресация является
          ;16-ти разрядной — use16

          assume cs:CSEG, ds:MESSAGE1 ;Для сегментирования адресов из сегмента CSEG выбирается регистр cs,
          ;а для сегментирования адресов из сегмента MESSAGE1 — ds.
          ;org 100h ;Внимание! Директива org 100h не используется.
          begin: ;Начало программы — точка входа.

          mov ax,MESSAGE1 ;Заносим адрес первого сегмента в ds черех ax.
          mov ds,ax ;

          mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
          mov dx,offset helloworld1 ;Указываем, что за фразу мы будем выводить.
          int 21h ;Выводим фразу.

          mov ax,MESSAGE2 ;Заносим адрес второго сегмента в ds черех ax.
          mov ds,ax ;

          mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
          mov dx,offset helloworld2 ;Указываем, что за фразу мы будем выводить.
          int 21h ;Выводим фразу.

          ;Выходим в DOS
          mov ah,4Ch ;Используем вместо int 20h
          int 21h

          MESSAGE1 segment USE16 — Без "USE16" программа-ассемблер будет использовать USE32 (т.е. 32-х разрядные данные) и выдавать ошибку на строках вида mov dx,offset Message;

          assume cs:CSEG, ds:MESSAGE1 — Указываем masm, что в значение сегмента CSEG установлен сегментный регистр cs, а в значение сегмента MESSAGE1 установлен сегментный регистр ds;

          В сегменте MESSAGE1 хранится наша первая строка, мы и направляем его в ds;

          ;org 100h — Все COM программы начинаются с адреса 100h. Директива "org 100h" нужна для резервирования места для PSP (префикс программного сегмента). Когда COM-программа начинает работать, все сегментные регистры содержат адрес PSP — 256-байтового (т.е. 100h в шестнадцатеричном исчислении) блока, который резервируется операционной системой DOS непосредственно перед COM или EXE программой в памяти. Так как адресация начинается со шестнадцатеричного смещения 100 от начала PSP, то в программе после оператора SEGMENT кодируется директива ORG 100h. EXE файлы же используют дополнительно регистры (и стек), и необходимость непосредственного указания в адресации со шестнадцатеричного смещения 100 отпадает (но PSP остаётся!).

          mov ax,MESSAGE1
          mov ds,ax
          — Заносим данные о MESSAGE1 в ds, но не напрямую, а через регистр ax (напрямую занести их нельзя). Направляем регистр ds на наш первый сегмент. Чуть дальше мы определим в dx смещение для фразы "Hello, world!". Теперь нам должны быть понятен текст вроде: "Данные о фразе ‘Hello, world!’ хранятся в DS:DX". Далее мы сменим сегмент и определим в dx новое смещение;

          mov ah,4Ch — Раньше для выхода в DOS мы использовали прерывание 20h. Для *.exe-программы мы будем использовать функцию 4Ch прерывания 21h.

          Усложним немного пример, но для начала расскажем о моделях памяти и директиве .model. Данная директива указывается в начале кода программы.

          Модели памяти

          Модель Тип кода Тип данных Назначение модели
          TINY near near Код, данные и стек объединены в одну группу с именем DGROUP и размером до 64Кб. Используется для создания программ формата .com. Некоторые языки эту модель не поддерживают.
          СS=DS=SS=DGROUP
          SMALL near near Код занимает один сегмент, данные и стек объединены в одну группу с именем DGROUP (хотя для описания могут использоваться разные сегменты). Эту модель обычно используют для большинства программ на ассемблере.
          CS=_text
          DS=SS=DGROUP
          MEDIUM far near Код занимает несколько сегментов, по одному на каждый объединяемый программный модуль. Все ссылки на передачу управления — типа far (вызов подпрограмм). Данные и стек объединены в одной группе DGROUP; все ссылки на них — типа near (для доступа к данным используется только смещение).
          CS=<модуль>_text
          DS=SS=DGROUP
          COMPACT near far Код находится в одном сегменте, данные и стек в группе DGROUP и могут занимать несколько сегментов, так что для обращения к данным требуется указывать сегмент и смещение (ссылка на данные — типа far).
          CS=_text
          DS=SS=DGROUP
          LARGE far far Код может занимать несколько сегментов, по одному на каждый объединяемый программный модуль. Стек и данные находятся в группе DGROUP. Для ссылки на данные используются дальние указатели — far.
          CS=<модуль>_text
          DS=SS=DGROUP
          HUGE far far Тоже что и модель LARGE, что касается TurboAssebmler.
          FLAT far far Тоже, что и TINY, но используются 32-битная адресация, так что максимальный размер сегмента, содержащего и данные, и код, и стек — 4Гб.

          Автор таблицы: asmforfun

          А теперь давайте усложним предыдущий пример. Все данные у нас будут находиться в одном сегменте, а исполняемый код — сначала в одном, потом в другом. То есть в определённый момент мы "прыгнем" из сегмента в сегмент. Посмотрим, как это будет выглядеть:

          .286 ;Разрешает ассемблирование непривилегированных инструкций
          ;процессора 80286 (реальный режим) и инструкций арифметического
          ;сопроцессора 80287.

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

          SST segment stack "stack" ;Начало сегмента SST, определяющего стек.
          dw 10 dup(?) ;Резервируем область памяти.
          SST ENDS ;Указываем на завершение сегмента SST.

          DATAS segment USE16 ;Начало сегмента DATAS
          ;Сообщаем ассемблеру, что внутрисегментная адресация является
          ;16-ти разрядной — use16

          helloworld1 db ‘Hello, world!$’ ;Определяем переменную helloworld1, доступную побайтно, с фразой
          ;"Hello, world!". В одинарных кавычках, после знака "!" ставим
          ;знак "$".

          helloworld2 db ‘Hello, world! (2)$’ ;Определяем переменную helloworld2, доступную побайтно, с фразой
          ;"Hello, world!". В одинарных кавычках, после знака "!" ставим
          ;знак "$".
          DATAS ends ;Указываем на завершение сегмента DATAS2.

          DATA2 segment USE16
          newmetka:
          mov ax,DATAS
          mov ds,ax

          mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
          mov dx,offset helloworld2 ;Указываем, что за фразу мы будем выводить.
          int 21h ;Выводим фразу.

          ;Выходим в DOS
          mov ah,4Ch ;Используем вместо int 20h
          int 21h

          CSEG segment USE16 ;Начало сегмента CSEG — в котором идёт наш код программы.
          ;Сообщаем ассемблеру, что внутрисегментная адресация является
          ;16-ти разрядной — use16

          assume cs:CSEG, ds:DATAS, es:DATA2, ss:SST ;

          begin: ;Начало программы — точка входа.

          mov ax,DATAS
          mov ds,ax

          mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
          mov dx,offset helloworld1 ;Указываем, что за фразу мы будем выводить.
          int 21h ;Выводим фразу.

          mov ax,DATA2
          mov ds,ax

          JMP FAR PTR newmetka

          Команда jmp имеет пять разновидностей:
          — переход прямой короткий (в пределах -128. + 127 байтов);
          — переход прямой ближний (в пределах текущего программного сегмента) ;
          — переход прямой дальний (в другой программный сегмент);
          — переход косвенный ближний;
          — переход косвенный дальний.
          Все разновидности переходов имеют одну и ту же мнемонику jmp, хотя и различающиеся коды операций. Во многих случаях транслятор может определить вид перехода по контексту, в тех же случаях, когда это невозможно, следует использовать атрибутные операторы:

          short — прямой короткий переход;
          near ptr — прямой ближний переход;
          far ptr — прямой дальний переход;
          word ptr — косвенный ближний переход;
          dword ptr — косвенный дальний переход.

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

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

          COMPACT – код размещается в одном сегменте, а для хранения данных могут использоваться несколько сегментов, так что для обращения к данным требуется указывать сегмент и смещение (данные дальнего типа);

          MEDIUM – код размещается в нескольких сегментах, а все данные – в одном, поэтому для доступа к данным используется только смещение, а вызовы подпрограмм применяют команды дальнего вызова процедуры;

          LARGE и HUGE – и код, и данные могут занимать несколько сегментов;

          FLAT – то же, что и TINY, но используются 32-битные сегменты, так что максимальный размер сегмента, содержащего и данные, и код, и стек, – 4 Мб.

          Язык – необязательный операнд, принимающий значения C, PASCAL, BASIC, FORTRAN, SYSCALL и STDCALL. Если он указан, подразумевается, что процедуры рассчитаны на вызов из программ на соответствующем языке высокого уровня, следовательно, если указан язык C, все имена ассемблерных процедур, объявленных как PUBLIC, будут изменены так, чтобы начинаться с символа подчеркивания, как это принято в C.

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

          После того как модель памяти установлена, вступают в силу упрощенные директивы определения сегментов, объединяющие действия директив SEGMENT и ASSUME. Кроме того, сегменты, объявленные упрощенными директивами, не требуется закрывать директивой ENDS – они закрываются автоматически, как только ассемблер обнаруживает новую директиву определения сегмента или конец программы.

          Директива .CODE описывает основной сегмент кода

          _TEXT segment word public ’CODE’

          для моделей TINY, SMALL и COMPACT и

          name_TEXT segment word public ’CODE’

          для моделей MEDIUM, HUGE и LARGE (name – имя модуля, в котором описан данный сегмент). В этих моделях директива .CODE также допускает необязательный операнд – имя определяемого сегмента, но все сегменты кода, описанные так в одном и том же модуле, объединяются в один сегмент с именем NAME_TEXT.

          Директива .STACK описывает сегмент стека и эквивалентна директиве

          STACK segment para public ’stack’

          Необязательный параметр указывает размер стека. По умолчанию он равен 1 Кб.

          Описывает обычный сегмент данных и соответствует директиве

          _DATA segment word public ’DATA’

          Описывает сегмент неинициализированных данных:

          _BSS segment word public ’BSS’

          Этот сегмент обычно не включается в программу, а располагается за концом памяти, так что все описанные в нём переменные на момент загрузки программы имеют неопределённые значения.

          Описывает сегмент неизменяемых данных:

          CONST segment word public ’CONST’

          В некоторых операционных системах этот сегмент будет загружен так, что попытка записи в него может привести к ошибке.

          Сегмент дальних данных:

          имя_сегмента segment para private ’FAR_DATA’

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

          Сегмент дальних неинициализированных данных:

          имя_сегмента segment para private ’FAR_BSS’

          Как и в случае с FARDATA, доступ к данным из этого сегмента потребует загрузки сегментного регистра. Если имя сегмента не указано, используется FAR_BSS.

          Во всех моделях памяти сегменты, представленные директивами .DATA, .DATA?, .CONST, .FARDATA и .FARDATA?, а также сегмент, описанный директивой .STACK, если не был указан модификатор FARSTACK, и сегмент .CODE в модели TINY автоматически объединяются в группу с именем FLAT – для модели памяти FLAT или DGROUP – для всех остальных моделей. При этом сегментный регистр DS (и SS, если не было FARSTACK, и CS в модели TINY) настраивается на всю эту группу, как если бы была выполнена команда ASSUME.

          Организация и модели памяти, адресация

          Память – способность объекта обеспечивать хранение данных.
          Все объекты, над которыми выполняются команды, как и сами команды, хранятся в памяти компьютера.

          Память состоит из ячеек, в каждой из которых содержится 1 бит информации, принимающий одно из двух значений: 0 или 1. Биты обрабатывают группами фиксированного размера. Для этого группы бит могут записываться и считываться за одну базовую операцию. Группа из 8 бит называется .
          Расположение битов в байте
          Байты последовательно располагаются в памяти компьютера.

          • 1 килобайт (Кбайт) = 2 10 = 1 024 байт
          • 1 мегабайт (Мбайт) = 2 10 Кбайт = 2 20 байт = 1 048 576 байт
          • 1 гигабайт (Гбайт) = 2 10 Мбайт = 2 30 байт = 1 073 741 824 байт

          Для доступа к памяти с целью записи или чтения отдельных элементов информации используются идентификаторы , определяющие их расположение в памяти. Каждому идентификатору в соответствие ставится адрес . В качестве адресов используются числа из диапазона от 0 до 2 k -1 со значением k, достаточным для адресации всей памяти компьютера.Все 2 k адресов составляют адресное пространство компьютера .

          Способы адресации байтов

          Существует прямой и обратный способы адресации байтов.
          При обратном способе адресации байты адресуются слева направо, так что самый старший (левый) байт слова имеет наименьший адрес.
          Обратная адресация

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

          Организация памяти

          Физическая память, к которой микропроцессор имеет доступ по шине адреса, называется оперативной памятью ОП (или оперативным запоминающим устройством — ОЗУ).
          Механизм управления памятью полностью аппаратный, т.е. программа сама не может сформировать физический адрес памяти на адресной шине.
          Микропроцессор аппаратно поддерживает несколько моделей использования оперативной памяти:

          • сегментированную модель
          • страничную модель
          • плоскую модель

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

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

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

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

          Регистры дополнительных сегментов ( ES, FS, GS ), предназначены для специального использования.

          Для доступа к данным внутри сегмента обращение производится относительно начала сегмента линейно, т.е. начиная с 0 и заканчивая адресом, равным размеру сегмента. Для обращения к любому адресу в программе, компьютер складывает адрес в регистре сегмента и смещение — расположение требуемого адреса относительно начала сегмента. Например, первый байт в сегменте кодов имеет смещение 0, второй байт – 1 и так далее.

          Таким образом, для обращения к конкретному физическому адресу ОЗУ необходимо определить адрес начала сегмента и смещение внутри сегмента.
          Физический адрес принято записывать парой этих значений, разделенных двоеточием

          сегмент : смещение

          Страничная модель памяти – это надстройка над сегментной моделью. ОЗУ делится на блоки фиксированного размера, кратные степени 2, например 4 Кб. Каждый такой блок называется страницей . Основное достоинство страничного способа распределения памяти — минимально возможная фрагментация. Однако такая организация памяти не использует память достаточно эффективно за счет фиксированного размера страниц.

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

          • при использовании плоской модели памяти упрощается создание и операционной системы, и систем программирования;
          • уменьшаются расходы памяти на поддержку системных информационных структур.

          В абсолютном большинстве современных 32(64)-разрядных операционных систем (для микропроцессоров Intel) используется плоская модель памяти.

          Модели памяти

          Директива .MODEL определяет модель памяти, используемую программой. После этой директивы в программе находятся директивы объявления сегментов ( .DATA, .STACK, .CODE, SEGMENT ). Синтаксис задания модели памяти

          .MODEL модификатор МодельПамяти СоглашениеОВызовах

          Параметр МодельПамяти является обязательным.

          Основные модели памяти:

          Модель памяти Адресация кода Адресация данных Операци-
          онная система
          Чередование кода и данных
          TINY NEAR NEAR MS-DOS Допустимо
          SMALL NEAR NEAR MS-DOS, Windows Нет
          MEDIUM FAR NEAR MS-DOS, Windows Нет
          COMPACT NEAR FAR MS-DOS, Windows Нет
          LARGE FAR FAR MS-DOS, Windows Нет
          HUGE FAR FAR MS-DOS, Windows Нет
          FLAT NEAR NEAR Windows NT, Windows 2000, Windows XP, Windows Vista Допустимо

          Модель tiny работает только в 16-разрядных приложениях MS-DOS. В этой модели все данные и код располагаются в одном физическом сегменте. Размер программного файла в этом случае не превышает 64 Кбайт.
          Модель small поддерживает один сегмент кода и один сегмент данных. Данные и код при использовании этой модели адресуются как near (ближние).
          Модель medium поддерживает несколько сегментов программного кода и один сегмент данных, при этом все ссылки в сегментах программного кода по умолчанию считаются дальними (far), а ссылки в сегменте данных — ближними (near).
          Модель compact поддерживает несколько сегментов данных, в которых используется дальняя адресация данных (far), и один сегмент кода с ближней адресацией (near).
          Модель large поддерживает несколько сегментов кода и несколько сегментов данных. По умолчанию все ссылки на код и данные считаются дальними (far).
          Модель huge практически эквивалентна модели памяти large.

          Особого внимания заслуживает модель памяти flat , которая используется только в 32-разрядных операционных системах. В ней данные и код размещены в одном 32-разрядном сегменте. Для использования в программе модели flat перед директивой .model flat следует разместить одну из директив:

          • .386
          • .486
          • .586
          • .686

          Желательно указывать тот тип процессора, который используется в машине, хотя это не является обязательным требованием. Операционная система автоматически инициализирует сегментные регистры при загрузке программы, поэтому модифицировать их нужно только в случае если требуется смешивать в одной программе 16-разрядный и 32-разрядный код. Адресация данных и кода является ближней ( near ), при этом все адреса и указатели являются 32-разрядными.

          Параметр модификатор используется для определения типов сегментов и может принимать значения use16 (сегменты выбранной модели используются как 16-битные) или use32 (сегменты выбранной модели используются как 32-битные).

          Параметр СоглашениеОВызовах используется для определения способа передачи параметров при вызове процедуры из других языков, в том числе и языков высокого уровня (C++, Pascal). Параметр может принимать следующие значения:

          • C,
          • BASIC,
          • FORTRAN,
          • PASCAL,
          • SYSCALL,
          • STDCALL.

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

          Assembler

          Ассемблер имеет ряд операторов , которые позволяют управлять процессом ассемблирования и формирования листинга . Эти операторы называются псевдокомандами или директивами . Они действуют только в процессе ассемблирования программы и не генерируют машинных кодов . Директивы формирования листинга не очень интересны, так как вывести на печать текст программы не составляет труда для любого современного редактора.
          Директивы ассемблирования буду изучать и по ходу дела добавлять их в блог. К сожалению, стройного и полного описания директив ассемблирования и примеров, разъясняющих их назначение и разницу между их параметрами, нет ни в одной из имеющихся у меня книг. Более-менее они описаны опять же у старичка Абеля (но книжка все таки старовата) и не плохое есть описание есть у Юрова, но что тот, что другой не приводят ни каких примеров, которые бы пояснили разницу между параметрами директивы SEGMENT и т.д. Так что придется все исследовать самостоятельно.

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

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

          Синтаксическое описание сегмента на ассемблере представляет собой конструкцию, изображенную на рисунке ниже:

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

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

            По умолчанию тип выравнивания имеет значение PARA.

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

            По умолчанию атрибут комбинирования принимает значение PRIVATE.

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

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

            Стандартные директивы сегментации изначально использовались для оформления программы в трансляторах MASM и TASM. Поэтому их называют стандартными директивами сегментации . Для простых программ, содержащих по одному сегменту для кода, данных и стека, хотелось бы упростить ее описание. Для этого в трансляторы MASM и TASM ввели возможность использования упрощенных директив сегментации . Но здесь возникла проблема, связанная с тем, что необходимо было как-то компенсировать невозможность напрямую управлять размещением и комбинированием сегментов. Для этого совместно с упрощенными директивами сегментации стали использовать директиву указания модели памяти MODEL , которая частично стала управлять размещением сегментов и выполнять функции директивы ASSUME (поэтому при использовании упрощенных директив сегментации директиву ASSUME можно не использовать). Директива MODEL связывает сегменты, которые в случае использования упрощенных директив сегментации имеют предопределенные имена, с сегментными регистрами (хотя явно инициализировать ds все равно придется).
            Пример программы с использованием упрощенных директив сегментации:

            Синтаксис директивы MODEL :

            Модификатор модели памяти

            Значение модификатора Назначение
            use16 Сегменты выбранной модели используются как 16-битные (если соответствующей директивой указан процессор i80386 или i80486)
            use32 Сегменты выбранной модели используются как 32-битные (если соответствующей директивой указан процессор i80386 или i80486)
            dos Программа будет работать в MS-DOS

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

            Модели памяти

            Модель Тип кода Тип данных Назначение модели
            TINY near near Код, данные и стек объединены в одну группу с именем DGROUP и размером до 64Кб. Используется для создания программ формата .com. Некоторые языки эту модель не поддерживают.
            СS=DS=SS=DGROUP
            SMALL near near Код занимает один сегмент, данные и стек объединены в одну группу с именем DGROUP (хотя для описания могут использоваться разные сегменты). Эту модель обычно используют для большинства программ на ассемблере.
            CS=_text
            DS=SS=DGROUP
            MEDIUM far near Код занимает несколько сегментов, по одному на каждый объединяемый программный модуль. Все ссылки на передачу управления — типа far (вызов подпрограмм). Данные и стек объединены в одной группе DGROUP; все ссылки на них — типа near (для доступа к данным испльзуется только смещение).
            CS= _text
            DS=SS=DGROUP
            COMPACT near far Код находится в одном сегменте, данные и стек в группе DGROUP и могут занимать несколько сегментов, так что для обращения к данным требуется указывать сегмент и смещение (
            ссылка на данные — типа far).
            CS=_text
            DS=SS=DGROUP
            LARGE far far Код может занимать несколько сегментов, по одному на каждый объединяемый программный модуль. Стек и данные находятся в группе DGROUP. Для ссылки на данные используются дальние указатели -far.
            CS= _text
            DS=SS=DGROUP
            HUGE far far Тоже что и модель LARGE, что касается TurboAssebmler.
            FLAT far far Тоже, что и TINY, но используются 32-битная адресация, так что максимальный размер сегмента, содержащего и данные, и код, и стек — 4Гб.

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

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

            Формат директивы
            (режим MASM)
            Формат директивы
            (режим IDEAL)
            Назначение
            .CODE [имя] CODESEG[имя] Начало или продолжение сегмента кода
            .DATA DATASEG Начало или продолжение сегмента инициализированных данных. Также используется для определения данных типа near
            .CONST CONST Начало или продолжение сегмента постоянных данных (констант) модуля
            .DATA? UDATASEG Начало или продолжение сегмента неинициализированных данных. Также используется для определения данных типа near
            .STACK [размер] STACK [размер] Начало или продолжение сегмента стека модуля. Параметр [размер] задает размер стека
            .FARDATA [имя] FARDATA [имя] Начало или продолжение сегмента инициализированных данных типа far
            .FARDATA? [имя] UFARDATA [имя] Начало или продолжение сегмента неинициализированных данных типа far

            Наличие в некоторых директивах параметра [имя] говорит о том, что возможно определение нескольких сегментов этого типа. С другой стороны, наличие нескольких видов сегментов данных обусловлено требованием обеспечить совместимость с некоторыми компиляторами языков высокого уровня, которые создают разные сегменты данных для инициализированных и неинициализированных данных, а также констант.

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

            Идентификаторы, создаваемые директивой MODEL

            Имя идентификатора Значение переменной
            @code Физический адрес сегмента кода
            @data Физический адрес сегмента данных типа near
            @fardata Физический адрес сегмента данных типа far
            @fardata? Физический адрес сегмента неинициализированных данных типа far
            @curseg Физический адрес сегмента неинициализированных данных типа far
            @stack Физический адрес сегмента стека

            Если вы посмотрите на текст примера, то увидите пример использования одного из этих идентификаторов. Это @data – с его помощью мы получили значение физического адреса сегмента данных нашей программы.

            Необязательные параметры язык и модификатор языка определяют некоторые особенности вызова процедур. Необходимость в использовании этих параметров появляется при написании и связывании программ на различных языках программирования.

            Язык — необязательный операнд , принимающий значения C, PASCAL, BASIC, FORTRAN, SYSCALL и STDCALL. Если он указан, подразумевается, что процедуры рассчитаны на вызов из программ на соответствующем языке высокого уровня, следовательно, если указан язык C, все имена ассемблерных процедур, объявленных как PUBLIC, будут изменены так, чтобы начинаться с символа подчеркивания, как это принято в C.
            Модификатор — необязательный операнд , принимающий значения NEARSTACK (по умолчанию) или FARSTACK . Во втором случае сегмент стека не будет объединяться в одну группу с сегментами данных.
            После того как модель памяти установлена, вступают в силу упрощенные директивы определения сегментов, объединяющие действия директив SEGMENT и ASSUME. Кроме того, сегменты, объявленные упрощенными директивами, не требуется закрывать директивой ENDS — они закрываются автоматически, как только ассемблер обнаруживает новую директиву определения сегмента или конец программы.

            Директива .CODE описывает основной сегмент кода

            _TEXT segment word public ’CODE’

            для моделей TINY, SMALL и COMPACT

            name_TEXT segment word public ’CODE’

            для моделей MEDIUM, HUGE и LARGE (name — имя модуля, в котором описан данный сегмент). В этих моделях директива .CODE также допускает необязательный операнд — имя определяемого сегмента, но все сегменты кода, описанные так в одном и том же модуле, объединяются в один сегмент с именем NAME_TEXT.

            Директива .STACK описывает сегмент стека

            STACK segment para public ’stack’

            Необязательный параметр указывает размер стека. По умолчанию он равен 1 Кб.

            Директива .DATA описывает обычный сегмент данных

            _DATA segment word public ’DATA’

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

            _BSS segment word public ’BSS’

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

            Директива .CONST описывает сегмент неизменяемых данных

            CONST segment word public ’CONST’

            В некоторых операционных системах этот сегмент будет загружен так, что попытка записи в него может привести к ошибке.

            Директива .FARDATA описывает сегмент дальних данных

            имя_сегмента segment para private ’FAR_DATA’

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

            Директива .FARDATA? описывает сегмент дальних неинициализированных данных

            имя_сегмента segment para private ’FAR_BSS’

            Как и в случае с FARDATA, доступ к данным из этого сегмента потребует загрузки сегментного регистра. Если имя сегмента не указано, используется FAR_BSS.

            Во всех моделях памяти сегменты, представленные директивами .DATA, .DATA?, .CONST, .FARDATA и .FARDATA?, а также сегмент, описанный директивой .STACK, если не был указан модификатор FARSTACK, и сегмент .CODE в модели TINY автоматически объединяются в группу с именем FLAT — для модели памяти FLAT или DGROUP — для всех остальных моделей. При этом сегментный регистр DS (и SS, если не было FARSTACK, и CS в модели TINY) настраивается на всю эту группу, как если бы была выполнена команда ASSUME.

            Порядок загрузки сегментов

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

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

            Порядок загрузки сегментов:

            1. Все сегменты класса ‘CODE’.
            2. Все сегменты, не принадлежащие группе DGROUP и классу ‘CODE’.
            3. Группа сегментов DGROUP:
            3.1. Все сегменты класса ‘BEGDATA’.
            3.2. Все сегменты, кроме классов ‘BEGDATA’, ‘BSS’ и ‘STACK’.
            3.3. Все сегменты класса ‘BSS’.
            3.4. Все сегменты класса ‘STACK’.

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

            Введение в технологию программирования на языке Ассемблера , страница 8

            Завершить выполнение COM-программы можно непосредственно командой Int 20h. Однако команда ret на один байт короче но, в то же время, передает управление на нулевой адрес текущего сегмента, то есть на первый байт PSP. А там находится код команды Int 20h, которая используется для возвращения управления в DOS. (DOS при вызове COM-файла помещает в стек сегментный адрес программы и ноль).

            Модель памяти small.

            Модель small, используемая при написании большинства ассемблерных программ, предусматривает размещение кода программ отдельно в кодовом сегменте, а данные и стек, объединенные в одну группу (DGROUP), — в другом отдельном сегменте. Для Ассемблеров MASM и TASM вместо DGROUP можно использовать предопределенную метку @data.Переходы в программе и вызовы подпрограмм, а также доступ ко всем переменным осуществляется с помощью короткой (near) адресации. В результате трансляции ассемблерных программ, при использовании этой модели памяти, формируется исполняемый файл типа exe.

            При запуске exe программы,регистр DS должен содержать указатель начального адреса размещения сегмента, в котором находятся данные и стек программы. Однако к этому времени в DS содержится служебный блок префикса программного сегмента PSP. Поэтому первой командой в кодовом сегменте должна быть команда инициализации (указания размещения в адресном пространстве) общего сегмента данных и стека, который обычно носит предопределенное имя DGROUP, или @data. Это указание осуществляется занесением в сегментный регистр DS этого предопределенного имени. Но команд для непосредственной пересылки данных из памяти в сегментный регистр DS нет. Поэтому для инициализации регистра DS используют две команды. Сначала начальный адрес этого общего сегмента данных и стека заносят в регистр общего назначения AX, а затем передают его в сегментный регистр DS.

            Размер стека, в программе, транслируемой при модели памяти small, должен задаваться непосредственно, в отличие от случая использования модели tiny, при которой стек формируется автоматически.

            Например: .model small

            Если же размер стека явно не указан, по умолчанию он будет сформирован размером 1 Кбайт.

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

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

            В общем виде псевдокоманды определения данных имеют следующий вид.

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

            гдеd* — одна из нижеприведенных директив:

            db – определить байт;

            dw – определить слово (два байта);

            dd – определить двойное слово (четыре байта);

            и еще несколько определений более крупных единиц данных.

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

            Table1 db 128 dup(FFh)

            cоздает массив единиц в памяти объемом 128 байт, начиная с ячейки по адресу, определенному меткой Table1. А директива:

            Table2 dw 512 dup(?)

            создает массив из 512 неинициализированных слов, адрес первого из которых указывает переменная Table2.

            При необходимости формирования целого сегмента неизменяемых данных, т.е. таблицу констант, часто используют директиву .const, а при задании сегмента неинициализированных данных – директиву .data?.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *