<===
2025-12-27 16:42:56
`cpucheck` — небольшая утилита для Linux, которая на старте собирает через CPUID и системные утилиты инфу о процессоре и виртуализации и всегда печатает полный отчёт. Она определяет уровень поддержки инструкций x86‑64 (v1/v2/v3/v4), наличие и включённость аппаратной виртуализации (VT‑x/AMD‑V) и показывает, запущен ли код внутри гипервизора. При запуске с параметрами `--require-x86-level`, `--require-virtualization`, `--require-hypervisor` или `--require-baremetal` утилита проверяет эти условия и возвращает ненулевой код выхода, если требования не выполнены, что удобно для скриптов и CI.
gcc -O2 -pipe -static -o cpucheck cpucheck.c
$ cat cpucheck.c
#define _GNU_SOURCE
#include <cpuid.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
/* ------------------ structures ------------------*/
typedef enum {
LEVEL_UNKNOWN = 0,
LEVEL_V1,
LEVEL_V2,
LEVEL_V3,
LEVEL_V4
} x86_level_t;
typedef struct {
char vendor[13];
char brand[49];
unsigned int eax1, ebx1, ecx1, edx1;
unsigned int max_basic, max_ext;
bool has_sse3;
bool has_ssse3;
bool has_sse41;
bool has_sse42;
bool has_popcnt;
bool has_avx;
bool has_avx2;
bool has_bmi1;
bool has_bmi2;
bool has_fma;
bool has_avx512f;
bool has_avx512bw;
bool has_avx512dq;
bool has_avx512vl;
bool hypervisor_present;
bool virt_flag_vmx;
bool virt_flag_svm;
bool virt_enabled_known;
bool virt_enabled; /* heuristic via lscpu/proc */
x86_level_t level;
} cpu_info_t;
typedef struct {
bool require_level;
x86_level_t required_level;
bool require_virtualization;
bool require_hypervisor;
bool require_baremetal;
bool json;
} options_t;
/* ------------------ helpers ------------------ */
static void trim_newline(char *s) {
if (!s) return;
size_t n = strlen(s);
while (n > 0 && (s[n-1] == '\n' || s[n-1] == '\r')) {
s[n-1] = '\0';
n--;
}
}
/* simple helper to run lscpu and find line "Virtualization:" */
static void detect_virtualization_enabled(cpu_info_t *ci) {
FILE *fp = popen("LC_ALL=C lscpu 2>/dev/null", "r");
if (!fp) {
ci->virt_enabled_known = false;
return;
}
char line[512];
bool found = false;
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "Virtualization:", 14) == 0) {
found = true;
char *p = strchr(line, ':');
if (p) {
p++;
while (*p == ' ' || *p == '\t') p++;
trim_newline(p);
if (*p == '\0' || strstr(p, "none") != NULL) {
ci->virt_enabled = false;
} else {
ci->virt_enabled = true;
}
ci->virt_enabled_known = true;
}
break;
}
}
pclose(fp);
if (!found) {
ci->virt_enabled_known = false;
}
}
/* ------------------ CPUID parsing ------------------ */
static void detect_basic_cpu_info(cpu_info_t *ci) {
unsigned int eax, ebx, ecx, edx;
/* max basic leaf and vendor */
__cpuid(0, eax, ebx, ecx, edx);
ci->max_basic = eax;
memcpy(ci->vendor + 0, &ebx, 4);
memcpy(ci->vendor + 4, &edx, 4);
memcpy(ci->vendor + 8, &ecx, 4);
ci->vendor[12] = '\0';
/* max extended leaf */
__cpuid(0x80000000, eax, ebx, ecx, edx);
ci->max_ext = eax;
/* brand string if available */
if (ci->max_ext >= 0x80000004) {
unsigned int *p = (unsigned int*)ci->brand;
for (unsigned int i = 0; i < 3; ++i) {
__cpuid(0x80000002 + i, p[0], p[1], p[2], p[3]);
p += 4;
}
ci->brand[48] = '\0';
trim_newline(ci->brand);
} else {
strcpy(ci->brand, "Unknown");
}
/* leaf 1: feature flags + hypervisor bit */
if (ci->max_basic >= 1) {
__cpuid(1, eax, ebx, ecx, edx);
ci->eax1 = eax;
ci->ebx1 = ebx;
ci->ecx1 = ecx;
ci->edx1 = edx;
ci->has_sse3 = (ecx & (1u << 0)) != 0;
ci->has_ssse3 = (ecx & (1u << 9)) != 0;
ci->has_sse41 = (ecx & (1u << 19)) != 0;
ci->has_sse42 = (ecx & (1u << 20)) != 0;
ci->has_popcnt = (ecx & (1u << 23)) != 0;
ci->has_fma = (ecx & (1u << 12)) != 0;
ci->hypervisor_present = (ecx & (1u << 31)) != 0;
}
/* leaf 7, subleaf 0: AVX2, BMI, AVX512 etc. */
if (ci->max_basic >= 7) {
__cpuid_count(7, 0, eax, ebx, ecx, edx);
ci->has_avx2 = (ebx & (1u << 5)) != 0;
ci->has_bmi1 = (ebx & (1u << 3)) != 0;
ci->has_bmi2 = (ebx & (1u << 8)) != 0;
ci->has_avx512f = (ebx & (1u << 16)) != 0;
ci->has_avx512dq = (ebx & (1u << 17)) != 0;
ci->has_avx512bw = (ebx & (1u << 30)) != 0;
ci->has_avx512vl = (ebx & (1u << 31)) != 0;
}
/* AVX presence needs OSXSAVE etc., но для уровня x86-64-v* достаточно флага AVX */
ci->has_avx = (ci->ecx1 & (1u << 28)) != 0;
}
/* detect virtualization support flags vmx/svm via /proc/cpuinfo */
static void detect_virt_flags(cpu_info_t *ci) {
FILE *fp = fopen("/proc/cpuinfo", "r");
if (!fp) return;
char line[1024];
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "flags", 5) == 0 || strncmp(line, "Features", 8) == 0) {
if (strstr(line, " vmx")) ci->virt_flag_vmx = true;
if (strstr(line, " svm")) ci->virt_flag_svm = true;
}
}
fclose(fp);
}
/* map to x86-64-v1/v2/v3/v4 по рекомендациям x86-64 psABI / дистров */
static void detect_x86_level(cpu_info_t *ci) {
/* v1: базовый x86-64, предполагаем что если тут вообще 64-bit, то хотя бы v1 */
ci->level = LEVEL_V1;
/* v2: +SSE3, SSSE3, SSE4.1, SSE4.2, POPCNT, CMPXCHG16B, LAHF/SAHF (упрощённо) */
bool v2_ok = ci->has_sse3 && ci->has_ssse3 && ci->has_sse41 &&
ci->has_sse42 && ci->has_popcnt;
if (!v2_ok) return;
ci->level = LEVEL_V2;
/* v3: добавляет AVX, AVX2, BMI1, BMI2, FMA, movbe и т.п (упрощённо) */
bool v3_ok = ci->has_avx && ci->has_avx2 && ci->has_bmi1 &&
ci->has_bmi2 && ci->has_fma;
if (!v3_ok) return;
ci->level = LEVEL_V3;
/* v4 (пока де-факто черновой): AVX-512F/BW/DQ/VL и др. */
bool v4_ok = ci->has_avx512f && ci->has_avx512bw &&
ci->has_avx512dq && ci->has_avx512vl;
if (!v4_ok) return;
ci->level = LEVEL_V4;
}
/* --------------- parsing options ---------------- */
static x86_level_t parse_level(const char *s) {
if (strcasecmp(s, "v1") == 0) return LEVEL_V1;
if (strcasecmp(s, "v2") == 0) return LEVEL_V2;
if (strcasecmp(s, "v3") == 0) return LEVEL_V3;
if (strcasecmp(s, "v4") == 0) return LEVEL_V4;
return LEVEL_UNKNOWN;
}
static const char* level_to_str(x86_level_t l) {
switch (l) {
case LEVEL_V1: return "v1";
case LEVEL_V2: return "v2";
case LEVEL_V3: return "v3";
case LEVEL_V4: return "v4";
default: return "unknown";
}
}
/* --------------- printing ---------------- */
static void print_help(const char *prog) {
printf("Usage: %s [OPTIONS]\n", prog);
printf("Always prints full CPU/virtualization report.\n\n");
printf("Options:\n");
printf(" --require-x86-level=LEVEL Require x86_64 level: v1|v2|v3|v4\n");
printf(" --require-virtualization Require CPU virtualization (VT-x/AMD-V) enabled\n");
printf(" --require-hypervisor Require running inside a hypervisor (VM)\n");
printf(" --require-baremetal Require running on bare metal (no hypervisor)\n");
printf(" --json Output JSON instead of text\n");
printf(" -h, --help Show this help\n\n");
printf("Exit codes:\n");
printf(" 0 All requirements satisfied (or none specified)\n");
printf(" 1 x86_64 level requirement not met\n");
printf(" 2 CPU virtualization not supported\n");
printf(" 3 CPU virtualization supported but not enabled/available\n");
printf(" 4 Environment requirement (hypervisor/baremetal) not met\n");
printf(" 10+ Internal error\n");
}
/* print plain text report */
static void print_text_report(const cpu_info_t *ci,
const options_t *opt,
int status_code,
const char *status_reason) {
printf("=== CPUCHECK REPORT ===\n");
printf("overall_status: %s (exit_code=%d)\n",
status_code == 0 ? "OK" : "FAIL",
status_code);
if (status_reason && *status_reason)
printf("reason: %s\n", status_reason);
printf("\n[CPU]\n");
printf("vendor: %s\n", ci->vendor);
printf("brand: %s\n", ci->brand);
printf("x86_64_level: %s\n", level_to_str(ci->level));
printf("\n[Features]\n");
printf("SSE3: %s\n", ci->has_sse3 ? "yes" : "no");
printf("SSSE3: %s\n", ci->has_ssse3 ? "yes" : "no");
printf("SSE4.1: %s\n", ci->has_sse41 ? "yes" : "no");
printf("SSE4.2: %s\n", ci->has_sse42 ? "yes" : "no");
printf("POPCNT: %s\n", ci->has_popcnt ? "yes" : "no");
printf("AVX: %s\n", ci->has_avx ? "yes" : "no");
printf("AVX2: %s\n", ci->has_avx2 ? "yes" : "no");
printf("BMI1: %s\n", ci->has_bmi1 ? "yes" : "no");
printf("BMI2: %s\n", ci->has_bmi2 ? "yes" : "no");
printf("FMA: %s\n", ci->has_fma ? "yes" : "no");
printf("AVX512F: %s\n", ci->has_avx512f ? "yes" : "no");
printf("AVX512BW: %s\n", ci->has_avx512bw ? "yes" : "no");
printf("AVX512DQ: %s\n", ci->has_avx512dq ? "yes" : "no");
printf("AVX512VL: %s\n", ci->has_avx512vl ? "yes" : "no");
printf("\n[Virtualization]\n");
printf("hypervisor_present (cpuid): %s\n",
ci->hypervisor_present ? "yes" : "no");
printf("virt_flag_vmx (Intel VT-x): %s\n",
ci->virt_flag_vmx ? "yes" : "no");
printf("virt_flag_svm (AMD-V): %s\n",
ci->virt_flag_svm ? "yes" : "no");
if (ci->virt_enabled_known) {
printf("virt_enabled (heuristic lscpu): %s\n",
ci->virt_enabled ? "yes" : "no");
} else {
printf("virt_enabled (heuristic lscpu): unknown\n");
}
printf("\n[Requirements]\n");
if (opt->require_level) {
printf("require_x86_level: %s\n", level_to_str(opt->required_level));
} else {
printf("require_x86_level: (none)\n");
}
printf("require_virtualization: %s\n",
opt->require_virtualization ? "yes" : "no");
printf("require_hypervisor: %s\n",
opt->require_hypervisor ? "yes" : "no");
printf("require_baremetal: %s\n",
opt->require_baremetal ? "yes" : "no");
}
/* print JSON report */
static void print_json_report(const cpu_info_t *ci,
const options_t *opt,
int status_code,
const char *status_reason) {
printf("{\n");
printf(" \"overall_status\": \"%s\",\n",
status_code == 0 ? "OK" : "FAIL");
printf(" \"exit_code\": %d,\n", status_code);
printf(" \"reason\": \"%s\",\n", status_reason ? status_reason : "");
printf(" \"cpu\": {\n");
printf(" \"vendor\": \"%s\",\n", ci->vendor);
printf(" \"brand\": \"%s\",\n", ci->brand);
printf(" \"x86_64_level\": \"%s\"\n", level_to_str(ci->level));
printf(" },\n");
printf(" \"features\": {\n");
printf(" \"SSE3\": %s,\n", ci->has_sse3 ? "true" : "false");
printf(" \"SSSE3\": %s,\n", ci->has_ssse3 ? "true" : "false");
printf(" \"SSE4_1\": %s,\n", ci->has_sse41 ? "true" : "false");
printf(" \"SSE4_2\": %s,\n", ci->has_sse42 ? "true" : "false");
printf(" \"POPCNT\": %s,\n", ci->has_popcnt ? "true" : "false");
printf(" \"AVX\": %s,\n", ci->has_avx ? "true" : "false");
printf(" \"AVX2\": %s,\n", ci->has_avx2 ? "true" : "false");
printf(" \"BMI1\": %s,\n", ci->has_bmi1 ? "true" : "false");
printf(" \"BMI2\": %s,\n", ci->has_bmi2 ? "true" : "false");
printf(" \"FMA\": %s,\n", ci->has_fma ? "true" : "false");
printf(" \"AVX512F\": %s,\n", ci->has_avx512f ? "true" : "false");
printf(" \"AVX512BW\": %s,\n", ci->has_avx512bw ? "true" : "false");
printf(" \"AVX512DQ\": %s,\n", ci->has_avx512dq ? "true" : "false");
printf(" \"AVX512VL\": %s\n", ci->has_avx512vl ? "true" : "false");
printf(" },\n");
printf(" \"virtualization\": {\n");
printf(" \"hypervisor_present\": %s,\n",
ci->hypervisor_present ? "true" : "false");
printf(" \"virt_flag_vmx\": %s,\n",
ci->virt_flag_vmx ? "true" : "false");
printf(" \"virt_flag_svm\": %s,\n",
ci->virt_flag_svm ? "true" : "false");
if (ci->virt_enabled_known) {
printf(" \"virt_enabled\": %s,\n",
ci->virt_enabled ? "true" : "false");
printf(" \"virt_enabled_known\": true\n");
} else {
printf(" \"virt_enabled\": false,\n");
printf(" \"virt_enabled_known\": false\n");
}
printf(" },\n");
printf(" \"requirements\": {\n");
if (opt->require_level) {
printf(" \"require_x86_level\": \"%s\",\n",
level_to_str(opt->required_level));
} else {
printf(" \"require_x86_level\": null,\n");
}
printf(" \"require_virtualization\": %s,\n",
opt->require_virtualization ? "true" : "false");
printf(" \"require_hypervisor\": %s,\n",
opt->require_hypervisor ? "true" : "false");
printf(" \"require_baremetal\": %s\n",
opt->require_baremetal ? "true" : "false");
printf(" }\n");
printf("}\n");
}
/* --------------- main ---------------- */
int main(int argc, char **argv) {
cpu_info_t ci;
memset(&ci, 0, sizeof(ci));
options_t opt;
memset(&opt, 0, sizeof(opt));
/* parse args */
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
print_help(argv[0]);
return 0;
} else if (strncmp(argv[i], "--require-x86-level=", 21) == 0) {
const char *val = argv[i] + 21;
x86_level_t lvl = parse_level(val);
if (lvl == LEVEL_UNKNOWN) {
fprintf(stderr, "Unknown x86 level: %s\n", val);
return 10;
}
opt.require_level = true;
opt.required_level = lvl;
} else if (strcmp(argv[i], "--require-virtualization") == 0) {
opt.require_virtualization = true;
} else if (strcmp(argv[i], "--require-hypervisor") == 0) {
opt.require_hypervisor = true;
} else if (strcmp(argv[i], "--require-baremetal") == 0) {
opt.require_baremetal = true;
} else if (strcmp(argv[i], "--json") == 0) {
opt.json = true;
} else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
return 10;
}
}
/* collect info */
detect_basic_cpu_info(&ci);
detect_virt_flags(&ci);
detect_virtualization_enabled(&ci);
detect_x86_level(&ci);
int exit_code = 0;
char reason[256];
reason[0] = '\0';
/* check requirements (first failing reason wins) */
if (opt.require_level) {
if (ci.level < opt.required_level) {
exit_code = 1;
snprintf(reason, sizeof(reason),
"x86_64 level requirement not met (required=%s, detected=%s)",
level_to_str(opt.required_level),
level_to_str(ci.level));
}
}
if (opt.require_virtualization && exit_code == 0) {
bool supported = ci.virt_flag_vmx || ci.virt_flag_svm;
if (!supported) {
exit_code = 2;
snprintf(reason, sizeof(reason),
"CPU virtualization not supported (no vmx/svm flags)");
} else {
/* supported, check enabled heuristic */
if (ci.virt_enabled_known && !ci.virt_enabled) {
exit_code = 3;
snprintf(reason, sizeof(reason),
"CPU virtualization supported but not enabled/available");
}
}
}
if (opt.require_hypervisor && exit_code == 0) {
if (!ci.hypervisor_present) {
exit_code = 4;
snprintf(reason, sizeof(reason),
"Expected to run inside hypervisor, but hypervisor bit is not set");
}
}
if (opt.require_baremetal && exit_code == 0) {
if (ci.hypervisor_present) {
exit_code = 4;
snprintf(reason, sizeof(reason),
"Expected bare metal, but hypervisor bit is set");
}
}
/* always print full report */
if (opt.json)
print_json_report(&ci, &opt, exit_code, reason);
else
print_text_report(&ci, &opt, exit_code, reason);
return exit_code;
}