В статье будут рассмотрены основы языка ассемблер применительно к архитектуре win32. Он представляет собой символическую запись машинных кодов. В любой электронно-вычислительной машине самым низким уровнем является аппаратный. Здесь управление процессами происходит командами или инструкциями на машинном языке. Именно в этой области ассемблеру предназначено работать.
Программирование на ассемблер
Написание программы на ассемблере - крайне трудный и затратный процесс. Чтобы создать эффективный алгоритм, необходимо глубокое понимание работы ЭВМ, знание деталей команд, а также повышенное внимание и аккуратность. Эффективность - это критический параметр для программирования на ассемблер.
Главное преимущество языка ассемблер в том, что он позволяет создавать краткие и быстрые программы. Поэтому используется, как правило, для решения узкоспециализированных задач. Необходим код, работающий эффективно с аппаратными компонентами, или нужна программа, требовательная к памяти или времени выполнения.
Регистры
Регистрами в языке ассемблер называют ячейки памяти, расположенные непосредственно на кристалле с АЛУ (процессор). Особенностью этого типа памяти является скорость обращения к ней, которая значительно быстрее оперативной памяти ЭВМ. Она также называется сверхбыстрой оперативной памятью (СОЗУ или SRAM).
Существуют следующие виды регистров:
- Регистры общего назначения (РОН).
- Флаги.
- Указатель команд.
- Регистры сегментов.
Есть 8 регистров общего назначения, каждый размером в 32 бита.
Доступ к регистрам EAX, ECX, EDX, EBX может осуществляться в 32-битовом режиме, 16-битовом - AX, BX, CX, DX, а также 8-битовом - AH и AL, BH и BL и т. д.
Буква "E" в названиях регистров означает Extended (расширенный). Сами имена же связаны с их названиями на английском:
- Accumulator register (AX) - для арифметических операций.
- Counter register (CX) - для сдвигов и циклов.
- Data register (DX) - для арифметических операций и операций ввода/вывода.
- Base register (BX) - для указателя на данные.
- Stack Pointer register (SP) - для указателя вершины стека.
- Stack Base Pointer register (BP) - для индикатора основания стека.
- Source Index register (SI) - для указателя отправителя (источника).
- Destination Index register (DI) - для получателя.
Специализация РОН языка ассемблер является условной. Их можно использовать в любых операциях. Однако некоторые команды способны применять только определенные регистры. Например, команды цикла используют ESX для хранения значения счетчика.
Регистр флагов. Под этим подразумевается байт, который может принимать значения 0 и 1. Совокупность всех флагов (их порядка 30) показывают состояние процессора. Примеры флагов: Carry Flag (CF) - Флаг переноса, Overflow Flag (OF) - переполнения, Nested Flag (NT) - флаг вложенности задач и многие другие. Флаги делятся на 3 группы: состояние, управление и системные.
Указатель команд (EIP - Instruction Pointer). Данный регистр содержит адрес инструкции, которая должна быть выполнена следующей, если нет иных условий.
Регистры сегментов (CS, DS, SS, ES, FS, GS). Их наличие в ассемблере продиктовано особым управлением оперативной памятью, чтобы увеличить ее использование в программах. Благодаря им можно было управлять памятью размером до 4 Гб. В архитектуре Win32 необходимость в сегментах отпала, но названия регистров сохранились и используются по-другому.
Стек
Это область памяти, выделенная для работы процедур. Особенность стека заключается в том, что последние данные, записанные в него, доступны для чтения первыми. Или иными словами: первые записи стека извлекаются последними. Представить этот процесс себе можно в качестве башни из шашек. Чтобы достать шашку (нижнюю шашку в основание башни или любую в середине) нужно сначала снять все, которые лежат сверху. И, соответственно, последняя положенная на башню шашка, при разборе башни снимается первой. Такой принцип организации памяти и работы с ней продиктован ее экономией. Стек постоянно очищается и в каждый момент времени одна процедура использует его.
Идентификаторы, целые числа, символы, комментарии, эквивалентность
Идентификатор в языке программирования ассемблер имеет такой же смысл, как и в любом другом. Допускается использование латинских букв, цифр и символов "_", ".", "?", "@", "$". При этом прописные и строчные буквы эквивалентны, а точка может быть только первым символом идентификатора.
Целые числа в ассемблере можно указывать в системах отсчета с основаниями 2, 8, 10 и 16. Любая другая запись чисел будет рассматриваться компилятором ассемблера в качестве идентификатора.
В записи символьных данных допускается использовать как апострофы, так и кавычки. Если в символьной строке требуется указать один из них, то правила следующие:
- в строке, заключенной в апострофы, кавычки указываются один раз, апостроф - дважды: 'can''t', ' he said "to be or not to be" ';
- для строки, заключенной в кавычки, правило обратное: дублируются кавычки, апострофы указываются как есть: "couldn't", " My favourite bar is ""Black Cat"" ".
Для указания комментирования в языке ассемблер используется символ точка с запятой - ";". Допустимо использовать комментарии как в начале строк, так и после команды. Заканчивается комментарий переводом строки.
Директива эквивалентности используется схожим образом тому, как в других языках указывают константные выражения. Эквивалентность указывается следующим способом:
EQU
Таким образом в программе все вхождения
Директивы данных
Языки высокого уровня (C++, Pascal) являются типизированными. То есть, в них используются данные, имеющие определенный тип, имеются функции их обработки и т. д. В языке программирования ассемблер подобного нет. Существует всего 5 директив для определения данных:
- DB - Byte: выделить 1 байт под переменную.
- DW - Word: выделить 2 байта.
- DD - Double word: выделить 4 байта.
- DQ - Quad word: выделить 8 байтов.
- DT - Ten bytes: выделить 10 байтов под переменную.
Буква D означает Define.
Любая директива может быть использована для объявления любых данных и массивов. Однако для строк рекомендуется использовать DB.
Синтаксис:
DQ [, ]
В качестве операнда допустимо использовать числа, символы и знак вопрос - "?", обозначающий переменную без инициализации. Рассмотрим примеры:
real1 DD 12.34 char db 'c' ar2 db '123456',0 ; массив из 7 байт num1 db 11001001b ; двоичное число num2 dw 7777o ; восьмеричное число num3 dd -890d ; десятичное число num4 dd 0beah ; шестнадцатеричное число var1 dd ? ; переменная без начального значения ar3 dd 50 dup (0) ; массив из 50 инициализированных эл-тов ar4 dq 5 dup (0, 1, 1.25) ; массив из 15 эл-тов, инициализированный повторами 0, 1 и 1.25
Команды (инструкции)
Синтаксис команд ассемблера или инструкций ассемблера выглядит следующим образом:
Метка (label:) обязательно завершается двоеточием и может располагаться в отдельной строке. Метки используются для того, чтобы ссылаться на команды внутри программы.
Инструкции указывают операцию, которая должна быть выполнена. В ассемблере операции представлены в виде буквенных сокращений для облегчения понимания. Инструкции также могут называться мнемокодами.
В роли операндов команды могут выступать:
- регистры, обращение к которым происходит по их именам;
- константы;
- адреса.
Подробнее об адресах
Адрес может передаваться несколькими способами:
- В виде имени переменной, которая в ассемблере является синонимом адреса.
- Если переменная является массивом, то обращение к элементу массива происходит через имя его переменной и смещения. Для этого существует 2 формы: [<имя> + <смещение>] и <имя>[<смещение>]. Следует учитывать, что смещение - это не индекс в массиве, а размер в байтах. Программисту самому необходимо понимать, на сколько нужно сделать смещение в байтах, чтобы получить нужный элемент массива.
- Можно использовать регистры. Для обращения к памяти, в которой хранится регистр, нужно использовать квадратные скобки: [ebx], [edi].
- [] - квадратные скобки допускают применение сложных выражений внутри себя для вычисления адреса: [esi + 2*eax].
В ассемблере адрес передается через квадратные скобки. Ввиду того, что переменная является также адресом, она может использоваться как с квадратными скобками, так и без.
Помимо этого, в ассемблер существуют сокращения: r - для регистров, m - для памяти и i - для операнда. Эти сокращения используются с числами 8, 16 и 32 для указания размера операнда: r8, m16, i32 и т. д.
add i8/i16/i32, m8/m16/m32 ;суммирование операнда с ячейкой памяти
Команда mov или пересылка
Данная инструкция является основной среди команд ассемблера. Она позволяет записывать в регистр значение другого регистра, ячейки памяти или константы. Она же осуществляет запись в ячейку памяти значения регистра или константы. Синтаксис команды:
MOV,
В процессоре существует и другие команды для реализации пересылки. Например, XCHG - команда обмена операндов значениями. Но с точки зрения программиста, все они реализованы через команду базовую MOV. Рассмотрим примеры:
MOV i, 0 ; Записать в i значение 0 MOV ECX, EBX ; Пересылка значения EBX в ECX
В виде операнда может выступать как регистр, так и ячейка памяти. Однако если содержимое двух регистров можно переставить, то двух ячеек памяти - нет. Следует внимательно следить за тем, чтобы операнды имели одинаковый размер. Также заметим, что команда MOV не изменяет значения флагов.
Инструментарий
Дальнейшее теоретическое изучение ассемблера может быть трудным, поэтому стоит задуматься об инструментах, используемых для разработки программ с его помощью. Здесь будет приведен лишь краткий список популярных средств:
- Borland Turbo Assembler (TASM) - один из самых популярных инструментов. Хорошо подходит для разработки под DOS и плохо - под Windows.
- Microsoft Macro Assembler (MASM) - это пакет для разработки на ассемблере в среде Windows. Существует как отдельно, так и в виде встроенной функции в среде Visual Studio. Ассемблер и языки высокого уровня часто совместимы. В том смысле, что последние могут использовать ассемблер напрямую. Например, С++.
- Netwide Assembler (NASM) - популярный свободный ассемблер для архитектуры Intel.
Существует множество инструментов. При этом следует сделать особую пометку о том, что нет единого стандарта синтаксиса ассемблера. Есть 2 наиболее применимых: AT&T-синтаксис, ориентированный на процессоры производства не Intel, и, соответственно, Intel-синтаксис.
Несмотря на кажущуюся сложность, ассемблер является простым языком программирования, понять который не составляет труда. Поэтому можно смело использовать учебную литературу на подобии "ассемблер для чайников" и изучать этот замечательный язык.
А ЧТО ВЫ ДУМАЕТЕ ОБ ЭТОМ?