Лабораторная №1. Введение. Арифметика. Циклы LOOP [2 балла]
1. Структура программы на языке ассемблера Intel 8088
Структура программы на языке ассемблера выглядит следующим образом:
.SECT .TEXT
! <последовательность инструкций процессора>
.SECT .DATA
! <последовательность команд ассемблера выделения памяти с инициализацией>
.SECT .BSS
! <последовательность команд выделения памяти без инициализации>
Последние две секции могут быть пустыми, но заголовки всё равно должны присутствовать. Первая секция предназначена для написания кода (инструкций процессору), вторые две — для хранения статических данных, это полный аналог глобальных переменных в высокоуровневых языках программирования (например, C).
Пример последовательности инструкций для сложения двух чисел 2 и 3:
MOV AX, 2
ADD AX, 3
Сначала в регистр AX
помещается (командой MOV
) двойка (регистрами называется быстрые именованные элементы памяти на процессоре, куда помещаются операнды). Затем к содержимому AX
добавляется тройка. Смысл инструкций MOV
и ADD
легко запомнить, если применять следующую мнемонику: большинство арифметических инструкций аналогичны присваивающим формам арифметических операций в C (в частном случае, MOV
аналогичен операции присваивания). То есть код выше можно было бы перевести на C следующим образом (считая AX
обычной переменной):
AX = 2
AX += 3
Попробуйте сассемблировать (с помощью as88
) и выполнить по шагам (с помощью t88
) программу, приведённую выше. Для этого нужно создать текстовый файл (назовём его task-0.s
) с объявлением всех трёх секций и с двумя указанными инструкциями в секции кода. А затем выполнить
as88 task-0.s && t88 task-0.s
в терминале (он запускается командой Ctrl+Alt+T). Если ассемблирование (команда as88
) пройдёт без ошибок, то запустится отладка (t88
). В ходе отладки проследите изменение значений регистра AX
. После выполнения обеих инструкций (клавиша Enter
) и проверки значения регистра AX
завершите работу отладчика командой q
.
Замечание по оформлению кода: правило «четырёх столбцов». Код рекомендуется оформлять в 4 столбца: столбец меток, столбец инструкций и команд ассемблера, столбец операндов, комментарии. Это правило используется, даже если столбец пуст (например, первая колонка с метками почти всегда пуста и потому инструкции пишутся с отступом). Исключением являются начала секций — директива .SECT
пишется без отступа. Столбцы разделяются достаточным для визуального восприятия количеством нажатий Tab (1 или 2 в зависимости от настроек редактора), желательно, чтобы визуально это соответствовало 8 пробелам. Между секциями следует оставлять пустые строки.
Помните, что выполнение требований по оформлению и именованию файлов оценивается.
Замечание об эффективной работе с инструментами. На странице с описанием утилит ассемблирования кратко описано, как лучше всего работать с текстами программ и выполнять их запуск. В частности, там предлагается скачать два вспомогательных сценария — рекомендуется сделать это прямо сейчас.
2. Режимы адресации
Под термином режимы адресации в языках ассемблеров понимается способ передачи («адресация») операндов инструкций, перечислим основные из этих способов.
Непосредственная адресация («непосредственные операнды») — операнд передаётся процессору прямо вместе с инструкцией. Как 2 и 3 в примере выше.
Регистровая адресация — операнд находится в указанном регистре процессора (пока мы использовали один регистр —
AX
).Прямая адресация в памяти — операнды располагаются в оперативной памяти (в наших примерах чаще всего — в сегменте данных) по известному на этапе ассемблирования адресу. Такой адрес обычно задаётся с помощью символического имени: «метки». Ниже приводится соответствующий пример.
.SECT .TEXT MOV AX, (x) ADD AX, (y) .SECT .DATA x: .WORD 2 y: .WORD 3 .SECT .BSS
В данном примере суммируемые значения размещены в сегменте данных по меткам x
и y
. Метка отделяется от команды двоеточием. Метка очень похожа на константный указатель C++: она представляет адрес нужного значения (в примере x
это адрес значения 2, y
это адрес значения 3. Для обращения по этому адресу используется запись (x)
, (y)
и т. п. (аналогично операции разыменования указателя *
в C). Часто, для краткости, метки наподобие x
и y
называют «переменными», хотя это не полностью отражает их смысл.
[
task-1.s
, вычитание] [0.25 балла] Создайте программу, которая вычитает из числа 3 число 2 по аналогии с примером выше (числа размещены в сегменте данных). Инструкция вычитанияSUB
получает операнды полностью аналогичноADD
.[
task-2.s
, запись результата] [0.25 балла] Добавьте в предыдущую программу (используйте команду текстового редактора «Сохранить как…» из меню «Файл») к переменнымx
иy
переменнуюres
. Поскольку уres
нет исходного значения, её разумно поместить в секции неинициализированных данныхBSS
. В этой секции вместо команды.WORD
следует использовать команду.SPACE
, после которой указывается количество байт под переменную. Все целочисленные значения имеют размер 2 байта.После получения результата в регистре
AX
скопируйте содержимоеAX
вres
с помощью инструкцииMOV
. Чтобы следить в отладчике за изменением памяти по меткеres
, можно использовать команду:/res
(её нужно ввести после запуска
t88
).
3. Простейшие арифметические команды
[
task-3.s
, умножение] [0,5 балла] Инструкция умноженияMUL
имеет отличный отADD
иSUB
интерфейс. Она имеет один (явный) операнд, он играет роль одного из сомножителей. Второй сомножитель берётся этой инструкцией (неявно) из регистраAX
. Результат умножения может не поместиться в один регистр, потому для него используется пара регистров: младшие 16 бит результата помещаются в регистрAX
, старшие — вDX
. На занятиях мы будем работать с маленькими числами, потому регистрDX
нас волновать не будет, однако такое устройство инструкцииMUL
может приводить к проблемам, если в регистреDX
хранится некая полезная информация — после выполненияMUL
она непременно затрётся (если результат умножения положителен и поместился в 16 бит, то вDX
попадут нули). А потому программист должен сохранить полезную информацию изDX
в каком-либо месте перед использованиемMUL
— в переменной или в другом регистре.Следуя данному описанию составьте программу, которая перемножает два числа, 2 и 3, заданные в сегменте данных, и помещает результат в переменную
res
, объявленную в секцииBSS
.[
task-4.s
, целочисленное деление] [0,5 балла] Инструкция целочисленного деленияDIV
имеет аналогичныйMUL
интерфейс. Так же используется один явный параметр, играющий роль делителя. Делимое считается 32-битным и берётся из пары регистровDX:AX
(старшие биты в DX). Если делимое помещается в 16 бит, то его нужно загрузить в регистр AX, а в DX забивается 1-битами, если делимое отрицательно и 0-битами в противном случае (такая операция называется знаковым расширением). Для этого непосредственно перед использованием инструкции DIV следует передать инструкцию CWD (Convert Word to Doubleword) без операндов. Вновь следует помнить о том, что хранившаяся вDX
информация будет утеряна.Частное от деления помещается в регистр
AX
, а остаток от целочисленного деления — вDX
.Следуя данному описанию, составьте программу, которая вычисляет сумму:
55 / 10 + 55 % 10
и помещает результат в переменнуюres
, объявленную в секцииBSS
. (Здесь и далее/
означает целочисленное деление, а%
— остаток от целочисленного деления).[
task-5.s
простейший цикл сLOOP
] [0,5 балла] Для организации циклов используются метки в сегменте кода и специальные инструкции перехода по этим меткам. Простейшая из таких инструкций —LOOP
:MOV CX, (n) ! количество итераций цикла загружаем в CX — ! это требование LOOP L1: ADD AX, (x) LOOP L1 ! на каждом шаге уменьшает CX на 1, и если CX не равен 0, ! то переходит по метке L1
этот код
(n)
раз прибавляет кAX
значение(x)
(предполагается, чтоn
иx
это метки из сегмента данных, указывающие на некоторые числа).Следуя данному описанию составьте программу, которая считает сумму
5 + 10 + 15 + 20 + 25
. Очередное слагаемое удобно хранить в отдельном регистре (например,BX
) и увеличивать его на 5 на каждом шаге цикла.
4. Дополнительное задание [1 балл]
[
task-6.s
] Вычислите значение многочлена 2x6 - 3x4 - 5x2+ x - 5 в точке x = 3 (задано в сегменте данных). Указание: четвёртая степень x должна вычисляться с помощью возведения в квадрат второй.