Сайт студентов математиков для студентов математиков!
Главная Учебные материалы по математике Введение в язык ассемблера 2015

Введение в язык ассемблера 2015

Введение в язык ассемблера

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

Представление данных

Двоичные числа

Компьютер сохраняет команды и данные в оперативной памяти как последовательность заряженных или разряженных ячеек. Такие ячейки идеально подходят для хранения двоичных чисел, которые используют базовое число 2, и поэтому отдельные биты могут принимать только два состояния — 0 или 1. На рис. 3.1 условно показано соответствие переключателей и двоичных чисел.

Рис. 3.1. Соответствие переключателей и двоичных чисел

Биты, байты, слова, двойные и учетверенные слова

Каждый разряд в двоичном числе называется битом. Восемь битов составляют байт — отдельно адресуемый элемент памяти в большинстве компьютеров. Байт может содержать простую машинную команду, символ или число. Следующим по размеру элементарным понятием является слово. В процессорах Intel слово составляет 16 бит (2 байта).

Размер слова не является жестко определенным. Компьютеры, в которых применяются процессоры Intel, используют 16-, 32- или 64-разрядные операнды, поэтому длина слова определяется в 16, 32 или 64 бит, т. е. существуют слова, двойные слова и учетверенные слова, как показано на рис. 3.2.

Рис. 3.2. Размеры слов

Команды и данные

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

010000010100001001000011

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

Числовые системы

Каждая числовая система имеет основание системы счисления, или базовое число — максимальное значение, которое может быть присвоено отдельной цифре. В табл. 3.2 приведены разрешенные значения для различных систем счисления. Во всех последующих главах при отображении записей в памяти, значений регистров и адресов будут использоваться шестнадцатеричные числа, для которых основанием системы счисления является число 16. Для компактного отображения значений больше 9 используются шестнадцатеричные символы от A до F, соответствующие десятичным значениям от 10 до 15.

Когда записывают двоичное, восьмеричное или шестнадцатеричное число, к нему добавляют определенный символ, представленный строчной буквой. Например, шестнадцатеричное число 45 должно быть записано как 45h, восьмеричное 76 — как 76o, а двоичное 11010011 необходимо записать как 11010011b. Таким образом, ассемблер распознает числовые константы в исходной программе.

Таблица 3.2. Цифры в различных числовых системах

Система

Базовое число

Разрешенные значения

Двоичная

2

0 1

Восьмеричная

8

0 1 2 3 4 5 6 7

Десятичная

10

0 1 2 3 4 5 6 7 8 9

Шестнадцатеричная

16

0 1 2 3 4 5 6 7 8 9 A B C D E F

Правила представления числовых данных

Очень важно знать и понимать правила представления данных в памяти и отображения их на экране. Для примера воспользуемся десятичным числом 65. Сохраненное в памяти как один байт оно будет представлено в двоичном виде как 01000001. Отладочная программа, вероятнее всего, будет его отображать как 41, т. е. как шестнадцатеричное значение. Но если это число послать в память видеоадаптера, то на экране увидим букву А. Это происходит потому, что в соответствии с кодировкой ASCII для символа А выбрано значение 01000001. Таким образом, интерпретация данного значения зависит от определенных условий, которые и придают ему смысл.

Числа со знаком

Двоичные числа могут быть как со знаком, так и без знака. Числа без знака используют все восемь битов для получения значения (например, 11111111 = 255). Просуммировав значения всех битов для преобразования в десятичное число, получим максимально возможное значение, которое может хранить байт без знака (255). Для слова без знака это значение будет составлять 65535. Байт со знаком использует только семь битов для получения значения, а старший восьмой бит зарезервирован для знака, при этом 0 соответствует положительному значению, а 1 — отрицательному. На представленном ниже рис. 3.6 показано отображение положительного и отрицательного числа 10.

Рис. 3.6. Отображение положительного и отрицательного числа 10

Хранение символов

Компьютеры могут хранить только двоичные значения, но нам необходимо работать не только с численными значениями, но и с символами, такими как “A” или “$”. Для этого компьютер использует схему кодирования символов, которая позволяет преобразовывать символы в числа и наоборот. Наиболее известная система кодирования для компьютеров обозначается аббревиатурой ASCII (American Standard Code for Information Interchange). В ASCII каждому символу присваивается уникальный код, включая контрольные символы, используемые при печати и передаче данных между компьютерами. Стандартный ASCII-код использует только 7 разрядов в диапазоне 0-127. Значения от 0 до 31 заняты служебными кодами, используемыми при печати, передаче информации и выводе на экран. В обычном режиме они не отображаются на экране. Остальные значения, допустимые в байте, — дополнительные, их применяют для расширения символьного ряда. В операционной системе MS DOS значения 128-255 используются для получения графических символов и греческих букв. В операционной системе Windows существует множество наборов символов, и в каждом из них дополнительным значениям соответствуют различные символы.

Строка символов представляет в памяти последовательность байт. Например, числовым кодам строки “ABC123” будет соответствовать последовательность значений 41h, 42h, 43h, 31n, 32h и 33h.

Команды языка ассемблера

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

CLC ; мнемокод
INC EAX ; мнемокод с одним операндом
MOV EAX, EBX ; мнемокод с двумя операндами

Любую команду можно сопроводить комментарием, отделяя его от команды точкой с запятой “;”.

Утверждения

В языке ассемблера утверждение состоит из имени, мнемокода, операндов и комментариев. Утверждения бывают двух типов: команды и директивы. Команды — это утверждения, выполняемые в программе, а директивы — утверждения для информирования компилятора о том, как создавать выполняемый код. Общая форма утверждения выглядит так:

[имя] [мнемокод] [операнды] [; комментарии]

Утверждение имеет свободную форму записи. Это означает, что его можно записывать с любой колонки и с произвольным количеством пробелов между операндами. Утверждение должно быть записано на одной строке и не заходить за 128-ю колонку. Можно продолжить запись со следующей строки, но при этом первая строка должна заканчиваться символом “” (обратная косая черта), как показано в примере ниже.

longArrayDefinition WORD l000h, 1020h, 1030h
1040h, 1050h, 1060h, 1070h, 1080h

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

CALL MySub ; Передача управления.
MOV AX,5 ; Передача данных.
ADD AX,20 ; Арифметическая.
JZ next1 ; Логическая ; (переход, если установлен флаг нуля).;
IN AL,20 ; Ввод-вывод (чтение из аппаратного порта).

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

count BYTE 50

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

.STACK 4096

Имена

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

Таблица 3.11. Допустимые для имен символы

Символы

Описание

A… Z, a… z

Буквы

0 … 9

Цифры

?

Знак вопроса

_

Подчеркивание

@

Знак @

$

Знак доллара

Для имен есть следующие ограничения.

n Максимальное количество символов — 247 (в MASM).

n Заглавные и строчные буквы не различаются.

n Первым символом могут быть @, _ или $. Последующими могут быть эти же символы, буквы или цифры. Избегайте использования в начале имени символа “@”, так как многие предопределенные имена начинаются именно с него.

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

Переменные — это данные какой-либо программы, которым присвоены имена.

count1 BYTE 50 ; Переменная (место в памяти).

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

Label1: MOV EAX,0
MOV EBX,0
Label2:
JMP Label1 ; Переход на Label1.

Ключевые слова всегда имеют предопределенный смысл в языке ассемблера. Это могут быть команды или директивы, например MOV, PROC, TITLE, ADD, AX или END. Ключевые слова не могут использоваться программистом для каких-либо других целей, например как имена.

ADD: MOV EAX,10 ; Синтаксическая ошибка!

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

Рис. 3.8. Этапы разработки программ на ассемблере

Пример простой программы

В листинге ниже приведена простая программа, которая отображает на экране традиционное приветствие “Hello, world!”. В этой программе использованы основные особенности приложений на языке ассемблера. В первой строке использована директива Title, остальные символы строки трактуются как комментарий, как и все символы во второй строке.

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

Программа “Hello World”

TITLE Hello World Program (hello. asm)
; Эта программа отображает слова "Hello, world!"
.MODEL flat, stdcall
.STACK 100h
.DATA
message BYTE "Hello, world!",0dh,0ah,0
.CODE
main PROC

MOV EDX, offset message
CALL WriteString

CALL WaitMsg
EXIT
main ENDP
END main

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

n Директива. MODEL flat сообщает ассемблеру, что в данной программе необходимо использовать плоскую модель памяти, т. е. сквозная нумерация всех ячеек памяти. Директива. STACK устанавливает размер пространства для стека емкостью 100h (256 байт). Директива. DATA отмечает начало сегмента данных, где сохраняются переменные. Здесь под именем message сохраняется строка “Hello, world!”, за которой следуют два служебных символа перехода на новую строку (0dh,0ah). Значение 0 используется как символ конца строки для подпрограмм, которые будут считывать эту строку.

n Директива .CODE отмечает начало сегмента кодов, где должны находиться выполняемые команды. Директива PROC объявляет начало процедуры. В этой программе объявлена процедура с именем main.

n В процедуре main на экран выводится строка символов. При этом вызывается функция WriteString, которая непосредственно выводит на экран значения из памяти, начиная с ячейки, адрес который указан в регистре EDX.

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

n Утверждение main ENDP использует директиву ENDP, которая отмечает конец процедуры, причем процедуры не могут “перекрывать” друг друга.

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