From: kmeaw
Date: 2022-05-05 21:27:51Z
> Почему же я не встречаю intel64, а только amd64?
Потому что AMD придумала набор инструкций для своего процессора
Athlon 64 с микроархитектурой K8, как новую версию K7 (Athlon XP),
значимыми отличаями которой были перенос контроллера памяти на кристалл
и те самые 64-битные расширения набора инструкций, названные AMD64.
> Есть действительно разные наборы команд и поведения для AMD и Intel
> процессоров?
На практике вне системного софта сложно столкнуться с разницей. Из
забавного вспомнил, что процессоры Intel и AMD отличаются поведением при
переполнении счётчика инструкций (EIP) - AMD генерирует исключение,
тогда как Intel просто продолжает выполнять код с (около)нулевого
адреса. Из-за этой разницы получилось взломать Xbox для запуска Linux
без необходимости вставлять в загрузчик проприетарный код или
использования взломанных ключей шифрования (что могло привести к
нарушению DMCA).
Ещё была проблема с инструкцией SYSRET на процессорах Intel. Эта
инструкция предназначена для возврата из системного вызова и копирует
адрес из регистра RCX в RIP, одновременно переключая сегмент кода. Но
если адрес оказался неканоничным (в диапозоне 0x0000_8000_0000_0000 …
0xFFFF_7FFF_FFFF_FFFF, иными словами, у которого биты 47…63 не равны
друг-другу), то будет сгенерировано исключение. Но на процессоре AMD это
происходит после выхода из привилегированного контекста, а на некоторых
процессорах Intel - после, что позволяет переписать одно слово в стеке.
А поскольку обычно SYSRET вызывают из ядра в тот момент, когда уже пора
обратно переключаться в пользовательский процесс, то у последнего есть
возможность непосредственно перед SYSCALL использовать произвольный
указатель на вершину стека просто загрузив его в регистр RSP, который
уже будет восстановлен к моменту вызова SYSRET, контролируя таким
образом адрес переписываемого слова. Пришлось и во FreeBSD, и в Linux, и
в Windows добавлять проверку адреса на каноничность перед SYSRET на
процессорах Intel.
Другая проблема появилась в процессорах Intel с переносом контроллера
прерываний APIC внутрь процессора (примерно в момент появления Pentium с
рабочим напряжением в 3.3 волта). Для настройки контроллера
использовались memory-mapped регистры по адресу 0xFEE0_0000…0xFEE0_1000.
Чтобы не сломать совместимость со старыми процессорами на платформах,
где APIC нет, Intel добавили возможность двигать это четырёхкилобайтное
окошко в другое место адресного пространства с помощью model-specific
registers, позволяя производителю оборудования избежать конфликта с
каким-нибудь другим устройством на шине. А потом на 20 лет все про это
забыли.
Но есть и другая история, без которой проблемы бы не возникло. Чтобы
обойти конкурентов, Intel ещё раньше выпустила процессор 386SL,
предназначенный для ноутбуков - его отличительной особенностью было
сниженное энергопотребление. Тогда впервые появился новый режим работы
процессора - SMM, позволяющий прошивке загрузить код и спрятать его от
ОС. Так, например, можно реализовать уход процессора в сон, когда
прошивка платформы определяет, что сейчас пользователь ничего полезного
не делает, без модификации и добавления каких-либо драйверов в ОС. В
дальнейшем этот режим использовался для ещё более изощрённых хаков -
эмуляции PS/2-клавиатуры для работы с USB-клавиатурой в DOS.
Вся суть режима SMM в том, что из системной памяти вытаскивается
небольшой кусочек, который называется SMRAM, и доступ к этому кусочку
памяти есть только из режима SMM, в который можно перейти по
специальному прерыванию SMI, которое вызывается оборудованием
(USB-контроллером, таймером, контроллером памяти или питания).
Операционная система никак не может потрогать SMRAM, если прошивка
правильно настроила lock bits в соответствующих регистрах.
Оказалось, что APIC находится настолько глубоко в процессорах Intel, что
проверка "не происходит ли обращение к memory-mapped регистрам APIC"
происходит даже в режиме SMM, даже при доступе к SMRAM. Что позволяет
злоумышленнику подвинуть это окошко, чтобы перекрыть важные структуры
данных, лежащие в SMRAM регистрами APIC, таким образом влияя на control
flow. Эта проблема починена начиная с Sandy Bridge возбуждением
исключения, если эти два региона памяти перекрываются.
Из того, с чем можно столкнуться прямо сейчас на практике, в голову
приходят только то, как устроены performance counters и model-specific
registers.
До amd64 когда-то сталкивался с особенностью процессоров Transmeta, в
которых не была реализована на тот момент недокументированная инструкция
CMOVxx, из-за чего i686-бинарники ломались - приходилось патчить ядро,
добавляя в его обработчик исключения #UD (invalid opcode) эмулятор. А
процессоры National Semiconductors Geode не поддерживают инструкцию
endbr32, которую вставляет GCC для защиты кода от Spectre. Процессоры
Intel начиная с Pentium Pro считают это документированным NOP, а Geode
сваливаются с SIGILL.
> приходится жутко страдать и делать CPU detection
Разработчики системного ПО стараются помочь с этим. Например, свежие
версии glibc умеют грузить библиотеки по CPU-зависимому пути, позволяя
положить, например, в /usr/lib64/glibc-hwcaps/x86-64-v4 версии
библиотек, использующие AVX512.
Но всё становится совсем сложно, когда появляются ассиметричные
многоядерные процессоры - у разных ядер могут быть разные instruction
sets. Мне пока неизвестно о наличии в мейнстримных ОС такого
планировщика, который бы умел мигрировать процессы с одного ядра на
другое, учитывая особенности сборки каждой запущенной задачи.
Несколько лет назад компилятор от Intel был замечен в "нечестном"
поведении - в момент запуска программы проверялся CPUID, и если там не
"GenuineIntel", то использовалась не самая оптимальная версия для
некоторых builtins, даже если текущий процессор был способен исполнить
её ожидаемым образом. Что приводило к дополнительным очкам для Intel в
некоторых тестах производительности.
> Или это как-раз то самое: мы работаем на AMD64, но поддерживаем
> чётко-заданный-список-CPU?
Если у разработчика прикладной программы нет цели выжать последние
проценты производительности из CPU, то можно не думать обо всём этом и
использовать то подмножество инструкций, которое работает и на AMD, и на
Intel. Разница уже не столь велика, как раньше на i686 - тогда указание
конкретных микроархитектур в gcc -march= давало куда более заментный
прирост производительности, чем сейчас.
From: Sergey Matveev
Date: 2022-05-06 08:20:38Z
*** kmeaw [2022-05-06 00:24]: [...]
Очень любопытно было почитать это! Спасибо.
>До amd64 когда-то сталкивался с особенностью процессоров Transmeta, в
>которых не была реализована на тот момент недокументированная инструкция CMOVxx
Про это помню, помню. Сам на практике не встречался, но читал.
>Но всё становится совсем сложно, когда появляются ассиметричные
>многоядерные процессоры - у разных ядер могут быть разные instruction
>sets.
Ого, не слышал про такое. Точнее я слышал, с выходом Apple M1, о ядрах
разной эффективности, но не о том, что могут быть ещё и разные инструкции.
Задавался ещё и вопросом а современные GNU/Linux, BSD, whatever учитывают
ли в своих планировщиках особенности разноэффектных ядер.
>Несколько лет назад компилятор от Intel был замечен в "нечестном"
Ага, тоже помню :-)
>Если у разработчика прикладной программы нет цели выжать последние
>проценты производительности из CPU, то можно не думать обо всём этом и
>использовать то подмножество инструкций, которое работает и на AMD, и на
>Intel. Разница уже не столь велика, как раньше на i686 - тогда указание
>конкретных микроархитектур в gcc -march= давало куда более заментный
>прирост производительности, чем сейчас.
Ok, ясно, спасибо. Прирост помню был ощутимый для мультимедиа, когда
комплировать MPlayer для K6-2 с 3DNow!.