![]() |
Современные процессоры имеют весьма внушительный набор команд. Общее их кол-во можно подсчитать открыв том(2) Интеловского мануала"Instruction Set Referense". На различных площадках народ сильно преувеличивает утверждая, будто число опкодов давно перевалило за тысячу. Чтобы-уж наверняка и ради собственного интереса, я скопировал из оглавления указанной доки в блокнот весь перечень, и к своему удивлению на выходе получил всего 632 строки текста, в каждой из которых одна инструкция. Судя по-всему, на данный момент именно такой объём полностью удовлетворяет нашим требованиям, иначе монархи из Intel не упустили-бы возможности записать на свой счёт очередной новый опкод. Из всего этого листа, в данной статье предлагаю рассмотреть одну из интересных инструкций под названием CPUID, которая призвана идентифицировать бортовой процессор CPU.
Под капотом.. 1. Основная идея; 2. Стандартные функции CPUID; 3. Расширенные функции; 4. Практика; 5. Постфикс. -------------------------------------------------------- 1. Основная идея При включении машины, системный BIOS/EFI должен каким-либо образом опознать установленный на материнской плате CPU, чтобы настроить под него чипсет и всё остальное. Нужно сказать, что на первом витке эволюции вплоть до древних 16-битных процессоров i80286 в этом не было необходимости, поскольку архитектура компьютера была примитивной, с довольно устойчивым состоянием. Но тут пришёл 32-битный процессор нового поколения i80386, который мог работать как в 16, так и в 32-битном защищённом режиме. Здесь-то и всплыла проблема. Инженеры Intel с присущим им оптимизмом решили её просто – они жёстко запрограммировали проц, чтобы на старте он возвращал свою "половую" принадлежность в регистровой паре EСX:EDX. Но дальше-больше.. Инструкцию CPUID принёс с собой следующий процессор i80486, когда благодаря механизму PAE (Physical Address Extension, расширение физического адреса) 32-битному CPU стало доступно сразу три ступени памяти, разрядностью 16, 32 и 36-бит. Теперь, биосу требовался паспорт процессора, иначе он просто не мог должным образом настроить ответственные за распределение ОЗУ регистры чипсета. Так возникла необходимость в идентификации CPU, что вынудило Intel включить в набор команд инструкцию CPUID. Первая его спецификация от Intel датируется 1993-годом. Если говорить об инструкциях в целом, то каждая из них является совокупностью микрокоманд. Когда инструкция простая (типа INC, ADD, SUB и т.д.), для неё достаточно одной микрокоманды, а если сложная – она собирается из нескольких таких команд. На этапе изготовления процессора, производитель зашивает в специально предназначенную для этих целей встроенную ROM-память весь поддерживаемый набор микрокоманд, из которых в конечном счёте и собираются разнообразные инструкции. Блок процессора под названием "микро-секвенсер" (microsequencer) считывает микрокоманды из хранилища ROM, и по-требованию передаёт их в декодер инструкций. Таким образом, чтобы включить в набор команд процессора какую-нибудь новую инструкцию, в большинстве случаях производителю достаточно добавить лишь пару ключевых микрокоманд. На этапе производства процессора отследить все его ошибки невозможно, что влечёт за собой различные хард-глюки. По этой причине, производитель предусматривает обновление микро-кодов уже при эксплуатации готового продукта. Обновы всегда кратны 2Кб и их можно найти на сайтах Intel и AMD. Включённый в пакет прошивальщик сначала идентифицирует бортовой CPU, и если его модель/степпинг/ревизия совпадает с прошивкой, то заливает обновлённые микро-коды не в ROM процессора, а в системный BIOS, от куда проц потом их и стягивает. Intel давно уже практикует рассылку своих обновлений всем производителям биосов, чтобы они включали их в свой код. В конечном итоге, поддержка биосами современных процессоров определяется в первую очередь наличием соответствующей прошивки. На уровне-же микроархитектуры CPUID устроена так, что помимо прочего, в постоянную память ROM производитель закладывает порядка 30-ти основных листов с детальной информацией о своём продукте, и некоторое кол-во (под)листов. В документации эти листы назвали "Leaf", а подлисты "Subleaf". Перед тем-как вызвать инструкцию CPUID, мы должны предварительно указать номер конкретного листа в регистре EAX, и если этот лист имеет вложенный (под)лист, то его номер занести ещё и в регистр ECX. На программном уровне, запрос каждого из листов представляет собой отдельную функцию, о чём прямым текстом и сообщается в спецификации CPUID. В таблице ниже я перечислил все доступные мне функции и их назначения. К сожалению, в наличии оказалась только спека от 2012-года, и более свежих данных мне так и не удалось выудить из сети (если кто поделится, буду благодарен). Причём действительна представленная табличка только для процессоров Intel, поскольку у AMD нумерация функций совсем иная. Как по мне, так двум этим гигантам пора-бы уже не выносить нам мозг, а "выпить за мировую", чтобы согласовывать между собой хотя-бы такие элементарные вещи. Ан-нет.. нужно чтобы всё было через известное место. В силу того, что на обеих моих машинах (бук и стационар) установлены процессоры Intel, всё нижесказанное будет относиться исключительно к продуктам Intel. https://forum.antichat.xyz/attachmen...d58e436cd2.png Обратите внимание, что вложенные подлисты имеют только функции EAX=04,07,0В и 0Dh (выделены красным). Например если мы запросим инструкцию Код:
CPUID.EAX=4:ECX=0Код:
CPUID.EAX=4Код:
EAX=4:ECX=2Спецификация CPUID для процессоров INTEL (2012 год) Спецификация CPUID для процессоров AMD (2010 год) 2. Стандартные функции Начнём по-порядку и рассмотрим на конкретных примерах, какого рода информацию возвращают все функции CPUID. В некоторых случаях, это поможет осуществить направленные на конкретный процессор атаки, ну или просто идентифицировать его, чтобы пустить код по нужной ветке. К примеру если мы написали приложение с использованием современных инструкций SSE4 или AVX, то это приложение может попасть на платформу, процессор которой вообще не поддерживает данный тип инструкций. Значит перед запуском софта нужно обязательно позвать CPUID и проверить в нём нужные биты. В любом случае знания всегда идут на шаг впереди их отсутствия, а потому профит по-любому будет. 2.0. Функция EAX=0. (Vendor-ID, and Largest Standard Function) Данная функция возвращает в EAX общее число поддерживаемых процессором стандартных функций, и в остальные три регистра сбрасывает строку с именем производителя. Для Intel и AMD эта строка будет иметь вид "GenuineIntel" и "AuthenticAMD" соответственно. На процессоре своего стационара я получил всего 14 указанных в таблице выше функций, при этом Код:
0x08Код:
0x0CC-подобный: Код:
xor eax2.1. ФункцияEAX=1. (Feature Information) Очередная функция возвращает характерные особенности бортового процессора, где можно будет найти его: Family/Model/Stepping, идентификатор текущего контролёра прерываний APIC, макс.кол-во поддерживаемых ядер (не путать с реальным кол-вом), размер линейки кэш-памяти, и в 64-битах регистровой пары ECX:EDX разнообразные свойства, типа поддержка инструкций SSE4/AVX, гипер-трейдинг HT, x2APIC, RDRAND и многое другое (см.спецификацию). Чтобы вывести все эти 64 типа информации на консоль, придётся чекать их биты по отдельности например инструкцией Код:
BTC-подобный: Код:
mov eaxКак видно из листа "FeatureFlags", мой бородатый проц не знаком с инструкциями SSE4/AVX, а лишь поддерживает макс.SSSE3. Имеет встроенный датчик температуры "TermalMonitor2" (TM2), в режиме энергосбережения может сохранять в специальной область памяти значения всех регистров CPU/FPU (xsave/fxsave соответственно), при переходе в ядро вместо устаревшего Код:
SYSCALLКод:
SYSENTERКод:
CLFLUSH2.2. ФункцияEAX=2. (Cache Descriptors) Код:
CPUID.EAX=2Код:
CPUID.EAX=2Каждый дескриптор кэша имеет размер 1-байт, и является закодированным значением определённой строки. На всякий-пожарный, я оформил все эти строки в виде таблички, и сохранил их в инклуд (см.скрепку в подвале статьи). Поскольку результат мы получим в четырёх 4-байтных регистрах EAX,EBX,ECX,EDX, итого получается 4х4=16 байт. Тогда общее кол-во дескрипторов будет 15, без учёта регистра-счётчика(AL). Если дескриптор имеет значение нуль, значит он пустой и не используется. Теперь, для разбора дескрипторов последовательно перемещаемся от самого старшего байта всех регистров вниз, к младшему их байту (ну или наоборот). Код реализации парсинга дескрипторов и фрагмент таблицы из спецификации представлены ниже: C-подобный: Код:
cinvoke printfПроясним немного ситуацию с устройством кэша TLB процессора.. Не секрет, что процессоры заранее кэшируют нужную информацию, как-только им предоставляется малейшая возможность обращения к оперативной памяти ОЗУ. При этом контролёр памяти старается оттяпать из неё как-можно больше данных, для чего предусмотрен пакетный режим чтения. Значение "Burst-Length" (длина пакета) выставляется чипсетом на макс. =8, в результате чего контролёр повторяет операцию чтения по 8-байтной шине памяти 8-раз. Итого за одно обращение к ОЗУ контролёр читает про запас как-минимум пакетами по 64-байта данных, которые помещает в кэш-линейки "64 byte line size" (см.скрин выше). Кэши L1 бывают двух типов – отдельно для инструкций, отдельно для данных. А вот кэши L2 и L3 уже общие, без разделения на инструкции и данные. Однако кроме кэша данных, в процессоре имеется ещё и кэш для хранения адресов страниц виртуальной памяти, к которым он обращался последний раз. Этот кэш назвали TLB, или "Translation Lookaside Buffer" (буфер ассоциативной трансляции). Проблема в том, что для доступа к памяти ОЗУ, транслятор контролёра памяти (в процессоре) сначала должен преобразовать текущий адрес из виртуального в физический, на что может уйти до 100-тактов процессора. По современным меркам, это чистой воды расточительство, поэтому один раз преобразовав вирт.адрес, процессор запоминает его в своём кэше TLB. Как показала практика, такой алго даёт существенный прирост скорости при чтении данных из оперативной памяти. 2.3.ФункцияEAX=3. (Processor Serial Number) Если процессор фирменный и благополучно прошёл все надзорные инстанции, производитель может записать в него серийный номер продукта. Но в наше время это скорее исключение, чем правило, и лично мне такие пока не встречались. 2.4.ФункцияEAX=4. (Deterministic Cache Parameters) Функция Код:
CPUID.EAX=4Вызов этой функции, в регистре EBX возвращает информацию об ассоциативности кэшей, кол-ве блоков "Way", и числу записей "Sets". Используя эти данные можно вычислить и размер кэша, который не возвращает функция Код:
CPUID.EAX=4C-подобный: Код:
Cache SizeНиже представлен возможный вариант вывода результата этой функции на консоль. Здесь я при первом вызове получаю кол-во ядер, и дальше в цикле зову эту функцию с инкрементом счётчика в ECX, чтобы получить инфу о кэшах всех уровней: C-подобный: Код:
mov eaxЗдесь стоит отметить, что для хранения информации о кэшах, современные процессоры 64-бит используют только данную функцию Код:
CPUID.EAX=4Код:
CPUID.EAX=2Код:
0xFFКод:
CPUID.EAX=2Код:
0xFFhttps://forum.antichat.xyz/attachmen...1574ea56ce.png 3. Расширенные функции Остальные стандартные функции не представляют особого интереса, и при желании вы можете по-экспериментировать с ними сами. А вот рассмотреть набор расширенных функций CPUID вполне себе стоит. Во времена своего младенчества, Intel и AMD разделили инструкцию CPUID пополам. Чтобы хоть как-то отличаться от Intel, инженеры AMD забрали себе старшую половину функций, начиная с 0x80000000. Однако позже всё утряслось и теперь обоим производителям принадлежит весь диапазон, хотя и нумерация по назначению абсолютно разная. 3.0.ФункцияEAX=8000_0000h (Largest Extended Function) Эта функция особо не напрягается, и возвращает в единственный регистр EAX общее число поддерживаемых расширенных функций. На обоих своих процессорах я получаю значение EAX=8. 3.1.ФункцияEAX=8000_0001h (Extended Feature Bits) Очередная функция возвращает в регистр EDX флаги расширенных возможностей процессора. C-подобный: Код:
mov eaxПосмотрим на флаг "XD bit". В документации AMD этот бит называется NX, от слова "No eXecution", а Intel обозвала его XD – "eXecution Disable". Это самый старший бит(64) в записях виртуальных страниц PTE (Page Table Entry), который предотвращает исполнение данных. Для более детального ознакомления с ним отправляю вас к статье"DEP и ASLR – игра без правил", где ему была посвящена одна глава. Как видно из скрина, мой древний проц поддерживает и его, и архитектуру Intel-64, хотя в упор не видит гигантских виртуальных страниц размером 1Gb (по-умолчанию 4Кб), и не знаком с современной инструкций генератора рандома Код:
RDTSCPКод:
SYSCALLКод:
SYSENTER3.2.ФункцииEAX=8000_0002/3/4 (Processor Brand String) Цепочка этих трёх функций возвращает имя процессора, как его окрестил производитель. Под строку брэнда производитель резервирует 48 байт во-встроенной памяти ROM, поэтому чтобы получить её полностью, нужно вызвать эти функции последовательно 3-раза. При каждом запросе в регистры EAX,EBX,ECX,EDX будет возвращаться новая информация, поэтому не забываем сохранять её в выделенный буфер. В примере ниже я поместил счётчик цикла в переменную "count", и на каждой итерации увеличиваю номер функции в EAX: C-подобный: Код:
mov ediВидимо хреновый контроль ведёт Intel над своими подчинёнными, о чём свидетельствует оформление строки. Зачем вставлять лишние пробелы в середину строки, когда можно дополнить её до 48-байт этими-же пробелами в конце? Здесь явно что-то не чисто.. 3.3.ФункцияEAX=8000_0006h (Extended L2 Cache Features) Ещё одна функция для сбора информации о кэше, только теперь исключительно об общем L2. Прямым текстом сообщает его размер и длину линейки, и в закодированном виде ассоциативность (мне лень было декодировать). Бьёт без промаха точно в цель на любых процессорах Intel, хоть 32, хоть 64-бит. C-подобный: Код:
mov eax3.4.ФункцияEAX=8000_0008h (Virtual & Physical Address Sizes) Это последняя функция из списка расширенных и возвращает способность процессора адресовать физическую и виртуальную память ОЗУ. Обратите внимание, что хоть процессор и 64-битный, память он адресует максимум 48-бит. Если вогнать это значение в степень двойки (зовите на помощь калькулятор), то получим макс.поддерживаемую современными процессорами память = Код:
281.474.976.710.656C-подобный: Код:
mov eax4. Пример кода для сбора информации CPUID В заключении приведу готовый пример кода, который соберёт всю представленную выше информацию. C-подобный: Код:
format pe console5. Постфикс Опыты выше показали, что CPUID изначально не способен возвратить некоторую информацию. К примеру в нём нет данных типа: кодовое имя ядра, порог рабочих напряжений, тип сокета, потребляемая мощность, и значение производственного тех.процесса. Судя по всему, софт на подобии CPU-Z хранит это всё в своих таблицах, которые программисты скурпулёзно собирают ручками. Базы подобного рода пишутся исходя из значений "Family/Model/Stepping", предоставляемые нам функцией Код:
CPUID.EAX=1В скрепке можно найти инклуд с описанием дескрипторов кэшей, и исполняемый файл для тестов. Всем удачи, пока! |
Очень познавательно. Спасибо.
|
Как всегда браво. Надо extern функцию для СИ собрать, для посмотреть на досуге.
|
Спасибо, Marylin! Коротко, классно и познавательно!
|
Респект и уважуха Marylin.Как обычно, коротко и ясно.
|
Кратко, четко и по факту!
|
| Время: 12:33 |