Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Архитектура вычислительных систем и Ассемблер с приложением методических указаний к лабораторным работам. Учебное пособие
.pdf
Контрольные вопросы 101
В отдельных источниках мантисса и вовсе рассматривается как целое беззнаковое число
на (2.104) и (2.105) при соответствующей коррекции порядка и смещения.
m1m2m3m4...mn[87]. Такая трактовка также допустима и равносиль-
Контрольные вопросы
1. Чем различаются качественные и количественные данные?
2. Какие числа называются натуральными?
3. Какие числа называются неотрицательными целыми?
4. Какие нецифровые символы используются в представлении чисел?
Какие способы представления беззнаковых целых чисел используются в ЭВМ?
5.
6. Какие способы представления знаковых целых чисел используются в ЭВМ?
7. Какие логические и битовые операции вы знаете?
8. Какие способы представления вещественных чисел используются в ЭВМ?
9. Как выглядит нормализованное представление вещественного числа?

Глава 3. Архитектура команд семейства x86
Знающий сокровенное и явное, силён, мудр.
Коран. 64.18
Обозначением x86 описывают целый класс вычислительных систем, включаю-
щий уже практически не используемую шестнадцатибитную архитектуру (8086–
i286), тридцатидвухбитную архитектуру IA-32 (i386–i686), шестидесятичетырёхбитную x86-64 (amd64, Intel 64 или IA-32e).
Все модели этого многочисленного семейства совместимы между собой на
уровне архитектуры команд, то есть все современные процессоры в определённом
режиме теоретически могут выполнять программы, написанные для более старых
(тем не менее из-за особенностей современных операционных систем, а также
сильно изменившихся временн
´
ых характеристик процессоров на практике чаще
используются эмуляторы).
Несовместимая с набором команд x86 архитектура IA-64 (Itanium) не рассмат-
ривается в данной книге.
В данной главе рассматриваются режимы работы x86-совместимых процессо-
ров, доступные регистры, флаги, режимы адресации, а также структура команды
и вытекающие из неё ограничения.
3.1. Развитие линейки x86 и режимы работы
В отношении деятельности опыт, по-видимому, ничем
не отличается от искусства; мало того, мы видим,
что имеющие опыт преуспевают больше, нежели те,
кто обладает отвлечённым знанием, но не имеет опыта.
Аристотель. Метафизика
В данном пособии рассматривается тридцатидвухбитный и шестидесятичеты-
рёхбитный режимы работы, как более простые для прикладного программирования
и более распространённые в настоящее время. Подробное описание качественно
отличного от них шестнадцатибитного режима можно найти, в частности, у Питера
Абеля [26].
Тем не менее, так как многие особенности архитектуры x86 обусловлены
исторически и поддерживаются для совместимости, необходимо сделать краткий
экскурс в историю данной линейки.

3.1. Развитие линейки x86 и режимы работы 103
3.1.1. История семейства x86
Я родился в таможне,
Когда я выпал на пол.
Мой отец был торговец,
Другой отец — Интерпол...
Б. Б. Гребенщиков. Таможенный блюз
Архитектура x86 основана на архитектуре четырёхразрядного микропроцес-
сора Intel 4004. Так как микросхема 4004 была разработана для настольного
калькулятора, в ней не были реализованы многие механизмы, давно и успешно
применявшиеся в более ранних компьютерах, в частности, аппаратная трансляция адресов. Позже была выпущена улучшенная версия 4004 — процессор 4040,
а на его основе был разработан восьмиразрядный 8008, включавший два восьмибитных регистра общего назначения
семь —
a, b, c, d, e, h, l
, объединявшиеся в три пары
была шестнадцатиразрядной (то есть можно было адресовать до
aиb
. В улучшенном 8080 их было уже
bc, de, hl
. Шина адреса в 8080
216байт, или 64
килобайта), адрес задавался парой регистров hl.
Шестнадцатибитные процессоры
Процессор 8086 — родоначальник семейства x86 — был шестнадцатибитным,
из-за чего 16 бит при программировании для x86 обычно называют
включал четыре шестнадцатибитных регистра общего назначения
каждый из которых фактически был парой восьмибитных (в частности,
al
), и четыре неделимых шестнадцатибитных регистраbp,sp,si,di. У каждого
словом.
Он
ax,bx,cx,dx
ax = ah :
из них было и специальное назначение:A— accumulator (неявный аргумент большинства команд),C— counter (счётчик),D— data (данные),B— base (базовый
регистр). В отличие от 32-разрядного режима, невозможно было использовать
в косвенной адресации любые регистры. Для задания адреса в памяти использовались только три бита поля
R/M
и поле смещения (раздел 3.6.2). Базовыми могли
быть толькоbxиbp(base pointer), индексными — толькоsiиdi(source index
и destination index). Масштабирование индекса не использовалось.
Шина адреса при этом была двадцатиразрядной. Для того, чтобы адресовать
220байт (один мегабайт) памяти шестнадцатибитными адресами, была введена
сегментная модель памяти.
Полный адрес складывался из шестнадцатибитного
адреса и значения специального сегментного регистра, умноженного на 16.
Область памяти, адресуемая с помощью одного сегментного регистра, называ-
лась сегментом. Сегмент занимал
216байт, то есть 64 килобайта; разные сегменты
могли пересекаться или полностью совпадать. В 8086 было четыре сегментных
регистра, соответственно в программе использовалось четыре сегмента:
,

104 Глава 3. Архитектура команд семейства x86
– cs
(code segment) — сегмент кода; значение регистраcsдобавлялось к адресам
команд;
(data segment) — сегмент данных, его значение добавлялось к адресам ста-
– ds
тических переменных;
(extra segment) — дополнительный сегмент данных, иногда там располага-
– es
лась куча;
– ss (stack segment) — сегмент стека, добавлялся к адресам в стеке.
Добавляемый сегментный регистр определялся процессором автоматически; для
данных при необходимости можно было использовать префикс замены сегмента.
Хотя в настоящее время используется плоская модель памяти (сегментные
регистры присутствуют, но содержат другую структуру данных — селектор сегмента) по традиции области адресного пространства, где располагаются код, данные,
стек и т. д., часто называются сегментами.
Так как четырёх сегментов по 64 килобайта часто не хватало, программисту
приходилось изменять значения сегментных регистров во время работы программы
для доступа к различным областям памяти. Управлять сегментами приходилось
вручную.
В 8086 всё ещё не было механизма трансляции адресов, так что прикладные
программы использовали реальные физические адреса ОЗУ; из-за этого режим
совместимости с моделью памяти 8086 в более поздних процессорах называет-
реальным режимом.
ся
При этом каждой программе реального режима была
доступна вся память компьютера, что не позволяло реализовать полноценную
многозадачность.
Для 8086 был разработан математический сопроцессор 8087, предназначенный
для вычислений с плавающей запятой. Сопроцессор устанавливался в отдельный
сокет на материнской плате. Начиная с этой модели, стали выпускаться урезанные
варианты процессоров. Так, 8086 с восьмибитной шиной данных получил название
8088.
На основе 8088 был построен компьютер IBM PC, так что большинство последующих процессоров Intel (и неинтеловских x86-совместимых процессоров)
совместимы с 8086 на уровне машинного кода. Теоретически любой современный
персональный компьютер можно загрузить в специальном режиме совместимости
и выполнить программу, написанную для 8086. Практически с этим возникнут
трудности, в частности, из-за несоответствия временн´ых характеристик.
Непосредственно следующая модель, 80186, отличалась от 8086 незначительно.
В 80286 появилась частичная поддержка
защищённого режима,
когда память
разных программ изолирована (защищена) друг от друга за счёт аппаратной трансляции адресов. Шина адреса была увеличена до 24 разрядов.

3.1. Развитие линейки x86 и режимы работы 105
Тридцатидвухбитные и шестидесятичетырёхбитные процессоры
Полноценная реализация защищённого режима появилась в тридцатидвухбитном процессоре 80386 (часто называемом просто 386). Так как разрядность процессора сравнялась с разрядностью шины адреса, в защищённом режиме 386 используется плоская модель памяти. Количество сегментных регистров возросло
до шести. При этом сегментные регистры защищённого режима содержат не часть
адреса, а селектор, кодирующий ссылку на запись в специальной таблице дескрипторов, которая, в свою очередь, задаёт границы сегмента в плоском адресном
пространстве и атрибуты защиты.
Современные операционные системы используют именно защищённый режим
процессора (либо очень схожий с ним шестидесятичетырёхбитный режим), в котором прикладной программе недоступны многие функции реального. При этом, так
как эти функции прозрачно обеспечиваются операционной системой, прикладное
программирование заметно упрощено.
Тем не менее, из-за используемого программного обеспечения даже более поздние модели (до Pentium 4) постоянно или часть времени работали в реальном
режиме, так что многие учебники ассемблера описывают его наравне с защищённым.
В целом разработка 386 — наиболее существенный шаг в развитии архитектуры семейства x86. В настоящее время «x86» обозначает, как правило, 386совместимый процессор (такая архитектура обозначается i386 или IA-32). Дальнейшее развитие в основном сводилось к добавлению новых команд, наращиванию
параллелизма и увеличению частоты.
В определённый момент четырёх гигабайт памяти, адресуемых 32-битным
указателем в плоской модели, оказалось недостаточно. В первую очередь это
проявилось на серверах и специализированных высокопроизводительных рабочих станциях. Разработчики ПО и аппаратного обеспечения не стали возрождать
неудобную сегментную модель памяти, вместо этого начали продвигаться решения
с 64-битными виртуальными адресами.
Результатом совместной разработки Intel и Hewlett Packard стала архитектура
IA-64, схожая с суперкомпьютером Эльбрус и свободная от недостатков, унаследованных от калькулятора 4004 и шестнадцатибитного 8086. IA-64 несовместима
с набором команд x86. Она не получила популярности в основном из-за недостаточного количества портированного под неё ПО и несовершенства компиляторов,
а также дороговизны и некоторых конструктивных недоработок воплощавших её
процессоров Itanium. В мае 2017 г. официально объявили о закрытии этой линейки.
Ведущий конкурент Intel, компания AMD, предложила расширение архитектуры IA-32, увеличивающее разрядность адресов до 64 бит и дающее возможность
увеличить разрядность данных с помощью специального префикса
REX
. Имен-
но это расширение, которое в настоящее время поддерживается и процессорами

106 Глава 3. Архитектура команд семейства x86
Intel — наиболее популярный способ увеличить адресуемую память персонального
компьютера.
Разработанная компанией AMD шестидесятичетырёхбитная архитектура x8664 (также называемая amd64, IA-32e и Intel 64, но не IA-64) не слишком существенно отличается от тридцатидвухбитной x86.
3.1.2. Режимы работы процессора
У меня есть две фазы, мама,
Я — чистый бухарский эмир.
Когда я трезв, я — Муму и Герасим, мама;
А так я — Война и Мир.
Б. Б. Гребенщиков. Таможенный блюз
Процессоры тридцатидвухбитной архитектуры x86 (IA-32) поддерживают четыре режима работы (рис. 3.1) [2, 16]:
– шестнадцатибитный реальный режим (Real Mode);
шестнадцатибитный режим виртуального 8086 (Virtual-8086 Mode), используе-
–
мый тридцатидвухбитными операционными системами для запуска устаревших
программ;
– тридцатидвухбитный защищённый режим (Protected Mode);
режим системного управления (System Management Mode, SMM) — сверхпри-
–
вилегированный режим, в котором обрабатываются скрытые от операционной
системы события.
Процессоры семейства x86-64 добавляют к ним ещё два режима, в совокупности обозначаемые в документации [2] как Long Mode (на рис. 3.1 эти два режима
объединены серым овалом вверху рисунка):
– тридцатидвухбитный режим совместимости;
– шестидесятичетырёхбитный режим.
Шестидесятичетырёхбитный режим использует по умолчанию тридцатидвухбитные данные и смещения и концептуально отличается от тридцатидвухбитного
режима гораздо меньше, чем шестнадцатибитный. Соответственно, его иногда
называют шестидесятичетырёхбитным расширением защищённого режима, а архитектуру x86-64 обозначают как IA-32e.
Порядок переключения режимов
При загрузке или сбросе процессор переходит в реальный режим. Из реального режима он может переключиться в защищённый, в котором работают все
тридцатидвухбитные операционные системы. При необходимости выполнения
шестнадцатибитного кода процессор может временно переключаться в режим
виртуального 8086.

3.1. Развитие линейки x86 и режимы работы 107
SMI# RSM
Режим
системного
управления
64-битный
П
е
р
режим
efer.LME =1,
cr4.P AE =1,
затем
cr0.P G =1
SMI# RSM
а
к
з
у
р
г
cr0.P E =1
а
з
е
р
е
П
а
к
з
у
р
г
а
з
е
cs.L =0
cs.L =1
cs.L =0
cr0.P G =0,
efer.LME =0
Защищённый
режим
cr0.P E =0
Реальный
режим
32-битный
режим совме-
стимости
затем
flags.V M =1
flags.V M =0
П
SMI#
RSM
SMI#
RSM
SMI# RSM
Режим
виртуального
8086
а
к
з
у
р
г
а
з
е
р
е
а
к
з
у
р
г
а
з
е
р
е
П
Рис. 3.1. Режимы работы современных процессоров
Из тридцатидвухбитного защищённого режима процессор может переключиться в тридцатидвухбитный режим совместимости, а затем в шестидесятичетырёхбитный режим. Эти два режима используются шестидесятичетырёхбитными
операционными системами. Таким образом, для выполнения тридцатидвухбитных
приложений процессор может временно переключаться обратно в режим совместимости. Выполнить шестнадцатибитное приложение в шестидесятичетырёхбитной
операционной системе невозможно — необходимо использовать эмулятор или
перезагрузить компьютер под другой операционной системой.
На рис. 3.1 переключения режимов, возможные в процессе работы, показаны
сплошными стрелками, переключения по перезагрузке — пунктиром.

108 Глава 3. Архитектура команд семейства x86
Из любого из этих режимов процессор при возникновении соответствующего события может ненадолго перейти в режим системного управления, а после
обработки события — вернуться в исходный режим.
В принципе, возможен переход из шестидесятичетырёхбитного режима через
режим совместимости в защищённый и из защищённого в реальный, но, как правило,
подобной необходимости не возникает и в современных операционных системах
она не реализована. Прикладная программа, запущенная в защищённом режиме, не
может перевести процессор в реальный (или, соответственно, из шестидесятичетырёхбитного в защищённый).
3.2. Сегменты памяти
Пара двух точек, разделённая растущим пространством.
В. Хлебников. Царапина по небу
В памяти вычислительной машины фон-неймановской архитектуры хранится
как код программы, так и данные.
Данные (переменные) в программе на языке высокого уровня, в частности, C++,
делятся на:
–
глобальные, время жизни которых равно времени жизни программы, а имя
доступно в любой области программы;
статические локальные, время жизни которых также равно времени жизни
–
программы, но имя доступно только в ограниченной области;
локальные — каждый вызов функции порождает новую копию переменной, вре-
–
мя жизни которой не превышает времени работы функции и которая доступна
только в ограниченной области;
динамические — память выделяется и освобождается с помощью операторов
–
new/delete или функций malloc()/f ree().
Различные виды переменных и программы находятся в разных областях диапазона доступных виртуальных адресов — адресного пространства процесса, исторически называемых
«секция»,
чтобы подчеркнуть использование плоской модели памяти и связь обла-
стей памяти с секциями исполняемого файла).
На рис. 3.2 представлено возможное распределение виртуальных адресов процесса в тридцатидвухбитной операционной системе GNU/Linux.
Конкретное расположение сегментов в адресном пространстве процесса может
различаться для различных операционных систем. Состав также может различаться,
но основные сегменты — кода, данных, кучи и стека — присутствуют всегда.
Хотя все сегменты располагаются в одном адресном пространстве, они могут
иметь разные атрибуты защиты. В частности, сегмент кода для предотвращения
сегментами
(сейчас иногда используется также термин

3.2. Сегменты памяти 109
Ядро операционной системы
1Гб
3Гб
Прикладные программы не могут ни читать,
ни перезаписывать память в данном диапазоне
Файлы, отображаемые в память
в том числе динамические библиотеки
Динамические переменные
(в частности, new char[80])
Ненициализированные
глобальные и статические переменные
(в частности, static char* s;)
Сегмент данных (Data)
Инициализированные
глобальные и статические переменные
(в частности, static char* s = "test";)
Сегмент кода (Text)
Двоичный код процесса (в частности,
Стек
Куча
BSS
/bin/sl
0x FFFF FFFF
0x C000 0000 == TASK_SIZE
Предельный
размер
стека
RLIMIT_STACK
Случайные смещения
для предотвращения
хакерских атак
на соответствующие
сегменты
program break
brk
start_brk
end_data
start_data
end_code
)
0x 0804 8000
0 == NULL
Смещение для
предотвращения
разыменования
нулевых
и некорректных
указателей
Рис. 3.2. Распределение памяти процесса в тридцатидвухбитной
операционной системе GNU/Linux
вредоносных модификаций доступен только для чтения, но не для записи, а для
сегментов, доступных программе на запись (данные, стек, куча) запрещено исполнение.

110 Глава 3. Архитектура команд семейства x86
Нулевой адрес и ближайшие к нему считаются некорректными для выявления
и предотвращения ошибок (разыменования переменных, которые указателями не
являются).
Кроме того, между динамически растущими сегментами (данных и кучи, кучи
и стека, стека и пространства ядра) добавляются «зазоры» случайного размера для
затруднения атак на соответствующие области памяти. Размеры этих смещений
определяются при загрузке программы в память.
Часть адресного пространства процесса (в тридцатидвухбитных системах по
умолчанию 1 Гб, в шестидесятичетырёхбитных — 512 Гб) занимает ядро операционной системы [75].
3.2.1. Код и статические данные
Центральная станция всех явлений,
путаница штепселей, рычагов и ручек.
В. В. Маяковский. Человек
Код выполняемой программы находится в сегменте кода.
Глобальные переменные программы, доступные в любой её точке и статические
переменные, отличающиеся от глобальных только областью видимости, расположены в
были инициализированы при объявлении, отделяются в специальный
сегменте данных.
Те глобальные и статические переменные, которые не
сегмент BSS.
Адреса глобальных и статических переменных в программе — неотрицательные
целые константы. Для адресов в коде (в частности, функций) возможно задать как
фиксированный адрес, так и смещение относительно текущего значения указателя
команд ip.
Размеры кода программы и переменных, время жизни которых совпадает со временем жизни программы, могут быть определены ещё на этапе загрузки программы
в память, поэтому размеры соответствующих областей памяти постоянны.
При этом адрес, по которому могут быть загружены код и статические данные,
в принципе может варьироваться (это особенно актуально для разделяемых библиотек). В этом случае требуется каким-то образом сохранить работоспособность
программы и доступность данных.
Для кода программы это достигается использованием для кода относительных
адресов (так называемых команд ближнего перехода, содержащих не сам адрес,
а его смещение относительно текущего значения указателя команд ip).
Для данных в тридцатидвухбитном режиме адресация относительноipневозможна. Соответственно, используются два варианта. Первый — коррекция фиксированных адресов в программе при загрузке исполняемого файла в память (это
мешает совместному использованию библиотек, так как, если несколько программ
попытаются загрузить библиотеку по разным адресам, получится разный код). Вто-
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
