Ассемблер и язык ассемблера
Выбери формат для чтения
Загружаем конспект в формате docx
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
§ 1.1. Ассемблер и язык ассемблера
«Сердцем» компьютера является процессор. С точки зрения программирования процессор – это «маленькая коробка», которой можно посылать команды и ждать результат.
Для того чтобы процессор мог исполнить программу, исходный текст программы должен преобразовываться (кем? – системой программирования) в исполняемый модуль – именно он будет загружаться в память на выполнение.
Исполняемый модуль (исполняемая программа) – это последовательность кодов команд процессора и кодов данных, то есть последовательность чисел (в двоичном коде) – машинный код.
Пример: exe-файлы.
Язык ассемблера – это ЯП, позволяющий писать исходную программу непосредственно на уровне команд процессора – это ЯП низкого уровня.
Инструкции (команды) языка ассемблера соответствуют командам процессора: каждой команде процессора сопоставлена англоязычная аббревиатура (инструкция) языка ассемблера.
Существуют различные модели процессоров со своим
собственным набором команд. Þ Существуют разновидности
(диалекты) языка ассемблера.
Не важно, язык какого именно ассемблера изучать. Главное понять принцип работы на уровне команд процессора. Тогда не составит труда освоить не только другой язык ассемблера, но и любой другой процессор со своим набором команд.
В лекциях рассматривается система команд процессоров Intel.
Трансляция (ассемблирование) – преобразование записанных на языке ассемблера инструкций и данных в машинные коды.
Ассемблер – программа-транслятор – программа, которая выполняет трансляцию в машинные коды.
Дизассемблер – программа, выполняющая обратную задачу: переводит машинные коды в ассемблерные инструкции.
int i = 5; mov dword ptr [i], 5 c7 45 f8 05
int j = 2; mov dword ptr [j], 2 c7 45 ec 02
j = j + i; mov eax, dword ptr [j] 8b 45 ec
add eax, dword ptr [i] 03 45 f8
mov dword ptr [j], eax 89 45 ec
Отличие ассемблеров от компиляторов
Трансляторы с языка ассемблера и компиляторы с ЯПВУ выполняют одну задачу: получить машинный код из исходного текста программы. Принципиальное отличие:
• Транслятор преобразует одну инструкцию языка ассемблера в одну машинную команду – преобразование «один к одному».
• Компилятор с ЯПВУ преобразует один оператор языка в последовательность из нескольких машинных команд – преобразование «один к нескольким». Чем «выше» уровень ЯП, тем к большему числу команд процессора будут преобразовываться операторные конструкции языка.
Кроме инструкций для процессора, в языке ассемблера есть служебные указания для транслятора – директивы. Их используют в исходном тексте программы для описания её структуры, форматов данных, управления трансляцией и т.д.
Место языка ассемблера в программировании
1. Там, где нужен прямой программный доступ к памяти и аппаратуре.
2. Для создания минимальной по размеру и/или минимальной по времени исполнения программы. С помощью ассемблерных вставок часто оптимизируют отдельные фрагменты программ на ЯПВУ. На практических занятиях программы на языке ассемблера необходимо будет реализовать как ассемблерные вставки на языке C/C++.
3. Это инструмент анализа исполняемых программ при отсутствии их исходных текстов.
4. Это уникальный учебный инструмент для понимания механизмов взаимодействия процессора с памятью и устройствами вычислительной системы.
#include
int main(){
int i = 5;
int j = 2;
//j = j + i;
_asm{
mov eax,i
add eax,j
mov i,eax
}
printf("%i", i);
return 0;
}
Часто вместо «язык ассемблера» говорят «ассемблер».
§ 1.2. Представление данных
Двоичная система счисления
Чтобы отличать двоичные числа от десятичных, в ассемблерных программах в конце каждого двоичного числа ставят букву «b».
10010110b = 1×27+0×26+0×25+1×24+0×23+1×22+1×21+0×20 = 150
Остаток
Разряд
150/2 = 75
75/2 = 37
37/2 = 18
18/2 = 9
9/2 = 4
4/2 = 2
2/2 = 1
1/2 = 0
1
1
1
1
1
2
3
4
5
6
7
Результат: 10010110b
1
1
1
1
7
6
5
4
3
2
1
байт
1
1
1
1
15
8
7
слово
старший байт
младший байт
aдрес (x+1)
aдрес (x)
Шестнадцатеричная система счисления
В ассемблерных программах при записи чисел, начинающихся с А, В, С, D, E, F, в начале приписывается цифра 0, чтобы нельзя было спутать такое число с названием переменной или другим идентификатором. После шестнадцатеричных чисел ставится буква «h».
1
2
3
4
5
6
7
0000b
0001b
0010b
0011b
0100b
0101b
0110b
0111b
0h
1h
2h
3h
4h
5h
6h
7h
8
9
10
11
12
13
14
15
1000b
1001b
1010b
1011b
1100b
1101b
1110b
1111b
8h
9h
0Ah
0Bh
0Ch
0Dh
0Eh
0Fh
Перевод в двоичную систему и обратно: вместо каждой шестнадцатеричной цифры подставляют соответствующее четырехзначное двоичное число:
10010110b = 96h
0ADh = 10101101b
16 = 10000b = 10h
Целые числа со знаком
Для представления отрицательных чисел существует специальная операция, известная как дополнение до двух. Для изменения знака числа выполняют инверсию, то есть заменяют в двоичном представлении числа все единицы нулями и нули единицами, а затем прибавляют 1.
150 = 0000000010010110b = 0096h
инверсия дает: 1111111101101001b
+1 = 1111111101101010b = 0FF6Ah
Проверим, что полученное число на самом деле -150: сумма с +150 должна, быть равна нулю:
+150 + (-150) = 0096h + 0FF6Ah =
= 10000h = 10000000000000000b
Единица в l6-м разряде не помещается в слово, и значит, мы действительно получили 0.
§ 1.3. Организация памяти
Структура вычислительной системы:
• Байты памяти,
• регистры и
• порты контроллеров –
«источники» данных для системы команд процессора.
Память – последовательность байт – единое адресное пространство. Каждый байт имеет уникальный (физический) адрес: 0, 1, 2, 3, и т.д. Байт – минимальная «единица» чтения или записи в память.
Регистры – специальные ячейки высокоскоростной памяти, расположенные физически внутри процессора. Доступ к регистрам осуществляется не по адресам, а по именам. Разрядность регистров: 8, 16, 32, 64 бит.
Порты – это регистры внутри контроллеров. Разрядность: 8 бит.
С памятью процессор связан несколькими шинами:
• На шину адреса процессор «выставляет» адрес памяти.
• По шине данных затем считывает данные из памяти в регистр или записывает из регистра в память.
От разрядности шины адреса зависит объем памяти, которую может адресовать процессор:
2N байтов, где N – разрядность адресной шины.
Для процессоров с 32-разрядной адресной шиной объем физически адресуемой памяти составляет 232 байтов (4 Гб):
от 0 до 232 – 1 .
Чтение из памяти слова, начиная с адреса 120:
Основные принципы архитектуры фон-Неймана:
1. Линейное пространство памяти: оперативная память представляет собой совокупность ячеек с последовательной нумерацией (физическими адресами) 0, 1, 2, ...
2. Принцип хранимой программы: код программы и ее данные находятся в одном и том же адресном пространстве памяти.
3. Отсутствие разницы между данными и командами в памяти: с точки зрения процессора и данные, и команды – это последовательности 0 и 1.
4. Последовательное выполнение программы.
Процессор после включения питания оказывается в режиме реальной адресации памяти, или просто реальном режиме.
Операционная система переводит его в защищенный режим, позволяющий обеспечивать многозадачность, распределение памяти и др.
Пользовательские программы часто работают в виртуальном режиме, из которого им доступно все то же, что и из реального, кроме инструкций, относящихся к управлению защищенным режимом...
Измерение объемов памяти в более крупных единицах:
1 Килобайт = 1024 байтов (210 байтов),
1 Мегабайт = 1024 Кб (220 байтов);
1 Гигабайт = 1024 Мб (230 байтов);
1 Терабайт = 1024 Гб (240 байтов).
§ 1.4. Регистры
Группа регистров
Назначение
1. Основные
1.1. Общего назначения (8):
eax, ebx, ecx, edx,
esi, edi, ebp, esp
Хранение данных и адресов
1.2. Сегментные (6):
cs, ds, es, fs, gs, ss
Хранение адресов сегментов памяти
1.3. Состояния и управления (2):
eip, eflags
2. Сопроцессора:
sf(0), sf(1), …, sf(7)
Работа с числами с плавающей точкой
3. MMX-расширения
4. Системные
Регистры общего назначения
eax/ax/ah/al
ebx/bx/bh/bl
ecx/cx/ch/cl
edx/dx/dh/dl
esi/si
edi/di
ebp/bp
esp/sp
Младшие 16 бит каждого из 32-разрядных (32-битных) регистров общего назначения могут использоваться как самостоятельные регистры и имеют имена. Отдельные байты в первых четырех 16-битных регистрах тоже имеют свои имена и могут использоваться как 8-битные регистры.
-x
e-
-h
-l
31
16
15
8
7
Буквы в названии регистра и их обозначения
e-
Extended – 32 бита
-h
High – старшие 8 бит
-x
eXtended – 16 бит
-l
Low – младшие 8 бит
eax, ebx, ecx, edx могут использоваться для хранения данных и адресов. Названия этих регистров происходят от того, что некоторые инструкции применяют их специальным образом:
• либо использование какого-то регистра обязательно,
• либо происходит неявно.
Аккумулятор (Accumulator register)
ax
eax
ah
al
31
16
15
8
7
Для хранения промежуточных данных. В некоторых инструкциях используется обязательно.
База (Base register)
bx
ebx
bh
bl
31
16
15
8
7
Для хранения базового адреса некоторого объекта в памяти.
Счетчик (Count register)
cx
ecx
ch
cl
31
16
15
8
7
В инструкциях, производящих повторяющиеся действия.
Регистр данных (Data register)
dx
edx
dh
dl
31
16
15
8
7
Применяется для хранения промежуточных данных.
Следующие 4 регистра имеют более конкретное назначение и могут применяться для хранения всевозможных временных данных, только когда они не используются по назначению.
Индекс источника (Sourse Index register)
esi
si
31
16
15
Индекс приемника (Destination Index register)
edi
di
31
16
15
esi и edi используются в паре в строковых (цепочечных) инструкциях: esi содержит текущий адрес элемента в цепочке-«источнике», edi содержит текущий адрес элемента в цепочке-«приемнике».
Указатель базы (Base Pointer register)
ebp
bp
31
16
15
Указатель стека (Stack Pointer register)
esp
sp
31
16
15
ebp и esp используются при работе со стеком: esp содержит адрес вершины стека.
Сегментные регистры
Сегментированная модель памяти: программы работают с памятью как с несколькими непрерывными последовательностями байт – сегментами. Для задания физического адреса байта требуется два числа:
• адрес начала сегмента – база (селектор),
• адрес байта внутри сегмента – смещение (эффективный адрес).
физический адрес = база : смещение
Процессор аппаратно поддерживает организацию программы в виде 3-х частей: сегмент стека, сегмент кода и сегмент данных.
1) сегмент стека – содержит временные данные (параметры вызываемых подпрограмм, локальные переменные, адреса возврата);
2) сегмент кода – содержит команды исполняемой программы;
3) сегмент данных – содержит обрабатываемые программой данные.
Сегментные регистры содержат адреса памяти, с которых начинаются соответствующие сегменты.
Регистр сегмента стека (Stack Segment register)
ss
15
Регистр сегмента кода (Code Segment register)
cs
15
Регистры сегмента данных (Data Segment register)
ds
es
fs
gs
15
Регистр eip и адресное пространство памяти
Адрес «следующей выполняемой команды» всегда хранится в специальном регистре eip (относится к регистрам состояния и управления). Программисту не доступен.
Указатель команды Instruction Pointer register
eip
ip
31
16
15
После чтения команды из памяти значение в eip аппаратно увеличивается на длину прочитанной команды в байтах.
Если быть точнее, в eip хранится не физический адрес, а смещение следующей выполняемой команды. В регистре esp также хранится смещение, а не физический адрес.
сs:eip – физический адрес следующей выполняемой команды
ss:esp – физический адрес вершины стека
При записи в стек значение смещения уменьшается, то есть стек растет вниз от максимально возможного адреса.
При вызове подпрограммы параметры в большинстве случаев помещают в стек, а в ebp записывают текущее значение esp. Тогда, если подпрограмма использует стек для хранения локальных переменных, esp изменится, но ebp можно будет использовать для того, чтобы считывать значения параметров напрямую из стека (их смещения будут записываться как ebp + номер параметра).
Регистр флагов
Используется при выполнении большинства инструкций (относится к регистрам состояния и управления).
Регистр флагов (Flag register)
eflags
flags
31
16
15
В этом регистре каждый бит является флагом: устанавливается
в 1 при определенных условиях или установка его в 1 изменяет поведение процессора.
Младшее слово регистра флагов (регистр flags)
nt
iopl
of
df
if
tf
sf
zf
af
pf
1
cf
15
14
13
12
11
10
09
08
07
06
05
04
03
02
01
00
cf – флаг переноса. Устанавливается в 1, если результат выполненной арифметической операции не уместился в «приемнике» и произошел перенос из старшего бита или если требуется заем (при вычитании), иначе устанавливается в 0.
При работе с числами без знака равенство 1 сигнализирует об ошибке.
zf – флаг нуля. Устанавливается в 1, если результат выполненной инструкции равен нулю.
sf – флаг знака. Этот флаг всегда равен старшему биту результата.
of – флаг переполнения. Этот флаг устанавливается в 1, если результат выполненной арифметической операции над числами со знаком выходит за допустимые для них пределы.
При работе с числами со знаком равенство 1 сигнализирует об ошибке.
Перенос из
разрядной сетки
Перенос в
знаковый бит
Флаги
+
+
cf = 1, of = 0
+
-
cf = 1, of = 1
-
+
cf = 0, of = 1
-
-
cf = 0, of = 0
Пусть складываются два числа со знаком
110 = 012 +
-110 = 112 (в дополнительном коде)
0 = 1002 (+ +) cf = 1, of = 0 ошибки нет
110 = 012 +
110 = 012
2 ? 112 = -110 (- +) cf = 0, of = 1 ошибка
30566 = 0111011101100110b +
30566 = 0111011101100110b
61132 = 1110111011001100b (- +) cf = 0, of = 1
Произошел перенос из 14-го разряда, из 15-го разряда переноса нет. Ошибка, т.к. переполнение: 16-битное число со знаком максимум +32767.
-30566 = 1000100010011010b +
-04875 = 1110110011110101b
-35441 = 10111010110001111b (+ -) cf = 1, of = 1
Произошел перенос из 15-го разряда. Ошибка, так как старший бит 0 и ответ получился положительным.
-4875 = 1110110011110101b +
-4875 = 1110110011110101b
-9750 = 11101100111101010b (+ +) cf = 1, of = 0
Есть переносы из 14-го и 15-го разрядов. Ошибки нет.
pf – флаг четности (паритета). Устанавливается в 1, если младший байт результата выполненной команды содержит четное число бит, равных 1; устанавливается в 0, если число единичных бит нечетное.
af – флаг полупереноса или вспомогательного переноса. Устанавливается в 1, если в результате выполнения команды произошел перенос (или заем) из третьего бита в четвертый. Этот флаг используется автоматически командами двоично-десятичной коррекции.
tf – флаг трассировки. Этот флаг был предусмотрен для работы отладчиков, не использующих защищенный режим. Установка его в 1 приводит к тому, что после выполнения каждой команды программы управление временно передается отладчику (вызывается прерывание № 1).
if – флаг прерываний. Установка этого флага в 1 приводит к тому, что процессор перестает обрабатывать прерывания от внешних устройств. Обычно его устанавливают на короткое время для выполнения критических участков кода.
df – флаг направления. Этот флаг контролирует поведение команд обработки строк – когда он установлен в 1, строки обрабатываются в сторону уменьшения адресов, а когда = 0 – наоборот.
Флаги iopl (уровень привилегий ввода-вывода) и nt (вложенная задача) применяются в защищенном режиме.
Все флаги, расположенные в старшем слове регистра, имеют отношение к управлению защищённым режимом.