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

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

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
4.3. Ассемблерные вставки в код C++ 171
Некоторые ограничения (типы), специфичные для i386 (определены в файле i386.h GCC)
— регистр, который можно адресовать по байтам младшего слова (
q
A, B, C, D
A — пара регистров D : A; a, b, c, d, S, D — регистры A, B, C, D, si, di соответственно; I..P — машинно-зависимые ограничения для констант:
I — непосредственное значение в диапазоне 0..31 (32-битный сдвиг);
J — непосредственное значение в диапазоне 0..63 (64-битный сдвиг);
— непосредственное значение в диапазоне 0..3 (сдвиги, которые может
M
выполнить lea);
— непосредственное значение в диапазоне 0..255 (беззнаковое однобайто-
N
вое).
4.3.5. Модификаторы параметров
Слышишь — капли там — Из обломанной ветки, да по губам, И кора мокра...
С. А. Калугин. Весна
Иногда в коде программы требуется подставить не значение параметра в неиз­менённом виде, а какую-либо его характеристику. В этом случае необходимо использовать так называемые модификаторы параметров (таблица 4.1).
Модификатор указывается между префиксом % и именем параметра.
В частности, при инициализации параметра
[dmem]
, находящегося в памяти, необходимо указать размер приёмника. В GCC это делается при помощи суффикса размера (раздел 5.1.5). Явное указание суффикса сделает программу неустойчивой к изменению типа переменной, передающейся как
[dmem]
. Если суффикс указан
с помощью модификатора z:
);
"mov%z[dmem]␣$0,␣%[dmem]\n"
данная команда будет корректно инициализировать переменную как типа
short
так и int или long long.
Печать константы без префикса$необходима, если эта константа используется не как непосредственный операнд команды, а как-то иначе. В частности, такая константа может быть частью адреса.
"movl␣$13,␣␣%c[FieldDisp](%[Struct])\n"
Приведённый фрагмент ассемблерной вставки инициализирует поле струк­туры, расположенной по адресу
[Struct]
. Смещение поля задано парамет-
ром [FieldDisp].
,
172 Глава 4. Связь уровней абстракции
Модификаторы параметров ассемблерных вставок GCC
Таблица 4.1
Модификатор Действие
z Печать суффикса команды для размера операнда
c Печать константы без префикса $
b Печать имени младшего байта регистра
(%al для регистра A)
h Печать имени старшего байта младшего слова регистра
(%ah для регистра A)
w Печать имени младшего слова регистра
(%ax для регистра A)
k Печать имени младшего двойного слова регистра
(%eax для регистра A)
q Печать 64-битного варианта имени регистра
(%rax для регистра A)
Модификаторы печати имени части регистра доступны только для параметров в регистрах, причём печать младшего байта и старшего байта младшего слова — только в тех, где эти байты можно адресовать. В тридцатидвухбитном режиме как младший байт, так и следующий за ним можно адресовать только для В шестидесятичетырёхбитном младший байт адресуется для всех шестнадцати регистров общего назначения с помощью префикса только для запрещает доступ к ahdh).
AD
, причём не в любой ситуации (использование префикса
REX
. Второй байт доступен
AD
REX
.
Контрольные вопросы
1. Какие стадии включает компиляция программы с помощью GCC?
2. Какое расширение имеет файл с исходным кодом на языке ассемблера?
3. Какое расширение имеет файл с исходным кодом на языке C++?
4. Как изменить имя выходного файла при сборке?
5. Как собрать программу, состоящую из нескольких модулей?
6. Каким ключевым словом открывается ассемблерная вставка?
7. Как из ассемблерной вставки обратиться к локальным переменным?
8.
Какие вы знаете ограничения на размещение параметров ассемблерных вста-
вок?
Глава 5. Синтаксис и команды GNU Assembler x86
А потому, после того, как будешь свободен, будь деятелен.
Коран. 94.7
Язык ассемблера — простейший символический язык программирования, каж­дая команда которого транслируется в одну команду машинного языка. Также существуют операторы ассемблера, не соответствующие машинным командам — директивы, комментарии, пустые операторы.
Набор команд ассемблера определяется как архитектурой используемого ком­пьютера, так и собственно ассемблером — транслятором с символического языка в объектный код. Команды имеют текстовые мнемонические обозначения (мне­моники). При этом одной и той же мнемонике может соответствовать несколько опкодов, выполняющих схожие действия над операндами, расположенными в раз­ных местах или разного размера. Часто существует один опкод для восьмибитных операндов и другой — для операндов, имеющих размер 32, 64 или 16 бит (для него разрядность операндов определяется текущим режимом и префиксами). Таким образом, иногда команды обрабатывают восьмибитные операнды немного иначе, чем любые другие. Кроме того, одному опкоду может соответствовать несколько разных мнемонических обозначений.
В данной главе описывается часть набора команд GNU Assembler (GAS) для архитектуры x86 с использованием традиционного синтаксиса AT&T, а также особенности этого синтаксиса.
5.1. Особенности GNU Assembler
А тот, кто сторожит баржу, спесив
И вообще не святой;
Но тот, кто сторожит баржу, красив
Неземной красотой.
Б. Б. Гребенщиков. Стерегущий баржу
GAS, как и его предок, ассемблер Unix as, использует так называемый син­таксис AT&T System V/386, часто называемый просто синтаксисом AT&T или синтаксисом GAS [8, 44].
Также для процессоров семейства Intel x86 часто используется синтаксис, пред­ложенный фирмой Intel. Основными отличиями синтаксиса Intel от AT&T считают­ся обратный порядок операндов, другие обозначения адресации и невозможность явного указания разрядности операции. Менее известно различие в мнемонических
174 Глава 5. Синтаксис и команды GNU Assembler x86
обозначениях команд. Современные версии ассемблера GAS поддерживают оба варианта синтаксиса.
Основными недостатками синтаксиса Intel является неоднозначность и труд­ность чтения инструкций. Кроме того, синтаксис Intel используется только для процессоров Intel или совместимых с ними.
Синтаксис AT&T иногда называется кроссплатформенным, так как GCC и, соответственно, GAS реализован для множества различных архитектур. Полной кроссплатформенности при использовании языка ассемблера достичь невозможно, так как у каждой платформы свой набор команд, регистров и методов адресации, но использование схожего синтаксиса облегчает переход между ними.
Также инструкции, записанные в соответствии с синтаксисом AT&T, легче читаются. Даже обозначение адреса в памяти, которое вначале кажется контрин­туитивным, распознаётся однозначно. Косвенный адрес в памяти, записанный по правилам синтаксиса AT&T, невозможно перепутать с непосредственным значени­ем, что происходит в диалектах Intel.
5.1.1. Общие правила
И малое замкнулось на великом, И Млечный Путь раскрылся для меня!
С. А. Калугин. Млечный путь
Так как GAS в основном используется на одном из этапов компиляции про­граммы на C/C++, многие синтаксические конструкции GAS и C/C++ совпадают.
В программе могут использоваться латинские буквы, цифры, а также нижнее подчёркивание и точка. Допустимые пробельные символы — пробел и табуляция; они могут сочетаться в любом порядке. Перевод строки является разделителем операторов.
Допускаются многострочные комментарии /* в стиле C */ и однострочные // в стиле C++. Также для различных платформ поддерживаются платформо­специфичные виды однострочных комментариев. В частности, для x86, кроме однострочного комментария в стиле C++, поддерживается символ комментария#, но он считается устаревшим.
Оператор ассемблера целиком размещается на одной строке. В начале строки может быть одна или несколько меток, заканчивающихся двоеточием. Если первый символ оператора — точка, то это — директива ассемблера (первая строка листин­га 5.1). Набор основных директив совпадает для всех архитектур, но для многих платформ есть и специфичные (в частности, директивы определения данных).
Листинг 5.1. Директива и команда
1 the_label: .directive ...
5.1. Особенности GNU Assembler 175
2 another_label: // Пустой оператор 3 instruction operand_1, operand_2, ...
Пустой оператор может состоять только из пробельных символов или быть пустой строкой (вторая строка). Оператор, начинающийся с буквы, представляет собой мнемоническое обозначение машинной команды, за которым при необходимости следуют операнды, разделённые запятыми (третья строка листинга 5.1).
Строковые литералы ограничиваются двойными кавычками, экранирующим символом является обратный слеш «\», спецсимволы кодируются аналогично C/C++. Числовые литералы также описываются аналогично C/C++.
5.1.2. Основные директивы
Кто море удержал брегами И бездне положил предел, И ей свирепыми волнами Стремиться дале не велел?
М. В. Ломоносов. Ода, выбранная из Иова
Директива ассемблера не соответствует никакой машинной команде. Рассмот­рим несколько наиболее употребительных директив; их можно разбить на несколь­ко классов.
Директивы определения секций
Как было описано в разделе 3.2, код программы и различные виды данных должны располагаться в различных диапазонах адресного пространства. По исто­рическим причинам эти диапазоны называются сегментами (соответствующие фраг­менты исходного кода и исполняемого файла — секциями). Начало содержимого того или иного сегмента в исходном ассемблерном коде отмечается специальными директивами.
С начала файла до первой директивы располагается сегмент кода. Указать продолжение сегмента кода можно директивой .text.
Сегмент данных открывается директивой
.data
. В принципе, описание ста­тических данных в сегменте кода не вызовет ошибки, но такие данные будет невозможно модифицировать, так как сегмент кода защищён от изменений.
Для сегментов стека и кучи нет соответствующих секций, они заполняются
динамически в процессе выполнения программы.
Директивы определения данных
В сегменте
.data
статические данные описываются также с помощью директив.
176 Глава 5. Синтаксис и команды GNU Assembler x86
После директивы определения данных указывается литерал подходящего типа или несколько литералов, перечисленных через запятую. В памяти соответственно резервируется одна или несколько ячеек соответствующего размера, которые инициализируются указанными значениями.
Для того, чтобы дать адресу ячейки имя, перед соответствующим определени­ем необходимо поставить метку (листинг 5.2).
Листинг 5.2. Определение статических данных
1 foo: .long 0, 1, 2 2 bar: .double -8.7
Важно помнить, что ассемблер, в отличие от языков высокого уровня, не является типизированным. Таким образом, если, в частности, по адресу восьмибайтовое значение двойной точности
8,7
, а программист обратится к нему
bar
расположено
как к числу одинарной точности (четыре байта), это не вызовет сообщения об ошибке, но прочитанное значение будет другим.
Для инициализации памяти целыми значениями различного размера использу­ются следующие директивы:
.byte
— однобайтовое (восьмибитное) целое,
.short
шестнадцатибитное, .long — тридцатидвухбитное, .quad — шестидесятичетырёх­битное. Размер и порядок байтов определяются платформой; приведены размеры для x86 (они же наиболее распространённые). Также существуют директивы и
.int
, для x86 определяющие шестнадцати- и тридцатидвухбитные целые соответ-
.word
ственно.
Значения с плавающей запятой одинарной (32 бита) и двойной (64 бита) точно­сти описываются директивами .float (.single) и .double.
Для инициализации памяти строковыми константами различного вида исполь­зуются директивы .string (.asciz) и .ascii.
Функции стандартной библиотеки C используют строки, завершающиеся нулём; их можно описать директивой .string (.asciz) (листинг 5.3).
Листинг 5.3. Определение строки, завершающейся нулём
1 msg: .string "Hello,␣world!\n"
Если после директивы указывается несколько строковых литералов через запятую, завершающий ноль добавляется после каждого.
Строка без завершающего нуля описывается директивой .ascii (листинг 5.4).
Листинг 5.4. Определение строки без завершающего нуля
1 msg: 2 .ascii "Hello,␣world!\n" 3 len = . - msg // символу len присваивается длина строки
5.1. Особенности GNU Assembler 177
Для обработки подобных строк нужно знать их длину (её нельзя определить, анализируя содержимое памяти). Для этого используется специальный символ
«.» — адрес текущего оператора (в том числе ячейки с данными).
Директивы определения данных точно так же сработают и будучи размещён­ными в сегменте кода (
.text
или неименованная секция в начале программы), но такое размещение будет ошибкой. Если данные попадут во фрагмент кода, кото­рый выполняется, они будут интерпретированы как команды, что, скорее всего, приведёт к сбою при декодировании. Даже если данные находятся в той части кода, которая не получает управления, их будет невозможно модифицировать.
Прочие директивы
Парные директивы
.rept ....endr
соответствуют повторению фрагмента между
ними, в частности
Листинг 5.5. Директива .rept
1 .rept 4 2 .long 0 3 .endr
интерпретируется как четырёхкратное повторение оператора .long 0:
Листинг 5.6. Эквивалентное описание данных без использования .rept
1 .long 0 2 .long 0 3 .long 0 4 .long 0
Директивы
.rept ... .endr
могут применяться и к командам. При этом проис­ходит именно многократное дублирование фрагмента кода в исполняемом файле, а не циклическое повторение одной и той же его копии.
Директива
.globl (.global)
делает символ доступным компоновщику (видимым
из других модулей).
Листинг 5.7. Перевод символа main в глобальную область видимости
1 .globl main
Переменные и функции, имена которых сделаны общедоступными при помощи директивы
.globl
или
.global
(возможны оба написания), могут быть импорти-
рованы, в частности, в модуль на C++ с помощью ключевого слова extern.
Парные директивы
.func ....endfunc
включают в исполняемый файл отладоч-
ную информацию о функции (только при сборке в отладочном режиме).
178 Глава 5. Синтаксис и команды GNU Assembler x86
Таким образом, если в файле несколько функций, можно указать для отладчика
их границы.
Листинг 5.8. Границы функции sqr()
1 .globl sqr 2 .func sqr 3 sqr: 4 movl 4(%esp), %eax 5 imull %eax, %eax 6 ret 7 .endfunc
Директивы
.func ... .endfunc
не влияют ни на что, кроме отладочной инфор-
мации. В режиме Release игнорируются.
5.1.3. Порядок операндов
Путь-дорога, господа! Вы откуда и куда?
П. П. Ершов. Конёк-горбунок
В GAS принят порядок записи операндов слева направо, следуя европейскому
направлению письма. Соответственно, инструкция GAS обычно имеет вид:
1 mnemonic source, destination
то есть вначале указывается источник, затем приёмник.
Для команд с тремя операндами (один из них в x86 — всегда непосредственное значение) вначале записывается непосредственное значение, затем источник, затем приёмник.
1 mnemonic immediate, source, destination
Если среди операндов нет приёмника (в частности, команды с двумя непосред­ственными операндами, такие как
enter
), порядок в AT&T совпадает с порядком,
указанным в документации Intel [8, 73].
При этом, если команда принимает операнды в регистрах или памяти, но не модифицирует их (в частности, команды сравнения
cmp
или выделения битаbt), в большинстве случаев один из операндов всё равно считается приёмником. Каждая из приведённых команд входит в семейство, большая часть команд которого модифицирует этот операнд.
В частности, рассмотрим одну из наиболее употребительных мнемоник ассем-
блера — команду пересылки (копирования)
mov
. Она соответствует оператору
присваивания языков высокого уровня. Её операнды — источник (обозначим его
5.1. Особенности GNU Assembler 179
src
) и приёмник (
mov dest, src
вид
mov src, dest
dest
). В синтаксисе Intel пересылка
dest = src
имела бы вид
; в используемом в данном пособии синтаксисе AT&T она имеет
. Например, команда
movb $0x05, %al
помещает значение 5
в регистр al.
5.1.4. Адресация операндов
Поскоблите язык — и вы увидите пространство и его шкуру.
В. Хлебников. Зангези
Параметры команд ассемблера, в отличие от операндов ЯВУ, не могут быть произвольными выражениями. В разделе 3.6.1 были описаны различные виды ад­ресации. Рассмотрим обозначения, принятые в GAS для методов адресации явно передаваемых параметров команд x86.
1. Непосредственная
— константа, значение которой при компиляции непосред­ственно включается в код команды (адрес глобальной переменной или выра­жение, вычисляемое на этапе компиляции, также является непосредственным значением). Непосредственные операнды отмечаются префиксом$. Например,$0,
$0xFFFFFFFF,$(0 + 1 + 2*2 - 7/4)
(значение, равное4),$n(адрес гло-
$13
бальной переменной n).
2. Прямая
(абсолютная) — переменная в памяти по фиксированному адресу
(статическая или глобальная), адрес при компиляции также включается в код
команды. Операнды, описываемые статическим адресом в памяти, не имеют префик-
0
са. Например,
(вызовет ошибку чтения по нулевому адресу),n(значение
глобальной переменной n).
3. Прямая относительная
, также
rip
-relative или PC-relative, от program counter — переменная или функция располагается в памяти по фиксированному адресу, но в команду включается не сам адрес, а его смещение относительно указателя команд ip. Для адресов в коде (функций, меток для перехода) неявно применяется по
умолчанию как в тридцатидвухбитном, так и в шестидесятичетырёхбитном
режиме. Операнд не имеет префикса и в ассемблером коде выглядит так же, как и прямой абсолютный адрес: call func, jmp label. В шестидесятичетырёхбитном режиме появилась возможность использовать прямую относительную адресацию и для данных. Операнд выглядит как ба­зовый адрес со смещением, где базой явно задан указатель команд:
(здесь
msg
msg(%rip), %rsi
ресация используется только для базового регистра
— метка в секции данных). Относительная ад-
rip
. Аналогичная запись
lea
,
180 Глава 5. Синтаксис и команды GNU Assembler x86
с другой базой соответствует косвенной адресации и использованию абсолют­ного, а не относительного адреса msg.
4. Регистровая
— переменная в регистре, в команду при компиляции включается имя (номер) регистра. Операнды в регистрах отмечаются префиксом%. Например,
%eax,%dh,%bp
%rsi, %r13.
5. Косвенно-регистровая (косвенная)
— переменная в памяти, указатель на неё в регистре (или наборе регистров). Например, ние относительно адреса, хранимого в регистре:
(%ebp)
— значение в памяти по адресу
4(%esp)
ebp
. Можно указать смеще-
— значение в памяти
по адресу esp +4. В x86-совместимых процессорах для косвенной адресации можно использо­вать до двух регистров и, кроме того, до двух констант. Таким образом, для вычисления адреса используется до четырёх параметров:
— значение в памяти по адресу
8)
отсутствовать:
(%edx, %esi, 8)
ebp +8edi +4.
Часть параметров может
— значение по адресу
4(%ebp, %edi,
edx +8esi,(%edx,
%esi) — по адресу edx + esi, (,%esi, 8) — по адресу 8esi.
Компоненты адреса
В языке ассемблера x86 конструкция косвенной адресации включает в себя
вычисление адреса и его разыменование (частичным аналогом может быть опе­ратор обращения к элементу массива на ЯВУ —[], а для сокращённых форм — разыменование указателя —*, но при этом косвенная адресация — более сложный и гибкий механизм).
Как было сказано в разделе 3.6.3, команда x86 может содержать до четы-
рёх полей, задающих адрес — номер базового регистра регистра
Index
, показатель масштаба индекcного регистра
Base
, номер индексного
Scale
и смещение
Displacement.
Используя для обозначения разыменования∗, как в С++, результат вычисле-
ния адреса с разыменованием в полной форме (с четырьмя параметрами) можно записать как
∗Base + σ · Index+ Displacement
(5.1)
,
где
BaseиIndex
ответствующей платформы),
σ
— натуральное число (масштабный коэффициент — степень двойки
чём только
— значения соответствующих регистров (32-разрядные для со-
Displacement
1, 2, 4
или8из-за размера соответствующего поля в теле команды).
— целое знаковое число (смещение),
Scale
2
, при-
Одно или оба числовых значения, а также любой из регистров могут быть опущены (если не указан масштаб, используется
σ =1
, вместо остальных пропущенных
параметров используется 0).
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]