Программирование на языке ассемблера

Лабораторная работа № 2

Программирование на языке ассемблера

1 Цель работы

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

2 Теоретические сведения

Описание языка ассемблера приведено по [2].

2.1. Основные понятия

Машинный код – система команд конкретной вычислительной машины (процессора, микропроцессора МП), которая интерпретируется непосредственно процессором. Команда, как правило, представляет собой двоичный код (целое число), который записывается в специальный регистр процессора (регистр команды). Процессор читает этот код и выполняет операцию, которая соответствует этой команде.

Язык программирования низкого уровня (низкоуровневый язык программирования) – это язык программирования, максимально приближенный к программированию в машинных кодах. В отличие от машинных кодов, в языке низкого уровня каждой команде соответствует не число, а сокращённое название команды (мнемоника). Например, команда ADD – это сокращение от слова ADDITION (сложение). Поэтому использование языка низкого уровня существенно упрощает написание и чтение программ (по сравнению с программированием в машинных кодах). Каждая команда языка низкого уровня соответствует одной машинной команде. Язык низкого уровня привязан к конкретному процессору. Например, если программа на языке низкого уровня написана для процессора Intel 80x86, то она не будет работать с процессорами AVR или PIC.

Язык программирования высокого уровня – это язык программирования, максимально приближенный к человеческому языку (обычно к английскому, но есть языки программирования на национальных языках, например, язык 1С основан на русском языке). Язык высокого уровня практически не привязан ни к конкретному процессору, ни к операционной системе (если не используются специфические директивы).

Язык ассемблера – это низкоуровневый язык программирования, на котором пишутся программы. Для каждого процессора существует свой язык ассемблера.

Ассемблер – это специальная программа, которая преобразует (ассемблирует, то есть собирает) исходные тексты программы, написанной на языке ассемблера, в исполняемый файл (файл с расширением EXE или COM). Точнее, то для создания исполняемого файла требуются дополнительные программы, а не только Ассемблер. В отличие от языков высокого уровня, таких, как Паскаль, Бейсик и т.п., для КАЖДОГО ПРОЦЕССОРА существует СВОЙ АССЕМБЛЕР, а для КАЖДОГО АССЕМБЛЕРА существует СВОЙ ЯЗЫК АССЕМБЛЕРА.

Наиболее известными ассемблерами для операционной системы DOS являлись Borland Turbo Assembler (TASM), Microsoft Macro Assembler (MASM) и Watcom Assembler (WASM). Последняя известная версия TASM — 5.3, поддерживающая создание программ для выполнения в среде Windows. Но эта программа платная, да и официально развитие программы полностью остановлено. Продукт MASM продолжает развиваться и по сей день, последние версии дают возможность написания программ для Windows. Хотя MASM больше не является коммерческим продуктом, Microsoft продолжает поддерживать исходный код, используемый и в других продуктах Microsoft. В настоящее время доступен свободно распространяемый MASM32, загрузить который можно с официального сайта http://www.masm32.com/. Отладчик DEBUG, который также можно использовать для написания и отладки простых программ, обладает скромными возможностями, но имеет большой плюс - входит в стандартный набор Windows.

2.2. Основные компоненты языка ассемблера

Алфавит языка составляют символы ASCII:

  • латинские буквы от A до Z (или от a до z, строчные и прописные буквы в ассемблере не различаются);
  • цифры от 0 до 9;
  • специальные символы @, $, ?, ., _, +, -, *, ‘, “, ;, : и т.д.

Из букв, цифр и символов @, $, ?, ., _ формируются:

  • простые сообщения: имена (идентификаторы) процедур (подпрограмм), переменных, директив, команд (метки), значения констант и переменных;
  • составные сообщения: команды (операторы);
  • директивы (псевдооператоры);
  • модификаторы (операции).

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

Константы - только целые числа. Различают:

  • двоичные числа - заканчиваются буквой В;
  • десятичные числа - без специального окончания или заканчиваются буквой D;
  • шестнадцатеричные числа - заканчиваются буквой Н.

Для обозначения цифр 10, 11, 12, 13, 14, 15 в шестнадцатеричной системе счисления используются, соответственно, буквы A, B, C, D, E, F; но начинаться шестнадцатеричные числа должны обязательно с цифры, например: выражение F19Н — не число, а идентификатор, правильно число надо записать так: 0F19Н.

Запись отрицательных чисел:

  • десятичные числа записываются обычным образом, просто со знаком: -32, -32D;
  • двоичные числа записываются только в дополнительном модифицированном коде: -32D 11100000В, -19В 11101101В (старшие два бита – знак числа);
  • шестнадцатеричные числа записываются только в дополнительном коде: -32D выглядит как Е0Н, -119D как 89Н.

При вводе команд языка ассемблера в окне отладчика DEBUG все числовые значения должны быть представлены в шестнадцатеричном формате и записаны как набор от 1 до 4 символов.

Константы – строки символов (литералы): включают в себя любые буквы, цифры и символы, но заключаются в кавычки: ‘ПЭВМ IВМ РС с микропроцессором Pentium 4’.

Команды (операторы). Формат команды:

[Метка [:]] КОП [Операнд] [, Операнд] [; Комментарий].

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

КОП (Код ОПератора) — мнемокод команды (состоит из 2-6 букв). Может быть до 256 различных кодов (в ассемблере IBM PC их число изменяется в зависимости от типа МП).

Операнд — явно заданный адрес (прямой или косвенный); имя метки, переменной; само значение переменной; ассоциативный признак и т.п. Количество необходимых в команде операндов ассемблер узнает по КОП. В большинстве двухадресных команд присутствуют операнды приемника (dst - destination) и источника (src - source). Источник не изменяет своего содержимого, а в приемнике содержимое, участвующее в операции, заменяется результатом.

Метка — имя команды ассемблера для ссылки (обращения) к этой команде (до 31 символа). Двоеточие, стоящее после метки, означает, что метка всегда находится в текущем сегменте памяти.

Комментарий — любой текст, поясняющий программу (не воспринимается ассемблером, но выводится в листинге).

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

Формат директивы:

[Идентификатор] КПОП [Операнд] [, Операнд]... [; Комментарий]

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

Идентификатор — имя директивы (для обращения к ней).

КПОП (Код ПсевдоОПератора) — мнемокод директивы (состоит из 2-7 букв).

Операнды — их в директиве может быть много; это уже не только адреса, но и различные процедуры. В операндах могут быть записаны простые выражения, использующие модификаторы (операции).

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

  • Арифметические модификаторы: «+» — сложить, «-» — вычесть, «*» — умножить, «/» — разделить, «mod» — остаток от деления и т.д. Применяются к двум операндам opr. Формат: opr mdf opr.
  • Логические модификаторы: and — «И», or — «ИЛИ», not — «НЕ», и xor — «исключающее ИЛИ». Формат: opr mdf opr.
  • Модификаторы отношения: eq — совпадения, nq — несовпадения, lt — меньше, gt — больше, le — меньше или равно, ge — больше или равно. Формат: opr mdf opr.
  • Модификаторы, возвращающие значения:
    • $ — возвращает значение смещения адреса текущего оператора;
    • seg — возвращает адрес сегмента адреса метки или переменной;
    • offset — возвращает смещение адреса метки или переменной;
    • length — возвращает длину операнда в единицах определения (байтах или словах);
    • type — возвращает атрибут типа переменной (1, если byte; 2 — word, 3 — dword) или метки (1 — near, 3 — far);
    • size — возвращает произведение length*type. Формат: mdf opr.
  • Модификаторы присваивания атрибута:
  • ptr — изменяет атрибут типа (byte, word или dword) операнда или атрибут дистанции (near или far) адресного операнда. Формат: тип ptr opr (здесь тип — новый атрибут, opr — идентификатор операнда, чей атрибут должен быть изменен);
  • ds:, es:, cs:, ss: — изменяет атрибут сегмента адреса. Формат: rsegm:адрес (адрес сегмента rsegm может быть задан именем сегмента, переменной, меткой или адресным выражением);
  • short — дополняет атрибут near метки оператора перехода, указывает, что переход осуществляется на расстояние ±128 байтов от текущей команды. Формат: short метка;
  • high — возвращает старший байт 16-битового значения opr. Формат: high opr;
  • low — возвращает младший байт 16-битового значения opr. Формат: low opr.

В программах используются:

  • атрибуты дистанции:
  • near — близкий, в пределах одного сегмента;
  • far — далекий, за пределами одного сегмента;
  • атрибуты типа данных:
  • byte — длиной 1 байт;
  • word — длиной 2 байта;
  • dword — длиной 4 байта.

2.3. Адресация регистров и ячеек памяти в ассемблере

При программировании на языке ассемблера используются непосредственный, прямой и косвенный методы адресации; причем для адресации регистров микропроцессора — только прямой, а для адресации ячеек оперативной памяти (ОП) — прямой, косвенный и смешанный непосредственный методы. Рассмотрим их на примере адресации второго операнда в команде MOV (переслать).

Непосредственная адресация.

Величина операнда i (impedance) непосредственно указывается в поле команды и может быть задана числом или идентификатором, а также простым выражением, в котором числа связаны символами арифметических операций: +, -, * и /. Идентификатор (с соответствующим именем, например const) должен быть предварительно описан в программе директивой типа: const equ 1024 или const = 1024. Примеры непосредственной адресации:

MOV АХ, 1024; МОV АL, 64; МОV ВХ, 1АН; МОV СН, 1011В;

МОV АХ, const; МОV АХ, 156*10Н/2

и т. п.

Следует помнить, что диапазон чисел, посылаемых в регистры, ограничивается вместимостью последнего: в 1-байтовый регистр (АН, АL, ВН и т.д.) можно посылать числа в диапазоне от 0 до +255 (целое без знака) или от -128 до +127 (целое со знаком); в 2-байтовый регистр (АХ, ВХ, СХ и т. д.) — от 0 до +65 535 (целое без знака) или от -32 768 до +32 767 (целое со знаком).

В отладчике DEBUG: числа – только шестнадцатиричные, регистры – только 2-байтовые.

Прямая адресация регистров МП.

В качестве адреса операнда указывается имя регистра (его символьное обозначение: АХ, АL АН, ВХ, ВL. и т.д.). Примеры:

MOV АХ, ВХ

МОV ВХ, DХ

MOV АН, ВL

Необходимо следить, чтобы разрядность второго операнда (его регистра - источника) соответствовала разрядности принимающего регистра.

Адресация ячеек ОП.

Абсолютный (полный, физический) адрес (Аабс) в общем случае является суммой адресов сегмента (Асегм) и исполнительного адреса (Аисп), в свою очередь формируемого как сумма максимум 3 адресов: смещения (Асмещ), базы (Абаз) и индекса (Аинд), то есть

Аабс = Асегм + Аисп = Асегм + Асмещ [+ Абаз] [+ Аинд] .

Прямая адресация ячеек ОП имеет несколько вариантов:

  • прямая обычная: МОV АХ, pole. Здесь pole — символьное имя переменной X, для которой в ОП были предварительно отведены (или зарезервированы) ячейки памяти директивами типа pole DВ X, pole DW X и т.п. В команде в качестве Аисп берется Асмещ первой ячейки поля, отведенной для переменной X;
  • прямая с индексированием: МОV АХ, pole[SI]. В команде в качестве Аисп берется Аисп = Асмещ + Аинд. Аинд находится в регистре SI;
  • прямая с базированием: МОV АХ, pole[ВХ]. В команде в качестве Аисп берется Аисп = Асмещ + Абаз. Абаз находится в регистре ВХ;
  • прямая с индексированием и базированием: МОV АХ, pole[SI+ВХ]. В команде в качестве Аисп берется Аисп = Асмещ + Аинд + Абаз.

Косвенная адресация ячеек ОП имеет два варианта:

  • косвенная обычная: MOV АХ, [ВХ]. Исполнительный адрес извлекается из регистра ВХ, то есть Аисп = [ВХ];
  • косвенная с индексированием: MOV АХ, [ВХ+SI]. Исполнительный адрес берется в виде суммы адресов, находящихся в регистрах ВХ и SI, Аисп = [ВХ] + [SI].

Смешанная непосредственная адресация ячеек ОП имеет несколько вариантов:

  • непосредственная обычная: MOV АХ, offset pole. В качестве операнда берется непосредственно смещение адреса первой ячейки поля памяти, отведенного для переменной X; offset указывает, что берется не значение переменной X, а именно смещение ее адреса;
  • непосредственная с индексированием: MOV АХ, [SI+const]. В качестве операнда берется сумма значения, хранящегося в регистре SI, и величины const; const может быть задана числом, идентификатором, смещением адреса переменной (offset pole) или их комбинацией — простым выражением;
  • непосредственная с базированием: MOV АХ, [ВХ+const]. Аналогично предыдущему варианту, но регистр SI замещен ВХ;
  • непосредственная с базированием и индексированием: MOV АХ, pole[SI+ВХ+const]. Аналогично предыдущему, но вместо содержимого одного регистра берется сумма содержимого регистров ВХ и SI.

Почти все команды ассемблера за редким исключением (исключения: РОР, PUSH, CALL, RET, IRET) в качестве Асегм обычно используют по умолчанию адрес, находящийся в регистре DS (в исполняемых программах типа .СОМ — в регистре SC); но регистр сегмента может быть задан и явно, например: MOV АХ, ЕХ:pole; MOV АХ, SS:[SI] ит.п.

Последняя команда, в частности, позволяет реализовать прямой доступ к ячейке стековой памяти, стек при этом не изменяется.

Команды РОР, PUSH, CALL, RET, IRET используют сегмент стека (регистр SS).

В отладчике DEBUG используется общее соглашение о том, что операнды, указывающие на адрес в памяти, заключаются в квадратные скобки ([ ]). Это единственный способ, который позволяет различать непосредственные операнды и ссылки на области в памяти. Например:

mov ax, 21 ; загрузить число 21h в регистр AX

mov ax, [21] ; загрузить содержимое памяти по адресу 21h в регистр AX.

2.4. Основные команды языка ассемблера

По назначению можно выделить следующие группы команд языка ассемблера:

  • пересылки данных (MOV — переслать, XCHG — обменять, IN — ввести в микропроцессор, OUT — вывести из микропроцессора и другие);
  • выполнения арифметических операций (ADD и ADC — сложения и сложения с переносом, SUB и SBB — вычитания и вычитания с заемом, MUL и IMUL — умножения без знака и со знаком, DIV и IDIV — деления без знака и со знаком, CMP — сравнения и другие);
  • выполнения логических операций (OR, AND, NOT, XOR, TEST и другие);
  • передачи управления (ветвления программы: JMP — безусловного перехода, CALL — вызова процедуры, RET — возврата из процедуры, J* — условного перехода, LOOP — управления циклом и т. д.);
  • обработки строк символов (MOVS — пересылки, CMPS — сравнения, LODS — загрузки, SCAS — сканирования). Эти команды обычно используются с префиксом (модификатором повторения) REP;
  • прерывания работы программы (INT — программные прерывания, INTO — условного прерывания при переполнении, IRET — возврата из прерывания);
  • управления микропроцессором (ST* и CL* — установки и сброса флагов, HLT — останова, WAIT — ожидания, NOP — холостого хода и другие).

С полным списком команд ассемблера, а также с подробными примерами программирования на языке ассемблера можно познакомиться в [6 - 9].

Команды пересылки данных:

  • MOV dst, src — пересылка данных (move — переслать из src в dst). Пересылает один байт (если src и dst имеют формат байта) или одно слово (если src и dst имеют формат слова) между регистрами или между регистром и памятью, а также заносит непосредственное значение в регистр или в память.

Операнды dst и src должны иметь одинаковый формат — байт или слово.

Src могут иметь тип: r (register) — регистр, m (memory) — память, i (impedance) — непосредственное значение. Dst могут быть типа r, m.

Нельзя в одной команде использовать операнды: rsegm совместно с i; два операнда типа m, два операнда типа rsegm. Операнд i может быть и простым выражением:

mov АХ, 156*10Н

mov АХ, (152 + 101В) / 15

и т.п.

Вычисление выражения выполняется только при трансляции. Флаги не меняет.

  • PUSH src — занесение слова в стек (push — протолкнуть; записать в стек из src). Помещает в вершину стека содержимое src — любого 16-битового регистра (в том числе и сегментного) или двух ячеек памяти, содержащих 16-битовое слово. Флаги не меняются;
  • РОР dst — извлечение слова из стека (рор — вытолкнуть; считать из стека в dst). Снимает слово с вершины стека и помещает его в dst — любой 16-битовый регистр (в том числе и сегментный) или в две ячейки памяти. Флаги не меняются.

В командах PUSH и РОР операнды dst и src могут быть только типов r и m.

Арифметические команды.

Операнды могут быть двоичные (8 или 16 битов, целые, со знаком или без знака), двоично-десятичные (от 1 до 255 байтов, без знака, в упакованном или распакованном (ASCII-коды) форматах). Машина не обращает внимания на формат и обращается с ними формально, как с двоичными числами в дополнительном коде. Но для десятичной арифметики после операции требуется коррекция (операции только над одним байтом).

Команды сложения, вычитания и сравнения.

Команды сложения, вычитания и сравнения — двухадресные.

  • ADD dst, src — сложение двоичных чисел (add — сложить). Прибавляет байт или слово из памяти, регистра или непосредственно к содержимому регистра; либо прибавляет байт или слово из регистра или непосредственно к полю памяти (содержимое src складывается с содержимым dst). Операнды dst и src должны иметь одинаковый формат — байт или слово; для операнда src используются r, m, i; для операнда dst — r, m. В качестве операндов нельзя использовать совместно rsegm, i, а также нельзя, чтобы оба операнда были типа m или rsegm. Команда ADD формирует флаги AF, CF, OF, PF, SF и ZF.
  • SUB dst, src — вычитание двоичных чисел (substract — вычесть). Вычитает из содержимого регистра байт или слово, взятое из поля памяти, регистра или непосредственно, либо вычитает из поля памяти байт или слово, взятое из регистра или непосредственно (содержимое src вычитается из содержимого dst). Операнды dst и src должны иметь одинаковый формат — байт или слово; для операнда src используются r, m, i; для операнда dst — r, m. В качестве операндов нельзя использовать совместно rsegm, i, а также нельзя, чтобы оба операнда были типа m или rsegm. Команда SUB формирует флаги AF, CF, OF, PF, SF и ZF.
  • СМР dst, src — сравнение (compare — сравнить). Сравнивает содержимое двух полей данных; фактически команда вычитает второй операнд (src) из первого (dst), значение dst не изменяет, а лишь формирует флаги. Операнды dst и src должны иметь одинаковый формат — байт или слово; для операнда src используются r, m, i; для операнда dst — r, m. В качестве операндов нельзя использовать совместно rsegm, i, а также нельзя, чтобы оба типа были m или оба rsegm. Команда СМР формирует флаги: CF, ZF при сравнении чисел без знака, CF, OF, SF, ZF при сравнении чисел со знаком; флаги AF, PF не определены.

Команды приращения.

Команды приращения — одноадресные.

  • INC dst: инкремент (increment — нарастить). Прибавляет 1 к содержимому dst. Операнд dst может быть представлен операндом типа r или m. Команда INC формирует флаги PF, AF, ZF, SF, OF.
  • DEC dst: декремент (decrement — уменьшить). Вычитает 1 из содержимого dst. Операнд dst может быть представлен операндом типа r или m. Команда DEC формирует флаги PF, AF, ZF, SF, OF.

Команды умножения.

Команды умножения — одноадресные. Указывается только src (множитель); места множимого и произведения задаются строго определенным образом.

  • MUL src — умножение (multiply — умножение без знака). Выполняет умножение беззнакового множимого (8 или 16 битов) на беззнаковый множитель (8 или 16 битов). Команда одноадресная — указывается только src (множитель); множимое берется строго определенным образом. Src может быть представлен операндом типа r или m (i — нельзя). Если формат src — байт, то множимое (байт) находится в AL, произведение (слово) будет в АХ; если формат src — слово, то множимое (слово) извлекается из АХ, произведение (двойное слово) помещается в DX : AX (старшие два байта в DX, младшие — в АХ). Команда MUL формирует флаги CF, OF.
  • IMUL src — целое умножение знаковых чисел (integer multiply — умножение целых со знаком). Выполняет умножение знакового множимого (8 или 16 битов) на знаковый множитель (8 или 16 битов). Команда одноадресная — указывается только src (множитель); множимое берется строго определенным образом. Src может быть представлен операндом типа r или m (i — не допускается). Если формат src — байт, то множимое (байт) извлекается из AL, произведение (слово) будет в АХ; если формат src — слово, то множимое (слово) находится в АХ, произведение (двойное слово) заносится в DX : AX (старшие два байта результата в DX, младшие — в AX). Команда IMUL формирует флаги CF, OF.

Команды деления.

Команды деления — одноадресные, указывается только src (делитель); делимое задается строго определенным образом.

  • DIV src — деление (divide — деление без знака). Выполняет деление беззнакового делимого (16 или 32 бита) на беззнаковый делитель (8 или 16 битов). Команда одноадресная — указывается только src (делитель); делимое берется строго определенным образом. Src может быть представлен операндом типа r или m (i — нельзя). Если формат src — байт, то делимое (слово) находится в АХ, частное от деления (байт) будет в AL, остаток от деления (байт) помещается в АН; если формат src — слово, то делимое (двойное слово) заносится в DX : AX (старшие два байта в DX, младшие — в АХ), частное от деления (слово) - в АХ, остаток от деления (слово) сохраняется в DX. Команда DIV формирует флаг IF : IF =1 при делении на 0 и при делении большого числа на очень малое, если частное вне диапазона (флаги AF, CF, OF, PF, SF, ZF не определены).
  • IDIV src — деление целых чисел со знаком (integer divide — деление целых чисел со знаком). Выполняет деление знакового делимого (16 или 32 бита) на знаковый делитель (8 или 16 битов). Команда одноадресная — указывается только src (делитель); делимое берется строго определенным образом. Src может быть представлен операндом типа r или m (i — нельзя). Если формат src — байт, то делимое (слово) — в АХ, частное от деления (байт) будет в AL, остаток от деления (байт) — в АН; если формат src — слово, то делимое (двойное слово) заносится в DX : AX (старшие два байта в DX, младшие в АХ), частное от деления (слово) — в АХ, остаток от деления (байт) — в DL. Команда IDIV формирует флаг IF: IF = 1 при делении на 0 и при делении большого числа на очень малое, если частное вне диапазона).

Логические команды.

Эти команды используются для сравнения, сброса и установки битов операнда в операциях преобразования кодов и при выполнении арифметических операций в кодах ASCII.

  • OR dst, src— логическое сложение (or — или). Команда выполняет поразрядную дизъюнкцию (логическое сложение — операцию «ИЛИ») битов двух операндов; устанавливает 1 в тех битах операнда dst, в которых была 1 хотя бы у одного из исходных операндов. Операнды dst и src должны иметь одинаковый формат (оба — или байт, или слово) и могут быть: src типа r, m, i; dst типа r, m (невозможно rsegm и нельзя, чтобы оба операнда были типа m). Команда OR сбрасывает флаги OF = 0 и CF = 0; формирует флаги PF, SF, ZF.
  • AND dst, src — логическое умножение (and — и). Команда выполняет поразрядную конъюнкцию (логическое умножение — операцию «И») битов двух операндов; устанавливает 1 в тех битах операнда dst, в которых у обоих исходных операндов были 1. Операнды dst и src должны иметь одинаковый формат (оба — или байт, или слово) и могут быть: src типа r, m, I; dst типа r, m (невозможно rsegm и нельзя, чтобы оба операнда были типа m). Флаги: OF = 0 и CF = 0; команда формирует PF, SF, ZF; значение AF не определено.

Команды сдвига и циклического сдвига.

Эти команды производят перемещение битов в операндах влево или вправо, причем сдвиг может быть как логическим (знак операнда не учитывается), так и арифметическим (для знаковых операндов). Сдвиг может быть влево или вправо. При сдвиге вправо на одну позицию крайний правый бит (младший) выталкивается в CF. При сдвиге влево выталкивается в CF крайний левый бит (старший). При сдвиге на несколько позиций выталкивается указанное количество битов. Циклический сдвиг возвращает вытолкнутые биты на место освободившихся.

Команды логического сдвига SHR dst, src и SHL dst, src выполняют сдвиг вправо и влево соответственно. Операнд dst – код, над которым выполняется операция сдвига. Операнд dst может быть типа r, m. Операнд src определяет количество позиций, на которое нужно произвести сдвиг. Чаще всего это непосредственное значений. Можно использовать в качестве src и регистр, но это может быть только регистр CL. Сдвиг возможен не более чем на 32 позиции, поэтому в расчет принимается не весь опреранд src, а остаток от деления его на 32. При сдвиге вправо старший бит замещается нулем, при сдвиге влево младший бит замещается нулем. Схемы выполнения сдвига показаны на рис. 2.1.

Рисунок 2.1. Схемы выполнения логического сдвига командами SHR и SHL

Команды арифметического сдвига SAR dst, src и SAL dst, src выполняют поразрядный сдвиг вправо и влево соответственно целых чисел со знаком (арифметический сдвиг). Старший (знаковый) разряд сохраняет свое значение, вытолкнутый бит помещается в CF. Dst и src такие же, как и в командах логического сдвига. Арифметический сдвиг влево на один разряд эквивалентен умножению целого числа со знаком на 2, вправо на один разряд – делению на 2. Схемы выполнения арифметического сдвига приведены на рис. 2.2.


Рисунок 2.2. Схемы выполнения арифметического сдвига командами SAR и SAL

Команды циклического сдвига через флаг переноса RCR dst, src и RCL dst, src осуществляют ротацию битов. Освободившиеся биты замещаются битом из CF, затем в CF помещаются выталкиваемые биты. Схемы выполнения циклического сдвига через флаг переноса приведены на рис. 2.3.

Рисунок 2.3. Схемы выполнения циклического сдвига через флаг переноса командами RCR и RCL

Команды циклического сдвига с выносом во флаг переноса ROR dst, src и ROL dst, src осуществляют ротацию битов. Выталкиваемые биты помещаются в CF. Освободившиеся биты замещаются вытолкнутыми в CF. Схемы выполнения циклического сдвига с выносом во флаг переноса приведены на рис. 2.4.

Рисунок 2.4. Схемы выполнения циклического сдвига с выносом во флаг переноса командами RCR и RCL

Команды безусловной передачи управления.

JMP opr — команда безусловной передачи управления (jump unconditionally — перейти безусловно). Операнд opr может быть задан прямым или косвенным адресом:

  • по прямому адресу: JMP метка.

Если метка в том же сегменте, что и команда JMP, переход считается внутренним (near), если не в том же сегменте — переход внешний (far). В написаний самой команды JMP разницы нет; тип перехода определяется видом метки: после метки для внутреннего перехода ставится двоеточие «:». Транслятор по таблице меток и их адресов сам определяет атрибуты near или far, и соответственно транслирует команду передачи управления в более короткую или более длинную (в последнем случае надо менять не только содержимое регистра смещения IP, но и регистра сегментов CS). Несколько сократить длину команды может указание программиста «JMP short метка» о том, что метка не далее ±128 байт от первого байта команды JMP (это указание не обязательно, но если оно есть и ошибочно, то транслятор выдаст ошибку);

  • по косвенному адресу. Косвенный адрес может быть задан: в регистре: JMP r или в памяти: JMP символьное_имя;
  • в памяти с косвенной адресацией: JMP near ptr [SI]; JMP far ptr [ВХ] и т. д.

В последних двух командах near ptr и far ptr указывать обязательно, так как, какое слово содержится в регистре SI — обычное или двойное, ассемблер заранее не знает и ему нужно помочь.

Команды перехода к подпрограмме и выхода из подпрограммы.

Подпрограммы оформляются как процедуры. Процедура начинается именем процедуры и заканчивается командой выхода ret (return) и директивой endp. В программе процедура помещается в операторные скобки: porc ... endp. Около оператора proc могут быть указаны атрибуты дистанции: near — близкая процедура (в том же сегменте) или far — дальний вызов (если этот атрибут дистан

ции опущен, то подразумевается near).

Пример:

DISP proc far; после имени процедуры двоеточие не ставится

; тело процедуры

ret

DISP endp

Команда перехода к подпрограмме: CALL opr.

Вызов процедуры (call a procedure — вызов процедуры), безусловная передача управления, выполняющая короткий или дальний вызов процедуры. Флаги не меняются.

В команде CALL атрибуты near или far указывать не надо, так как ассемблер нужную информацию получит сам из директивы определения процедуры. По команде CALL должно быть выполнено:

  • запоминание в стеке адреса возврата (содержимого IP и CS для следующей команды: 16 битов, если near, и 32 бита, если far);
  • переход к выполнению процедуры (инициируется записью в IP и в CS (если far) нового адреса команды).

Операнд opr, определяющий адрес процедуры, бывает:

  • непосредственным: CALL имя_процедуры;
  • прямым — процедуру с атрибутом near можно вызвать через регистр, в котором содержится смещение адреса процедуры: CALL r;
  • косвенным:
    • процедура с атрибутом near вызывается, используя переменную размером в слово: CALL word ptr символьное_имя,
    • процедура с атрибутом far — используя переменную размером в двойное слово: CALL dword ptr символьное_имя.

Команда выхода из подпрограммы.

RET — возврат из процедуры (return from procedure). Команда извлекает из стека адрес возврата и возвращает управление из процедуры, вызванной ранее командой CALL. Необязательный параметр команды RET указывает количество байтов, которые освобождаются в стеке после извлечения адреса возврата. Если процедура имеет атрибут near, то команда RET извлекает из стека одно слово и заносит его в регистр IP; если процедура имеет атрибут far, то команда RET извлекает из стека два слова: сначала смещение адреса, а затем адрес сегмента, и заносит их, соответственно, в регистр IP и в регистр CS. Флаги не меняются.

Процедуры могут быть вложенными, глубина вложения допускается любая (ограничивается лишь емкостью стека).

Команды условной передачи управления.

Есть 31 команда условной передачи управления (УПУ), но некоторые попарно совпадают, например: «если >» и «если не < и не =». Разных команд всего 17. Условная передача управления может быть только ближней (near) и короткой (short), то есть метка перехода должна быть в том же сегменте и не далее ±128 байтов от УПУ.

Общий формат команды:

J* метка

где J* — условие передачи управления.

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

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

Надо четко различать J* без знака и со знаком. Например, пусть регистр АХ содержит 11000110, а регистр ВХ — 00010110, и команда СМР АХ, ВХ сравнивает содержимое этих регистров. Если данные беззнаковые, то число в АХ больше, а если знаковые — то меньше (поскольку в последнем случае единица в крайнем левом разряде определяет знак числа — в АХ число отрицательное).

Команды условной передачи управления для беззнаковых данных.

  • JA / JNBE (Jump if Above / Not Below nor Equal) — переход, если выше/не ниже или равно (переход, если флаги ZF = 0 и CF = 0).
  • JAE / JNB (Jump if Above or Equal / Not Below) — переход, если выше или равно/не
  • ниже (CF = 0).
  • JB / JNAE (Jump if Below / Not Above nor Equal) — переход, если ниже/не выше или равно (CF = 1).
  • JBE / JNA (Jump if Below or Equal / Not Above) — переход, если ниже или равно/не
  • выше (СР = 1 или АР = 1).

Команды условной передачи управления для знаковых данных.

  • JG / JNLE (Jump if Greater / Not Less nor Equal) — переход, если больше/не меньше или равно (ZF = 0 и SF = OF).
  • JGE / JNL (Jump if Greater or Equal / Not Less) — переход, если больше или равно/не меньше (SF = OF).
  • JL / JNGE (Jump if Less / Not Greater nor Equal) — переход, если меньше/не больше или равно (SF >< OF).
  • JLE / JNG (Jump if Less or Equal / Not Greater) — переход, если меньше или равно/не больше (ZF = 1 или SF >< OF).

Команды условной передачи управления для прочих проверок.

  • JE / JZ (Jump if Equal / Zero) — переход, если равно/нуль (ZF = 1).
  • JNE / JNZ (Jump if Not Equal / Not Zero) — переход, если не равно/не нуль (ZF = 0). JS (Jump if Sign) — переход, если есть знак (отрицательно) (SF = 1).
  • JNS (Jump if Not Sign) — переход, если нет знака (положительно) (ZF = 0).
  • JC (Jump if Carry) — переход, если есть перенос (аналог JB) (CF = 1).
  • JNC (Jump if Not Carry) — переход, если нет переноса (аналог JNB) (CF = 0).
  • JO (Jump if Overflow) — переход, если есть переполнение (OF = 1).
  • JNO (Jump if Not Overflow) — переход, если нет переполнения (OF = 0).
  • JP / JPE (Jump if Parity / Parity Even) — переход, если есть четность (PF = 1).
  • JNP / JPO (Jump if No Parity / Parity Odd) — переход, если нет четности (PF = 0).
  • JCXZ (Jump if CX is Zero) — переход, если содержимое регистра СХ равно 0 (СХ = 0).

Команды управления циклами.

Используются для повторения цикла известное число раз. Количество повторений предварительно записывается в регистр СХ (счетчик циклов). Каждый цикл автоматически уменьшает показание СХ на 1.

Основная команда: LOOP метка.

Циклы повторяются до обнуления СХ. Команда уменьшает значение в регистре СХ на единицу и передает управление по прямому адресу — метке, если значение в регистре СХ не равно нулю; в противном случае выполняется следующая по порядку команда. Флаги не меняет. Существуют еще 4 альтернативные команды, в которых можно поставить дополнительные условия. Передачи управления командами типа LOOP только ближние и короткие (метки near и short).

Команды прерывания.

У команд прерывания есть некоторая аналогия с командами вызова процедуры CALL: прекращается выполнение текущей программы и осуществляется переход к подпрограмме обработки прерывания; но при прерываниях нет деления на процедуры near или far, так как начальный адрес подпрограммы обработки прерывания (вектор прерывания) берется из таблицы векторов ОЗУ и он всегда 32-битовый; кроме того, при вызове процедуры в стеке сохраняется только адрес возврата, а при прерывании еще и флаги.

Имеется три команды прерывания.

  • INT opr - прервать (INTerrupt) выполнение программы и передать управление по одному из 256 адресов (векторов прерывания), определяемых номером прерывания — opr. По этой команде микропроцессор:
    • помещает в стек содержимое регистров: FL (флагов), CS (сегмента команд), IP (указателя команд);
    • обнуляет флаги TF и IF (флаги системного прерывания и блокировки прерывания);
    • загружает в CS и IP, соответственно, второе и первое слова вектора прерываний, считанного из таблицы векторов в ОЗУ по адресу 4 • opr (4 • номер прерывания); вся таблица векторов занимает 1024 байта, то есть всего может быть 256 различных векторов прерывания. Например, команда INT 1Ah считает из ОЗУ вектор, находящийся по адресу 68h = 4 • 1Ah, то есть в регистр CS будет загружен адрес сегмента из слова по адресу 6Ah, а в регистр IP — из слова по адресу 68h смещение программы обработки этого прерывания.

Команда сбрасывает флаги IF = 0 и TF = 0.

  • INTO - прервать по переполнению (INTerrupt if Overflow), при возникновении переполнения флаг OF = 1 и управление передается по адресу 10H (аналог команды INT 4). Команда сбрасывает флаги IF = 0 и TF = 0.
  • IRET - возврат из обработки прерывания (interrupt return) обеспечивает возврат из программы обработки прерывания. IRET — последняя команда подпрограммы обработки прерывания, по этой команде из стека извлекаются 3 последних слова и загружаются в регистры IP, CS и FL, при этом содержимое SP увеличивается на 6. Команда устанавливает значения всех флагов.

2.5. Основные директивы ассемблера

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

Директивы определения идентификаторов.

Присваивают идентификатору с данным именем некоторое текстовое или числовое значение (выражение). Формат директив:

имя EQU текст

имя = числовое значение (выражение)

Разница между псевдооператорами EQU и =:

  • EQU — присваивает значение постоянно (изменять нельзя), текст может быть символьным, числовым или смешанным выражением, определяющим константу, адрес, другое символьное имя, метку и т. д.;
  • = — выполняет текущее присваивание (значение может быть переназначено, но только при трансляции, естественно); присваивает только числовое выражение, содержащее простые математические преобразования, которые при трансляции и будут выполнены (например: const + 1, 15Н*4, 3*12/4 и т.п.).

Директивы определения данных.

Используются для идентификации переменных и полей памяти. Формат директивы

[имя] D* выражение [, выражение] [, …].

Ключевые слова О* могут быть следующими:

  • DB — определить байт (1 байт);
  • DW — определить слово (2 байта);
  • DD — определить двойное слово (4 байта);
  • DQ — определить 8 байтов;
  • DT — определить 10 байтов.

Рассматриваемые директивы объявляют переменную (имя) или присваивают полям (ячейкам) памяти начальные значения; резервируют в памяти (с более поздним присвоением значения) один или несколько байтов — DB, слов — DW, двойных слов — DD и т. д.

Выражение показывает, какое количество элементов памяти необходимо выделить и какие данные там должны содержаться. Выражение может быть:

  • константой:

const DB 56; const DW 1936; const DD 3FFH.

Обязательно следует учитывать диапазон и вместимость байта, слова и т.д.; так, для DB константа не может быть больше 255, для DW — 65 535, для DD — 65 5352 - 1 = 4 294 967 295;

  • вектором или таблицей:

table1 DB 30, 4, -15, 0, 0, 0, 56; table2 DW 1936, 3004, 56, 15.

В одном псевдооператоре допускается поместить строку до 132 позиций, причем вместо повторения одного и того же значения несколько раз (0 в table1) можно использовать псевдооператор DUP (duplicate — дублировать):

table1 DB 30, 4, -15, 3 DUP(0), 56;

  • строкой символов:

str1 DB 'Вы ввели слишком большое число';

str2 DB 'Bad command’.

В псевдооператоре DB строка может содержать 255 символов, во всех остальных (DW, DD, DQ, DT) — только 2 символа.

  • пустым полем:

pole1 DB ?; pole2 DW 12 dup(?),

при этом в элементы резервируемой памяти при загрузке программы ничего не записывается (заносится не 0, как, например, в директиве

poleЗ DW 5 dup(0),

а просто резервируются ячейки памяти);

  • символическим именем переменной:

var1 DW disp; var2 DD vector.

Здесь одна переменная определяется адресом другой, в директивах указывать offset не надо, поскольку имя переменной воспринимается как ее адрес. Такой вариант подходит, например, для хранения адресов ячеек памяти, меток, на которые допустимо ссылаться в программе (var1 DW disp), причем, если переменная находится в том же сегменте, что и ссылающаяся команда, то достаточно в качестве адреса указать только смещение (2 байта), то есть обойтись DW; если же переменная находится в другом сегменте, то необходимо указать и сегмент, и смещение (всего 4 байта), то есть следует использовать уже DD (var2 DD vector);

  • простым выражением:

fn1 DB 80*3; fn2 DW (disp) + 256, вычисляемым, разумеется, только при трансляции программы.

Директивы определения сегментов и процедур.

Сегмент определяется псевдооператорами;

имя_сег segment

имя_сег ends

В программе можно использовать 4 сегмента (по числу сегментных регистров) и для каждого указать соответствующий регистр сегмента псевдооператором ASSUME (assume — присвоить), например:

Codeseg segment

Assume CS:codeseg, DS:dataseg, SS:stackseg

Codeseg ends

В директиве ASSUME регистр_сег : имя_сег [, …], в частности, Assume CS:codeseg, указывается, что для сегмента имя_сег (codeseg) выбран регистр регистр_сег (CS).

После директивы ASSUME следует явным образом загрузить адрес начала сегмента данных в регистр DS:

mov АХ, dataseg

mov DS, АХ

Процедура определяется псевдооператорами:

имя_процедуры ргос [far]

...

ret

имя_процедуры endp

При определении процедуры после ключевого слова proc должен быть указан атрибут дистанции near или far; если этого атрибута нет, то по умолчанию подразумевается near. Обычно процедура должна заканчиваться командой ret. (return). Если процедура объявлена как near, то обращение к ней (call) должно производиться из того же сегмента; если proc far, то из любого сегмента (в этом случае командой ret из стека при возврате будет извлечено два слова: для IP и для CS).

Директивы управления трансляцией.

Их несколько, наиболее часто используется END. Директива END отмечает конец программы и указывает ассемблеру, где завершить трансляцию. Формат: END [имя_программы].

3. Выполнение работы

3.1. Получить вариант задания на лабораторную работу по табл. 1.3 (см. описание лабораторной работы № 1).

3.2. Написать программу на языке ассемблера в соответствии со следующим алгоритмом.

Шаг 1. Занести значение Nя (по варианту задания) в регистр SI.

Шаг 2. Занести число X (по варианту задания) в ячейку памяти, адрес которой находится в регистре SI.

Шаг 3. Переслать число из ячейки памяти, адрес которой находится в регистре SI, в регистр RG (по варианту задания).

Шаг 4. 16-разрядный регистр, содержащий регистр RG, скопировать в регистр AX.

Шаг 5. Увеличить содержимое регистра SI на 1.

Шаг 6. Занести число Y (по варианту задания) в ячейку памяти, адрес которой находится в регистре SI.

Шаг 7. Выполнить логический сдвиг числа в регистре RG: для нечетных вариантов – влево на 3 разряда, для четных вариантов – вправо на 2 разряда.

Шаг 8. Условный переход: если флаг переноса = 1, то перейти к шагу 11.

Шаг 9. Сложить число из ячейки памяти, адрес которой находится в регистре SI, с числом в регистре AX.

Шаг 10. Условный переход: если знак результата отрицательный, перейти к шагу 7.

Шаг 11. Для четных вариантов: вычесть число, находящееся в ячейке Nя, из числа в регистре AX. Для нечетных вариантов: вычесть число, находящееся в ячейке Nя + 1, из числа в регистре AX.

Шаг 13. Занести слово из регистра AX в ячейку памяти Nя + 2.

3.3. Занести программу в свободную область оперативной памяти с помощью отладчика DEBUG.

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

3.5. Осуществить выполнение программы в автоматическом режиме. Сравнить результаты выполнения пп. 3.4, 3.5. Прочитать результат работы программы из ячейки памяти.

3.6. Определить длину программы в байтах.

3.7. С помощью команд N и W отладчика DEBUG записать программу на диск.

3.8. Используя команды отладчика N и L, загрузить программу с диска. Убедиться в работоспособности загруженной программы.

4. Требования к отчету

Отчет по лабораторной работе должен содержать:

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

5. Контрольные вопросы

  1. Назовите основные компоненты языка ассемблера.
  2. Приведите формат ассемблерной программы и поясните его.
  3. Приведите формат ассемблерной директивы и поясните его.
  4. Поясните ограничения, накладываемые на операнда в составе двухадресных команд.
  5. Назовите команды безусловной передачи управления и поясните их структуру и назначение.
  6. Назовите несколько команд условной передачи управления и поясните их структуру и назначение.
  7. Рассмотрите листинг ассемблерной программы и поясните все компоненты его строк.
  8. Поясните последовательность работы блоков персонального компьютера при реализации команды машинной программы.
  9. Какие команды отладчика DEBUG используются при работе с программами на языке ассемблера?
  10. Какие команды отладчика DEBUG используются при записи программ на диск и для загрузки программ с диска?


7

0

0

CFF

0

7

CFF

0

0

7

CFF

CFF

0

0

7

0

7

CFF

CFF

0

0

7

0

7

CFF

CFF

0

7

Программирование на языке ассемблера