Как вызвать инструкцию cpuid в рамках Mac?

Я хочу использовать инструкцию cpuid для определения характеристик процессора Intel. Я нашел заголовок cpuid.h в Kernel.framework, поэтому добавил Kernel.framework в свой проект и включил <Kernel/i386/cpuid.h> в исходный файл. Это произвело

kern/kern_types.h: No such file or directory

чего я не понимаю. Но функция do_cpuid, которую, я думаю, я хочу использовать, определена встроенно, поэтому я попытался просто скопировать ее в свой исходный код.

static inline void
do_cpuid(uint32_t selector, uint32_t *data)
{
    asm("cpuid"
        : "=a" (data[0]),
          "=b" (data[1]),
          "=c" (data[2]),
          "=d" (data[3])
        : "a"(selector));
}

Это дало мне ошибки:

error: can't find a register in class 'BREG' while reloading 'asm'
error: 'asm' operand has impossible constraints

Поиск этой ошибки в Google привел меня к этому вопросу: asm">Проблема на Mac: не удается найти регистр в классе BREG при перезагрузке asm

Но решение этого вопроса заключалось в использовании параметра dynamic-no-pic (настройка сборки GCC_DYNAMIC_NO_PIC), а в справке Xcode по настройкам сборки говорится: «Не подходит для общих библиотек (которые должны быть независимыми от позиции)». Я создаю фреймворк, который, как мне кажется, считается общей библиотекой. Итак, как я могу заставить это работать?


person JWWalker    schedule 31.08.2012    source источник


Ответы (2)


Это довольно загадочное сообщение об ошибке:

error: can't find a register in class 'BREG' while reloading 'asm'
error: 'asm' operand has impossible constraints

происходит из-за того, что одно из ограничений не разрешено. В данном случае это EBX. При компиляции 32-битного кода с опцией -fPIC (позиционно-независимый код) для перемещения используется регистр EBX. Его нельзя использовать в качестве вывода или отображать как затертый регистр.

Хотя большинство людей предлагают компилировать со специальными флагами компилятора, функцию можно переписать для поддержки x86-64/IA32 и PIC/не -PIC, изменив ассемблерный код, чтобы сохранить сам регистр EBX и восстановить его после. Это можно сделать с помощью такого кода:

#include <inttypes.h>

static inline void
do_cpuid(uint32_t selector, uint32_t *data)
{
    __asm__ __volatile__ (
        "xchg %%ebx, %k[tempreg]\n\t"
        "cpuid\n\t"
        "xchg %%ebx, %k[tempreg]\n"
        : "=a" (data[0]),
          [tempreg]"=&r" (data[1]),
          "=c" (data[2]),
          "=d" (data[3])
        : "a"(selector),
          "c"(0));
}

Существенным изменением является то, что значение data[1] будет возвращено в доступном регистре, выбранном компилятором. Ограничение =&r сообщает компилятору, что какой бы регистр он ни выбрал, он не может быть ни одним из других входных регистров (мы заранее затираем регистр кодом xchg). В коде мы обмениваем EBX на доступный регистр, выбранный компилятором. Затем мы обмениваем его обратно после этого. По завершении EBX будет содержать исходное значение, а выбранный свободный регистр будет содержать то, что было возвращено CPUID. Затем шаблон ассемблера переместит содержимое этого свободного регистра в data[1].

По сути, мы обошли проблему, позволив компилятору выбирать свободный регистр. Компилятор достаточно умен, чтобы не использовать EBX, если он связан, потому что он может использоваться для перемещаемого кода. В 64-битном коде EBX не используется для перемещения, как в 32-битном коде, поэтому он может быть доступен для использования.

Проницательный наблюдатель мог бы заметить xor %%ecx,%%ecx. Это изменение я внес независимо от проблемы EBX. В настоящее время считается хорошей практикой очищать ECX, поскольку некоторые процессоры AMD могут возвращать устаревшие значения, если ECX не равно нулю. Если вы разрабатываете только для платформы Mac, отличной от PPC, в этом изменении нет необходимости, поскольку Apple использует процессоры Intel, которые не демонстрируют такого поведения.

Как правило, EBX является особенным в 32-битном перемещаемом/PIC-коде, поэтому компилятор изначально жаловался на его загадочное сообщение.

person Michael Petch    schedule 13.09.2015
comment
@DavidWohlferd: на данный момент я бы просто подумал об использовании встроенной функции cpuid. - person Michael Petch; 11.09.2019
comment
@DavidWohlferd: я бросил сдачу. - person Michael Petch; 11.09.2019

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


sysctl предоставляет много информации о процессоре машины в macOS. Для начала введите man sysctl в Терминале и посмотрите на все с префиксом machdep.cpu. Возьмем, к примеру, feature_bits вместо uint64_t cpuid_features(void); из cpuid.h:

sysctl machdep.cpu.feature_bits дает вам фактическую битовую маску функций, хотя и не очень удобочитаемую. sysctl machdep.cpu.features делает свою работу немного лучше и предоставляет вам в основном понятные аббревиатуры.

Теперь сделать это программно еще интереснее и на самом деле вполне осуществимо:

#include <sys/sysctl.h>

// ...

int64_t value = 0;
size_t valueByteSize = sizeof(value);
int error = sysctlbyname("machdep.cpu.feature_bits", &value, &valueByteSize, NULL, 0);
if (!error)
{
   // Check for the next best bit defined in cpuid.h
   // Since you can't build with cpuid.h directly, just copy over the definitions you actually need
   bool hasFPU = value & CPUID_FEATURE_FPU;
}
person bfx    schedule 15.10.2019