CPUID 명령어 심화
x86 CPUID 기반 런타임 기능 탐지를 부트 초기화와 동적 최적화 경로 관점에서 심층 분석합니다. Leaf/Subleaf 구조와 vendor 확장 해석, Intel·AMD 기능 비트 차이, microcode 업데이트로 달라지는 기능 가시성, Linux 커널의 boot_cpu_data·x86_capability·alternative patching 연계, 취약점 완화 기능(IBRS/SSBD 등) 감지, 가상화 환경에서의 CPUID masking 영향, 사용자 공간 노출과 커널 내부 정책 차이까지 안전한 기능 게이팅 설계에 필요한 핵심을 다룹니다.
핵심 요약
- Leaf 구조 — EAX에 leaf 번호, ECX에 sub-leaf를 넣고 CPUID 실행하면 EAX/EBX/ECX/EDX에 정보 반환.
- 주요 Leaf — Leaf 0x01(기본 피처), 0x07(확장 피처), 0x0D(XSAVE), 0x80000001(확장 피처 AMD).
- 커널 추상화 — x86_capability[] 배열에 피처 비트 통합, cpu_has() 매크로로 런타임 확인.
- Intel vs AMD — 동일 기능도 다른 Leaf/비트 위치 사용, 커널이 벤더별로 추상화.
- 부팅 초기화 — early_cpu_detect()에서 boot_cpu_data 구조체 초기화.
단계별 이해
- 기본 CPUID 동작
EAX 레지스터에 leaf 번호를 넣고 CPUID를 실행하면 4개 레지스터로 CPU 정보가 반환됩니다. - 주요 Leaf 파악
Leaf 0x01과 0x07이 가장 중요합니다. 각 비트 의미를 Intel/AMD SDM에서 확인하세요. - 커널 추상화 이해
boot_cpu_data, x86_capability[], cpu_has() 매크로를 통해 커널이 CPUID를 어떻게 추상화하는지 파악합니다. - 벤더 차이 인식
Intel과 AMD가 동일 기능에 다른 Leaf를 쓰는 경우를 파악하고 커널의 통합 방식을 이해합니다.
CPUID는 x86 프로세서의 기능, 모델 정보, 캐시 토폴로지, 확장 기능 등을 쿼리하는 핵심 명령어입니다. Linux 커널은 부팅 초기에 CPUID를 광범위하게 사용하여 CPU 기능을 탐지하고, 런타임에 최적의 코드 경로를 선택합니다.
CPUID 기본 동작
/* CPUID 명령어 기본 사용법
* 입력: EAX = leaf (기능 번호), ECX = sub-leaf (일부 leaf)
* 출력: EAX, EBX, ECX, EDX (leaf에 따라 의미 다름)
*
* Intel SDM Vol. 2A, "CPUID — CPU Identification" 참조
*/
/* Leaf 0: 최대 기본 leaf 번호 + 벤더 문자열 */
mov $0, %eax
cpuid
/* EAX = 최대 지원 leaf 번호 (예: 0x20)
* EBX:EDX:ECX = 벤더 ID 문자열
* Intel: "GenuineIntel" (EBX=0x756E6547 EDX=0x49656E69 ECX=0x6C65746E)
* AMD: "AuthenticAMD" (EBX=0x68747541 EDX=0x69746E65 ECX=0x444D4163)
*/
/* Leaf 1: 프로세서 식별 + 기능 플래그 */
mov $1, %eax
cpuid
/* EAX = Version Information (Stepping, Model, Family)
* [3:0] Stepping ID
* [7:4] Model
* [11:8] Family
* [13:12] Processor Type
* [19:16] Extended Model
* [27:20] Extended Family
* EBX = [7:0] Brand Index, [15:8] CLFLUSH size, [23:16] Max APIC IDs, [31:24] Local APIC ID
* ECX = Feature flags (SSE3, PCLMULQDQ, VMX, SMX, SSE4.1, SSE4.2, x2APIC, AES-NI, AVX, ...)
* EDX = Feature flags (FPU, VME, PSE, TSC, MSR, PAE, APIC, SEP, MTRR, SSE, SSE2, HTT, ...)
*/
주요 CPUID Leaf 요약
| EAX (Leaf) | ECX (Sub-leaf) | 설명 | 핵심 반환값 |
|---|---|---|---|
0x00 | - | 벤더 ID + 최대 기본 leaf | EBX:EDX:ECX = 벤더 문자열 |
0x01 | - | 프로세서 식별 + 기능 플래그 | EAX=버전, ECX/EDX=피처 비트 |
0x02 | - | 캐시/TLB 디스크립터 (Intel) | 바이트별 디스크립터 코드 |
0x04 | 0,1,2,... | 캐시 파라미터 (Intel) | 캐시 타입, 크기, 연관도, 라인 크기 |
0x06 | - | Thermal / Power Management | Turbo Boost, HWP 지원 여부 |
0x07 | 0 | 확장 기능 플래그 | AVX2, BMI1/2, AVX-512, SHA, UMIP 등 |
0x07 | 1 | 확장 기능 플래그 2 | AVX-VNNI, HRESET, LAM 등 |
0x0A | - | Performance Monitoring | PMC 버전, 카운터 수/비트 폭 → PMU 탐지 상세 |
0x0B | 0,1,2 | Extended Topology | SMT/Core/Package 토폴로지 |
0x0D | 0,1,2,... | XSAVE 상태 영역 | XSAVE 크기, 지원 컴포넌트 (SSE, AVX, MPX, AVX-512, PKRU 등) → XSAVE 상세 |
0x0F | 0,1 | QoS Monitoring (Intel RDT) | L3 캐시 모니터링 지원 |
0x10 | 0,1,2,3 | QoS Enforcement (Intel RDT) | L3 CAT, L2 CAT, MBA 지원 |
0x12 | 0,1,2,... | SGX Capability (Intel) | SGX 지원 여부, EPC 섹션 정보 |
0x14 | 0,1 | Intel Processor Trace | PT 기능, 필터링 범위 |
0x15 | - | TSC / Core Crystal Clock | TSC:Core 비율 (TSC 주파수 계산) → 주파수 탐지 상세 |
0x16 | - | Processor Frequency | 기본/최대/버스 주파수 (MHz) → 주파수 탐지 상세 |
0x1F | 0,1,2,... | V2 Extended Topology | Module/Tile/Die 레벨 포함 |
확장 CPUID (0x80000000+)
| EAX (Leaf) | 설명 | 핵심 반환값 |
|---|---|---|
0x80000000 | 최대 확장 leaf 번호 | EAX = 최대 확장 leaf (예: 0x80000008) |
0x80000001 | 확장 기능 플래그 | ECX/EDX: LAHF, SVM(AMD), NX, 1GB Pages, RDTSCP, Long Mode 등 |
0x80000002~4 | 프로세서 브랜드 문자열 | 48바이트 ASCII: "Intel(R) Core(TM) i9-..." 등 |
0x80000005 | L1 캐시/TLB 정보 (AMD) | L1 Data/Instruction 캐시 크기/연관도 |
0x80000006 | L2/L3 캐시 정보 | 캐시 크기, 연관도, 라인 크기 |
0x80000007 | 고급 전력 관리 | EDX bit 8: Invariant TSC 지원 |
0x80000008 | 주소 크기 + 코어 수 | 물리/가상 주소 비트 수, 코어 수 (AMD) |
0x8000000A | SVM 기능 (AMD) | SVM 리비전, ASID 수, Nested Paging 등 |
0x8000001E | AMD 토폴로지 | Compute Unit ID, Node ID |
인라인 어셈블리로 CPUID 사용
/* 커널의 CPUID 래퍼 — arch/x86/include/asm/processor.h */
static inline void cpuid(unsigned int leaf,
unsigned int *eax, unsigned int *ebx,
unsigned int *ecx, unsigned int *edx)
{
*eax = leaf;
*ecx = 0; /* leaf-only 쿼리는 sub-leaf=0 */
asm volatile ("cpuid"
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
: "0"(*eax), "2"(*ecx));
}
/* sub-leaf 지정 버전 */
static inline void cpuid_count(unsigned int leaf,
unsigned int subleaf,
unsigned int *eax, unsigned int *ebx,
unsigned int *ecx, unsigned int *edx)
{
*eax = leaf;
*ecx = subleaf;
asm volatile ("cpuid"
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
: "0"(*eax), "2"(*ecx));
}
/* 단일 레지스터만 필요한 경우의 편의 함수 */
static inline unsigned int cpuid_eax(unsigned int leaf)
{
unsigned int eax, ebx, ecx, edx;
cpuid(leaf, &eax, &ebx, &ecx, &edx);
return eax;
}
/* cpuid_ebx(), cpuid_ecx(), cpuid_edx()도 동일 패턴 */
Leaf 1 피처 비트 상세 (ECX/EDX)
ECX 피처 플래그 (Leaf 1)
| 비트 | 이름 | 커널 상수 | 설명 |
|---|---|---|---|
| 0 | SSE3 | X86_FEATURE_SSE3 | Streaming SIMD Extensions 3 |
| 1 | PCLMULQDQ | X86_FEATURE_PCLMULQDQ | Carry-Less Multiplication (CRC, GCM) |
| 5 | VMX | X86_FEATURE_VMX | Virtual Machine Extensions (Intel VT-x) |
| 6 | SMX | X86_FEATURE_SMX | Safer Mode Extensions (Intel TXT) |
| 9 | SSSE3 | X86_FEATURE_SSSE3 | Supplemental SSE3 |
| 12 | FMA | X86_FEATURE_FMA | Fused Multiply-Add (FMA3) |
| 13 | CX16 | X86_FEATURE_CX16 | CMPXCHG16B 지원 |
| 19 | SSE4.1 | X86_FEATURE_SSE4_1 | SSE4.1 명령어 |
| 20 | SSE4.2 | X86_FEATURE_SSE4_2 | SSE4.2 (PCMPESTRI, CRC32) |
| 21 | x2APIC | X86_FEATURE_X2APIC | Extended APIC (MSR 기반) |
| 25 | AES-NI | X86_FEATURE_AES | AES 명령어 (AESENC/AESDEC) |
| 26 | XSAVE | X86_FEATURE_XSAVE | XSAVE/XRSTOR/XSETBV/XGETBV |
| 27 | OSXSAVE | X86_FEATURE_OSXSAVE | OS가 XSAVE를 활성화함 |
| 28 | AVX | X86_FEATURE_AVX | Advanced Vector Extensions |
| 30 | RDRAND | X86_FEATURE_RDRAND | 하드웨어 난수 생성 |
| 31 | Hypervisor | X86_FEATURE_HYPERVISOR | 하이퍼바이저 존재 (VM에서만 set) |
EDX 피처 플래그 (Leaf 1)
| 비트 | 이름 | 커널 상수 | 설명 |
|---|---|---|---|
| 0 | FPU | X86_FEATURE_FPU | x87 FPU 내장 |
| 3 | PSE | X86_FEATURE_PSE | Page Size Extension (4MB 페이지) |
| 4 | TSC | X86_FEATURE_TSC | Time Stamp Counter (RDTSC) |
| 5 | MSR | X86_FEATURE_MSR | RDMSR/WRMSR 지원 |
| 6 | PAE | X86_FEATURE_PAE | Physical Address Extension (36+ bit) |
| 9 | APIC | X86_FEATURE_APIC | 온칩 APIC |
| 11 | SEP | X86_FEATURE_SEP | SYSENTER/SYSEXIT |
| 12 | MTRR | X86_FEATURE_MTRR | Memory Type Range Registers |
| 13 | PGE | X86_FEATURE_PGE | Page Global Enable |
| 16 | PAT | X86_FEATURE_PAT | Page Attribute Table |
| 19 | CLFLUSH | X86_FEATURE_CLFLUSH | 캐시 라인 플러시 |
| 25 | SSE | X86_FEATURE_SSE | Streaming SIMD Extensions |
| 26 | SSE2 | X86_FEATURE_SSE2 | SSE2 (x86-64 필수) |
| 28 | HTT | X86_FEATURE_HTT | Hyper-Threading Technology |
참고: NX는 Leaf 1 EDX가 아니라 확장 Leaf 0x80000001의 EDX bit 20에서 확인합니다.
Leaf 7 Sub-leaf 1 EAX 피처 플래그 (ECX=1)
Leaf 7 Sub-leaf 1(EAX=0x07, ECX=1)은 Ice Lake 이후 추가된 신규 확장 기능을 보고합니다. 커널은 arch/x86/kernel/cpu/common.c에서 cpuid_count(7, 1, ...)으로 이 값을 읽어 x86_capability 배열에 저장합니다.
| 비트 | 이름 | 커널 상수 | 설명 |
|---|---|---|---|
| 4 | AVX-VNNI | X86_FEATURE_AVX_VNNI | VEX-인코딩 벡터 뉴럴 네트워크 INT8 곱셈누산 (Alder Lake+) |
| 5 | AVX512_BF16 | X86_FEATURE_AVX512_BF16 | BFloat16 변환 명령어 (AI/ML 추론 최적화) |
| 7 | CMPCCXADD | X86_FEATURE_CMPCCXADD | 비교 조건부 원자 추가 (락 없는 연산, Sapphire Rapids+) |
| 10 | FZRM | X86_FEATURE_FZRM | Fast Zero-length REP MOVSB |
| 11 | FSRS | X86_FEATURE_FSRS | Fast Short REP STOSB (짧은 memset 최적화) |
| 12 | FSRCS | X86_FEATURE_FSRC | Fast Short REP CMPSB/SCASB |
| 17 | FRED | X86_FEATURE_FRED | Flexible Return and Event Delivery (인터럽트 지연 단축) |
| 18 | LKGS | X86_FEATURE_LKGS | Load Kernel GS Base (SWAPGS 대체, 보안 강화) |
| 19 | WRMSRNS | X86_FEATURE_WRMSRNS | Non-serializing WRMSR (성능 개선) |
| 21 | AMX-FP16 | X86_FEATURE_AMX_FP16 | AMX FP16 행렬 곱셈 (4세대 Xeon Scalable+) |
| 22 | HRESET | X86_FEATURE_HRESET | History Reset — 분기 예측기 히스토리 선택적 초기화 |
| 26 | LAM | X86_FEATURE_LAM | Linear Address Masking — 포인터 상위 비트에 태그 저장 (사용자 공간) |
Leaf 7 확장 기능 (EBX/ECX/EDX)
커널 부팅 시 CPUID 처리 흐름
/* 커널의 CPU 식별 흐름 — arch/x86/kernel/cpu/common.c */
/* 1단계: 부팅 초기 CPU 탐지 (early_cpu_init) */
void early_cpu_init(void)
{
/* CPU 벤더 테이블에서 일치하는 벤더 찾기 */
const struct cpu_dev *cdev;
for (cdev = __x86_cpu_dev_start; cdev < __x86_cpu_dev_end; cdev++) {
if (cdev->c_detect) cdev->c_detect(&boot_cpu_data);
}
early_identify_cpu(&boot_cpu_data);
}
/* 2단계: CPU 식별 상세 */
static void early_identify_cpu(struct cpuinfo_x86 *c)
{
/* Leaf 0: 벤더 확인 */
cpuid(0x00, &c->cpuid_level, ...);
/* Leaf 1: Family/Model/Stepping + 기본 피처 */
cpuid(0x01, &tfms, &misc, &cap[4], &cap[0]);
c->x86 = (tfms >> 8) & 0xf; /* Family */
c->x86_model = (tfms >> 4) & 0xf; /* Model */
c->x86_stepping = tfms & 0xf; /* Stepping */
/* Extended Family/Model (Family ≥ 6 또는 15인 경우) */
if (c->x86 == 0xf)
c->x86 += (tfms >> 20) & 0xff; /* + Extended Family */
if (c->x86 >= 0x6)
c->x86_model += ((tfms >> 16) & 0xf) << 4; /* + Extended Model */
/* Leaf 7: 확장 기능 (AVX2, SMEP, SMAP, ...) */
cpuid_count(0x07, 0, &eax, &cap[9], &cap[16], &cap[18]);
/* 확장 leaf: Long Mode, NX, GB Pages, ... */
cpuid(0x80000001, &eax, &ebx, &cap[6], &cap[1]);
/* 물리/가상 주소 비트 수 */
cpuid(0x80000008, &eax, ...);
c->x86_phys_bits = eax & 0xff; /* 예: 46 (Intel), 48 (AMD) */
c->x86_virt_bits = (eax >> 8) & 0xff; /* 예: 48 또는 57 (LA57) */
}
/* 3단계: 벤더별 초기화 (Intel/AMD 고유 처리) */
/* intel_init(), amd_init() 에서 각 벤더 고유 CPUID leaf 추가 해석 */
x86_capability 배열과 피처 테스트
/* arch/x86/include/asm/cpufeature.h */
/* CPU 피처 비트를 저장하는 구조체 (커널 전역) */
struct cpuinfo_x86 {
__u8 x86; /* CPU family */
__u8 x86_vendor; /* X86_VENDOR_INTEL, _AMD, ... */
__u8 x86_model;
__u8 x86_stepping;
__u32 x86_capability[NCAPINTS]; /* 피처 비트 배열 */
char x86_model_id[64]; /* 브랜드 문자열 */
__u8 x86_phys_bits; /* 물리 주소 비트 */
__u8 x86_virt_bits; /* 가상 주소 비트 */
/* ... */
};
/* boot_cpu_data: 부팅 CPU의 정보 (전역 변수) */
extern struct cpuinfo_x86 boot_cpu_data;
/* 피처 존재 여부 확인 */
if (boot_cpu_has(X86_FEATURE_AVX2)) {
/* AVX2 사용 가능한 코드 경로 */
}
/* static_cpu_has(): 부팅 후 패치되는 최적화 버전 */
/* Alternative 메커니즘으로 NOP ↔ JMP으로 패치됨 */
if (static_cpu_has(X86_FEATURE_XSAVE)) {
/* 조건 분기 오버헤드 없이 코드 경로 결정 */
}
static_cpu_has()는 Alternative 명령어 패치 메커니즘을 사용합니다. 부팅 시 CPUID 결과에 따라 실제 기계어 코드를 NOP 또는 JMP으로 바이너리 패치하여, 런타임에 조건 분기 오버헤드를 완전히 제거합니다. .altinstructions 섹션에 패치 정보가 저장됩니다.
Family / Model / Stepping 해석
/* CPUID Leaf 1 EAX에서 Family/Model/Stepping 추출 */
/*
* EAX 비트 레이아웃:
* [3:0] Stepping ID
* [7:4] Base Model
* [11:8] Base Family
* [13:12] Processor Type (00=OEM, 01=OverDrive, 10=Dual, 11=Reserved)
* [19:16] Extended Model
* [27:20] Extended Family
*
* Display Family = Base Family + Extended Family (if Base Family == 0xF)
* = Base Family (otherwise)
* Display Model = (Extended Model << 4) + Base Model (if Base Family >= 0x6 또는 0xF)
* = Base Model (otherwise)
*/
/* 예시: Intel Core i9-14900K (Raptor Lake-S Refresh) */
/* CPUID 1 EAX = 0x000B0671
* Base Family = 0x6, Extended Family = 0x0 → Display Family = 6
* Base Model = 0x7, Extended Model = 0xB → Display Model = 0xB7
* Stepping = 0x1
* → "Family 6, Model 183, Stepping 1"
*/
/* 예시: AMD Ryzen 9 7950X (Zen 4, Raphael) */
/* CPUID 1 EAX = 0x00A60F12
* Base Family = 0xF, Extended Family = 0x19 → Display Family = 25 (0x19)
* Base Model = 0x1, Extended Model = 0x6 → Display Model = 0x61
* Stepping = 0x2
* → "Family 25, Model 97, Stepping 2"
*/
/* 커널에서 CPU 모델별 분기 예시 */
#define INTEL_FAM6_RAPTORLAKE_S 0xB7
#define INTEL_FAM6_ALDERLAKE 0x97
#define INTEL_FAM6_SAPPHIRERAPIDS 0x8F
switch (c->x86_model) {
case INTEL_FAM6_RAPTORLAKE_S:
/* Raptor Lake 고유 workaround/최적화 */
break;
case INTEL_FAM6_SAPPHIRERAPIDS:
/* Sapphire Rapids 서버 최적화 */
break;
}
CPUID 기반 토폴로지 탐지
/* Leaf 0x0B (Extended Topology Enumeration)
* 또는 Leaf 0x1F (V2 Extended Topology — 최신 CPU)
*
* Sub-leaf ECX 입력에 따라 토폴로지 레벨 반환:
* sub-leaf 0: SMT 레벨
* sub-leaf 1: Core 레벨
* sub-leaf 2: Module/Die 레벨 (Leaf 0x1F만)
*
* 반환값:
* EAX[4:0] = 다음 레벨 APIC ID 시프트 비트 수
* EBX[15:0] = 이 레벨의 논리 프로세서 수
* ECX[15:8] = 레벨 타입 (1=SMT, 2=Core, 3=Module, 4=Tile, 5=Die)
* EDX = x2APIC ID
*/
/* arch/x86/kernel/cpu/topology.c — 커널의 토폴로지 파싱 */
void detect_extended_topology(struct cpuinfo_x86 *c)
{
unsigned int eax, ebx, ecx, edx;
int leaf = 0x1f; /* V2 먼저 시도 */
cpuid_count(leaf, 0, &eax, &ebx, &ecx, &edx);
if (ebx == 0) leaf = 0x0b; /* V2 미지원이면 V1 사용 */
/* sub-leaf 0: SMT 레벨 */
cpuid_count(leaf, 0, &eax, &ebx, &ecx, &edx);
c->x86_max_cores = ebx & 0xffff; /* 코어당 SMT 스레드 수 */
c->topo_smt_shift = eax & 0x1f;
/* sub-leaf 1: Core 레벨 */
cpuid_count(leaf, 1, &eax, &ebx, &ecx, &edx);
c->topo_core_shift = eax & 0x1f;
/* APIC ID 기반 패키지/코어/스레드 매핑 */
c->topo.apicid = edx;
c->topo.pkg_id = edx >> c->topo_core_shift;
c->topo.core_id = (edx >> c->topo_smt_shift) &
((1 << (c->topo_core_shift - c->topo_smt_shift)) - 1);
}
CPUID 캐시 정보 (Leaf 4)
/* Leaf 0x04: Deterministic Cache Parameters (Intel)
* Sub-leaf를 0부터 증가시키며 각 캐시 레벨 정보를 열거
*
* 반환값:
* EAX[4:0] = Cache Type (1=Data, 2=Instruction, 3=Unified, 0=No more)
* EAX[7:5] = Cache Level (1=L1, 2=L2, 3=L3)
* EAX[25:14]= 이 캐시를 공유하는 최대 APIC ID 수
* EBX[11:0] = Line Size - 1
* EBX[21:12]= Partitions - 1
* EBX[31:22]= Associativity (Ways) - 1
* ECX = Number of Sets - 1
*
* 캐시 크기 = (Ways+1) × (Partitions+1) × (LineSize+1) × (Sets+1)
*/
/* 예: Intel i9-14900K의 L3 캐시 (sub-leaf 3) */
/* EAX=0x3C004163 EBX=0x03C0003F ECX=0x00003FFF EDX=0x00000006
* Type: Unified (3) Level: L3 (3)
* Line Size: 64 bytes (0x3F + 1)
* Partitions: 1 (0 + 1)
* Ways: 16 (0xF + 1)
* Sets: 16384 (0x3FFF + 1)
* → Size = 16 × 1 × 64 × 16384 = 16 MB (per tile)
*/
/* arch/x86/kernel/cpu/cacheinfo.c */
static void ci_leaf_init(struct cacheinfo *ci,
unsigned int eax, unsigned int ebx, unsigned int ecx)
{
ci->level = (eax >> 5) & 0x7;
ci->type = eax & 0x1f;
ci->coherency_line_size = (ebx & 0xfff) + 1;
ci->ways_of_associativity = ((ebx >> 22) & 0x3ff) + 1;
ci->number_of_sets = ecx + 1;
ci->size = ci->ways_of_associativity *
ci->coherency_line_size *
ci->number_of_sets; /* 바이트 단위 캐시 크기 */
}
가상화와 CPUID
/* 하이퍼바이저 탐지: Leaf 1 ECX bit 31 (Hypervisor Present) */
static bool detect_hypervisor(void)
{
unsigned int eax, ebx, ecx, edx;
cpuid(1, &eax, &ebx, &ecx, &edx);
return !!(ecx & (1 << 31)); /* Hypervisor bit */
}
/* 하이퍼바이저 전용 CPUID leaf (0x40000000+) */
/* Leaf 0x40000000: 하이퍼바이저 벤더 문자열 */
cpuid(0x40000000, &eax, &ebx, &ecx, &edx);
/* KVM: EBX:ECX:EDX = "KVMKVMKVM\0\0\0" */
/* Hyper-V: EBX:ECX:EDX = "Microsoft Hv" */
/* VMware: EBX:ECX:EDX = "VMwareVMware" */
/* Xen: EBX:ECX:EDX = "XenVMMXenVMM" */
/* KVM에서 CPUID 에뮬레이션 — arch/x86/kvm/cpuid.c */
int kvm_emulate_cpuid(struct kvm_vcpu *vcpu)
{
u32 eax = kvm_rax_read(vcpu);
u32 ecx = kvm_rcx_read(vcpu);
struct kvm_cpuid_entry2 *entry;
/* 게스트에 노출할 CPUID 값 조회 (호스트와 다를 수 있음) */
entry = kvm_find_cpuid_entry(vcpu, eax, ecx);
if (entry) {
kvm_rax_write(vcpu, entry->eax);
kvm_rbx_write(vcpu, entry->ebx);
kvm_rcx_write(vcpu, entry->ecx);
kvm_rdx_write(vcpu, entry->edx);
}
return kvm_skip_emulated_instruction(vcpu);
}
/* QEMU/KVM에서 게스트 CPUID 커스터마이징:
* -cpu host → 호스트 CPUID 그대로 전달
* -cpu host,-avx512f → AVX-512 기능 숨김
* -cpu EPYC → AMD EPYC 프로필
*/
CPUID faulting: 최신 Intel CPU는 MSR_MISC_FEATURES_ENABLES (0x140) bit 0으로 CPUID faulting을 지원합니다. 활성화하면 Ring 3(유저 모드)에서 CPUID 실행 시 #GP 예외가 발생하여, 커널이 가상화된 CPUID 값을 반환할 수 있습니다. 하이퍼바이저 없이도 CPUID 가상화가 가능해집니다.
CPUID와 보안 (Spectre/Meltdown 완화)
/* CPU 취약점 탐지에 CPUID가 핵심적으로 사용됨 */
/* arch/x86/kernel/cpu/bugs.c */
/* 1. CPUID Leaf 7 EDX: 하드웨어 완화 기능 확인 */
/* bit 26: IBRS/IBPB — Indirect Branch Restricted Speculation */
/* bit 27: STIBP — Single Thread Indirect Branch Predictor */
/* bit 29: IA32_ARCH_CAPABILITIES MSR 존재 */
/* bit 31: SSBD — Speculative Store Bypass Disable */
/* 2. IA32_ARCH_CAPABILITIES MSR (CPUID로 존재 확인 후 읽기) */
if (boot_cpu_has(X86_FEATURE_ARCH_CAPABILITIES)) {
u64 ia32_cap = x86_read_arch_cap_msr();
/* bit 0: RDCL_NO → Meltdown에 취약하지 않음 */
/* bit 1: IBRS_ALL → IBRS가 모든 코드에서 작동 */
/* bit 2: RSBA → 대안 예측기 사용 (RetBleed 관련) */
/* bit 3: SKIP_L1DFL → L1D flush 불필요 */
/* bit 4: SSB_NO → SSB 취약점 없음 */
/* bit 5: MDS_NO → MDS 취약점 없음 */
/* bit 6: IF_PSCHANGE_MC_NO → PSCHANGE MC 영향 없음 */
/* bit 7: TSX_CTRL → TSX 비활성화 가능 */
}
/* 3. /proc/cpuinfo의 "bugs" 필드는 이 탐지 결과에 기반 */
/* 예: bugs: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass ... */
유저 공간에서 CPUID 활용
# /proc/cpuinfo — 커널이 파싱한 CPUID 결과
cat /proc/cpuinfo | head -30
# processor : 0
# vendor_id : GenuineIntel
# cpu family : 6
# model : 183
# model name : 13th Gen Intel(R) Core(TM) i9-13900K
# stepping : 1
# flags : fpu vme de pse tsc msr pae mce cx8 apic ... avx2 ...
# bugs : spectre_v1 spectre_v2 spec_store_bypass ...
# cpuid 명령어 도구 (cpuid 패키지)
cpuid -1 -l 0 # Leaf 0 (벤더 문자열)
cpuid -1 -l 1 # Leaf 1 (피처 플래그)
cpuid -1 -l 7 # Leaf 7 (확장 기능)
cpuid -1 -l 4 # Leaf 4 (캐시 정보)
# /dev/cpu/N/cpuid 디바이스 파일 (CONFIG_X86_CPUID)
# 특정 CPU에 직접 CPUID 실행
# lseek offset = leaf | (subleaf << 32)
dd if=/dev/cpu/0/cpuid bs=16 count=1 skip=0 2>/dev/null | xxd
# C에서 직접 CPUID 호출 (유저 공간)
/* 유저 공간 CPUID 직접 호출 */
#include <cpuid.h> /* GCC 내장 헤더 */
void check_features(void)
{
unsigned int eax, ebx, ecx, edx;
/* GCC __get_cpuid() 내장 함수 */
__get_cpuid(1, &eax, &ebx, &ecx, &edx);
if (ecx & (1 << 25)) printf("AES-NI supported\\n");
if (ecx & (1 << 28)) printf("AVX supported\\n");
/* Leaf 7 확인 */
__get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx);
if (ebx & (1 << 5)) printf("AVX2 supported\\n");
if (ebx & (1 << 16)) printf("AVX-512F supported\\n");
/* 브랜드 문자열 (Leaf 0x80000002~4) */
char brand[49] = {};
__get_cpuid(0x80000002, (unsigned int*)&brand[0],
(unsigned int*)&brand[4], (unsigned int*)&brand[8],
(unsigned int*)&brand[12]);
__get_cpuid(0x80000003, (unsigned int*)&brand[16],
(unsigned int*)&brand[20], (unsigned int*)&brand[24],
(unsigned int*)&brand[28]);
__get_cpuid(0x80000004, (unsigned int*)&brand[32],
(unsigned int*)&brand[36], (unsigned int*)&brand[40],
(unsigned int*)&brand[44]);
printf("CPU: %s\\n", brand);
}
Intel vs AMD CPUID 차이점
| 항목 | Intel | AMD |
|---|---|---|
| 벤더 문자열 | GenuineIntel | AuthenticAMD |
| 캐시 정보 (상세) | Leaf 0x04 (Deterministic) | Leaf 0x8000001D (유사 형식) |
| L1 캐시 (간략) | Leaf 0x02 (디스크립터) | Leaf 0x80000005 (직접 크기) |
| 토폴로지 | Leaf 0x0B / 0x1F | Leaf 0x8000001E (CU/Node) |
| 가상화 기능 | VMX (Leaf 1 ECX bit 5) | SVM (Leaf 0x8000000A) |
| 전력 관리 | Leaf 0x06 (HWP) | Leaf 0x80000007 (CPB, APM) |
| 주소 크기 | Leaf 0x80000008 | Leaf 0x80000008 (동일) |
| Family 인코딩 | Family 6 (대부분) | Family 0xF+Ext (예: 0x17=Zen, 0x19=Zen3/4, 0x1A=Zen5) |
| SGX | Leaf 0x12 (지원) | 해당 없음 (SEV 사용) |
| Encrypted VM | TME/MKTME (MSR 기반) | SEV/SEV-ES/SEV-SNP (Leaf 0x8000001F) |
커널의 벤더 추상화: 커널은 struct cpu_dev 구조체를 통해 Intel/AMD/Hygon/Centaur/Zhaoxin 등 벤더별 CPUID 해석 차이를 추상화합니다. arch/x86/kernel/cpu/intel.c와 arch/x86/kernel/cpu/amd.c에 각 벤더의 초기화 로직이 분리되어 있으며, 공통 피처 플래그는 x86_capability 배열에 통합 저장됩니다.
CPUID 직렬화 효과와 성능 영향
CPUID는 Intel SDM이 정의한 완전한 직렬화 명령어(Serializing Instruction)입니다. 직렬화 명령어는 실행 전 이전 모든 명령어가 완전히 완료될 때까지 파이프라인을 멈추며, 이후 명령어의 프리페치·비순차 실행도 막습니다. 이 특성 때문에 CPUID는 성능 측정 경계 설정과 정밀 타이밍에 전통적으로 활용됩니다.
| 명령어 | 직렬화 수준 | 주요 용도 | 비고 |
|---|---|---|---|
CPUID | 완전 직렬화 (Fully Serializing) | 정밀 타이밍 경계, 부팅 시 CPU 탐지 | 권한 무관 실행 가능, 런타임 반복 비권장 |
MFENCE | 메모리 직렬화 (Store+Load) | 메모리 순서 보장, lock-free 알고리즘 | 파이프라인 비움 없음 |
LFENCE | 로드 직렬화 (Load) | Spectre 완화, 로드 순서 강제 | AMD: 스펙실행 방지 효과 있음 |
IRET | 완전 직렬화 | 인터럽트 핸들러 복귀 | 컨텍스트 전환 포함 |
WRMSR | 완전 직렬화 (일부 MSR) | MSR 설정 후 즉시 효과 반영 | MSR 종류에 따라 다름 |
/* RDTSC 정밀 측정 — CPUID로 파이프라인 직렬화 후 타이밍 시작
* 출처: Intel SDM Vol. 2B "RDTSC" 사용 지침
*/
/* 측정 시작: cpuid로 이전 명령 완전 완료 보장 */
xor %eax, %eax
cpuid /* 파이프라인 flush — 이전 명령 모두 완료 */
rdtsc /* EAX=TSC[31:0], EDX=TSC[63:32] */
shl $32, %rdx
or %rax, %rdx
mov %rdx, %r8 /* 시작 TSC 저장 */
/* ... 측정할 코드 실행 ... */
/* 측정 종료: rdtscp + lfence (아웃-오브-오더 방지) */
rdtscp /* ECX=IA32_TSC_AUX (core ID), EAX/EDX=TSC */
shl $32, %rdx
or %rax, %rdx
sub %r8, %rdx /* 경과 사이클 수 */
lfence /* 이후 명령이 TSC 읽기 전 실행되지 않도록 */
/* 커널이 CPUID를 부팅 시에만 호출하는 이유:
* 직렬화로 인한 성능 페널티 — 현대 CPU에서 수백 클럭 사이클 소요
* 런타임 성능 경로에서는 static_cpu_has() 또는 alternative() 사용 */
/* 잘못된 패턴: 런타임에 CPUID 반복 호출 (성능 저하) */
bool has_avx_bad(void) {
unsigned int eax, ebx, ecx, edx;
cpuid_count(7, 0, &eax, &ebx, &ecx, &edx);
return ebx & (1 << 5); /* 매번 CPUID 실행 → 수백 사이클 낭비 */
}
/* 올바른 패턴: 부팅 시 저장된 피처 비트 참조 (인라인 비트 검사) */
bool has_avx_good(void) {
return cpu_feature_enabled(X86_FEATURE_AVX2);
}
RDTSCP vs RDTSC: 현대 커널 측정에서는 RDTSCP를 선호합니다. RDTSCP는 이전 로드 명령을 직렬화(load-serializing)하고 IA32_TSC_AUX에서 코어 ID도 함께 반환합니다. 단, RDTSCP는 완전한 직렬화가 아니므로 이후 명령 직렬화를 위해 LFENCE를 추가합니다. CPUID는 현대 측정에서는 오버헤드가 커 RDTSCP+LFENCE 조합으로 대체됩니다.
Leaf 0x0D: XSAVE 상태 컴포넌트 관리
Leaf 0x0D는 프로세서가 지원하는 XSAVE 상태 컴포넌트의 크기, 오프셋, 비트맵을 질의합니다. 커널은 이 정보를 바탕으로 컨텍스트 전환 시 저장/복원할 FPU 상태 영역을 정확하게 할당합니다. CR4.OSXSAVE를 설정해 OS가 XSAVE를 지원함을 프로세서에 알려야만 AVX·AVX-512·AMX 등 확장 상태를 사용할 수 있습니다.
/* arch/x86/kernel/fpu/xstate.c — XSAVE 상태 초기화 */
void fpu__init_system_xstate(unsigned int legacy_size)
{
u32 eax, ebx, ecx, edx;
/* Leaf 0x0D Sub-leaf 0: 전체 지원 컴포넌트 비트맵 */
cpuid_count(0xd, 0, &eax, &ebx, &ecx, &edx);
xfeatures_mask_all = eax + ((u64)edx << 32);
/* EBX: 현재 XCR0 기준 크기, ECX: 최대 크기 */
fpu_kernel_cfg.max_size = ecx;
fpu_kernel_cfg.used_size = ebx;
/* Sub-leaf 1: XSAVE 기능 플래그 (XSAVEOPT, XSAVEC 등) */
cpuid_count(0xd, 1, &eax, &ebx, &ecx, &edx);
if (eax & (1 << 1))
setup_force_cpu_cap(X86_FEATURE_XSAVEC);
if (eax & (1 << 3))
setup_force_cpu_cap(X86_FEATURE_XSAVES);
if (eax & (1 << 4))
setup_force_cpu_cap(X86_FEATURE_XFD); /* AMX 지연 저장 */
/* Sub-leaf N (N≥2): 각 컴포넌트의 크기와 XSAVE 영역 내 오프셋 */
for (int i = 2; i < XFEATURE_MAX; i++) {
cpuid_count(0xd, i, &eax, &ebx, &ecx, &edx);
xstate_sizes[i] = eax; /* 컴포넌트 크기 (바이트) */
xstate_offsets[i] = ebx; /* XSAVE 영역 내 오프셋 */
}
}
Leaf 0x0A: 성능 모니터링 유닛(PMU) 탐지
Leaf 0x0A는 Intel Performance Monitoring Unit(PMU)의 버전과 카운터 구성을 보고합니다. perf 서브시스템은 이 정보를 사용해 사용 가능한 하드웨어 카운터 수와 비트 폭을 결정합니다. AMD는 다른 메커니즘(Leaf 0x80000022 EPMC)을 사용하므로 Leaf 0x0A는 사실상 Intel 전용입니다.
/* arch/x86/kernel/cpu/perf_event_intel.c — Intel PMU 탐지 */
void x86_pmu_show_pmu_cap(struct x86_pmu *pmu)
{
unsigned int eax, ebx, ecx, edx;
cpuid(0x0a, &eax, &ebx, &ecx, &edx);
pmu->version = eax & 0xff; /* EAX[7:0]: PMU 버전 (0이면 미지원) */
pmu->num_counters = (eax >> 8) & 0xff; /* EAX[15:8]: 범용 카운터 수 */
pmu->counter_bits = (eax >> 16) & 0xff; /* EAX[23:16]: 카운터 비트 폭 */
/* EBX: 사용 불가 이벤트 비트맵 (bit=1이면 해당 이벤트 미지원) */
/* bit 0=명령 실행, 1=비중단 사이클, 2=참조 사이클, 3=LLC참조,
* 4=LLC미스, 5=분기 실행, 6=분기 미스 예측 */
pmu->unavailable = ebx & 0xff;
/* EDX: 고정 카운터 정보 */
pmu->num_counters_fixed = edx & 0x1f; /* EDX[4:0]: 고정 카운터 수 */
pmu->fixed_counter_bits = (edx >> 5) & 0xff; /* EDX[12:5]: 고정 카운터 비트 폭 */
pr_info("PMU: version %u, %u counters (%u bits), %u fixed\n",
pmu->version, pmu->num_counters,
pmu->counter_bits, pmu->num_counters_fixed);
}
| PMU 버전 | 도입 마이크로아키텍처 | 주요 기능 |
|---|---|---|
| v1 | Pentium M, Core Solo/Duo | 범용 카운터 2개, 기본 이벤트 (cycles, instructions, cache-misses, branch-misses) |
| v2 | Core 2 (Merom, 2006) | 3개 고정 카운터 추가 (INST_RETIRED, CPU_CLK_UNHALTED.CORE, REF), PEBS 지원 |
| v3 | Nehalem (2008) | 범용 카운터 4개, Off-core Response 이벤트, 멀티스레드 카운터 지원 |
| v4 | Skylake (2015) | 인터럽트 억제 개선, LBR(Last Branch Record) 통합, 필터링 기능 강화 |
| v5 | Sapphire Rapids (2023) | Architectural LBR (XSAVE 기반, 최대 32개), PEBS 지연 개선, 정밀 이벤트 |
Leaf 0x15/0x16: TSC·코어 주파수 탐지
현대 Linux 커널은 Leaf 0x15와 Leaf 0x16을 통해 HPET나 PIT를 사용하지 않고 TSC 주파수를 정확하게 계산합니다. Leaf 0x15는 Core Crystal Clock과 TSC의 비율을, Leaf 0x16은 코어·최대·버스 주파수를 MHz 단위로 직접 반환합니다. 두 Leaf가 모두 사용 불가한 경우 커널은 PIT 기반 보정으로 폴백합니다.
/* arch/x86/kernel/tsc.c — TSC 주파수 탐지 (두 Leaf 조합) */
unsigned int native_calibrate_tsc(void)
{
unsigned int eax_denominator, ebx_numerator, ecx_crystal_hz, edx;
/* Leaf 0x15: Crystal Clock ↔ TSC 비율
* TSC_Hz = Crystal_Hz × EBX / EAX */
cpuid(0x15, &eax_denominator, &ebx_numerator, &ecx_crystal_hz, &edx);
if (eax_denominator && ebx_numerator && ecx_crystal_hz) {
return (ecx_crystal_hz / 1000) *
ebx_numerator / eax_denominator; /* kHz 단위 반환 */
}
/* ECX=0인 플랫폼: Leaf 0x16으로 코어 주파수 직접 참조 */
unsigned int eax_base_mhz, ebx_max_mhz, ecx_bus_mhz;
cpuid(0x16, &eax_base_mhz, &ebx_max_mhz, &ecx_bus_mhz, &edx);
if (eax_base_mhz)
return eax_base_mhz * 1000; /* MHz → kHz 변환 */
return 0; /* 실패 시 PIT/HPET 기반 보정 폴백 */
}
| 마이크로아키텍처 | 코드명 | Crystal Clock (ECX) | 비고 |
|---|---|---|---|
| Skylake, Kaby Lake | SKL, KBL | 24 MHz | ECX=24,000,000 |
| Ice Lake, Tiger Lake | ICL, TGL | 19.2 MHz | ECX=19,200,000 |
| Alder Lake, Raptor Lake | ADL, RPL | 38.4 MHz | ECX=38,400,000 |
| Atom Goldmont, Tremont | GLM, TRM | 19.2 / 25 MHz | 모델에 따라 다름 |
| Sapphire Rapids | SPR | 25 MHz | ECX=25,000,000 |
| AMD (대부분) | - | ECX=0 (미지원) | Leaf 0x16 EAX 사용 |
Invariant TSC: Leaf 0x80000007 EDX bit 8이 설정된 CPU는 전력 절약 상태(C-state)와 무관하게 TSC가 일정한 속도로 동작합니다. Linux는 이 비트를 확인해 TSC를 클럭소스로 사용할지 결정합니다. /proc/cpuinfo의 constant_tsc·nonstop_tsc·tsc_reliable 플래그로 확인할 수 있습니다.
마이크로코드 업데이트와 CPUID 변화
마이크로코드 업데이트는 CPU의 내부 동작을 수정하며, 특히 보안 취약점 완화를 위한 새로운 CPUID 피처 비트를 활성화합니다. Spectre·Meltdown 공개(2018) 이후, 많은 시스템에서 마이크로코드 로드 전후로 CPUID 결과가 달라집니다. Linux 커널은 early microcode 로드 후 CPUID를 재질의해 새 피처를 반영합니다.
/* arch/x86/kernel/cpu/microcode/core.c — 마이크로코드 로드 흐름 */
/* 1단계: early_initcall에서 microcode 로드 */
static int __init microcode_init(void)
{
microcode_ops = init_intel_microcode(); /* 또는 init_amd_microcode() */
return microcode_ops->apply_microcode(0);
}
/* 2단계: 마이크로코드 적용 후 피처 비트 재스캔 및 변경 사항 보고 */
void microcode_check(struct cpuinfo_x86 *prev_info)
{
struct cpuinfo_x86 info;
get_cpu_cap(&info); /* CPUID 재질의 */
for (int i = 0; i < NCAPINTS; i++) {
u32 changed = info.x86_capability[i] ^ prev_info->x86_capability[i];
if (changed)
pr_info("x86/CPU: Word %d: bits %#x changed\n", i, changed);
}
}
/* 3단계: 보안 완화 재적용 (마이크로코드로 활성화된 MSR 비트 사용) */
void spectre_v2_select_mitigation(void)
{
/* IBRS: Leaf 7 EDX bit 26 — 마이크로코드 업데이트 후 나타남 */
if (boot_cpu_has(X86_FEATURE_IBRS))
wrmsrl(MSR_IA32_SPEC_CTRL, SPEC_CTRL_IBRS);
/* STIBP: Leaf 7 EDX bit 27 — 형제 스레드 간 분기 예측 격리 */
if (boot_cpu_has(X86_FEATURE_STIBP))
wrmsrl(MSR_IA32_SPEC_CTRL, SPEC_CTRL_STIBP);
}
Early vs Late 마이크로코드 로드: initramfs의 마이크로코드 이미지(/boot/intel-ucode.img)는 GRUB가 커널보다 먼저 메모리에 올려 early boot 단계에서 적용됩니다. 늦은 마이크로코드 로드(/dev/cpu/microcode 쓰기)는 이미 수행된 CPUID 기반 초기화를 재실행하지 않으므로, 보안 완화 픽스에는 반드시 early 로드가 필요합니다. AMD는 /lib/firmware/amd-ucode/ 경로를 사용합니다.
CPU 기능 마스킹과 cmdline 제어
Linux 커널은 CPUID로 탐지된 피처라도 커맨드라인 옵션이나 에라타 워크어라운드로 강제로 비활성화하거나 활성화할 수 있습니다. setup_clear_cpu_cap()은 이미 설정된 피처 비트를 제거하며, setup_force_cpu_cap()은 CPUID에 없어도 비트를 강제 설정합니다. 이 메커니즘은 x86_capability와 cpu_caps_cleared/set 비트맵 배열을 통해 동작합니다.
/* arch/x86/kernel/cpu/common.c — 피처 비트 마스킹 메커니즘 */
/* 피처 강제 제거: boot_cpu_data와 이후 초기화되는 모든 CPU에 적용 */
void setup_clear_cpu_cap(unsigned int bit)
{
clear_cpu_cap(&boot_cpu_data, bit);
set_bit(bit, cpu_caps_cleared); /* SMP: 이후 부팅하는 CPU도 동일하게 클리어 */
}
/* cmdline 파싱 예: "noibrs" 옵션 처리 */
static int __init noibrs_setup(char *str)
{
setup_clear_cpu_cap(X86_FEATURE_IBRS);
setup_clear_cpu_cap(X86_FEATURE_IBRS_ENHANCED);
return 1;
}
__setup("noibrs", noibrs_setup);
/* 에라타 마스킹: 특정 Family/Model에서 버그있는 피처 비트 강제 제거 */
static void intel_workarounds(struct cpuinfo_x86 *c)
{
/* Kaby Lake/Coffee Lake 일부 스테핑: TSX 버그 → 강제 비활성화 */
if (c->x86 == 6 && c->x86_model == 0x8e &&
c->x86_stepping < 0xb) {
setup_clear_cpu_cap(X86_FEATURE_HLE);
setup_clear_cpu_cap(X86_FEATURE_RTM);
}
}
| cmdline 옵션 | 효과 | 관련 X86_FEATURE_* |
|---|---|---|
noibrs | IBRS 비활성화 (Spectre-v2 완화 비적용) | X86_FEATURE_IBRS |
noibpb | 간접 분기 예측기 배리어 비활성화 | X86_FEATURE_IBPB |
nox2apic | x2APIC 비활성화 (레거시 xAPIC 강제) | X86_FEATURE_X2APIC |
nopti | Kernel Page Table Isolation 비활성화 (Meltdown 완화 해제) | X86_FEATURE_PTI |
mitigations=off | 모든 CPU 취약점 완화 비활성화 (성능 우선) | IBRS, STIBP, SSBD, L1TF 등 다수 |
tsx=off | TSX (HLE/RTM) 강제 비활성화 (TAA/TSX-async-abort) | X86_FEATURE_HLE, X86_FEATURE_RTM |
nosmt | SMT(하이퍼스레딩) 비활성화 | 논리적 CPU 오프라인 처리 |
spectre_v2=off | Spectre-v2 완화 전체 비활성화 | X86_FEATURE_IBRS, X86_FEATURE_IBPB 등 |
CPUID Faulting: 유저 공간 CPUID 제한
CPUID Faulting은 Intel이 Ivy Bridge(3세대 Core)에서 도입한 보안 기능으로, 비특권 유저 공간에서 CPUID 실행 시 #GP(0) 예외를 발생시킵니다. 커널이 이 예외를 처리해 CPUID 결과를 에뮬레이션하거나 제한할 수 있으며, 이를 통해 컨테이너나 가상 환경에서 CPU 마이크로아키텍처 정보 노출을 방지합니다.
/* arch/x86/kernel/process.c — CPUID Faulting 설정 */
/* MSR_MISC_FEATURES_ENABLES(0x140) bit 0 = CPUID_FAULT */
void enable_cpuid_fault(void)
{
if (!boot_cpu_has(X86_FEATURE_CPUID_FAULT))
return;
wrmsrl(MSR_MISC_FEATURES_ENABLES, MISC_FEATURES_CPUID_FAULT);
pr_info("x86/CPU: CPUID Faulting enabled\n");
}
/* do_general_protection() 핸들러에서 CPUID 에뮬레이션 처리 */
static bool emulate_cpuid(struct pt_regs *regs)
{
unsigned int eax = regs->ax, ecx = regs->cx;
unsigned int r_eax, r_ebx, r_ecx, r_edx;
/* 커널이 CPUID 결과를 직접 계산해 유저 레지스터에 삽입 */
cpuid_count(eax, ecx, &r_eax, &r_ebx, &r_ecx, &r_edx);
/* 민감한 정보 마스킹: 하이퍼바이저 leaf는 숨김 */
if (eax >= 0x40000000)
r_eax = r_ebx = r_ecx = r_edx = 0;
regs->ax = r_eax; regs->bx = r_ebx;
regs->cx = r_ecx; regs->dx = r_edx;
regs->ip += 2; /* CPUID = 2바이트 명령어 (0x0F 0xA2) */
return true;
}
| 항목 | CPUID Faulting | CR4.UMIP (User-Mode Instruction Prevention) |
|---|---|---|
| 대상 명령어 | CPUID | SGDT, SLDT, SIDT, STR, SMSW |
| 활성화 방법 | MSR_MISC_FEATURES_ENABLES bit 0 | CR4 bit 11 |
| 예외 발생 | #GP(0) → 커널 에뮬레이션 가능 | #GP(0) → SIGSEGV 전달 |
| 보안 목적 | CPU 마이크로아키텍처 정보 노출 방지 | GDT/LDT/IDT 주소 노출 방지 (KASLR 보완) |
| 커널 상수 | X86_FEATURE_CPUID_FAULT | X86_FEATURE_UMIP |
| 도입 | Ivy Bridge (3세대 Core, 2012) | Goldmont (Atom), Cannon Lake+ (2018) |
컨테이너 보안 활용: CPUID Faulting은 컨테이너가 호스트 CPU의 정확한 모델·스테핑 정보를 획득하지 못하게 합니다. KVM은 게스트에게 가상화된 CPUID 결과를 에뮬레이션하는 방식으로 유사한 효과를 달성합니다. seccomp과 prctl(PR_SET_TSC, PR_TSC_SIGSEGV)를 조합하면 RDTSC도 제한할 수 있습니다.
AMD 전용 확장 Leaf 심화 (SME/SEV/QoS)
AMD는 0x8000001F를 통해 Secure Memory Encryption(SME)과 Secure Encrypted Virtualization(SEV) 지원 여부를 보고합니다. 이 Leaf는 메모리 암호화 C-bit 위치와 물리 주소 크기 감소량도 반환합니다. Linux 커널의 arch/x86/mm/mem_encrypt_amd.c는 이를 사용해 암호화 마스크를 초기화합니다.
/* arch/x86/mm/mem_encrypt_amd.c — SME/SEV 탐지 및 초기화 */
void amd_get_encrypted_memory_parameters(void)
{
unsigned int eax, ebx, ecx, edx;
/* Leaf 0x8000001F: AMD 메모리 암호화 기능 */
cpuid(0x8000001f, &eax, &ebx, &ecx, &edx);
if (!(eax & 1)) /* EAX bit 0: SME 지원 여부 */
return;
/* EBX[5:0]: C-bit 위치 (물리 주소에서 암호화 비트가 위치하는 비트 번호) */
sme_me_mask = 1ULL << (ebx & 0x3f);
/* EBX[11:6]: C-bit 포함으로 인한 물리 주소 비트 수 감소량 */
unsigned int phys_bits_reduced = (ebx >> 6) & 0x3f;
pr_info("AMD SME: C-bit at physical address bit %llu "
"(phys_bits reduced by %u)\n",
__ffs(sme_me_mask), phys_bits_reduced);
if (eax & (1 << 1)) /* EAX bit 1: SEV 지원 */
setup_force_cpu_cap(X86_FEATURE_SEV);
/* ECX: 동시 실행 가능한 암호화 게스트(ASID) 수 */
/* EDX: 최소 ASID (최대 동시 SEV 게스트 = ASID_MAX - EDX + 1) */
}
| 비트 | 이름 | 설명 |
|---|---|---|
| 0 | SME | Secure Memory Encryption — DRAM 전체 투명 암호화 (AES-128, C-bit 설정 시 활성) |
| 1 | SEV | Secure Encrypted Virtualization — 게스트 메모리 독립 암호화 |
| 2 | PAGE_FLUSH_MSR | MSR_AMD64_SME_PA로 물리 페이지 TLB 플러시 |
| 3 | SEV-ES | SEV Encrypted State — 게스트 레지스터 상태도 암호화 (VMSA 보호) |
| 4 | SEV-SNP | SEV Secure Nested Paging — 메모리 소유권 검증으로 무결성 보호 추가 |
| 5 | VMPL | VM Permission Levels — 게스트 내 권한 계층 4단계 (VMPL0~3) |
| 10 | SEV-TIO | Trusted I/O Virtualization 지원 |
| 28 | VMSA_REGPROT | VMSA 레지스터 보호 강화 (SEV-ES 보완) |
AMD Leaf 0x80000020은 Platform QoS(L3 캐시 할당, 메모리 대역폭 할당)를, 0x80000022는 Extended Performance Monitoring(EPMC)을 보고합니다. EPMC는 AMD Zen 4부터 도입되어 Linux perf가 Intel 수준의 하드웨어 카운터 접근을 AMD에서도 활용할 수 있게 합니다.
ARM64 ID 레지스터와 비교
ARM64(AArch64) 아키텍처는 x86의 CPUID 명령어 대신 시스템 레지스터를 사용해 CPU 기능을 보고합니다. MRS 명령어로 읽는 ID_AA64*_EL1 레지스터들이 x86의 CPUID Leaf에 해당하며, 커널의 arch/arm64/kernel/cpufeature.c가 이를 추상화합니다. ARM64는 각 레지스터의 4비트 필드 값이 클수록 더 새로운 기능 버전을 의미합니다.
/* arch/arm64/kernel/cpufeature.c — ARM64 CPU 기능 탐지 패턴 */
/* x86: boot_cpu_has(X86_FEATURE_AES) 에 대응 */
static bool has_aes(const struct arm64_cpu_capabilities *cap, int scope)
{
/* ARM64: MRS x0, ID_AA64ISAR0_EL1 로 읽는 값 */
u64 val = read_sanitised_ftr_reg(SYS_ID_AA64ISAR0_EL1);
return cpuid_feature_extract_unsigned_field(
val, ID_AA64ISAR0_EL1_AES_SHIFT) >= 1;
}
/* x86: boot_cpu_has(X86_FEATURE_LSE_ATOMICS) 에 대응 */
static bool has_lse_atomics(const struct arm64_cpu_capabilities *cap, int scope)
{
u64 val = read_sanitised_ftr_reg(SYS_ID_AA64ISAR0_EL1);
return cpuid_feature_extract_unsigned_field(
val, ID_AA64ISAR0_EL1_ATOMIC_SHIFT) >= 2;
}
/* x86: boot_cpu_has(X86_FEATURE_HYPERVISOR) 에 대응 */
static bool has_el2_virtualization(const struct arm64_cpu_capabilities *cap, int scope)
{
/* EL2(하이퍼바이저) 지원: ID_AA64PFR0_EL1 EL2 필드 */
u64 val = read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1);
return cpuid_feature_extract_unsigned_field(
val, ID_AA64PFR0_EL1_EL2_SHIFT) > 0;
}
| 기능 범주 | x86 CPUID Leaf | ARM64 ID 레지스터 | 읽기 명령 |
|---|---|---|---|
| 암호화 가속 | Leaf 1 ECX (AES-NI), Leaf 7 EBX (SHA) | ID_AA64ISAR0_EL1 | MRS x0, ID_AA64ISAR0_EL1 |
| 메모리 모델·페이지 | Leaf 1 EDX (PSE), 0x80000001 EDX (1GB) | ID_AA64MMFR0_EL1 | MRS x0, ID_AA64MMFR0_EL1 |
| 프로세서 기능·EL | Leaf 1 ECX (VMX), Leaf 7 ECX (UMIP) | ID_AA64PFR0_EL1 | MRS x0, ID_AA64PFR0_EL1 |
| 디버그·추적 | Leaf 0x14 (Intel PT) | ID_AA64DFR0_EL1 | MRS x0, ID_AA64DFR0_EL1 |
| SIMD/벡터 | Leaf 1 ECX (AVX), Leaf 7 (AVX-512) | ID_AA64PFR0_EL1, SVE: ID_AA64ZFR0_EL1 | MRS x0, ID_AA64ZFR0_EL1 |
| 원자 연산 | Leaf 1 ECX (CX16) | ID_AA64ISAR0_EL1 ATOMIC 필드 | MRS x0, ID_AA64ISAR0_EL1 |
| 물리 주소 크기 | Leaf 0x80000008 EAX[7:0] | ID_AA64MMFR0_EL1 PARange 필드 | MRS x0, ID_AA64MMFR0_EL1 |
| 가상화 | Leaf 1 ECX bit 5 (VMX) | ID_AA64PFR0_EL1 EL2 필드 | MRS x0, ID_AA64PFR0_EL1 |
ARM64의 피처 탐지 철학: ARM64는 CPU ID 레지스터를 4비트 필드(field) 단위로 정의합니다. 값이 클수록 더 새로운 기능 버전을 의미합니다 (예: ATOMIC≥2 → LSE 지원). x86의 단일 피처 비트와 달리 "이 기능의 버전 N 이상"처럼 질의합니다. 또한 ARM64는 모든 코어가 동일한 ID 레지스터 값을 가져야 하므로 x86의 코어별 CPUID 불일치 문제가 없습니다.
하이퍼바이저 Leaf (0x40000000+) 심화
x86 CPU는 0x40000000~0x4FFFFFFF 범위를 하이퍼바이저 전용 CPUID 공간으로 예약합니다. 하이퍼바이저는 이 범위를 사용해 자신의 존재와 기능을 게스트 OS에 알립니다. Leaf 1 ECX bit 31이 설정되면 하이퍼바이저가 존재하며, Leaf 0x40000000에서 하이퍼바이저 벤더 이름을 확인할 수 있습니다.
/* arch/x86/kvm/cpuid.c — KVM CPUID 에뮬레이션 */
static void kvm_set_cpuid_entry_hypervisor(struct kvm_cpuid_entry2 *entry)
{
switch (entry->function) {
case 0x40000000:
/* KVM 최대 leaf + 벤더 문자열 "KVMKVMKVM\0\0\0" */
entry->eax = KVM_CPUID_FEATURES; /* 최대 하이퍼바이저 leaf = 0x40000001 */
memcpy(&entry->ebx, "KVMK", 4);
memcpy(&entry->ecx, "VMKV", 4);
memcpy(&entry->edx, "M\0\0\0", 4);
break;
case 0x40000001:
/* KVM 파라가상화 기능 플래그 */
entry->eax = 0;
if (kvm_clock_supported())
entry->eax |= (1 << KVM_FEATURE_CLOCKSOURCE2);
if (kvm_steal_time_supported())
entry->eax |= (1 << KVM_FEATURE_STEAL_TIME);
if (kvm_async_pf_supported())
entry->eax |= (1 << KVM_FEATURE_ASYNC_PF);
break;
}
}
/* VMCS/VMCB에서 게스트 CPUID 결과 제어 */
int kvm_emulate_cpuid(struct kvm_vcpu *vcpu)
{
u32 eax, ebx, ecx, edx;
kvm_cpuid(vcpu, &eax, &ebx, &ecx, &edx, false);
/* 게스트 레지스터에 결과 저장 후 RIP += 2 */
kvm_rax_write(vcpu, eax); kvm_rbx_write(vcpu, ebx);
kvm_rcx_write(vcpu, ecx); kvm_rdx_write(vcpu, edx);
return kvm_skip_emulated_instruction(vcpu);
}
| 하이퍼바이저 | 벤더 문자열 | 기능 Leaf |
|---|---|---|
| KVM (Linux) | KVMKVMKVM\0\0\0 | 0x40000001 (KVM 기능 플래그) |
| Microsoft Hyper-V | Microsoft Hv | 0x40000002~0x40000006 |
| VMware | VMwareVMware | 0x40000010 (포트 I/O 기반) |
| Xen | XenVMMXenVMM | 0x40000001 (Xen 인터페이스 버전) |
| QEMU TCG | TCGTCGTCGTCG | 0x40000001 |
| 비트 | 상수 이름 | 설명 |
|---|---|---|
| 0 | KVM_FEATURE_CLOCKSOURCE | KVM 클럭소스 v1 (MSR 기반) |
| 1 | KVM_FEATURE_NOP_IO_DELAY | I/O 딜레이 NOP 처리 (성능 개선) |
| 3 | KVM_FEATURE_CLOCKSOURCE2 | KVM 클럭소스 v2 (shared memory 기반, 권장) |
| 4 | KVM_FEATURE_ASYNC_PF | 비동기 페이지 폴트 (게스트 블로킹 감소) |
| 5 | KVM_FEATURE_STEAL_TIME | Steal Time 통계 (스케줄러 공정성 개선) |
| 9 | KVM_FEATURE_PV_TLB_FLUSH | 파라가상화 TLB 플러시 (VMEXIT 횟수 감소) |
| 11 | KVM_FEATURE_PV_SEND_IPI | 파라가상화 IPI 전송 |
| 13 | KVM_FEATURE_POLL_CONTROL | 게스트가 하이퍼바이저 폴링 제어 가능 |
| 14 | KVM_FEATURE_PV_SCHED_YIELD | vCPU yield 힌트 전달 (오버커밋 환경 최적화) |
하이퍼바이저 탐지 방법: grep hypervisor /proc/cpuinfo로 Leaf 1 ECX bit 31을 확인합니다. cpuid -l 0x40000000 명령으로 벤더 문자열을 직접 읽을 수 있습니다. KVM은 /dev/kvm 존재 여부로도 탐지 가능하며, systemd-detect-virt 명령은 다수의 탐지 방법을 통합합니다.
관련 문서
CPUID와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.