Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Архитектура вычислительных систем и Ассемблер с приложением методических указаний к лабораторным работам. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
5.2. Основные команды 191
Команды передачи управления, вызова и возврата
Таблица 5.4
Команда Действие
jmp src Безусловный переход по адресу src (goto src)
jCC src Условный переход по адресу src (если верно условие src)
Вызов и возврат из функций
call src Вызов подпрограммы — помещает в стек адрес следующей инструкции
(адрес возврата) и переходит по адресу src
ret [imm]
int imm8 Вызов прерывания с номером imm8 — помещает в стек флаги flags,
iret Возврат из прерывания — снимает со стека адрес возврата и флаги,
sysenter Быстрый системный вызов
sysexit Возврат из системного вызова
syscall Быстрый системный вызов
sysret Возврат из системного вызова
Возврат из подпрограммы — снимает со стека адрес возврата и помещает его в указатель команд. Если указан параметр imm, снимает со стека ещё imm байтов.
Вызов и возврат из программного прерывания
затем адрес возврата, после чего переходит к обработчику прерывания
imm8
возвращает к прерванной программе
Вызов и возврат из системного вызова (32 бита)
Вызов и возврат из системного вызова (64 бита)
данные (флаги, указатель стека и т. д). Парная к ней команда возврата восста­навливает сохранённые элементы и помещает в указатель команд адрес возврата. Таким образом, после выполнения подпрограммы управление перейдёт обратно к вызвавшей её программе и продолжится именно с той команды, которая следует за командой вызова.
Для вызова подпрограммы любого вида (функции или системного вызова) допу­стимо использовать только ту команду, которая является парной к завершающей эту подпрограмму команде возврата. В противном случае произойдёт крах.
Команды вызова и возврата не осуществляют передачу в подпрограмму па­раметров и возврат значения. Эти действия выполняются вручную и регламенти­руются соглашениями о вызовах (подробнее механизм вызова рассматривается в разделе 6.2).
В систему команд x86 входят три пары команд вызова/возврата.
192 Глава 5. Синтаксис и команды GNU Assembler x86
1.
Команды
call/ret
предназначены для вызова функций и процедур, описанных в самой программе и прикладных библиотеках. Команды
2.
int/iret
предназначены для программного обращения к прерыва­нию. В современной прикладной программе явный вызов программного прерывания обычно используется только для обращения к ядру операционной системы
(системного вызова, подробнее в разделе 6.2.8).
3. Команды, предназначенные специально для системных вызовов. В тридцатидвухбитном режиме это
sysenter/sysexit
, в шестидесятичеты-
рёхбитном — syscall/sysret.
Вызов и возврат из функций
Вызов функции в ассемблере выполняется командой
call
. Эта команда имеет
один операнд — адрес подпрограммы в памяти.
ётся
Команда
foo
call foo
сохраняет указатель команд в стеке, управление переда-
. Возврат из подпрограммы выполняется командой
ret
, которая должна находиться в конце кода подпрограммы. — управление передаётся адресу, снятому со стека.
В листинге 5.9 показана подпрограмма
foo()
, прибавляющая к значению реги-
стра eax константу 5, а также её вызов.
Листинг 5.9. Функция и её вызов
1 // Вызывающая программа 2 movl $10, %eax 3 call foo 4 // теперь %eax == 15 5 ... 6 // Функция foo() 7 foo: 8 addl $5, %eax 9 ret
Так же, как и для команд перехода
является метка
label
, неявно используется прямая относительная адресация (в ма-
jmp/jCC
, если операндом команды
call
шинный код включается смещение относительно текущего указателя команд
label ip
), а если операнд в регистре или памяти — это абсолютный адрес перехо-
да, задаваемый соответственно регистровой или косвенной адресацией.
Вызов подпрограммы по указателю применяется, в частности, для реализации механизма виртуальных функций, адрес которых выбирается из таблицы виртуаль­ных методов на этапе выполнения программы.
5.2. Основные команды 193
Вызов и возврат из прерывания
Для вызова прерывания необходимо указать его восьмибитный номер (то есть номера прерываний могут принимать значения от0до
255
imm8
). Каждому номеру прерывания соответствует специальный системный регистр, содержащий адрес обработчика этого прерывания.
Команда вызова прерывания
flags
, затем адрес возврата. Если обработчик выполняется с привилегиями ядра
int imm8
помещает в стек сначала регистр флагов
операционной системы, в специальном регистре сохраняется также указатель стека, так как система использует другой стек. После этого управление передаётся обработчику прерывания imm8.
Команда возврата из прерывания
iret
восстанавливает указатель стека (при
необходимости), флаги и передаёт управление по адресу возврата (из стека при этом извлекаются оба помещённых туда командой int машинных слова).
Необходимо отметить, что в документации AMD команда вызова программно-
го прерывания
int
относится к командам общего назначения, а соответствующая
команда возврата iret — к системным [3].
Один из номеров прерываний в тридцатидвухбитных операционных системах
обычно используется для системных вызовов.
Вызов и возврат из системных вызовов
Начиная с Pentium II, доступна предложенная Intel команда
sysenter
, ускоря­ющая обращение к ядру. Адрес возврата и другие сохраняемые данные помещаются командой
sysenter
в специальные регистры, что быстрее обращения к памяти.
В конце обработчика системного вызова их восстанавливает команда sysexit.
В шестидесятичетырёхбитном режиме использование команды
sysenter
невоз-
можно.
Для быстрого обращения к функциям ядра в шестидесятичетырёхбитном ре-
жиме применяется команда
syscall
, предложенная AMD. Она также сохраняет
данные для возврата в регистрах. Для возврата из системного вызова в шестидеся­тичетырёхбитном режиме предназначена команда sysret.
Отметим, что команда возврата из системного вызова (
sysexit
или
sysret
используется только в обработчике этого вызова, то есть в коде ядра операционной системы.
Конкретная команда, используемая для системного вызова (
syscall
), определяется используемым ядром. Используемые в Linux команды
int,sysenter
или
и соглашения системных вызовов описаны в разделе 6.2.8.
)
194 Глава 5. Синтаксис и команды GNU Assembler x86
5.2.3. Обнуление регистра
— Начало? — лицо Эдуарда приобрело обиженное выражение. — То есть к нулю? Но это же вырожденный случай!
А. В. Жвалевский, И. Е. Мытько.
Сестрички и другие чудовища
Исторически для обнуления регистров использовались команды побитового исключающего «или» с одинаковыми операндами регистра из самого себя манды пересылки
mov $0, %reg
sub %reg, %reg
, а также занимали меньше места. Зависимость по
, так как они выполнялись быстрее ко-
xor %reg, %reg
и вычитания
данным в ранних моделях процессора не имела значения, так как вычисления не были конвейеризированы.
После введения конвейера традиция обнуления регистров командами
xorиsub
сохранилась. Поэтому в современных моделях процессоров команды обнуления регистров (zero idioms) распознаются при декодировании и выполняются как не имеющие зависимостей по данным.
Таким образом, сейчас руководство по оптимизации Intel [15] снова рекомен­дует использовать для обнуления регистров общего назначения команды:
1 xor %reg, %reg 2 sub %reg, %reg
Для регистров расширения XMM распознаются следующие команды обнуления:
1 xorps/pd %xmmreg, %xmmreg 2 pxor %xmmreg, %xmmreg 3 subps/pd %xmmreg, %xmmreg 4 psubb/w/d/q %xmmreg, %xmmreg
Для некоторых архитектур используются и другие команды обнуления регистров расширения X/Y/ZMM [15].
При этом команды
xorиsub
, не распознающиеся как zero idioms, выполняются
медленнее из-за зависимости по данным (даже если результатом будет ноль).
5.2.4. Команды целочисленной арифметики
В одном потоке чехарды Игра числа и чисел сроки.
В. Хлебников. Зангези
Из-за ограниченного количества операндов в системе команд x86 практиче­ски нет привычных по языкам высокого уровня неразрушающих арифметических
5.2. Основные команды 195
операторов. Один из операндов, как правило, используется и как исходное дан­ное, и как ячейка для записи результата. В частности, аналогом ассемблерной команды сложения щий свои операнды (
add src, dest
в C++ будет не оператор «плюс», не изменяю-
dest = src1+ src2), а оператор «+=»(
dest += src
, то есть
dest = dest + src).
Некоторые команды, предназначенные для обработки целых чисел, перечис­лены в таблице 5.5. Также в таблице 5.5 представлены команды, которые часто используются для выполнения арифметических операций над целыми числами, но не относятся к арифметическим — вычисление эффективного адреса lea и бито­вые сдвиги.
Команды сложения и вычитания
К этой группе, кроме собственно сложения и вычитания, относятся также команды сравнения и изменения знака. В некоторых источниках [78] к командам сложения и вычитания относят также команды инкремента и декремента. которые выставляют только пять из шести флагов состояния (P F, AF, ZF, SF, OF ).
Команда сравнения
src, dest
, но не изменяет приёмник — только выставляет флаги в соответствии
со знаком разности
cmp src, dest
dest src
эквивалентна команде вычитания
. Команды инкремента (увеличения приёмника
sub
dest
на единицу) и декремента (уменьшения на единицу) выполняются быстрее, чем добавление или вычитание единицы командами add/sub и не меняют флаг CF.
Как было сказано в разделе 2.5, представление отрицательных чисел выбира­лось так, чтобы знаковые числа можно было складывать и вычитать с помощью беззнакового сумматора. Соответственно, операции сложения и вычитания не делятся на знаковые и беззнаковые. Команды из группы сложения и вычитания выставляют значения всех шести флагов состояния (
CF,PF,AF,ZF,SF,OF
соответственно результату, так что программист, понимая, какого рода числа он обрабатывает, может выбрать для анализа нужные флаги.
Числа, разрядность которых превышает разрядность системы, при необходи­мости можно складывать и вычитать по частям. Для этого вначале младшие части обрабатываются командами адресов —
adc/sbb
, учитывающие перенос из младшей части. Части могут иметь
add/sub
, затем к остальным в порядке возрастания
любую разрядность (в частности, шестибайтовые целые можно разбить на две части — четыре и два байта или на шесть однобайтовых), но логичнее использовать четырёхбайтовые части на тридцатидвухбитной системе и восьмибайтовые — на шестидесятичетырёхбитной.
)
196 Глава 5. Синтаксис и команды GNU Assembler x86
Команды целочисленной арифметики
Таблица 5.5
Команда Действие
inc dest Инкремент ++dest (dest = dest +1)
dec dest Декремент −−dest (dest = dest 1)
Сложение и вычитание
add src, dest Сложение dest += src (dest = dest + src)
adc src, dest Сложение с переносом из предыдущей части
dest += src + CF
sub src, dest Вычитание dest = src (dest = dest src)
cmp src, dest Вычитание dest src без изменения dest (сравнение dest
и src)
sbb src, dest Вычитание с переносом из предыдущей части
dest = src + CF
neg dest Изменение знака dest = −dest
Расчёт линейной комбинации
lea δ(r1,r2,σ), dreg dreg = r1+σ · r2+δ (не изменяет флагов)
Умножение и деление
mul srm Беззнаковое умножение D:A = A · srm (таблица 5.6)
imul srm Знаковое умножение D:A = A · srm (таблица 5.6)
imul srm, dreg Умножение dreg = srm (dreg = dreg · srm)
imul imm, srm, dreg Знаковое умножение dreg = imm · srm
div srm
idiv srm
Беззнаковое деление с остатком (таблица 5.6) Знаковое деление с остатком (таблица 5.6)
Масштабирование (битовый сдвиг)
shr times, dest Беззнаковое деление dest / =2
sar times, dest
Знаковое математическое деление предполагается неотрицательным)
shl times, dest
sal times, dest
Умножение dest =2
dest = dest +(src + CF )
dest = dest − (src + CF)
A =(D:A)/srm D =(D:A)%srm
A =(D:A)/srm D =(D:A)%srm
dest / =2
times
(остаток
times
times
Вычисление линейной комбинации регистров
Также для арифметических вычислений используется команда вычисления эффективного адреса
lea
, которая, в соответствии с возможностями косвенной
5.2. Основные команды 197
адресации, может рассчитать выражение
dreg = r1+σ · r2+δ
,где
dreg, r1,r2
— регистры,σ— константа 1, 2, 4 или 8,δ— произвольная тридцатидвухбитная константа (может быть опущен любой из регистров и любая из констант).
Команда телями), но смещениеδинтерпретируется как знаковое. Так как разрядность иr2совпадает с разрядностью результата
lea
предназначена для манипуляций с беззнаковыми данными (указа-
dreg
, результат совпадает со знаковым.
r1
В отличие от «настоящих» арифметических команд, lea не изменяет флагов.
Команды умножения и деления
Самые старые команды умножения рассчитывают произведение заданного мно­жителя
srm
на неявный операнд — регистрAтой же разрядности, что и
srm
Разрядность произведения при этом вдвое больше разрядности множителей, так что младшая половина жителя, а старшая — в регистр
A · srm
помещается в регистрAна место неявного мно-
D
той же разрядности. Исключением является случай с восьмибитными множителями — так как на момент появления команд умножения уже существовали шестнадцатибитные регистры, результат
al · srm
размещается вax(таблица 5.6). Старшая половина результата отличается для зна­ковой и беззнаковой интерпретации множителей, так что существуют две команды описанного действия — mul для беззнакового умножения и imul для знакового.
Команды умножения и деления неявного аргумента A
Таблица 5.6
Размер srm Действие [i]mul srm Действие [i]div srm
4 байта edx : eax = eax · srm
2 байта dx : ax = ax · srm
1 байт ax = al · srm
eax =(edx : eax)/srm edx =(edx : eax)%srm
ax =(dx : ax)/srm dx =(dx : ax)%srm
al = ax/srm ah = ax%srm
.
Введённая позже команда
imul srm, dreg
рассчитывает только младшую половину произведения, а она совпадает для знаковых и беззнаковых чисел. Та­ким образом, двухоперандную форму команды
imul
можно использовать и для
знакового, и для беззнакового умножения.
Трёхоперандная форма младшую половину произведения, но перед этим константа расширяется. Данная форма соответствует двум опкодам — с константой
imul imm, srm, dreg
также рассчитывает только
imm
при необходимости
imm
разрядность которой соответствует разрядности источника и приёмника (в этом случае расширение не требуется) и с восьмибитной константой
imm8[17
]. Во
,
198 Глава 5. Синтаксис и команды GNU Assembler x86
втором случае случае
imm8
расширяется как знаковое, поэтому трёхоперандную
форму imul следует считать командой знакового умножения.
Если произведение помещается в младшей половине произведения, все формы команд значащие биты (для двух- и трёхоперандной форм
mul/imul
сбрасывают оба флага
CFиOF
. Если в старшей половине есть
imul
это значит, что результат некорректен), оба этих флага взводятся [54]. Значения флагов нуля и знака не определены [78].
Для деления существуют только однооперандная форма. Делимое (неявный
srm
операнд) всегда вдвое больше делителя (явного операнда в паре регистров
D : A
(старшая половина — вD, младшая — вA), кроме случая
) и располагается
восьмибитного делителя (таблица 5.6). Необходимо помнить об этом и корректно инициализировать регистр D перед делением.
Таким образом, команды деления обратны однооперандной форме умножения.
Соответственно, деление также будет беззнаковым (div) и знаковым (idiv).
После деления
D : Aнаsrm
частное помещается на место младшей половины делимого (вA), остаток — на место старшей половины (вD). Если старшая по­ловина делимого содержит значащие биты, возможна ситуация, когда частное не помещается в отведённом для него регистре. Соответственно, результат деления будет некорректным.
Масштабирование (умножение и деление на 2)
Умножение и деление на
times
2
(
times
трактуется как беззнаковое число)
может также быть выполнено с помощью битовых сдвигов (раздел 5.2.5).
Умножение на
ние — беззнаковым сдвигом вправо (
times
2
выполняется сдвигом влево (
shr
shl/sal
), беззнаковое деле-
). Остаток при делении сдвигом не вычис­ляется (при делении сдвигом на2однобитовый остаток равенCF). Необходимо отметить, что знаковый сдвиг вправо соответствует математическому опреде­лению деления с остатком (остаток предполагается неотрицательным даже при dest < 0), а не тому, что реализовано в команде div (подробнее см. раздел 2.7.3).
Расширение целых чисел
Также к командам целочисленной арифметики можно отнести команды расши-
рения (таблица 5.7).
Современная система команд x86 включает два вида команд, которые исполь-
зуются для расширения — пересылка из источника малой разрядности в приёмник большей и удвоение разрядности неявного операнда в регистре A.
Существует две команды пересылки с расширением —
расширения (дополнения нулями) и
movs
для знакового (дополнения знаковым
movz
для беззнакового
5.2. Основные команды 199
Команды расширения (увеличения разрядности)
Таблица 5.7
Команда Действие
movz srm, dreg dreg = srm с беззнаковым расширением
(размер srm меньше dreg)
movs srm, dreg dreg = srm
cStD Знаковое расширение регистра A (таблица 5.8)
со знаковым расширением
(размер srm меньше dreg)
битом). Некоторые версии GCC, а также документация Intel, используют для них соответственно мнемоники movzx и movsx.
В отличие от простой пересылки
mov
, для команд пересылки с расширением приёмник может быть только регистром, источник — регистром или переменной в памяти.
При пересылке возможно увеличение разрядности более чем в два раза, поэто­му, если источник находится в памяти, для команды обязательно нужно указывать два суффикса (раздел 5.1.5).
Также существует набор команд для удвоения разрядности неявного операнда в регистре A (таблица 5.8).
Мнемоники команд знакового расширения A
Таблица 5.8
Размер
8 16 al ax cbw cbtw
16 32 ax eax cwde cwtl ax dx : ax cwd cwtd
32 64 eax rax cdqe cltq eax edx : eax cdq cltd
64 128 rax rdx : rax cqo cqto
Расширение в регистр A Расширение в пару D : A
Действие Intel AT&T Действие Intel AT&T
Практически для всех случаев есть две команды — расширениеAдоAвдвое большей разрядности и расширениеAдо пары димо использовать перед командами, использующими пару регистров
D : A
. Последний вариант необхо-
D : A
источник, в частности, командами деления (D : A)/srm.
Для команд удвоения разрядности регистраAесть два набора мнемоник — исторически используемые в ассемблере Unix (AT&T) и предложенные Intel.
Мнемоники AT&T для команд удвоения разрядности регистра A строятся по схеме
cStD
(convertStoD), гдеS— размер источника,D— размер приёмника
или символ d для пары регистров (кроме cqto).
как
200 Глава 5. Синтаксис и команды GNU Assembler x86
Мнемоники Intel построены по одной из двух схем —
cSD
или
cSDe
. Истори­чески на шестнадцатибитных машинах первыми доступными вариантами удвоения были
al axиax dx : ax
В дальнейшем суффикс
e
, так что они получили имена без суффиксаe.
использовался для расширения
A A
, а расширение
A D : A выполняется командой без суффикса.
Для команд удвоения разрядностиAGAS поддерживает оба набора мнемо-
ник — AT&T и Intel — перечисленные в таблице 5.8.
Увеличить разрядность неявного операндаAболее чем в два раза с помощью команд таблицы 5.8 можно только последовательным применением нескольких команд удвоения.
Все команды удвоения разрядностиAвыполняют знаковое расширение. Без­знаковое расширение
A D : A
может быть выполнено явным обнулением
регистра D.
5.2.5. Битовые операции
Она может двигать, Она может двигать собой, В полный рост — Она знает толк в полный рост
Б. Б. Гребенщиков. Она может двигать
Некоторые команды, предназначенные для обработки битовых строк, перечис­лены в таблице 5.9.
Система команд x86 включает поразрядные логические операции, все описан­ные в разделе 2.7 битовые сдвиги, а также команды выделения бита по номеру.
Поразрядные логические операции «и», «или», «не» и «исключающее или» изменяют в соответствии с полученным результатом три флага состояния — флаги нуля, знака и чётности.
Команды битового сдвига принимают два операнда: сдвигаемое значение и беззнаковое количество сдвигов
times
. Количество
times
может быть непосред-
dest
ственным значением или регистромcl, причём даже в шестидесятичетырёхбитных системах используются только младшие шесть его бит (в тридцатидвухбитных — пять).
При
times =1
происходит сдвиг в указанную сторону на один бит. Значение бита, вышедшего за разрядную сетку, после выполнения команды заносится вCF. Освободившаяся с другого конца
dest
ячейка инициализируется в соответствии
с видом сдвига.
При times > 1 однобитовый сдвиг повторяется times раз. Знаковый и беззнаковый сдвиги используется для быстрого умножения и деле-
ния на степени двойки. Сдвиги вправо эквивалентны соответственно знаковому
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]