欢迎光临散文网 会员登陆 & 注册

DOS下和WINDOWS下用CPUID读CPU信息

2023-12-18 10:33 作者:失传技术  | 我要投稿

DOS下和WINDOWS下用CPUID读CPU信息

liwu_111

于 2008-04-09 12:10:00 发布

阅读量1.9k

 收藏 1

点赞数 1

文章标签: windows dos vc++ c

版权

一 :WINDOWS下,用VC++来编译。

         格式:   unsigned long DBase;
char cA[13];
char cB[49];
_asm
  {
    xor eax, eax
    cpuid
    mov DBase      ,eax
    mov dword ptr cA    ,ebx
    mov dword ptr cA+4  ,edx 
    mov dword ptr cA+8  ,ecx 


    mov eax, 0x80000002
    cpuid
    mov dword ptr cB    , eax
    mov dword ptr cB + 4  , ebx
    mov dword ptr cB + 8  , ecx
    mov dword ptr cB + 12  ,edx

    mov eax, 0x80000003
    cpuid
    mov dword ptr cB + 16  , eax
    mov dword ptr cB + 20  , ebx
    mov dword ptr cB + 24  , ecx
    mov dword ptr cB + 28  , edx

    mov eax, 0x80000004
    cpuid
    mov dword ptr cB + 32  , eax
    mov dword ptr cB + 36  , ebx
    mov dword ptr cB + 40  , ecx
    mov dword ptr cB + 44  , edx
  }

二:DOS下,需要用DJGPP来编译(支持32位)。

          格式:

#define CPUID(op,a,b,c,d)  /
__asm__ __volatile__("cpuid":"=a"(a),"=b"(b),"=c"(c),"=d"(d):"a"(op))

 

 MSN:   twinliwu@hotmail.com  [TurboC++] 如何在DOS下的16位C++编译器中使用CPUID指令获取CPU信息

  有时我们需要编写DOS实模式下的CPU信息诊断程序,但是TurboC++等很多16位C++编译器不支持CPUID指令和32位汇编。于是本文介绍了一种办法,靠内嵌机器码实现了获取CPUID信息。

一、CPUID指令简介

  CPUID指令是intel IA32架构下获得CPU信息的汇编指令,可以得到CPU类型,型号,厂商信息,商标信息,序列号,缓存等一系列CPU相关的东西。

  CPUID指令一般使用使用eax作为输入参数(某些时候会用到ecx),eax、ebx、ecx、edx作为输出参数。例如这样的汇编代码——

mov    eax, 1cpuid...

 

  以上代码以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.amd.com/us/Embedded_TechDocs/25481.pdf

二、将CPUID封转为函数

  用汇编语言来做软件开发的成本很高,所以一般使用C++等高级语言来做软件开发,于是我们希望能将CPUID指令封装为函数。
  但现在遇到一个难题——虽然TurboC++等16位C++编译器支持asm语句来实现内嵌汇编,但它只支持16位汇编,不支持CPUID指令和32位汇编。该怎么办呢?

  幸好TurboC++支持在asm语句插入机器码。比如我们可以将CPUID指令的机器码插入到asm语句中——

   _asm{        ...        db 0x0f; db 0xa2;    // cpuid        ...    }

 

  “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位,例如——

   _asm{        db 0x66; mov ax, [di];    // mov eax, DWORD PTR [di]    }

注:在16位代码中要想使用32位寻址,得使用67H前缀来调整地址大小(67H prefix - Address Size Override)。本文没有用到,读者可自行学习。

  利用上述知识,我们可以构造CPUID函数了——

// 获得CPU信息(加强版)//// pdwout4: 返回eax, ebx, ecx, edx这四个寄存器的值。// id: 功能ID。既CPUID指令的eax参数。// subid: 子功能ID。既CPUID指令的ecx参数。void getcpuidex(DWORD* pdwout4, DWORD id, DWORD subid) {    if (NULL==pdwout4)    return;    pdwout4[0] = id;    // eax    pdwout4[2] = subid;    // ecx    _asm{        // load. 读取参数到寄存器        mov di, pdwout4;    // 准备用di寻址pdwout4        db 0x66; mov cx, [di+8];    // mov ecx, DWORD PTR [di+8]        db 0x66; mov ax, [di];    // mov eax, DWORD PTR [di]        db 0x66; xor dx, dx;    // xor edx, edx        db 0x66; xor bx, bx;    // xor ebx, ebx        // CPUID        db 0x0f; db 0xa2;    // cpuid        // save. 将寄存器保存到pdwout4        db 0x66; mov [di], ax;    // mov DWORD PTR [di], eax        db 0x66; mov [di+4], bx;    // mov DWORD PTR [di+4], ebx        db 0x66; mov [di+8], cx;    // mov DWORD PTR [di+8], ecx        db 0x66; mov [di+12], dx;    // mov DWORD PTR [di+12], edx    } }

 

  注解——
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厂商(Vendor)//// result: 成功时返回字符串的长度(一般为12)。失败时返回0。// pvendor: 接收厂商信息的字符串缓冲区。至少为13字节。int cpu_getvendor(char* pvendor) {    DWORD dwBuf[4];    if (NULL==pvendor)    return 0;    // Function 0: Vendor-ID and Largest Standard Function    getcpuid(dwBuf, 0);    // save. 保存到pvendor    *(DWORD*)&pvendor[0] = dwBuf[1];    // ebx: 前四个字符    *(DWORD*)&pvendor[4] = dwBuf[3];    // edx: 中间四个字符    *(DWORD*)&pvendor[8] = dwBuf[2];    // ecx: 最后四个字符    pvendor[12] = '\0';    return 12; }

 

3.2 取得CPU商标(Brand)

  在我的电脑上点击右键,选择属性,可以在窗口的下面看到一条CPU的信息,这就是CPU的商标字符串。CPU的商标字符串也是通过cpuid得到的。由于商标的字符串很长(48个字符),所以不能在一次cpuid指令执行时全部得到,所以intel把它分成了3个操作,eax的输入参数分别是0x80000002,0x80000003,0x80000004,每次返回的16个字符,按照从低位到高位的顺序依次放在eax, ebx, ecx, edx。
  为了稳妥,最好事先调用0x80000000号功能检查一下扩展功能号的范围。

  代码为——

// 取得CPU商标(Brand)//// result: 成功时返回字符串的长度(一般为48)。失败时返回0。// pbrand: 接收商标信息的字符串缓冲区。至少为49字节。int cpu_getbrand(char* pbrand) {    DWORD dwBuf[4];    if (NULL==pbrand)    return 0;    // Function 0x80000000: Largest Extended Function Number    getcpuid(dwBuf, 0x80000000UL);    if (dwBuf[0] < 0x80000004UL)    return 0;    // Function 80000002h,80000003h,80000004h: Processor Brand String    getcpuid((DWORD*)&pbrand[0], 0x80000002UL);    // 前16个字符    getcpuid((DWORD*)&pbrand[16], 0x80000003UL);    // 中间16个字符    getcpuid((DWORD*)&pbrand[32], 0x80000004UL);    // 最后16个字符    pbrand[48] = '\0';    return 48; }

 

四、全部代码

  全部代码——

#include <stdio.h>typedef unsigned long DWORD;char szBuf[64]; DWORD dwBuf[4];// 获得CPU信息(加强版)//// pdwout4: 返回eax, ebx, ecx, edx这四个寄存器的值。// id: 功能ID。既CPUID指令的eax参数。// subid: 子功能ID。既CPUID指令的ecx参数。void getcpuidex(DWORD* pdwout4, DWORD id, DWORD subid) {    if (NULL==pdwout4)    return;    pdwout4[0] = id;    // eax    pdwout4[2] = subid;    // ecx    _asm{        // load. 读取参数到寄存器        mov di, pdwout4;    // 准备用di寻址pdwout4        db 0x66; mov cx, [di+8];    // mov ecx, DWORD PTR [di+8]        db 0x66; mov ax, [di];    // mov eax, DWORD PTR [di]        db 0x66; xor dx, dx;    // xor edx, edx        db 0x66; xor bx, bx;    // xor ebx, ebx        // CPUID        db 0x0f; db 0xa2;    // cpuid        // save. 将寄存器保存到pdwout4        db 0x66; mov [di], ax;    // mov DWORD PTR [di], eax        db 0x66; mov [di+4], bx;    // mov DWORD PTR [di+4], ebx        db 0x66; mov [di+8], cx;    // mov DWORD PTR [di+8], ecx        db 0x66; mov [di+12], dx;    // mov DWORD PTR [di+12], edx    } }// 获得CPU信息void getcpuid(DWORD* pdwout4, DWORD id) {    getcpuidex(pdwout4, id, 0); }// 取得CPU厂商(Vendor)//// result: 成功时返回字符串的长度(一般为12)。失败时返回0。// pvendor: 接收厂商信息的字符串缓冲区。至少为13字节。int cpu_getvendor(char* pvendor) {    DWORD dwBuf[4];    if (NULL==pvendor)    return 0;    // Function 0: Vendor-ID and Largest Standard Function    getcpuid(dwBuf, 0);    // save. 保存到pvendor    *(DWORD*)&pvendor[0] = dwBuf[1];    // ebx: 前四个字符    *(DWORD*)&pvendor[4] = dwBuf[3];    // edx: 中间四个字符    *(DWORD*)&pvendor[8] = dwBuf[2];    // ecx: 最后四个字符    pvendor[12] = '\0';    return 12; }// 取得CPU商标(Brand)//// result: 成功时返回字符串的长度(一般为48)。失败时返回0。// pbrand: 接收商标信息的字符串缓冲区。至少为49字节。int cpu_getbrand(char* pbrand) {    DWORD dwBuf[4];    if (NULL==pbrand)    return 0;    // Function 0x80000000: Largest Extended Function Number    getcpuid(dwBuf, 0x80000000UL);    if (dwBuf[0] < 0x80000004UL)    return 0;    // Function 80000002h,80000003h,80000004h: Processor Brand String    getcpuid((DWORD*)&pbrand[0], 0x80000002UL);    // 前16个字符    getcpuid((DWORD*)&pbrand[16], 0x80000003UL);    // 中间16个字符    getcpuid((DWORD*)&pbrand[32], 0x80000004UL);    // 最后16个字符    pbrand[48] = '\0';    return 48; }int main(void) {    //getcpuidex(dwBuf, 0,0);    //printf("%.8lX\t%.8lX\t%.8lX\t%.8lX\n", dwBuf[0],dwBuf[1],dwBuf[2],dwBuf[3]);    cpu_getvendor(szBuf);    printf("CPU Vendor:\t%s\n", szBuf);    cpu_getbrand(szBuf);    printf("CPU Name:\t%s\n", szBuf);    return 0; }

 

五、编译和运行

  在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.amd.com/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.mouseos.com/x64/doc3.html
《在C++中使用cpuid指令获得CPU信息》。闲人(freeman)著。http://freeman.cnblogs.com/archive/2005/08/30/226128.html
http://en.wikipedia.org/wiki/CPUID
http://baike.baidu.com/view/1829765.htm

 

源码下载——
https://files.cnblogs.com/zyl910/getcpuid.rar

作者:zyl910

出处:http://www.cnblogs.com/zyl910/

版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0.

分类: --- My_原创 , --- Program_编程 , C00 Language_语言 , C10 C系列 , C11 C , C12 C++ , D00 Platform_平台 , D20 DOS/BIOS , H00 Hardware_硬件 , H10 Architecture_体系结构 , H11 x86

标签: Cpp , x86 , Asm , CPUID , dos , 16bit , 32bit

PC获取硬件标识(Windows篇)


Lmagic


设计

2 人赞同了该文章

参考:设备唯一标识方法(Unique Identifier):如何在Windows系统上获取设备的唯一标识


(1)网卡MAC地址

在Windows系统中通过命令行运行“systeminfo"。

(2)CPU ID

在Windows系统中通过命令行运行“wmic cpu get processorid”就可以查看CPU ID。

目前CPU ID也无法唯一标识设备,Intel现在可能同一批次的CPU ID都一样,不再提供唯一的ID。而且经过实际测试,新购买的同一批次PC的CPU ID很可能一样。这样作为设备的唯一标识就会存在问题。

(3)硬盘序列号

在Windows系统中通过命令行运行“wmic diskdrive get serialnumber”可以查看。

硬盘序列号作为设备唯一ID存在的问题是,很多机器可能存在多块硬盘,特别是服务器,而且机器更换硬盘是很可能发生的事情,更换硬盘后设备ID也必须随之改变,不然也会影响授权等应用。因此,很多授权软件没有考虑使用硬盘序列号。而且,不一定所有的电脑都能获取到硬盘序列号。

(4)主板smBIOS UUID

在Windows系统中通过命令行运行“wmic csproduct get UUID”可以查看。

主板UUID是很多授权方法和微软官方都比较推崇的方法,即便重装系统UUID应该也不会变(笔者没有实测重装,不过在一台机器上安装双系统,获取的主板UUID是一样的,双系统一个windows一个Linux,Linux下用“dmidecode -s system-uuid”命令可以获取UUID)。

但是这个方法也有缺陷,因为不是所有的厂商都提供一个UUID,当这种情况发生时,wmic会返回“FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF”,即一个无效的UUID。

补充:

UUID就是Universal Unique IDentifier的缩写,它是一个128位,16字节的值,并确保在时间和空间上唯一。

它是把硬件地址、时间以及随机数结合在一起,来确保其唯一性的。

一般情况下,生成算法用计算机网卡的地址和一个60位的timestamp生成,时间是以100ns为时间间隔。

例如,一台300PL 6862的计算机,主板集成的网卡的MAC地址为00-04-AC-2E-B7-DC,而UUID的最后六个字节也会是0004AC2EB7DC


DOS下和WINDOWS下用CPUID读CPU信息的评论 (共 条)

分享到微博请遵守国家法律