Компилятор masm


Содержание

Дневники чайника. Чтива 0, виток0

Знакомство с MASM32 и Win32-программированием

Мне приходит довольно много писем с просьбами рассказать о MASM’е больше. И я решил слегка переделать эту главу.

Кроме того, я уже написал справочные статьи для тех, кто не застал времена MS-DOS. Если интересно, загляните в следующий виток

Как получаются программы?

До сих пор мы писали все примеры в Hiew’e, но этот способ крайне извращенный. Я использовал его только для того, чтобы вы увидели настоящий Асм. Вначале очень трудно отличить высокоуровневые наслоения от самого языка Ассемблер, и некоторое время я сам путал команды процессора и директивы.

Но теперь, когда вы видели язык во всей его наготе, можно слегка приодеть его.

Нормальные программы пишут в обычном текстовом виде, а не как мы раньше (каждую строку сразу транслировали в машинную команду). Такой текстовый вид называется исходным, а файлы — исходниками, или сырцами (от английского source).

Текст исходника можно писать где угодно, хоть в Ворде (при установленном фильтре простого текста). На вкус, на цвет. Я пишу в RadAsm’e. Это целая среда для разработки приложений, очень удобно. Однако на первых порах рекомендую писать просто в редакторе Far’а, можно подключить к нему специальный плагин Colorer. Этот плаг подсвечивает синтаксис многих языков программирования. Но всё это не обязательно. Тут каждый сам себе берлогу выстилает.

Когда исходный текст набран и сохранён, его можно компилировать.

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

  1. Исходный текст преобразуется в промежуточный файл. Это делает транслятор (в случае с MASM’ом он называется ml.exe).
  2. Затем создаётся готовый исполняемый модуль в определённом формате. То есть программа. Это делает линковщик (в случае с MASM’ом он называется link.exe).

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

Всё остальное не обязательно, однако реальные программы Win32 используют внешние функции, стандартные константы и переменные, ресурсы и много другое. Всё это требует дополнительных файлов, которые мы и видим в.

MASM32 SDK

Это очень распространённый пакет, собранный Стивеном Хатчисоном (Hutch).

Важно понять, что MASM32 вовсе не компилятор, а сборник для программирования под Win32, в который входит 32-битный компилятор MASM.

Сегодня (С наступающим, 2012-м, годом!) я использую пакет MASM32 версии 10.

Описывать всё, что входит в пакет MASM32 SDK, — не вижу смысла. Лучше сосредоточиться на самом компиляторе.

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

Из чего состоит компилятор MASM?

Основных файлов всего два:

ml.exe — транслятор. Он преобразует исходный текст в obj-файл (объектного формата COFF или OMF),

link.exe — линковщик. Создаёт готовый исполняемый exe или dll модуль (в формате для DOS, Windows. ).

Эти файлы включены в основной состав MS Visual Studio (и в .NET).

При желании можно скачать в открытом доступе самые новые версии вместе с Visual Studio Express (или в обновлениях) на сайте MS.

Правда, я не вижу в этом особого смысла (в следующем витке описана причина).

Установка и настройка MASM32

Ну, для начала хорошо бы его иметь. Можно скачать с wasm.ru или с сайта автора.

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

2. Устанавливать пакет советую на тот диск, где будут ваши исходники.

Теперь полезно прописать в системе путь к файлам компилятора.

В Win7 это можно сделать так:

В меню «пуск» правый клик на «Компьютер» > Свойства > Дополнительные параметры системы > закладка «Дополнительно» > Переменные среды.

Здесь в списке системных переменных нужно изменить значение переменной «PATH». Дописать в конце строки:

(не пропустите точку с запятой и вместо «D» укажите букву диска на котором установлен MASM32).

Поскольку редактор исходных файлов Quick Editor я не использую (уж больно он убогий), на этом можно считать подготовку MASM32 к работе завершенной.

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

Реальный процесс разработки программы

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

Но общие правила здесь тоже имеются.

  1. Постановка задачи
  2. Создание алгоритма (в той или иной степени)
  3. Кодирование на конкретном языке
  4. Попытка компиляции. Если счетчик ошибок не ноль, прыг на шаг 3 или даже 2
  5. Удивление, что оно хоть как-то заработало
  6. Если счетчик найденных ошибок не ноль, то прыг на шаг 1
  7. Я пока так оставлю, а потом ещё доделаю
  8. Если обстоятельства (обычно лень) не позволяют продолжить — проект умирает. Или на шаг 1

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

Научить вас ставить задачи я даже пытаться не буду, сам не умею.

Создавать алгоритмы вас научит Кнут, это его стихия, а я могу только восхищаться.

Дональд Эрвин Кнут — Великий Автор, его трёхтомник можно взять на сайте int3.net (после регистрации). Мне кажется, человек, который не читал Кнута, программистом себя называть не имеет права (сам я, кстати, тоже не читал :).

А если серьёзно, то абсолютное большинство существующих учебников и литературы о программировании не может выжить и семи лет, потому что такие книги ориентированы на изучение возможностей того или иного языка (как мои статьи). Кнут же на протяжении полувека(!) учит создавать реальные программы, а не оболочки для простого ввода/вывода. Думаю, вы поняли, что его книгу хорошо бы купить. Цена, правда, хорошая, но трёхтомник того стоит.

Итак, вернёмся к бренной жизни.

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

Win32

Многие считают, что мир форточек устроен по законам Майкрософт, но это лишь очень поверхностный взгляд. Как бы ни старались специалисты этой конторы, они тоже вынуждены подчиняться правилам Intel. Так что главное отличие DOS-среды от Windows — это то, что последняя опирается на защищённый режим процессора (Protected Mode — PM).

Кардинальное отличие PM от Real Mode — новый (довольно хитрый) механизм работы с памятью, и он сильно изменил жизнь программистов.

Win32 использует страничную адресацию памяти, которая является надстройкой над сегментной адресацией защищённого режима.

Несмотря на то, что работа с памятью в PM устроена сложно и запутанно, внешне всё выглядит элементарно. По крайней мере в Windows адресацию сделали довольно удобной.

В данном случае советую всем начать именно с внешнего осмотра. Ну если, конечно, вы самые смелые, самые умные и уже готовы разбирать всё по винтикам изнутри, тогда берите учебник по i80386 процессору и читайте про селекторы, таблицы дискрипторов, разбирайтесь, как в PM при страничной адресации формируется физический адрес из линейного. Лучше, конечно, начать с рассылки Broken Sword’a.

Но что-то мне подсказывает, что вам пока рано браться даже за популярное изложение особенностей PM :).

Сам я уже год как пытаюсь подступиться к этой теме, просветление уже где-то рядом. :)

Внешний осмотр Win32.

Что же мы имеем после загрузки форточек:

  • Каждая программа загружается в собственное изолированное виртуальное адресное пространство (Виртуальное Пространство Процесса).
  • Адреса в таком ВПП могут быть от 00000000 до FFFFFFFF (4Gb), будем называть их виртуальными.
  • При загрузке и далее при выполнении в эту виртуальность проецируется всё необходимое для работы программы (код и данные операционной системы, динамические части самой программы, файлы и т.п.).
  • Программа, кроме собственных вычислений, практически ничего самостоятельно сделать не может. Ей приходится просить ОС вывести что-либо или предоставить в её ВПП. Для этого она вызывает нужные API-функции.
  • Файлы с машинным кодом и данными у Win32-программ устроены в соответствии с PE-форматом, который довольно сильно связан с особенностями защищённого режима процессора.

Исполняемые файлы PE-формата делятся на секции. Должна быть минимум одна секция (секция кода), но файл может иметь и другие секции для разных целей (данные, ресурсы, служебные секции и т.д.).

А ещё нужно знать, что в защищённом режиме существуют 4 уровня привилегий. Нулевой самый сильный, 3-й самый слабый. Эти уровни называют кольцами (ring0,1,2,3).

Win32 использует только ring0 для ядра и драйверов и ring3 для прикладных программ.

Таких поверхностных данных вполне достаточно, чтобы мы могли дальше разумно изучать программирование на Ассемблере, и приступить к программированию WinAPI.

По крайней мере сам я о защищённом режиме и о том, как его используют форточки знаю немногим больше :(.

Так что будем учиться вместе.

Первый пример Windows-программы

Давайте рассмотрим, как может выглядеть маленькая программа для Win32, написанная под MASM.

Языка Ассемблер здесь нет! Этот текст будет преобразован в запускаемый файл формата PE (Portable Executable — портируемый exe-формат). То есть стандартную программу для форточек.

Попробуйте набрать в файл «prax05.asm» все эти строки, большая часть которых, кстати сказать, — директивы MASM’a.

. Хотя это как раз можно и не набирать, ещё успеете натыкаться :), возьмите мой файл.

Компилировать программу новичку проще всего так (читатели меня всё-таки убедили =):

1. Открыть редактор исходных файлов «Quick Editor» (икона «MASM32 Editor» на рабочем столе).

2. Открыть или набрать в нём пример.

3. После всех изменений обязательно нужно сохранить пример и обязательно на тот диск где установлен MASM32.

4. Для самой компиляции нужно выполнить пункт меню Project > Build All.


5. Должно появиться консольное окошко с отчётом компилятора (если консоли нет, значит, по какой-то причине компиляции не было).

Запустить пример можно из того же Quick Editor’a кнопкой «Run File» или пунктом меню Project > Run Program.

Если вы планируете работать с компилятором на разных дисках, то самый грубый, но быстрый и универсальный, способ решить проблему компиляции – тупо скопировать папку MASM32 в корень всех дисков, где будет проходить компиляция программ.

Хочу предостеречь юные умы от выражений типа «иконка открывает MASM».

В корне неправильные слова влекут неправильные выводы и действия.

Ещё раз обращаю ваше внимание: MASM – это компилятор, он состоит из двух частей (ml.exe и link.exe). Обе части не имеют другого способа общения с пользователем кроме как через командную строку. А иконка на рабочем столе открывает программу-посредник под названием «Quick Editor», которая для компиляции всего лишь запускает файл build.bat.

И тут мы переходим к следующему уровню пользования компилятором.

Другой способ компиляции программ — использовать файл build.bat без посредника (описание в следующем витке).

В командной строке ОСи (В Far’e внизу или в «Пуск > Все программы > Стандартные > Командная строка») набираем следующее:

! Разумеется, текущей папкой должна быть папка с исходником.

! Имя файла в командной строке пишите без расширения.

! Расширение у файла должно быть asm.

! файл должен быть на диске с пакетом MASM32 в корне.

После нажатия Enter вызовется бат-файл. Его задача превратить текстовый файл (исходник) в готовую программу (бинарник). Для этого он выполняет ml.exe с параметрами

Если в исходнике нет явных ошибок, появится файл prax05.obj. И далее выполнится link.exe, который и создаст готовую Win-программу.

На выходе мы имеем два файла. Один — объектный, он нам не пригодится, это для нас промежуточный файл. Второй — exe, сами знаете, что с ним делать.

Ошибки будут — это я гарантирую. Опечатки обычно быстро находятся, так как компилятор сообщает тип ошибки и строку, которая её вызывает.

Однако это далеко не всегда означает, что ошибка именно в этой строке. Тут нужно головой думать. Подозреваю, что она у вас работает хорошо, раз мы с вами добрались до Windows. Но если у вас недостаточно знаний пользователя и из-за этого не собирается даже пример prax05.asm, — не расстраивайтесь. Прочитайте главу следующего витка. Должно помочь и уж, по крайней мере, поднимет ваш «уровень юзера». Да, да, у вас всё получится.

Поздравляю, матрос! Мы в гиперпространстве.

Полноценная программа для Windows. Её уже не исследуешь в CodeView. Для отладки форточных программ мы запаслись OllyDbg.

Хорошо бы настроить и его (цвета, шрифты, размер и пути в ini-файле). Это у вас займёт некоторое время, ко всему надо привыкнуть. Я всё-таки надеюсь, вас не испугает такая небольшая программка весом в 2Mb, ведь вы наверняка настраивали и более навороченные приложения. Кроме того, недавно хорошие ребята, потрудившись наславу, сделали русский help, который можно взять тут.

Спасибо: deNULL, HyPeR, name, Wenden.

И ещё есть вводная статья Bit-hack’a http://cracklab.ru/art/ollydbg.php, за что ему тоже огромное спасибо.

Exe-файл формата PE содержит кучу всякой ерунды, которая, впрочем, необходима для запуска Win32-программы. Сложного там ничего нет, позже вы скорее всего познакомитесь с устройством PE-формата, а сейчас нужно понять лишь одно. На начало исполняемого кода программы в памяти указывает Entry Point (EP — точки входа). В разных программах она может указывать на разные адреса. Но в простых примерах, собранных build.bat’ом, адрес первой команды будет 401000h. Значение EntryPoint в таком случае будет = 1000h, плюс Image base 400000h = 401000h.

Про PE-формат мы, наверное, ещё поговорим, и даже очень плотно, ну а сейчас.

Откройте prax05.exe в OllyDbg. Для этого достаточно загрузить Olly и открыть программу как обычный документ в форточках. После работы Win-загрузчика Олли покажет вам первую инструкцию в окне кода (ориентируясь именно на EP). Исследовать этот пример вам пока не надо, через регистры здесь слишком трудно разобраться.

  • В верхней левой части — вся программа, которая выводит на экран сообщение типа Message Box. Вы видите, нет ни одной строчки из исходного файла prax05.asm, потому что это уже Ассемблер!
  • Вверху справа — регистры процессора (заметьте, EBX стоит четвёртым, рядом с регистрами-указателями, и флаги выглядят по-другому). Как я уже сказал, регистры селекторов (сегментные регистры ES-GS) нас не волнуют, даже не обращайте на них внимание.
  • Внизу слева — байты памяти (Olly сразу же показывает секцию данных программы).
  • Внизу справа отображается содержимое стека (о нём следующая глава).

Нужно заметить, несмотря на то, что Ollydbg выглядит как обычная программа, это всё-таки отладчик и у него есть свои ограничения. Рекомендую, например, закрывать его каждый раз, как вам понадобится загрузить новую программу.

Мы узнаем, как работает этот пример, но прежде остановимся на исходнике.

Весь код из исходника, кроме двух строк, будет во всех программах Win32 под MASM’ом, поэтому вы должны знать, что он означает. Но не воспринимайте это слишком серьёзно. Там будет много длинно-умных слов. За такими словами очень часто прячется дребедень.

Хочу, чтобы вы поняли одну простую вещь. Совсем скоро вы будете знать команды Асма, и если вы поймёте основы программирования, а затем познаете интерес математики :), то каждый из вас сможет написать собственный компилятор для программ Ассемблера, возможно, не хуже MASM’a или FASM’a. Вы сможете сами придумывать всякие директивы, псевдокоманды, макросы, плагины и новые удобные фишки. Но команды процессора — совсем другое дело. Кроме того, что они в процессоре и изменить их нельзя (уточнение), большинство из команд имеет под собой математическое основание, заложенное в структуру цифровых технологий. Поэтому приоритет нужно отдавать в пользу команд процессора. А директивы MASM’a и все страшные слова за ними воспринимайте поверхностно. Я бы вообще не объяснял их (как Калашников в первых уроках), если б не знал, как сильно отвлекает внимание новичка непонятная строка в исходнике.

Теперь можно приступать к разбору этих самых директив.

Писать каждый раз такой код заново не надо. Это каркас программы для форточек. Мало того, к этому каркасу будут добавляться такие же постоянно повторяющиеся вещи. Создали один раз файл-заготовку — и далее можно вписывать туда только полезный код.

Я уже не раз говорил, что после точки с запятой идёт комментарий. Значит, MASM такую информацию не воспринимает. Можно писать всё что хочется.

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

В exe эта информация записывается в самом начале после заголовка PE. Если интересно, найдите в exe-файле буквы PE. Потом идут два нулевых байта, так как заголовок PE — это dword (4 байта). В следующем слове (двух байтах) как раз и будет информация о командах процессора, которые используются программой. Всё, от 386-го до сегодняшних 32-битных Intel-совместимых процессоров, обозначается кодом 014Сh. Для нас такая информация бесполезна, а вот директива «.386» обязывает нас не использовать новые команды, иначе программа не соберётся, пока мы не исправим эту директиву на «.486» или «.586» :).

Директива «.model» задаёт модель памяти flat для нашей программы. Модель flat лучше переводить: сплошная модель, а не плоская, как многие пишут.

Stdcall — это «уговор» о том, кто будет чистить параметры (функция, которую вызвали, или сам вызывающий). Мы всегда будем использовать вариант «функция чистит свои параметры». Он и называется stdcall. Однако такое объяснение далеко не полное, и я вернусь к параметру stdcall при объяснении вызова функций. К этому моменту вы уже будете знать, что такое стек.

От директивы .model явным образом зависит скомпилированный код в объектном файле и также зависит формат получившегося исполняемого файла.

Это просто. Всё уже объяснено в комментарии. В этой строке включается чувствительность к ПРОПИСНЫМ или строчным символам. То есть «МЕТКА» и «метка», «Имя» и «имя» будут уже не одно и то же!

На exe это никак не отражается.

Include — директива для подключения файлов (их расширение не обязательно должно быть inc). В подключаемых файлах может быть всё то же самое, что и в основном файле. То есть исходный код программы, данные, макросы (тоже код), комментарии и т.д. Причём вы можете вставлять инклуды, например, в середине кода, а продолжать программу уже в том файле.

В этих трёх inc-файлах в текстовом виде содержатся описания констант, структур и прототипов функций Windows. Далее я всё объясню, и вы поймёте, что это очень просто — как словарь или каталог товаров.

Вы можете перенести нужные описания в наш файл, и подключать inc’и не придётся, только это неудобно.

Includelib — директива для подключения файлов импорта. Я сам только что в первый раз задумался: а что в lib-файлах? Думаю, что вам сегодня объяснять это не нужно. Просто там информация о системных библиотеках, на основе которой будет сформирован вызов API-функций в нашей программе.

В exe-файл в данном случае из inc’ов не попадёт ни строчки кода, но компилятору они нужны для преобразования двух полезных строк (invoke) в машинный код. А из lib-файлов будут взяты две строки кода и данные, которые в exe запишутся в директорию импорта (всё тот же PE-формат).

Вот это очень интересная строка. Тоже директива.

Чтобы разделить код и разного рода данные, в exe-файлах существуют секции.

У каждой секции свои атрибуты и они тоже напрямую связаны с защищённым режимом. Это позволяет стабилизировать работу программ и упорядочить процесс написания. Считается, что машинный код менять не нужно, значит, для него подойдёт атрибут read only (только чтение). А данные (как теперь считается) выполнять не нужно, значит, можно в секции данных выключить атрибут исполняемого кода. Слава Богу, что всё это только теоретически (пока). Любая секция может содержать и код, и данные, и соответствующие атрибуты. Так что сейчас можно воспринимать секции только как полезную для программистов вещь.

Итак, секция данных объявляется директивой «.data», и это выставляет в ней нужные для данных атрибуты.

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

Здесь от MASM’a только db. Перед ним идут названия переменных. После db идёт содержание переменной, то есть текстовые или цифровые данные. Если данные текстовые, то они помещаются в кавычках или апострофах. А если данные просто байты, то так и пишут через запятую.

Добавьте в этой секции вот такую строку:

Теперь соберите файл ещё раз (старые obj и exe будут стёрты build.bat’om).

Несмотря на то, что переменная LaLa в коде не использовалась, в готовой программе будут эти байты:

Найдите их, загрузив пример в отладчик OllyDbg.

Я уже говорил, что обычно мы будем заканчивать строку текста нулевым байтом. Теперь вы видите, как это пишется в синтаксисе MASM’а.

В файле будет одно и то же: байты символов и 00.

А Str1, Str2 — это имена переменных, они могут быть любые, лишь бы не нарушали правил, описанных в help-файлах MASM’a.

Осталось объяснить, что такое db — одна из ряда однотипных директив инициализации данных и их резервирования. Длинные слова, мало что значат.

DB — сокращение от Data in Bytes (данные в байтах). Есть ещё DW (Data in Words), и DD (Data in Dwords), и ещё несколько аналогичных, побольше. Суть только в том, что запятая в DB разделяет байт, а в DD запятая разделяет 4 байта. Пример.

В исходнике так:

В исходнике так:

вызовет ошибку при сборке файла — размер в последней запятой не тот.

Вот и все основные премудрости представления целочисленных данных без знака (то есть положительных целых чисел) в MASM’e. Хотя нет, забыл сказать, что можно ещё объявлять резерв. Данные, которые на момент старта программы ещё не появились. Для чего это делается?

Допустим, в программе много промежуточных данных. Вписывая их в секцию неинициализированных данных (.data?), мы экономим в размере файла. Пока программа не стартовала, никаких данных в этой секции нет, а вот во время загрузки OS выделяет память сразу под все данные.

На первых порах можно спокойно писать все данные в секцию .data. Файл от этого больше не станет, а наоборот, будет только меньше. В PE-формате есть много всяких тонкостей, одна из них — это выравнивание секций. В большинстве программ в конце каждой секции есть нули, посмотрите в Hiew’e, и вы увидите, сколько места пропадает. Если я ничего не путаю, то секция должна быть кратна 512 байтам (200h). Значит, если у вас нет данных на эту сумму, то лучше не заводить новую секцию «.data?» а писать всё в «.data».

Здесь закончилась секция данных.

. и началась секция кода.

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

Вот мы и добрались до нашей первой метки. Метки в exe-файл не идут. Первая метка начинает код программы. Можно написать хоть «tuki_puki:», лишь бы в конце кода было «end tuki_puki». Что и сообщит MASM’у, где кончилась программа, а самое главное — где она началась.

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

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

то больше об адресах беспокоиться не надо. В exe окажется правильная маш.команда, а на Асме она будет выглядеть «JMP 00401000» или куда укажет метка.

В этих двух строчках и скрывается сама программа.

Первая строка вызывает API-функцию MessageBox и передаёт ей необходимые параметры.


Вторая строка выполняет действие, похожее на int 20 в DOS. Это завершающий код с параметром NULL.

На самом деле в коде будет не NULL, а нулевой байт (00h). Это значит, что и здесь можно писать:

Подробный разбор содержания этих двух строк будет уже в следующей Чтиве. Она была написана год назад, я тогда в первый раз увидел в WinXP код программы на Асме, поэтому дотошно описывал всё новое (но с таким количеством ошибок! :/).

Найдя оператор END в этом случае, компилятор MASM’a сочтёт, что метка «start» будет точкой входа в программу. Потому что больше заканчивать в этом файле нечего. Именно со строки
END первая метка
начнётся трансляция полезного кода в файл :).

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

Вернуться на главную

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

Некоторую информацию вы можете получить в популярной статье iXbt, а также здесь.

Язык Ассемблера. Компилятор FASM.

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

Преимущества знания ассемблера:

· Глубокое понимание работы компьютера и операционной системы.

· Максимальная гибкость при работе с аппаратными ресурсами.

· Оптимизация программ по скорости выполнения.

· Оптимизация программ по размеру кода.

· Дизассемблирование и отладка.

1 — Глубокое понимание работы компьютера и операционной системы

Илон Маск рекомендует:  Что такое код printer_create_brush

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

2 — Максимальная гибкость при работе с аппаратными ресурсами

Используя ассемблер, можно делать с компьютером все что угодно. А языки высокого уровня ограничены компилятором и используемыми библиотеками. Такие современные языки, как Java и C# вообще не позволяют работать с аппаратными ресурсами и операционной системой напрямую.

3 — Оптимизация программ по скорости выполнения.

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

4 — Оптимизация программ по размеру кода

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

5 — Дизассемблирование и отладка

Дизассемблирование — процесс и/или способ получения исходного текста программы на ассемблере из программы в машинных кодах. Целью дизассемблирования обычно является изменение чужих программ (чаще всего это взлом защиты). Если вы попытаетесь просмотреть в текстовом редакторе файлы с расширением .ехе , то увидите непонятную смесь символов. Это инструкции для процессора. Компьютер читает и исполняет их. Но иногда человек тоже хочет понять, что записано в этих файлах. Тогда он выполняет дизассемблирование. Дизассемблирование превращает исполняемый файл в текст программы на ассемблере. Если вы знаете ассемблер, у вас появляется часть прочесть текст программы и внести в него изменения.

Ассемблер очень может помочь и при отладке. Иногда случаются ошибки и в компиляторах. Вроде бы корректно написанный код выполняется вовсе не так, как предполагалось. Чтобы обнаружить такую ошибку надо посмотреть во что скомпилировался код, а разобраться в этом без ассемблера невозможно.

Учиться программировать начнем с процессора Intel 8086. Будем писать программы под DOS. Практическая ценность от программирования под DOS в наше время не очень большая, если вы, конечно, не собираетесь тесно работать с этой операционной системой. Но она позволит быстро освоить основы ассемблера, а потом мы уже перейдем к программированию под 32-битные системы.

Для программирования на ассемблере прежде всего необходим компилятор. Наиболее известные компиляторы это TASM, MASM и FASM. Мы будем использовать FASM. Это довольно новый, удобный, быстро развивающийся компилятор ассемблера, написанный на себе самом. Его преимущества — это поддержка сложных макросов и мультиплатформенность. Есть версии под DOS, Windows и Linux.

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

Последнюю версию FASM’a можно скачать с официального сайта http://www.flatassembler.net/. Для установки содержимое архива надо распаковать в какую-нибудь папку. Например, папка C:\FASM.

Для отладки написанных программ будем использовать старый добрый Turbo Debugger из пакета TASM. Архив также надо распаковать в какую-нибудь папку. Например, папка
C:\TD.

Особенности разработки программ в MASM

Для успешной работы с ассемблером MASM корпорации Microsoft в современных операционных средах (WindowsNT\2000\XP) необходимо иметь версию 6.13 этого пакета или выше. В него входят следующие основные программы:

— ml.exe— ассемблер и компоновщик (Masm and Link);

— cv.exe— отладчик (CodeView);

— lib.exe, implib.exe, nmake.exe, cref.exe, h2inc.exe, exehdr.exe, cvpack.exe, helpmake.exe, rm.exe, undeL.exe, exp.exe— вспомогательные утилиты.

В пакете MASM изначально предпринимались попытки совместить удобство средств программирования, свойственных языкам высокого уровня, с традицион­ными средствами машинно-ориентированных языков. Например, буква «М» в на­звании пакета означает слово «macro», то есть возможность создания макроопре­делений (или макросов), представляющих собой именованные группы команд. Их можно вставлять в программу в любом месте, указав только имя группы. Когда-то такая возможность действительно была отличительным свойством этого пакета, но сейчас этим никого не удивишь — макросредства есть во всех ассемблерах для платформы Intel (например, TASM), но, тем не менее, название осталось.

С помощью пакета MASM разработка программ выполняется традиционным для ассемблерного программирования способом — запуском отдельных программ трансляции, компоновки и отладки. Для этого используются программы masm.exe, mL.exe, link.exe и cv.exe. Во избежание путаницы необходимо отметить, что транс­ляция исходного файла может производиться двумя программами: masm.exe и mL.exe. В чем разница? До выхода MASM версии 5.1 включительно программа masm.exe была самостоятельным транслятором ассемблера. Начиная с MASM вер­сии 6.0 трансляция ассемблерного файла обычно выполняется программой mL.exe, которая кроме трансляции файла вызывает компоновщик link.exe. Это изменение сделано с целью унификации вызовов компиляторов командной строки для всех поддерживаемых этой фирмой языков программирования. К примеру, компиля­цию программы на языке С из командной строки выполняет программа cl.exe. Ее задачи — компиляция исходной программы на языках C/C++ и, при отсутствии синтаксических ошибок, компоновка и формирование исполняемого модуля. Ана­логичные задачи решает и программаmL.exe. Хотя в пакете MASM 6.13 допустимо использование программы masm.exe, нужно иметь в виду, что запуск link.exe в этом случае также должен выполняться отдельно. Наличие программы masm.exe в пакете MASM6.13 можно объяснить соображениями совместимости с предыдущими версиями пакета, поэтому особого смысла в ее использовании нет.

Далее приведены форматы командных строк для запуска программ ml.exe, Masm.exe и link.exe, а описание их ключей можно найти в приложении В(http:// www.piter.com/download).

Командная строка mL.exe имеет вид

ml [ключи] исх_файл_1 [[ключи] исх_файл_2] . [/link ключи_linк]

Ключи командной строки для ml.exe чувствительны к регистру.

Командная строка masm.exe имеет вид

masm [ключи] исх_файл [, [объектный_файл] [, [файл_листинга]

Компоновщик компонует (объединяет) объектные файлы и библиотеки в ис­полняемый файл или динамически компонуемую библиотеку (DLL). Командная строка link.exe имеет вид

link[ключи] объект_файлы [,[исполн_файл] [,[файл_карты] [,[файлы_библиотек] [.,def-файл]]][;]

Так же как и в случае пакета TASM, для эффективной работы с MASM нужно провести некоторую дополнительную работу. Исполняемые файлы пакета MASM 6.13 находятся в двух каталогах: ..\BIN и ..\BINR. Для удобства работы их лучше объединить в одном каталоге, к примеру, в каталоге ..\WORK. После этого остается поместить туда исходный файл. Пример командной строки для получе­ния пригодного к отладке исполняемого модуля может быть следующим:

ML.EXE /Zi /Fl Prg_6_l.asm

Если нет синтаксических ошибок, то можно запускать отладчик: CV.EXE PRG_6_1.EXE

Итоги

Структура программы на ассемблере отражает особенности архитектуры про­цессора. Для процессоров Intel типичная программа состоит из трех сегментов: кода, стека и данных. Но это не обязательное условие; например, если програм­ма не использует стек и для ее работы не требуется определения данных, то она может состоять всего лишь на одного сегмента кода.

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

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

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

— После получения корректного объектного модуля программу необходимо скомпоновать. Для этого применяется утилита-компоновщик, одним из ОСНОВНЫХ назначений которой является разрешение внешних ссылок. Если целевая про­грамма состоит из нескольких отдельно оттранслированных модулей и в них есть взаимные ссылки на переменные или модули, то компоновщик разрешает их, формируя тем самым правильные перемещаемые адреса.

— Результатом работы компоновщика является исполняемый (загрузочный) мо­дуль, имеющий расширение .ехе. Его уже можно запускать в надежде, что он правильно вы полнит задуманные программистом действия. Но чаше всего при первых запусках программы этого не случается, что говорит о наличии в про­грамме логических ошибок, поиск которых без специальных средств может быть долгим. Для поиска и устранения логических ошибок предназначен специаль­ный вид программного обеспечения — отладчики. С их помощью в большин­стве случаев довольно быстро удается снять большинство программных про­блем подобного рода.

Контрольные вопросы

1. Жизненный цикл программы – этапы;

2. каждой тетраде можно поставить в соответствие одну…

3. для пре­образования кода символа шестнадцатеричной цифры в соответствующее двоич­ное число есть два пути: …

4. Алгоритм преобразования должен различать прописные и строчные буквенные символы шестнадцатеричных цифр и корректи­ровать значение ASCII-кода на величину…

5. после записи значения шестнадцатеричной циф­ры следует символ «h» — зачем?

6. общая схема процесса разработки программы на ассемблере — рис.;

7. На этапе трансляции решается несколько задач: …;

8. Итог рабо­ты ассемблера — два файла:…;

9. При использовании пакета TASM получение объектного модуля исходного файла производится программой (ассемблером)…;

10. Перед работой с программой tasm.exe желательно провести некоторые подготовительные операции. После установки пакета TASM в каталоге \TASM\BIN…;

11. Для устранения ошибок нужно…;

12. файл листинг – для чего?

13. исходный файл наассемблере в общем случае может содержать конструкции следующих типов:…;

14. После устранения ошибок и получения объектного модуля можно приступать к следующему этапу — созданию исполняемого (загрузочного) модуля, или, как еще называют этот процесс, к… — зачем?

15. Результатом работы компоновщика является создание загрузочного файла с расширением …;

16. Отладка программы – назначение?

17. Отладчики бывают двух типов:…;

18. Отладчик позволяет решить две главные задачи:…;


19. Как произвести запуск отладчика?

20. Управление работой отладчика ведется с помощью системы меню. Имеются два тина таких меню:…;

21. Запустить программу в отладчике можно в одном из четырех режимов:;

22. Режим безусловного выполнения программы целесообразно применять…, и как;

23. Режим выполнения программы до текущего положения курсора целесообразно применять…, и как;

24. В режиме выполнения программы с установкой точек прерывания целесообразно применять…, и как;

25. Режим выполнения программы по шагам применяется для детального целесообразно применять…, и как;

26. Окно CPUотражает состояние процессора и состоит из пяти подчиненных окон…;

27. Особенности разработки программ в MASM — в него входят следующие основные программы:….

Темы и вопросы для самостоятельного обучения

Вопросы для самостоятельного изучения

Не нашли то, что искали? Воспользуйтесь поиском:

Лучшие изречения: Сдача сессии и защита диплома — страшная бессонница, которая потом кажется страшным сном. 8774 — | 7145 — или читать все.

188.64.174.135 © studopedia.ru Не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования. Есть нарушение авторского права? Напишите нам | Обратная связь.

Отключите adBlock!
и обновите страницу (F5)

очень нужно

Assembler

Глава #13 — Программирование в MASM

Программирование в MASM
Компиляторы языка Assembler несколько отличаются друг от друга т.к не существует единого стандарта. Обычно для Intel совместимых процессоров используется синтаксис команд, указанный в документации от Intel и AMD (называемый Intel), сами же команды и их использование описаны в документации с официальных сайтов производителей процессоров.
Но, кроме команд, в исходном коде программы также встречаются специальные символы и директивы, указывающие компилятору как нужно компилировать программу. Обычно, именно эти элементы программы отличаются для разных компиляторов.
Для программирования на Assembler’е под Windows чаще всего используется компилятор MASM, поэтому желательно знать особенности программирования на нем. Многие другие компиляторы пытаются быть совместимыми с MASM, поэтому данный материал частично пригоден и для их использования.

Об использовании чисел можно посмотреть в главе #2, о работе с памятью в главе #4 и компилирование программы показано в главе #8. Повторно этот и другие, уже изложенные материалы рассмотрены не будут.
Дополнительную информацию можно найти в пакете masm32 — \masm32\help\masm32.hlp а также в пакете с компилятором MASM (если он в виде отдельного продукта) — \bin\qh.exe (переменная среды HELPFILES должна указывать на папку с файлами справки — \help в каталоге установленного MASM’а).
Краткое описание MASM есть также в MSDN: .NET Development -> Visual Studio .NET -> Product Documentation -> Visual C++ -> Reference -> Microsoft Macro Assembler Reference.

Данная глава посвящена, в основном, макросредствам компилятора MASM, его элементам хранения данных и улучшения читаемости исходного кода.
——————————-

Зачем нужны разнообразные макросредства?
Компилятор нужен для автоматизации вычислений, необходимых при создании программы.
Пример: jmp AnyLabel — безусловный переход на метку AnyLabel
  • Если знаковое смещение от метки AnyLabel до конца этой команды поместится в байт, то создается следующий код:
    db 0EBh,AnyLabel-($+1)
  • Если разница больше байта, то:
    db 0E9h
    dd AnyLabel-($+4)

Как видно из примера, код команды и операнд зависят от текущего адреса (обозначен символом «$») и от адреса метки AnyLabel. Поэтому программу удобнее писать не в hex редакторе, а используя компилятор, который будет производить все нужные вычисления при модификации программы.

В отличие от компиляторов языков высокого уровня, Assembler не «думает» за программиста, а только производит «механические» вычисления, тем самым не искажая оригинальные мысли автора программы. Чем больше компилятор будет считать, тем проще будет написание программы, не ухудшая откомпилированный вариант. Также, получится более понятный и организованный исходный код, который будет легче воспринимать и модифицировать в дальнейшем.
Но, самое главное — нужно устанавливать зависимость одних участков программы от других, где это возможно (как в примере с командой jmp). Это избавит вас от рутинной переработки исходника при модификации, и от возможных ошибок, возникающих при этом.
——————————-

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

Следующий тип комментария удобен для пояснения небольших участков кода. Текст, находящийся за символом «;» и до конца строки игнорируется компилятором. пример:
xchg eax,eax ;поменяет местами значения регистров eax и eax
Также бывают блочные комментарии, исключающие целый блок текста:
comment @ Текст
пояснения @
после директивы comment указывается символ, который будет означать последнюю строку блочного комментария.

Если строка слишком длинная, ее можно разделить на несколько, используя символ «\»
Пример:
xchg eax,\ ;начало строки
eax ;продолжение — конец

Большие программы удобно разделять на несколько файлов, после чего можно использовать директиву «include имя файла», для их объединения. Файл, указанный этой директиве компилируется также, как будто его содержимое находится вместо директивы include.
——————————-

Выражения
Вместо непосредственно указанных чисел, в программе можно использовать выражения, автоматически вычисляемые при компиляции. В выражениях можно использовать следующие операторы: +, -, *, /, mod (остаток от деления), div, and, or, xor, not, shr, shl, скобки для указания порядка вычислений и т.д.
Пример:
mov eax,2*(3+4) ;откомпилируется как mov eax,14
mov ecx,3 shl 2 ;mov ecx,12
Также, можно использовать переменные в выражениях. Переменными желательно обозначать числа, многократно используемые в программе. Пример:
WindowSize=100+5 ;создадим переменную WindowSize, имеющую значение 105
mov eax,WindowSize+2 ;mov eax,107
mov ecx,WindowSize+4 ;mov ecx,109
Такие переменные не являются ячейками памяти программы, а существуют только для компиляции. Значение переменной может быть изменено. Переменные обычно объявляются в одном месте программы для удобной настройки ее работы, а в больших программах выносятся в отдельный файл.
При помощи директив equ и textequ можно указать строку в качестве значения переменной. Объявленная при помощи директивы equ переменная может быть как текстовой так и числовой, в зависимости от того сможет ли компилятор интерпретировать ее значение как число или нет при инициализации. Тип такой переменной в последующим не может быть изменен. Для указания строк, их заключают в угловые скобки. Длинна строки не может превышать 255 символов. Для указания самих скобок и других специальных символов в качестве текста, перед ними ставится знак «!»
Пример:
ProgName equ «Any Program»
WindowTitle db «This program name is: «, ProgName
——————————-

Структуры
Структуры нужны для того чтобы задать формат хранения группы данных, используемых в программе. Используются обычно для упрощения модификации формата хранения данных а также для согласованности форматов данных между разными модулями и частями программы.
Пример:
AnyStruct struct
AnyDword dd ?
Any3Words dw 10,20,30
TString5 db 3 dup (‘z’)
AnyStruct ends

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

После объявления структуры, например, можно использовать следующие выражения:
sizeof(AnyStruct)- размер структуры в байтах (=13)
AnyStruct.Any3Words- (=4)

Структуры позволяют также задать выравнивание (указывается как число после слова struct) для выравнивания элементов структуры. Также, структура может содержать выражение org Число, указывающее смещение структуры.
После описания, можно объявлять данные указанного типа, например:
StructTest AnyStruct <>
Создаст следующие данные:
StructTest.AnyDword dd ?
StructTest.Any3Words dw 10,20,30
StructTest.TString5 db 3 dup (‘z’)
Т.к все параметры структуры не инициализированы (не указаны в угловых скобках), то область памяти StructTest имеет значения всех элементов структуры AnyStruct по умолчанию

Пример инициализации структуры:
TStruct2 AnyStruct ,»ttt»>
Получим следующие данные:
TStruct2.AnyDword dd 2
TStruct2.Any3Words dw 5,20,8
TStruct2.TString5 db «ttt»
В данном примере опущено только второе слово из Any3Words, и принимает значение по умолчанию, остальные данные инициализируются значениями, указанными в угловых скобках.

После инициализации, к данным можно обращаться следующим образом:
mov [TStruct2.AnyDword],10
inc [TStruct2.Any3Words]
Если адрес структуры, например, находится в регистре eax, то:
assume eax: ptr AnyStruct
mov ecx,[eax].AnyDword
mov bx,[eax].Any3Words
assume eax:nothing
либо
mov eсx,[eax].AnyStruct.AnyDword

Кроме структур, можно создать еще и следующие типы данных: union (объединение) и record (запись). Чаще всего они встречаются внутри структур.

union
Представляет одну и туже область памяти как разные типы данных. Используется обычно в случаях, когда исключено одновременное хранение перечисленных данных а также для ссылки на одну и туже область данных как на разные типы, в зависимости от имени используемого элемента. Размер объединения равен максимальному размеру перечисленного элемента. Указываются объединения почти также, как и структуры:
AnyUnion union
FullDword dd ?
LowestWord dw ?
LowestByte db ?
AnyUnion ends

TUnion AnyUnion
После этого TUnion.FullDword = 12345678h, TUnion.LowestWord=5678h, а TUnion.LowestByte=78h

record
Используется для описания группы бит. Позволяют удобно использовать выражения с битами, и создавать битовую маску.
Пример — запись, описывающая число Real4 (с.м предыдущую главу):
FpuReal4 record Sign:1, Exponent:8=127, Significand:23
mov eax,FpuReal4 ;mov eax,-1.5

record поддерживает следующие выражения: mask — получить маску записи/поля width — узнать размер в битах записи/поля. В выражениях, название элемента записи равно смещению (в битах) от начала записи.
Пример:
FpuReal4 record Sign:1, Exponent:8=127, Significand:23
mov ecx,FpuReal4 > ;2.0
;получим значение поля Exponent в регистр eax
mov eax,ecx
shr eax,Exponent
and eax,mask Exponent shr Exponent ;eax=128
——————————-

Макросы
Макросы используются для автоматизации создания часто повторяющихся участков программы. Обычно эти участки схожи по смыслу между собой, но несколько отличаются в реализации, либо слишком малы для выноса в процедуру. Макрос представляет из себя описание алгоритма для генерации исходного кода, интерпретируемого при компиляции.
Существует два типа макросов: макро-функции и макро-процедуры.
Макро-процедуры при вызове просто заменяются на последовательность команд, вызываются аналогично обычной команде, и может быть вызваны только в начале строки.
Макро-функции отличаются тем, что могут возвращать некое значение, и могут вызываться в выражениях. Параметры вызова указываются в круглых скобках. Для указания макро-функции внутри макроса используется директива «EXITM »
Макросы могут иметь локальные переменные. Объявляются они перечислением через запятую после слова «local». Они существуют только в самом макросе, и поэтому не создают конфликт с уже объявленными в других местах переменными с таким же именем.
Внутри макросов можно использовать специальные макро- комментарии, указываемые после символов «;;». Они не попадают в листинг.

Пример макро-процедуры:
ZeroEax macro ;объявим макро-функцию
xor eax,eax
endm ;конец макроса

После объявления макрос можно использовать также, как и команду процессора.
Пример:
ZeroEax ;откомпилируется как xor eax,eax

Макрос может иметь параметры
Пример макро-функции:
XorAndRet macro reg
xor reg,reg
EXITM
ENDM
.
neg XorAndRet(eax) ;Откомпилируется в xor eax,eax и neg eax

Пример #2:
ZeroReg macro param1:=
xor param1,param1
ENDM
.
RVar equ edx
ZeroReg ecx ;откомпилируется как xor ecx,ecx
ZeroReg RVar ;раскроется как xor RVar,RVar после откомпилируется как xor edx,edx
ZeroReg ;параметр неуказан, используется дефолтное значение (получим xor eax,eax)

Как видно из примера, компилятор просто производит поиск текста «param1» в теле макроса, заменяет его на указанное в качестве параметра значение, и после компилирует. Этот процесс часто называется «раскрытием макроса». Это важно понимать для написания макросов.
Для того, чтобы передать в качестве параметра именно значение переменной нужно поставить перед ней знак %, а для того, чтобы раскрыть макросы во всей строке, нужно поставить этот же знак в начале строки.
Пример:
ProgName equ
%WindowTitle db «This program name is: &ProgName&»
Для того чтобы отделить имя переменной от текста, используется символ «&» Пример:

moveXx macro dest,src
mov e&dest&x,e&src&x
ENDM
moveXx a,b ;откомпилируется в mov eax,ebx

Параметров может быть несколько, можно задавать значения по умолчанию (как в примере ZeroReg), можно также указывать следующие модификаторы:
-req — обязательный параметр. Если при вызове он не указан то компилятор выдаст ошибку.
-vararg — последний параметр может быть vararg. Это означает, что все, что указанно при вызове макроса в качестве параметров присваивается этому параметру как единая текстовая строка.
Пример:
DisplayNote macro etxt:vararg
% echo Note: @FileCur(@CatStr(%@Line)) etxt
ENDM
.
DisplayNote any,test,params ;comment

Переменной etxt присвоится текст «any,test,params» как единый параметр т.к она vararg. Если убрать слово vararg и двоеточие, то переменной etxt присвоится только текст «any», и при компиляции появится предупреждение о том, что указаны лишние параметры при вызове (каковыми компилятор посчитает слова «test» и «params»). Параметры vararg обычно используются при создании макросов с переменным количеством параметров.

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

Организовать цикл можно при помощи следующих директив:
FOR, FORC, REPEAT, WHILE, и GOTO
FOR parametr[:req | :=значение по умолчанию],
Данная директива повторяет цикл столько раз, сколько параметров содержится в указанной текстовой строке. Первый операнд для FOR указывает имя переменной, которой присваивается текущий параметр для каждой итерации. Можно задать также значение по умолчанию, или модификатор req.
Пример:

ZeroRegs macro reglist:vararg
FOR curparam:= ,
xor curparam,curparam
ENDM
ENDM
.
ZeroRegs ebx,,ecx ;откомпилируется в xor ebx,ebx / xor eax,eax и xor ecx,ecx

Пример#2. Следующая запись в исходном коде обнулит значения регистров, указанных в угловых скобках:
FOR curreg,
xor curreg,curreg
ENDM

Для преждевременного прерывания цикла или макроса используется директива EXITM Остальные директивы работают схожим образом:
Директива FORC parametr, организовывает цикл, повторяющийся для каждого символа заданной строки, REPEAT выражение — заданное количество раз, WHILE выражение — пока истинно (не нулевое) значение выражения. Конец блока обозначается директивой ENDM.

GOTO макрометка — осуществляет безусловный переход на указанную макрометку, которая задается следующим образом :LabelName.

Часто в макросах встречаются директивы условного ассемблирования. Эти директивы проверяют заданное условие, и на основе этого решают компилировать указанный блок или нет. Конец блока обозначается директивой ENDIF

IF не нулевой (истина)
IFE нулевой (ложен)
IFB tесли выражение пустое
IFNB если выражение не пустое
IFDEF если переменная определена
IFNDEF если переменная не определена
IFDIF текстовые строки разные
IFDIFI текстовые строки разные (без учета регистра)
IFIDN текстовые строки идентичны
IFIDNI текстовые строки идентичны (без учета регистра)

Пример:
IFDEF AnyVar
.. ;откомпилируется если определена переменная AnyVar
ELSEIFDEF AnySecondVar
.. ;откомпилируется если определена переменная AnySecondVar
ELSE
.. ;откомпилируется если переменные AnyVar и AnySecondVar не были определены.
ENDIF

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

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

Существует директива .ERR [текст] для отображения ошибки, и ее версии с окончаниями, как у директивы IF для отображения ошибки при определенном условии.

Для операции сравнения используются следующие операторы:
EQ (равно), NE (не равно), GT (больше чем), LT (меньше чем), GE (больше или равно), LE (меньше или равно), которые возвращают 0 если ложно, или -1 если истинно. Они часто используются в директивах условного ассемблирования.

Пример — макрос, печатающий числовой параметр — слово или другой тип:
IsWord macro number:req
IF number GT 0FFh and number LE 0FFFFh
echo number is word
ELSE
echo number is not word
ENDIF
ENDM

Компилятор masm

Yasm – свободный ассемблер, являющийся попыткой полностью переписать ассемблер NASM. Лицензируется под лицензией BSD и в настоящее время развивается Питером Джонсоном и Майклом Ерманом. Yasm… Скачать

WinAsm Studio

WinAsm Studio — бесплатная среда разработки программного обеспечения для Microsoft Windows и DOS, изначально предназначенная для написания программ на языке ассемблера. Создана программистом Антонисом Киприану…. Скачать

SASM (SimpleASM) — бесплатная простая кроссплатформенная Open Source среда разработки программного обеспечения на языках ассемблера NASM, MASM, GAS, FASM с подсветкой синтаксиса и отладчиком. SASM… Скачать

RadASM

RadASM — бесплатная среда разработки программного обеспечения для операционных систем Microsoft Windows и не только, изначально предназначенная для написания программ на языке ассемблера. Создана программистом Ketil Olsen… Скачать

NASM (Netwide Assembler) — свободный ассемблер для архитектуры Intel x86. Используется для написания 16-, 32- и 64-разрядных программ. NASM был создан Саймоном Тэтхемом совместно с… Скачать

Fresh > 1

Fresh IDE — свободная интегрированная среда разработки на визуальном языке ассемблера для операционных систем Microsoft Windows со встроенным FASM. Fresh IDE поддерживает сборку программ для тех же… Скачать

Visual Assembler

Visual Assembler – бесплатная компьютерная программа, которая написана для тех, кто программирует на ассемблере. Borland Tasm – транслятор сделанный для MS-DOS. Т. к. начинающему программисту на… Скачать

Beye (от англ. Binary EYE, старое название Biew) – мультиплатформенный редактор файлов с поддержкой бинарного, шестнадцатеричного и дизассемблерных режимов. Для дизассемблирования используется Intel-синтаксис. Цветные AVR/Java/x86-i386-AMD64/ARM-XScale/PPC64… Скачать

Программирование на языке Assembler в FASM

В начале было слово… Если точнее то было просто предложение от Kinder-а написать статью посвящённую макросам в FASM. Я согласился попробовать, но рассматривать макросы отдельно от синтаксиса как-то не очень правильно, а синтаксис без примеров разобрать сложно и в результате получилось сочинение о том как писать программы в FASM.


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

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

Данная статья посвящена использованию макросов в компиляторе FASM. Для начала вопрос: «Что такое макрос?» (поскольку я не могу услышать Ваш ответ прямо сейчас, когда пишу эту статью, отправляйте свои ответы мне по электронной почте, если Ваше мнение отличается от моего мнения).

Макрос — это инструкция препроцессору компилятора развернуть, встретившееся в коде программы, имя макроса (возможно с параметрами) в последовательность строк кода ассемблера с использованием переданных параметров (если таковые заданы).

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

Внутри определений макросов можно использовать инструкции if и else при этом каждая инструкция if должна закрываться инструкцией end if. Пример из руководства, поставляемого вместе с компилятором:

Этот макрос является расширением инструкции процессора mov. В данном случае если оба операнда макроса являются сегментными регистрами используется связка «push — pop», в любом другом случае используется стандартная инструкция «mov op1,op2». Оператор in позволяет проверить соответствие операнда нескольким значениям в угловых скобках.

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

В данном случае нас интересует инструкция eq, которая позволяет проверить эквивалентен ли операнд какому либо значению, в нашем случае мы проверяем его отсутствие, и если он отсутствует, то подставляется ветвь if иначе подставляется ветвь else.

Директива purge позволяет отменить последнее определение макроса.

Кроме уже изложенного в определение макроса можно передавать заранее неизвестное количество операндов (пар операндов и т.д.), для этого необходимо заключить операнды, количество которых заранее неизвестно, в квадратные скобки. Например для вызова процедур написан макрос STDCALL:

Операндами этого макроса служат один обязательный (proc) и несколько необязательных параметров (arg). Директива reverse сообщает препроцессору, что следующие строки необходимо повторить столько раз, сколько параметров arg передано макросу начиная с последнего параметра. Директива common сообщает препроцессору, что следующие строки необходимо повторить только один раз. Директива forward сообщает препроцессору, что следующие строки необходимо повторить столько раз, сколько параметров arg передано макросу начиная с первого параметра. Действие директив common, forward и reverse заканчивается другой директивой common, forward или re-verse соответственно или закрывающейся фигурной скобкой. Если ни одна из этих директив не встречается в определении макроса, то макрос развернётся для всех параметров начиная с первого. Неизвестное количество параметров можно передать и другому макросу:

В этом примере при выполнении ветви if в макрос stdcall кроме параметра [proc] передаются все полученные параметры arg.

Внутри макроопределения может использоваться оператор #, который может использоваться для составления операторов ассемблера, например условного перехода:

При выполнении макроса jif ax,ae,10h,exit макрос будет развёрнут в следующую конструкцию:

Так же этот оператор можно использовать и для составления имён переменных или макросов внутри макроопределений:

Теперь при вызове данного макроса в секции данных

Мы будем иметь два массива из 10 байт каждый, с именами Chif.mas и Rab.mas.

Аналогично можно создать и переменные и макросы внутри определения макроса. Однако определить макрос внутри макроса обычным путём:

препроцессор не позволит использовать данную конструкцию, выход в использовании директивы fix она эквивалентна директиве equ, но препроцессор обрабатывает fix позже чем equ, что и позволяет использовать подобную конструкцию:

Синтаксис компилятора ФАСМ несколько отличается от синтаксиса других компиляторов, но отличия незначительны и в основном касаются макроязыка.

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

mov eax,var; записываем в регистр еах адрес переменной var mov eax,[var]; записываем в регистр еах значение переменной var

Структура каркасного приложения на ФАСМ-е

В этой части мы рассмотрим, основы
синтаксиса данного компилятора и
рассмотрим каркасные приложения для
основных форматов исполняемых файлов.

Итак, что необходимо для работающего приложения?

Во-первых мы должны указать компилятору какого формата должен быть создаваемый файл. Это делается директивой format после, которой следует указание форматов:

  • PE формат исполняемых файлов Windows. Далее должно следовать уточнение GUI (графический интерфейс), console (консольное приложение) и native (если Вы знаете, что это такое сообщите мне). Если выбран графический интерфейс, то следует также указать версию графического интерфейса 4.0, а так же зарезервированное слово DLL, если собирается динамическая библиотека.
  • MZ формат исполняемых файлов MS DOS.
  • COFF или MS COFF (для линковки продуктами Microsoft)для создания объектного файла, который в дальнейшем будет линковаться к другому проекту или к ресурсам.
  • ELF формат для создания исполняемых файлов UNIX подобных систем (Linux).

Как Вы наверно поняли, я не знаком с файлами ELF типа и в данной статье мы не будем его рассматривать. Если директива format не указана, компилятор соберёт файл в формате com.

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

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

Форматы PE исполняемых файлов Windows.
Консольные приложения
Консольное приложение может использовать все функции API предоставляемые Windows, но работать с такой программой пользователь может только через командную строку.

Вот простой пример консольного приложения:

В данном примере создаётся три секции: данных, кода и секция импортируемых функций. Секцию импорта я создаю при помощи макроса _library, в стандартной поставке такого макроса нет, но если написать без подчёркивания перед library то всё скомпилируется правильно. Причина проста, я пользуюсь FASM-ом с версии 1.35, а тогда такого макроса еще не было (а сам я ещё не смог бы его написать) и для совместимости с ранее написанными программами я включил два варианта этого макроса. В ранней версии, необходимо было записывать все используемые в приложении функции вручную, теперь же работу по подключению только используемых функций берёт на себя компилятор, хотя за такое удобство приходится платить некоторым увеличением времени компиляции. Более подробно о принципе работы этого и других макросов будет написано в соответствующем разделе ниже.

Название секции импорта «.idata», хотя название можно изменить и это не повлияет на работоспособность программы, т.к. мы указали назначение этой секции import data, далее следует указание атрибутов, в данном случае readable, что означает для чтения и этого достаточно, если Вы не собираетесь менять содержимое этой секции.

Следующей рассмотрим секцию данных. У нас секция данных имеет название «.data», далее следует указание назначения секции data, что означает секцию данных, атрибуты указаны readable writeable, т.е. эта область памяти будет доступна для чтения и записи. Если указать только первый атрибут (readable) при выполнении программы произойдёт ошибка «Программа выполнила недопустимую операцию».

Последней рассмотрим секцию кода, она имеет название «.code» назначение секции code, т.е. исполняемая часть программы, атрибуты readable executable, что означает, что из секции кода мы можем читать данные (под данными может подразумеваться и коды инструкций и определённые в этой секции данные) и можем выполнять содержимое этой секции.

Кроме рассмотренных секций, консольное приложение может иметь секции экспортируемых функций и секцию ресурсов.

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

Графическое приложение Графические приложения Windows это оконные приложения, такие как Word, Excel, Notepad и т.д.

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

Так же, как и в примере с консольным приложением, в первой строке указана директива format PE, но вместо console, как в предыдущем примере, дано указание GUI 4.0 компилятору собирать приложение с графическим интерфейсом версии 4.0.

Приложение из нашего примера ничего полезного не делает, оно просто создаёт окно, которое реагирует только на нажатие двух системных кнопок в верхнем правом углу окна «Свернуть» и «Закрыть». Приложение содержит секции данных, кода, импорта и ресурсов. После указания точки входа в программу entry start подключаются файлы содержащие макросы и определения констант и структур Windows. Затем следует секция данных, определение которой дано в предыдущем разделе, и секция кода. В секции кода мы получаем хендл нашего модуля (GetModuleHandle), загружаем иконку (LoadIcon), определённую в секции ресурсов, загружаем стандартный курсор Windows (LoadCursor) и заполняем структуру wc для дальнейшей регистрации класса нашего окна (RegisterClass). После регистрации класса окна мы создаём окно зарегистрированного класса (CreateWindowEx). В структуре wc имеется член .lpfnWndProc, в который мы записываем адрес начала процедуры обработки событий окна WindowProc. Далее следует цикл обработки сообщений, посылаемых окну системой Windows.

Процедура обработки сообщений получает от Windows четыре параметра:

proc WindowProc, hwnd, wmsg, wparam, lparam

hwnd — хендл нашего окна
wmsg — код сообщения, которое Windows послало нашему окну
wparam и lparam — уточняющие параметры посланного сообщения.

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

Таким образом следующая строка proc Win,param будет развёрнута в следующую конструкцию:

Более подробно это означает, что мы определяем метку Win, которая означает начало процедуры. В следующей строке записана директива virtual at ebp+8, которая должна заканчиваться директивой end virtual. Внутри этого определения помещаются переменные, которые будут размещаться с адреса указанного после at, в нашем примере это ebp+8. Это означает, что в нашем примере запись «mov param,eax» будет заменена на «mov [ebp+8],eax«. Если бы в определении процедуры было две или более переменных, то адрес каждой последующей переменной увеличивался бы на размер предыдущей переменной. Таким образом:

определит три переменные с адресами

У некоторых читателей может возникнуть вопрос: «А зачем все эти пляски?». Ответ прост: регистр ebp указывает на вершину стека и все переменные, передаваемые в процедуру, находятся выше (в смысле в памяти с адресами больше чем вершина стека т.к. вершина стека сдвигается вниз когда в стек помещаются данные и вверх, когда данные из стека изымаются) текущей вершины стека.

Первой, в определении переменных внутри virtual — end virtual записана директива local, которая указывает компилятору, что следующие за ней через запятую метки, локальны в данном макросе.

Последней, внутри описания виртуальных переменных, следует определение макропеременной ..ret = $ — (ebp+8), которая фактически содержит количество переданных процедуре байт параметров. Она используется при написании эпилога процедуры для очистки стека при возврате управления инструкции, следующей за вызовом данной процедуры.

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

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

Таким образом dynamic_size — это размер памяти отведённой под локальные переменные. Последняя инструкция, в прологе процедуры, enter dynamic_size,0. Это единственная строка, которая попадёт в исполняемый код программы. Завершаться процедура должна макросом return, который определён следующим образом:

Полагаю здесь не должно быть сложностей с пониманием макроса.

Процедура должна правильно обрабатывать поступившие сообщения. Те сообщения, которые мы хотим обработать сами, выделяются в первых строках процедуры и управление передаётся на те участки кода, которые должны производить необходимые действия, а те сообщения, которые нам нет необходимости обрабатывать, мы доверяем обработать системе Windows (Def-WindowProc).

Хочу обратить Ваше внимание на то, что в данной программе используются глобальные метки (wmdestroy, wmcreate, defwndproc), что не очень удобно в программах содержащих несколько процедур (большинство оконных процедур обрабатывают одни и те же сообщения), поэтому рекомендуется внутри процедур использовать локальные метки (.wmdestroy, .wmcreate, .defwndproc).

Теперь пришло время посмотреть на секцию импорта. Она отличается от рассмотренной в предыдущем разделе здесь мы используем макрос library, который определён следующим образом:

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

Таким образом, макрос library создаёт заголовок таблицы импорта, вначале записывая смещение названия библиотеки DLL, а затем смещение, с которого начинаются имена импортируемых функций.

Далее следуют макросы import, определённые следующим образом:

Он создаёт тело таблицы импорта для каждой из импортируемых библиотек.

Вначале создавая таблицу смещений имён импортируемых функций, а затем записывая имена импортируемых функций. Более подробно о структуре PE-файла Вы должны прочитать в соответствующей литературе (например в туториалах, которые написал Iczelion).

После таблицы импорта создаётся таблица ресурсов:

В нашем примере мы используем только одну иконку.

Динамические библиотеки (DLL) Последним в данном разделе рассмотрим простую динамическую библиотеку.

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

Мы рассмотрим создание динамической библиотеки на примере процедуры, возвращающей строку, содержащую текущую дату. Мы создадим два модуля один файл динамической библиотеки (dll2.dll) и файл использующий созданную нами библиотеку (usedll.exe).

Вот файл динамической библиотеки:

Первые строки format PE GUI 4.0 DLL и entry DllEntryPoint указывают компилятору создать РЕ-файл динамической библиотеки с точкой входа на метке DllEntryPoint.

Как Вы могли заметить динамическая библиотека практически ни чем не отличается от обычной программы Windows, кроме того, что входная процедура вызывается системой в четырёх случаях:

  • Когда динамическая библиотека загружается в адресное пространство процесса (параметр fdwReason = DLL_PROCESS_ATTACH = 1);
  • Когда динамическая библиотека выгружается из адресного пространства процесса (параметр fdwReason = DLL_PROCESS_ DETACH = 0);
  • Когда создается новый поток в рамках динамической библиотеки (параметр fdwReason = DLL_ THREAD_ATTACН = 2);
  • Когда разрушается (прекращается) поток в рамках динамической библиотеки (параметр fdwReason = DLL_ THREAD_DETACH = 3).


В обработке стартовой процедуры мы определяем значение параметра fdwReason и если динамическая библиотека была загружена или выгружена выдаём соответствующее сообщение.

В конце файла мы создаём секцию экспорта нашей библиотеки:

В таблице мы задаём имя файла динамической библиотеки и имена экспортируемых процедур.

Макрос export определён следующим образом:

Сперва, объявляются локальные метки и подсчитывается количество экспортируемых процедур:

Затем заносится смещение RVA имени библиотеки:

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

Далее объявляется метка начала массива смещений экспортируемых функций и создаётся массив их смещения:

Следом объявляется метка начала массива смещений имён экспортируемых функций и создаётся сам массив:

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

Ну и в конце макроса записывается имя динамической библиотеки и создаётся массив имен экспортируемых функций:

Для более подробного ознакомления со структурой секции экспорта необходимо читать специальную литературу по структуре РЕ — файлов.

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

Обычное графическое приложение не имеющее своего окна и ресурсов размером 2 кБ. Импортирует только три функции две предоставляемые системой Windows и одна написанной нами динамической библиотекой. Как видите подключение созданной нами динамической библиотеки, ничем не отличается от подключения стандартных библиотек Windows. В этом и заключается вся прелесть использования ФАСМ-а, он не берёт на себя слишком много, что в купе с гибким макроязыком позволяет сделать очень многое максимально просто.

Формат исполняемых файлов MS COFF (obj — файлы) Использование obj-файлов в ФАСМ, лично мне, не кажется жизненно необходимым, однако использование этого формата возможно понадобится при создании проектов на С/С++. Именно на таком примере мы и рассмотрим формат файлов MS COFF.

Для примера напишем программу, выводящую МессаджБокс с текущей датой. Модуль на ассемблере будет составлять строку символов, а модуль на С++ будет выводить эту строку в МессаджБокс.

Вот модуль на ассемблере:

Обратите внимание на строку 2, где закомментировано определение точки входа в программу (_WinMainCRTStartup)если эту строку раскомментировать, то точка входа в программу будет именно в модуле на ассемблере. Результат работы функции записывается в переменную _outs и адрес (указатель на строку) возвращаем в регистре еах. В этом модуле используются макросы копирования строки в строку (StringCopyHTML ebx,day). Этот макрос не входит в поставку компилятора и содержится в файлах strings.mac и strings.asm он просто копирует строку (не включая нулевой символ), указатель на которую передаётся вторым параметром, в строку, указатель которой передаётся вторым параметром, после его выполнения в регистре еах количество скопированных символов. Эта процедура выдаёт строку типа: «День: 28 Месяц: 10 Год:2003». Для использования объектного модуля созданного на ассемблере необходимо в проект на С++ добавить созданный нами объектный файл (Project->Add to project->Files. . . в появившемся окне необходимо выбрать тип файлов obj и указать используемый файл). Теперь рассмотрим текст программы на MS Visual C++ 5.0:

Здесь ничего сложного: подключаем файл «windows.h», затем объявляем внешнюю процедуру, возвращающую указатель на строку символов с нулём в конце, объявляем переменную dat, как указатель на строку символов, и наконец функция WinMain в которой вызываем нашу функцию (Wind) на ассемблере и выводим результат в мессаджбокс. Внимательный читатель скажет: «Стоп, но в модуле на ассемблере функцию мы объявили как ?Wind@@YAPADXZ, а в модуле на С++ вызываем Wind». Всё правильно. Просто MS Visual C++ 5.0, к объявленным внешним (находящимся в скомпилированных модулях) процедурам добавляет дополнительные символы, для контроля соответствия типов передаваемых результатов из процедуры и переменных в процедуру. Вот краткая структура имени функций формируемых MS Visual C++:

Префикс Объявленное имя переменной Разделитель Тип вызова процедуры Тип результата Тип параметров Постфикс
? Wind @@Y A PAD X Z

Причём постфикс изменяется в зависимости от наличия передаваемых в функцию параметров. Если параметры передаются, то постфикс «@Z», а если параметры не передаются то просто «Z».

Типы вызова процедуры следующие:

__cdecl A
__fastcall I
__stdcall G

Типы результата и передаваемых переменных имеют одинаковые обозначения:

Тип результата Размер Обозначение
char 1 D
unsigned char 1 E
short 2 F
unsigned short 2 G
enum 2 ?AW4__unnamed@@
long 4 J
unsigned long 4 K
int 4 H
unsigned int 4 I
float 4 M
double 8 N
long double 10 O
bool 1 _N

Причём если передаётся (получается) не параметр, а его адрес (например char *) то перед обозначением переменной добавляется PA. Если параметры, получаемые из процедуры или передаваемые в процедуру, отсутствуют, вместо них ставится X.

Читатель может сказать: «Это как же нужно мозг морщить, чтобы всё запомнить и не перепутать ничего при составлении имени процедуры?». Такая работа имеет основное свойство: она не особо интеллектуальна, но муторна и действия при её решении вполне укладываются в рамках алгоритма. Отсюда вывод: компьютер на то и создан, что бы выполнять подобные задачи так пусть сам, и добавляет все эти префиксы, постфиксы и т.п. Напишем макрос, который будет объявлять процедуру составляя её имя из имени которое мы ему передаём и добавляя к нему все необходимые префиксы.

Использовать переменные длиннее 4 байт мы не будем, но думаю Вы сами сможете модернизировать макросы под свои нужды если понадобится.

Для начала мы должны определить символьные константы, соответствующие типам вызова процедуры и типам получаемых и возвращаемых параметров:

Далее следует определение макроса:

В первой строке мы очищаем временную символьную константу, далее следует указание препроцессору обработать строку «dr fix dr#type» для всех значений параметров type начиная с первого. Эта строчка, просто создаёт константу содержащую все символы соответствующие всем получаемым процедурой параметрам. Например если в процедуру передаётся два параметра типа int, то dr будет эквивалентно HH. Далее следует указание препроцессору следующие строки обработать только один раз (common) в этих строках мы делаем видимой из других модулей программы метку (название) нашей процедуры и собственно следует содержимое макроса proc, рассмотренного ранее.

Полагаю, этот макрос не должен вызвать затруднений. Сложности в понимании может вызвать только строка «?#name#@@Y#calltype#returns#dr#@Z», объясню её более подробно. Сперва стоит знак вопроса (посмотрите табличку со структурой имени процедуры), после которого, значок номера (#), он говорит препроцессору, что далее следует символьная константа, которую следует склеить в одну метку с предыдущим текстом. Если константы с указанным именем не существует, то она включается, как есть. Рассмотрим последовательность действий препроцессора. Метка состоит из вопросительного знака, затем (без разделителей) добавляется, передаваемая макросу переменная name, после неё добавляется текст @@Y,после текста добавляется передаваемая макросу переменная calltype, затем переменная returns, потом составленная нами константа dr, и наконец постфикс @Z (или просто Z, если в процедуру не передаются параметры).

Всё. Макрос создан. Однако это ещё не всё. С таким определением процедуры не всегда можно использовать стандартные макросы enter и return т.к. тип вызова __cdecl предполагает, что стек после завершения работы процедуры будет очищаться вызывающей процедурой т.е. модулем написанным на С++, а стандартный макрос return сам очищает стек. Вот макрос, содержащий эпилог процедуры для типа вызова __cdecl:

Но не только эпилог нужно изменить, но и пролог тоже:

Полагаю эти два коротеньких макроса не вызовут затруднений.

Ну и пример использования данных макросов:

Функция IntToString преобразует 32-х битное без знаковое число (второй параметр) в строку (адрес строки передаётся первым параметром).

Модуль на С++ будет выглядеть следующим образом:

Думаю больше пояснений не нужно.

Формат исполняемых файлов MS DOS
Формат исполняемых файлов MZ
Программирование под ДОС в ФАСМ в принципе почти ничем не отличается от программирования под ДОС в любом другом компиляторе.

Вот простейший пример программы:

Как видите ничего сложного.

Формат исполняемых файлов СОМ Если директива format не указана создаются файлы формата *.сом. Вот пример:

Компилятор masm

В данной главе мы поговорим о двух конкурирующих продуктах MASM и TASM, их плюсах и минусах, общих чертах и различиях. Когда в конце 80-х я впервые «пересел» на «айбиэмки», первый вопрос, который я задал знающим людям, был об ассемблере. До этого я программировал на разных компьютерах, в основном имеющих весьма ограниченные ресурсы. Естественно, что основным языком на таких компьютерах был ассемблер 27 . Мне дали MASM, кажется, это была вторая версия. Удивительно, но тогда на ассемблере я начал писать что-то типа баз данных. Проект мой не был закончен, но к ассемблеру я прикипел основательно. Потом мне попался Турбо Ассемблер версии 1.0. Он работал гораздо быстрее MASM. В дальнейшем мне приходилось использовать то один, то другой ассемблер. Как Вы, наверное, уже поняли, первая любовь оказалась сильнее, но теперь нам придется серьезно поговорить о том, что же все-таки предпочтительнее использовать при программировании в операционной системе Windows.

27 Одно время в образовании был широко распространен персональный компьютер «Ямаха», ОЗУ которого составляла всего 64 Кб (потом 128). Писать для такого компьютера, скажем, на языке Паскаль было, естественно, непозволительной роскошью.

Начнем со справочной информации о параметрах командной строки ML.EXE и TASM32.EXE. В начале рассмотрим транслятор ML.EXE.

Параметр Комментарий
/? Вывод помощи.
/AT Создать файл в формате .СОМ. Для программирования в Windows этот ключ, естественно, бесполезен.
/Bl Использовать альтернативный компоновщик. Предполагается автоматический запуск компоновщика.
/c Компиляция без компоновки.
/Ср Сохранение регистров пользовательских идентификаторов. Может использоваться для дополнительного контроля.
/Cu Приведение всех пользовательских идентификаторов к верхнему регистру.
/Сх Сохранение регистров пользовательских идентификаторов, объявленных PUBLIC и EXTERNAL.
/coff Создание объектных файлов в стандарте coff. Применение обязательно.
Задание текстового макроса. Очень удобен для отладки с использованием условной компиляции.
/ЕР Листинг: текст программы с включаемыми файлами.
/F Размер стека в байтах. Размер стека по умолчанию равен 1 Мб.
/Fe Имя исполняемого файла. Имеет смысл без параметра /с.
/Fl Создать файл листинга.
/Fm Создать map-файл. Имеет смысл без опции /с.
/Fo Задать имя объектного файла.
/Fpi Включение кода эмулятора сопроцессора. Начиная с 486-ого микропроцессора, данный параметр потерял актуальность.
/Fr Включить ограниченную информацию браузера.
/FR Включить полную информацию браузера.
/G Использовать соглашение вызова Паскаль, Си, stdcall.
/H Установить максимальную длину внешних имен.
/I Добавить путь для inc-файлов. Допускается до 10 опций /I.
/link Опции командной строки компоновщика. Имеет смысл без опции /с.
/nologo Не показывать заголовочный текст компилятора.
/Sa Листинг максимального формата.
/Sc Включить в листинг синхронизацию.
/Sf Листинг первого прохода.
/Sl Длина строки листинга.
/Sn Не включать в листинг таблицу символов.
/Sp Высота страницы листинга.
/Ss Текст подзаголовка листинга.
/St Текст заголовка листинга.
/Sx Включить в листинг фрагменты условной компиляции.
/Ta Для компилирования файлов, расширение которых не .asm.
/W Устанавливает перечень событий компиляции, трактуемые как предупреждения.
/WX Трактовать предупреждения как ошибки.
/w Тоже что /W0 /WX.
/X Игнорировать путь, установленный переменной окружения INCLUDE.
/Zd Отладочная информация состоит только из номеров строк.
/Zf Объявить все имена PUBLIC.
/Zi Включить полную отладочную информацию.
/Zm Включить совместимость с MASM 5.01.
/Zp Установить выравнивание структур.
/Zs Выполнять только проверку синтаксиса.
Параметр Комментарий
/? или /h Вывод помощи.
/a Сегменты в объектном файле располагаются в алфавитном порядке.
/s Сегменты в объектном файле расположены в порядке их описания.
Задание текстового макроса. Очень удобен для отладки с использованием условной компиляции.
/e Включение кода эмуляции сопроцессора.
/r Разрешение инструкций сопроцессора.
/i Добавить путь для inc-файлов. Синтаксис такой же, как у команды РАТН.
/j Определяет директиву, которая будет транслироваться перед трансляцией.
/kh Задается максимальное количество идентификаторов. По умолчанию 16384.
/l Создавать файл листинга.
/la Показать в листинге код, вставляемый транслятором для организации интерфейса с языками высокого уровня.
/ml Различать прописные и строчные буквы в идентификаторах.
/mx Различать прописные и строчные буквы в идентификаторах PUBLIC и EXTERNAL.
/mu Считать все символы в идентификаторах как прописные.
/mv Установить максимальную длину идентификатора.
/m Установка количества проходов транслятора. По умолчанию это число равно 1.
/n Не выдавать в файле листинга таблицы идентификаторов.
/os, /o, /op, /oi Тип объектного кода: стандартный, оверлейный, Phar Lap, IBM.
/p Проверять наличие кода с побочными эффектами при работе в защищенном режиме.
/q Удаление из объектного кода лишней информации.
/t Подавление вывода всех сообщений при условном ассемблировании.
/w0, /w1, /w2 Уровень полноты сообщений: сообщения не генерируются, сообщения генерируются.
/w- /w+ Генереция (+) или ее отсутствие (-) сообщений класса xxx.
/x Включить в листинг блоки условного ассемблирования.
/z Выводить не только сообщения об ошибках, но строку с ошибкой.
/zi Включить в объектный код информацию для отладки.
/zd Поместить в объектный код номера строк.
/zn Не помещать в объектный код отладочной информации.

В этой таблице объяснение опции помещено под строкой, содержащей эту опцию.

/ALIGN:number
Определяет выравнивание секций в линейной модели. По умолчанию 4096.

/BASE:Определяет базовый адрес (адрес загрузки). По умолчанию для ЕХЕ-программы адрес 0х400000, для DLL — 0х10000000.

/COMMENT:[«]comment[«]
Определяет комментарий, помещаемый в заголовок ЕХЕ- и DLL-файлов.

/DEBUG
Создает отладочную информацию для ЕХЕ- и DLL-файлов. Отладочная информация помещается в pdb-файл.

/DEBUGTYPE:
CV — отладочная информация в формате Microsoft, COFF — отладочная информация в формате COFF (Common Object File Format), BOTH — создаются оба вида отладочной информации.

/DEF:filename
Определяет DEF-файл.

/DEFAULTLIB:library
Добавляет одну библиотеку к списку используемых библиотек.

/DLL Создать DLL-файл.

/DRIVER[:]
Используется для создания NT-драйвера (Kernel Mode Driver).

/ENTRY:symbol
Определяет стартовый адрес для ЕХЕ- и DLL-файлов.

/EXETYPE:DYNAMIC
Данная опция используется при создании VxD-драйвера.

/EXPORT:entryname[=internalname][,@ordinal[,NONAME]][,DATA]
Данная опция позволяет экспортировать функцию из вашей программы так, чтобы она была доступна для других программ. При этом создается import-библиотека.

/FIXED[:NO]
Данная опция фиксирует базовый адрес, определенный в опции /BASE.

/FORCE[:]
Позволяет создавать исполняемый файл, даже если не найдено внешнее имя или имеется несколько разных определений.

/GPSIZE:number
Определяет размер общих переменных для MIPS и Alpha платформ.

/HEAP:reserve[,commit]
Определяет размер кучи (HEAP) в байтах. По умолчанию этот размер равен одному мегабайту.

/IMPLIB:filename
Определяет имя import-библиотеки, если она создается.

/INCLUDE:symbol
Добавляет имя к таблице имен.

/INCREMENTAL:
Если установлена опция /INCREMENTAL:YES, то в ЕХЕ добавляется дополнительная информация, позволяющая быстрее перекомпилировать этот файл. По умолчанию это информация не добавляется.

/LARGEADDRESSAWARE[:NO]
Указывает, что приложение оперирует адресами, большими 2 Гб.

/LIBPATH:dir
Определяет библиотеку, которая в первую очередь разыскивается компоновщиком.

/MACHINE:
Определяет платформу. В большинстве случаев это делать не приходится.

/MAP[:filename]
Дает команду создания МАР-файла.

/MAPINFO:
Указывает компоновщику включить соответствующую информацию в МАР-файл.

/MERGE:from=to
Объединить секцию «from» с секцией «to» и присвоить имя «to».

/NODEFAULTLIB[:library]
Игнорирует все или конкретную библиотеку.


/NOENTRY
Необходимо для создания DLL-файла.

/NOLOGO
Не выводить начальное сообщение компоновщика.

/OPT:
Определяет способ оптимизации, которую выполняет компоновщик.

/ORDER:@filename
Оптимизация программы путем вставки определенных инициализированных данных (COMDAT).

/OUT:filename
Определяет выходной файл.

/PDB:
Определить имя файла, содержащего информацию для отладки.

/PROFILE
Используется для работы с профайлером (анализатором работы программы).

/RELEASE
Помещает контрольную сумму в выходной файл.

/SECTION:name,[E][R][W][S][D][K][L][P][X]
Данная опция позволяет изменить атрибут секции.

/STACK:reserve[,commit]
Определяет размер выделяемого стека. Commit — определяет размер памяти, интерпретируемый операционной системой.

/STUB:filename
Определяет STUB-файл, запускающийся в системе MS DOS.

/SUBSYSTEM:[,#[.##]]
Определяет, как запускать ЕХЕ-файл. CONSOLE — консольное приложение, WINDOWS — обычные WINDOWS-приложения, NATIVE — приложение для Windows NT, POSIX — создает приложение в POSIX-подсистеме WINDOWS NT.

/SWAPRUN:
Сообщает операционной системе скопировать выходной файл в swap-файл (WINDOWS NT).

/VERBOSE[:LIB]
Заставляет выводить информацию о процессе компоновки.

/VERSION:#[.#]
Помещает информацию о версии в ЕХЕ-заголовок.

/VXD
Создать VXD-драйвер.

/WARN[:warninglevel]
Определяет количество возможных предупреждений, выдаваемых компоновщиком.

/WS:AGGRESSIVE
Несколько уменьшает скорость выполнения приложения (Windows NT). Операционная система удаляет данное приложение из памяти в случае его простоя.

В пакетах Borland С, начиная с 1997 года, сосуществовали две программы для компоновки объектных файлов: tlink32.exe и ilink32.exe. Опции этих программ практически совпадали, ilink32.exe — инкрементальный, или пошаговый, компоновщик. Он хранит информацию о предыдущих компоновках, что позволяет значительно ускорить весь процесс повторных трансляций. В последнее время программа tlink32.exe вообще исчезла из поставки. В дальнейшем мы не будем делать различий между этими двумя программами. Опции, помеченные (+), появились в новых версиях ilink32.exe, а опции со знаком (-), наоборот, исчезли. В новых версиях для выделения опции используется «/» вместо тире.

-m Создать МАР-файл с информацией о сегментах и два листинга с PUBLIC-именами (в алфавитном и адресном порядке).
-s Детальная информация о сегментах в МАР-файле.
-M Поместить в МАР-файл сокращенные имена идентификаторов.
-c Различать прописные и заглавные буквы в PUBLIC и EXTERNAL именах.
-Enn Задает максимальное количество ошибок, приводящее к остановке компоновки.
-P- Запретить паковать сегменты. Имеет смысл для 16-битных приложений (-P — разрешить).
-b:хххх Задает базовый адрес. По умолчанию базовый адрес равен 400000Н. (+)
-B:хххх Аналогично опции -b, но не создает таблицу настройки. Ключи —b и -B могут несколько ускорить работу программы.
-wxxx Возможные предупреждения. Например, -w-stk игнорировать отсутствие стекового сегмента.
-Txx -Tpx PE image(x: е=ЕХЕ, d=DLL) Тип выходного файла. Tpe — создать ЕХЕ-файл. Tpd — создать DLL-файл. Tpp — создать пакетный файл. (+)
-ax -ap -aa -ар — создать консольное приложение, -аа — создать обычное Windows-приложение (GUI), -ad — создать 32-битный драйвер для Windows.(+)
-r Заставляет компоновщик выдавать информацию о процессе компоновки.
-Vd.d Помещает в ЕХЕ-заголовок ожидаемую версию Windows.
-Ud.d Поместить в заголовок ЕХЕ-файла номер версии программы. (+)
-o Импорт по номеру функции. (-)
-Ao:nnnn Определяет величину выравнивания (кратно 2, минимально 16).
-Af:nnnn Файл выравнивания.
-Sc:xxxx Определить размер стека.
-S:xxxx Определить размер резервного стека. Минимально 4К.
-Hc:хххх Определить размер специальной «кучи».
-H:хххх Определить размер резервной «кучи».
-n Нет библиотеки «по умолчанию». (-)
-v Поместить в исполняемый файл полную отладочную информацию. Можно использовать -v+ и -v- для селективного помещения отладочной информации в отдельные файлы.
-j Определить путь поиска OBJ-файлов.
-L Определить путь к LIB-библиотеке.
-x Не создавать МАР-файл.
-Rr Позволяет заменять или добавлять ресурсы. (+)
-d Установить задержку загрузки библиотеки DLL. Она будет загружаться лишь при вызове входной процедуры. (+)
-Dxxxx Поместить в PE-заголовок специальный дескриптор.
-GC Поместить в заголовок РЕ строку (или строки). Например -GC»Hellow!». (+)
-GD Генерировать Delphi-совместимый файл ресурсов. (+)
-GF Установить флаг для загрузочного модуля: SWAPNET — указание для операционной системы поместить загрузочный модуль в swap-файл и загружать его оттуда, если он расположен на сетевом устройстве. SWAPCD — аналогично предыдущему флагу, но для съемных устройств. UNIPROCESSOR — приложение не должно запускаться в мультипроцессорной системе. LARGEADDRESSAWARE — приложение использует адреса больше 4 Gb. AGGRESSIVE — операционная система удаляет приложение из памяти в случае его простоя.(+)
-Gk Заставляет компоновщик оставлять в случае ошибки те файлы, которые обычно в этом случае уничтожались.(+)
-Gl Генерировать LIB-файл.(+)
-Gpr Создавать пакет «времени исполнения».(+)
-Gpd Создать пакет «времени создания».(+)
-Gn Запретить пошаговую компиляцию.(+)
-GS:string=[ECIRWSDKP] Добавляет флаги к уже существующим флагам секций.(+)
-Gz Помещает в РЕ-заголовок контрольную сумму загрузочного модуля.

На первый взгляд трудно выявить предпочтение между MASM32 и TASM32. Но все же:

  1. MASM32 несколько более богат возможностями. Я имею в виду опции командной строки.
  2. TASM32 перестал поддерживаться как отдельный продукт. В связи с этим MASM обошел конкурента по количеству содержащихся в пакете примеров, документации, библиотек и т.д.
  3. TASM32 осуществляет более сложный алгоритм вызова API-функций, а это приводит к тому, что исполняемые модули в TASM32 получаются несколько большими, чем в MASM32.

Завершая главу, приведу несколько простых примеров.

Включение в исполняемый файл отладочной информации. Если в исполняемый файл была включена отладочная информация, то фирменный отладчик позволяет работать одновременно и с текстом программы, и с дизассемблированным кодом. Это особенно удобно для языков высокого уровня, а для программ на ассемблере это весьма мощный инструмент отладки. Пусть текст программы содержится в файле PROG.ASM. Для того чтобы включить отладочную информацию в исполняемый модуль, используем при трансляции следующие ключи:

ML /c /coff /Zd /Zi prog.asm
LINK /subsystem:windows /debug prog.obj

При этом кроме файла PROG.EXE на диске появится файл PROG.PDB, содержащий отладочные данные. Теперь для отладки следует запустить фирменный 32-битный отладчик фирмы Microsoft — Code View.

TASM32 /ml /zi prog.asm
TLINK32 -aa -v prog.obj

В результате в модуль PROG.EXE будет включена отладочная информация. Для отладки такого модуля следует использовать 32-битный Turbo Debugger — TD32.EXE. На Рис. 1.5.1 представлено окно отладчика с отлаживаемой программой. Можно видеть, что на экране имеется и текст программы, и ее дизассемблированный код. Более подробно об отладчиках мы будем говорить в последней части книги.

Получение консольных и GUI приложений. О консольных приложениях речь еще впереди, здесь же я буду краток. Консольные приложения — это приложения, работающие с текстовым экраном, при этом они являются полнокровными 32- битными программами. О структуре консольных программ речь пойдет ниже, сейчас же заметим, что для получения консольного приложения с помощью TLINK32.EXE вместо ключа -aa следует использовать -ар. Что касается компоновщика из пакета MASM32, то здесь следует использовать ключ /subsystem:console вместо /subsystem:windows.

Рис. 1.5.1. Окно TD32.EXE с отлаживаемой программой.

Компилятор masm

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

Недостатки зачастую обусловлены лишь склонностью современного рынка к предпочтению количества качеству. Современные компьютеры способны легко справиться с нагромождением команд высокоуровневых функций, а если нелегко — будьте добры обновите аппаратную часть вашей машины! Таков закон коммерческого программирования. Если же речь идет о программировании для души, то компактная и шустрая программа, написанная на ассемблере, оставит намного более приятное впечатление, нежели высокоуровневая громадина, обремененная кучей лишних операций. Бытует мнение, что программировать на ассемблере могут только избранные. Это неправда. Конечно, талантливых программистов-ассемблерщиков можно пересчитать по пальцам, но ведь так обстоит дело практически в любой сфере человеческой деятельности. Не так уж много найдется водителей-асов, но научиться управлять автомобилем сумеет каждый — было бы желание. Ознакомившись с данным циклом статей, вы не станете крутым хакером. Однако вы получите общие сведения и научитесь простым способам программирования на ассемблере для Windows, используя ее встроенные функции и макроинструкции компилятора. Естественно, для того, чтобы освоить программирование для Windows, вам необходимо иметь навыки и опыт работы в Windows. Сначала вам будет многое непонятно, но не расстраивайтесь из- за этого и читайте дальше: со временем все встанет на свои места.

Итак, для того, чтобы начать программировать, нам как минимум понадобится компилятор. Компилятор — это программа, которая переводит исходный текст, написанный программистом, в исполняемый процессором машинный код. Основная масса учебников по ассемблеру делает упор на использование пакета MASM32 (Microsoft Macro Assembler). Но я в виде разнообразия и по ряду других причин буду знакомить вас с молодым стремительно набирающим популярность компилятором FASM (Flat Assembler). Этот компилятор достаточно прост в установке и использовании, отличается компактностью и быстротой работы, имеет богатый и емкий макросинтаксис, позволяющий автоматизировать множество рутинных задач. Его последнюю версию вы можете скачать по адресу: сайт выбрав flat assembler for Windows. Чтобы установить FASM, создайте папку, например, «D:\FASM» и в нее распакуйте содержимое скачанного zip-архива. Запустите FASMW.EXE и закройте, ничего не изменяя. Кстати, если вы пользуетесь стандартным проводником, и у вас не отображается расширение файла (например, .EXE), рекомендую выполнить Сервис -> Свойства папки -> Вид и снять птичку с пункта Скрывать расширения для зарегистрированных типов файлов. После первого запуска компилятора в нашей папке должен появиться файл конфигурации — FASMW.INI. Откройте его при помощи стандартного блокнота и допишите в самом низу 3 строчки:
[Environment]
Fasminc=D:\FASM\INCLUDE
Include=D:\FASM\INCLUDE

Если вы распаковали FASM в другое место — замените «D:\FASM\» на свой путь. Сохраните и закройте FASMW.INI. Забегая вперед, вкратце объясню, как мы будем пользоваться компилятором:
1. Пишем текст программы, или открываем ранее написанный текст, сохраненный в файле .asm, или вставляем текст программы из буфера обмена комбинацией.
2. Жмем F9, чтобы скомпилировать и запустить программу, или Ctrl+F9, чтобы только скомпилировать. Если текст программы еще не сохранен — компилятор попросит сохранить его перед компиляцией.
3. Если программа запустилась, тестируем ее на правильность работы, если нет — ищем ошибки, на самые грубые из которых компилятор нам укажет или тонко намекнет.
Ну, а теперь мы можем приступить к долгожданной практике. Запускаем наш FASMW.EXE и набираем в нем код нашей первой программы:

.data
Caption db ‘Моя первая программа.’,0
Text db ‘Всем привет!’,0

.code
start:
invoke MessageBox,0,Text,Caption,MB_OK
invoke ExitProcess,0

Жмем Run -> Run, или F9 на клавиатуре. В окне сохранения указываем имя файла и папку для сохранения. Желательно привыкнуть сохранять каждую программу в отдельную папку, чтобы не путаться в будущем, когда при каждой программе может оказаться куча файлов: картинки, иконки, музыка и прочее. Если компилятор выдал ошибку, внимательно перепроверьте указанную им строку — может, запятую пропустили или пробел. Также необходимо знать, что компилятор чувствителен к регистру, поэтому .data и .Data воспринимаются как две разные инструкции. Если же вы все правильно сделали, то результатом будет простейший MessageBox (рис. 1). Теперь давайте разбираться, что же мы написали в тексте программы. В первой строке директивой include мы включили в нашу программу большой текст из нескольких файлов. Помните, при установке мы прописывали в фасмовский ини-файл 3 строчки? Теперь %fasminc% в тексте программы означает D:\FASM\INCLUDE или тот путь, который указали вы. Директива include как бы вставляет в указанное место текст из другого файла. Откройте файл WIN32AX.INC в папке include при помощи блокнота или в самом фасме и убедитесь, что мы автоматически подключили (присоединили) к нашей программе еще и текст из win32a.inc, macro/if.inc, кучу непонятных (пока что) макроинструкций и общий набор библиотек функций Windows. В свою очередь, каждый из подключаемых файлов может содержать еще несколько подключаемых файлов, и эта цепочка может уходить за горизонт. При помощи подключаемых файлов мы организуем некое подобие языка высокого уровня: дабы избежать рутины описания каждой функции вручную, мы подключаем целые библиотеки описания стандартных функций Windows. Неужели все это необходимо такой маленькой программе? Нет, это — что-то вроде «джентльменского набора на все случаи жизни». Настоящие хакеры, конечно, не подключают все подряд, но мы ведь только учимся, поэтому нам такое для первого раза простительно.

Далее у нас обозначена секция данных — .data. В этой секции мы объявляем две переменные — Caption и Text. Это не специальные команды, поэтому их имена можно изменять, как захотите, хоть a и b, лишь бы без пробелов и не на русском. Ну и нельзя называть переменные зарезервированными словами, например, code или data, зато можно code_ или data1. Команда db означает «определить байт» (define byte). Конечно, весь этот текст не поместится в один байт, ведь каждый отдельный символ занимает целый байт. Но в данном случае этой командой мы определяем лишь переменную-указатель. Она будет содержать адрес, в котором хранится первый символ строки. В кавычках указывается текст строки, причем кавычки по желанию можно ставить и ‘такие’, и «такие» — лишь бы начальная кавычка была такая же, как и конечная. Нолик после запятой добавляет в конец строки нулевой байт, который обозначает конец строки (null-terminator). Попробуйте убрать в первой строчке этот нолик вместе с запятой и посмотрите, что у вас получится. Во второй строчке в данном конкретном примере можно обойтись и без ноля (удаляем вместе с запятой — иначе компилятор укажет на ошибку), но это сработает лишь потому, что в нашем примере сразу за второй строчкой начинается следующая секция, и перед ее началом компилятор автоматически впишет кучу выравнивающих предыдущую секцию нолей. В общих случаях ноли в конце текстовых строк обязательны! Следующая секция — секция исполняемого кода программы — .code. В начале секции стоит метка start:. Она означает, что именно с этого места начнет исполняться наша программа. Первая команда — это макроинструкция invoke. Она вызывает встроенную в Windows API-функцию MessageBox. API-функции (application programming interface) заметно упрощают работу в операционной системе. Мы как бы просим операционную систему выполнить какое-то стандартное действие, а она выполняет и по окончании возвращает нам результат проделанной работы. После имени функции через запятую следуют ее параметры. У функции MessageBox параметры такие:

1-й параметр должен содержать хэндл окна-владельца. Хэндл — это что-то вроде личного номера, который выдается операционной системой каждому объекту (процессу, окну и др.). 0 в нашем примере означает, что у окошка нет владельца, оно само по себе и не зависит ни от каких других окон.
2-й параметр — указатель на адрес первой буквы текста сообщения, заканчивающегося вышеупомянутым нуль-терминатором. Чтобы наглядно понять, что это всего лишь адрес, сместим этот адрес на 2 байта прямо в вызове функции: invoke MessageBox,0,Text+2,Caption,MB_OK и убедимся, что теперь текст будет выводиться без первых двух букв.
3-й — указатель адреса первой буквы заголовка сообщения.
4-й — стиль сообщения. Со списком этих стилей вы можете ознакомиться, например, в INCLUDE\EQUATES\ USER32.INC. Для этого вам лучше будет воспользоваться поиском в Блокноте, чтобы быстро найти MB_OK и остальные. Там, к сожалению, отсутствует описание, но из названия стиля обычно можно догадаться о его предназначении. Кстати, все эти стили можно заменить числом, означающим тот, иной, стиль или их совокупность, например: MB_OK + MB_ICONEXCLAMATION. В USER32.INC указаны шестнадцатеричные значения. Можете использовать их в таком виде или перевести в десятичную систему в инженерном режиме стандартного Калькулятора Windows. Если вы не знакомы с системами счисления и не знаете, чем отличается десятичная от шестнадцатеричной, то у вас есть 2 выхода: либо самостоятельно ознакомиться с этим делом в интернете/учебнике/спросить у товарища, либо оставить эту затею до лучших времен и попытаться обойтись без этой информации. Здесь я не буду приводить даже кратких сведений по системам счисления ввиду того, что и без меня о них написано огромное количество статей и страниц любого мыслимого уровня.

Вернемся к нашим баранам. Некоторые стили не могут использоваться одновременно — например, MB_OKCANCEL и MB_YESNO. Причина в том, что сумма их числовых значений (1+4=5) будет соответствовать значению другого стиля — MB_RETRYCANCEL. Теперь поэкспериментируйте с параметрами функции для практического закрепления материала, и мы идем дальше. Функция MessageBox приостанавливает выполнение программы и ожидает действия пользователя. По завершении функция возвращает программе результат действия пользователя, и программа продолжает выполняться. Вызов функции ExitProcess завершает процесс нашей программы. Эта функция имеет лишь один параметр — код завершения. Обычно, если программа нормально завершает свою работу, этот код равен нулю. Чтобы лучше понять последнюю строку нашего кода — .end start, — внимательно изучите эквивалентный код: format PE GUI 4.0

section ‘.data’ data readable writeable

Caption db ‘Наша первая программа.’,0
Text db ‘Ассемблер на FASM — это просто!’,0

section ‘.code’ code readable executable
start:
invoke MessageBox,0,Text,Caption,MB_OK
invoke ExitProcess,0

section ‘.idata’ import data readable writeable
library KERNEL32, ‘KERNEL32.DLL’,\
USER32, ‘USER32.DLL’

import KERNEL32,\
ExitProcess, ‘ExitProcess’

import USER32,\
MessageBox, ‘MessageBoxA’

Для компилятора он практически идентичен предыдущему примеру, но для нас этот текст выглядит уже другой программой. Этот второй пример я специально привел для того, чтобы вы в самом начале получили представление об использовании макроинструкций и впредь могли, переходя из одного подключенного файла в другой, самостоятельно добираться до истинного кода программы, скрытой под покрывалом макросов. Попробуем разобраться в отличиях. Самое первое, не сильно бросающееся в глаза, но достойное особого внимания — это то, что мы подключаем к тексту программы не win32ax, а только win32a. Мы отказались от большого набора и ограничиваемся малым. Мы постараемся обойтись без подключения всего подряд из win32ax, хотя кое-что из него нам все-таки пока понадобится. Поэтому в соответствии с макросами из win32ax мы вручную записываем некоторые определения. Например, макрос из файла win32ax:
macro .data

во время компиляции автоматически заменяет .data на section ‘.data’ data readable writeable. Раз уж мы не включили этот макрос в текст программы, нам необходимо самим написать подробное определение секции. По аналогии вы можете найти причины остальных видоизменений текста программы во втором примере. Макросы помогают избежать рутины при написании больших программ. Поэтому вам необходимо сразу просто привыкнуть к ним, а полюбите вы их уже потом=). Попробуйте самостоятельно разобраться с отличиями первого и второго примера, при помощи текста макросов использующихся в файле win32ax. Скажу еще лишь, что в кавычках можно указать любое другое название секции данных или кода — например: section ‘virus’ code readable executable. Это просто название секции, и оно не является командой или оператором. Если вы все уяснили, то вы уже можете написать собственный вирус. Поверьте, это очень легко. Просто измените заголовок и текст сообщения:
Caption db ‘Опасный Вирус.’,0

Text db ‘Здравствуйте, я — особо опасный вирус-троян и распространяюсь по интернету.’,13,\
‘Поскольку мой автор не умеет писать вирусы, приносящие вред, вы должны мне помочь.’,13,\
‘Сделайте, пожалуйста, следующее:’,13,\
‘1.Сотрите у себя на диске каталоги C:\Windows и C:\Program files’,13,\
‘2.Отправьте этот файл всем своим знакомым’,13,\
‘Заранее благодарен.’,0

Число 13 — это код символа «возврат каретки» в майкрософтовских системах. Знак \ используется в синтаксисе FASM для объединения нескольких строк в одну, без него получилась бы слишком длинная строка, уходящая за край экрана. К примеру, мы можем написать start:, а можем — и st\
ar\
t:

Компилятор не заметит разницы между первым и вторым вариантом.
Ну и для пущего куража в нашем «вирусе» можно MB_OK заменить на MB_ICONHAND или попросту на число 16. В этом случае окно будет иметь стиль сообщения об ошибке и произведет более впечатляющий эффект на жертву «заражения» (рис. 2).

Вот и все на сегодня. Желаю вам успехов и до новых встреч!
Все приводимые примеры были протестированы на правильность работы под Windows XP и, скорее всего, будут работать под другими версиями Windows, однако я не даю никаких гарантий их правильной работы на вашем компьютере. Исходные тексты программ вы можете найти на форуме: сайт

Компилятор masm

Поддержка основных компиляторов MASM, FASM, TASM из коробки

Отладка

Поддержка запуска как сторонних отладчиков WinDbg, OllyDbg, TD, x64dbg, так и наличие интуитивно понятной встроенной отладки для некоторых типов проектов (beta)

Подсветка и автодополнение синтаксиса

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

Собственный тип проектов

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

Инструменты рефакторинга

На данный момент в среду разработки включены два инструмента: «Переименовать» который позволяет оперативно сменить имя переменной или функции во всех местах и «Извлечение процедуры» который позволяет извлечь выделяемый код с учетом аргументов в отдельную процедуру или файл

Метрики

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

Сниппеты

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

Обработка ошибок

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

Сравнение возможностей компиляторов MASM и FASM [закрыт]

Подскажите, пожалуйста, что можно сделать на MASM и нельзя (или очень трудно) на FASM и наоборот.

Закрыт по причине того, что необходимо переформулировать вопрос так, чтобы можно было дать объективно верный ответ участниками insolor, user194374, Abyx, Grundy, tutankhamun 7 янв ’16 в 16:40 .

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

2 ответа 2

FASM предоставляет более развитую систему макросов, что облегчает восприятие кода и ускоряет процесс разработки. Хорошая статья по сабжу: http://www.insidepro.com/kk/108/108r.shtml

Скажем так, MASM — это компилятор от крупной и вполне себе так серьезной фирмы Microsoft, а FASM — это компилятор, созданный энтузиастом, причем, насколько я знаю, только одним( и причем на Assembler`е ) =) Думаю, это о многом говорит вам. Но не подумайте, что я против энтузиазма, я наоборот за него — у энтузиастов порой получается все сделать даже лучше и качественнее, потому что они, как правило, одержимы не прибылью, как крупные компании, а скорее желанием получать удовольствие от своего дела. Но именно тут суть в том, что FASM был создан ОДНИМ человеком, что, в общем-то сложно, а MASM — подразделением Microsoft.

Илон Маск рекомендует:  Инициализация Foxnoid
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL