Лабораторная №1. Введение. Арифметика. Циклы LOOP.[4 балла]
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.5 балла] Создайте программу, которая вычитает из числа 3 число 2 по аналогии с примером выше (числа размещены в сегменте данных). Инструкция вычитанияSUBполучает операнды полностью аналогичноADD. -
[
task-2.s, запись результата] [0.5 балла] Добавьте в предыдущую программу (используйте команду текстового редактора «Сохранить как…» из меню «Файл») к переменнымxиyпеременнуюres. Поскольку уresнет исходного значения, её разумно поместить в секции неинициализированных данныхBSS. В этой секции вместо команды.WORDследует использовать команду.SPACE, после которой указывается количество байт под переменную. Все целочисленные значения имеют размер 2 байта.После получения результата в регистре
AXскопируйте содержимоеAXвresс помощью инструкцииMOV. Чтобы следить в отладчике за изменением памяти по меткеres, можно использовать команду:/res(её нужно ввести после запуска
t88).
3. Простейшие арифметические команды
-
[
task-3.s, умножение] [1 балл] Инструкция умноженияMULимеет отличный отADDиSUBинтерфейс. Она имеет один (явный) операнд, он играет роль одного из сомножителей. Второй сомножитель берётся этой инструкцией (неявно) из регистраAX. Результат умножения может не поместиться в один регистр, потому для него используется пара регистров: младшие 16 бит результата помещаются в регистрAX, старшие — вDX. На занятиях мы будем работать с маленькими числами, потому регистрDXнас волновать не будет, однако такое устройство инструкцииMULможет приводить к проблемам, если в регистреDXхранится некая полезная информация — после выполненияMULона непременно затрётся (если результат умножения положителен и поместился в 16 бит, то вDXпопадут нули). А потому программист должен сохранить полезную информацию изDXв каком-либо месте перед использованиемMUL— в переменной или в другом регистре.Следуя данному описанию составьте программу, которая перемножает два числа, 2 и 3, заданные в сегменте данных, и помещает результат в переменную
res, объявленную в секцииBSS. -
[
task-4.s, целочисленное деление] [1 балл] Инструкция целочисленного деления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] [1 балл] Для организации циклов используются метки в сегменте кода и специальные инструкции перехода по этим меткам. Простейшая из таких инструкций —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 должна вычисляться с помощью возведения в квадрат второй.