Физически сегмент представляет собой область памяти, занятую командами и (или) данными, адреса которых вычисляются относительно значения в соответствующем сегментном регистре.
Каждая программа содержит 3 типа сегментов:
- Сегмент кодов – содержит машинные команды для выполнения. Обычно первая выполняемая команда находится в начале этого сегмента, и операционная система передает управление по адресу данного сегмента для выполнения программы. Регистр сегмента кодов (CS) адресует данный сегмент.
- Сегмент данных – содержит данные, константы и рабочие области, необходимые программе. Регистр сегмента данных (DS) адресует данный сегмент.
- Сегмент стека — содержит адреса возврата как для программы (для возврата в операционную систему), так и для вызовов подпрограмм (для возврата в главную программу), а также используется для передачи параметров в процедуры. Регистр сегмента стека (SS) адресует данный сегмент. Адрес текущей вершины стека задается регистрами SS:ESP.
Функциональное назначение сегмента несколько шире, чем простое разбиение программы на блоки кода, данных и стека. Сегментация является частью более общего механизма, связанного с концепцией модульного программирования. Она предполагает унификацию оформления объектных модулей, создаваемых компилятором, в том числе с разных языков программирования. Это позволяет объединять программы, написанные на разных языках. Именно для реализации различных вариантов такого объединения и предназначены директивы сегментации.
#1 Первая программа на ассемблере MASM
Упрощенные директивы сегментации
Для задания сегментов в тексте программы можно пользоваться упрощенными директивами:
- .CODE — для указания начала сегмента кода;
- .DATA — для указания начала сегмента данных;
- .STACK — для указания начала сегмента стека.
Однако использование упрощенных директив сегментации не позволяет создать более трех сегментов для одной программы.
Стандартные директивы сегментации
Наряду с упрощенными директивами сегментации может также использоваться стандартная директива SEGMENT , которая определяет начало любого сегмента. Синтаксис:
ИмяСегмента SEGMENT align combine dim ‘class’
…
ИмяСегмента ENDS
Директива ENDS определяет конец сегмента.
Атрибут выравнивания сегмента (тип выравнивания) align сообщает компоновщику о том, что нужно обеспечить размещение начала сегмента на заданной границе. Это важно, поскольку при правильном выравнивании доступ к данным в процессорах, совместимых с базовым i8086, выполняется быстрее. Допустимые значения этого атрибута следующие:
- BYTE — выравнивание не выполняется. Сегмент может начинаться с любого адреса памяти;
- WORD — сегмент начинается по адресу, кратному двум, то есть последний (младший) значащий бит физического адреса равен 0 (выравнивание на границу слова);
- DWORD — сегмент начинается по адресу, кратному четырем, то есть два последних (младших) значащих бита равны 0 (выравнивание по границе двойного слова);
- PARA — сегмент начинается по адресу, кратному 16, то есть последняя шестнадцатеричная цифра адреса должна быть 0h (выравнивание по границе параграфа);
- PAGE — сегмент начинается по адресу, кратному 256, то есть две последние шестнадцатеричные цифры должны быть 00h (выравнивание по границе страницы размером 256 байт);
- MEMPAGE — сегмент начинается по адресу, кратному 4 Кбайт, то есть три последние шестнадцатеричные цифры должны быть 000h (адрес следующей страницы памяти размером 4 Кбайт);
По умолчанию тип выравнивания имеет значение PARA .
ЯЗЫК АССЕМБЛЕРА С НУЛЯ | #1 НАЧАЛО
Атрибут комбинирования сегментов (комбинаторный тип) 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 :
ASSUME CS : FLAT , DS: FLAT , SS : FLAT , ES : FLAT , FS : ERROR , GS : ERROR
Регистры FS и GS программами не используются, поэтому для них указывается атрибут ERROR .
Источник: prog-cpp.ru
Синтаксис макроассемблера masm32
Имена переменных, констант и меток должны удовлетворять следующим условиям.
1. Длина идентификатора не должна превышать 247 символов.
2. Регистр букв идентификаторов не учитывается, если только нет опции option casemap: none
3. Первым символом идентификатора должна быть одна из букв латинского алфавита (A…Z или a…z) либо символы подчёркивания (_) либо знака доллара ($). Последующие символы могут быть также цифрами.
Директива – команда макроассемблера, которая выполняется во время трансляции исходного кода программы. Директивы используются для определения логических сегментов, выбора модели памяти, определения переменных, создания процедур и т.п. Директивы независимы от регистра символов, поэтому записи.data,.DATA или.Data совершенно одинаковы.
Директива.data определяет сегмент программы, в котором располагаются значения переменных, хранимых в exe-файле.
Директива.data? используется для объявления в программе сегмента памяти, содержащего неинициализированные данные. Выделение памяти и инициализация данных происходит во время выполнения программы, что сокращает размер исполняемого файла на количество байтов, занимаемое всеми переменными, объявленными в этом сегменте.
Директива.code определяет сегмент в программе, в котором располагаются машинные команды.
В masm32 предусмотрено большое количество разных директив. Полный список директив и операторов можно просмотреть в справке masm32 ® Help ® Masm32 Help ® Full Listing.
Формат записи команды ассемблера имеет четыре поля:
Метка: | Мнемоника | Операнд(ы) | ; Комментарии |
Однострочные комментарии начинаются с символа точки с запятой (;) и располагаются до конца текущей строки.
Блочные комментарии начинаются с директивы COMMENT, за которой следует маркер начала и конца блока, определяемы программистом. Текст комментария располагается между маркерами, например:
Здесь роль маркера играет восклицательный знак.
Определение типов данных
Пример 1.1.4. Разработаем программу сложения трёх беззнаковых четырёхбайтовых чисел, хранящихся в исполнимом файле, и размещения суммы в ячейках неинициализированной памяти. Пусть значения трёх слагаемых: 11111111h, 0AAAAAAAAh и 44444444h. Имена переменных, в которых будут храниться эти слагаемые соответственно пусть будут X, Y и Z. В качестве имени переменной, в которую будет записана сумма, выберем SUM. Заметим, что размер каждого слагаемого 4 байта.
В таблице 1.1.1 представлены целочисленные типы данных, используемые в masm32.
Таблица 1.1.1. Целочисленные типы данных
Тип (Type) | Размер (Size) | Описание (Description) |
BYTE | 1 byte | 8-разрядное беззнаковое целое (0…255) |
SBYTE | 1 byte | 8-разрядное знаковое целое (-128…+127) |
WORD | 2 bytes | 16-разрядное беззнаковое целое (0…65’535) |
SWORD | 2 bytes | 16-разрядное знаковое целое (-32’768…+32’767) |
DWORD | 4 bytes | 32-разрядное беззнаковое целое (0…4’294’967’295) |
SDWORD | 4 bytes | 32-разрядное знаковое целое (-2’147’483’648…+2’147’483’647) |
FWORD | 6 bytes | 48-разрядное беззнаковое целое |
QWORD | 8 bytes | 64-разрядное беззнаковое целое |
TBYTE | 10 bytes | 80-разрядное беззнаковое целое |
Как видно из таблицы беззнаковое четырёхбайтовое число имеет тип DWORD. Следовательно, в программе необходимо объявить переменные X, Y, Z и SUM типа DWORD. Теперь можно записать исходный текст программы.
.model flat, stdcall
option casemap: none
X dword 11111111h; Инициализация переменной X
Y dword 0AAAAAAAAh; Инициализация переменной Y
Z dword 44444444h; Инициализация переменной Z
SUM dword?; Выделение памяти под переменную SUM
PrintHex eax, «- Инициализация eax»
PrintHex eax, «- Первое сложение»
PrintHex eax, «- Второе сложение»
PrintHex SUM, «- Запись результата в SUM»
Запустите программу и проверьте правильность полученного результата на калькуляторе MS Windows.
Основные логические команды ассемблера
Потренируемся писать программы, используя простейшие логические команды. Описание логических команд приведено в приложении Б, а также в справке masm32 ® Help ® Opcodes Help. К основным логическим командам можно отнести:
1. or operand1, operand2 – команда поразрядного логического сложения (ИЛИ). В математических выражениях обозначается символом Ú. Результат помещается в operand1.
2. and operand1, operand2 – команда поразрядного логического умножения (И). В математических выражениях обозначается символом Ù. Результат помещается в operand1.
3. xor operand1, operand2 – команда поразрядного логического сложения по модулю 2 (исключающее ИЛИ). В математических выражениях обозначается символом Å. Результат помещается в operand1.
4. not operand1 – команда поразрядного логического отрицания (НЕ). В математических выражениях обозначается символом Ø. Результат помещается в operand1.
Заметим, что размер операндов в двухоперандных командах должен быть одинаков.
Упражнение 1.1.3. Разработайте программу для вычисления выражения R = (XÚY) + (XÙZ). Исходные данные хранить в переменных:
Результат поместить в переменную R, хранимую в сегменте неинициализированных данных. В окне отладки вывести содержимое регистров в шестнадцатеричном представлении и объяснить полученный результат.
Упражнение 1.1.4. Разработайте программу для вычисления выражения R = Ø(XÙY) Å (XÙZ). Исходные данные хранить в переменных:
Результат поместить в переменную R, хранимую в сегменте неинициализированных данных. В окне отладки вывести содержимое регистров в шестнадцатеричном представлении и объяснить полученный результат.
Упражнение 1.1.5. Разработайте программу для вычисления выражения R = 1 Å 2 Å 3 Å 4 Å 5, используя для этого регистры наименьшего размера. Результат вывести в шестнадцатеричном представлении.
Упражнение 1.1.6. Разработайте программу для вычисления выражения R = X‑Y+Z. Исходные данные хранить в переменных со знаковым типом данных:
Оператор повторения DUP
Для создания переменных, содержащих повторяющиеся значения элементов, используется оператор DUP. Например, оператор:
X byte 8 DUP(41h)
инициализирует переменную X байтом 41h, повторенным 8 раз, т.е. числом 4141414141414141h.
В макроассемблере смысл понятия “переменная” отличается от этого понятия, используемого в языках высокого уровня. На самом деле переменная X есть не что иное, как адрес, по которому располагается первый байт из последовательности повторяющихся байтов. Второй байт будет располагаться по адресу X+1, третий байт по адресу X+2 и т.д.
Поэтому команда ассемблера mov al, X означает пересылку в регистр al первого байта, расположенного в памяти по адресу X. Для пересылки второго байта надо воспользоваться командой mov al, X+1, третьего байта mov al, X+2, четвёртого байта mov al, X+3 и т.д.
Для загрузки в регистр общего назначения собственно адреса X, а не данных, хранящихся по этому адресу, существует специальная команда загрузки расширенного адреса lea ebx, X. Загрузку адреса можно осуществить также и командой пересылки mov eax, offset X. Здесь ключевую роль играет директива offset. Встречая эту директиву, masm32 ещё на этапе компиляции определяет конкретный адрес X и подставляет его непосредственно в код команды. В отличие от offset команда lea вычисляет адрес X в ходе выполнения программы. Однако результаты при этом получаются одинаковые.
Пример 1.1.5. Найдём адреса ячеек памяти, по которому masm32 разместит байты переменной X, объявленной выше, и увеличим самый первый байт переменной X на единицу, а второй байт уменьшим на единицу. Для вывода дампа памяти воспользуемся командой отладчика DbgDump. Обратите внимание на символьное представление данных в дампе памяти. Байт 42h – код большой латинской буквы B.
.model flat, stdcall
option casemap: none
X byte 8 DUP(42h); объявление и инициализация переменной X
mov eax, offset X; загрузка в eax адреса переменной X (1 способ)
PrintHex eax, «-адрес, полученный ком-й mov eax, offset X»
lea ebx, X; загрузка в ebx адреса переменной X (2 способ)
PrintHex ebx, «- адрес, полученный командой lea ebx, X»
DbgDump offset X, 8; печать дампа памяти
inc X; инкремент первого байта переменной X
DbgDump offset X, 8; печать дампа памяти
dec X+1; декремент второго байта переменной X
DbgDump offset X, 8; печать дампа памяти
Команда inc X осуществляет увеличение на единицу байта, хранящегося по адресу X. Команда dec X+1 уменьшает на единицу содержимое байта, хранящегося по адресу X+1.
В этом примере показано два равноправных способа получения адреса, по которому хранится переменная. Первый способ mov eax, offset X выполняется за 4 такта на i80386, а второй способ lea ebx, X за два такта. На процессорах i80486 и более поздних обе команды выполняются за один такт (см. справку masm32 ® Help ® Opcodes Help ® mov и lea).
Упражнение 1.1.7. Используя определение данных из примера 1.1.5, разработайте программу определения суммы всех восьми байтов. Заметьте, что размер суммы восьми байтов 42h больше одного байта. Поэтому сумму необходимо аккумулировать в двухбайтовом или четырёхбайтовом регистре. Перед сложением аккумулятора al с очередным байтом из памяти необходимо предварительно занести этот байт в другой регистр, например в bl:
mov eax,0; обнуление регистра eax
mov ebx,0; обнуление регистра ebx
mov al,X; размещение первого байта в регистре al
mov bl,X+1; размещение второго байта в регистре bl
add eax,ebx; сложение первого и второго байта,
; результат – в eax
Множественная инициализация используется для определения массивов данных. Например, оператор:
X word 2222h, 3333h, 8888d, 9090d
инициализирует переменную X словом 2222h или, что то же самое, записывает в адрес X слово 2222h. Слово 3333h располагается в памяти по адресу X+2, так как размер первого слова два байта. Слово 8888d располагается в памяти по адресу X+4, а слово 9090d – по адресу X+6. Обратите внимание, что множественная инициализация допускает смешение типов данных.
Упражнение 1.1.8. Инициировать в памяти ASCII-коды первых восьми прописных букв латинского алфавита и вывести их дамп памяти. Подсказка: ASCII–код прописной латинской буквы “A” равен 41h.
Источник: infopedia.su
Структура программы на ассемблере
Программа, написанная на ассемблере MASM, может состоять из нескольких частей, называемых модулями, в каждом из которых могут быть определены один или несколько сегментов данных, стека и кода. Любая законченная программа на ассемблере должна включать один главный, или основной, модуль, с которого начинается ее выполнение. Модуль может содержать программные сегменты, сегменты данных и стека, объявленные при помощи соответствующих директив. Кроме того, перед объявлением сегментов нужно указать модель памяти при помощи директивы .MODEL.
Пример «ничего не делающей» программы:
.MODEL FLAT, STDCALL
В данной программе представлена всего одна команда микропроцессора. Эта команда RET. Она обеспечивает правильное окончание работы программы. В общем случае эта команда используется для выхода из процедуры.
Остальная часть программы относится к работе транслятора.
.686P — разрешены команды защищенного режима Pentium 6 (Pentium II). Данная директива выбирает поддерживаемый набор команд ассемблера, указывая модель процессора. Буква P, указанная в конце директивы, сообщает транслятору о работе процессора в защищенном режиме.
.MODEL FLAT, stdcall — плоская модель памяти. Эта модель памяти используется в операционной системе Windows. stdcall — используемое соглашение о вызовах процедур.
.DATA — блок программы, содержащий данные. В MS-DOS это называлось сегментом. В Windows, для прикладной программы, сегменты отсутствуют, точнее есть один большой плоский сегмент. Такие блоки здесь называются секциями. Секции можно задать различные свойства.
Например, запретить запись в нее, или сделать секцию доступной для других программ, используя стандартные директивы сегментации.
Данная программа не использует стек, поэтому секция .STACK отсутствует.
.CODE — блок программы, содержащей код.
START — метка. В ассемблере метки играют большую роль, что не скажешь о современных языках высокого уровня.
END START — конец программы и сообщение компилятору, что начинать выполнение программы надо с метки START. Каждая программа должна содержать директиву END, отмечающую конец исходного кода программы. Все строки, которые следуют за директивой END, игнорируются. Если вы опустите директиву END, то генерируется ошибка.
Метка, указанная после директивы END, сообщает транслятору имя главного модуля, с которого начинается выполнение программы. Если программа содержит один модуль, метку после директивы END можно не указывать.
Чтобы представленный текст превратить в исполняемый модуль, будем использовать пакет MASM32 8.0.
ML /c /coff prog.asm
LINK /subsystem:windows prog.obj
В результате выполнения этих строк появится исполняемый модуль prog.exe. Правда, выполнение его не даст никакого эффекта.
Простейшая программа в ос Windows
Операционная система Windows является многозадачной средой. Каждая задача имеет свое адресное пространство и свою очередь сообщений. Более того, даже в рамках одной программы может быть осуществлена многозадачность — любая процедура может быть запущена как самостоятельная задача.
Программирование в Windows основывается на использовании функций API (Application Program Interface, т.е. интерфейс программного приложения). Их количество достигает двух тысяч. Программа для Windows в значительной степени состоит из таких вызовов. Все взаимодействие с внешними устройствами и ресурсами операционной системы будет происходить посредством таких функций.
Операционная система Windows использует плоскую модель памяти. Другими словами, всю память можно рассматривать как один сегмент. Для программиста на языке ассемблера это означает, что адрес любой ячейки памяти будет определяться содержимым одного 32-битного регистра, например EBX. Следовательно, мы фактически не ограничены в объеме данных, кода или стека (объеме локальных переменных). Выделение в тексте программы сегмента кода и сегмента данных является теперь простой формальностью, улучшающей читаемость программы.
Вызов функций API. В файле помощи любая функция API представлена в виде (например):
int MessageBox ( HWND hWnd, LPCTSTR lpText,
LPCTSTR lpCaption, UINT uType);
Данная функция выводит на экран окно с сообщением и кнопкой (или кнопками) выхода. Смысл параметров:
hWnd -дескриптор окна, в котором будет появляться окно-сообщение,
lpText — текст, который будет появляться в окне,
lpCaption — текст в заголовке окна,
uType — тип окна, в частности можно определить количество кнопок выхода.
Результат выполнения любой API функции — это, как правило, целое число, которое возвращается в регистре EAX.
.MODEL FLAT, STDCALL
STR1 DB «Моя первая программа»,0
STR2 DB «Привет всем!»,0
Источник: studfile.net