Требуют ли инструкции x86 своей собственной кодировки, а также всех своих аргументов для одновременного присутствия в памяти?

64

Я пытаюсь выяснить, возможно ли запустить виртуальную машину Linux, чья память поддерживается только одной физической страницей.

Чтобы смоделировать это, я изменил обработчик ошибок вложенной страницы в KVM, чтобы удалить текущий бит из всех записей вложенной таблицы страниц (NPT), кроме той, которая соответствует текущей обработанной ошибке страницы.

Пытаясь запустить гостевую систему Linux, я заметил, что инструкции по сборке, использующие операнды памяти, такие как

add [rbp+0x820DDA], ebp

приводить к циклу сбоя страницы, пока я не восстановлю текущий бит для страницы, содержащей инструкцию, а также для страницы, на которую ссылается операнд (в этом примере [rbp+0x820DDA]).

Мне интересно, почему это так. Разве процессор не должен обращаться к страницам памяти последовательно, то есть сначала читать инструкцию, а затем обращаться к операнду памяти? Или x86 требует, чтобы страница инструкций и все страницы операндов были доступны одновременно?

Я тестирую на AMD Zen 1.

savvybug
источник
2
Почему вы хотите это сделать?
SS Anne
11
Просто из технического интереса :)
savvybug
14
Голосование за веселую идею проекта.
труба
10
Это безумие на уровне «загрузки Linux на эмуляторе 486, работающем на JavaScript в браузере». Я люблю это.
Крили - на забастовке -
3
Хех, очевидно, я взял этот вопрос к тому же логическому выводу, о котором вы уже думали, о минимальном рабочем наборе для гарантированного продвижения вперед. Я уже ответил на это, прежде чем вы добавили этот первый первый абзац к вопросу. : PI добавил несколько ссылок и больше деталей в нескольких местах (например, обходчику страниц разрешено кэшировать некоторые записи каталога гостевой страницы внутри), поскольку этот вопрос привлекает гораздо больше внимания, чем я ожидал, благодаря тому, что он каким-то образом добрался до HNQ.
Питер Кордес

Ответы:

56

Да, они требуют машинного кода и всех операндов памяти.

Разве процессор не должен обращаться к страницам памяти последовательно, то есть сначала читать инструкцию, а затем обращаться к операнду памяти?

Да, это логично, что происходит, но исключение ошибки страницы прерывает этот двухэтапный процесс и отклоняет любой прогресс. Процессор никак не может вспомнить, какую инструкцию он выполнял в момент возникновения сбоя страницы.

Когда обработчик сбоя страницы возвращается после обработки допустимого сбоя страницы, RIP = адрес ошибочной инструкции, поэтому ЦП пытается выполнить его с нуля .

Было бы законно, чтобы ОС модифицировала машинный код ошибочной инструкции и ожидала, что она выполнит другую команду после iretобработчика ошибок страницы (или любого другого исключения или обработчика прерываний). Так что AFAIK архитектурно требует, чтобы процессор возвращал выборку кода из CS: RIP в случае, если вы говорите. (Предполагая, что он даже возвращается к отказавшему CS: RIP вместо планирования другого процесса в ожидании сбоя диска на жесткой странице или доставки SIGSEGV обработчику сигнала при сбое неверной страницы.)

Вероятно, это также архитектурно необходимо для входа / выхода гипервизора. И даже если это явно не запрещено на бумаге, это не то, как работают процессоры.

@torek отмечает, что некоторые (CISC) микропроцессоры частично декодируют инструкции и выдают состояние микрорегистра при сбое страницы , но x86 не такой.


Несколько инструкций являются прерываемыми и могут выполнять частичный прогресс, например rep movs(memcpy в банке) и другие строковые инструкции, или собирать хранилища загрузки / разброса. Но единственным механизмом является обновление архитектурных регистров, таких как RCX / RSI / RDI для строковых операций, или регистров назначения и маски для сборок (например, руководство для AVX2vpgatherdd ). Отсутствие кода операции / декодирования приводит к некоторому скрытому внутреннему регистру и перезапускает его после iret из обработчика ошибок страницы. Это инструкции, которые делают несколько отдельных обращений к данным.

Также имейте в виду, что x86 (как и большинство ISA) гарантирует, что инструкции являются атомарными. прерывания / исключения: они либо полностью происходят, либо не происходят вообще, до прерывания. Прерывание инструкции по сборке во время ее работы . Так, например add [mem], reg, потребуется сбросить нагрузку, если часть магазина вышла из строя, даже без lockпрефикса.


В худшем случае число гостевых страниц пользовательского пространства для продвижения вперед может составлять 6 (плюс отдельные поддеревья таблицы гостевого ядра для каждой из них):

  • movsqили movsw2-байтовая инструкция, охватывающая границу страницы, поэтому для ее декодирования необходимы обе страницы.
  • операнд qword [rsi]также разделенный на страницы
  • операнд назначения qword [rdi]также разделенный на страницы

Если какая-то из этих 6 страниц ошибается, мы вернемся к исходной точке.

rep movsdтакже является 2-байтовой инструкцией, и выполнение шага на одном шаге будет иметь такое же требование. Подобные случаи, такие как push [mem]или pop [mem]могут быть построены со смещенным стеком.

Одна из причин (или побочных преимуществ) для / создания "прерывистости" сборочных нагрузок / хранилищ разброса (обновление вектора маски по мере их продвижения) состоит в том, чтобы избегать увеличения этой минимальной площади для выполнения одной инструкции. Также для повышения эффективности обработки нескольких неисправностей во время одной сборки или разброса.


@Brandon указывает в комментариях, что гостю понадобятся его таблицы страниц в памяти , и разделение страниц в пользовательском пространстве также может быть разделением в 1 ГБ, поэтому обе стороны находятся в разных поддеревьях верхнего уровня PML4. Для продвижения по странице HW необходимо коснуться всех этих страниц таблицы гостевых страниц. Такая патологическая ситуация вряд ли случится случайно.

TLB (и внутренним элементам обхода страниц) разрешено кэшировать некоторые данные таблицы страниц, и не требуется перезапуск обхода страниц с нуля, если ОС не invlpgустановила или не установила новый каталог страниц верхнего уровня CR3. Ничто из этого не является необходимым при изменении страницы с отсутствия на настоящее; x86 на бумаге гарантирует, что он не нужен (поэтому «отрицательное кэширование» отсутствующих PTE не разрешено, по крайней мере, невидимо для программного обеспечения). Таким образом, процессор не может выйти из VMexit, даже если некоторые из гостевых физических страниц таблицы страниц на самом деле отсутствуют.

Счетчики производительности PMU могут быть включены и настроены таким образом, что для инструкции также требуется событие perf для записи в буфер PEBS для этой инструкции. С маской счетчика, сконфигурированной для подсчета только инструкций пользовательского пространства, а не ядра, вполне возможно, что он продолжает пытаться переполнить счетчик и сохранять сэмпл в буфере каждый раз, когда вы возвращаетесь в пользовательское пространство, вызывая ошибку страницы.

Питер Кордес
источник
15
В худшем случае для одной инструкции может быть что-то вроде " push dword [foo" (или даже просто call [foo]), когда все смещено по "границе таблицы указателей каталогов страниц" (добавление до 6 страниц, 6 таблиц страниц, 6 каталогов страниц, 6 PDPT и одного PML4); с включенной и настроенной функцией ЦП «точная выборка на основе событий с буфером PEBS», позволяющая pushдобавлять данные мониторинга производительности в буфер PEBS. Для консервативного «минимального количества страниц, предоставляемых хостом, чтобы гость мог прогрессировать в патологических случаях», я хотел бы по крайней мере 16 страниц.
Брендан
4
Обратите внимание, что такого рода вещи всегда были распространены в CISC-ы архитектур. Некоторые микропроцессоры частично декодируют инструкции и сбрасывают состояние микрорегистра при сбое страницы, но другие не требуют и / или не требуют, чтобы адресные операнды для инструкций loop-y (DBRA на m68k, MOVC3 / MOVC5 на Vax и т. Д.) Находились в регистрах, аналогичных к вашему примеру REP MOVS.
Торек
1
@ Брендан: кто-то посчитал наихудший случай в инструкции VAX около 50 страниц. Я забыл подробности, но вы, очевидно, поместили бы саму инструкцию на границе страницы, использовали бы что-то вроде поиска в таблице перевода с таблицей, охватывающей границу страницы, используйте (rX) [rY] с косвенными ссылками на границах страницы, и скоро. Самые сложные инструкции занимали до 6 операндов (загружая их в r0-r5), и я думаю, что все шесть могут быть двойными косвенными.
Торек
3
ОС может изменить инструкцию, но она также может измениться EIP. Таким образом, есть логический дополнительный вопрос. Какое минимальное количество страниц требуется при условии интеллектуальной схемы исправлений инструкций? Например, скопируйте невыровненное значение в выровненный рабочий буфер, эмулируйте инструкцию и IRET для следующей инструкции.
MSalters
1
Страница с iretинструкцией ОС также должна быть в памяти. Это однобайтовая инструкция, поэтому одна дополнительная страница. Адрес прерывания обработчика ошибок страницы также должен быть в памяти, но это может быть та же страница, что и выше.
Стиг Хеммер