CPUIDを叩く

インラインアセンブラを使ってCPUIDを叩く練習。


参考: http://download.intel.com/jp/developer/jpdoc/Processor_Identification_071405_i.pdf

main.c

#include <stdio.h>

#include "intel_cpuid_flag.h"

#define SUPPORT(f) ( (f) ? "Support" : "None")

char * bits(unsigned int x, unsigned int bit){
	static char buffer[1024];
	int i=0;
	while(bit--){
		buffer[i++] = (x>>bit)&1?'1':'0';
		if(bit%4==0) buffer[i++] = ' ';
	}
	buffer[i] = 0;
	return buffer;
}

void cpuid_0h(void){
	unsigned int _eax, _ebx, _ecx, _edx;
	_eax = _ebx = _ecx = _edx = 0;

	_asm{
		MOV EAX, 01h
		CPUID
		MOV _eax, EAX;
		MOV _ebx, EBX;
		MOV _ecx, ECX;
		MOV _edx, EDX;
	}
	
	puts("CPUID(EAX=01h)");
	puts  ("       28   24   20   16   12    8    4    0");
	printf("EAX: %s (%08X)\n", bits(_eax,32), _eax);
	printf("EBX: %s (%08X)\n", bits(_ebx,32), _ebx);
	printf("ECX: %s (%08X)\n", bits(_ecx,32), _ecx);
	printf("EDX: %s (%08X)\n", bits(_edx,32), _edx);
	
	// EDX
	printf("FPU     : %s\n", SUPPORT(_edx & FPU_FLAG) );
	printf("VME     : %s\n", SUPPORT(_edx & VME_FLAG) );
	printf("DE      : %s\n", SUPPORT(_edx & DE_FLAG) );
	printf("PSE     : %s\n", SUPPORT(_edx & PSE_FLAG) );
	printf("TSC     : %s\n", SUPPORT(_edx & TSC_FLAG) );
	printf("MSR     : %s\n", SUPPORT(_edx & MSR_FLAG) );
	printf("PAE     : %s\n", SUPPORT(_edx & PAE_FLAG) );
	printf("MCE     : %s\n", SUPPORT(_edx & MCE_FLAG) );
	printf("CX8     : %s\n", SUPPORT(_edx & CX8_FLAG) );
	printf("APIC    : %s\n", SUPPORT(_edx & APIC_FLAG) );
//	printf("Reserve1: %s\n", SUPPORT(_edx & 0x0400) );
	printf("SEP     : %s\n", SUPPORT(_edx & SEP_FLAG) );
	printf("MTRR    : %s\n", SUPPORT(_edx & MTRR_FLAG) );
	printf("PGE     : %s\n", SUPPORT(_edx & PGE_FLAG) );
	printf("MCA     : %s\n", SUPPORT(_edx & MCA_FLAG) );
	printf("CMOV    : %s\n", SUPPORT(_edx & CMOV_FLAG) );
	printf("PAT     : %s\n", SUPPORT(_edx & PAT_FLAG) );
	printf("PSE-36  : %s\n", SUPPORT(_edx & PSE36_FLAG) );
	printf("PSNUM   : %s\n", SUPPORT(_edx & PSNUM_FLAG) );
	printf("CLFSH   : %s\n", SUPPORT(_edx & CLFLUSH_FLAG) );
//	printf("Reserve2: %s\n", SUPPORT(_edx & 0x100000) );
	printf("DS      : %s\n", SUPPORT(_edx & DTS_FLAG) );
	printf("ACPI    : %s\n", SUPPORT(_edx & ACPI_FLAG) );
	printf("MMX     : %s\n", SUPPORT(_edx & MMX_FLAG) );
	printf("FXSR    : %s\n", SUPPORT(_edx & FXSR_FLAG) );
	printf("SSE     : %s\n", SUPPORT(_edx & SSE_FLAG) );
	printf("SSE2    : %s\n", SUPPORT(_edx & SSE2_FLAG) );
	printf("SS      : %s\n", SUPPORT(_edx & SS_FLAG) );
	printf("HTT     : %s\n", SUPPORT(_edx & HTT_FLAG) );
	printf("TM      : %s\n", SUPPORT(_edx & TM_FLAG) );
//	printf("Reserve3: %s\n", SUPPORT(_edx & 0x40000000) );
	printf("PBE     : %s\n", SUPPORT(_edx & PBE_FLAG) );
	// ECX
	printf("SSE3    : %s\n", SUPPORT(_ecx & SSE3_FLAG) );
//	printf("Reserve1: %s\n", SUPPORT(_ecx & 0x0002) );
//	printf("Reserve2: %s\n", SUPPORT(_ecx & 0x0004) );
	printf("MONITOR : %s\n", SUPPORT(_ecx & MONITOR_FLAG) );
	printf("DS-CPL  : %s\n", SUPPORT(_ecx & DS_CPL_FLAG) );
//	printf("Reserve3: %s\n", SUPPORT(_ecx & 0x0020) );
//	printf("Reserve4: %s\n", SUPPORT(_ecx & 0x0040) );
	printf("EIST    : %s\n", SUPPORT(_ecx & EIST_FLAG) );
	printf("TM2     : %s\n", SUPPORT(_ecx & TM2_FLAG) );
//	printf("Reserve5: %s\n", SUPPORT(_ecx & 0x0200) );
	printf("CID     : %s\n", SUPPORT(_ecx & CID_FLAG) );
//	printf("Reserve6: %s\n", SUPPORT(_ecx & 0x0800) );
//	printf("Reserve7: %s\n", SUPPORT(_ecx & 0x1000) );
	printf("CX16    : %s\n", SUPPORT(_ecx & CX16_FLAG) );
	printf("xTPR    : %s\n", SUPPORT(_ecx & XTPR_FLAG) );
	// 以下予約領域
	puts("----------------------------------------");
	_eax = _ebx = _ecx = _edx = 0;
}



int main(){
	cpuid_0h();
}

intel_cpuid_flag.h

// これは参考先から引用しました。

#define FPU_FLAG     0x0001 
#define VME_FLAG     0x0002 
#define DE_FLAG      0x0004 
#define PSE_FLAG     0x0008 
#define TSC_FLAG     0x0010 
#define MSR_FLAG     0x0020 
#define PAE_FLAG     0x0040 
#define MCE_FLAG     0x0080 
#define CX8_FLAG     0x0100 
#define APIC_FLAG    0x0200 
#define SEP_FLAG     0x0800 
#define MTRR_FLAG    0x1000 
#define PGE_FLAG     0x2000 
#define MCA_FLAG     0x4000 
#define CMOV_FLAG    0x8000 
#define PAT_FLAG     0x10000 
#define PSE36_FLAG   0x20000 
#define PSNUM_FLAG   0x40000 
#define CLFLUSH_FLAG 0x80000 
#define DTS_FLAG     0x200000 
#define ACPI_FLAG    0x400000 
#define MMX_FLAG     0x800000 
#define FXSR_FLAG    0x1000000 
#define SSE_FLAG     0x2000000 
#define SSE2_FLAG    0x4000000 
#define SS_FLAG      0x8000000 
#define HTT_FLAG     0x10000000 
#define TM_FLAG      0x20000000 
#define PBE_FLAG     0x80000000

#define SSE3_FLAG    0x0001 
#define MONITOR_FLAG 0x0008 
#define DS_CPL_FLAG  0x0010 
#define EIST_FLAG    0x0080 
#define TM2_FLAG     0x0100 
#define CID_FLAG     0x0400 
#define CX16_FLAG    0x2000
#define XTPR_FLAG    0x4000 

結果

CPUID EAX=01h
       28   24   20   16   12    8    4    0
EAX: 0000 0000 0000 0001 0000 0110 0111 0111  (00010677)
EBX: 0000 0001 0000 0100 0000 1000 0000 0000  (01040800)
ECX: 0000 0000 0000 1000 1110 0011 1111 1101  (0008E3FD)
EDX: 1011 1111 1110 1011 1111 1011 1111 1111  (BFEBFBFF)
FPU     : Support
VME     : Support
DE      : Support
PSE     : Support
TSC     : Support
MSR     : Support
PAE     : Support
MCE     : Support
CX8     : Support
APIC    : Support
SEP     : Support
MTRR    : Support
PGE     : Support
MCA     : Support
CMOV    : Support
PAT     : Support
PSE-36  : Support
PSNUM   : None
CLFSH   : Support
DS      : Support
ACPI    : Support
MMX     : Support
FXSR    : Support
SSE     : Support
SSE2    : Support
SS      : Support
HTT     : Support
TM      : Support
PBE     : Support
SSE3    : Support
MONITOR : Support
DS-CPL  : Support
EIST    : Support
TM2     : Support
CID     : None
CX16    : Support
xTPR    : Support
                                                                              • -


今思ったんだが、これってAMD系のCPUでも動くのだろうか。あとで調べるか。


古い情報を使った(IA-64命令について書かれていなかったり)ので、ここで予約領域扱いにしている部分に新しい命令のサポートについて入るものがあるかもしれない。
調べるのが面倒になって投げたry
EAXに渡す値によってCPUID命令の挙動が変わるのがポイントですかね。


CPUの温度情報をcpuid命令で取れるんじゃないかなとか思ったが、甘かったですね。
マザーボードのI/Oチップがその辺の情報を持ってるらしい。
でも機種依存になるだろうし、

GCCで動かしたい

ちなみにこのコードはVC++2010で動作確認しています。このままではGCCでは動きません
MinGWなどのGCCの拡張インラインアセンブラで書いた場合はこんな感じに書けば動くっぽい。

	__asm__(
		"movl $0x01, %%eax;"
		"cpuid;"
		: "=a"(_eax),"=b"(_ebx),"=c"(_ecx),"=d"(_edx)
	);

GCC拡張インラインアセンブラは分かりにくいけど記述量を減らせる。
GCCではAT&T記法なので、VCなどのIntel記法を使ってると違和感がある。
あと、この通りのコードを吐いてくれず、機械が使いやすい形に勝手に代入しだす。
例えば、ECXの部分はEDIにコピーして使いだしたりとか。まぁでも、動いてくれるので良しとする。


GCC拡張インラインアセンブラは分かりにくいので、もう少しメモすると、

		MOV _eax, EAX;
		MOV _ebx, EBX;
		MOV _ecx, ECX;
		MOV _edx, EDX;

にあたる部分を、

		: "=a"(_eax),"=b"(_ebx),"=c"(_ecx),"=d"(_edx)

の1行で行っている。
詳しい記述の解説は別で知ってもらうとして、こんな感じでカンマで区切ることで複数定義できる。
"=a"(_eax)はEAXレジスタの内容を変数_eaxにコピーする意味。
"=b"(_ebx)はEBXレジスタの内容を変数_ebxにry
以下略。
"=r"とすると、適当なレジスタを使ってくれるらしいのでラクだが、
今回は、EAX,EBX,ECX,EDXのそれぞれのレジスタがちゃんと意味を持つので、指定してあげること。


GCCインラインアセンブラ,参考
http://caspar.hazymoon.jp/OpenBSD/annex/gcc_inline_asm.html
http://sci10.org/on_gcc_asm.html