• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Статья CPUID – идентификация процессора

Современные процессоры имеют весьма внушительный набор команд. Общее их кол-во можно подсчитать открыв том(2) Интеловского мануала . На различных площадках народ сильно преувеличивает утверждая, будто число опкодов давно перевалило за тысячу. Чтобы-уж наверняка и ради собственного интереса, я скопировал из оглавления указанной доки в блокнот весь перечень, и к своему удивлению на выходе получил всего 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.


CPUID_func.png


Обратите внимание, что вложенные подлисты имеют только функции EAX=04,07,0В и 0Dh (выделены красным). Например если мы запросим инструкцию CPUID.EAX=4:ECX=0, то в регистрах EAX,EBX,ECX,EDX процессор вернёт нам информацию о своём кэше L1. Теперь, чтобы получить характеристики кэша L2, мы должны повторно вызвать CPUID.EAX=4, только на этот раз сменить номер (под)листа на ECX=1. Соответственно для кэша L3 будет уже EAX=4:ECX=2, и т.д. в том-же духе. Вне зависимости от разрядности процессора 32 или 64-бит, CPUID всегда возвращает информацию в первые четыре регистра общего назначения EAX,EBX,ECX,EDX. Надеюсь суть вы уловили, и вооружившись спекой можно теперь смело пробираться вглубь.





2. Стандартные функции

Начнём по-порядку и рассмотрим на конкретных примерах, какого рода информацию возвращают все функции CPUID. В некоторых случаях, это поможет осуществить направленные на конкретный процессор атаки, ну или просто идентифицировать его, чтобы пустить код по нужной ветке. К примеру если мы написали приложение с использованием современных инструкций SSE4 или AVX, то это приложение может попасть на платформу, процессор которой вообще не поддерживает данный тип инструкций. Значит перед запуском софта нужно обязательно позвать CPUID и проверить в нём нужные биты. В любом случае знания всегда идут на шаг впереди их отсутствия, а потому профит по-любому будет.


2.0. Функция EAX=0. (Vendor-ID, and Largest Standard Function)

Данная функция возвращает в EAX общее число поддерживаемых процессором стандартных функций, и в остальные три регистра сбрасывает строку с именем производителя. Для Intel и AMD эта строка будет иметь вид "GenuineIntel" и "AuthenticAMD" соответственно. На процессоре своего стационара я получил всего 14 указанных в таблице выше функций, при этом 0x08 и 0x0C находятся в резерве. А вот на буке с 64-битным процессором счётчик показывает уже 25 поддерживаемых функций, но как говорилось выше спеки на него нет. Пример вызова функции выглядит так:


C-подобный:
        xor     eax,eax             ;//<--- функция(0)
        cpuid
        mov     dword[buff+0],ebx   ;// кидаем производителя в буфер
        mov     dword[buff+4],edx
        mov     dword[buff+8],ecx

       cinvoke  printf,<10,' ***** EAX=0 ***************',\
                        10,'   Func supported.:  %d',\
                        10,'   Vendor.........:  %s',10,0>,eax,buff

eax_0.png



2.1. Функция EAX=1. (Feature Information)

Очередная функция возвращает характерные особенности бортового процессора, где можно будет найти его: Family/Model/Stepping, идентификатор текущего контролёра прерываний APIC, макс.кол-во поддерживаемых ядер (не путать с реальным кол-вом), размер линейки кэш-памяти, и в 64-битах регистровой пары ECX:EDX разнообразные свойства, типа поддержка инструкций SSE4/AVX, гипер-трейдинг HT, x2APIC, RDRAND и многое другое (см.спецификацию). Чтобы вывести все эти 64 типа информации на консоль, придётся чекать их биты по отдельности например инструкцией BT (bit-test), как в примере ниже. Если тестируемый бит взведён, инструкция взводит флаг процессора(CF):


C-подобный:
         mov     eax,1
         cpuid
         mov     [feature1],ecx    ;//<--- запомнить свойства в переменных
         mov     [feature2],edx

         movzx   ecx,bl      ;// Brand index
         movzx   edx,bh      ;// Cache line size
         shl     edx,3       ;// ^^^^^
         shr     ebx,16      ;//
         movzx   esi,bl      ;//
         movzx   edi,bh      ;//
        cinvoke  printf,<10,' ***** EAX=1 ****************',\
                         10,'   Family & Model.:  0x%x',\
                         10,'   Brand index....:  0x%02x',\
                         10,'   Cache line.....:  %d byte',\
                         10,'   CPU cores......:  %d',\
                         10,'   APIC id........:  %d',\
                         10,'   Feature flags..:  ',0>, eax,ecx,edx,esi,edi

         bt      [feature2],25       ;// проверить бит(25) в регистре EDX
         jnc     @f                  ;// пропустить, если он сброшен
        cinvoke  printf,<'SSE,'>     ;// иначе: есть поддержка инструкций SSE
 @@:     bt      [feature2],26       ;// проверить на SSE2..
         jnc     @f                  ;//
        cinvoke  printf,<'SSE2,'>    ;// есть поддержка
 @@:     bt      [feature1],0        ;// и т.д..

eax_1.png


Как видно из листа "FeatureFlags", мой бородатый проц не знаком с инструкциями SSE4/AVX, а лишь поддерживает макс.SSSE3. Имеет встроенный датчик температуры "TermalMonitor2" (TM2), в режиме энергосбережения может сохранять в специальной область памяти значения всех регистров CPU/FPU (xsave/fxsave соответственно), при переходе в ядро вместо устаревшего SYSCALL использует SYSENTER, способен очищать линейки кэша L1 при помощи инструкции CLFLUSH, и прочее. Описание всех этих аббревиатур имеется в спецификации, так-что курите её.


2.2. Функция EAX=2. (Cache Descriptors)

CPUID.EAX=2 возвращает дескрипторы кэша и TLB. На выходе, младшие 8-бит регистра EAX (AL) содержат значение, которое определяет, сколько раз нужно потянуть за CPUID.EAX=2, чтобы получить полный образ подсистемы кэш процессора. В большинстве случаях процессоры возвращают в AL значение(1), значит достаточно только одного вызова инструкции CPUID.

Каждый дескриптор кэша имеет размер 1-байт, и является закодированным значением определённой строки. На всякий-пожарный, я оформил все эти строки в виде таблички, и сохранил их в инклуд (см.скрепку в подвале статьи). Поскольку результат мы получим в четырёх 4-байтных регистрах EAX,EBX,ECX,EDX, итого получается 4х4=16 байт. Тогда общее кол-во дескрипторов будет 15, без учёта регистра-счётчика(AL). Если дескриптор имеет значение нуль, значит он пустой и не используется.

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


C-подобный:
        cinvoke  printf,<10,10,' ***** EAX=2 ****************',0>
@@:      mov     eax,2
         cpuid
         dec     al            ;// повторить CPUID.EAX=2 по значению в AL
         jnz     @b
         push    ebx ecx edx 

         call    printCpuid2     ;// распечатать дескрипторы из EAX
         pop     eax             ;//<--- из EDX
         call    printCpuid2
         pop     eax             ;//<--- из ECX
         call    printCpuid2
         pop     eax             ;//<--- из EBX
         call    printCpuid2
;//-----------------------------
printCpuid2:
         mov     ecx,4            ;// байт в регистре
@next:   xor     ebx,ebx          ;// под очередной дескриптор
         mov     bl,al            ;// взять мл.байт из источника
         shr     eax,8            ;// удалить этот байт в источнике
         or      ebx,ebx          ;// нулевой дескриптор?
         jz      @f               ;//
         call    printCacheDescriptor  ;// если нет..
@@:      loop    @next            ;//
ret

printCacheDescriptor:
         push    eax ecx          ;//
         mov     ecx,[tSize]      ;// размер таблицы в инклуде
         mov     esi,cacheTable   ;// её адрес
@@:      cmp     [esi],ebx        ;// поиск соответствия
         jnz     @fuck            ;// промах!
         add     esi,4            ;// иначе: берём адрес соответствующей строки
        cinvoke  printf,<10,'%s',0>,dword[esi]
         jmp     @f               ;// ..и на выход.
@fuck:   add     esi,8            ;// если промах, смещаемся к сл.дескриптору в таблице..
         loop    @b               ;//
@@:      pop     ecx eax          ;//
         ret

eax_2.png


Проясним немного ситуацию с устройством кэша 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 имеет подлист, поэтому вызывать её нужно с регистром ECX. Как и функция EAX=2, она возвращает информацию о кэшах процессора L1,2,3, только без кэшей TLB. Индекс в регистре ECX указывает, о каком кэше следует возвращать информацию. Чтобы получить информацию о кэшах всех уровней, мы должны вызывать её в цикле, пока в битах EAX[4:0] не получим нуль. Порядок возврата кэшей не регламентируется и может быть изменен по усмотрению Intel. Это означает, что при вызове функции с ECX=0 мы можем получить инфу например о кэше L3 и т.д. Бонусом, индекс(0) в регистре ECX возвращает ещё и кол-во ядер процессора, в старших битах EAX[31:26].

Вызов этой функции, в регистре EBX возвращает информацию об ассоциативности кэшей, кол-ве блоков "Way", и числу записей "Sets". Используя эти данные можно вычислить и размер кэша, который не возвращает функция CPUID.EAX=4 в явном виде. Спецификация предлагает нам такую формулу для этих целей:


C-подобный:
Cache Size in Bytes = (Ways +1) x (Partitions +1) x (Line Size +1) x (Sets +1)
                    = (EBX[31:22]+1) x (EBX[21:12]+1) x (EBX[11:0]+1 x (ECX +1)

Ниже представлен возможный вариант вывода результата этой функции на консоль. Здесь я при первом вызове получаю кол-во ядер, и дальше в цикле зову эту функцию с инкрементом счётчика в ECX, чтобы получить инфу о кэшах всех уровней:


C-подобный:
         mov     eax,4          ;// лист(4)
         xor     ecx,ecx        ;// подлист(0)
         cpuid
         shr     eax,26         ;//<--- оставить в EAX только биты 31:26
         inc     eax
        cinvoke  printf,<10,\
                         10,' ***** EAX=4 ****************',\
                         10,'   CPU cores......:  %d',0>,eax
;//----------------------------
@@:      mov     eax,4
         mov     ecx,[count]          ;// счётчик в переменной
         cpuid
         push    ecx ebx ebx ebx eax
         and     eax,11111b           ;// оставить 5 мл.бит в EAX
         or      eax,eax              ;// проверить на окончание цикла
         jz      @stop04              ;// да – на выход

         mov     esi,cacheType      ;// иначе: ESI линк на таблицу в инклуде
         dec     eax                ;// делаем из EAX смещение в таблице
         shl     eax,2              ;// ^^^^
         add     esi,eax            ;// ESI = указатель на строку с именем кэша

         pop     ebx                ;// EBX = уровень кэша L1,2,3
         shr     ebx,5              ;//
         and     ebx,111b           ;//
        cinvoke  printf,<10,'   Cache..........:  L%d %-12s',0>,ebx,dword[esi]

         pop     eax ebx ecx edx
         and     eax,0xfff       ;//<----- считаем размер кэша по формуле..
         inc     eax
         shr     ebx,12
         and     ebx,0x3ff
         inc     ebx
         shr     ecx,22
         and     ecx,0x3ff
         inc     ecx
         imul    eax,ebx
         imul    eax,ecx
         inc     edx
         imul    eax,edx
         shr     eax,10
        cinvoke  printf,<' %dK',0>,eax

         inc     [count]     ;// ECX +1
         jmp     @b          ;// уйти на повтор цикла..

@stop04: add     esp,4*5

eax_4.png


Здесь стоит отметить, что для хранения информации о кэшах, современные процессоры 64-бит используют только данную функцию CPUID.EAX=4, полностью игнорируя ранее рассмотренную CPUID.EAX=2, которая возвращает дескрипторы кэшей. В этом случае, при вызове EAX=2 на выходе получаем 0xFF вместо дескрипторов, что означает см.функцию EAX=4. Вот фрагмент из описания функции CPUID.EAX=2 с дескриптором 0xFF:


eax_4_1.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,0x80000001
         cpuid
         mov     [feature1],edx   ;//<--- флаги в переменную

        cinvoke  printf,<10,10,' ***** EAX=8000_0001h ********',\
                            10,'   Ext features....:  ',0>

         bt      [feature1],11
         jnc     @f
        cinvoke  printf,<'SYSCALL,'>   ;//<--- чекаем биты EDX..
 @@:     bt      [feature1],20
         jnc     @f
        cinvoke  printf,<'NX, '>
 @@:     bt      [feature1],26
         jnc     @f
        cinvoke  printf,<'1Gb-Pages, '>
 @@:     bt      [feature1],27
         jnc     @f
        cinvoke  printf,<'RDTSCP, '>
 @@:     bt      [feature1],29
         jnc     @f
        cinvoke  printf,<'Intel64, '>
 @@:

eax_8001.png


Посмотрим на флаг "XD bit".
В документации AMD этот бит называется NX, от слова "No eXecution", а Intel обозвала его XD – "eXecution Disable". Это самый старший бит(64) в записях виртуальных страниц PTE (Page Table Entry), который предотвращает исполнение данных. Для более детального ознакомления с ним отправляю вас к статье
"DEP и ASLR – игра без правил", где ему была посвящена одна глава. Как видно из скрина, мой древний проц поддерживает и его, и архитектуру Intel-64, хотя в упор не видит гигантских виртуальных страниц размером 1Gb (по-умолчанию 4Кб), и не знаком с современной инструкций генератора рандома RDTSCP. Для перехода в ядро при вызовах API-функций инструкция SYSCALL уже давно не юзается даже процессорами Pentium, и ей на смену пришла свежая SYSENTER.


3.2. Функции EAX=8000_0002/3/4 (Processor Brand String)

Цепочка этих трёх функций возвращает имя процессора, как его окрестил производитель. Под строку брэнда производитель резервирует 48 байт во-встроенной памяти ROM, поэтому чтобы получить её полностью, нужно вызвать эти функции последовательно 3-раза. При каждом запросе в регистры EAX,EBX,ECX,EDX будет возвращаться новая информация, поэтому не забываем сохранять её в выделенный буфер. В примере ниже я поместил счётчик цикла в переменную "count", и на каждой итерации увеличиваю номер функции в EAX:


C-подобный:
        mov     edi,buff         ;// адрес приёмника для STOSD
        mov     eax,0x80000002   ;//<--- начинаем цикл с функции(2)
@@:     push    eax
        cpuid              ;//<--- очередной вызов CPUID
        nop
        stosd              ;//<--- сохраняю все регистры по адресу EDI
        mov     eax,ebx
        stosd
        mov     eax,ecx
        stosd
        mov     eax,edx
        stosd
        pop     eax
        inc     eax        ;//<--- сл.функция..
        dec     [count]    ;//<--- счётчик -1
        jnz     @b

       cinvoke  printf,<10,' ***** EAX=8000_0002/3/4 ******',\
                        10,'   Name...........:  %s',10,0>,buff

eax_8002.png


Видимо хреновый контроль ведёт Intel над своими подчинёнными, о чём свидетельствует оформление строки. Зачем вставлять лишние пробелы в середину строки, когда можно дополнить её до 48-байт этими-же пробелами в конце? Здесь явно что-то не чисто..


3.3. Функция EAX=8000_0006h (Extended L2 Cache Features)

Ещё одна функция для сбора информации о кэше, только теперь исключительно об общем L2. Прямым текстом сообщает его размер и длину линейки, и в закодированном виде ассоциативность (мне лень было декодировать). Бьёт без промаха точно в цель на любых процессорах Intel, хоть 32, хоть 64-бит.


C-подобный:
         mov     eax,0x80000006
         cpuid
         push    ecx
         xchg    eax,ecx
         and     eax,0xff    ;//<--- cache-line
         pop     ebx
         shr     ebx,16      ;//<--- cache-size

        cinvoke  printf,<10,10,' ***** EAX=8000_0006h ********',\
                            10,'   L2 cache size...:  %d Kb',\
                            10,'   L2 line  size...:  %d byte',0>,ebx,eax

eax_8006.png



3.4. Функция EAX=8000_0008h (Virtual & Physical Address Sizes)

Это последняя функция из списка расширенных и возвращает способность процессора адресовать физическую и виртуальную память ОЗУ. Обратите внимание, что хоть процессор и 64-битный, память он адресует максимум 48-бит. Если вогнать это значение в степень двойки (зовите на помощь калькулятор), то получим макс.поддерживаемую современными процессорами память = 281.474.976.710.656 байт:


C-подобный:
         mov     eax,0x80000008
         cpuid
         push    eax
         and     eax,0xFF  ;//<--- EAX = физ.память
         pop     ebx
         shr     ebx,8     ;//<--- EBX = вирт.память

        cinvoke  printf,<10,10,' ***** EAX=8000_0008h ********',\
                            10,'   Physical addr...:  %d bit',\
                            10,'   Virtual  addr...:  %d bit',0>,eax,ebx

eax_8008.png



4. Пример кода для сбора информации CPUID

В заключении приведу готовый пример кода, который соберёт всю представленную выше информацию.


C-подобный:
format   pe console
entry    start
;//-----------
section '.inc' data readable
include 'win32ax.inc'
include 'equates/cpuid.inc'
;---------
.data
cacheType   dd  szInst,szData,szUni
szInst      db  'Instruction',0
szData      db  'Data',0
szUni       db  'Unified',0

cpu         db  128 dup(0)
count       dd  3
feature1    dd  0
feature2    dd  0
buff        db  0
;---------
.code
start:  invoke  SetConsoleTitle,<'*** CPUID example ***',0>

;//*************************************
        xor     eax,eax
        cpuid
        mov     dword[buff+0],ebx       ;кидаем производителя в буфер
        mov     dword[buff+4],edx
        mov     dword[buff+8],ecx
       cinvoke  printf,<10,' ***** EAX=0 ***************',\
                        10,'   Func supported.:  %d',\
                        10,'   Vendor.........:  %s',10,0>,eax,buff

;//*************************************
        mov     edi,buff
        mov     eax,0x80000002
@@:     push    eax
        cpuid
        nop
        stosd
        mov     eax,ebx
        stosd
        mov     eax,ecx
        stosd
        mov     eax,edx
        stosd
        pop     eax
        inc     eax
        dec     [count]
        jnz     @b
       cinvoke  printf,<10,' ***** EAX=8000_0002/3/4 ******',\
                        10,'   Name...........:  %s',10,0>,buff

;//*************************************
         mov     eax,1
         cpuid
         mov     [feature1],ecx
         mov     [feature2],edx

         movzx   ecx,bl      ;// Brand index
         movzx   edx,bh      ;// Cache line size
         shl     edx,3       ;// ^^^^^
         shr     ebx,16      ;//
         movzx   esi,bl      ;//
         movzx   edi,bh      ;//
        cinvoke  printf,<10,' ***** EAX=1 ****************',\
                         10,'   Family & Model.:  0x%x',\
                         10,'   Brand index....:  0x%02x',\
                         10,'   Cache line.....:  %d byte',\
                         10,'   CPU cores......:  %d',\
                         10,'   APIC id........:  %d',\
                         10,'   Feature flags..:  ',0>,\
                         eax,ecx,edx,esi,edi

         bt      [feature2],25
         jnc     @f
        cinvoke  printf,<'SSE,'>
 @@:     bt      [feature2],26
         jnc     @f
        cinvoke  printf,<'SSE2,'>
 @@:     bt      [feature1],0
         jnc     @f
        cinvoke  printf,<'SSE3,'>
 @@:     bt      [feature1],9
         jnc     @f
        cinvoke  printf,<'SSSE3,'>
 @@:     bt      [feature1],19
         jnc     @f
        cinvoke  printf,<'SSE4.1,'>
 @@:     bt      [feature1],20
         jnc     @f
        cinvoke  printf,<'SSE4.2,'>
 @@:     bt      [feature1],28
         jnc     @f
        cinvoke  printf,<'AVX,'>
 @@:     bt      [feature2],23
         jnc     @f
        cinvoke  printf,<'MMX,'>
 @@:     bt      [feature2],28
         jnc     @f
        cinvoke  printf,<'HT,'>
 @@:     bt      [feature1],2
         jnc     @f
        cinvoke  printf,<'DTES64,'>
 @@:     bt      [feature1],3
         jnc     @f
        cinvoke  printf,<'MWAIT,'>
 @@:     bt      [feature1],5
         jnc     @f
        cinvoke  printf,<'VMX,'>
 @@:     bt      [feature1],6
         jnc     @f
        cinvoke  printf,<'SMX,'>
 @@:     bt      [feature1],7
         jnc     @f
        cinvoke  printf,<'EIST,'>
 @@:     bt      [feature1],8
         jnc     @f
        cinvoke  printf,<'TM2,'>
 @@:     bt      [feature1],13
         jnc     @f
        cinvoke  printf,<'CMPXCHG16,'>
 @@:     bt      [feature1],18
         jnc     @f
        cinvoke  printf,<'DCA,'>
 @@:     bt      [feature1],21
         jnc     @f
        cinvoke  printf,<'x2APIC,'>
 @@:     bt      [feature1],22
         jnc     @f
        cinvoke  printf,<'MOVBE,'>
 @@:     bt      [feature1],25
         jnc     @f
        cinvoke  printf,<'AES,'>
 @@:     bt      [feature1],26
         jnc     @f
        cinvoke  printf,<'XSAVE,'>
 @@:     bt      [feature1],29
         jnc     @f
        cinvoke  printf,<'F16C,'>
 @@:     bt      [feature1],30
         jnc     @f
        cinvoke  printf,<'RDRAND,'>
 @@:     bt      [feature2],1
         jnc     @f
        cinvoke  printf,<'VME,'>
 @@:     bt      [feature2],2
         jnc     @f
        cinvoke  printf,<'DE,'>
 @@:     bt      [feature2],3
         jnc     @f
        cinvoke  printf,<'PCE,'>
 @@:     bt      [feature2],4
         jnc     @f
        cinvoke  printf,<'TSC,'>
 @@:     bt      [feature2],6
         jnc     @f
        cinvoke  printf,<'PAE,'>
 @@:     bt      [feature2],9
         jnc     @f
        cinvoke  printf,<'APIC,'>
 @@:     bt      [feature2],11
         jnc     @f
        cinvoke  printf,<'SYSENTER,'>
 @@:     bt      [feature2],17
         jnc     @f
        cinvoke  printf,<'PSE-36,'>
 @@:     bt      [feature2],19
         jnc     @f
        cinvoke  printf,<'CLFLUSH,'>
 @@:     bt      [feature2],24
         jnc     @f
        cinvoke  printf,<'FXSAVE,'>
 @@:

;//*************************************
        cinvoke  printf,<10,10,' ***** EAX=2 ****************',0>
@@:      mov     eax,2
         cpuid
         dec     al
         jnz     @b
         push    ebx ecx edx

         call    printCpuid2
         pop     eax
         call    printCpuid2
         pop     eax
         call    printCpuid2
         pop     eax
         call    printCpuid2

;//************************************
         xor     ecx,ecx
         mov     eax,4
         cpuid
         shr     eax,26
         inc     eax
        cinvoke  printf,<10,\
                         10,' ***** EAX=4 ****************',\
                         10,'   CPU cores......:  %d',0>,eax
@@:      mov     eax,4
         mov     ecx,[count]
         cpuid
         push    ecx ebx ebx ebx eax
         and     eax,11111b
         or      eax,eax
         jz      @stop04

         mov     esi,cacheType
         dec     eax
         shl     eax,2
         add     esi,eax

         pop     ebx
         shr     ebx,5
         and     ebx,111b
        cinvoke  printf,<10,'   Cache..........:  L%d %-12s',0>,ebx,dword[esi]

         pop     eax ebx ecx edx
         and     eax,0xfff     ;// line size
         inc     eax
         shr     ebx,12
         and     ebx,0x3ff     ;// partition
         inc     ebx
         shr     ecx,22
         and     ecx,0x3ff
         inc     ecx
         imul    eax,ebx
         imul    eax,ecx
         inc     edx
         imul    eax,edx
         shr     eax,10
        cinvoke  printf,<' %dK',0>,eax

         inc     [count]
         jmp     @b

@stop04: add     esp,4*5
;//*************************************
         mov     eax,0x80000000
         cpuid
         sub     eax,0x80000000
        cinvoke  printf,<10,10,' ***** EAX=8000_0000h *************************',\
                         10,'   Func supported..:  %d',0>,eax

         mov     eax,0x80000001
         cpuid
         mov     [feature1],edx
        cinvoke  printf,<10,10,' ***** EAX=8000_0001h ********',\
                            10,'   Ext features....:  ',0>

         bt      [feature1],11
         jnc     @f
        cinvoke  printf,<'SYSCALL,'>
 @@:     bt      [feature1],20
         jnc     @f
        cinvoke  printf,<'NX, '>
 @@:     bt      [feature1],26
         jnc     @f
        cinvoke  printf,<'1Gb-Pages, '>
 @@:     bt      [feature1],27
         jnc     @f
        cinvoke  printf,<'RDTSCP, '>
 @@:     bt      [feature1],29
         jnc     @f
        cinvoke  printf,<'Intel64, '>
 @@:

;//************************************
         mov     eax,0x80000006
         cpuid
         push    ecx
         xchg    eax,ecx
         and     eax,0xff    ;// line
         pop     ebx
         shr     ebx,16
        cinvoke  printf,<10,10,' ***** EAX=8000_0006h ********',\
                            10,'   L2 cache size...:  %d Kb',\
                            10,'   L2 line  size...:  %d byte',0>,ebx,eax

;//************************************
         mov     eax,0x80000008
         cpuid
         push    eax
         and     eax,0xFF
         pop     ebx
         shr     ebx,8
        cinvoke  printf,<10,10,' ***** EAX=8000_0008h ********',\
                            10,'   Physical addr...:  %d bit',\
                            10,'   Virtual  addr...:  %d bit',0>,eax,ebx


@exit:  cinvoke  _getch
        cinvoke  exit,0
;//---------------------
printCpuid2:
         mov     ecx,4
@next:   xor     ebx,ebx
         mov     bl,al
         shr     eax,8
         or      ebx,ebx
         jz      @f
         call    printCacheDescriptor
@@:      loop    @next
ret

printCacheDescriptor:
         push    eax ecx
         mov     ecx,[tSize]
         mov     esi,cacheTable
@@:      cmp     [esi],ebx
         jnz     @fuck
         add     esi,4
        cinvoke  printf,<10,'%s',0>,dword[esi]
         jmp     @f
@fuck:   add     esi,8
         loop    @b
@@:      pop     ecx eax
         ret
;//---------------------
section '.idata' import data readable
library  msvcrt,'msvcrt.dll',kernel32,'kernel32.dll'
include  'api\msvcrt.inc'
include  'api\kernel32.inc'

Result.png



5. Постфикс

Опыты выше показали, что CPUID изначально не способен возвратить некоторую информацию. К примеру в нём нет данных типа: кодовое имя ядра, порог рабочих напряжений, тип сокета, потребляемая мощность, и значение производственного тех.процесса. Судя по всему, софт на подобии CPU-Z хранит это всё в своих таблицах, которые программисты скурпулёзно собирают ручками. Базы подобного рода пишутся исходя из значений "Family/Model/Stepping", предоставляемые нам функцией CPUID.EAX=1. В любом случае, теперь мы знаем, на что способен "зверь" под названием CPUID, и будем делать соответствующие выводы.

В скрепке можно найти инклуд с описанием дескрипторов кэшей, и исполняемый файл для тестов. Всем удачи, пока!
 

Вложения

  • CPUID.ZIP
    4 КБ · Просмотры: 302
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!