Какие регистры относятся к регистрам общего назначения процессоров 80x86
Процессор Intel x86 использует архитектуру набора сложных инструкций SET Computer (ЦИСК), что означает, что вместо большого количества регистров общего назначения используется небольшое количество специальных регистров. Это также означает, что сложные инструкции специального назначения будут предоминате.
Microsoft Win32 использует процессор x86 в 32-разрядном плоском режиме. Эта документация будет сосредоточена только на плоском режиме.
Архитектура x86 состоит из следующих непривилегированных регистров целых чисел.
EAX
EBX
ecx
EDX
Регистр данных — можно использовать для доступа к портам ввода-вывода и арифметическим функциям.
ESI
Регистр исходного индекса
EDI
Регистр индекса назначения
ebp
Регистр базового указателя
инструмент
политике
Младшие 16 бит EAX
Регистр
Младшие 16 разрядов EBX
/CX
Младшие 16 разрядов ECX
DX
Младшие 16 разрядов EDX
Си
Младшие 16 разрядов ESI
di
Младшие 16 разрядов EDI
ВР
Младшие 16 бит ebp
портов
Младшие 16 разрядов ESP
Al
Младшие 8 бит EAX
заголовки
Старшие 8 бит AX
BL
Младшие 8 бит EBX
BH
Старшие 8 бит BX
cl
Младшие 8 разрядов ECX
канал
Старшие 8 бит CX
DL
Младшие 8 бит EDX
dh
Старшие 8 бит DX
Работа с подрегистром влияет только на подрегистры и ни на один из частей, не входящих в подрегистр. Например, при хранении в регистре AX старшие 16 бит регистра EAX остаются без изменений.
При использовании функции ? (Вычисление выражения) команде регистр должен начинаться с символа "at" ( @ ). Например, следует использовать ? @ax , а не ? AX. Это гарантирует, что отладчик распознает AX как регистр, а не символ.
Однако в команде r (registers) не требуется (@). Например, r AX = 5 всегда будет интерпретироваться правильно.
Два других регистра важны для текущего состояния процессора.
EIP
flags
Указатель инструкции — это адрес выполняемой инструкции.
Регистр флагов — это коллекция одноразрядных флагов. Многие инструкции изменяют флаги, чтобы описать результат инструкции. Затем эти флаги можно проверить с помощью условных инструкций перехода. Дополнительные сведения см. в разделе Флаги x86 .
Соглашения о вызовах
Архитектура x86 имеет несколько различных соглашений о вызовах. К счастью, все они следуют правилам сохранения регистров и возвращаемым значениям функций:
Функции должны сохранять все регистры, за исключением EAX, ECXи EDX, которые можно изменить в вызове функции, и ESP, который должен быть обновлен в соответствии с соглашением о вызовах.
Регистр EAX принимает возвращаемые значения функции, если результат равен 32 бит или меньше. Если результат равен 64 бит, то результат сохраняется в паре EDX: EAX .
Ниже приведен список соглашений о вызовах, используемых в архитектуре x86.
Win32 (__stdcall)
Параметры функции передаются в стеке, отправляются справа налево, а вызываемый очищает стек.
Вызов собственного метода C++ (также известный как thiscall)
Параметры функции передаются в стеке, отправленном справа налево, указатель this передается в регистр ECX , а вызываемый очищает стек.
COM (__stdcall для вызовов методов C++)
Параметры функции передаются в стек, передается справа налево, затем указатель "this" помещается в стек, а затем вызывается функция. Вызываемый объект очищает стек.
__fastcall
Первые два аргумента типа DWORD и меньше передаются в регистры ECX и EDX . Остальные параметры передаются в стеке, отправленном справа налево. Вызываемый объект очищает стек.
__cdecl
Параметры функции передаются в стеке, отправляются справа налево, а вызывающий объект очищает стек. Соглашение о вызовах __cdecl используется для всех функций с параметрами переменной длины.
Многопортовые регистры
Пока что мы рассмотрели восемь «нижних регистров» общего назначения. У 8086 есть также семь «верхних регистров», используемых для доступа к памяти, включая печально известные сегментные регистры. У этих регистров схема работы более сложная, многопортовая, позволяющая одновременно вести несколько процессов чтения и записи. К примеру, многопортовый регистровый файл позволяет прочесть счётчик программы, сегментный регистр и записать другой сегментный регистр – и всё это одновременно.
Приведённая ниже блок-схема отличается от большинства блок-схем для 8086, поскольку показывает реальную физическую реализацию процессора, а не ту, что представляет себе программист. В частности, на диаграмме показано два «регистра внутренних коммуникаций» среди регистров модуля интерфейса шины (справа) вместе с сегментными регистрами, совпадающими с 7 регистрами, которые можно рассмотреть на кристалле. Временные регистры, показанные ниже, физически являются частью АЛУ, поэтому в данной статье я их не рассматриваю.
В книжке "Разработка современных процессоров" обсуждаются сложные системы регистров в процессоре, начиная с 2000-х годов. Там написано, что сложность цепей быстро выходит за рамки трёх портов, а в некоторых передовых процессорах существуют регистровые файлы с 20 или более портами.
Многопортовая регистровая ячейка ниже создана на основе той же схемы из двух инверторов, однако у неё есть три разрядных шины (а не одна, как в предыдущем случае) и пять управляющих шин (вместо двух). Три шины чтения позволяют читать содержимое регистровой ячейки по любой из трёх разрядных шин, а две шины записи позволяют разрядной шине А или С вести запись в регистровую ячейку.
Многопортовая регистровая ячейка в процессоре 8086
У верхних регистров есть разное количество портов для чтения и записи: два регистра с 3 шинами чтения и 2 шинами записи, один регистр с 2 шинами чтения и 2 шинами записи, четыре регистра с 2 шинами чтения и 1 шиной записи. Три первых регистра – это, вероятно, счётчик программы, непрямой временный регистр и временный регистр операнда. Последние четыре – вероятно, сегментные регистры SS, DS, SS и ES. Также там есть три регистра буфера предварительной выборки команд, каждый с одной шиной для чтения и одной для записи.
Файл верхних регистров, состоящий из десяти 16-битных регистров. На фото видно кремний и поликремний. Вертикальные красные линии – остатки удалённого металлического слоя. Кликабельно.
Соглашения о вызовах
Архитектура x86 имеет несколько различных соглашений о вызовах. К счастью, все они следуют правилам сохранения регистров и возвращаемым значениям функций:
Функции должны сохранять все регистры, за исключением EAX, ECXи EDX, которые можно изменить в вызове функции, и ESP, который должен быть обновлен в соответствии с соглашением о вызовах.
Регистр EAX принимает возвращаемые значения функции, если результат равен 32 бит или меньше. Если результат равен 64 бит, то результат сохраняется в паре EDX: EAX .
Ниже приведен список соглашений о вызовах, используемых в архитектуре x86.
Win32 (__stdcall)
Параметры функции передаются в стеке, отправляются справа налево, а вызываемый очищает стек.
Вызов собственного метода C++ (также известный как thiscall)
Параметры функции передаются в стеке, отправленном справа налево, указатель this передается в регистр ECX , а вызываемый очищает стек.
COM (__stdcall для вызовов методов C++)
Параметры функции передаются в стек, передается справа налево, затем указатель "this" помещается в стек, а затем вызывается функция. Вызываемый объект очищает стек.
__fastcall
Первые два аргумента типа DWORD и меньше передаются в регистры ECX и EDX . Остальные параметры передаются в стеке, отправленном справа налево. Вызываемый объект очищает стек.
__cdecl
Параметры функции передаются в стеке, отправляются справа налево, а вызывающий объект очищает стек. Соглашение о вызовах __cdecl используется для всех функций с параметрами переменной длины.
Счётчик команд PC (R15)
Регистр PC (Program Counter) используется для хранения адресов выполняющихся команд. При последовательном выполнении команд он последовательно увеличивается, при переходах и прерываниях в него загружается новое значение.
Многие команды запрещают программисту явно использовать PC или налагают на это определённые ограничения, что всегда оговаривается в описаниях конкретных команд. Нарушение этих правил обычно ведёт к непредсказуемым результатам.
В тех случаях, когда описание команды не налагает ограничений на использование PC, применяются изложенные ниже правила.
Отображение регистров и флагов отладчиком
Ниже приведен пример регистрации отладчика.
При отладке в пользовательском режиме можно игнорировать иопл и всю последнюю строку экрана отладчика.
Заключение
Хотя 8086 процессору уже 42 года, он до сих пор оказывает значительное влияние на современные компьютеры, поскольку архитектура х86 до сих пор очень часто используется. Регистры 8086 до сих пор существуют в современных компьютерах х86, хотя сегодня они уже имеют длину в 64 бита, а кроме этих регистров существует и множество других.
Кристалл 8086-го очень интересно изучать, поскольку его транзисторы можно рассмотреть под микроскопом. В своё время это был сложный процессор, поскольку на нём расположено 29 000 транзисторов, однако достаточно простой для того, чтобы отследить все цепи и понять, как они работают.
Регистры общего назначения предназначены для хранения операндов арифметико-логических инструкций, а также адресов или отдельных компонентов адресов ячеек памяти.
В микропроцессоре 8086 было восемь 16-разрядных регистров общего назначения. Все они могли выступать в качестве операндов основных арифметико - логических инструкций, но только четыре годились для целей адресации. Кроме того, каждый регистр имел свои специфические функции:
- AX — аккумулятор. Использовался для хранения операндов в командах умножения и деления, ввода-вывода, в некоторых командах обработки строк и других операциях;
- BX — регистр базы. Используется для хранения адреса или части адреса операнда, находящегося в памяти;
- CX — счётчик. Содержит количество повторений строковых операций, циклов и сдвигов;
- DX — регистр данных. Используется для косвенной адресации портов ввода-вывода, а также как «расширитель» аккумулятора в операциях удвоенной разрядности;
- SI — регистр адреса источника. Используется в строковых операциях, а также в качестве индексного регистра при обращении к операндам в памяти;
- DI — регистр адреса приёмника. Используется в строковых операциях, а также в качестве индексного регистра при обращении к операндам в памяти;
- BP — указатель кадра стека. Используется для адресации операндов, расположенных в стеке;
- SP — указатель стека. Используется при выполнении операций со стеком, но не для явной адресации операндов в стеке.
Первые четыре регистра могут делиться на две однобайтовых части каждый: AH, BH, CH и DH для старших байтов и AL, BL, CL и DL для младших байтов.
В микропроцессоре 80386 разрядность регистров была удвоена и составила 32 бита. Обновлённые регистры не заменили, а дополнили уже имеющиеся: 32-разрядные версии получили имена EAX, EBX, ECX, EDX, ESI, EDI, EBP и ESP, а их младшие слова сохранили прежние обозначения, причём у первых четырёх регистров сохранилась возможность раздельного обращения к двум младшим байтам (AH, AL и так далее). С помощью префикса изменения размера операнда возможно выполнение 32-разрядных операций в реальном режиме и режиме виртуального процессора 8086.
Другим важным новшеством процессора 80386 стало уменьшение «дискриминации» между регистрами: теперь компоненты 32-разрядных адресов можно хранить в любом регистре. Появилась также возможность масштабирования — использования содержимого регистра в качестве индекса, при вычислении адреса автоматически умножаемого на 2, 4 или 8. Однако при работе в реальном режиме и режиме виртуального процессора 8086 для адресации по-прежнему используются только BX, BP, SI и DI, отсутствует и возможность масштабирования.
Появление 64-разрядных микропроцессоров (технология EM64T/AMD64) повлекло серьёзные изменения в наборе регистров общего назначения. Суть изменений сводится к следующему:
С точки зрения прикладного программиста, процессор располагает 16-ю 32-разрядными регистрами общего назначения (РОН, GPR), из которых три на деле имеют специальные функции:
- общие регистры R0–R12;
- указатель стека SP, он же регистр R13;
- регистр связи LR, он же регистр R14;
- счётчик команд PC, он же регистр R15.
Первые 13 регистров с точки зрения архитектуры являются абсолютно равноправными, за исключением двух моментов:
- регистры R0–R7 и PC существуют в единственном экземпляре, общем для всех режимов;
- регистры R8–R12 существуют в двух экземплярах: для режима обработки быстрого прерывания (FIQ) и для всех остальных режимов;
- регистры LR и SP являются общими для режимов пользователя и системы, но существуют отдельно для каждого другого режима, т. е. в сумме их имеется 6 или 7 пар.
Когда необходимо указать, регистр какого именно режима используется, в документации используется запись вида LR_svc (в данном случае она указывает на регистр LR режима супервизора).
В процессорах M-профиля имеется 17 или 19 регистров, причём R0–R12, LR и PC всегда существуют в одном экземпляре, и лишь SP имеет несколько экземпляров:
- процессор без расширения безопасности (в том числе любой процессор версии ARMv6-M или ARMv7-M) имеет два регистра: основного стека MSP и стека процесса PSP (иногда обозначаются как SP_main и SP_process);
- процессор с расширением безопасности имеет четыре указателя стека: два для безопасного (MSP_S и PSP_S) и два для небезопасного (MSP_NS и PSP_NS) режимов.
В режиме обработчика всегда используется указатель основного стека; в режиме потока может использоваться как он, так и указатель стека процесса, что определяется содержимым управляющего регистра CONTROL (в случае наличия расширения безопасности используется регистр, соответствующий текущему режиму безопасности). Для доступа к конкретному указателю стека привилегированный код может воспользоваться командами Особые случаи использования регистров
Как регистры реализуются в кремнии
Начну с описания того, как 8086 создаётся из N-МОП транзисторов. Затем объясню, как делается инвертор, как при помощи инверторов хранятся одиночные биты, и как делается регистр.
8086, как и другие чипы той эпохи, создавались на основе транзисторов типа N-МОП. Эти чипы состояли из кремниевой подложки, в которую при помощи диффузии внедряли примеси из мышьяка или бора, формировавшие транзисторы. Находящиеся над кремнием проводники из поликремния формировали вентили транзисторов и соединяли все компоненты друг с другом. Находившийся ещё выше металлический слой содержал дополнительные проводники. Для сравнения, современные процессоры используют технологию КМОП, комбинирующую N-МОП и P-МОП транзисторы, и содержат множество металлических слоёв.
На схеме ниже показан инвертор, состоящий из N-МОП транзистора и резистора. При низком входном напряжении транзистор выключен, поэтому подтягивающий резистор подтягивает выход вверх. При высоком входном напряжении транзистор включается, соединяет землю и выход, подтягивая выход вниз. Таким образом входящий сигнал инвертируется.
На самом деле, подтягивающий резистор в вентиле N-МОП представляет собой транзистор особого рода. Работающий в режиме обеднения транзистор ведёт себя, как резистор, будучи при этом более компактным и эффективным.
На схеме показано, как из транзистора и резистора получается инвертор. На фото показана реализация на чипе. Металлический слой удалён, чтобы было видно кремний и поликремний.
На фото выше видно, как физически сделан инвертор у 8086. Розоватые участки – это кремний с примесями, делающими его токопроводящим, а линии медного цвета – это поликремний сверху. Транзистор появляется там, где поликремний пересекается с кремнием. Поликремний формирует вентиль транзистора, а участки кремния с двух сторон дают исток и сток. Большой прямоугольник поликремния формирует подтягивающий резистор между +5 В и выходом. Таким образом, схема чипа совпадает со схемой инвертора. Подобные цепи можно рассмотреть под микроскопом и подвергнуть реверс-инжинирингу.
Строительный блок регистра – два инвертора в цепи обратной связи, хранящих один бит (см. ниже). Если на верхнем проводнике 0, правый инвертор выдаст 1 на нижний проводник. Затем левый инвертор выдаст 0 на верхний проводник, завершая цикл. Таким образом цепь оказывается стабильной и «помнит» 0. И наоборот, если на верхнем проводнике 1, она превратится в 0 на нижнем проводнике, и обратно в 1 на верхнем. В итоге цепь может хранить 0 или 1, формируя однобитную память.
У 8086 два спаренных инвертора хранят в регистре один бит. Схема стабильно находится в состоянии 0 или 1.
К паре инверторов добавляются три транзистора, чтобы получилась пригодная для использования ячейка регистра. Один транзистор выбирает ячейку для чтения, второй выбирает ячейку для записи, третий усиливает сигнал при чтении. В центре схемы ниже два инвертора хранят бит. Для чтения бита ток подаётся на красную шину. Это соединяет выход инвертора с разрядной шиной через усиливающий транзистор. Для записи бита ток подаётся на красную шину, соединяющую разрядную шину с инверторами. Подавая высокотоковые сигналы 0 или 1 на разрядную шину (и, соответственно, на хранящийся бит), мы заставляем инверторы переключаться на нужное значение. Отметьте, что разрядная шина используется и для чтения, и для записи.
В других процессорах используются несколько другие ячейки для хранения регистров. 6502 использует дополнительный транзистор в цепи обратной связи инвертора, чтобы разбивать цепь при записи нового значения. Z80 пишет одновременно в оба инвертора, что «облегчает» изменение, однако требует наличия двух проводников для записи. У 8086 есть усиливающий транзистор в каждой ячейке регистра для чтения, а другие процессоры считывают выходной сигнал с обоих инверторов и используют внешний дифференциальный усилитель для усиления сигнала. Базовая ячейка регистра 8086 состоит из 7 транзисторов (7Т), что больше, чем в типичной статичной ячейке RAM, использующей 6 или 4 транзистора, однако она использует только одну разрядную шину, а не две разных. Динамическая память (DRAM) работает гораздо эффективнее, и использует один транзистор и конденсатор, однако без обновления данных они будут утеряны.
Принципиальная схема хранящей бит ячейки регистра. Регистровый файл делается из массива таких ячеек.
Регистровый файл состоит из матрицы ячеек регистров, подобных описанному выше. Ширина матрицы составляет 16 ячеек, поскольку в регистрах хранятся 16-битные значения. Каждый регистр расположен по горизонтали, поэтому шина чтения или записи выбирает все ячейки определённого регистра. 16 вертикальных разрядных шин формируют единую шину, поэтому все 16 битов в выбранном регистре читаются и записываются параллельно.
На фото ниже показан увеличенный регистровый файл 8086 общего назначения, и видна матрица регистровых ячеек: 16 столбцов и 8 строк, 8 16-битных регистров. Затем дано увеличение одной регистровой ячейки в файле. Объясню, как реализована эта ячейка.
Фото кристалла 8086 с увеличением нижнего регистрового файла (восьми 16-битовых регистров), а потом и отдельной регистровой ячейки. Чтобы было видно кремниевые структуры, металлический и поликремниевый слои удалены.
8086 делается из кремния с примесями и поликремниевых проводников с металлическими проводниками сверху. На левом фото ниже показаны вертикальные металлические проводники регистровой ячейки. Отмечены земля, питание и проводники разрядной шины (оставшийся проводник пересекает регистровый файл, но не контачит с ним). На фото справа металлический слой растворён, чтобы было видно поликремний и кремний. Шины чтения и записи – это горизонтальные поликремниевые проводники. Поскольку у чипа только один металлический слой, в регистрах для вертикальных шин используется металл, а для горизонтальных – поликремний, чтобы они не пересекались друг с другом). Сквозные соединения металла и кремния видны как более яркие круги на фото с металлом и как кружки на фото с кремнием.
Ячейка хранения регистра. На фото слева показан металлический слой, а справа – соответствующие слои поликремния и кремния.
На диаграмме ниже показано соответствие физической схемы регистровой ячейки с принципиальной. Инверторы состоят из транзистора А и В и регистров. Транзисторы C, D и E формируются отмеченными кусочками поликремния. Разрядная шина не видна, поскольку располагается в металлическом слое. Отметьте, что схема ячейки памяти сильно оптимизирована для минимизации размера. Также отметьте, что транзистор А гораздо меньше других. У инвертора А довольно слабый выходной ток, поэтому разрядная шина может преодолеть его при записи.
Регистровая ячейка от 8086 с соответствующей схемой
Регистровая ячейка на кристалле повторяются не единообразно – каждая вторая ячейка является зеркальным отражением предыдущей. Это увеличивает плотность регистровой ячейки – шина питания, идущая между двумя зеркальными ячейками, может питать их обе (то же касается и земли). Зеркальные повторения уменьшают количество требуемых шин питания и земли в два раза.
Две пары ячеек памяти с разными цепями. У ячеек слева одна шина записи, а справа – отдельные шины записи для левых и правых битов.
Считывание счётчика команд
Считывание PC используется главным образом для позиционно-независимой адресации, включая позиционно-независимые переходы, загрузку констант (так называемых литералов; на языке ассемблера эти операции задаются командами вида LDRB , LDRD , LDRH , Запись в счётчик команд
Поскольку счётчик команд содержит адрес выполняемой команды, запись в него вызывает переход на команду по записываемому адресу. В командах, где он может явно указываться в качестве регистра-приёмника, используются следующие общие правила.
В арифметико-логических командах набора ARM использование PC в качестве приёмника вызывает переход на соответствующий адрес. В ранних версиях (все варианты ARMv5 и раньше) записываемое в PC значение должно иметь нули в двух младших разрядах, иначе последствия будут непредсказуемыми. В версии ARMv6 два младших бита записываемого значения игнорируются и принудительно устанавливаются в 0. Наконец, в версии ARMv7 младший бит используется в качестве индикатора системы команд, активизируемой после перехода: если он равен нулю, по-прежнему будет использоваться система команд ARM (при этом бит 1 должен быть равен нулю), а если равен единице — система команд Thumb (тогда бит 1 является частью адреса перехода).
В системе команд Thumb явная запись в PC возможна в 16-разрядных командах ADD и Указатель стека SP (R13)
Регистр SP предназначен для использования в качестве указателя стека.
Единственной командой набора ARM, неявно использующей SP по прямому назначению, является весьма специфическая команда PUSH , использующие SP как указатель стека. Стек растёт вниз, а значение SP указывает на последнее помещённое в него слово. Заметим, что в унифицированном языке ассемблера для системы команд ARM поддерживаются аналогичные мнемоники, хотя они транслируются в определённые разновидности команд LDM и Регистр связи LR (R14)
Регистр LR (Link Register) используется в следующих случаях:
Перечисленные выше команды применяются для вызова подпрограмм, при этом в LR помещается адрес команды, следующей за BL или BLX, либо код возврата из функции (FNC_RETURN) для команды BLXNS.
В случае возникновения прерывания в процессорах A- и R-профилей в LR заносится значение PC, на 4 или 8 превосходящее адрес команды, выполняемой непосредственно перед прерыванием; подробнее об этом говорится в разделе «Обработка прерываний в A- и R-профилях». В процессоре M-профиля при прерывании в LR заносится специальный код, называемый в документации EXC_RETURN и определяющий, как необходимо выполнять возврат из обработчика данного прерывания (см. раздел «Обработка прерываний в M-профиле)».
Хотя при создании своей ОС программист волен определять собственные соглашения о связях и правила использования регистров, есть смысл придерживаться правил, разработанных компанией ARM и поддерживаемых всеми популярными трансляторами для этой архитектуры. Вкратце они заключаются в следующем.
- В качестве указателя стека всегда используется регистр SP (R13). Стек растёт вниз, SP указывает на последнее используемое слово. Эти правила полностью соответствуют аппаратным требованиям набора команд Thumb.
- Для передачи параметров в вызываемую подпрограмму могут использоваться регистры R0–R3. Если необходимо передать параметр, по размерам превышающий слово, при использовании обычного порядка слов «младший–старший» младшее слово помещается в регистр с меньшим номером; при использовании порядка «старший–младший» — в регистр со старшим номером. Если параметров слишком много, дополнительные параметры передаются через стек. Кроме того, при наличии арифметического сопроцессора для передачи параметров могут использоваться его регистры, но их использование здесь не обсуждается.
- Возврат результатов осуществляется через регистры R0–R3.
- Вызываемая подпрограмма обязана сохранять значения регистров R4–R11, а также корректно восстанавливать значение указателя стека.
- Регистры R0–R3, R12 и LR, а также флаги регистра состояния вызываемой подпрограммой могут свободно изменяться.
- Компоновщик при связывании вызовов подпрограмм может использовать регистр R12 в качестве рабочего, поэтому подпрограммы не могут рассчитывать на сохранность его содержимого, если используют вызовы подпрограмм из других модулей.
- В ряде систем R7 хранит значение, зависящее от системы (например, адрес локальной памяти потока — TLS). В таких системах изменять этот регистр обычно запрещается.
Указатель стека сам по себе всегда должен быть выровнен на границу слова. Тем не менее, в некоторых случаях требуется его выравнивание на границу двойного слова. Для контроля за соблюдением выравнивания на границу двойного слова объектные модули могут снабжаться специальными атрибутами, которые контролируются компоновщиком. Последний, в частности, не должен допускать вызова подпрограмм, расположенных в модуле, требующем выравнивания стека на границу двойного слова, из подпрограмм, относящихся к модулю, обеспечивающему выравнивание лишь на границу слова. Компиляторы языков высокого уровня обычно снабжают свои файлы соответствующими атрибутами сами; при программировании на языке ассемблера это является задачей программиста. Например, транслятор ассемблера фирмы KEIL имеет для этого директивы PRESERVE8 (модуль обеспечивает выравнивание стека на границу двойного слова, если он уже был на неё выровнен) и REQUIRE8 (модуль требует выравнивания стека на границу двойного слова).
Флаги x86
В предыдущем примере коды из двух букв в конце второй строки являются флагами. Это одноразрядные регистры, которые имеют множество применений.
В следующей таблице перечислены флаги x86.
Код флага | Имя флага | Значение | Состояние флага | Описание |
---|---|---|---|---|
окна | Флаг переполнения | 0 1 | нвов | Без переполнения. переполнение |
df | Флаг направления | 0 1 | упдн | Направление вверх — направление вниз |
if | Флаг прерывания | 0 1 | диеи | Прерывания отключены — прерывания включены |
SF | Флаг подписи | 0 1 | плнг | Положительный (или ноль) — отрицательный |
зф | Нулевой флаг | 0 1 | нззр | Ненулевое значение 0 |
AF | Дополнительный флаг для переноса | 0 1 | наак | Нет вспомогательных вспомогательных для выполнения |
PF | Флаг четности | 0 1 | пепо | Четность четно-четное |
CF | Флаг переноса | 0 1 | нкци | Без переноса |
TF | Флаг ловушки | Если tf равно 1, процессор вызовет исключение STATUS_SINGLE_STEP после выполнения одной инструкции. Этот флаг используется отладчиком для реализации одношаговой трассировки. Он не должен использоваться другими приложениями. | ||
иопл | Уровень привилегий ввода-вывода | Уровень привилегий ввода-вывода это 2-разрядное целое число со значениями от 0 до 3. Он используется операционной системой для управления доступом к оборудованию. Он не должен использоваться приложениями. |
Когда регистры отображаются в результате выполнения какой-либо команды в отладчике командное окно, это отображаемое состояние флага . Однако если вы хотите изменить флаг с помощью команды r (registers) , вы должны обратиться к нему по коду флага.
В окне регистров в WinDbg код флага используется для просмотра или изменения флагов. Состояние флага не поддерживается.
Ниже приведен пример. В предыдущем отображении регистра отображается состояние флага NG . Это означает, что для флага знака в настоящий момент установлено значение 1. Чтобы изменить это, используйте следующую команду:
Это устанавливает для флага знака нулевое значение. Если вы выполняете другую регистрацию, код состояния NG не отображается. Вместо этого отобразится код состояния PL .
Наиболее часто используемые флаги являются флагом знака, нулевым флагом и флагом.
Состоянии
Условие описывает состояние одного или нескольких флагов. Все условные операции на платформе x86 выражаются с точки зрения условий.
Для представления условия ассемблер использует одну или две аббревиатуры. Условие может быть представлено несколькими сокращениями. Например, AE ("выше или равно") — это то же условие, что и NetBIOS ("не ниже"). В следующей таблице перечислены некоторые распространенные условия и их значения.
Значения селекторов могут быть загружены при исполнении программы и являются специфичными для задачи. Это значит, что регистры сегментов в защищенном режиме перезагружаются автоматически при переключении микропроцессора на другую задачу. В то же время, использование сегментных регистров зависит от того, какая модель адресного пространства используется. При использовании сплошной модели в сегментные регистры загружается один и тот же селектор сегмента с базой 0 и пределом 0FFFFFFFFh, обеспечивая доступ ко всему линейному адресному пространству. При использовании сегментированной модели значения селекторов в сегментных регистрах независимы, так что программа может одновременно обращаться к шести сегментам.
Не все сегментные регистры равнозначны. Регистр CS хранит селектор сегмента кода. Процессор извлекает очередную инструкцию для исполнения, формируя логический адрес из селектора в CS и смещения в регистре EIP. Значение этого регистра нельзя изменить непосредственно, оно меняется в командах межсегментного перехода (FAR JMP), межсегментного вызова (FAR CALL), при вызове обработчика прерывания (INT) и при возврате из дальней процедуры (RETF) или обработчика прерывания (IRET).
Регистр SS хранит селектор сегмента стека. Стек используется для передачи параметров подпрограммам и для сохранения адреса возврата при вызове подпрограммы или обработчика прерывания. Вершиной стека считается байт, логический адрес которого образуется из селектора в регистре SS и смещения в регистре ESP. Программа может непосредственно изменить значение SS, что дает ей возможность переключаться между несколькими стеками. Причем на время выполнения команды MOV SS,xxxx и одной команды следующей за ней (обычно это MOV ESP,xxxx) запрещаются маскируемые и блокируются немаскируемые прерывания.
Регистры DS, ES, FS и GS хранят селекторы сегментов данных. Если инструкция обращается к памяти, но содержит только смещение, то считается, что она обращается к данным в сегменте DS. Сегмент ES может использоваться без явного указания в цепочечных командах. Сегменты FS и GS используются при обращении к памяти только при явном использовании в инструкции префиксов этих сегментов.
Указатель команд
Указатель команд (EIP) является 32-разрядным регистром. Он содержит смещение следующей команды, подлежащей выполнению. Относительный адрес отсчитывается от начала сегмента исполняемой задачи. Указатель команд непосредственно недоступен программисту, но он управляется явно командами управления потоком, прерываниями и исключениями (JMP, CALL, RET, IRET, команды условного перехода). Получить текущее значение EIP можно, если выполнить команду CALL, а затем прочитать слово на вершине стека.
Младшие 16 бит регистра EIP обозначаются IP и могут быть использованы процессором независимо при исполнении 16-битного кода.
Регистр системных флагов
Регистр EFLAGS содержит группу флагов состояния, управления и системных флагов. Младшие 16 бит регистра представляют собой 16-разрядный регистр флагов и состояния МП 8086, называемый FLAGS, который наиболее полезен при исполнении программ для МП 8086 и 80286. Структура регистра флагов показана на рисунке. Неопределенные биты зарезервированы, то есть на данный момент они не имеют значения, однако могут быть использованы для специальных целей в последующих версиях микропроцессора. Далее термин "установлен" означает значение 1, а термин "сброшен" - значение 0.
- LAHF/SAHF - загрузка/сохранение младших 8 битов регистра флагов в регистре AH;
- PUSHF/POPF - помещение/извлечение из стека младших 16 битов регистра флагов;
- PUSHFD/POPFD - помещение/извлечение из стека 32-битного регистра EFLAGS.
DF - флаг направления (Direction Flag) управляет поведением цепочечных инструкций (MOVS, CMPS, SCAS, LODS, STOS). Когда флаг сброшен, при выполнении цепочечной команды происходит автоинкремент адресов источника и приемника. Когда флаг установлен - автодекремент. Флаг можно непосредственно установить при помощи инструкции STD и сбросить при помощи CLD.
Регистры управления сегментированной памятью
Эти регистры также известны как регистры системных адресов. Четыре регистра указывают на структуры данных, которые управляют механизмом сегментированной памяти. Они определены для ссылок на таблицы или сегменты, поддерживаемые моделью защиты микропроцессора.
Регистр глобальной дескрипторной таблицы (GDTR). Содержит 32-разрядный линейный адрес и 16-разрядную границу глобальной дескрипторной таблицы. Значение этого регистра можно загрузить/сохранить при помощи привилегированных инструкций LGDT/SGDT. В реальном режиме этот регистр не используется. Перед переходом в защищенный режим в этот регистр следует загрузить корректные значения.
Регистр таблицы дескрипторов прерываний (IDTR). Указывает на таблицу точек входа в программы обработки прерываний. Регистр содержит 32-разрядный линейный базовый адрес и 16-разрядный предел таблицы. Значение этого регистра можно загрузить/сохранить при помощи привилегированных инструкций LIDT/SIDT. При инициализации процессора базовый адрес IDT устанавливается в 0, а предел - 0FFFFh. В реальном режиме эта таблица хранит 4-байтные вектора прерываний, а в защищенном - дескритпоры шлюзов обработчиков прерываний и исключений. Это единственный регистр среди перечисленных, который используется в реальном режиме.
Intel 8086 – один из самых влиятельных из когда-либо созданных компьютерных чипов. Он создал архитектуру x86, доминирующую в современных настольных и серверных компьютерах. Я занимался реверс-инжинирингом 8086 на основе фотографий кристаллов, и в данной статье я опишу реализацию регистрового файла (набора регистров).
Кристалл 8086. Отмечено место хранения регистров. Верхние регистры используются блоком интерфейса шины для доступа к памяти, а нижние регистры общего назначения используются исполнительным блоком. Буфер команд – это 6-байтная очередь, состоящая из заранее запрошенных команд.
На фото дано изображение процессора 8086 под микроскопом. Виден металлический слой сверху чипа, под которым прячется кремний. Расположенные по внешнему краю соединительные провода соединяют площадки на кристалле с 40 внешними контактами чипа.
Рамкой обведены пятнадцать 16-битовых регистров 8086 и шесть байтов очереди предварительного запроса команд (предвыборки кода). Регистры занимают значительную часть кристалла, хотя в сумме их наберётся всего на 36 байт. Из-за ограничений по площади у первых микропроцессоров было относительно небольшое количество регистров. Для сравнения, у современных чипов процессоров есть килобайты регистров и мегабайты кэшей.
8086 был одним из самых ранних микропроцессоров, реализовавших предвыборку кода, однако у Motorola 68000 (1979) немного раньше появился буфер предвыборки на 4 байта. В мейнфреймах предвыборка существовала у IBM Stretch (1961), CDC 6600 (1964) и IBM System/360 Model 91 (1966).
Количество регистров у современного процессора подсчитать сложно. Единственное точное число, найденное мною, содержалось в книге "Анатомия высокоскоростного микропроцессора" (1997), подробно описывающей процессор AMD K6. Из-за переименования регистров у современных процессоров физических регистров гораздо больше, чем архитектурных (тех, что видны программисту), а количество физических регистров в документации не указывается. У K6, кроме восьми регистров х86 общего назначения, было 16 микроархитектурных рабочих регистров для переименования.
У процессоров с поддержкой AVX-512 есть 32 512-битовых регистра, то есть для одной этой функции зарезервировано 2 кБ регистров. В таких случаях размер регистров подсчитать ещё сложнее. Что до размера кэша, то у передовых процессоров его объём доходит до 77 МБ.
Читайте также: