Intel x86_64 CPUID指令介绍


一、CPUID简介

1.1 CPUID 功能简介

从名字就可以知道该指令是用来读取处理器的各种标识和特性信息(比如:CPU型号和支持的功能),并将指令执行完后返回的信息保存在 EAX, EBX, ECX,和 EDX 寄存器中。

CPUID指令有两组功能:一组返回的是基本信息,另一组返回的是扩展信息。

该指令有一个输入参数(可能会有两个),该参数会传递给EAX(ECX)寄存器,一般情况下只输入一个参数,根据输入参数的不同,返回给EAX, EBX, ECX, and EDX寄存器的信息也不一样。简介如下:
在这里插入图片描述

1.2 处理器是否支持 CPUID指令

EFLAGS寄存器的bit21位表明处理器是否支持CPUID指令,如果可以设置或者清除该标志位,那么代表处理器支持CPUID指令。
在这里插入图片描述
在这里插入图片描述

1.3 指令返回基本信息

CPUID指令的第一组功能:
确定CPUID指令输出处理器的基本信息以及EAX寄存器所能接受的最大的输入值:
输入参数为 00 H ,汇编伪代码为:
MOV EAX, 00H
CPUID

该指令会返回处理器能接受的最大输入参数 Maximum(即EAX寄存器能接受的最大输入值),为了返回有效的CPU基本信息,因此输入参数必须得在0~ Maximum之间,同时该返回值也是保存在EAX寄存器中。以及供应商ID字符串(Vendor Identification String)
如下图所示:
在这里插入图片描述

从图中可以看出,供应商ID字符串以EBX、EDX和ECX的形式返回。
对于Intel处理器,该字符串为" GenuineIntel ",并表示为:
在这里插入图片描述
接下来我们来简单的演示一下,在 intel 主机上执行 lscpu命令:
在这里插入图片描述
对于AMD处理器,该字符串为"AuthenticAMD "
在这里插入图片描述

1.4 指令返回扩展信息

CPUID指令的第二组功能:
确定CPUID指令输出处理器的扩展信息以及EAX寄存器所能接受的最大的输入值:
输入参数为 80000000H ,汇编伪代码为:
MOV EAX, 80000000H
CPUID

该指令会返回处理器能接受的最大输入参数 Maximum(即EAX寄存器能接受的最大输入值),为了返回有效的CPU扩展信息,因此输入参数必须得在80000000H ~ Maximum之间,同时该返回值也是保存在EAX寄存器中。
在这里插入图片描述

二、CPUID指令参数输入为01H

2.1 简介

接下来我们主要来介绍输入参数为01是CPUID的功能。

这一章节的内容都与下篇文章intel LBR和BTS有关:Intel x86_64 LBR & BTS功能

我这里先简单的介绍下:简单来说就是LBR和BTS用来记录CPU产出的跳转指令:包括call、ret、jmp、jxx(间接跳转)、中断、异常。利用这个功能,就可以保存每个分支跳转指令的起始地址和目的地址。这样就可以了解到当前CPU执行代码的流程情况。
举一个简单的例子jmp指令:

4004d3: eb 03    jmp 4004d8
当执行下面这条jmp指令时,那么将产生一个 from 4004d3 to 4004d8的跳转,那么产生的LBR记录项就是
{ From:4004d3 ,To:4004d8 } /* jmp指令 */

再举一个简单的例子关于call ,ret 指令:

int add(int a, int b)
{
	return (a+b);
}
 
int main(void)
{
	return add(1,2);
}
上述两个函数将会产生两条LBR记录项:
{From : main , to : add} /* call 指令*/
{From : add , to : main} /* ret 指令*/

在二进制安全中,有很多攻击都是hook控制流,比如隐藏进程,隐藏文件,隐藏tcp,提权等等,使正在运行的系统按照攻击者的流程运行下去,这样就和系统正常的控制转移过程将会不一样,比如在Linux系统上,由于攻击者hook掉一些函数,当用户使用ls、top、ps、netstat等命令时,那么将会过滤掉一些进程,文件,tcp连接等等,这样用户调用shell命令时,便看不到过滤的进程,文件,tcp连接等,那么攻击者就会利用到这些进程、文件、tcp连接,对系统危害极大。

CPU当前正在产生的分支、中断和异常等跳转指令提供一种方法来确定系统当前运行中的控制转移过程。我们根据这些跳转指令便能够分析出系统是否被攻击了。
画个简图(只是简图描述的不太准确)来描述一下:
在这里插入图片描述
上图中的不正常的跳转指令是操作系统text中不存在的跳转指令,那么我们就可以通过LBR,BTS获取CPU正在产生的跳转指令来对比操作系统中text中的跳转指令来判断系统是否被攻击了,描述的比较简单,下篇文章我将详细介绍。

从上文可以得知01参数属于第一组功能:返回处理器基本信息。

输入参数为 01 H ,汇编伪代码为:
MOV EAX, 01H
CPUID
在这里插入图片描述

2.2 返回值EAX内容分析

在这里插入图片描述
EAX返回的内容主要是 处理器的Model ID、 Family ID 、Processor Type、Stepping ID 。

2.3 返回值EBX内容分析

此处省略。。。。。。

2.4 返回值ECX内容分析

在这里插入图片描述
在这里主要解释一下3个参数,与LBR和BTS有关,有关LBR和BTS可看下文链接:
Intel LBR & BTS 功能介绍
PDCM:Perfmon and Debug Capability 。该值为1表示处理器支持性能和调试能力。
DS-CPL:CPL Qualified Debug Store。该值为1表示处理器支持对Debug Store特性的扩展,允许根据当前系统处于的特权等级对 branch message 进行过滤。(0:表示内核态,3:表示用户态)
DTES64:64-bit DS Area。该值为1表示处理器支持在DS Area存放64位地址。

2.5 返回值EDX内容分析

在这里插入图片描述
在这里主要解释一下2个参数,与LBR和BTS有关:
DS:Debug Store。处理器支持将调试信息写入内存驻留缓冲区。BTS和PEBS使用该特性。可以理解为处理器是否支持BTS和 PEBS功能。
MSR :Model Specific Registers RDMSR and WRMSR Instructions。CPU是否支持指令rdmsr/wrmsr来读写MSR寄存器

三、 linux中的CPUID指令

3.1 linux应用层调用cpuid指令

我写了一个简单的应用程序来获取处理器厂商ID(vendor ID)和 family ,如下:

#include <stdio.h>

#define X86_VENDOR_INTEL       0
#define X86_VENDOR_AMD         1
#define X86_VENDOR_UNKNOWN     2

#define QCHAR(a, b, c, d) ((a) + ((b) << 8) + ((c) << 16) + ((d) << 24))
#define CPUID_INTEL1 QCHAR('G', 'e', 'n', 'u')
#define CPUID_INTEL2 QCHAR('i', 'n', 'e', 'I')
#define CPUID_INTEL3 QCHAR('n', 't', 'e', 'l')
#define CPUID_AMD1 QCHAR('A', 'u', 't', 'h')
#define CPUID_AMD2 QCHAR('e', 'n', 't', 'i')
#define CPUID_AMD3 QCHAR('c', 'A', 'M', 'D')

#define CPUID_IS(a, b, c, ebx, ecx, edx)	\
		(!((ebx ^ (a))|(edx ^ (b))|(ecx ^ (c))))

static inline void cpuid(int op, unsigned int *eax, unsigned int *ebx,
				             unsigned int *ecx, unsigned int *edx)
{
     asm volatile("cpuid" //asm 表示内核汇编,执行cpuid指令, volatile 表示告诉gcc编译器不要优化代码 
	    : "=a" (*eax),   //第一个冒号后面: 是输出参数。
	      "=b" (*ebx),   //输出操作数约束应该带有一个约束修饰符 "=",指定它是输出操作数
	      "=c" (*ecx),
	      "=d" (*edx)
	    : "0" (*eax)	//第二个冒号后面: 是输入参数  Intel手册也说明ecx有时候也作为输入参数
	    : "memory");     
}

static int x86_vendor(void)
{
	unsigned eax = 0x00000000;
	unsigned ebx, ecx = 0, edx;

	cpuid(0, &eax, &ebx, &ecx, &edx);

	if (CPUID_IS(CPUID_INTEL1, CPUID_INTEL2, CPUID_INTEL3, ebx, ecx, edx))
          printf("GenuineIntel\n");
		return X86_VENDOR_INTEL;

	if (CPUID_IS(CPUID_AMD1, CPUID_AMD2, CPUID_AMD3, ebx, ecx, edx))
          printf("AuthenticAMD\n");
		return X86_VENDOR_AMD;

	return X86_VENDOR_UNKNOWN;
}

static int x86_family(void)
{
	unsigned eax = 0x00000001;
	unsigned ebx, ecx = 0, edx;
	int x86;

	cpuid(1, &eax, &ebx, &ecx, &edx);

	x86 = (eax >> 8) & 0xf;
	if (x86 == 15)
		x86 += (eax >> 20) & 0xff;

	return x86;
}

int main()
{
    unsigned int eax = 0;
	unsigned int ebx = 0;
	unsigned int ecx = 0;
	unsigned int edx = 0;

     cpuid(0, &eax, &ebx, &ecx, &edx);
     printf("EBX ← %x (“Genu”)EDX ← %x (“ineI”) ECX ← %x (“ntel”)\n", ebx, edx ,ecx);

     int vendor = x86_vendor();
     int family = x86_family();

     printf("%d %d \n", vendor, family);

     return 0;

}

让我们来看看输出结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结果显示与Intel手册,lscpu命令显示的一致,感兴趣的同学还可以打印跟多的cpu信息,在这里我就不继续介绍了。
补充一下:

内联汇编的基本格式
asm ( assembler template 
           : output operands                  /* optional */
           : input operands                   /* optional */
           : list of clobbered registers      /* optional */
           );

3.2 linux内核中调用cpuid指令

除了在应用层通过汇编指令调用cpuid指令外,还可以在内核模块直接调用cpuid函数接口
该接口定义在arch/x86/include/asm/processor.h (内核版本3.10.0)中:

/*
 * Generic CPUID function
 * clear %ecx since some cpus (Cyrix MII) do not set or clear %ecx
 * resulting in stale register contents being returned.
 */
static inline void cpuid(unsigned int op,
			 unsigned int *eax, unsigned int *ebx,
			 unsigned int *ecx, unsigned int *edx)
{
	*eax = op;
	*ecx = 0;
	__cpuid(eax, ebx, ecx, edx);
}

#define __cpuid			native_cpuid

static inline void native_cpuid(unsigned int *eax, unsigned int *ebx,
				unsigned int *ecx, unsigned int *edx)
{
	/* ecx is often an input as well as an output. */
	asm volatile("cpuid"
	    : "=a" (*eax),
	      "=b" (*ebx),
	      "=c" (*ecx),
	      "=d" (*edx)
	    : "0" (*eax), "2" (*ecx)
	    : "memory");
}

现在我写个简单的内核模块来测试一下cpuid指令:

#include <linux/kernel.h>
#include <linux/module.h>


//内核模块初始化函数
static int __init lkm_init(void)
{
	unsigned int eax = 0;
	unsigned int ebx = 0;
	unsigned int ecx = 0;
	unsigned int edx = 0;

	cpuid(0, &eax, &ebx, &ecx, &edx);
	
	printk("EBX:%xh(“Genu”) EDX:%xh(“ineI”) ECX:%xh(“ntel”)\n", ebx, edx ,ecx);
		
	return 0;
}

//内核模块退出函数
static void __exit lkm_exit(void)
{
	printk(KERN_DEBUG "exit\n");
}

module_init(lkm_init);
module_exit(lkm_exit);

MODULE_LICENSE("GPL");

Makefile文件:

obj-m := kcpuid.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

![在这里插入图片描述](https://img-blog.csdnimg.cn/cdc3739cf73e4d28a6801510a0ceab34.png
可以看出和intel手册显示的一致。感兴趣的同学可以写个内核模块在这里获取更多的cpu信息。
在这里插入图片描述

总结

这条指令支持的内容十分丰富,在这里我只介绍了CPUID指令的一点点内容,并在应用层和内核模块各自写了一个简单的程序验证了一下,在下篇文章我会介绍这条指令的其它内容(与LBR和BTS有关),大家感兴趣的可以自行阅读 intel vol2 chapter3 3.2小节 关于CPUID指令的详细介绍。
在这里插入图片描述

参考资料

https://blog.csdn.net/bin_linux96/article/details/104236808
https://blog.csdn.net/razor87/article/details/8711712
https://blog.csdn.net/subfate/article/details/50719396
linux内核源码 3.10.0
Intel官方手册 vol2
intel官方手册 vol3

<think>好的,我现在要解决用户的问题:“use of undeclared identifier __cpuid”。首先,我需要确认这个错误通常发生在什么情况下。根据经验,这个错误可能出现在C或C++代码中,当开发者尝试使用__cpuid这个内联函数或者宏时,但编译器无法识别它。 首先,我应该回想一下__cpuid的常见使用场景。__cpuid是一个用于获取CPU信息的函数,常见于需要硬件优化的代码,比如SIMD指令集的检测。这个函数通常由编译器提供,比如微软的Visual C++编译器或者GCC、Clang等。但不同编译器可能有不同的实现方式,或者需要包含特定的头文件。 接下来,我需要考虑用户可能使用的编译环境。如果用户使用的是MSVC,那么可能需要包含<intrin.h>头文件,因为微软的文档中提到__cpuid函数在这个头文件中声明。而如果是GCC或Clang,可能需要使用__get_cpuid或者不同的内联汇编写法,因为__cpuid可能不是直接可用的,或者需要特定的宏定义。 然后,用户可能没有包含正确的头文件,或者没有启用必要的编译器选项。例如,在GCC中,可能需要包含<cpuid.h>并使用__get_cpuid函数,而不是直接使用__cpuid。此外,某些编译器可能需要特定的架构选项,比如在编译时指定-march=native或者启用某些指令集扩展。 另外,跨平台兼容性也是一个问题。用户可能在跨平台项目中使用了一段依赖于特定编译器的代码,当切换到另一个平台或编译器时,就会出现未声明的标识符错误。这时候需要根据不同的编译器使用条件编译指令,比如#ifdef _MSC_VER来区分MSVC和其他编译器,并包含相应的头文件和函数。 还有可能用户拼写错误,比如将__cpuid写成了其他形式,比如_cpuid或者cpuid,但根据错误信息,用户确实使用了__cpuid,所以拼写错误的可能性较低,但也不能完全排除。 接下来,我需要考虑如何验证这些可能性。首先,检查用户的编译器类型和版本,然后确认是否正确包含了对应的头文件。对于MSVC,确认是否包含<intrin.h>;对于GCC/Clang,确认是否包含<cpuid.h>,并且使用正确的函数名和语法。此外,检查编译器的文档,确认__cpuid是否在该编译器中可用,或者是否有替代方案。 另外,可能需要检查编译器的架构设置,是否针对x86x86_64编译,因为CPUID指令在这些架构上才有效。如果用户尝试在其他架构(如ARM)上使用,自然会导致错误,但此时错误可能不仅仅是未声明的标识符,还可能涉及指令不支持的问题。 最后,需要提供用户解决方案,包括包含正确的头文件、使用条件编译、检查编译器选项以及提供跨平台的示例代码。同时,提醒用户注意编译器特定的实现差异,确保代码的可移植性。</think>遇到`__cpuid`未声明标识符错误时,通常是因为编译器无法识别该函数。以下是逐步解决方案: --- ### 1. **检查编译器类型** `__cpuid`是编译器特定的内联函数: - **MSVC(Visual Studio)**:需包含头文件`<intrin.h>` - **GCC/Clang**:需改用`__get_cpuid`或内联汇编,并包含`<cpuid.h>` --- ### 2. **针对不同编译器的修复方法** #### **MSVC 解决方案** ```c #include <intrin.h> // 必须包含此头文件 void get_cpu_info() { int info[4]; __cpuid(info, 0); // 第一个参数为数组,第二个为功能号 } ``` #### **GCC/Clang 解决方案** ```c #include <cpuid.h> // 必须包含此头文件 void get_cpu_info() { unsigned int eax, ebx, ecx, edx; if (__get_cpuid(0, &eax, &ebx, &ecx, &edx)) { // 使用__get_cpuid函数 // 处理结果 } } ``` --- ### 3. **检查编译架构** - **仅限x86/x86_64**:CPUID指令仅在Intel/AMD CPU上有效。 - 确保编译器未针对ARM等其他架构编译。 --- ### 4. **跨平台兼容写法** 使用条件编译适配不同编译器: ```c #if defined(_MSC_VER) #include <intrin.h> #elif defined(__GNUC__) || defined(__clang__) #include <cpuid.h> #endif void get_cpu_info() { #if defined(_MSC_VER) int info[4]; __cpuid(info, 0); #elif defined(__GNUC__) || defined(__clang__) unsigned int eax, ebx, ecx, edx; if (__get_cpuid(0, &eax, &ebx, &ecx, &edx)) { // 处理结果 } #endif } ``` --- ### 5. **验证编译器选项** - **GCC/Clang**:添加`-march=native`确保支持CPUID指令: ```bash gcc -march=native -o program source.c ``` --- ### 6. **替代方案:手动内联汇编(仅限GCC/Clang)** ```c void get_cpu_info() { unsigned int eax, ebx, ecx, edx; asm volatile( "cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(0) // 功能号0 ); // 处理eax, ebx, ecx, edx } ``` --- ### 总结 - **关键点**:`__cpuid`是编译器/平台特定的函数。 - **修复步骤**: 1. 包含正确的头文件(`<intrin.h>`或`<cpuid.h>`) 2. 使用编译器对应的函数(`__cpuid`或`__get_cpuid`) 3. 通过条件编译实现跨平台兼容 若问题仍未解决,请提供代码片段和编译环境详情(如操作系统、编译器版本)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值