有时我们需要编写DOS实模式下的CPU信息诊断程序,但是TurboC++等很多16位C++编译器不支持CPUID指令和32位汇编。于是本文介绍了一种办法,靠内嵌机器码实现了获取CPUID信息。
一、CPUID指令简介
CPUID指令是intel IA32架构下获得CPU信息的汇编指令,可以得到CPU类型,型号,厂商信息,商标信息,序列号,缓存等一系列CPU相关的东西。
CPUID指令一般使用使用eax作为输入参数(某些时候会用到ecx),eax、ebx、ecx、edx作为输出参数。例如这样的汇编代码——
以上代码以1为输入参数,执行cpuid后,eax、ebx、ecx、edx的值都被返回值填充。针对不同的输入参数eax的值,输出参数的意义都不相同。具体含义可参考Intel和AMD的手册——
《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z》. May 2012. http://developer.intel.com/content/www/us/en/processors/processor-identification-cpuid-instruction-note.html
《Intel® Processor Identification and the CPUID Instruction》. April 2012. http://developer.intel.com/content/www/us/en/processors/processor-identification-cpuid-instruction-note.html
《AMD CPUID Specification》. September 2010. http://support./us/Embedded_TechDocs/25481.pdf
二、将CPUID封转为函数
用汇编语言来做软件开发的成本很高,所以一般使用C++等高级语言来做软件开发,于是我们希望能将CPUID指令封装为函数。
但现在遇到一个难题——虽然TurboC++等16位C++编译器支持asm语句来实现内嵌汇编,但它只支持16位汇编,不支持CPUID指令和32位汇编。该怎么办呢?
幸好TurboC++支持在asm语句插入机器码。比如我们可以将CPUID指令的机器码插入到asm语句中——
“0f a2”是CPUID指令的机器码,在Intel手册上可以查到——
现在我们又遇到一个难题——CPUID指令使用了eax、ebx、ecx、edx这几个32位寄存器,但TurboC++中的内嵌汇编只支持16位汇编,既只能访问ax、bc、cx、dx。
这时,可以使用66h前缀来调整操作数的大小(66H prefix - Operand Size Override)。给一条16位指令加上66H前缀时,它的操作数——既寄存器宽度变为了32位。比如原16位指令用的是ax,加上66H前缀时变为eax。注意此时的内存寻址方式仍是16位的,仅是操作数变为32位,例如——
注:在16位代码中要想使用32位寻址,得使用67H前缀来调整地址大小(67H prefix - Address Size Override)。本文没有用到,读者可自行学习。
利用上述知识,我们可以构造CPUID函数了——
getcpuidex(DWORD* (NULL==pdwout4) ] = id;
pdwout4[] = subid;
mov di, pdwout4;
db ; mov cx, [di+];
db ; mov ax, [di];
db ; xor dx, dx;
db ; xor bx, bx;
db ; db ;
db ; mov [di], ax;
db ; mov [di+], bx;
db ; mov [di+], cx;
db ; mov [di+], dx;
注解——
1. 因为CPUID的返回值会占满eax、ebx、ecx、edx这四个通用寄存器,所以利用di寄存器来寻址。
2. 为了减少寄存器占用,将输入参数id、subid写入pdwout4,然后再在汇编代码中通过di寄存器寻址来加载参数。
3. 虽然对于CPUID指令来说,不需要“xor edx, edx”等指令将edx、dbx清零。但考虑某些16位编译器对内嵌汇编的支持性不够好,手工写上dx、bx后能让编译器知道该内嵌汇编代码用到这2个寄存器,不会将这2个寄存器挪作他用。
上面我们采用了内嵌机器码的形式实现了getcpuidex函数。但手工编写机器码是很容易出错的,怎样才能验证机器码是正确的呢?这时可以先将程序编译为exe,然后利用反汇编器来解析机器码。例如我用IDA Pro打开编译后的exe,因为现在程序很短小,能很快的找到调用cpuid指令的地方——
反汇编的结果与我们预想的相同,验证通过。
三、常用的CPUID功能
3.1 取得CPU厂商(Vendor)
把eax = 0作为输入参数,可以得到CPU的厂商信息。
cpuid指令执行以后,会返回一个12字符的厂商信息,前四个字符的ASC码按低位到高位放在ebx,中间四个放在edx,最后四个字符放在ecx。比如说,对于intel的cpu,会返回一个“GenuineIntel”的字符串,返回值的存储格式为——
31 23 15 07 00
EBX| u (75)| n (6E)| e (65)| G (47)
EDX| I (49)| e (65)| n (6E)| i (69)
ECX| l (6C)| e (65)| t (74)| n (6E)
代码为——
cpu_getvendor(* (NULL==pvendor)
getcpuid(dwBuf,
*(DWORD*)&pvendor[] = dwBuf[];
*(DWORD*)&pvendor[] = dwBuf[];
*(DWORD*)&pvendor[] = dwBuf[];
pvendor[] =
3.2 取得CPU商标(Brand)
在我的电脑上点击右键,选择属性,可以在窗口的下面看到一条CPU的信息,这就是CPU的商标字符串。CPU的商标字符串也是通过cpuid得到的。由于商标的字符串很长(48个字符),所以不能在一次cpuid指令执行时全部得到,所以intel把它分成了3个操作,eax的输入参数分别是0x80000002,0x80000003,0x80000004,每次返回的16个字符,按照从低位到高位的顺序依次放在eax, ebx, ecx, edx。
为了稳妥,最好事先调用0x80000000号功能检查一下扩展功能号的范围。
代码为——
cpu_getbrand(* (NULL==pbrand)
getcpuid(dwBuf, (dwBuf[] < )
getcpuid((DWORD*)&pbrand[], );
getcpuid((DWORD*)&pbrand[], );
getcpuid((DWORD*)&pbrand[], );
pbrand[] =
四、全部代码
全部代码——
#include <stdio.h> szBuf[ getcpuidex(DWORD* (NULL==pdwout4) ] = id;
pdwout4[] = subid;
mov di, pdwout4;
db ; mov cx, [di+];
db ; mov ax, [di];
db ; xor dx, dx;
db ; xor bx, bx;
db ; db ;
db ; mov [di], ax;
db ; mov [di+], bx;
db ; mov [di+], cx;
db ; mov [di+], dx; getcpuid(DWORD* cpu_getvendor(* (NULL==pvendor)
getcpuid(dwBuf,
*(DWORD*)&pvendor[] = dwBuf[];
*(DWORD*)&pvendor[] = dwBuf[];
*(DWORD*)&pvendor[] = dwBuf[];
pvendor[] = cpu_getbrand(* (NULL==pbrand)
getcpuid(dwBuf, (dwBuf[] < )
getcpuid((DWORD*)&pbrand[], );
getcpuid((DWORD*)&pbrand[], );
getcpuid((DWORD*)&pbrand[], );
pbrand[] = main(
五、编译和运行
在Turbo C++ 3.0和Borland C++ 3.1中编译通过。将编译后的exe放在C盘根目录。然后重启电脑进入DOS实模式,运行成功——
在Windows的命令提示符中也运行成功——
参考文献——
《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z》. May 2012. http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html
《Intel® Processor Identification and the CPUID Instruction》. April 2012. http://developer.intel.com/content/www/us/en/processors/processor-identification-cpuid-instruction-note.html
《AMD CPUID Specification》. September 2010. http://support./us/Embedded_TechDocs/25481.pdf
《DOS下使用32位寄存器及CPUID》。senvei著。http://hi.baidu.com/senvei/blog/item/4820c11b4ad4e09b6438db64.html
《x86/x64 指令编码内幕(适用于 AMD/Intel)》中的“2. 深入了解 prefix”。mik著。http://www./x64/doc3.html
《在C++中使用cpuid指令获得CPU信息》。闲人(freeman)著。http://freeman.cnblogs.com/archive/2005/08/30/226128.html
http://en./wiki/CPUID
http://baike.baidu.com/view/1829765.htm
源码下载——
https://files.cnblogs.com/zyl910/getcpuid.rar