- •Аппаратно-ориентированное программирование
- •Ббк 32.973.73
- •Удк 681.3 ббк 32.973.73ф 73
- •1. Основы программирования на ассемблере
- •1.1. Принципы построения ассемблерных программ
- •1.2. Понятие архитектуры компьютера
- •1.3. Регистры программиста в ia32
- •1.4. Описание сегментной структуры программы
- •2. Простейшие средства ассемблера
- •2.1. Средства описания данных
- •2.2. Обращения к функциям ос посредством прерываний
- •2.3. Средства преобразования в исполняемый файл
- •2.4. Управление строками при выводе и ввод данных
- •2.5. Простейшие способы адресации
- •3. Архитектурные элементы для построения программ
- •3.1. Организация условных переходов
- •3.2. Средства организации циклов
- •3.3. Особенности команд умножения и деления
- •3.4. Организация процедур
- •3.5. Неарифметические операции над кодами
- •4. Использование неэлементарных способов адресации
- •4.1. Косвенно-регистровая адресация
- •4.2. Использование индексной адресации данных
- •4.3. Базовая и индексно базовая адресации
- •4.4. Адресация с масштабированием
- •5. Взаимосвязи программных единиц
- •5.1. Многомодульная разработка программ
- •5.2. Использование библиотек объектных модулей
- •5.3. Организация стекового кадра подпрограммы
- •5.4. Программный доступ к системным функциям Win32
- •5.5. Особенности использования объектных файлов формата coff
- •5.6. Стандартный доступ к системным функциям Unix
- •6. Вспомогательные средства базовой архитектуры
- •6.1. Использование строковых команд пересылки
- •6.2. Применение строковых команд сравнения
- •7. Использование ассемблерных отладчиков
- •7.1. Особенности отладчика gdb для программ в Linux
- •7.2. Отладчики текстового режима для Windows
- •Библиографический список
- •Оглавление
3.2. Средства организации циклов
После условных управляющих конструкций следующими по значимости оказываются средства организации циклов. Заметим, что циклы можно реализовать уже на основе команд условных переходов, если в качестве параметра цикла использовать именованное поле или зарезервировать за ним некоторый регистр. Тогда, учитывая замеченное свойство указанных команд осуществлять переход «куда хотим», можно построить цикл по схеме
MOV параметр, число_повторений_цикла
метка_тела_цикла:
команды тела цикла
SUB параметр, 1
JG метка_тела_цикла
или по схеме
MOV параметр, 0
метка_тела_цикла:
команды тела цикла
ADD параметр, 1
CMP параметр, число_повторений_цикла
JNE метка_тела_цикла
В связи с очень частым использованием элементарных действий увеличения на единицу и уменьшения на единицу для этих операций введены отдельные команды. Они имеют мнемонику INC и DEC (от английских слов increment - увеличить и decrement - уменьшить). Обе эти команды имеют единственный операнд, задающий регистр или место в памяти. При использовании таких команд последние схемы перепишутся в виде
MOV параметр, число_повторений_цикла
метка_тела_цикла:
команды тела цикла
DEC параметр
JG метка_тела_цикла
или по схеме
MOV параметр, 0
метка_тела_цикла:
команды тела цикла
INC параметр
CMP параметр, число_повторений_цикла
JNE метка_тела_цикла
Первый вариант в рассмотренных схемах имеет на одну команду меньше внутри цикла и, следовательно, должен обеспечивать большее быстродействие. Поэтому именно он и был взят за основу архитектурной реализации цикла с помощью специальных команд.
Простейшая из этих команд в мнемонике ассемблера изображается с помощью служебного слова LOOP и имеет общий вид
LOOP метка_тела_цикла
По выполняемым действиям эта команда равносильна двум последним командам первой схемы с тем дополнением, что значение параметра предполагается размещенным стандартно в регистре ECX. (Практически для команд результирующего объектного файла, предназначенного для 32-битной архитектуры, строится код машинной команды для регистра ECX, а для результирующего файла, предназначенного для 16-битной архитектуры, строится код машинной команды для регистра CX.)
Поэтому использование команды LOOP в общем случае имеет вид
MOV параметр, число_повторений_цикла
метка_тела_цикла:
команды тела цикла
LOOP метка_тела_цикла
Для сознательного применения этой команды нужно четко представлять, что в ходе выполнения команды LOOP вначале значение регистра для параметра цикла (обычно регистра ecx) уменьшается на единицу, а только затем осуществляется проверка полученного значения на нуль. Следствием такого хода внутренних действий является несколько неожиданное поведение цикла при нулевом начальном параметре. Цикл при этом не будет повторяться "нуль раз", как мог бы подумать поверхностно мыслящий учащийся - поскольку команда LOOP организует цикл с проверкой после тела цикла, то получается цикл с постусловием, который должен выполняться не менее одного раза. При начальном значении параметра цикла, равного нулю, цикл будет выполняться максимально возможное число раз! (Это число равно 232 4 млр. ) Казалось бы, нормально думающий программист и не задаст с помощью команды MOV нулевое число повторений цикла. Но, в действительности, начальное значение для параметра цикла может определяться не константой, а событиями в предшествующей части программы. В частности, и достаточно характерно, может определяться числом действительно введенных символов. Тогда не исключена возможность ввода нулевого числа символов с последующими непредвиденными последствиями.
Поэтому разработчики даже ввели специальные команды для предшествующей циклу проверки на нуль регистра ecx и регистра cx. Они имеют мнемонику JECXZ и JCXZ, а записываются на ассемблере в виде
JECXZ метка_перехода
и
JCXZ метка_перехода
где в качестве метки перехода метки_перехода предполагается использовать метку сразу за концом цикла, использующего команду LOOP.
Заметим, что команда LOOP предполагает ее использование в двух основных модификациях: с регистром ecx и с регистром cx. Эти различия на ассемблере NASM могут задаваться дополнительным операндом в одном из вариантов
LOOP метка_тела_цикла, ecx
LOOP метка_тела_цикла, cx
явно подчеркивающим использование того или иного регистра. На ассемблерах MASM и TASM выбор варианта использования регистра ecx задается расширенным мнемокодом в виде LOOPD, а применение обычного обозначения LOOP влечет использование регистра cx в качестве места хранения параметра цикла.
На использование команды LOOP накладывается то же ограничение, что и на простейшие команды Jcc, а именно с помощью их нельзя перейти достаточно далеко от места самой команды (не далее чем на 127 байтов вперед или -128 назад).
Когда требуется использовать вложенные циклы, ограниченная настройка команды LOOP только на один специализированный регистр ecx, казалось бы, является ограничением. В действительности, решение проблемы заключается во временном сохранении значения параметра для внешнего цикла.
Наиболее удобным средством временного хранения информации в современных архитектурах является стек. Это обусловлено тем, что стек как аппаратно-программная структура обязательно присутствует в этих архитектурах и предоставляется для использования во множестве ситуаций. Важнейшим его назначением является использование для передачи аргументов рекурсивным подпрограммам, но его используют везде, где это удобно. В частности, применение стека для временного хранения позволяет не именовать и не выделять специальные области данных для такого сохранения, не следить за возможностью их многократного использования в результате рекурсивных вызовов (особенно косвенных рекурсий).
Для простейшего применения встроенного стека достаточно знать две команды для манипуляций со стеком. Они называются PUSH и POP, обе из них имеют единственный операнд. В зависимости от используемого варианта (32-битного или 16-битного) эти команды вставляют в стек или выдают из него 32-битные двоичные данные или 16-битные. Манипуляции для 8-битных данных со стеком в рассматриваемой архитектуре не предусмотрены (просто нет таких команд в процессорах семейства Intel и совместимых с ним).
В качестве операнда команды PUSH может быть задан регистр (но только не 8-битный), а также места в памяти, где помещаются запоминаемое слово. Допускается также использование числовой константы в качестве такого операнда. В последних двух случаях для компилятора с ассемблера возникает неопределенность - какой вариант команды PUSH использовать: для 32-битных или для 16-битных данных. Здесь также вынуждено используются модификаторы размера операндов, рассмотренные в разделе 2.6.1.
Построение вложенных циклов на изложенной основе демонстрирует следующая схема
MOV ecx, число_повторений_внешнего_цикла
метка_начала_внешнего_цикла:
PUSH ecx
MOV параметр, число_повторений_внутреннего_цикла
метка_начла_внутреннего_цикла:
команды тела цикла
LOOP метка_начала_внутреннего_цикла
POP ecx
LOOP метка_начала_внешнего_цикла
По существу, в этом решении перед собственно началом внутреннего цикла запоминается значение внешнего параметра, а в конце внутреннего цикл восстанавливается из стека. Поэтому вторая из записанных в этой схеме команд LOOP всегда использует содержимое регистра ecx, которое отвечает параметру внешнего цикла.
Кроме простейшего варианта команды цикла имеется два ее несколько более сложных варианта. Они используют в качестве мнемокода обозначения LOOPE и LOOPNE, которым также равносильны обозначения LOOPZ и LOOPNZ. Отличие этих вариантов от простой команды LOOP заключается в том, что переход по метке, заданной в этих командах, производится не просто по условию отличия от нуля текущего значения регистра параметра (регистра ecx или регистра cx), а только в том случае, когда текущее условие, зафиксированное в регистре EFLAGS, отвечает дополнительному условию "равно" или "не равно" (или, что равносильно, результат "равен нулю" или "не равен нулю"). Таким образом, условие перехода по метке можно записать в виде
(ecx 0) И (флаг ZF установлен) для команд LOOPE и LOOPZ,
(ecx 0) И (флаг ZF не установлен) для команд LOOPNE и LOOPNZ.
Применение этих вариантов команды цикла будут рассмотрено позже при изучении более сложных способов адресации, которые позволяют обрабатывать аналоги массивов данных из языков высокого уровня.