Рис. 6.1. Полный текст программы на языке ассемблера, для сложения пяти целых чисел
Три символических имени, объявленных в разделе данных, используются в командах в разделе кода. В частности, метка MAIN предназначена для определения точки начала выполнения программы, а также задается в ассемблерной директиве END, которой заканчивается текстовый файл программы. В ассемблере IA-32 имеются и другие директивы. Некоторые из них будут рассмотрены позже.
6.3. Управление потоком выполнения программы
Существует два способа отклонения программы от «прямолинейного курса». Первый из них (о нем рассказывается в разделе 5.8) — это вызов подпрограммы и возврат из нее. Кроме того, в программе могут выполняться переходы к заданным командам — как условные, так и безусловные. О них мы сейчас и поговорим.
6.3.1. Условные переходы и флаги кодов условий
Представленная на рис. 5.8, команда
JG STARTADD
относится к числу команд условного перехода. Условие «больше нуля» задается в ней суффиксом кода операции G. Это условие касается и результата последней выполненной команды обработки данных, которой в нашем примере была команда
DEC ECX
Признаки результатов, которые генерируются командами типа Decrement и Add, выполняющими арифметические операции и операции сравнения, записываются в четыре флага кодов условий в регистре состояния процессора, показанном на рис. 5.2. В зависимости от результата операции эти флаги, называемые SF (sign — знак), ZF (zero — нуль), OF (overflow — переполнение) и CF (carry — перенос), устанавливаются в 1 или очищаются нулем. Но существует одно исключение. В операции вычитания бит CF устанавливается в 1, если перенос не выполняется, что соответствует сигналу обратного переноса. Состояние этих флагов можно проверить в последующих командах условного перехода, с тем чтобы решить, следует ли осуществлять переход. В нашем примере при выполнении условия [ECX] > 0 управление передается команде, записанной по целевому адресу STARTADD.
В команде условного перехода задается не абсолютное значение целевого адреса перехода, а число со знаком, которое прибавляется к содержимому регистра указателя команды, то есть целевой адрес задается относительно адреса в указателе команды. Значение указателя команды увеличивается сразу после выборки очередной команды, поэтому он всегда определяет следующую выполняемую команду программы. Когда к указателю прибавляется относительный адрес перехода, он начинает указывать на команду, следующую за командой перехода. Предположим, что адрес STARTADD в нашем примере равен 1000. Для кодирования команд ADD, INC, DEC и JG из программы на рис. 5.8, а требуется 7 байт. Обновленное содержимое регистра указателя команды EIP будет равно 1007, то есть адресу последней в программе команды MOV. Таким образом, относительное расстояние до целевой команды перехода составляет -7; именно данное значение и содержится в команде условного перехода. Это маленькое отрицательное число можно представить одним байтом. Поэтому, с учетом байта кода операции, для записи команды условного перехода достаточно 2 байт. Такой размер имеют команды перехода, в которых относительный адрес перехода лежит в диапазоне от -128 до +127. Если же расстояние до целевой команды перехода больше, то используется 4-байтовое смещение.
В этом примере проверяется значение в регистре ЕСХ — нас интересует, больше ли оно нуля. Другие свойства результата можно проверить при помощи иных команд условного перехода. Например, если результат равен нулю, переход выполняется командой JZ (или JE), а если знак результата отрицателен (знаковый бит равен 1) — командой JS.
6.3.2. Команды сравнения
Условный переход в программах часто осуществляется в соответствии с результатом сравнения двух чисел. Команда Compare
CMP dst,src
выполняет операцию
[dst] - [src]
и на основе полученного результата устанавливает флаги кодов условий. При этом ни один из операндов не изменяется и первый операнд всегда сравнивается со вторым. Например, переход по условию «больше» выполняется в том случае, если операнд назначения dst больше исходного операнда src.
6.3.3. Безусловный переход
Команда безусловного перехода JMP всегда вызывает переход к команде по целевому адресу. В ней может быть задано короткое (1 байт) или длинное (4 байта) относительное смещение со знаком. Кроме того, как и в командах условного перехода, могут использоваться другие режимы адресации. Такая гибкость определения целевого адреса перехода может быть очень полезной. В каждой точке программы выполняется только одно из альтернативных вычислений. Предположим, что в специальной таблице в памяти начиная с адреса JUMPTABLE хранятся 4-байтовые адреса первой команды каждой из подпрограмм, представляющих возможные ветви программы. Если последовательно пронумеровать эти ветви как 0, 1, 2... и загрузить индекс выполняемой подпрограммы в регистр ESI, то переход к выбранной ветви может быть выполнен посредством такой команды с использованием индексной адресации со смещением:
JMP [JUMPTABLE + ESI*4]
6.4. Логические команды, команды сдвига и циклического сдвига
6.4.1. Логические операции
В архитектуре IA-32 имеются команды, выполняющие логические операции И, ИЛИ и Исключающее ИЛИ (END, OR, XOR). Это поразрядные операции с двумя операндами и записью результата по адресу назначения. Предположим, что в регистре ЕАХ содержится шестнадцатеричное значение 0000FFFF, а в регистре ЕВХ — значение 02FA62CA. Команда
AND EBX,EAX
очистит левую половину регистра ЕВХ, заполнив ее нулями, а правую его часть оставит без изменений. В результате в ЕВХ окажется значение 000062СА.
Кроме того, в архитектуре IA-32 имеется команда NOT, генерирующая логическое дополнение всех битов операнда, то есть заменяющая все единицы нулями, а все нули единицами.
6.4.2. Операции сдвига и циклического сдвига
При помощи операции логического или арифметического сдвига операнд может быть смещен влево или вправо на заданное количество разрядов. Формат команды сдвига таков:
КодОперации dst,count
где сдвигаемый операнд dst задается при помощи одного из стандартных адресных режимов, а количество разрядов сдвига count представляется 8-разрядным значением, либо задаваемым непосредственно в команде, либо содержащимся в 8-разрядном регистре CL. Существует четыре команды сдвига:
- SHL — логический сдвиг влево;
- SHR — логический сдвиг вправо;
- SAL — арифметический сдвиг влево (то же, что SHL);
- SAR — арифметический сдвиг вправо.
При внимательном рассмотрении представления двоичного числа, понятно, что сдвиг числа на один разряд влево эквивалентен его умножению на 2, а сдвиг на один разряд вправо — его делению на 2. Конечно, при сдвиге влево может произойти переполнение, а при сдвиге вправо может потеряться конец числа. Освобождающиеся в результате сдвига разряды устанавливаются в 0, а сдвигаемые за границу операнда разряды отмечаются с помощью флага переноса С, а затем удаляются. Устанавливать флаг С особенно удобно при выполнении арифметических операций с большими числами, занимающими больше одного слова. На рис. 6.2, а показан пример сдвига содержимого регистра R0 влево на два разряда. Команда логического сдвига вправо работает точно так же (рис. 6.2, 6).
Еще одно важное наблюдение заключается в том, что при сдвиге вправо в освободившемся разряде должен быть повторен знаковый бит. Этим арифметический сдвиг вправо отличается от логического, в котором освобождающиеся разряды всегда заполняются нулями. Пример арифметического сдвига вправо (SAR) приведен на рис. 6.2, в. Арифметический сдвиг влево ничем не отличается от логического.
а
б
в
Рис. 6.2. Команды логического и арифметического сдвига: логический сдвиг влево, SHL R0,2 (а); логический сдвиг вправо, SHR R0,2 (б); арифметический сдвиг вправо, SAR R0,2 (в)
Существует также четыре команды циклического сдвига:
ROL - циклический сдвиг влево;
ROR - циклический сдвиг вправо;
RCL - циклический сдвиг влево с установкой флага CF;
RCR - циклический сдвиг вправо с установкой флага CF.
Компьютер поддерживает две версии циклического сдвига вправо и две версии циклического сдвига влево. В первой версии разряды операнда просто циклически сдвигаются, а во второй в сдвиге участвует еще и флаг CF. Примеры всех четырех операций циклического сдвига приведены на рис. 6.3.
Рис. 6.3. Команды циклического сдвига: влево без переноса, ROL R0,2 (a); влево с пере-носом, RLC R0,2 (б); вправо без переноса, ROR R0,2 (a); вправо с переносом, RRC R0,2 (г)
6.4.3. Программа упаковки цифр
В качестве простого примера использования указанных команд давайте рассмотрим программу упаковки цифр. Код этой программы для процессоров IA-32 вы видите на рис. 6.4. Два байта ASCII загружаются в регистры AL и BL Команда SHL сдвигает байт в регистре AL на четыре позиции влево, заполняя четыре освободившихся младших бита нулями. Посредством второго операнда этой команды задается количество разрядов, на которое должен быть сдвинут первый операнд. Команда AND очищает четыре старших бита второго байта, записывая в них нули. После этого 4-разрядные значения, представляющие BCD-коды чисел, объединяются командой OR в регистре AL, а затем сохраняются в памяти по адресу PACKED.
Рис. 6.4. Программа для процессоров IA-32, упаковывающая две цифры BCD в один байт
6.5. Другие команды
Мы рассмотрели лишь малую часть набора команд архитектуры IA-32. Еще несколько важных команд будут представлены ниже.
6.5.1. Вычитание
Команда SUB (вычитание), так же как и ADD выполняет вычитание байтов или слов, содержащих двоичные данные. Вычитание осуществляется в компьютере по методу сложения с двоичным дополнением: для второго операнда устанавливаются обратные значения битов и прибавляется 1, а затем происходит сложение с первым операндом. Во всем, кроме первого шага, операции сложения и вычитания идентичны.
Существует пять возможных комбинаций операндов: Примеры:
вычитание регистр – регистр SUB EAX,EBX;
вычитание память – регистр SUB NUMB,EBX;
вычитание регистр – память SUB EAX,NUMB;
вычитание регистр - непосредственное значение SUB EAX,100;
вычитание память - непосредственное значение SUB 100,EBX.
6.5.2. Команды умножения и деления
Кроме команд для сложения и вычитания целых чисел со знаком, описанных в разделе 5.5, в наборе команд IA-32 имеются команды для целочисленного умножения и деления, а также для выполнения арифметических операций над числами с плавающей запятой.
Имеются следующие команды умножения:
MUL – для умножения чисел без знака;
IMUL - для умножения чисел со знаком.
В общем случае в результате умножения двух 32-разрядных чисел получается произведение двойной длины, то есть 64-разрядное значение. Однако для многих приложений достаточно иметь результат одинарной длины, то есть 32-разрядное значение. В подобных ситуациях используются разные команды. Результат одинарной длины генерирует команда
IMUL REG,src(исходный)
помещающая 32-разрядное значение результата в регистр общего назначения REG. Исходный операнд может находиться либо в регистре, либо в памяти.
В случае результата двойной длины команды
IMUL src и MUL src
используют в качестве второго операнда регистр AL, AX или ЕАХ, в зависимости от размера исходного операнда, располагающегося либо в регистре, либо в памяти. Произведение двойной длины помещается в два регистра: старшая половина разрядов результата сохраняется в регистрах DH, DX или EDX, а младшая — в регистрax AL, AX или ЕАХ.
Команда для выполнения целочисленного деления имеет следующий формат:
IDIV src
Выполняет целочисленное деление со знаком AL, АХ или ЕАХ (в зависимости от размера источника) на источник (регистр или переменная) и помещает результат в AL, АХ или ЕАХ, а остаток — в АН, DX или EDX соответственно. Результат всегда округляется в сторону нуля, знак остатка всегда совпадает со знаком делимого, абсолютное значение остатка всегда меньше абсолютного значения делителя.
Числа с плавающей запятой имеют гораздо больший диапазон значений, чем целые числа, и используются в первую очередь для научных вычислений. В архитектуре IA-32 применяется полный набор арифметических операций с такими числами. Их операнды и результаты располагаются в регистрах с плавающей запятой, показанных на рис. 5.12. Поддерживаются два формата чисел: с одинарной (32 разряда) и двойной (64 разряда) точностью.
6.5.3. Команды мультимедийного расширения
Двухмерные графические и видеоизображения можно представить массивом, состоящим из большого количества точек. Цвет и яркость каждой точки, называемой пикселом, могут быть закодированы 8-битовым элементом данных. Обработка таких данных имеет две особенности. Во-первых, при обработке отдельных пикселов часто приходится выполнять очень простые арифметические и логические операции. Во-вторых, для некоторых приложений реального времени требуется исключительно высокая скорость вычислений. Сказанное касается и обработки аудиосигналов и речи, представляемых последовательностью отсчетов непрерывного аналогового сигнала, генерируемых через фиксированные промежутки времени.
Ускорить процесс обработки данных в приложениях обоих типов можно за счет разделения последовательности элементов данных (как правило, байтов или 16-разрядных слов) на маленькие группы, которые можно обрабатывать параллельно. В наборе команд IA-32 имеется множество таких, которые параллельно обрабатывают данные группами по 64 бита, называемыми четверными словами. (Четверное слово содержит восемь байт или четыре 16-разрядных слова.) Эти команды называются ММХ-командами или командами мультимедийного расширения (multimedia extension). Их операнды могут располагаться в памяти или в регистрах с плавающей запятой. Таким образом, эти регистры служат двойной цели:
в них могут храниться числа с плавающей запятой или операнды ММХ-команд. При использовании в командах ММХ регистры обозначаются как ММ0-ММ7.
Для пересылки 64-разрядных ММХ-операндов между памятью и регистрами ММХ используются специальные команды Move.
Команда
PADDB MMi,src
складывает соответствующие байты 8-байтовых операндов и помещает в регистр назначения восемь сумм. Исходный операнд может располагаться в памяти или в регистре ММХ, но операнд назначения обязательно должен находиться в регистре ММХ. Подобные команды имеются как для операций по вычитанию, так и для логических операций.
Типичной операцией, выполняемой в приложениях обработки сигналов, является свертка — умножение короткой последовательности отсчетов входных сигналов на константы, называемые весовыми коэффициентами, и сложение произведений для получения значения выходного сигнала. Подобные операции осуществляются с помощью специальной команды ММХ, объединяющей умножение и сложение. В ней используются 64-разрядные операнды ММХ, содержащие по четыре 16-разрядных элемента данных, представляющих отсчеты сигнала.
6.5.4. Векторные команды
В архитектуре IA-32 определен набор команд, предназначенных для выполнения арифметических операций над маленькими группами чисел с плавающей запятой. Команды SIMD (Single Instruction, Multiple Data — одиночный поток команд и множественный поток данных) полезны для векторных и матричных вычислений в научных приложениях. В терминологии Intel они называются командами потокового расширения SIMD (Streaming SIMD Extension, SSE). Эти команды обрабатывают составные операнды длиной 128 бит, состоящие из четырех 32-разрядных чисел с плавающей запятой. Для хранения этих операндов имеются 128-разрядные регистры (на рис. 5.1 не показаны). Двумя базовыми командами этой группы являются команды сложения и умножения. Они воздействуют на четыре соответствующие пары исходных 32-разрядных значений, которые находятся в составных 128-разрядных операндах, и помещают четыре отдельных результата в 128-разрядный операнд назначения.
6.6. Подпрограммы
Очень часто программа должна по многу раз выполнять определенную подзадачу, но с разными значениями данных. Такая подзадача обычно называется подпрограммой. Подпрограмма может, скажем, вычислять функцию sin или сортировать список в порядке возрастания или убывания значений.
Составляющий такую подпрограмму блок команд можно включать во все те места программы, где он должен выполняться. Однако на практике так никогда не поступают. Для экономии места в память помещают только одну копию блока команд, и любая программа, которой потребуется выполнить эту подпрограмму, просто переходит к ее начальному адресу. Такой переход называется вызовом подпрограммы и выполняется в помощью команды Call.
После реализации подпрограммы работа вызывающей ее программы должна быть продолжена. В таком случае говорят, что выполняется возврат из подпрограммы в вызывающую программу. Делается это с помощью команды Return. Поскольку подпрограмма может вызываться из нескольких разных мест основной программы, при ее вызове где-то должен сохраняться адрес возврата. Иными словами, для обеспечения правильного возврата из подпрограммы команда Call должна сохранить содержимое регистра PC.
Применяемый компьютером способ выполнения вызовов подпрограмм и возврата из таковых называется методом связывания подпрограмм. Простейший метод связывания подпрограмм заключается в сохранении адреса возврата в заданном месте, которым может быть специально выделенный для этого регистр. Такой регистр называется регистром связи. Когда работа подпрограммы завершается, команда Return возвращает управление вызывающей программе, выполняя неявный переход через регистр связи.
Особой разновидностью команды перехода является команда Call, выполняющая такие операции, как сохранение содержимого регистра PC в регистре связи и переход по указанному в команде целевому адресу. Команда Return также является разновидностью команды перехода, но она выполняет переход по адресу, заданному в регистре связи. Этот процесс проиллюстрирован на рис. 6.5.
Рис. 6.5. Связывание подпрограммы через регистр связи
6.6.1. Вложенность подпрограмм и стек процессора
В программировании очень распространена практика вызова одних подпрограмм из других. Такие подпрограммы, вызываемые друг из друга, называются вложенными. Если вложенные вызовы будут реализованы по описанной выше технологии, вторая команда Call сохранит адрес возврата в регистре связи, уничтожив его текущее содержимое, после чего возврат в исходную программу станет не возможным. Поэтому перед вызовом очередной подпрограммы нужно где-то сохранять содержимое регистра связи.
Теоретически подпрограммы могут вкладываться на любую глубину. Рано или поздно последняя вызванная подпрограмма завершит свою работу и вернет управление вызвавшей ее подпрограмме. Необходимый для этого адрес возврата — это последний адрес, сохраненный в данной цепочке вызовов подпрограмм. Иными словами, адреса возврата сохраняются и используются в порядке LIFO (Last In First Out, что в переводе с английского значит «последним вошел — первым вышел»). Очевидно, что адреса возврата, связанные с вызовами подпрограмм, должны помещаться в стек. Многие процессоры делают это автоматически, как часть работы команды Call. Для использования в качестве указателя стека (SP) вызовов подпрограмм выделяется отдельный регистр, именуемый стеком процессора. Команда Call помещает в стек процессора содержимое регистра PC, а команда Return выталкивает из этого стека адрес возврата и помещает его в PC.
6.6.2. Организация стека
Стек, это специальная структура данных (один из способов организации данных), которая предназначена для обмена информацией между главной программой и подпрограммой.
Стек — это список элементов данных, обычно слов или байтов, доступ к которым ограничен следующим правилом: элементы этого списка могут добавляться только в его конец и удаляться только из конца. Конец списка называется вершиной стека, а его начало — дном. Такую структуру иногда называют магазином. Этот механизм хранения и обработки данных хорошо описывается емкой фразой «последним вошел — первым вышел» (Last In First Out, LIFO), означающей, что элемент данных, помещенный в стек последним, удаляется из него первым. Операцию помещения нового элемента в стек часто называют его проталкиванием (push), а операцию извлечения последнего элемента из стека называют его выталкиванием (pop).
Данные, организованные в виде стека хранятся, так чтобы последовательные элементы располагались друг за другом. Первый элемент хранится по адресу BOTTOM, а когда в стек помещаются новые элементы, они располагаются в порядке уменьшения последовательных адресов. Таким образом, стек растет в направлении уменьшения адресов. На рис. 6.6 показано, как располагается в памяти компьютера стек, элементы которого занимают по одному 32-разрядному слову. На дне он содержит числовое значение 43, а на вершине -28. Для отслеживания адреса вершины стека используется регистр процессора, называемый указателем стека (Stack Pointer, SP (ESP)).
Так как память адресуется побайтово и слово имеет длину 32 разряда (4 байта), операцию проталкивания в стек можно реализовать так:
SUB ESP,4
MOV NEWITEM,(ESP)
где команда SUB вычитает операнд 4 из результирующего операнда, содержащегося в регистре ESP, и помещает результат в регистр ESP. Эти две команды помещают слово, хранящееся по адресу NEWITEM, на вершину стека, предварительно уменьшая указатель стека (адрес вершины) на одно слово равное 4 байтам. Операция выталкивания из стека может быть реализована так:
MOV ITEM,(ESP)
ADD ESP,4
Эти две команды перемещают значение, хранившееся на вершине стека, в другое место памяти, по адресу ITEM, а затем уменьшают указатель стека на 4, чтобы он указывал на тот элемент, который теперь располагается на вершине стека.
Рис. 6.6. Стек слов в памяти
Как было уже отмечено, стек процессора удобно применять для выполнения операций, связанных с входом в подпрограммы и возвратом из таковых. В архитектуре IA-32 в качестве указателя стека используется регистр ESP, указывающий на текущую вершину стека процессора (то есть на его верхний элемент). Ширина стека составляет 32 разряда, а это означает, что его элементы являются двойными словами.
Существует четыре команды для проталкивания элементов в стек и выталкивания их из стека.
Команда
PUSH Source
уменьшает значение ESP на 4, а затем сохраняет двойное слово, находящееся по адресу Source, в памяти, который указан в ESP.
Команда POP
POP Destination
выполняет обратную операцию: считывает из памяти двойное слово, на которое указывает ESP, то есть считывает из стека верхний элемент, а затем сохраняет его по адресу Destination и увеличивает значение ESP на 4, удаляя тем самым верхний элемент из стека. Регистр ESP используется в этой команде неявно. Исходный и результирующий операнды задаются в одном из режимов адресации IA-32. Еще две команды предназначены для выталкивания из стека и проталкивания в него сразу нескольких элементов.
Команда
PUSHAD
проталкивает в стек содержимое восьми регистров общего назначения, от ЕАХ до EDI, а команда
POPAD
выталкивает их из стека в обратном порядке. При извлечении сохраненного значения ESP команда POPAD удаляет его из стека, не загружая в регистр ESP, и продолжает выталкивать последующие элементы, записывая их в соответствующие регистры. Применение этих двух команд при реализации подпрограмм позволяет более эффективно сохранять и восстанавливать содержимое всех регистров.
6.6.3. Передача параметров
Вызывая подпрограмму, программа должна передать ей параметры (операнды), которые будут использоваться в вычислениях, или же их адреса. Закончив свою работу, подпрограмма вернет другие параметры — результаты вычислений. Такой обмен информацией между вызывающей программой и подпрограммой называется передачей параметров. Передача параметров может выполняться несколькими способами. Например, параметры можно помещать в регистры или в память, откуда подпрограмма сможет их считать. В качестве альтернативы параметры можно поместить в стек процессора, используемый для хранения адресов возврата.
Использование регистров процессора — способ простой и эффективный. На рис. 6.7 показано, как реализовать программу, выполняющую сложение последовательности чисел, в виде подпрограммы с передачей параметров через регистры. Длина последовательности n, информация о которой хранится в памяти по адресу N, и адрес первого числа, NUM1, передаются подпрограмме через регистры ECX и EBX. Вычисленная подпрограммой сумма возвращается вызывающей программе через регистр EAX. Соответствующую часть вызывающей программы составляют первые четыре команды из числа представленных на рис. 5.19. Первые две команды загружают в регистры ECX и EBX значения N и NUM1. Команда Call выполняет переход к подпрограмме, начинающейся по адресу LISTADD. Кроме того, эта команда помещает в стек процессора адрес возврата из подпрограммы. Подпрограмма вычисляет сумму и помещает ее в регистр EAX. После возврата из подпрограммы вызывающая программа сохраняет эту сумму в памяти по адресу SUM.
Таким образом, регистры ЕВХ, ЕСХ и ЕАХ используются для передачи параметров. Регистр EDI подпрограмма при выполнении сложения задействует в качестве индексного регистра, поэтому его содержимое должно сохраняться и восстанавливаться при помощи команд PUSH и POP. Подпрограмма вызывается командой
CALL LISTADD
Первым делом эта команда проталкивает в стек адрес возврата, а затем выполняет переход по адресу LISTADD. Содержимое стека после сохранения в нем содержимого регистра EDI показано на рис. 6.7, б. Адрес возврата в нашем примере — это адрес команды MOV, непосредственно следующей в вызывающей программе за командой CALL. Команда RET возвращает управление вызывающей программе, выталкивая из стека содержимое указателя команды EIP.
а
б
Рис. 6.7. Программа с рис. 5.8, переписанная в виде подпрограммы для процессоров IA-32; параметры передаются через регистры: вызывающая программа и подпрограмма (а);
содержимое стека после сохранения значения EDI в подпрограмме (б)
На рис. 6.8 показан еще один вариант этой же программы, в котором параметры передаются подпрограмме через стек. Параметры NUM1 и N проталкиваются в стек двумя командами PUSH в вызывающей программе. После выполнения команды CALL вершина стека располагается на уровне 2. Регистры EDI, ЕАХ, ЕВХ и ЕСХ используются так же, как в подпрограмме на рис. 5.7. Их значения сохраняются в стеке, затем в них загружаются начальные значения и параметры. Эту работу выполняют первые 8 команд подпрограммы. В результате вершина стека оказывается на уровне 3. После сложения чисел при помощи цикла из четырех команд сумма помещается в стек на место параметра NUM1. Выполнив команду RET, команды вызывающей программы ADD и POP удаляют из стека параметр N и помещают результирующую сумму в память по адресу SUM, возвращая вершину стека на уровень 1.
а
б
Рис. 6.8. Программа с рис. 6.7, а, переписанная в виде подпрограммы для процессоров IA-32 (параметры передаются через стек): вызывающая программа и подпрограмма (а);
содержимое стека после сохранения значения EDI в подпрограмме (б)
В завершение темы вызова подпрограмм мы рассмотрим пример обработки вложенных вызовов. На рис. 5.9 приведен код программы на языке ассемблера IA-32, иллюстрирующей пример обработки вложенных вызовов. Стековые фреймы обоих подпрограммы вы видите на рис. 5.10. Указателем на фрейм служит регистр ЕВР. В наборе команд IA-32 имеются команды PUSHAD и POPAD, с помощью которых можно сохранить в стеке и восстановить из него все восемь регистров общего назначения, но в программе на рис. 5.9 мы предпочли воспользоваться отдельными командами PUSH и POP, поскольку в подпрограммах задействована только половина всех регистров.
Рис. 6.9. Вложенные подпрограммы на языке ассемблера IA-32
Рис. 6.10. Стековые фреймы для программы, представленной на рис 6.9. (Вершина стека ESP)
6.7. Примеры программ
6.7.1. Программа для вычисления скалярного произведения двух векторов
На рис. 6.11 приведена программа вычисления скалярного произведения двух векторов для процессоров архитектуры IA-32. Начальные адреса этих векторов равны AVEC и BVEC. В программе для доступа к последовательным элементам векторов используется базовая индексная адресация. В качестве индексного регистра применяется регистр EDI. Коэффициент масштабирования равен 4, поскольку элементы векторов являются двойными словами (4 байта). В качестве счетчика цикла используется регистр ЕСХ, инициализированный значением n. Это позволяет задействовать команду LOOP (см. раздел 5.2), которая сначала уменьшает значение регистра ЕСХ, а затем выполняет условный переход по адресу LOOPSTART, если содержимое регистра ЕСХ не равно нулю. Предполагается, что произведение двух элементов векторов поместится в двойное слово, поэтому в команде умножения IMUL явно задан регистр назначения EDX (см. раздел 5.14).
Рис. 6.11. Программа для процессоров IA-32, вычисляющая скалярное произведение двух векторов
6.7.2. Программа сортировки байтов
int main (int argc, char*, argv[])
{
for (j=n-1; j>0; j=j-1)
{for(k=j-1; k >= 0; k = k - 1)
{if (LIST[k] > LIST[j])
{TEMP = LIST[k];
LIST[k] = LIST[j];
LIST[j] = TEMP;
}
}
}
}
Рис 6.12. Программа сортировки байтов для процессоров IA-32 на языке С
На рис. 6.13, вы видите программу для процессора IA-32, выполняющую сортировку байтов. Регистр ЕАХ инициализируется значением LIST и используется в качестве базового регистра для доступа к элементам списка при базовой индексной адресации. Регистр EDI служит индексным регистром для внешнего цикла (индекс j), а регистр ЕСХ — индексным регистром для внутреннего цикла (индекс k). В регистре DL содержится текущий наибольший байт сортируемого подсписка. Для замены элементов LIST(k) и LIST(j) программе требуются три команды, а также регистр для временного хранения данных.
В программе введена еще одна неизвестная команда
XCNG operand1, operand 2,
которая меняет значения операндов.
Рис. 6.13. Программа сортировки байтов для процессоров IA-32 на языке ассемблера
6.7.3. Подпрограммы для вставки и удаления элементов связного списка
Программы на рис. 6.14 и 6.15, выполняют вставку и удаление элементов связного списка. Параметры им передаются через регистры. Причем регистры с именами RHEAD, RNEWREC, RIDNUM, RCURRENT и RNEXT используются так же, как в универсальных подпрограммах. Указанные имена применяются вместо имен регистров IA-32 ЕАХ, ЕВХ и т. д. Для хранения кода новой вставляемой записи задействован шестой регистр, RNEWID, в который первая команда подпрограммы с рис. 6.14 загружает код новой записи.
В программе вставки в связный список нового элемента для процессоров IA-32 предполагается, что код новой записи не совпадает ни с одним из кодов, имеющихся с списке записей, а в программе удаления предполагается, что в списке имеется запись с кодом, заданным в регистре RIDNUM.
Рис. 6.14. Подпрограмма для процессоров IA-32, вставляющая в связный список новый элемент
Рис. 6.15. Подпрограмма для процессоров ia-32 удаляющая элемент из связного списка
6.8. Различия между программами в ЕХЕ - и СОМ - файлах
Несмотря на то, что существуют служебные программы, например EXE2BIN, которые преобразует ЕХЕ -файл в СОМ -файл, однако нужно знать определенные различия между программой, выполняемой как ЕХЕ -файл и программой, выполняемой как СОМ-файл.
1. Размер программы.
Программа в формате ЕХЕ может иметь любой размер, в то время как СОМ-файл ограничен размером одного сегмента и не превышает 64 К. Размер СОМ-файла всегда меньше, чем размер соответствующего ЕХЕ-файла; одна из причин этого - отсутствие в СОМ-файле 512-байтового заголовка ЕХЕ-файла (префикса программного сегмента, PSP).
2. Сегмент стека.
В ЕХЕ-программе определяется сегмент стека, в то время как СОМ-программа генерирует стек автоматически. Таким образом, при создании ассемблерной программы, которая будет преобразована в СОМ-файл, стек должен быть опущен.
3. Сегмент данных.
В ЕХЕ- программе обычно определяется сегмент данных, а регистр DS инициализируется адресом этого сегмента. В СОM-программе все данные должны быть определены в сегменте кода.
4. Инициализация.
В ЕХЕ-программе выполняются запись нулевого слова в стек и инициализация регистра DS. Так как в СОМ-программе стек и сегмент данных не определены, то эти шаги отсутствуют. Когда СОМ-программа начинает работать, все сегментные регистры содержат адрес префикса программного сегмента (PSP) - 256-байтовый (100Н) блок, который резервируется операционной системой DOS непосредственно перед СОМ- или ЕХЕ-программой в памяти. Так как адресация начинается со смещения 100Н от начала PSP, то в программе после оператора SEGMENT кодируется директива ORG 100Н.
5. Обработка.
Для программ в ЕХЕ- и СОМ-форматах выполняются:
a) ассемблирование для получения OBJ-файла:
Для TASM:
tasm /z /zi /l prog.asmкроме файла: prog.obj дополнительно создается:
файл.lst (листинг программы (опция /l);
выводится исходный текст вместе с сообщением об ошибках (опция /z);
дополнительная информация для дебагера(опция /zi).
b) компоновка для получения ЕХЕ-файла:
Для TLINK:
tlink /v prog.objСоздает:
файлы: prog.exe и prog.map
дополнительную информацию для дебагера (опция /v).
Если программа создается для выполнения как ЕХЕ-файл, то ее уже можно выполнить.
Если же программа создается для выполнения как СОМ-файл, то компоновщиком будет выдано сообщение:
Warning: No STACK Segment*
Это сообщение можно игнорировать, так как определение стека в программе не предполагалось.
6.8.1. Пример программы типа СОМ
Программа PROG, приведенная на рис.6.16, написана согласно требованиям СОМ-формата. Обратите внимание на следующие особенности в этой СОМ-программе:
• Сегмент стека и сегмент данных отсутствуют.
• Директива ORG служит для резервирования 100H байт от начального адреса под PSP.
• Директива ORG 100H устанавливает относительный адрес для начала выполнения программы. Программный загрузчик устанавливает этот адрес в командном указателе.
• Команда JMP служит для обхода данных, определенных в программе.
Размеры ЕХЕ- и СОМ-программ - 788 и 20 байт соответственно. Учитывая такую эффективность СОМ-файлов, рекомендуется все небольшие программы создавать в СОМ-формате.
Некоторые программисты кодируют элементы данных после команд так, что первая команда JMP не требуется. Кодирование элементов данных перед командами позволяет ускорить процесс ассемблирования и является хорошим стилем, рекомендуемым в руководствах по ассемблеру.
;Программа Prog
;Программа складывает два числа и завершается
.MODEL TINY; модель памяти, используемая для СОМ.CODE [SEGMENT]; начало сегмента кода ORG 100H; начальное значение счетчика - 100h, конец PSPBEGIN: JMP START;обход через данные
FLDA DW 0F77Fh;определение данных
FLDB DW 03219h
FLDC DW?
| Поделиться: |
Поиск по сайту
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных