<===

ProNotes

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;
}
← Previous
Back to list