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