カタバミさんのプログラミングノート

日曜プログラマーがプログラミング関係のメモを記録するブログです。

CPUID(インラインアセンブリ)の覚書

はじめに

アセンブリの命令コードCPUIDを使用することでCPUの基本情報を取得することができます。Microsoft Visual C++ではインラインアセンブリ__asmステートメント)(32ビット環境限定)あるいはintrin.hの__cpuid関数(32ビットおよび64ビット)を使用することでCPUIDを呼び出すことができます。

この投稿では32ビット環境におけるインラインアセンブリ版CPUIDの基本的な使い方を記述します。

インラインアセンブリ版CPUID

呼び出し方

CPUIDはアセンブリの命令コード(OPコード)で、Microsoft Visual C++からは次の様に呼び出すことができます。

__asm {
    mov eax, 0
    cpuid
}

__asm {...}は{...}内部がインラインアセンブラであることを示します。

インラインアセンブラの一行目であるmov eax, 0はeaxという名前のレジスタ(有り体に言えば変数)に0を代入することを意味します。二行目のcpuidはCPUがサポートするCPUIDという命令を呼び出す指示です。

一見、cpuidは引数も戻り値も取らないように見えますが、実際には呼び出し前に設定されたeaxレジスタの値によって処理を変え、結果をeax、ebx、ecx、edxの4つのレジスタに代入しています。

以下のコードを実行することでその意味を確認することができます。

CPUIDの基本的な呼び出しサンプル

#include <iostream>
#include <iomanip>

void main()
{
    int op = 0x00000000;
    int a, b, c, d;
    __asm {
        mov eax, op
        cpuid
        mov a, eax
        mov b, ebx
        mov c, ecx
        mov d, edx
    }

    std::cout << "CPUID" << std::endl;
    std::cout << "op.: 0x" << std::setfill('0') << std::setw(8) << std::hex << op << std::endl;
    std::cout << "eax: 0x" << std::setfill('0') << std::setw(8) << std::hex << a << std::endl;
    std::cout << "ebx: 0x" << std::setfill('0') << std::setw(8) << std::hex << b << std::endl;
    std::cout << "ecx: 0x" << std::setfill('0') << std::setw(8) << std::hex << c << std::endl;
    std::cout << "edx: 0x" << std::setfill('0') << std::setw(8) << std::hex << d << std::endl;

    std::cout << "<PRESS ANY KEY>" << std::endl;
    std::cin.get();
}
CPUID
op.: 0x00000000
eax: 0x0000000d
ebx: 0x756e6547
ecx: 0x6c65746e
edx: 0x49656e69
<PRESS ANY KEY>

呼び出せる機能の数

上のようにCPUIDでは呼び出し前のeaxによって異なる戻り値を得ることができます。このとき、eaxに指定できる値には一般的な値(0x00000000~、0x80000000)とそれ以外(0x10000000、……)が存在します。

一般的な値では0x00000000で実行後のeaxに0x00000000から始まる基本機能の最大値、0x80000000で0x80000000から始まる拡張機能の最大値を取得することができます。

サンプルコードと実行結果

#include <iostream>
#include <iomanip>

void main()
{
    int op = 0x00000000;
    int number_of_basic_functions;
    int number_of_extended_functions;
    __asm {
        // 基本機能の最大値の取得
        mov eax, 0x00000000
        cpuid
        mov number_of_basic_functions, eax
        // 拡張機能の最大値の取得
        mov eax, 0x80000000
        cpuid
        mov number_of_extended_functions, eax
    }

    std::cout << "CPUIDの機能範囲" << std::endl;
    std::cout << "基本機能: 0x00000000-0x" << std::setw(8) << std::setfill('0')
        << std::hex << number_of_basic_functions << std::endl;
    std::cout << "拡張機能: 0x80000000-0x" << std::setw(8) << std::setfill('0')
        << std::hex<< number_of_extended_functions << std::endl;
    std::cout << "<PRESS ANY KEY>" << std::endl;
    std::cin.get();
}

インラインアセンブラ内部でのコメントは「;」で十分ですが、Qiitaが未対応みたいなので「//」を用いています。

CPUIDの機能範囲
基本機能: 0x00000000-0x0000000d
拡張機能: 0x80000000-0x80000008
<PRESS ANY KEY>

呼び出せる機能をまとめて呼び出してみる

CPUIDは引数としてeax、戻り値としてeax~edxの4つのレジスタしか使用しないので、呼び出し前のeaxの値を変えて繰り返し呼び出すことで全ての結果を得ることができます。戻り値の詳細はマニュアルを参照しなければなりませんが、実際に呼び出すコードと結果は以下のようになります。

#include <iostream>
#include <iomanip>
#include <algorithm>

void cpuid(int cpu_info[4], int info_type)
{
    __asm
    {
        mov eax, info_type
            cpuid
            push eax
            mov eax, cpu_info
            mov[eax + 3 * type cpu_info], edx
            mov[eax + 2 * type cpu_info], ecx
            mov[eax + 1 * type cpu_info], ebx
            mov ebx, eax
            pop eax
            mov[ebx + 0 * type cpu_info], eax
    }
}

inline void cpuid_0x00000000(unsigned int& number_of_basic_functions, std::string& brand_name)
{
    int cpu_info[4];
    cpuid(cpu_info, 0x00000000);

    number_of_basic_functions = cpu_info[0];
    brand_name.resize(4 * 3 + 1);
    auto it = std::copy_n((const char*)(cpu_info + 1), 4, brand_name.begin());
    it = std::copy_n((const char*)(cpu_info + 3), 4, it);
    it = std::copy_n((const char*)(cpu_info + 2), 4, it);
    brand_name[12] = '\0';
}

void output_cpuid_range(unsigned int begin, unsigned int end)
{
    int cpu_info[4];
    for (unsigned int i = begin; i < end; i++)
    {
        cpuid(cpu_info, i);
        std::cout
            << "op. " << std::setw(8) << std::setfill('0') << std::hex << i << ", "
            << "eax: " << std::setw(8) << std::setfill('0') << std::hex << cpu_info[0] << ", "
            << "ebx: " << std::setw(8) << std::setfill('0') << std::hex << cpu_info[1] << ", "
            << "ecx: " << std::setw(8) << std::setfill('0') << std::hex << cpu_info[2] << ", "
            << "edx: " << std::setw(8) << std::setfill('0') << std::hex << cpu_info[3] << std::endl;
    }
}

void main()
{
    // CPUの名前を取得
    unsigned int number_of_basic_functions;
    std::string brand_name;
    cpuid_0x00000000(number_of_basic_functions, brand_name);
    std::cout << "CPUID for " << brand_name.c_str() << std::endl;

    // 基本命令の列挙
    int cpu_info[4];
    cpuid(cpu_info, 0x00000000);
    output_cpuid_range(0x00000000, cpu_info[0]);

    // 拡張命令の列挙
    cpuid(cpu_info, 0x80000000);
    output_cpuid_range(0x80000000, cpu_info[1]);

    std::cout << "PRESS ANY KEY" << std::endl;
    std::cin.get();
}
CPUID for GenuineIntel
op. 00000000, eax: 0000000d, ebx: 756e6547, ecx: 6c65746e, edx: 49656e69
op. 00000001, eax: 000206a7, ebx: 00100800, ecx: 0dbae3bf, edx: bfebfbff
op. 00000002, eax: 76035a01, ebx: 00f0b0ff, ecx: 00000000, edx: 00ca0000
op. 00000003, eax: 00000000, ebx: 00000000, ecx: 00000000, edx: 00000000
op. 00000004, eax: 1c004121, ebx: 01c0003f, ecx: 0000003f, edx: 00000000
op. 00000005, eax: 00000040, ebx: 00000040, ecx: 00000003, edx: 00000120
op. 00000006, eax: 00000075, ebx: 00000002, ecx: 00000009, edx: 00000000
op. 00000007, eax: 00000000, ebx: 00000000, ecx: 00000000, edx: 00000000
op. 00000008, eax: 00000000, ebx: 00000000, ecx: 00000000, edx: 00000000
op. 00000009, eax: 00000000, ebx: 00000000, ecx: 00000000, edx: 00000000
op. 0000000a, eax: 07300803, ebx: 00000000, ecx: 00000000, edx: 00000603
op. 0000000b, eax: 00000001, ebx: 00000001, ecx: 00000100, edx: 00000000
op. 0000000c, eax: 00000000, ebx: 00000000, ecx: 00000000, edx: 00000000
PRESS ANY KEY