How to Debug VMAware When It Incorrectly Detects or Misses Virtual Machines

Enable the __VMAWARE_DEBUG__ macro to trace technique execution, use VM::detect() with a custom flagset to isolate specific checks, inspect VM::memo::cache_fetch() for cached results, and force a full scan by disabling the shortcut flag when VMAware returns unexpected boolean results.

VMAware is a comprehensive C++ library that detects virtual machines by aggregating scores from dozens of hardware and software checks in src/vmaware.hpp. When the library reports false positives on bare metal or misses a hypervisor entirely, you need targeted debugging strategies to identify which techniques are misbehaving. This guide walks through precise debugging steps using the actual source code implementation in kernelwernel/vmaware.

Understanding VMAware's Detection Scoring System

VMAware operates on a point-based accumulation model defined in core::run_all() (lines 11733-11802). The library iterates through a table of technique functions, accumulates points for positive detections, and compares the total against a threshold.

Default thresholds:

  • 150 points: Standard detection threshold
  • 300 points: High-threshold mode when VM::HIGH_THRESHOLD is set (lines 11736-11743)

The main entry point VM::detect() builds a std::bitset of enabled techniques via core::arg_handler() (lines 12081-12089), then executes checks until the accumulated score meets the current threshold. If the threshold is never reached, the function falls back to a hardened-environment check at lines 12112-12133.

Enabling Internal Debug Output

The fastest way to identify which techniques fire is compiling with the __VMAWARE_DEBUG__ macro. This activates the debug_msg helper function (lines 434-442), which prints a unique one-line message the first time each technique runs, showing the contributing points and detected brand.

Compilation flags:


# GCC/Clang

g++ -std=c++20 -DDEBUG your_file.cpp

# MSVC (automatically defined in Debug builds)

cl /DDEBUG your_file.cpp

When enabled, the debug stream outputs lines like:

[DEBUG] CPU: GenuineIntel
[DEBUG] Hypervisor bit: Microsoft Hv
[DEBUG] VM detection points: 172

These messages are emitted only once per technique thanks to the internal printed_messages set tracked at lines 434-472.

Isolating Specific Techniques

When a particular technique produces noise or false positives, isolate it using the VM::detect() overload that accepts a custom flagset. This eliminates interaction effects from other checks.

Step-by-step isolation:

#include "vmaware.hpp"
#include <iostream>

int main() {
    // Generate a flagset with all techniques enabled
    VM::flagset fs = VM::core::generate_all();
    
    // Disable everything except the suspect technique
    fs.reset();
    fs.set(VM::HYPERVISOR_BIT);  // Test only the hypervisor bit check
    
    bool result = VM::detect(fs);
    std::cout << "Hypervisor bit technique returned: " << result << "\n";
}

This approach uses the core::arg_handler() logic at lines 12081-12089 to execute only the specified bit in the flagset.

Inspecting the Memo Cache

VMAware caches technique results to avoid redundant execution. When debugging stale or incorrect results, verify the cache contents using the memo API (lines 11755-11788).

Cache inspection methods:

// After running detection
bool vm = VM::detect();

// Check if a specific technique is cached
if (VM::memo::is_cached(VM::HYPERVISOR_BIT)) {
    auto data = VM::memo::cache_fetch(VM::HYPERVISOR_BIT);
    std::cout << "Cached result: " << data.result << "\n";
    std::cout << "Points: " << static_cast<int>(data.points) << "\n";
}

The cache stores the raw boolean result, point value, and detected brand string. Call VM::memo::clear() to force fresh execution on the next detect() call.

Disabling Early Exit for Full Scans

False negatives often occur because the shortcut optimization stops execution early once the threshold is met. Disable this to reveal hidden points that would push the score higher.

Force complete execution:

VM::flagset all = VM::core::generate_all();

// Pass false as the shortcut argument to run_all()
// Located at lines 11895-11900 in vmaware.hpp
bool full_result = VM::core::run_all(all, false) >= VM::threshold_score;

This ensures every technique runs regardless of the current point total, helping identify under-scoring issues.

Adjusting Detection Thresholds

For borderline environments (such as sandboxes that reveal only minimal hints), switch to high-threshold mode to determine if the library is under-scoring rather than over-scoring.

Enable high-threshold mode:

VM::flagset fs = VM::core::generate_all();
fs.set(VM::HIGH_THRESHOLD);  // Raises requirement from 150 to 300 points

bool strict_result = VM::detect(fs);

This uses the threshold selection logic at lines 11738-11743, requiring stronger evidence before reporting a positive detection.

Complete Debugging Workflow

Combine these techniques into a systematic debugging routine:

#include "vmaware.hpp"
#include <iostream>

int main() {
    // Step 1: Compile with -DDEBUG to enable __VMAWARE_DEBUG__ prints
    
    // Step 2: Run full detection with debug output
    bool vm = VM::detect();
    std::cout << "Standard result: " << vm << "\n\n";
    
    // Step 3: Clear cache and isolate suspect technique
    VM::memo::clear();
    VM::flagset only_hv = VM::core::generate_all();
    only_hv.reset();
    only_hv.set(VM::HYPERVISOR_BIT);
    bool hv = VM::detect(only_hv);
    std::cout << "Hypervisor bit alone: " << hv << "\n";
    
    // Step 4: Check cache state
    if (VM::memo::is_cached(VM::HYPERVISOR_BIT)) {
        auto data = VM::memo::cache_fetch(VM::HYPERVISOR_BIT);
        std::cout << "Cached points: " << static_cast<int>(data.points) << "\n";
    }
    
    // Step 5: Force full scan without shortcut
    VM::flagset all = VM::core::generate_all();
    int full_score = VM::core::run_all(all, false);
    std::cout << "Full scan score: " << full_score 
              << " (threshold: " << VM::threshold_score << ")\n";
    
    return 0;
}

Compile with debug symbols and the DEBUG macro to see the internal technique trace, then correlate the output with the specific line ranges in src/vmaware.hpp to identify malfunctioning checks.

Summary

  • Enable __VMAWARE_DEBUG__ to trace which techniques execute and their point contributions via the debug_msg helper at lines 434-442.
  • Isolate techniques using VM::flagset with core::generate_all() and specific bit resets to test individual checks without interference.
  • Inspect the memo cache through VM::memo::cache_fetch() to verify cached results at lines 11755-11788, and call VM::memo::clear() to reset state.
  • Disable the shortcut by passing false to core::run_all() (lines 11895-11900) to force complete execution and reveal hidden detection points.
  • Adjust thresholds using VM::HIGH_THRESHOLD to switch between 150-point and 300-point modes (lines 11736-11743).

Frequently Asked Questions

Why does VMAware detect a VM when I'm running on bare metal?

False positives typically occur when specific hardware or software configurations mimic hypervisor signatures. Enable __VMAWARE_DEBUG__ to see which techniques contribute points, then isolate each technique using a custom flagset to identify the false positive source. Check the memo cache with VM::memo::cache_fetch() to verify if stale data is causing the incorrect result.

How do I permanently disable a noisy technique?

Construct a VM::flagset using VM::core::generate_all(), then call fs.reset(VM::TECHNIQUE_NAME) for each technique you want to disable. Pass this filtered flagset to VM::detect(fs). For permanent removal, modify the library source at src/vmaware.hpp lines 12081-12089 where core::arg_handler() processes technique bits.

What does the memo cache store and when should I clear it?

The memo cache stores a struct containing the raw boolean result, point value, and detected brand string for each technique ID (lines 11755-11788). Clear the cache by calling VM::memo::clear() when you suspect stale results between detection runs, or when debugging to ensure fresh execution of all techniques.

How do I interpret the point values in debug output?

Each technique contributes a specific weight (typically 50-100 points) defined in the technique table at lines 11733-11802. The debug output shows the running total. If the total reaches 150 (or 300 in high-threshold mode), detect() returns true. Compare the debug output against the threshold to determine if the environment is under-scoring (few techniques fire) or over-scoring (noisy techniques fire incorrectly).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →