- •Лекция №12. Процедуры в программах на ассемблере. Crc-код
- •1) Если имя – это имя переменной, то тип может принимать значения byte, word, dword, pword, fword, qword и tbyte;
- •3) Если имя – это имя константы, то тип должен быть abs.
- •Выполнить трансляцию модуля modul1.Asm и получить объектный модуль modul1.Obj;
- •Выполнить трансляцию модуля modul2.Asm и получить объектный модуль modul2.Obj;
- •Cкомпановать программу утилитой tlink командной строкой вида:
- •1) Используя последовательность из n команд рор хх.
- •2) Откорректировать регистр указателя стека sp на величину 2*n, например, командой
- •1) Сложение по правилам полиномиальной арифметики по модулю 2 (crc-арифметика), будет выполнено так (нет переносов):
- •2) Операция вычитания:
- •1) Согласование имен,
- •2) Согласование параметров,
- •3) Согласование вызовов.
- •Примеры
- •1) Пример использования директивы asm и команд сопроцессора в программе на языке Паскаль (Delphi 5.0).
- •3) Программа, которая использует функцию stdcall get_str_length с асемблерной вставкой, для нахождения длины asciiz-строки.
- •4) Программа, которая складывает два числа в десятичной системе исчисления и выдаёт результат тоже в десятичной системе исчисления. (Example2)
- •Выполнить трансляцию модуля modul1.Asm и получить объектный модуль modul1.Obj;
- •Выполнить трансляцию модуля modul2.Asm и получить объектный модуль modul2.Obj;
- •Cкомпановать программу утилитой tlink командной строкой вида:
- •4) В ассемблерном модуле вызываемая процедура должна быть дополнительно объявлена при помощи директивы public.
- •К имени процедуры в объектном модуле автоматически добавляется @8 (а не @0).
- •Ассемблер автоматически управляется со стеком.
- •3) Сложение двух целых чисел.
1) Согласование имен,
2) Согласование параметров,
3) Согласование вызовов.
Согласование вызовов.
В ОС MS DOS вызываемая процедура могла находиться:
1) в том же сегменте, что и команда вызова, тогда вызов называется близким (NEAR) или внутрисегментным. Адрес возврата состоит из 2 байт;
2) в другом сегменте, тогда вызов назывался дальним (FAR) или межсегментным. Адрес возврата состоит из 4 байт.
Возврат из процедуры мог быть либо:
1) близким (RETN), адрес возврата формировался на основе двух байт, взятых из стека;
2) дальним (RETF), адрес формировался на основе четырех байт, взятых из стека.
Вызов и возврат должны быть согласованы друг с другом. В рамках единой программы это не вызывает больших проблем. Но при работе с двумя модулями могут возникнуть трудности.
Например: если в объектном модуле возврат осуществляется по RETN, то необходимо компоновать объектные модули так, чтобы сегмент, где находится процедура, был объединен с сегментом, откуда осуществляется вызов. Вызов в этом случае должен быть близким.
Если же возврат из процедуры осуществлялся по команде RETF, то и вызов этой процедуры должен быть дальним. При этом при компоновке вызов и сама процедура должны были попасть в разные сегменты.
С этим были связаны и так называемые модели памяти в языке Си.
При переходе к Windows (плоская модель памяти) все вызовы по типу являются близкими, т.е. осуществляющимися в рамках одного огромного сегмента. Тем самым была снята проблема согласования вызовов.
Согласование имен.
1) Транслятор MASM добавляет в конце имени процедуры @N, где N количество передаваемых в стек параметров. То же делает и компилятор Visual C++. Т.о. трудности возникают уже при согласовании двух ассемблерных модулей. ТASM является более гибким компилятором, так как в конце любого имени можно добавить @N, тем самым согласовав имена.
2) Подчеркивание перед именем. Транслятор MASM генерирует подчеркивание автоматически, если в начале программы устанавливается тип вызова stdcall (Standard Call, т.е. стандартный вызов). Транслятор TASM этого не делает, то есть, при необходимости это нужно делать в тексте программы, что является положительным моментом. Между фирмами Borland и Microsoft в этом вопросе полное разночтение.
4) Согласование заглавных и прописных букв. При трансляции с помощью TASM мы используем ключ /ml для того, чтобы различать буквы прописные и заглавные. Транслятор MASM делает это автоматически. В стандарте языка Си с самого начала предполагалось различие между заглавными и прописными буквами. В Паскале же прописные и заглавные буквы не различаются.
5) Уточняющие имена в Си++. В Си++ возможна так называемая перегрузка. Это значит, что одно и тоже имя может относиться к разным функциям. В тексте программы эти функции отличаются друг от друга по количеству и типу параметров и типу возвращаемого значения. Поэтому компилятор Си++ автоматически делает в конце имени добавку - так, чтобы разные по смыслу функции различались при компоновке. Фирмы Borland и Microsoft тут не пожелали согласовать свои позиции и делают в конце имени совершенно разные добавки. Обойти эту проблему не так сложно, нужно использовать модификатор EXTERN "С".
Согласование параметров
В таблице ниже представлены основные соглашения по передаче параметров в процедуру.
Ранее во всех ассемблерных программах указывался тип передачи параметров как stdcall. Однако, это никак и нигде не использовалось - так как передача и извлечение параметров делалась явно, без помощи транслятора.
Когда происходит взаимодействие ассемблерного модуля с языками высокого уровня, это необходимо учитывать и знать, как работают те или иные соглашения.
Таблица, представляющая соглашения о вызовах
Соглашение |
Параметры |
Очистка стека |
Регистры |
Pascal (конвенция языка Паскаль) |
Слева направо |
Процедура |
Нет |
Register (быстрый или регистровый вызов) |
Слева направо |
Процедура |
Задействованы три регистра (EAX,EDX,ECX), далее стек |
Cdecl (конвенция С) |
Справа налево |
Вызывающая программа |
Нет |
Stdcall (стандартный вызов) |
Справа налево |
Процедура |
Нет |
Тип возвращаемых функцией данных
В ассемблерном модуле в регистре EAX возвращается значение, которое может быть либо числом, либо указателем на некую переменную или структуру. Если возвращаемое число типа WORD, то оно содержится в младшем слове регистра EAX.
Встроенный ассемблер
Встроенные ассемблеры часто несколько отстают от обычных ассемблеров в части поддержки новых команд микропроцессоров. Это вполне объяснимо, так как разработка новой версии пакета, скажем C++ Builder, требует гораздо больше времени, чем пакета TASM.
Ассемблерные вставки (inline-ассемблер) используются в двух случаях:
-
Если нужно оптимизировать критичные по скорости и небольшие по объему участки программы. Грамотно написанный ассемблерный код всегда быстрее кода, который генерируется компилятором С++ или Delphi.
-
Нужен прямой доступ к памяти и портам. Чаще всего используется в драйверах, так как из третьего кольца защиты с портами не очень-то поработаешь.