5.4. Команды ia-32
Набор команд архитектуры IA-32 очень обширен. Машинные команды имеют переменную длину и не всегда однотипный формат, о чем будет рассказано ниже. В большинстве команд IA-32 задаются один или два операнда. Если операндов два, только один их них может храниться в памяти, а другой должен находиться в регистре процессора. Наряду с обычными командами для пересылки данных между памятью и регистрами процессора, выполнения арифметических операций в наборе команд IA-32 имеется множество команд для реализации логических операций, операций сдвига и циклического сдвига, а также для манипулирования стеком процессора. Для обработки нечисловых данных предусмотрены команды, оперирующие строками байтов.
Мы начнем с рассмотрения небольшого набора команд и покажем, как они могут использоваться в сравнительно небольшой программе. Команда
ADD dst,src
выполняет операцию
dst <- [dst] + [src]
а команда
MOV dst,src
как вы уже знаете, — операцию
dst <- [src]
Предположим, что операнды хранятся в регистрах ЕАХ и ЕВХ. Следующие две команды вычисляют сумму операндов в регистре ЕАХ и сохраняют ее в памяти по адресу SUM:
ADD EAX,EBX
MOV SUM,EAX
Поскольку в памяти может располагаться только один из операндов команды, для реализации операции С<-[А]+[В] с тремя операндами в памяти потребуется целых три команды:
MOV ЕАХ,А
ADD EAX,B
MOV С,ЕАХ
Команда вычитания
SUB dst,src
выполняет операцию
dst <- [dst] - [src]
Для увеличения и уменьшения операнда на 1 предназначены команды автоувеличения (инкремента) и автоуменьшения (декремента), INC и DEC.
Прежде чем мы перейдем к рассмотрению программы, выполняющей сложение чисел, вам нужно познакомиться еще с двумя командами. Первая из них — команда перехода. Если результат последней арифметической операции был больше нуля, команда
JG LOOPSTART
осуществляет переход по адресу LOOPSTART. Все команды условного перехода начинаются с буквы J, соответствующей слову Jump (переход), за которой следуют буквы, обозначающие условие. В данном примере буква G обозначает «больше нуля». О других командах условного перехода будет рассказано чуть позже.
Чтобы получить возможность при косвенной регистровой адресации использовать регистр общего назначения, нужно сначала загрузить в такой регистр адрес операнда команды. Это можно сделать двумя способами. Если для нужного адреса в программе явно определена адресная метка, скажем LOCATION, этот адрес можно загрузить в регистр при помощи команды Move и с применением непосредственной адресации, вот так:
MOV EBX,OFFSET LOCATION
Данная команда загружает в регистр ЕВХ адрес, представленный меткой LOCATION. В качестве альтернативы можно задействовать команду Load Effective Address (загрузка исполнительного адреса) с мнемоническим обозначением LEA. Команда
LEA EBX,LOCATION
выполняет ту же операцию, что и приведенная выше команда. При помощи команды LEA можно загружать в регистры адреса, динамически вычисляемые во время выполнения программы. Предположим, нам нужно загрузить в регистр ЕВХ адрес операнда, для доступа к которому применяется базовая адресация со смещением. Команда
LEA ЕВХ,[ЕВР+12]
загрузит в регистр ЕВХ адрес операнда, расположенного по адресу [ЕВР]+12. Адрес зависит от содержимого регистра ЕВP в момент выполнения команды.
Программа для сложения чисел
Используя только что описанные команды, мы можем создать программу для сложения чисел при помощи цикла. Предположим, что в памяти по адресу N содержится набор 32-разрядных чисел, расположенных последовательно начиная с адреса NUM1. На рис. 5.8. приведена программа на языке ассемблера, складывающая эти числа и помещающая результат в память по адресу SUM.
В регистр ЕВХ загружается значение адреса NUM1. Он используется как базовый регистр при базовой индексной адресации в первой команде цикла, расположенной по адресу STARTADD. Индексным регистром здесь является ECX. На первой итерации цикла к содержимому регистра ЕАХ, первоначально установленному в 0, прибавляется последнее число из списка, хранящееся по адресу [ЕВХ+ECX*4] = NUM1 – 4 + n*4, поскольку в команде ADD задан коэффициент масштабирования 4, а базовый регистр мы предварительно уменьшили на 4 командой SUB. Затем значение индексного регистра уменьшается на 1. На второй итерации цикла к содержимому регистра ЕАХ прибавляется предпоследнее 32-разрядное число, расположенное по адресу NUM1 - 4+(n-1)*4. На каждой следующей итерации к сумме прибавляются числа, находящиеся по адресам на 4 меньше, и на последней итерации при n=1 прибавится число, расположенное по адресу NUM1. Регистр ЕСХ используется в качестве индекса дополнительно, главная его «специализация» использование его в качестве счетчика. Сначала вторая команда программы загружает в него значение, хранящееся в памяти по адресу N. Далее на каждой итерации цикла значение в этом регистре уменьшается на 1. Команда условного перехода JG выполняет переход к началу цикла по адресу STARTADD, если [ЕСХ] > 0. Когда содержимое регистра ЕСХ становится равным нулю, это означает, что все числа списка сложены. В таком случае переход не осуществляется и следующая команда пересылки сохраняет содержимое регистра ЕАХ в памяти по адресу SUM.
Так как счетчик (регистр ECX) меняет свое значение от n до 1, то если бы мы не уменьшили значение базового регистра на 4, то мы сложили бы n чисел, заканчивая NUM2, а начиная NUMn+4 т.е. с адресами на 4 больше, чем NUM1 и NUMn.
Используемая в программе команда LOOP STARTADD объединяет функции двух команд:
DEC ЕСХ
JG STARTADD
т.е. эта команда сначала уменьшает содержимое регистра ЕСХ, а затем выполняет переход по адресу STARTADD, если значение ЕСХ не равно нулю.
LEA EBX,NUM1 ;Загрузка адреса NUM1 в базовый регистр
SUB EBX,4 ;Уменьшение адреса на 4
MOV ECX,N ; Загрузка значения счетчика
MOV EAX,0 ;Обнуление регистра суммы
STARTADD: ADD EAX,(EBX+ECX*4) ;Определение адреса следующего и
;прибавление к EAX
LOOP STARTADD ;Уменьшение на 1 счетчика ECX и
;если ECX >0, переход на STARTADD
MOV SUM,EAX ;Пересылка суммы по адресу SUM
… …
SUM DEF ? ;сегмент данных программы
N DEF n
NUM1 def ?
NUM2 def ?
NUM3 def ?
… …
NUMn def ?
Рис. 5.8. Программа для процессоров IA-32, выполняющая сложение последовательности чисел;
Обработка списков и массивов всегда требует внимательности, особенно при выборе условия перехода и метода индексации — с нуля или единицы. Неверный выбор может привести к ошибкам в программе. В языках высокого уровня эта задача упрощается, поскольку к элементам списка можно явно обращаться как к LIST(0), LIST(1), ..., LIST(n-l), а начало и конец цикла можно явно связать со значениями индекса при помощи таких выражений, как
FOR I FROM 0 UPTO (n-1)
или
FOR I FROM (n-1) DOWNTO 0
Это краткое описание некоторых из числа наиболее часто используемых команд IA-32 и примера программного цикла можно считать введением в систему команд и язык ассемблера IA-32. В следующем разделе рассматривается машинное представление команд этого семейства процессоров.
Формат машинных команд
Общий формат машинных команд процессоров IA-32 показан на рис. 5.9. Команды имеют переменную длину — от 1 до 12 байт и могут включать до четырех полей. Команды длиной в 1 байт содержат только обязательное поле кода операции. Как правило, длина этого поля составляет 1 байт, иногда — 2 байта. Информация о режиме адресации содержится в одном или двух байтах, следующих за полем кода операции. В тех командах, где для формирования исполнительного адреса операнда используется только один регистр, поле режима адресации имеет длину 1 байт. Второй байт нужен для кодирования двух последних режимов адресации (см. табл. 5.1). В этих режимах для формирования исполнительного адреса операнда нужно иметь 2 байта.
Если для вычисления исполнительного адреса операнда требуется указать смещение, его значение записывается в один или два байта в поле, следующем за полем режима адресации. Если один из операндов задается непосредственно, он помещается в последнее поле команды и занимает 1 или 4 байта.
Для ряда простых команд, подобных тем, о которых рассказывалось в предыдущем разделе, код используемого регистра задается прямо в байте кода операции.
Однако для большинства команд и режимов адресации регистры задаются в поле режима адресации.
Во многих системах команд в тех случаях, когда таковые имеют переменную длину, в начале двоичного представления команды должна быть задана ее длина. Связано это с тем, что последовательные команды располагаются в памяти друг за другом, без указания границ между ними.
Рис. 5.9. Формат команды IA-32
Однобайтовые команды
Команды INC и DEC, предназначенные для уменьшения и увеличения значения регистра, имеют длину 1 байт. Например, в командах