From 4e0ab94ee24e4d78ae3de28c041153f61a15ae95 Mon Sep 17 00:00:00 2001 From: Shawn Date: Fri, 10 Jul 2026 00:58:56 -0400 Subject: [PATCH] feat: GPU detection for LLM server (NVIDIA/AMD/Intel/Vulkan + CPU fallback) - Added start-llama-server.bat with cross-brand GPU auto-detection: * Detection 1: nvidia-smi for NVIDIA * Detection 2: WMIC for AMD, Intel Arc, Intel HD/Iris/UHD * Detection 3: vulkaninfo as final fallback probe * Falls back to CPU-only build when no GPU found - Downloaded llama-server b9947 builds: * Vulkan build (91 MB) - supports all GPU brands * CPU build (45 MB) - fallback in cpu/ subdir - Downloaded Qwen2.5-Coder-1.5B Q4_K_M model (1.1 GB) - Fixed run-hermes.bat polling: replaces fixed 5s timeout with curl-based readiness polling (up to 30 attempts, 1s apart) - All .bat files verified: ASCII text, CRLF line terminators - Added .gitignore for build artifacts and large binaries --- .gitignore | 32 + build/build-custom-iso.sh | 734 +++++++++++ build/output/run-hermes.bat | 165 +++ build/output/start-llama-server.bat | 125 ++ build/quick-rebuild-iso.sh | 96 ++ src/diagnostics/__init__.py | 13 + src/diagnostics/__main__.py | 9 + src/diagnostics/bsod.py | 1660 ++++++++++++++++++++++++ src/diagnostics/drivers.py | 888 +++++++++++++ src/diagnostics/hardware.py | 56 +- src/diagnostics/stress.py | 21 +- src/diagnostics/tests/test_hardware.py | 515 ++++++++ src/hermes/autorun.sh | 8 +- src/hermes/config.yaml | 97 +- 14 files changed, 4353 insertions(+), 66 deletions(-) create mode 100644 .gitignore create mode 100755 build/build-custom-iso.sh create mode 100644 build/output/run-hermes.bat create mode 100644 build/output/start-llama-server.bat create mode 100755 build/quick-rebuild-iso.sh create mode 100644 src/diagnostics/__main__.py create mode 100644 src/diagnostics/bsod.py create mode 100644 src/diagnostics/drivers.py create mode 100644 src/diagnostics/tests/test_hardware.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c59e73f --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Build artifacts (large binaries, not tracked in git) +build/output/hermes-rescue.iso +build/output/casper-rw +build/output/hermes-data.ext4 +build/output/fix-line-endings.ps1 +build/iso-staging/ +build/xubuntu-24.04*.iso +build/SHA256SUMS +build/hermes-build.fs + +# Model and native binaries (large, downloaded at build time) +*.gguf +*.dll +*.exe + +# Python cache +__pycache__/ +*.pyc +*.pyo + +# IDE/editor +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Secrets +src/hermes/auth.json diff --git a/build/build-custom-iso.sh b/build/build-custom-iso.sh new file mode 100755 index 0000000..3937c0a --- /dev/null +++ b/build/build-custom-iso.sh @@ -0,0 +1,734 @@ +#!/usr/bin/env bash +# ============================================================================== +# Hermes Portable Rescue — Custom Live ISO Builder +# ============================================================================== +# +# Builds a custom Xubuntu 24.04 LTS live ISO with Hermes Agent pre-installed, +# diagnostic tools, autorun setup, and persistent storage support. +# +# Usage: +# sudo ./build/build-custom-iso.sh [options] +# +# Options: +# --input-iso PATH Path to Xubuntu 24.04 ISO (default: auto-detect) +# --output-dir DIR Output directory (default: ./build/output) +# --work-dir DIR Working directory for build (default: /tmp/hermes-iso-build) +# --skip-download Don't download ISO if missing +# --keep-workdir Don't clean up workdir after build +# --help Show this message +# +# Requirements: +# - squashfs-tools (mksquashfs, unsquashfs) +# - xorriso +# - syslinux-common, isolinux +# - sudo access for mount operations +# +# ============================================================================== + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +BUILD_DIR="$PROJECT_DIR/build" + +# ─── Defaults ────────────────────────────────────────────────────────────────── + +DEFAULT_INPUT_ISO="$BUILD_DIR/xubuntu-24.04.4-desktop-amd64.iso" +OUTPUT_DIR="${OUTPUT_DIR:-$BUILD_DIR/output}" +WORKDIR="${WORKDIR:-/tmp/hermes-iso-build}" +SKIP_DOWNLOAD=false +KEEP_WORKDIR=false + +# ─── Colors ──────────────────────────────────────────────────────────────────── + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' +info() { echo -e "${BLUE}[INFO]${NC} $*"; } +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +die() { error "$*"; exit 1; } + +# ─── Parse Arguments ─────────────────────────────────────────────────────────── + +INPUT_ISO="$DEFAULT_INPUT_ISO" + +while [[ $# -gt 0 ]]; do + case "$1" in + --input-iso) INPUT_ISO="$2"; shift 2 ;; + --output-dir) OUTPUT_DIR="$2"; shift 2 ;; + --work-dir) WORKDIR="$2"; shift 2 ;; + --skip-download) SKIP_DOWNLOAD=true; shift ;; + --keep-workdir) KEEP_WORKDIR=true; shift ;; + -h|--help) head -50 "$0"; exit 0 ;; + *) die "Unknown option: $1" ;; + esac +done + +# ─── Prerequisites ───────────────────────────────────────────────────────────── + +check_command() { + if ! command -v "$1" &>/dev/null; then + die "Required command '$1' not found" + fi +} + +prerequisites() { + info "Checking prerequisites..." + check_command unsquashfs + check_command mksquashfs + check_command xorriso + check_command dd + check_command mount + check_command rsync + check_command chroot + ok "All prerequisites available." +} + +# ─── Phase 1: Download / Verify ISO ──────────────────────────────────────────── + +phase_download_iso() { + if [[ -f "$INPUT_ISO" ]]; then + local iso_size + iso_size=$(stat -c%s "$INPUT_ISO" 2>/dev/null || echo 0) + if [[ "$iso_size" -gt 1000000000 ]]; then + ok "Input ISO found: $INPUT_ISO ($(du -h "$INPUT_ISO" | cut -f1))" + # Verify SHA256 + local expected_sha="fc2e995bb05c41ea19f1dbfd91f6deea7b2aed7a83b9934d98fc9d9cac527d97" + local actual_sha + actual_sha=$(sha256sum "$INPUT_ISO" | awk '{print $1}') + if [[ "$actual_sha" == "$expected_sha" ]]; then + ok "SHA256 verified." + else + warn "SHA256 mismatch! Expected $expected_sha, got $actual_sha" + warn "Continuing anyway — ISO may be incomplete or different build." + fi + return 0 + fi + fi + + if $SKIP_DOWNLOAD; then + die "ISO not found at $INPUT_ISO and --skip-download is set." + fi + + info "Downloading Xubuntu 24.04.4 LTS Desktop ISO (~4.1 GB)..." + wget -c "https://cdimage.ubuntu.com/xubuntu/releases/24.04/release/xubuntu-24.04.4-desktop-amd64.iso" \ + -O "$INPUT_ISO" 2>&1 + ok "Download complete." +} + +# ─── Phase 2: Extract ISO ───────────────────────────────────────────────────── + +phase_extract_iso() { + info "Extracting ISO contents..." + + local iso_mount="/mnt/hermes-iso-source" + mkdir -p "$iso_mount" "$WORKDIR/iso-contents" + + # Mount ISO (handle both .iso and .img) + mount -o loop "$INPUT_ISO" "$iso_mount" 2>/dev/null || \ + die "Failed to mount ISO. Is it a valid ISO file?" + + # List contents for debugging + info "ISO contents:" + ls -la "$iso_mount/" 2>/dev/null + echo "" + + # Determine boot structure + if [[ -d "$iso_mount/isolinux" ]]; then + ok "BIOS boot: isolinux detected" + fi + if [[ -f "$iso_mount/boot/grub/efi.img" ]] || [[ -f "$iso_mount/boot/grub/eltorito.img" ]]; then + ok "UEFI boot: GRUB efi.img detected" + fi + + # Check squashfs location + local squashfs_src="" + if [[ -f "$iso_mount/casper/filesystem.squashfs" ]]; then + squashfs_src="$iso_mount/casper/filesystem.squashfs" + ok "Squashfs: casper/filesystem.squashfs" + elif [[ -f "$iso_mount/casper/filesystem.squashfs.xz" ]]; then + squashfs_src="$iso_mount/casper/filesystem.squashfs.xz" + ok "Squashfs: casper/filesystem.squashfs.xz (compressed)" + else + ls -la "$iso_mount/" "$iso_mount/casper/" 2>/dev/null + umount "$iso_mount" + die "Cannot locate filesystem.squashfs in the ISO." + fi + + # Copy everything EXCEPT the squashfs (we'll replace it) + info "Copying ISO contents (excluding squashfs)..." + rsync -a --exclude="casper/filesystem.squashfs*" \ + "$iso_mount/" "$WORKDIR/iso-contents/" 2>&1 | tail -5 + + # Copy the squashfs for extraction + cp "$squashfs_src" "$WORKDIR/filesystem.squashfs" + + # Extract the MBR from the ISO for hybrid boot + info "Extracting MBR boot sector..." + dd if="$INPUT_ISO" of="$WORKDIR/iso-contents/isohdpfx.bin" bs=440 count=1 2>/dev/null + ok "MBR extracted." + + umount "$iso_mount" + rmdir "$iso_mount" + ok "ISO contents extracted to $WORKDIR/iso-contents" +} + +# ─── Phase 3: Unsquash Filesystem ────────────────────────────────────────────── + +phase_unsquash() { + info "Unsquashing filesystem (this may take a few minutes)..." + + mkdir -p "$WORKDIR/squashfs-root" + + if [[ "$WORKDIR/filesystem.squashfs" == *.xz ]]; then + xz -d "$WORKDIR/filesystem.squashfs" -c > "$WORKDIR/filesystem.squashfs.unxz" 2>/dev/null + unsquashfs -d "$WORKDIR/squashfs-root" -f "$WORKDIR/filesystem.squashfs.unxz" 2>&1 | tail -3 + else + unsquashfs -d "$WORKDIR/squashfs-root" -f "$WORKDIR/filesystem.squashfs" 2>&1 | tail -3 + fi + + local root_size + root_size=$(du -sh "$WORKDIR/squashfs-root" | cut -f1) + ok "Filesystem unsquashed: $root_size" +} + +# ─── Phase 4: Customize Filesystem (Chroot) ──────────────────────────────────── + +# This function writes the customization script that runs INSIDE the chroot +generate_chroot_script() { + cat > "$WORKDIR/chroot-customize.sh" << 'CHROOTSCRIPT' +#!/bin/bash +# ============================================================================== +# Hermes Portable Rescue — Chroot Customization Script +# ============================================================================== +# Runs inside the live filesystem chroot to install everything. +# ============================================================================== + +set -euo pipefail + +# Redirect output to log +exec > /tmp/chroot-build.log 2>&1 + +echo "=== Hermes Portable Rescue — Chroot Customization ===" +echo "Date: $(date)" +echo "" + +# ─── 1. Mount virtual filesystems ────────────────────────────────────────────── +mount -t proc none /proc +mount -t sysfs none /sys +mount -t devtmpfs none /dev || mount -t tmpfs none /dev +mkdir -p /dev/pts +mount -t devpts none /dev/pts + +# ─── 2. Configure package sources ────────────────────────────────────────────── +echo "=== Configuring package sources ===" +apt-get update -qq 2>/dev/null || true + +# ─── 3. Install essential packages ───────────────────────────────────────────── +echo "=== Installing packages ===" + +PACKAGES=( + # Python + dev + python3-pip + python3-venv + python3-setuptools + python3-wheel + git + + # Diagnostic tools + smartmontools + dmidecode + lshw + testdisk + ddrescue + ntfs-3g + stress-ng + + # Network tools + nmap + net-tools + curl + wget + openssh-client + iperf3 + traceroute + + # Storage tools + parted + rsync + htop + iotop + exfatprogs + btrfs-progs + xfsprogs + + # Filesystem tools + dosfstools + e2fsprogs + + # Windows-specific tools + hivex # Windows registry access +) + +apt-get install -y -qq "${PACKAGES[@]}" 2>&1 | tail -5 +echo "Package installation complete." + +# ─── 4. Install Hermes Agent ─────────────────────────────────────────────────── +echo "=== Installing Hermes Agent ===" + +# Create Hermes directory structure +mkdir -p /opt/hermes/{agent,tools,scripts,models,diagnostics,config} + +# Install from pip +pip3 install --no-cache-dir \ + hermes-agent \ + pyyaml \ + psutil \ + requests \ + 2>&1 | tail -3 + +# Freeze package list for provenance +pip3 freeze > /opt/hermes/pip-freeze.txt + +echo "Hermes Agent installed." + +# ─── 5. Configure Hermes autorun service ─────────────────────────────────────── +echo "=== Configuring autorun ===" + +# Systemd service that runs the Hermes rescue autorun on boot +cat > /etc/systemd/system/hermes-autorun.service << 'SERVICEEOF' +[Unit] +Description=Hermes Portable Rescue — Auto-Diagnostic Agent +After=network.target multi-user.target +ConditionPathExists=/opt/hermes/autorun.sh + +[Service] +Type=oneshot +ExecStart=/opt/hermes/autorun.sh +RemainAfterExit=yes +StandardOutput=journal+console +StandardError=journal+console +Environment=HERMES_DIR=/opt/hermes +Environment=CONFIG=/opt/hermes/config.yaml +Environment=REPORT_DIR=/opt/hermes/reports +Environment=BOOT_MODE=live + +[Install] +WantedBy=multi-user.target +SERVICEEOF + +# Desktop autostart for GUI mode — opens a terminal running Hermes +mkdir -p /etc/xdg/autostart +cat > /etc/xdg/autostart/hermes-rescue.desktop << 'DESKTOPDF' +[Desktop Entry] +Type=Application +Name=Hermes Portable Rescue +Comment=AI-driven PC diagnostic & repair toolkit +Exec=xfce4-terminal --title="Hermes Rescue" -e "/opt/hermes/autorun.sh" --hold +Terminal=false +Categories=System;Utility; +StartupNotify=true +X-GNOME-Autostart-enabled=true +DESKTOPDF + +# Also create a desktop shortcut on the user desktop for manual launch +mkdir -p /etc/skel/Desktop +cat > /etc/skel/Desktop/hermes-rescue.desktop << 'DESKTOPEOF' +[Desktop Entry] +Type=Application +Name=Hermes Rescue +Comment=AI-driven PC diagnostic & repair toolkit +Exec=xfce4-terminal --title="Hermes Rescue" -e "/opt/hermes/autorun.sh" --hold +Terminal=false +Icon=system-run +Categories=System;Utility; +StartupNotify=true +DESKTOPEOF +chmod +x /etc/skel/Desktop/hermes-rescue.desktop + +# Enable the service for live boot +systemctl enable hermes-autorun.service 2>/dev/null || true + +echo "Autorun service configured." + +# ─── 6. Create symlinks for diagnostic scripts ───────────────────────────────── +echo "=== Setting up diagnostic scripts ===" + +# The scripts will be copied from the project source via bind mount. +# Create placeholder structure here — actual files come from the build host. +mkdir -p /opt/hermes/scripts +cat > /opt/hermes/scripts/README.txt << 'README' +Diagnostic scripts are copied from the build host at ISO build time. +Place your scripts in /opt/hermes/scripts/ on the running system. +README + +echo "Diagnostic scripts directory ready." + +# ─── 7. Configure auto-login (lightdm for XFCE) ──────────────────────────────── +echo "=== Configuring auto-login ===" + +# LightDM auto-login for the live session user (xubuntu) +mkdir -p /etc/lightdm/lightdm.conf.d +cat > /etc/lightdm/lightdm.conf.d/50-hermes-autologin.conf << 'LIGHTDMEOF' +[Seat:*] +autologin-user=xubuntu +autologin-user-timeout=0 +autologin-session=xubuntu +LIGHTDMEOF + +echo "Auto-login configured for 'xubuntu'." + +# ─── 8. Create persistent storage hook ───────────────────────────────────────── +echo "=== Setting up persistent storage hook ===" + +# Script to mount persistent storage if available +mkdir -p /opt/hermes/scripts +cat > /opt/hermes/scripts/mount-persistent.sh << 'MOUNTEOF' +#!/bin/bash +# Mount persistent storage partition (NTFS/exFAT labeled HERMES_DATA) +set -e + +PERSISTENT_LABEL="HERMES_DATA" +MOUNT_POINT="/mnt/hermes-data" + +mkdir -p "$MOUNT_POINT" + +# Find and mount by label +for dev in /dev/sd* /dev/nvme* /dev/mmcblk*; do + if [ -b "$dev" ]; then + label=$(blkid -o value -s LABEL "$dev" 2>/dev/null || echo "") + if [ "$label" = "$PERSISTENT_LABEL" ]; then + fstype=$(blkid -o value -s TYPE "$dev" 2>/dev/null || echo "") + case "$fstype" in + ntfs) mount -t ntfs-3g "$dev" "$MOUNT_POINT" 2>/dev/null && break ;; + ext4) mount "$dev" "$MOUNT_POINT" 2>/dev/null && break ;; + vfat|exfat) mount "$dev" "$MOUNT_POINT" 2>/dev/null && break ;; + esac + fi + fi +done + +if mountpoint -q "$MOUNT_POINT"; then + echo "Persistent storage mounted at $MOUNT_POINT" + # Symlink persistent config + if [ -f "$MOUNT_POINT/hermes/config.yaml" ]; then + ln -sf "$MOUNT_POINT/hermes/config.yaml" /opt/hermes/config.yaml + fi +else + echo "No persistent storage found. Running in temporary mode." +fi +MOUNTEOF +chmod +x /opt/hermes/scripts/mount-persistent.sh + +echo "Persistent storage hook created." + +# ─── 9. Clean up package caches ───────────────────────────────────────────────── +echo "=== Cleaning up ===" +apt-get clean -qq 2>/dev/null || true +apt-get autoremove --purge -qq 2>/dev/null || true +rm -rf /var/lib/apt/lists/* 2>/dev/null || true +rm -rf /root/.cache 2>/dev/null || true +rm -rf /tmp/* 2>/dev/null || true + +# Clean Python caches +find /usr -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true +find /usr -type f -name '*.pyc' -delete 2>/dev/null || true +find /opt/hermes -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true +find /opt/hermes -type f -name '*.pyc' -delete 2>/dev/null || true + +echo "" +echo "=== Chroot customization complete! ===" +echo "Packages installed: $(dpkg-query -W -f='${Package}\n' 2>/dev/null | wc -l)" +echo "Hermes config at: /opt/hermes/" +echo "Autorun service: hermes-autorun.service" +echo "" + +# Unmount virtual filesystems +umount /dev/pts 2>/dev/null || true +umount /sys 2>/dev/null || true +umount /proc 2>/dev/null || true + +exit 0 +CHROOTSCRIPT + + chmod +x "$WORKDIR/chroot-customize.sh" +} + +phase_customize() { + info "Preparing chroot environment..." + + SQUASHFS_ROOT="$WORKDIR/squashfs-root" + + # Bind mount necessary filesystems + mount --bind /dev "$SQUASHFS_ROOT/dev" + mount --bind /proc "$SQUASHFS_ROOT/proc" + mount --bind /sys "$SQUASHFS_ROOT/sys" + mount -t devpts none "$SQUASHFS_ROOT/dev/pts" 2>/dev/null || true + + # Network connectivity inside chroot + cp /etc/resolv.conf "$SQUASHFS_ROOT/etc/resolv.conf" + + # Copy the project's diagnostic scripts and config into the chroot + mkdir -p "$SQUASHFS_ROOT/tmp/hermes-project" + if [[ -d "$PROJECT_DIR/src/diagnostics" ]]; then + cp -r "$PROJECT_DIR/src/diagnostics/"* "$SQUASHFS_ROOT/tmp/hermes-project/" 2>/dev/null || true + fi + if [[ -f "$PROJECT_DIR/src/hermes/config.yaml" ]]; then + cp "$PROJECT_DIR/src/hermes/config.yaml" "$SQUASHFS_ROOT/tmp/hermes-project/" 2>/dev/null || true + fi + if [[ -f "$PROJECT_DIR/src/hermes/autorun.sh" ]]; then + cp "$PROJECT_DIR/src/hermes/autorun.sh" "$SQUASHFS_ROOT/tmp/hermes-project/" 2>/dev/null || true + fi + if [[ -f "$PROJECT_DIR/src/hermes/launch.sh" ]]; then + cp "$PROJECT_DIR/src/hermes/launch.sh" "$SQUASHFS_ROOT/tmp/hermes-project/" 2>/dev/null || true + fi + if [[ -d "$PROJECT_DIR/src/hermes/scripts" ]]; then + cp -r "$PROJECT_DIR/src/hermes/scripts/"* "$SQUASHFS_ROOT/tmp/hermes-project/scripts/" 2>/dev/null || true + fi + + # Copy the chroot script in + cp "$WORKDIR/chroot-customize.sh" "$SQUASHFS_ROOT/tmp/chroot-customize.sh" + chmod +x "$SQUASHFS_ROOT/tmp/chroot-customize.sh" + + info "Running chroot customization (this may take 10-20 minutes)..." + echo "" + + if chroot "$SQUASHFS_ROOT" /bin/bash /tmp/chroot-customize.sh; then + ok "Chroot customization completed successfully." + else + warn "Chroot script exited with code $? — checking log..." + fi + + # Check the build log + if [[ -f "$SQUASHFS_ROOT/tmp/chroot-build.log" ]]; then + echo "" + info "Chroot build log (last 30 lines):" + tail -30 "$SQUASHFS_ROOT/tmp/chroot-build.log" + fi + + # Copy project files from temp to /opt/hermes + info "Installing project files..." + if [[ -f "$SQUASHFS_ROOT/tmp/hermes-project/config.yaml" ]]; then + cp "$SQUASHFS_ROOT/tmp/hermes-project/config.yaml" "$SQUASHFS_ROOT/opt/hermes/config.yaml" + fi + if [[ -f "$SQUASHFS_ROOT/tmp/hermes-project/autorun.sh" ]]; then + cp "$SQUASHFS_ROOT/tmp/hermes-project/autorun.sh" "$SQUASHFS_ROOT/opt/hermes/autorun.sh" + chmod +x "$SQUASHFS_ROOT/opt/hermes/autorun.sh" + fi + if [[ -f "$SQUASHFS_ROOT/tmp/hermes-project/launch.sh" ]]; then + cp "$SQUASHFS_ROOT/tmp/hermes-project/launch.sh" "$SQUASHFS_ROOT/opt/hermes/launch.sh" + chmod +x "$SQUASHFS_ROOT/opt/hermes/launch.sh" + fi + if [[ -d "$SQUASHFS_ROOT/tmp/hermes-project/scripts" ]]; then + cp -r "$SQUASHFS_ROOT/tmp/hermes-project/scripts/"* "$SQUASHFS_ROOT/opt/hermes/scripts/" 2>/dev/null || true + fi + if [[ -d "$SQUASHFS_ROOT/tmp/hermes-project/diagnostics" ]]; then + cp -r "$SQUASHFS_ROOT/tmp/hermes-project/diagnostics/"* "$SQUASHFS_ROOT/opt/hermes/scripts/" 2>/dev/null || true + fi + if [[ -d "$SQUASHFS_ROOT/tmp/hermes-project/__init__.py" ]]; then + cp "$SQUASHFS_ROOT/tmp/hermes-project/__init__.py" "$SQUASHFS_ROOT/opt/hermes/scripts/" 2>/dev/null || true + fi + if [[ -f "$SQUASHFS_ROOT/tmp/hermes-project/__main__.py" ]]; then + cp "$SQUASHFS_ROOT/tmp/hermes-project/__main__.py" "$SQUASHFS_ROOT/opt/hermes/scripts/" 2>/dev/null || true + fi + + # Clean up + rm -rf "$SQUASHFS_ROOT/tmp/hermes-project" "$SQUASHFS_ROOT/tmp/chroot-customize.sh" 2>/dev/null || true +} + +# ─── Phase 5: Clean Chroot ───────────────────────────────────────────────────── + +phase_clean_chroot() { + info "Cleaning chroot..." + + SQUASHFS_ROOT="$WORKDIR/squashfs-root" + + # Remove resolv.conf + rm -f "$SQUASHFS_ROOT/etc/resolv.conf" + + # Unmount virtual filesystems + umount "$SQUASHFS_ROOT/dev/pts" 2>/dev/null || true + umount "$SQUASHFS_ROOT/sys" 2>/dev/null || true + umount "$SQUASHFS_ROOT/proc" 2>/dev/null || true + umount "$SQUASHFS_ROOT/dev" 2>/dev/null || true + + ok "Chroot cleaned." +} + +# ─── Phase 6: Re-pack Squashfs ───────────────────────────────────────────────── + +phase_repack_squashfs() { + info "Re-packing squashfs filesystem (this takes a while)..." + + local target_squashfs="$WORKDIR/iso-contents/casper/filesystem.squashfs" + mkdir -p "$(dirname "$target_squashfs")" + + # Rebuild with high compression + mksquashfs "$WORKDIR/squashfs-root" "$target_squashfs" \ + -comp xz -b 1M -Xdict-size 100% \ + -noappend \ + 2>&1 | tail -5 + + local squash_size + squash_size=$(du -h "$target_squashfs" | cut -f1) + ok "Squashfs re-packed: $squash_size" + + # Update filesystem.size for casper + info "Updating filesystem.size..." + local fs_size + fs_size=$(du -sx --block-size=1 "$WORKDIR/squashfs-root" 2>/dev/null | cut -f1) + echo "$fs_size" > "$WORKDIR/iso-contents/casper/filesystem.size" + ok "filesystem.size updated: $fs_size bytes" +} + +# ─── Phase 7: Generate ISO ───────────────────────────────────────────────────── + +phase_generate_iso() { + info "Generating bootable ISO..." + + mkdir -p "$OUTPUT_DIR" + local output_iso="$OUTPUT_DIR/hermes-rescue.iso" + local iso_contents="$WORKDIR/iso-contents" + + # Build the ISO with hybrid MBR (BIOS + UEFI) + cd "$iso_contents" + + # Determine which efi.img to use + local efi_img="" + if [[ -f "boot/grub/efi.img" ]]; then + efi_img="boot/grub/efi.img" + elif [[ -f "boot/grub/eltorito.img" ]]; then + efi_img="boot/grub/eltorito.img" + else + # Search for any efi boot image + efi_img=$(find . -name "efi.img" -o -name "eltorito.img" 2>/dev/null | head -1) + if [[ -z "$efi_img" ]]; then + warn "No EFI boot image found — ISO may not boot on UEFI systems." + fi + fi + + if [[ -n "$efi_img" ]]; then + info "Using EFI image: $efi_img" + xorriso -as mkisofs -r -V "HERMES_RESCUE" \ + -J -joliet-long \ + -isohybrid-mbr "$iso_contents/isohdpfx.bin" \ + -b isolinux/isolinux.bin \ + -c isolinux/boot.cat \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + -eltorito-alt-boot -e "$efi_img" -no-emul-boot \ + -isohybrid-gpt-basdat \ + -isohybrid-apm-hfsplus \ + -o "$output_iso" \ + "$iso_contents" 2>&1 | tail -10 + else + # BIOS-only fallback + warn "Building BIOS-only ISO (no UEFI support)." + xorriso -as mkisofs -r -V "HERMES_RESCUE" \ + -J -joliet-long \ + -isohybrid-mbr "$iso_contents/isohdpfx.bin" \ + -b isolinux/isolinux.bin \ + -c isolinux/boot.cat \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + -o "$output_iso" \ + "$iso_contents" 2>&1 | tail -10 + fi + + local iso_size + iso_size=$(du -h "$output_iso" | cut -f1) + ok "ISO generated: $output_iso ($iso_size)" + + cd "$PROJECT_DIR" +} + +# ─── Phase 8: Create Persistent Storage Overlay ──────────────────────────────── + +phase_create_persistence() { + info "Creating persistent storage files..." + + # Create a casper-rw file for persistence (1GB by default) + local persist_size="${PERSIST_SIZE:-1024}" + local persist_file="$OUTPUT_DIR/casper-rw" + + if [[ -f "$persist_file" ]]; then + warn "casper-rw already exists — skipping." + return 0 + fi + + dd if=/dev/zero of="$persist_file" bs=1M count="$persist_size" status=progress 2>/dev/null + mkfs.ext4 -F -L casper-rw "$persist_file" 2>/dev/null + ok "Persistent storage created: $persist_file (${persist_size}MB)" + + # Create a writeable partition image for Windows-accessible data + local data_img="$OUTPUT_DIR/hermes-data.ext4" + if [[ ! -f "$data_img" ]]; then + dd if=/dev/zero of="$data_img" bs=1M count=512 status=progress 2>/dev/null + mkfs.ext4 -F -L HERMES_DATA "$data_img" 2>/dev/null + ok "Data partition image: $data_img (512MB)" + fi +} + +# ─── Phase 9: Cleanup ────────────────────────────────────────────────────────── + +phase_cleanup() { + if $KEEP_WORKDIR; then + info "Keeping workdir at $WORKDIR" + return 0 + fi + + info "Cleaning up build artifacts..." + rm -rf "$WORKDIR/squashfs-root" 2>/dev/null || true + rm -f "$WORKDIR/filesystem.squashfs" "$WORKDIR/filesystem.squashfs.unxz" 2>/dev/null || true + rm -rf "$WORKDIR/iso-contents" 2>/dev/null || true + rm -f "$WORKDIR/chroot-customize.sh" 2>/dev/null || true + ok "Cleanup complete." +} + +# ─── Main ────────────────────────────────────────────────────────────────────── + +main() { + echo "" + echo "╔════════════════════════════════════════════════════════╗" + echo "║ Hermes Portable Rescue — Live ISO Builder ║" + echo "╚════════════════════════════════════════════════════════╝" + echo "" + echo " Input ISO: $INPUT_ISO" + echo " Output: $OUTPUT_DIR/hermes-rescue.iso" + echo " Workdir: $WORKDIR" + echo " Project: $PROJECT_DIR" + echo "" + + # Phase count + TOTAL=8 + CURRENT=0 + + CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Prerequisites"; prerequisites + CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Download/Verify ISO"; phase_download_iso + CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Extract ISO"; phase_extract_iso + CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Unsquash filesystem"; phase_unsquash + CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Generate chroot script"; generate_chroot_script + CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Customize (chroot)"; phase_customize + CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Clean chroot"; phase_clean_chroot + CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Re-pack squashfs"; phase_repack_squashfs + CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Generate ISO"; phase_generate_iso + CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Create persistence"; phase_create_persistence + CURRENT=$((CURRENT + 1)); echo ""; echo "[${CURRENT}/${TOTAL}] Cleanup"; phase_cleanup + + echo "" + echo "╔════════════════════════════════════════════════════════╗" + echo "║ BUILD COMPLETE ║" + echo "╚════════════════════════════════════════════════════════╝" + echo "" + echo " ISO: $OUTPUT_DIR/hermes-rescue.iso" + echo " Persist: $OUTPUT_DIR/casper-rw" + echo " Data img: $OUTPUT_DIR/hermes-data.ext4" + echo "" + echo "Write to USB:" + echo " # Copy ISO to a Ventoy USB, or write directly:" + echo " sudo dd if=$OUTPUT_DIR/hermes-rescue.iso of=/dev/sdX bs=4M status=progress" + echo "" + echo "For persistence, copy casper-rw to the USB root alongside the ISO," + echo "or the live system will auto-detect a partition labeled 'HERMES_DATA'." + echo "" +} + +main "$@" diff --git a/build/output/run-hermes.bat b/build/output/run-hermes.bat new file mode 100644 index 0000000..1dfdf17 --- /dev/null +++ b/build/output/run-hermes.bat @@ -0,0 +1,165 @@ +@echo off +title Hermes Portable Rescue - Windows Agent +setlocal enabledelayedexpansion + +cd /d "%~dp0" + +set HERMES_HOME=%~dp0..\hermes +set HERMES_ROOT=%~dp0..\hermes +set HERMES_CONFIG=%HERMES_ROOT%\config.yaml +set PYTHON=%~dp0python\python.exe +set PIP=%~dp0python\Scripts\pip.exe +set WHEELS_DIR=%~dp0bin\wheels +set MARKER=%~dp0\.installed +set WEB_SERVER=%~dp0..\hermes\web\server.py +set OLLAMA_DIR=%~dp0bin\ollama + +echo. +echo ============================================ +echo Hermes Portable Rescue - Windows Agent +echo ============================================ +echo. +echo Python: !PYTHON! +echo Config: !HERMES_CONFIG! +echo. + +:: Check Python +if not exist "!PYTHON!" ( + echo [ERROR] Python not found at !PYTHON! + pause + exit /b 1 +) + +:: First-run: bootstrap pip + install hermes + zeroconf +if not exist "!MARKER!" ( + echo [*] First run -- installing Hermes Agent... + echo. + "!PYTHON!" "%~dp0bin\get-pip.py" --quiet --no-warn-script-location 2>&1 + if errorlevel 1 ( + echo [ERROR] Failed to bootstrap pip. + pause + exit /b 1 + ) + "!PYTHON!" -m pip install --no-index --find-links "!WHEELS_DIR!" --quiet hermes-agent zeroconf ifaddr 2>&1 + if errorlevel 1 ( + echo [ERROR] Failed to install Hermes Agent. + pause + exit /b 1 + ) + echo. > "!MARKER!" + echo [OK] Hermes Agent installed. + echo. +) + +:: Launch menu +:menu +cls +echo ============================================ +echo Hermes Portable Rescue - Windows Agent +echo ============================================ +echo. +echo Python: !PYTHON! +echo Config: !HERMES_CONFIG! +echo. +echo What would you like to do? +echo. +echo 1) Hermes CLI (ONLINE - opencode-go) +echo 2) Hermes CLI (OFFLINE - local LLM) +echo 3) Web Dashboard (http://rescue.local:8080) +echo 4) Full Diagnostics (hardware, disk, mem, stress) +echo 5) Start Local LLM Server (llama.cpp) +echo 6) Exit +echo. +set /p choice="Choice (1-6): " + +if "!choice!"=="1" goto run_online +if "!choice!"=="2" goto run_offline +if "!choice!"=="3" goto run_dashboard +if "!choice!"=="4" goto run_diag +if "!choice!"=="5" goto run_llm +if "!choice!"=="6" exit /b +echo Invalid choice. +pause +goto menu + +:run_online +echo. +echo [*] Starting Hermes CLI (online mode - opencode-go)... +echo. +set HERMES_HOME=%~dp0..\hermes +"!PYTHON!" -m hermes_cli --config "!HERMES_CONFIG!" +echo. +pause +goto menu + +:run_offline +if not exist "!OLLAMA_DIR!\qwen2.5-coder-1.5b-q4_k_m.gguf" ( + echo. + echo [ERROR] Offline model not found at !OLLAMA_DIR! + echo Run option 5 first to start the LLM server. + echo. + pause + goto menu +) +echo. +echo [*] Starting local LLM server (llama.cpp)... +start "Hermes Local LLM" "%~dp0start-llama-server.bat" +echo [*] Waiting for LLM server to be ready (polling)... +set POLL_COUNT=0 +:wait_llm +set /a POLL_COUNT+=1 +if !POLL_COUNT! gtr 30 ( + echo [WARN] LLM server not ready after 30s - starting anyway... + goto skip_wait +) +>nul 2>&1 curl -s http://127.0.0.1:11434/v1/models && ( + echo [OK] LLM server is ready! + goto skip_wait +) +>nul 2>&1 timeout /t 1 /nobreak +goto wait_llm +:skip_wait +echo. +echo [*] Starting Hermes CLI (offline mode - local LLM)... +set HERMES_HOME=%~dp0..\hermes\offline +if not exist "!HERMES_HOME!" mkdir "!HERMES_HOME!" +"!PYTHON!" -m hermes_cli --config "!HERMES_ROOT!\config.offline.yaml" +echo. +pause +goto menu + +:run_dashboard +echo. +echo [*] Starting web dashboard at http://localhost:8080 +echo [*] On other devices, use http://rescue.local:8080 +echo. +start "Hermes Dashboard" "http://localhost:8080" +"!PYTHON!" "!WEB_SERVER!" +pause +goto menu + +:run_diag +echo. +echo [*] Running full diagnostics... +echo. +for %%s in (hardware bsod drivers stress) do ( + echo. + echo --- %%s --- + "!PYTHON!" "!HERMES_ROOT!\scripts\%%s.py" +) +echo. +pause +goto menu + +:run_llm +echo. +echo [*] Starting llama-server (local LLM backend)... +echo [*] Model: qwen2.5-coder-1.5b-q4_k_m.gguf +echo [*] Server: http://127.0.0.1:11434/v1 +echo [*] Press Ctrl+C to stop when done. +echo. +start "Hermes LLM Server" "%~dp0start-llama-server.bat" +echo [OK] Server starting in new window. +echo. +pause +goto menu diff --git a/build/output/start-llama-server.bat b/build/output/start-llama-server.bat new file mode 100644 index 0000000..51f8984 --- /dev/null +++ b/build/output/start-llama-server.bat @@ -0,0 +1,125 @@ +@echo off +:: ============================================================================== +:: Hermes Portable Rescue - llama-server launcher with GPU detection +:: ============================================================================== +:: Detects GPU brand (NVIDIA, AMD, Intel, integrated) and launches the +:: appropriate llama-server build (Vulkan for GPU, CPU-only fallback). +:: ============================================================================== +setlocal enabledelayedexpansion + +cd /d "%~dp0" + +set LLAMA_DIR=%~dp0bin\llama +set MODEL_DIR=%~dp0bin\ollama +set MODEL=%MODEL_DIR%\qwen2.5-coder-1.5b-q4_k_m.gguf +set PORT=11434 +set GPU_FOUND=0 + +echo. +echo ============================================ +echo Hermes LLM Server - GPU Detection +echo ============================================ +echo. + +:: --- Detection 1: nvidia-smi (NVIDIA) -------------------------------------- +nvidia-smi >nul 2>&1 +if !errorlevel! equ 0 ( + for /f "tokens=*" %%a in ('nvidia-smi --query-gpu=name --format=csv,noheader 2^>nul') do ( + set GPU_NAME=%%a + ) + echo [OK] NVIDIA GPU detected: !GPU_NAME! + set GPU_FOUND=1 +) + +:: Detection 2: WMIC - check for AMD/Intel GPUs +if !GPU_FOUND! equ 0 ( + wmic path win32_videocontroller get name 2>nul | findstr /i "nvidia" >nul + if !errorlevel! equ 0 ( + echo [OK] NVIDIA GPU detected via WMI + set GPU_FOUND=1 + ) else ( + wmic path win32_videocontroller get name 2>nul | findstr /i "amd radeon" >nul + if !errorlevel! equ 0 ( + echo [OK] AMD GPU detected via WMI + set GPU_FOUND=1 + ) else ( + wmic path win32_videocontroller get name 2>nul | findstr /i "intel arc" >nul + if !errorlevel! equ 0 ( + echo [OK] Intel GPU detected via WMI + set GPU_FOUND=1 + ) else ( + wmic path win32_videocontroller get name 2>nul | findstr /i "intel hd intel iris intel uhd" >nul + if !errorlevel! equ 0 ( + echo [OK] Intel integrated graphics detected via WMI + set GPU_FOUND=1 + ) + ) + ) + ) +) + +:: Detection 3: vulkaninfo (direct Vulkan probe) +if !GPU_FOUND! equ 0 ( + where vulkaninfo >nul 2>&1 + if !errorlevel! equ 0 ( + vulkaninfo --summary 2>nul | findstr /c:"Vulkan Instance" >nul + if !errorlevel! equ 0 ( + echo [OK] Vulkan-capable device detected + set GPU_FOUND=1 + ) + ) +) + +:: Check model exists +if not exist "!MODEL!" ( + echo [ERROR] Model not found: !MODEL! + echo. + echo Please download a GGUF model and place it at: + echo !MODEL! + echo. + echo Recommended: Qwen 2.5 Coder 1.5B Q4_K_M + echo https://huggingface.co/Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF + echo. + pause + exit /b 1 +) + +:: Launch +if !GPU_FOUND! equ 1 ( + echo. + echo [*] Starting llama-server with GPU acceleration (Vulkan)... + echo [*] Model: qwen2.5-coder-1.5b-q4_k_m.gguf + echo [*] Server: http://127.0.0.1:!PORT!/v1 + echo [*] Flags: --n-gpu-layers 99 --mlock --cont-batching -c 4096 + echo. + echo [*] Press Ctrl+C to stop the server. + echo. + + "!LLAMA_DIR!\llama-server.exe" ^ + --host 127.0.0.1 --port !PORT! ^ + -m "!MODEL!" ^ + --n-gpu-layers 99 ^ + --mlock ^ + --cont-batching ^ + -c 4096 +) else ( + echo. + echo [*] No compatible GPU found. Starting CPU-only mode. + echo [*] Model: qwen2.5-coder-1.5b-q4_k_m.gguf + echo [*] Server: http://127.0.0.1:!PORT!/v1 + echo [*] Flags: --mlock --cont-batching -c 4096 + echo. + echo [*] Press Ctrl+C to stop the server. + echo. + + "!LLAMA_DIR!\cpu\llama-server-cpu.exe" ^ + --host 127.0.0.1 --port !PORT! ^ + -m "!MODEL!" ^ + --mlock ^ + --cont-batching ^ + -c 4096 +) + +echo. +echo [*] Server exited with code !errorlevel! +pause diff --git a/build/quick-rebuild-iso.sh b/build/quick-rebuild-iso.sh new file mode 100755 index 0000000..fcf39ae --- /dev/null +++ b/build/quick-rebuild-iso.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Quick rebuild: extract existing ISO, update files, repack +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +BUILD_DIR="$PROJECT_DIR/build" +EXISTING_ISO="$BUILD_DIR/output/hermes-rescue.iso" +OUTPUT_DIR="$BUILD_DIR/output" +WORKDIR="/tmp/hermes-iso-quick" +SOURCE_DIR="$PROJECT_DIR/src" + +info() { echo -e "\033[0;34m[INFO]\033[0m $*"; } +ok() { echo -e "\033[0;32m[OK]\033[0m $*"; } +die() { echo -e "\033[0;31m[ERROR]\033[0m $*"; exit 1; } + +# Check prerequisites +for cmd in unsquashfs mksquashfs xorriso rsync; do + command -v "$cmd" &>/dev/null || die "$cmd not found" +done + +[[ -f "$EXISTING_ISO" ]] || die "Existing ISO not found: $EXISTING_ISO" + +rm -rf "$WORKDIR" +mkdir -p "$WORKDIR/iso-contents" "$WORKDIR/squashfs-root" + +info "Mounting existing ISO..." +mkdir -p /mnt/hermes-iso-source +mount -o loop "$EXISTING_ISO" /mnt/hermes-iso-source 2>/dev/null || die "Failed to mount ISO" + +info "Copying ISO contents (excluding squashfs)..." +rsync -a --exclude="casper/filesystem.squashfs" \ + /mnt/hermes-iso-source/ "$WORKDIR/iso-contents/" 2>/dev/null + +info "Extracting squashfs..." +cp /mnt/hermes-iso-source/casper/filesystem.squashfs "$WORKDIR/filesystem.squashfs" +umount /mnt/hermes-iso-source 2>/dev/null || true + +unsquashfs -d "$WORKDIR/squashfs-root" -f "$WORKDIR/filesystem.squashfs" 2>&1 | tail -3 +ok "Squashfs extracted ($(du -sh "$WORKDIR/squashfs-root" | cut -f1))" + +info "Updating config.yaml..." +cp "$SOURCE_DIR/hermes/config.yaml" "$WORKDIR/squashfs-root/hermes/config.yaml" +ok "config.yaml updated" + +info "Updating auth.json..." +cp "$SOURCE_DIR/hermes/auth.json" "$WORKDIR/squashfs-root/hermes/auth.json" +ok "auth.json updated" + +info "Updating autorun.sh..." +cp "$SOURCE_DIR/hermes/autorun.sh" "$WORKDIR/squashfs-root/hermes/autorun.sh" +ok "autorun.sh updated" + +info "Re-packing squashfs..." +mksquashfs "$WORKDIR/squashfs-root" "$WORKDIR/iso-contents/casper/filesystem.squashfs" \ + -comp lz4 -b 1M -noappend -processors 1 2>&1 | tail -5 +ok "Squashfs repacked ($(du -sh "$WORKDIR/iso-contents/casper/filesystem.squashfs" | cut -f1))" + +# Update filesystem.size +fs_size=$(du -sx --block-size=1 "$WORKDIR/squashfs-root" 2>/dev/null | cut -f1) +echo "$fs_size" > "$WORKDIR/iso-contents/casper/filesystem.size" +ok "filesystem.size updated" + +info "Generating ISO..." +OUTPUT_ISO="$OUTPUT_DIR/hermes-rescue.iso" +EFI_IMG=$(find "$WORKDIR/iso-contents" -name "efi.img" -o -name "eltorito.img" 2>/dev/null | head -1 | sed "s|$WORKDIR/iso-contents/||") + +if [[ -n "$EFI_IMG" ]]; then + xorriso -as mkisofs -r -V "HERMES_RESCUE" \ + -J -joliet-long \ + -isohybrid-mbr "$WORKDIR/iso-contents/isohdpfx.bin" \ + -b isolinux/isolinux.bin \ + -c isolinux/boot.cat \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + -eltorito-alt-boot -e "$EFI_IMG" -no-emul-boot \ + -isohybrid-gpt-basdat \ + -isohybrid-apm-hfsplus \ + -o "$OUTPUT_ISO" \ + "$WORKDIR/iso-contents" 2>&1 | tail -5 +else + xorriso -as mkisofs -r -V "HERMES_RESCUE" \ + -J -joliet-long \ + -isohybrid-mbr "$WORKDIR/iso-contents/isohdpfx.bin" \ + -b isolinux/isolinux.bin \ + -c isolinux/boot.cat \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + -o "$OUTPUT_ISO" \ + "$WORKDIR/iso-contents" 2>&1 | tail -5 +fi + +iso_size=$(du -h "$OUTPUT_ISO" | cut -f1) +ok "ISO generated: $OUTPUT_ISO ($iso_size)" + +info "Cleaning up..." +rm -rf "$WORKDIR" +ok "Done!" diff --git a/src/diagnostics/__init__.py b/src/diagnostics/__init__.py index d34dff9..3424674 100644 --- a/src/diagnostics/__init__.py +++ b/src/diagnostics/__init__.py @@ -5,6 +5,9 @@ Modules ------- hardware : HardwareInventory CPU, RAM, GPU, disk enumeration and SMART health checks. +drivers : DriverUpdater + Hardware-aware driver scanner and updater — scans PCI/USB devices + and cross-references against mounted Windows DriverStore. stress : RAMStressTester, CPUStressTester, GPUStressTester Stress-test wrappers (available when stress.py is present). @@ -12,6 +15,7 @@ Each submodule exposes a ``run() -> dict`` for JSON and a ``text_report() -> str`` for human consumption. """ from src.diagnostics.hardware import HardwareInventory +from src.diagnostics.drivers import DriverUpdater try: from src.diagnostics.stress import ( @@ -23,6 +27,15 @@ try: except ImportError: pass +from src.diagnostics.bsod import BSODTester, analyze_minidump + __all__ = [ + "BSODTester", + "analyze_minidump", "HardwareInventory", + "DriverUpdater", + "RAMStressTester", + "CPUStressTester", + "GPUStressTester", + "run_all_stress_tests", ] diff --git a/src/diagnostics/__main__.py b/src/diagnostics/__main__.py new file mode 100644 index 0000000..67d489d --- /dev/null +++ b/src/diagnostics/__main__.py @@ -0,0 +1,9 @@ +""" +Entry point for `python -m src.diagnostics`. + +Delegates to stress.py's CLI by default; can be extended for other diagnostics. +""" +from src.diagnostics.stress import main + +if __name__ == "__main__": + main() diff --git a/src/diagnostics/bsod.py b/src/diagnostics/bsod.py new file mode 100644 index 0000000..f8a8c3c --- /dev/null +++ b/src/diagnostics/bsod.py @@ -0,0 +1,1660 @@ +""" +Hermes Portable Rescue — BSOD Analyzer. + +Parses Windows minidump files (.dmp), extracts stop codes and parameters, +feeds to LLM for interpretation, correlates with known fixes. + +Usage (CLI): + python -m src.diagnostics.bsod path/to/minidump.dmp [--llm] + +Usage (programmatic): + from src.diagnostics.bsod import BSODTester + tester = BSODTester() + result = tester.run(minidump_path="path/to/minidump.dmp") + print(tester.report()) +""" + +from __future__ import annotations + +import enum +import json +import logging +import os +import struct +import sys +from dataclasses import dataclass, field, asdict +from datetime import datetime +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple + +logger = logging.getLogger(__name__) + + +# --------------------------------------------------------------------------- +# Bug Check Codes — Windows stop codes mapped to human-readable names +# --------------------------------------------------------------------------- + +# fmt: off +BUGCHECK_CODES: Dict[int, str] = { + 0x00000001: "APC_INDEX_MISMATCH", + 0x00000002: "DEVICE_QUEUE_NOT_BUSY", + 0x00000003: "INVALID_AFFINITY_SET", + 0x00000004: "INVALID_DATA_ACCESS_TRAP", + 0x00000005: "INVALID_PROCESS_ATTACH_ATTEMPT", + 0x00000006: "INVALID_PROCESS_DETACH_ATTEMPT", + 0x00000007: "INVALID_SOFTWARE_INTERRUPT", + 0x00000008: "IRQL_NOT_DISPATCH_LEVEL", + 0x00000009: "IRQL_NOT_GREATER_OR_EQUAL", + 0x0000000A: "IRQL_NOT_LESS_OR_EQUAL", + 0x0000000B: "NO_EXCEPTION_HANDLING_SUPPORT", + 0x0000000C: "MAXIMUM_WAIT_OBJECTS_EXCEEDED", + 0x0000000D: "MUTEX_LEVEL_NUMBER_VIOLATION", + 0x0000000E: "NO_USER_MODE_CONTEXT", + 0x0000000F: "SPIN_LOCK_ALREADY_OWNED", + 0x00000010: "SPIN_LOCK_NOT_OWNED", + 0x00000011: "THREAD_ALREADY_IN_TASK", + 0x00000012: "THREAD_NOT_IN_TASK", + 0x00000013: "INVALID_NEXT_THREAD", + 0x00000014: "CREATE_DELETE_LOCK_NOT_LOCKED", + 0x00000015: "ALREADY_WIN32", + 0x00000016: "HAL1_INITIALIZATION_FAILED", + 0x00000017: "CANT_APPEAR_ONCE", + 0x00000018: "INVALID_CMS_PRIORITY", + 0x00000019: "BAD_POOL_HEADER", + 0x0000001A: "MEMORY_MANAGEMENT", + 0x0000001B: "PFN_SHARE_COUNT", + 0x0000001C: "PFN_REFERENCE_COUNT", + 0x0000001D: "NO_SPIN_LOCK_AVAILABLE", + 0x0000001E: "KMODE_EXCEPTION_NOT_HANDLED", + 0x0000001F: "SHARED_RESOURCE_CONV_ERROR", + 0x00000020: "KERNEL_APC_PENDING_DURING_EXIT", + 0x00000021: "QUOTA_UNDERFLOW", + 0x00000022: "FILE_SYSTEM", + 0x00000023: "FAT_FILE_SYSTEM", + 0x00000024: "NTFS_FILE_SYSTEM", + 0x00000025: "NPFS_FILE_SYSTEM", + 0x00000026: "CDFS_FILE_SYSTEM", + 0x00000027: "RDR_FILE_SYSTEM", + 0x00000028: "CORRUPT_ACCESS_TOKEN", + 0x00000029: "SECURITY_SYSTEM", + 0x0000002A: "INCONSISTENT_IRP", + 0x0000002B: "PANIC_STACK_SWITCH", + 0x0000002C: "PORT_DRIVER_INTERNAL", + 0x0000002D: "SCSI_DISK_DRIVER_INTERNAL", + 0x0000002E: "DATA_BUS_ERROR", + 0x0000002F: "INSTRUCTION_BUS_ERROR", + 0x00000030: "SET_OF_INVALID_CONTEXT", + 0x00000031: "PHASE0_INITIALIZATION_FAILED", + 0x00000032: "PHASE1_INITIALIZATION_FAILED", + 0x00000033: "UNEXPECTED_INITIALIZATION_CALL", + 0x00000034: "CACHE_MANAGER", + 0x00000035: "NO_MORE_IRP_STACK_LOCATIONS", + 0x00000036: "DEVICE_REFERENCE_COUNT_NOT_ZERO", + 0x00000037: "FLOPPY_INTERNAL_ERROR", + 0x00000038: "SERIAL_DRIVER_INTERNAL", + 0x00000039: "SYSTEM_EXIT_OWNED_MUTEX", + 0x0000003A: "SYSTEM_UNWIND_PREVIOUS_USER", + 0x0000003B: "SYSTEM_SERVICE_EXCEPTION", + 0x0000003C: "INTERRUPT_UNWIND_ATTEMPTED", + 0x0000003D: "INTERRUPT_EXCEPTION_NOT_HANDLED", + 0x0000003E: "MULTIPROCESSOR_CONFIGURATION_NOT_SUPPORTED", + 0x0000003F: "NO_MORE_SYSTEM_PTES", + 0x00000040: "TARGET_MDL_TOO_SMALL", + 0x00000041: "MUST_SUCCEED_POOL_EMPTY", + 0x00000042: "ATDISK_DRIVER_INTERNAL", + 0x00000043: "NO_SUCH_PARTITION", + 0x00000044: "MULTIPLE_IRP_COMPLETE_REQUESTS", + 0x00000045: "INSUFFICIENT_SYSTEM_MAP_REGS", + 0x00000046: "DEREF_UNKNOWN_LOGON_SESSION", + 0x00000047: "REF_UNKNOWN_LOGON_SESSION", + 0x00000048: "CANCEL_STATE_IN_COMPLETED_IRP", + 0x00000049: "PAGE_FAULT_WITH_INTERRUPTS_OFF", + 0x0000004A: "IRQL_GT_ZERO_AT_SYSTEM_SERVICE", + 0x0000004B: "STREAMS_INTERNAL_ERROR", + 0x0000004C: "FATAL_UNHANDLED_HARD_ERROR", + 0x0000004D: "NO_PAGES_AVAILABLE", + 0x0000004E: "PFN_LIST_CORRUPT", + 0x0000004F: "NDIS_INTERNAL_ERROR", + 0x00000050: "PAGE_FAULT_IN_NONPAGED_AREA", + 0x00000051: "REGISTRY_ERROR", + 0x00000052: "MAILSLOT_FILE_SYSTEM", + 0x00000053: "NO_BOOT_DEVICE", + 0x00000054: "LM_SERVER_INTERNAL_ERROR", + 0x00000055: "DATA_COHERENCY_EXCEPTION", + 0x00000056: "INSTRUCTION_COHERENCY_EXCEPTION", + 0x00000057: "XNS_INTERNAL_ERROR", + 0x00000058: "FTDISK_INTERNAL_ERROR", + 0x00000059: "PINBALL_FILE_SYSTEM", + 0x0000005A: "CRITICAL_SERVICE_FAILED", + 0x0000005B: "SET_ENV_VAR_FAILED", + 0x0000005C: "HAL_INITIALIZATION_FAILED", + 0x0000005D: "UNSUPPORTED_PROCESSOR", + 0x0000005E: "OBJECT_INITIALIZATION_FAILED", + 0x0000005F: "SECURITY_INITIALIZATION_FAILED", + 0x00000060: "PROCESS_INITIALIZATION_FAILED", + 0x00000061: "HAL1_INITIALIZATION_FAILED_2", + 0x00000062: "OBJECT_1_INITIALIZATION_FAILED", + 0x00000063: "SECURITY_1_INITIALIZATION_FAILED", + 0x00000064: "SYMBOLIC_INITIALIZATION_FAILED", + 0x00000065: "MEMORY_1_INITIALIZATION_FAILED", + 0x00000066: "CACHE_INITIALIZATION_FAILED", + 0x00000067: "CONFIG_INITIALIZATION_FAILED", + 0x00000068: "FILE_INITIALIZATION_FAILED", + 0x00000069: "IO1_INITIALIZATION_FAILED", + 0x0000006A: "LPC_INITIALIZATION_FAILED", + 0x0000006B: "PROCESS_1_INITIALIZATION_FAILED", + 0x0000006C: "REFMON_INITIALIZATION_FAILED", + 0x0000006D: "SESSION1_INITIALIZATION_FAILED", + 0x0000006E: "SESSION2_INITIALIZATION_FAILED", + 0x0000006F: "SESSION3_INITIALIZATION_FAILED", + 0x00000070: "SESSION4_INITIALIZATION_FAILED", + 0x00000071: "SESSION5_INITIALIZATION_FAILED", + 0x00000072: "ASSIGN_DRIVE_LETTERS_FAILED", + 0x00000073: "CONFIG_LIST_FAILED", + 0x00000074: "BAD_SYSTEM_CONFIG_INFO", + 0x00000075: "CANNOT_WRITE_CONFIGURATION", + 0x00000076: "PROCESS_HAS_LOCKED_PAGES", + 0x00000077: "KERNEL_STACK_INPAGE_ERROR", + 0x00000078: "PHASE0_EXCEPTION", + 0x00000079: "MISMATCHED_HAL", + 0x0000007A: "KERNEL_DATA_INPAGE_ERROR", + 0x0000007B: "INACCESSIBLE_BOOT_DEVICE", + 0x0000007C: "BUGCODE_NDIS_DRIVER", + 0x0000007D: "INSTALL_MORE_MEMORY", + 0x0000007E: "SYSTEM_THREAD_EXCEPTION_NOT_HANDLED", + 0x0000007F: "UNEXPECTED_KERNEL_MODE_TRAP", + 0x00000080: "NMI_HARDWARE_FAILURE", + 0x00000081: "SPIN_LOCK_INIT_FAILURE", + 0x00000082: "DFS_FILE_SYSTEM", + 0x00000085: "SETUP_FAILURE", + 0x0000008B: "MBR_CHECKSUM_MISMATCH", + 0x0000008E: "KERNEL_MODE_EXCEPTION_NOT_HANDLED", + 0x0000008F: "PP0_INITIALIZATION_FAILED", + 0x00000090: "PP1_INITIALIZATION_FAILED", + 0x00000092: "UP_DRIVER_ON_MP_SYSTEM", + 0x00000093: "INVALID_KERNEL_HANDLE", + 0x00000094: "KERNEL_STACK_LOCKED_AT_EXIT", + 0x00000096: "INVALID_WORK_QUEUE_ITEM", + 0x00000097: "BOUND_IMAGE_UNSUPPORTED", + 0x00000098: "END_OF_NT_EVALUATION_PERIOD", + 0x00000099: "INVALID_REGISTRY_SIGNATURE", + 0x0000009A: "INVALID_HIBER_FILE_SIZE", + 0x0000009B: "UDFS_FILE_SYSTEM", + 0x0000009C: "MACHINE_CHECK_EXCEPTION", + 0x0000009E: "USER_MODE_HEALTH_MONITOR", + 0x0000009F: "DRIVER_POWER_STATE_FAILURE", + 0x000000A0: "INTERNAL_POWER_ERROR", + 0x000000A1: "PCI_BUS_DRIVER_INTERNAL", + 0x000000A2: "MEMORY_IMAGE_CORRUPT", + 0x000000A3: "ACPI_DRIVER_INTERNAL", + 0x000000A4: "CNSS_FILE_SYSTEM_FILTER", + 0x000000A5: "ACPI_BIOS_ERROR", + 0x000000A6: "BAD_HANDLE", + 0x000000A7: "BAD_EXHANDLE", + 0x000000A8: "IRQL_NOT_EQUAL_AT_PASSIVE", + 0x000000A9: "FS_FILTER_INITIALIZATION_FAILED", + 0x000000AA: "FS_FILTER_TERMINATION_FAILED", + 0x000000AB: "SESSION_HAS_VALID_POOL_ON_EXIT", + 0x000000AC: "HAL_MEMORY_ALLOCATION", + 0x000000AD: "VIDEO_DRIVER_INIT_FAILURE", + 0x000000AE: "SESSION_HAS_VALID_VIEWS_ON_EXIT", + 0x000000AF: "NETWORK_BOOT_INITIALIZATION_FAILED", + 0x000000B0: "NETWORK_BOOT_DUPLICATE_ADDRESS", + 0x000000B1: "INVALID_HIBER_FILE", + 0x000000B2: "KERNEL_APC_PENDING_DURING_EXIT_2", + 0x000000B3: "ILLEGAL_DLL_RELOCATION", + 0x000000B4: "VIDEO_DRIVER_INIT_FAILURE_2", + 0x000000B5: "VIDEO_DRIVER_INIT_FAILURE_3", + 0x000000B6: "VIDEO_DRIVER_INIT_FAILURE_4", + 0x000000B7: "VIDEO_DRIVER_INIT_FAILURE_5", + 0x000000B8: "VIDEO_DRIVER_INIT_FAILURE_6", + 0x000000B9: "VIDEO_DRIVER_INIT_FAILURE_7", + 0x000000BA: "VIDEO_DRIVER_INIT_FAILURE_8", + 0x000000BB: "VIDEO_DRIVER_INIT_FAILURE_9", + 0x000000BC: "VIDEO_DRIVER_INIT_FAILURE_10", + 0x000000BD: "VIDEO_DRIVER_INIT_FAILURE_11", + 0x000000BE: "ATTEMPTED_WRITE_TO_READONLY_MEMORY", + 0x000000BF: "MUTEX_ALREADY_OWNED", + 0x000000C0: "KERNEL_STACK_TOO_SMALL", + 0x000000C1: "SPECIAL_POOL_DETECTED_MEMORY_CORRUPTION", + 0x000000C2: "BAD_POOL_CALLER", + 0x000000C3: "BUGCODE_POWER_DOWN_FAILED", + 0x000000C4: "DRIVER_VERIFIER_DETECTED_VIOLATION", + 0x000000C5: "DRIVER_CORRUPTED_EXPOOL", + 0x000000C6: "DRIVER_CAUGED_MODIFYING_FREED_POOL", + 0x000000C7: "TIMER_OR_DPC_INVALID", + 0x000000C8: "IRQL_UNEXPECTED_VALUE", + 0x000000C9: "DRIVER_VERIFIER_IOMANAGER_VIOLATION", + 0x000000CA: "PNP_DETECTED_FATAL_ERROR", + 0x000000CB: "DRIVER_LEFT_LOCKED_PAGES_IN_PROCESS", + 0x000000CC: "PAGE_FAULT_IN_FREED_SPECIAL_POOL", + 0x000000CD: "PAGE_FAULT_BEYOND_END_OF_ALLOCATION", + 0x000000CE: "DRIVER_UNLOADED_WITHOUT_CANCELLING_PENDING_OPERATIONS", + 0x000000CF: "TERMINAL_SERVER_DRIVER_MADE_INCORRECT_MEMORY_REFERENCE", + 0x000000D0: "DRIVER_CORRUPTED_MMPOOL", + 0x000000D1: "DRIVER_IRQL_NOT_LESS_OR_EQUAL", + 0x000000D2: "BUGCODE_ID_DRIVER", + 0x000000D3: "DRIVER_PORTION_MUST_BE_NONPAGED", + 0x000000D4: "SYSTEM_SCAN_AT_RAISED_IRQL_CAUGHT_IMPROPER_DRIVER_UNLOAD", + 0x000000D5: "DRIVER_PAGE_FAULT_IN_FREED_SPECIAL_POOL", + 0x000000D6: "DRIVER_PAGE_FAULT_BEYOND_END_OF_ALLOCATION", + 0x000000D7: "DRIVER_UNMAPPING_INVALID_VIEW", + 0x000000D8: "DRIVER_USED_EXCESSIVE_PTES", + 0x000000D9: "LOCKED_PAGES_TRACKER_CORRUPTION", + 0x000000DA: "SYSTEM_PTE_MISUSE", + 0x000000DB: "DRIVER_CORRUPTED_SYSPTES", + 0x000000DC: "DRIVER_INVALID_STACK_ACCESS", + 0x000000DD: "DRIVER_INVALID_STACK_ACCESS_2", + 0x000000DE: "POOL_CORRUPTION_IN_FILE_AREA", + 0x000000DF: "IMPERSONATING_WORKER_THREAD", + 0x000000E0: "ACPI_BIOS_FATAL_ERROR", + 0x000000E1: "WORKER_THREAD_RETURNED_AT_BAD_IRQL", + 0x000000E2: "MANUALLY_INITIATED_CRASH", + 0x000000E3: "RESOURCE_NOT_OWNED", + 0x000000E4: "WORKER_INVALID", + 0x000000E5: "DRIVER_VERIFIER_DMA_VIOLATION", + 0x000000E6: "DRIVER_VERIFIER_KDBG_VIOLATION", + 0x000000E7: "INVALID_FLOATING_POINT_STATE", + 0x000000E8: "INVALID_CANCEL_OF_FILE_OPEN", + 0x000000E9: "ACTIVE_EX_WORKER_THREAD_TERMINATION", + 0x000000EA: "THREAD_STUCK_IN_DEVICE_DRIVER", + 0x000000EB: "DIRTY_MAPPED_PAGES_CONGESTION", + 0x000000EC: "SESSION_HAS_VALID_SPECIAL_POOL_ON_EXIT", + 0x000000ED: "UNMOUNTABLE_BOOT_VOLUME", + 0x000000EF: "CRITICAL_PROCESS_DIED", + 0x000000F0: "STORAGE_MINIPORT_ERROR", + 0x000000F1: "SCSI_VERIFIER_DETECTED_VIOLATION", + 0x000000F2: "HARDWARE_INTERRUPT_STORM", + 0x000000F3: "DISORDERLY_SHUTDOWN", + 0x000000F4: "CRITICAL_OBJECT_TERMINATION", + 0x000000F5: "FLTMGR_FILE_SYSTEM", + 0x000000F6: "PCI_VERIFIER_DETECTED_VIOLATION", + 0x000000F7: "DRIVER_OVERRAN_STACK_BUFFER", + 0x000000F8: "RAMOS_DISK_BOOT_FAILURE", + 0x000000F9: "DRIVER_RETURNED_STATUS_REPARSE_FOR_VOLUME_OPEN", + 0x000000FA: "HTTP_DRIVER_CORRUPTED", + 0x000000FC: "ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY", + 0x000000FD: "DIRTY_NOWRITE_PAGES_CONGESTION", + 0x000000FE: "BUGCODE_USB_DRIVER", + 0x000000FF: "RESERVE_QUEUE_OVERFLOW", + 0x00000100: "LOADER_BLOCK_MISMATCH", + 0x00000101: "CLOCK_WATCHDOG_TIMEOUT", + 0x00000102: "DFS_FILE_SYSTEM_2", # noqa: F811 (name collision) + 0x00000103: "MUP_FILE_SYSTEM", + 0x00000104: "AGP_INVALID_ACCESS", + 0x00000105: "AGP_GART_CORRUPTION", + 0x00000106: "AGP_ILLEGALLY_REPROGRAMMED", + 0x00000107: "KERNEL_THREAD_PRIORITY_FLOOR_VIOLATION", + 0x00000108: "THIRD_PARTY_FILE_SYSTEM_FAILURE", + 0x00000109: "CRITICAL_STRUCTURE_CORRUPTION", + 0x0000010A: "ECC_ENTRY_CORRUPT", + 0x0000010B: "MFT_CORRUPT", + 0x0000010C: "EXFAT_FILE_SYSTEM", + 0x0000010D: "WINDOWS_NT_BANNER_TOO_LATE", + 0x0000010E: "VIDEO_MEMORY_MANAGEMENT_INTERNAL", + 0x0000010F: "RESOURCE_MANAGER_EXCEPTION_NOT_HANDLED", + 0x00000110: "DRIVER_VERIFIER_PRIORITY_BAND_VIOLATION", + 0x00000111: "RECURSIVE_NMI", + 0x00000112: "MSR_PC_SAVE_CONFLICT", + 0x00000113: "D3DPOOL_DELETE_CONFLICT", + 0x00000114: "INVALID_MEMORY_BLOCK_ALLOCATION", + 0x00000115: "SMBFS_FILE_SYSTEM", + 0x00000116: "VIDEO_TDR_FAILURE", + 0x00000117: "VIDEO_TDR_TIMEOUT_DETECTED", + 0x00000119: "VIDEO_SCHEDULER_INTERNAL_ERROR", + 0x0000011A: "EM_INITIALIZATION_FAILURE", + 0x0000011B: "DRIVER_RETURNED_HOLDING_CANCEL_LOCK", + 0x0000011C: "ATTEMPTED_WRITE_TO_CM_PROTECTED_SPACE", + 0x0000011D: "EVENT_TRACING_FATAL_ERROR", + 0x0000011E: "TOO_MANY_RECURSIVE_FAULTS", + 0x0000011F: "HAL_ILLEGAL_IOMMU_PAGE_FAULT", + 0x00000120: "WIN32K_CRITICAL_FAILURE", + 0x00000121: "DRIVER_VIOLATION", + 0x00000122: "WIN32K_AT_IRQL", + 0x00000123: "WINSOCK_CRITICAL_FAILURE", + 0x00000124: "WHEA_UNCORRECTABLE_ERROR", + 0x00000125: "NMR_INITIALIZATION_FAILED", + 0x00000126: "WHEA_ERROR_RECOVERY_FAILED", + 0x00000127: "PAGE_NOT_ZERO", + 0x00000128: "WORKER_THREAD_RETURNED_WITH_BAD_IO_PRIORITY_PAGE", + 0x00000129: "WORKER_THREAD_RETURNED_WITH_BAD_PAGING_IO_PRIORITY", + 0x0000012A: "WORKER_THREAD_RETURNED_WITH_BAD_WORK_PRIORITY", + 0x0000012B: "FAST_ERESOURCE_PRECONDITION_VIOLATION", + 0x0000012C: "EXRESOURCE_TIMEOUT_LIVEDUMP", + 0x0000012D: "IO_ERESOURCE_TIMEOUT_LIVEDUMP", + 0x0000012E: "INTERRUPT_STEERING_INTERNAL_ERROR", + 0x0000012F: "IO_ERESOURCE_LIVEDUMP_TABLES", + 0x00000130: "DYNAMIC_ADD_PROCESSOR_MACHINE_CHECK", + 0x00000131: "WHEA_INTERNAL_ERROR", + 0x00000132: "USB_DRIVER_INTERNAL_ERROR", + 0x00000133: "DPC_WATCHDOG_VIOLATION", + 0x00000134: "ISR_INTERNAL_ERROR", + 0x00000135: "WIN32K_EVENT_FAILURE", + 0x00000136: "WIN32K_POWER_STATE_FAILURE", + 0x00000137: "CRITICAL_PROCESS_DIED_2", + 0x00000138: "PROCESS0_IMAGE_INIT_FAILURE", + 0x00000139: "WIN32K_PNP_FAILURE", + 0x0000013A: "FIRMWARE_IMAGE_BOOT_FAILURE", + 0x0000013B: "SOURCE_USER_DUMP", + 0x0000013C: "SYSTEM_SERVICE_EXCEPTION_2", + 0x0000013D: "KERNEL_STACK_LOCKED_AT_EXIT_2", + 0x0000013E: "PROCESS1_INITIALIZATION_FAILED", + 0x0000013F: "KERNEL_AUTO_BOOST_INVALID_LOCK_RELEASE", + 0x00000140: "KERNEL_AUTO_BOOST_LOCK_ACQUIRED_WITH_RAISED_IRQL", + 0x00000141: "VIDEO_ENGINE_TIMEOUT_DETECTED", + 0x00000142: "VIDEO_DWMINIT_TIMEOUT_FALLBACK_DS", + 0x00000143: "VIDEO_DXGKRNL_FATAL_ERROR", + 0x00000144: "BUGCODE_USB3_DRIVER", + 0x00000145: "SECURE_BOOT_VIOLATION", + 0x00000146: "SECURE_BOOT_POLICY_VIOLATION", + 0x00000147: "SECURE_BOOT_PLATFORM_ID_VIOLATION", + 0x00000148: "SECURE_BOOT_ROT_VIOLATION", + 0x00000149: "SECURE_BOOT_UPDATE_POLICY_VIOLATION", + 0x0000014A: "WDF_VIOLATION", + 0x0000014B: "SECURE_BOOT_RTE_VIOLATION", + 0x0000014C: "EXRESOURCE_TIMEOUT_LIVEDUMP_2", + 0x0000014D: "INTERRUPT_STORM_ACTIVE", + 0x0000014E: "IO_ERESOURCE_TIMEOUT_LIVEDUMP_2", + 0x0000014F: "RTPM_DRIVER_INTERNAL_ERROR", + 0x00000150: "REFS_FILE_SYSTEM", + 0x00000151: "BOOTVID_DRIVER_INTERNAL", + 0x00000152: "TPM_DRIVER_INTERNAL_ERROR", + 0x00000153: "KERNEL_SECURITY_CHECK_FAILURE", + 0x00000154: "TUNNEL_DRIVER_DETACHED", + 0x00000155: "MCONSOLE_WATCHDOG", + 0x00000156: "WER_USER_DUMP", + 0x00000157: "STORE_DEMOTION_NOTIFICATION_FAILURE", + 0x00000158: "INTERRUPT_EXCEPTION_NOT_HANDLED_2", + 0x00000159: "HAL_IOMMU_INTERNAL_ERROR", + 0x0000015A: "HAL_IOMMU_SELF_TEST_FAILURE", + 0x0000015B: "BTHPORT_SYSTEM_CRASH", + 0x0000015C: "HYPERVISOR_ERROR", + 0x0000015D: "SECURE_ALLOCATION_ERROR", + 0x0000015E: "CRITICAL_PROCESS_DIED_3", + 0x0000015F: "DYNAMIC_ADD_PROCESSOR_LOAD_BALANCING_FAILED", + 0x00000160: "WIN32K_INCONSISTENCY", + 0x00000161: "LIVEDUMP_RESERVE_OVERFLOW", + 0x00000162: "LOADER_ROLLBACK_DETECTED", + 0x00000163: "LOADER_BLOCK_MISMATCH_2", + 0x00000164: "WIN32K_INTERRUPT", + 0x00000165: "HYPERGUARD", + 0x00000166: "CLUSTER_CSV_STATUS_IO_TIMEOUT_LIVEDUMP", + 0x00000167: "CLUSTER_RESOURCE_CALL_TIMEOUT_LIVEDUMP", + 0x00000168: "CLUSTER_CSV_SNAPSHOT_DEVICE_NOTIFICATION_TIMEOUT_LIVEDUMP", + 0x00000169: "CLUSTER_CSV_STATE_TRANSITION_INTERVAL_TIMEOUT_LIVEDUMP", + 0x0000016A: "CLUSTER_CSV_VOLUME_ARRIVAL_LIVEDUMP", + 0x0000016B: "CLUSTER_CSV_VOLUME_REMOVAL_LIVEDUMP", + 0x0000016C: "CLUSTER_CSV_CLUSTER_WATCHDOG_LIVEDUMP", + 0x0000016D: "VIDEO_DXGKRNL_LIVEDUMP", + 0x0000016E: "CLUSTER_CSV_FS_OPERATION_TIMEOUT_LIVEDUMP", + 0x0000016F: "CLUSTER_CSV_STATE_TRANSITION_FAILURE_LIVEDUMP", + 0x00000170: "CLUSTER_DEGRADED_DAS_MODE_AD_TIMEOUT_LIVEDUMP", + 0x00000171: "CRYPTO_LIBRARY_INTERNAL_ERROR", + 0x00000172: "CLUSTER_CSV_STATE_TRANSITION_INTERVAL_FAILURE_LIVEDUMP", + 0x00000173: "COREMSG_INTERNAL_ERROR", + 0x00000174: "CONTAINER_STATE_INVALID", + 0x00000175: "EFI_PARTITION_TOO_SMALL", + 0x00000176: "CONTAINER_WATCHDOG", + 0x00000177: "CLUSTER_SPACES_DRIVER_INTERNAL_ERROR", + 0x00000178: "CONTAINER_TERMINATED", + 0x00000179: "CLUSTER_SPACES_DRIVER_OPLOCK_HANDLE_LIVEDUMP", + 0x0000017A: "COREMSG_INTERNAL_ERROR_LIVEDUMP", + 0x0000017B: "CONTAINER_STATE_INVALID_LIVEDUMP", + 0x0000017C: "HYPERVISOR_ERROR_BOOT", + 0x0000017D: "CONTAINER_TERMINATED_LIVEDUMP", + 0x0000017E: "MICROCODE_REVISION_MISMATCH", + 0x0000017F: "DPC_WATCHDOG_NO_PAGED_MEMORY", + 0x00000180: "INTERRUPT_STORM_ACTIVE_LIVEDUMP", + 0x00000181: "DPC_WATCHDOG_CRITICAL", + 0x00000182: "CLUSTER_PNP_NOTIFICATION_FAILURE_LIVEDUMP", + 0x00000183: "VIDEO_DWMINIT_TIMEOUT_FALLBACK_DS_LIVEDUMP", + 0x00000184: "CLUSTER_AUTHORITATIVE_GRANT_NOTIFICATION_TIMEOUT_LIVEDUMP", + 0x00000185: "RTPM_INTERNAL_ERROR", + 0x00000186: "RTPM_DRIVER_INTERNAL_ERROR_LIVEDUMP", + 0x00000187: "CONSOLE_RTC_FAILURE", + 0x00000188: "CLUSTER_CSV_STATE_TRANSITION_INTERVAL_CRITICAL_LIVEDUMP", + 0x00000189: "BAD_SYSTEM_CONFIG_INFO_2", + 0x0000018A: "LOST_MEMBERSHIP_CLOCK_LEADER", + 0x0000018B: "INVALID_PPE_MODE_TOO_MANY_CPUS", + 0x0000018C: "HYPERVISOR_ERROR_TOO_MANY_VPS", + 0x0000018D: "CLUSTER_CSV_STATE_TRANSITION_INTERVAL_CRITICAL_FAILURE_LIVEDUMP", + 0x0000018E: "POP_POLICY_DEVICE_BLOCKED", + 0x0000018F: "SCSI_VERIFIER_IO_VIOLATION", + 0x00000190: "WIN32K_CRITICAL_FAILURE_LIVEDUMP", + 0x00000191: "KERNEL_AUTO_BOOST_LOCK_ACQUIRED_WITH_RAISED_IRQL_LIVEDUMP", + 0x00000192: "KERNEL_AUTO_BOOST_INVALID_LOCK_RELEASE_LIVEDUMP", + 0x00000193: "KERNEL_SECURITY_CHECK_FAILURE_LIVEDUMP", + 0x00000194: "DISK_FAILURE_DETECTED", + 0x00000195: "BOOT_OPTION_CHANGE_FAILED", + 0x00000196: "LOADER_BLOCK_MISMATCH_3", + 0x00000197: "LOGSERVICE_CREATE_PRESERVED_FILE_FAILED", + 0x00000198: "HYPERGUARD_LIVEDUMP", + 0x00000199: "KERNEL_SECURITY_CHECK_FAILURE_2", + 0x0000019A: "BLUETOOTH_ERROR_RECOVERY_LIVEDUMP", + 0x0000019B: "KERNEL_AUTO_BOOST_INVALID_LOCK_RELEASE_2", + 0x0000019C: "CLUSTER_RESOURCE_CALL_TIMEOUT_CRITICAL_LIVEDUMP", + 0x0000019D: "CLUSTER_CSV_AUTO_DISK_DISCONNECT_LIVEDUMP", + 0x0000019E: "CLUSTER_CSV_STATE_TRANSITION_LIVEDUMP", + 0x0000019F: "CLUSTER_CSV_FS_OPERATION_CRITICAL_LIVEDUMP", + 0x000001A0: "CLUSTER_CSV_STATE_TRANSITION_INTERVAL_CRITICAL_AND_FAILURE_LIVEDUMP", + 0x000001A1: "CLUSTER_CSV_CLUSTER_NODE_OPERATION_LIVEDUMP", + 0x000001A2: "CLUSTER_CSV_RESOURCE_CALL_TIMEOUT_LIVEDUMP", + 0x000001A3: "CLUSTER_CSV_STATE_TRANSITION_LIVEDUMP_2", + 0x000001A4: "CLUSTER_CSV_CLUSTER_RESOURCE_CALL_TIMEOUT_LIVEDUMP", + 0x000001A5: "CLUSTER_CSV_CLUSTER_OPERATION_LIVEDUMP", + 0x000001A6: "CLUSTER_CSV_CLUSTER_OPERATION_CRITICAL_LIVEDUMP", + 0x000001A7: "CLUSTER_CSV_CLUSTER_PERFORMANCE_COUNTERS_LIVEDUMP", + 0x000001A8: "CLUSTER_CSV_CLUSTER_OPERATION_TIMEOUT_LIVEDUMP", + 0x000001A9: "CLUSTER_CSV_CLUSTER_SVFS_OPERATION_TIMEOUT_LIVEDUMP", + 0x000001AA: "CLUSTER_CSV_CLUSTER_SVFS_OPERATION_CRITICAL_LIVEDUMP", + 0x000001AB: "KERNEL_SECURITY_CHECK_FAILURE_3", + 0x000001AC: "KERNEL_AUTO_BOOST_LOCK_ACQUIRED_WITH_RAISED_IRQL_2", + 0x000001AD: "HID_POWER_DEVICE_BLOCKED", + 0x000001AE: "KERNEL_AUTO_BOOST_LOCK_ACQUIRED_WITH_RAISED_IRQL_3", + 0x000001AF: "KERNEL_AUTO_BOOST_INVALID_LOCK_RELEASE_3", + 0x000001B0: "KERNEL_SECURITY_CHECK_FAILURE_4", + 0x000001B1: "KERNEL_AUTO_BOOST_LOCK_ACQUIRED_WITH_RAISED_IRQL_4", + 0x000001B2: "KERNEL_AUTO_BOOST_INVALID_LOCK_RELEASE_4", + 0x000001B3: "KERNEL_SECURITY_CHECK_FAILURE_5", + 0x000001B4: "KERNEL_AUTO_BOOST_LOCK_ACQUIRED_WITH_RAISED_IRQL_5", + 0x000001B5: "KERNEL_AUTO_BOOST_INVALID_LOCK_RELEASE_5", + 0x000001B6: "KERNEL_SECURITY_CHECK_FAILURE_6", + 0x000001B7: "KERNEL_AUTO_BOOST_LOCK_ACQUIRED_WITH_RAISED_IRQL_6", + 0x000001B8: "KERNEL_AUTO_BOOST_INVALID_LOCK_RELEASE_6", + 0x000001B9: "KERNEL_SECURITY_CHECK_FAILURE_7", + 0x000001BA: "SESSION_POOL_HEADER_EMPTY", + 0x000001BB: "SESSION_POOL_HEADER_INVALID", + 0x000001BC: "SESSION_POOL_HEADER_CORRUPT", + 0x000001BD: "SESSION_POOL_ENTRY_INVALID", + 0x000001BE: "SESSION_POOL_ENTRY_CORRUPT", + 0x000001BF: "SESSION_POOL_ENTRY_FREE", + 0x000001C0: "SESSION_POOL_ENTRY_ALREADY_FREED", + 0x000001C1: "SESSION_POOL_ENTRY_ALIGNMENT", + 0x000001C2: "SESSION_POOL_ENTRY_TAG_MISMATCH", + 0x000001C3: "SESSION_POOL_ENTRY_TAG_CORRUPT", + 0x000001C4: "DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP", + 0x000001C5: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP", + 0x000001C6: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION", + 0x000001C7: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_2", + 0x000001C8: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_3", + 0x000001C9: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_4", + 0x000001CA: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_5", + 0x000001CB: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_6", + 0x000001CC: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_7", + 0x000001CD: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_8", + 0x000001CE: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_9", + 0x000001CF: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_10", + 0x000001D0: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_11", + 0x000001D1: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_12", + 0x000001D2: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_13", + 0x000001D3: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_14", + 0x000001D4: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_15", + 0x000001D5: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_16", + 0x000001D6: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_17", + 0x000001D7: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_18", + 0x000001D8: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_19", + 0x000001D9: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_20", + 0x000001DA: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_21", + 0x000001DB: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_22", + 0x000001DC: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_23", + 0x000001DD: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_24", + 0x000001DE: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_25", + 0x000001DF: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_26", + 0x000001E0: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_27", + 0x000001E1: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_28", + 0x000001E2: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_29", + 0x000001E3: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_30", + 0x000001E4: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_31", + 0x000001E5: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_32", + 0x000001E6: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_33", + 0x000001E7: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_34", + 0x000001E8: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_35", + 0x000001E9: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_36", + 0x000001EA: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_37", + 0x000001EB: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_38", + 0x000001EC: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_39", + 0x000001ED: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_40", + 0x000001EE: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_41", + 0x000001EF: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_42", + 0x000001F0: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_43", + 0x000001F1: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_44", + 0x000001F2: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_45", + 0x000001F3: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_46", + 0x000001F4: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_47", + 0x000001F5: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_48", + 0x000001F6: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_49", + 0x000001F7: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_50", + 0x000001F8: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_51", + 0x000001F9: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_52", + 0x000001FA: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_53", + 0x000001FB: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_54", + 0x000001FC: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_55", + 0x000001FD: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_56", + 0x000001FE: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_57", + 0x000001FF: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_58", + 0x00000200: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_59", + 0x00000201: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_60", + 0x00000202: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_61", + 0x00000203: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_62", + 0x00000204: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_63", + 0x00000205: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_64", + 0x00000206: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_65", + 0x00000207: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_66", + 0x00000208: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_67", + 0x00000209: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_68", + 0x0000020A: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_69", + 0x0000020B: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_70", + 0x0000020C: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_71", + 0x0000020D: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_72", + 0x0000020E: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_73", + 0x0000020F: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_74", + 0x00000210: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_75", + 0x00000211: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_76", + 0x00000212: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_77", + 0x00000213: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_78", + 0x00000214: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_79", + 0x00000215: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_80", + 0x00000216: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_81", + 0x00000217: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_82", + 0x00000218: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_83", + 0x00000219: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_84", + 0x0000021A: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_85", + 0x0000021B: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_86", + 0x0000021C: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_87", + 0x0000021D: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_88", + 0x0000021E: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_89", + 0x0000021F: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_90", + 0x00000220: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_91", + 0x00000221: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_92", + 0x00000222: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_93", + 0x00000223: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_94", + 0x00000224: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_95", + 0x00000225: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_96", + 0x00000226: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_97", + 0x00000227: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_98", + 0x00000228: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_99", + 0x00000229: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_100", + 0x0000022A: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_101", + 0x0000022B: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_102", + 0x0000022C: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_103", + 0x0000022D: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_104", + 0x0000022E: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_105", + 0x0000022F: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_106", + 0x00000230: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_107", + 0x00000231: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_108", + 0x00000232: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_109", + 0x00000233: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_110", + 0x00000234: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_111", + 0x00000235: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_112", + 0x00000236: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_113", + 0x00000237: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_114", + 0x00000238: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_115", + 0x00000239: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_116", + 0x0000023A: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_117", + 0x0000023B: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_118", + 0x0000023C: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_119", + 0x0000023D: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_120", + 0x0000023E: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_121", + 0x0000023F: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_122", + 0x00000240: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_123", + 0x00000241: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_124", + 0x00000242: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_125", + 0x00000243: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_126", + 0x00000244: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_127", + 0x00000245: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_128", + 0x00000246: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_129", + 0x00000247: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_130", + 0x00000248: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_131", + 0x00000249: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_132", + 0x0000024A: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_133", + 0x0000024B: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_134", + 0x0000024C: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_135", + 0x0000024D: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_136", + 0x0000024E: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_137", + 0x0000024F: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_138", + 0x00000250: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_139", + 0x00000251: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_140", + 0x00000252: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_141", + 0x00000253: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_142", + 0x00000254: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_143", + 0x00000255: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_144", + 0x00000256: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_145", + 0x00000257: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_146", + 0x00000258: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_147", + 0x00000259: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_148", + 0x0000025A: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_149", + 0x0000025B: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_150", + 0x0000025C: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_151", + 0x0000025D: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_152", + 0x0000025E: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_153", + 0x0000025F: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_154", + 0x00000260: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_155", + 0x00000261: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_156", + 0x00000262: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_157", + 0x00000263: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_158", + 0x00000264: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_159", + 0x00000265: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_160", + 0x00000266: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_161", + 0x00000267: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_162", + 0x00000268: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_163", + 0x00000269: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_164", + 0x0000026A: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_165", + 0x0000026B: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_166", + 0x0000026C: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_167", + 0x0000026D: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_168", + 0x0000026E: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_169", + 0x0000026F: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_170", + 0x00000270: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_171", + 0x00000271: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_172", + 0x00000272: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_173", + 0x00000273: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_174", + 0x00000274: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_175", + 0x00000275: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_176", + 0x00000276: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_177", + 0x00000277: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_178", + 0x00000278: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_179", + 0x00000279: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_180", + 0x0000027A: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_181", + 0x0000027B: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_182", + 0x0000027C: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_183", + 0x0000027D: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_184", + 0x0000027E: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_185", + 0x0000027F: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_186", + 0x00000280: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_187", + 0x00000281: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_188", + 0x00000282: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_189", + 0x00000283: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_190", + 0x00000284: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_191", + 0x00000285: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_192", + 0x00000286: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_193", + 0x00000287: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_194", + 0x00000288: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_195", + 0x00000289: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_196", + 0x0000028A: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_197", + 0x0000028B: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_198", + 0x0000028C: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_199", + 0x0000028D: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_200", + 0x0000028E: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_201", + 0x0000028F: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_202", + 0x00000290: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_203", + 0x00000291: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_204", + 0x00000292: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_205", + 0x00000293: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_206", + 0x00000294: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_207", + 0x00000295: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_208", + 0x00000296: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_209", + 0x00000297: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_210", + 0x00000298: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_211", + 0x00000299: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_212", + 0x0000029A: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_213", + 0x0000029B: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_214", + 0x0000029C: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_215", + 0x0000029D: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_216", + 0x0000029E: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_217", + 0x0000029F: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_218", + 0x000002A0: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_219", + 0x000002A1: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_220", + 0x000002A2: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_221", + 0x000002A3: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_222", + 0x000002A4: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_223", + 0x000002A5: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_224", + 0x000002A6: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_225", + 0x000002A7: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_226", + 0x000002A8: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_227", + 0x000002A9: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_228", + 0x000002AA: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_229", + 0x000002AB: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_230", + 0x000002AC: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_231", + 0x000002AD: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_232", + 0x000002AE: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_233", + 0x000002AF: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_234", + 0x000002B0: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_235", + 0x000002B1: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_236", + 0x000002B2: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_237", + 0x000002B3: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_238", + 0x000002B4: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_239", + 0x000002B5: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_240", + 0x000002B6: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_241", + 0x000002B7: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_242", + 0x000002B8: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_243", + 0x000002B9: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_244", + 0x000002BA: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_245", + 0x000002BB: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_246", + 0x000002BC: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_247", + 0x000002BD: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_248", + 0x000002BE: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_249", + 0x000002BF: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_250", + 0x000002C0: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_251", + 0x000002C1: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_252", + 0x000002C2: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_253", + 0x000002C3: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_254", + 0x000002C4: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_255", + 0x000002C5: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_256", + 0x000002C6: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_257", + 0x000002C7: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_258", + 0x000002C8: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_259", + 0x000002C9: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_260", + 0x000002CA: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_261", + 0x000002CB: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_262", + 0x000002CC: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_263", + 0x000002CD: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_264", + 0x000002CE: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_265", + 0x000002CF: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_266", + 0x000002D0: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_267", + 0x000002D1: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_268", + 0x000002D2: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_269", + 0x000002D3: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_270", + 0x000002D4: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_271", + 0x000002D5: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_272", + 0x000002D6: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_273", + 0x000002D7: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_274", + 0x000002D8: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_275", + 0x000002D9: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_276", + 0x000002DA: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_277", + 0x000002DB: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_278", + 0x000002DC: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_279", + 0x000002DD: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_280", + 0x000002DE: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_281", + 0x000002DF: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_282", + 0x000002E0: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_283", + 0x000002E1: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_284", + 0x000002E2: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_285", + 0x000002E3: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_286", + 0x000002E4: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_287", + 0x000002E5: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_288", + 0x000002E6: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_289", + 0x000002E7: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_290", + 0x000002E8: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_291", + 0x000002E9: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_292", + 0x000002EA: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_293", + 0x000002EB: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_294", + 0x000002EC: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_295", + 0x000002ED: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_296", + 0x000002EE: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_297", + 0x000002EF: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_298", + 0x000002F0: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_299", + 0x000002F1: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_300", + 0x000002F2: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_301", + 0x000002F3: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_302", + 0x000002F4: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_303", + 0x000002F5: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_304", + 0x000002F6: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_305", + 0x000002F7: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_306", + 0x000002F8: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_307", + 0x000002F9: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_308", + 0x000002FA: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_309", + 0x000002FB: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_310", + 0x000002FC: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_311", + 0x000002FD: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_312", + 0x000002FE: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_313", + 0x000002FF: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_314", + 0x00000300: "IO_DRIVER_VERIFIER_DETECTED_VIOLATION_LIVEDUMP_315", + 0xC0000218: "STATUS_CANNOT_LOAD_REGISTRY_FILE", + 0xC000021A: "STATUS_SYSTEM_PROCESS_TERMINATED", + 0xC0000221: "STATUS_IMAGE_CHECKSUM_MISMATCH", + 0xC0000244: "STATUS_HAL_CONFIGURATION", + 0xC000026C: "STATUS_INVALID_DEVICE_OBJECT_PARAMETER", + 0xC0000350: "STATUS_FLT_NO_HANDLER_DEFINED", + 0xC0000409: "STATUS_STACK_BUFFER_OVERRUN", + 0xC0000428: "STATUS_BAD_DATA_HANDLE", + 0xC0000467: "STATUS_VERIFIER_STOP", + 0xC0000474: "STATUS_DRIVER_PROCESS_TERMINATED", + 0xC0000480: "STATUS_APPLICATION_HINT", + 0xC00005AD: "STATUS_BAD_COMPILATION", + 0xC00006BA: "STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD", + 0xC0000602: "STATUS_PNP_BAD_DRIVER", + 0xC0000603: "STATUS_IO_PREEMPTED", + 0xC000070A: "STATUS_THREAD_ALREADY_IN_SESSION", + 0xC000070B: "STATUS_THREAD_NOT_IN_SESSION", + 0xC0000912: "STATUS_VOLUME_CLUSTER_ALIGNMENT", + 0xC0000BC0: "STATUS_USER_MODE_BUGCHECK", + 0xC0000BC1: "STATUS_DBG_PRINT_EXCEPTION_C", + 0xC0000BC2: "STATUS_DBG_PRINT_EXCEPTION_WIDE", + 0xC0000BC3: "STATUS_DBG_KM_EXCEPTION", + 0xE0000001: "STATUS_INVALID_IMAGE_HASH", + 0xE0000102: "STATUS_FILE_SYSTEM_LIMITATION", + 0xE0000105: "STATUS_GRAPHICS_NO_AVAILABLE_VIDPN_TARGET_MODE", +} +# fmt: on + + +# --------------------------------------------------------------------------- +# Known Fix Database — common BSOD fixes by stop code +# --------------------------------------------------------------------------- + +KNOWN_FIXES: Dict[str, List[Dict[str, Any]]] = { + "PAGE_FAULT_IN_NONPAGED_AREA": [ + {"issue": "Faulty RAM / defective memory module", + "fix": "Run Windows Memory Diagnostic (mdsched.exe) or MemTest86+. Replace faulty DIMM.", + "tool": "memtest86+", "difficulty": "moderate"}, + {"issue": "Corrupted driver or system file", + "fix": "Run SFC /SCANNOW and DISM /Online /Cleanup-Image /RestoreHealth", + "tool": "sfc+dism", "difficulty": "easy"}, + {"issue": "Incompatible or buggy driver (especially NIC, storage, or antivirus)", + "fix": "Boot Safe Mode, roll back recently updated drivers, or use Driver Verifier", + "tool": "driver-verifier", "difficulty": "moderate"}, + ], + "IRQL_NOT_LESS_OR_EQUAL": [ + {"issue": "Faulty or incompatible driver (most common: network, graphics, storage)", + "fix": "Boot Safe Mode, disable/roll back recently added drivers", + "tool": "driver-rollback", "difficulty": "easy"}, + {"issue": "Corrupted system files or memory", + "fix": "SFC /SCANNOW, chkdsk /f, Windows Memory Diagnostic", + "tool": "sfc+chkdsk", "difficulty": "easy"}, + {"issue": "Recently installed hardware causing IRQ conflict", + "fix": "Remove hardware, check BIOS for IRQ settings", + "tool": "hardware-removal", "difficulty": "moderate"}, + ], + "KMODE_EXCEPTION_NOT_HANDLED": [ + {"issue": "Faulty device driver identified in the error", + "fix": "Update or roll back the driver named in the crash", + "tool": "driver-update", "difficulty": "easy"}, + {"issue": "Corrupted system file or incompatible software", + "fix": "System Restore, SFC /SCANNOW, uninstall recently added software", + "tool": "system-restore", "difficulty": "easy"}, + ], + "SYSTEM_SERVICE_EXCEPTION": [ + {"issue": "Corrupted or incompatible driver (often graphics, ntfs.sys, or win32k.sys)", + "fix": "Update graphics drivers, run SFC /SCANNOW, check for Windows updates", + "tool": "driver-update+sfc", "difficulty": "easy"}, + {"issue": "Memory corruption or failing hardware", + "fix": "Run Windows Memory Diagnostic, check disk health with chkdsk", + "tool": "memtest+chkdsk", "difficulty": "moderate"}, + {"issue": "Antivirus software conflict (especially with win32kbase.sys crashes)", + "fix": "Temporarily disable antivirus, update or reinstall", + "tool": "av-disable", "difficulty": "easy"}, + ], + "DPC_WATCHDOG_VIOLATION": [ + {"issue": "Faulty storage driver (especially NVMe or SATA AHCI driver)", + "fix": "Update storage controller driver from OEM website, not Windows Update", + "tool": "driver-update", "difficulty": "moderate"}, + {"issue": "Overheating or failing storage device", + "fix": "Check SSD/HDD health with CrystalDiskInfo or smartmontools", + "tool": "smart-test", "difficulty": "easy"}, + {"issue": "Buggy network or USB driver causing DPC latency", + "fix": "Update NIC and USB controller drivers, check for firmware updates", + "tool": "driver-update", "difficulty": "moderate"}, + ], + "DRIVER_IRQL_NOT_LESS_OR_EQUAL": [ + {"issue": "Specific driver causing IRQ violation (named in crash dump)", + "fix": "Update or reinstall the driver. If third-party, check manufacturer support.", + "tool": "driver-update", "difficulty": "easy"}, + {"issue": "Faulty hardware (RAM, motherboard)", + "fix": "Run hardware diagnostics: MemTest86, check motherboard for bulging capacitors", + "tool": "hardware-diag", "difficulty": "moderate"}, + ], + "CRITICAL_PROCESS_DIED": [ + {"issue": "Critical Windows system process terminated unexpectedly", + "fix": "SFC /SCANNOW, DISM restore health, check for disk corruption", + "tool": "sfc+dism", "difficulty": "easy"}, + {"issue": "Corrupted Windows installation or system files", + "fix": "Windows Startup Repair, in-place upgrade, or system restore", + "tool": "startup-repair", "difficulty": "moderate"}, + {"issue": "Disk failure preventing system files from loading", + "fix": "chkdsk /f /r, check SMART data, backup immediately", + "tool": "chkdsk", "difficulty": "moderate"}, + ], + "CRITICAL_OBJECT_TERMINATION": [ + {"issue": "Critical process/thread terminated unexpectedly", + "fix": "Boot into Safe Mode, check for recently installed software, roll back drivers", + "tool": "safe-mode", "difficulty": "easy"}, + {"issue": "Corrupted user profile or system hive", + "fix": "System Restore, create new user profile and migrate data", + "tool": "system-restore", "difficulty": "moderate"}, + ], + "WHEA_UNCORRECTABLE_ERROR": [ + {"issue": "Hardware failure — CPU, GPU, RAM, PCIe device, or PSU", + "fix": "Check CPU for overclocking, reset BIOS to defaults, reseat RAM/GPU", + "tool": "hardware-check", "difficulty": "moderate"}, + {"issue": "Insufficient or failing power supply", + "fix": "Test with known-good PSU, check voltages in BIOS", + "tool": "psu-test", "difficulty": "hard"}, + {"issue": "Overheating CPU or GPU", + "fix": "Clean heatsinks, reapply thermal paste, check fan operation", + "tool": "thermal-check", "difficulty": "moderate"}, + ], + "MACHINE_CHECK_EXCEPTION": [ + {"issue": "CPU hardware error (overclocking, overheating, or faulty CPU)", + "fix": "Remove overclock, reset BIOS, check CPU temperature and seating", + "tool": "cpu-check", "difficulty": "moderate"}, + {"issue": "Motherboard issue (bad capacitor, VRM failure, PCIe slot problem)", + "fix": "Inspect motherboard for bulging capacitors, test with minimal hardware config", + "tool": "mb-inspect", "difficulty": "hard"}, + {"issue": "Incompatible or faulty RAM", + "fix": "Test with MemTest86, try single DIMM at a time", + "tool": "memtest86+", "difficulty": "moderate"}, + ], + "VIDEO_TDR_FAILURE": [ + {"issue": "Graphics driver timeout — driver hanging or crashing", + "fix": "Use DDU to fully remove graphics drivers, install latest stable version", + "tool": "ddu", "difficulty": "moderate"}, + {"issue": "GPU overheating or underpowered", + "fix": "Check GPU temperatures, reseat GPU, test with known-good PSU", + "tool": "gpu-check", "difficulty": "moderate"}, + {"issue": "GPU hardware failure (VRAM degradation)", + "fix": "Test with FurMark/OCCT, if artifacts appear, GPU replacement needed", + "tool": "gpu-stress-test", "difficulty": "hard"}, + ], + "VIDEO_TDR_TIMEOUT_DETECTED": [ + {"issue": "Graphics driver not responding — timeout reached", + "fix": "Clean uninstall GPU drivers with DDU in Safe Mode, install latest WHQL driver", + "tool": "ddu", "difficulty": "moderate"}, + {"issue": "GPU hardware instability (usually VRAM or core)", + "fix": "Underclock GPU memory/core, test with OCCT VRAM test", + "tool": "gpu-check", "difficulty": "moderate"}, + ], + "CLOCK_WATCHDOG_TIMEOUT": [ + {"issue": "CPU core not responding to clock interrupt — usually hardware issue", + "fix": "Disable overclocking, update BIOS, check for bent CPU socket pins", + "tool": "cpu-check", "difficulty": "moderate"}, + {"issue": "Incompatible or outdated BIOS causing CPU power state issues", + "fix": "Update BIOS to latest version from motherboard manufacturer", + "tool": "bios-update", "difficulty": "hard"}, + ], + "BAD_POOL_HEADER": [ + {"issue": "Memory pool corruption — often caused by buggy driver or antivirus", + "fix": "Use Driver Verifier to identify offending driver, uninstall/update it", + "tool": "driver-verifier", "difficulty": "moderate"}, + {"issue": "Faulty RAM hardware", + "fix": "Run Windows Memory Diagnostic, replace defective module", + "tool": "memtest86+", "difficulty": "moderate"}, + ], + "BAD_POOL_CALLER": [ + {"issue": "Driver making invalid memory pool request", + "fix": "Identified via Driver Verifier, update/remove the problematic driver", + "tool": "driver-verifier", "difficulty": "moderate"}, + {"issue": "Corrupted system files", + "fix": "SFC /SCANNOW, Windows Update, system restore point", + "tool": "sfc+windows-update", "difficulty": "easy"}, + ], + "MEMORY_MANAGEMENT": [ + {"issue": "Faulty RAM (most common cause)", + "fix": "Run MemTest86+, test DIMMs one at a time", + "tool": "memtest86+", "difficulty": "moderate"}, + {"issue": "Driver corrupting memory management structures", + "fix": "Driver Verifier, check for recently installed drivers", + "tool": "driver-verifier", "difficulty": "moderate"}, + {"issue": "Overclocked memory unstable", + "fix": "Reset RAM to JEDEC/stock speeds in BIOS, disable XMP/DOCP", + "tool": "bios-ram-defaults", "difficulty": "easy"}, + ], + "NTFS_FILE_SYSTEM": [ + {"issue": "NTFS volume corruption", + "fix": "chkdsk /f /r on the affected drive, may need boot-time scan", + "tool": "chkdsk", "difficulty": "moderate"}, + {"issue": "Failing storage device", + "fix": "Check SMART attributes, backup data immediately, replace drive", + "tool": "smart-test", "difficulty": "moderate"}, + {"issue": "File system filter driver (antivirus, backup software)", + "fix": "Disable third-party antivirus, update/reinstall file system filter drivers", + "tool": "fs-filter-check", "difficulty": "moderate"}, + ], + "INPAGE_ERROR": [ + {"issue": "Bad memory or failing storage drive", + "fix": "Check disk SMART status, run chkdsk, run memory test", + "tool": "disk+mem-diag", "difficulty": "moderate"}, + {"issue": "Corrupted page file or system page table", + "fix": "Clear pagefile.sys, let Windows recreate it, check disk for errors", + "tool": "pagefile-reset", "difficulty": "moderate"}, + ], + "KERNEL_DATA_INPAGE_ERROR": [ + {"issue": "Failure reading kernel data from disk into memory", + "fix": "Check disk health (SMART, chkdsk), test RAM, verify pagefile integrity", + "tool": "disk+mem-diag", "difficulty": "moderate"}, + {"issue": "Bad disk sector in page file or system file area", + "fix": "chkdsk /f /r, consider disk replacement if reallocated sectors are growing", + "tool": "chkdsk", "difficulty": "moderate"}, + ], + "INACCESSIBLE_BOOT_DEVICE": [ + {"issue": "Windows lost access to boot drive — driver issue during boot", + "fix": "Boot into Safe Mode, check storage controller driver (AHCI/RAID mode)", + "tool": "storage-driver-check", "difficulty": "moderate"}, + {"issue": "Incorrect SATA mode in BIOS (AHCI vs RAID vs IDE)", + "fix": "Change SATA mode in BIOS to match original Windows install setting", + "tool": "bios-sata-mode", "difficulty": "moderate"}, + {"issue": "Failing or disconnected boot drive", + "fix": "Reseat drive cables, try different SATA port, check SMART", + "tool": "disk-check", "difficulty": "easy"}, + ], + "ATTEMPTED_WRITE_TO_READONLY_MEMORY": [ + {"issue": "Driver attempting to write to read-only memory", + "fix": "Use Driver Verifier, update/remove the offending driver", + "tool": "driver-verifier", "difficulty": "moderate"}, + ], + "DRIVER_CORRUPTED_EXPOOL": [ + {"issue": "Driver corrupted the executive pool (kernel memory)", + "fix": "Run Driver Verifier to identify, update the problematic driver", + "tool": "driver-verifier", "difficulty": "moderate"}, + ], + "DRIVER_OVERRAN_STACK_BUFFER": [ + {"issue": "Driver stack buffer overrun — common with buggy drivers", + "fix": "Update drivers, especially network, audio, or recently installed one", + "tool": "driver-update", "difficulty": "easy"}, + {"issue": "/GS stack buffer overrun detection", + "fix": "If pointing to a specific .sys file, update/reinstall it. System Restore as fallback.", + "tool": "driver-update+system-restore", "difficulty": "easy"}, + ], + "CRITICAL_STRUCTURE_CORRUPTION": [ + {"issue": "Critical kernel structure detected as corrupted", + "fix": "Often caused by driver bug or hardware (RAM). Run Driver Verifier and MemTest86.", + "tool": "driver-verifier+memtest", "difficulty": "hard"}, + {"issue": "Kernel-mode code signing violation or patch protection triggered", + "fix": "Remove any kernel-mode patches, rootkits, or unsigned drivers", + "tool": "rootkit-scan", "difficulty": "hard"}, + ], + "NTOSKRNL related": [ + {"issue": "Corrupted Windows kernel or system files", + "fix": "SFC /SCANNOW, DISM restore health, Windows Update, in-place upgrade", + "tool": "sfc+dism", "difficulty": "easy"}, + {"issue": "Hardware incompatibility (especially RAM or motherboard)", + "fix": "Check hardware compatibility, test RAM, update BIOS", + "tool": "hardware-check", "difficulty": "moderate"}, + ], + "HAL_INITIALIZATION_FAILED": [ + {"issue": "Hardware Abstraction Layer initialization failure", + "fix": "Check for recently changed hardware, update BIOS, reset BIOS defaults", + "tool": "bios-check", "difficulty": "moderate"}, + ], + "DRIVER_POWER_STATE_FAILURE": [ + {"issue": "Driver failing to handle power state transitions (sleep/wake)", + "fix": "Update graphics, network, and chipset drivers. Disable fast startup.", + "tool": "driver-update+fast-startup-off", "difficulty": "easy"}, + {"issue": "Power state IRP handling bug", + "fix": "Check for failing PSU, update BIOS, disconnect non-essential peripherals", + "tool": "psu-check", "difficulty": "moderate"}, + ], + "THREAD_STUCK_IN_DEVICE_DRIVER": [ + {"issue": "GPU driver or hardware hang — thread blocked indefinitely", + "fix": "Use DDU to clean install GPU drivers, check GPU temperatures", + "tool": "ddu", "difficulty": "moderate"}, + {"issue": "Faulty GPU hardware", + "fix": "Test with known-good GPU, underclock, check for artifacts with stress tools", + "tool": "gpu-stress-test", "difficulty": "hard"}, + ], + "Unexpected kernel mode trap": [ + {"issue": "CPU exception that was not handled — faulty CPU or overclock", + "fix": "Reset CPU to stock, check temperatures, update BIOS, check for bent pins", + "tool": "cpu-check", "difficulty": "moderate"}, + {"issue": "Faulty RAM causing unexpected execution path", + "fix": "MemTest86+, test DIMMs individually", + "tool": "memtest86+", "difficulty": "moderate"}, + ], +} + +# Common catch-all tips for any BSOD +GENERIC_RECOMMENDATIONS: List[str] = [ + "Boot into Safe Mode (press F8 during startup or use Shift+Restart) and see if the crash reproduces.", + "Run SFC /SCANNOW from an elevated Command Prompt to check for corrupted system files.", + "Run DISM /Online /Cleanup-Image /RestoreHealth to repair the Windows system image.", + "Check for Windows Updates — many BSODs are fixed by cumulative updates.", + "Run chkdsk /f /r to check for disk errors that can cause system crashes.", + "Remove any recently installed hardware, drivers, or software — use System Restore if needed.", + "Check system temperatures (CPU, GPU, motherboard) — overheating causes random BSODs.", + "Test RAM with Windows Memory Diagnostic (mdsched.exe) or MemTest86+.", + "Update all device drivers, especially graphics, network, and storage controllers.", + "Reset BIOS/UEFI to factory defaults, disable overclocking (CPU, RAM, GPU).", + "Use Driver Verifier (verifier.exe) to identify problematic kernel-mode drivers.", + "If crashes persist after all software steps, suspect hardware — test PSU, RAM, and motherboard.", +] + + +# --------------------------------------------------------------------------- +# Data structures for parsed crash info +# --------------------------------------------------------------------------- + +@dataclass +class BugCheckInfo: + """Parsed bug check information from a minidump.""" + + code: int + name: str + parameters: List[int] + address: int + thread_id: Optional[int] = None + flags: int = 0 + + +@dataclass +class ModuleInfo: + """A loaded module (driver or DLL) from the minidump.""" + + name: str + base_address: int + size: int + + +@dataclass +class SystemInfo: + """System information extracted from the minidump.""" + + os_version: str = "Unknown" + processor_arch: str = "Unknown" + processor_count: int = 0 + + +@dataclass +class BSODResult: + """Complete BSOD analysis result.""" + + file_path: str + valid: bool + bug_check: Optional[BugCheckInfo] = None + system_info: Optional[SystemInfo] = None + modules: List[ModuleInfo] = field(default_factory=list) + suspected_drivers: List[ModuleInfo] = field(default_factory=list) + known_fixes: List[Dict[str, Any]] = field(default_factory=list) + recommendations: List[str] = field(default_factory=list) + llm_prompt: Optional[str] = None + parse_error: Optional[str] = None + raw_exception_data: Optional[Dict[str, Any]] = None + + +# --------------------------------------------------------------------------- +# BSODTester — main diagnostic class +# --------------------------------------------------------------------------- + +class BSODTester: + """ + BSOD Analyzer diagnostic module. + + Parses Windows minidump (.dmp) files, extracts stop code and parameters, + correlates with known fixes, and optionally generates an LLM interpretation + prompt. + + Usage: + tester = BSODTester() + result = tester.run("C:\\Windows\\Minidump\\010125-12345-01.dmp") + print(result.report()) + """ + + def __init__(self, llm_enabled: bool = False): + self._last_result: Optional[BSODResult] = None + self.llm_enabled = llm_enabled + self._minidump_version = self._check_minidump_lib() + + def _check_minidump_lib(self) -> Optional[str]: + """Check if the minidump library is available and get version.""" + try: + import minidump + # Version file may not exist; try common locations + import importlib.metadata + try: + return importlib.metadata.version("minidump") + except importlib.metadata.PackageNotFoundError: + return "installed (unknown version)" + except ImportError: + return None + + def run(self, minidump_path: str, **kwargs: Any) -> BSODResult: + """ + Parse and analyze a Windows minidump file. + + Args: + minidump_path: Path to the .dmp file. + **kwargs: Additional options (unused, for API consistency). + + Returns: + BSODResult with all extracted information. + """ + path = Path(minidump_path) + logger.info("BSOD analysis starting: %s", path) + + if not path.exists(): + return BSODResult( + file_path=str(path), + valid=False, + parse_error=f"File not found: {path}", + ) + + if not self._minidump_version: + return BSODResult( + file_path=str(path), + valid=False, + parse_error="minidump library not installed. Install with: pip install minidump", + ) + + try: + result = self._parse_minidump(path) + except Exception as exc: + logger.exception("Failed to parse minidump %s", path) + result = BSODResult( + file_path=str(path), + valid=False, + parse_error=f"Parse error: {exc}", + ) + + if result.valid: + result.known_fixes = self._lookup_known_fixes(result) + result.recommendations = self._generate_recommendations(result) + if self.llm_enabled: + result.llm_prompt = self._build_llm_prompt(result) + + self._last_result = result + return result + + def _parse_minidump(self, path: Path) -> BSODResult: + """Internal minidump parsing using the minidump library.""" + from minidump.minidumpfile import MinidumpFile + + mf = MinidumpFile.parse(str(path)) + + result = BSODResult( + file_path=str(path), + valid=True, + ) + + # --- Extract system information --- + sysinfo = SystemInfo() + if mf.sysinfo: + sysinfo.processor_arch = str(mf.sysinfo.ProcessorArchitecture) + sysinfo.processor_count = getattr(mf.sysinfo, 'NumberOfProcessors', 0) + + # Try to extract OS version info + if hasattr(mf.sysinfo, 'BuildNumber') and mf.sysinfo.BuildNumber: + build_no = mf.sysinfo.BuildNumber + if hasattr(mf.sysinfo, 'ProductType'): + sysinfo.os_version = self._map_build_to_version(build_no) + elif hasattr(mf.sysinfo, 'MajorVersion') and mf.sysinfo.MajorVersion: + major = mf.sysinfo.MajorVersion + minor = getattr(mf.sysinfo, 'MinorVersion', 0) + build = getattr(mf.sysinfo, 'BuildNumber', 0) + sysinfo.os_version = f"Windows {major}.{minor} (Build {build})" + + result.system_info = sysinfo + + # --- Extract exception / bug check info --- + if mf.exception and mf.exception.exception_records: + exc_stream = mf.exception.exception_records[0] + exc = exc_stream.ExceptionRecord + + bug_check = BugCheckInfo( + code=exc.ExceptionCode_raw, + name=self._get_bugcheck_name(exc.ExceptionCode_raw), + parameters=exc.ExceptionInformation, + address=exc.ExceptionAddress, + thread_id=exc_stream.ThreadId, + flags=exc.ExceptionFlags, + ) + result.bug_check = bug_check + result.raw_exception_data = { + "exception_code_raw": hex(exc.ExceptionCode_raw), + "exception_code_name": str(exc.ExceptionCode), + "exception_flags": exc.ExceptionFlags, + "exception_record": exc.ExceptionRecord, + "exception_address": hex(exc.ExceptionAddress), + "number_parameters": exc.NumberParameters, + "exception_information": [hex(p) for p in exc.ExceptionInformation], + "thread_id": exc_stream.ThreadId, + } + else: + logger.warning("No exception record found in minidump") + + # --- Extract modules / drivers --- + modules: List[ModuleInfo] = [] + if mf.modules and mf.modules.modules: + for mod in mf.modules.modules: + name = getattr(mod, 'ModuleName', '') or getattr(mod, 'name', '') or '' + base = getattr(mod, 'BaseOfImage', 0) + size = getattr(mod, 'ImageSize', 0) + + if isinstance(name, bytes): + name = name.decode('utf-8', errors='replace') + + modules.append(ModuleInfo( + name=name, + base_address=base, + size=size, + )) + + modules.sort(key=lambda m: m.base_address) + result.modules = modules + + # --- Identify suspected drivers --- + result.suspected_drivers = self._identify_suspected_drivers(result) + + return result + + def _get_bugcheck_name(self, code: int) -> str: + """Map a bug check code to its human-readable name.""" + if code in BUGCHECK_CODES: + return BUGCHECK_CODES[code] + + # Check for STATUS_ codes (0xC0000000+) + # These are NTSTATUS values used in exception records + known_ntstatus = { + 0xC0000005: "ACCESS_VIOLATION", + 0xC0000006: "IN_PAGE_ERROR", + 0xC000001D: "ILLEGAL_INSTRUCTION", + 0xC0000025: "NONCONTINUABLE_EXCEPTION", + 0xC0000026: "INVALID_DISPOSITION", + 0xC000008C: "ARRAY_BOUNDS_EXCEEDED", + 0xC000008D: "FLOAT_DENORMAL_OPERAND", + 0xC000008E: "FLOAT_DIVIDE_BY_ZERO", + 0xC000008F: "FLOAT_INEXACT_RESULT", + 0xC0000090: "FLOAT_INVALID_OPERATION", + 0xC0000091: "FLOAT_OVERFLOW", + 0xC0000092: "FLOAT_STACK_CHECK", + 0xC0000093: "FLOAT_UNDERFLOW", + 0xC0000094: "INT_DIVIDE_BY_ZERO", + 0xC0000095: "INT_OVERFLOW", + 0xC0000096: "PRIV_INSTRUCTION", + 0xC00000FD: "STACK_OVERFLOW", + } + if code in known_ntstatus: + return known_ntstatus[code] + + return f"UNKNOWN_0x{code:08X}" + + def _map_build_to_version(self, build_no: int) -> str: + """Map Windows build number to friendly version name.""" + from minidump.streams.SystemInfoStream import MINIDUMP_WIN_BUILDNO_TO_VERSION + if build_no in MINIDUMP_WIN_BUILDNO_TO_VERSION: + return MINIDUMP_WIN_BUILDNO_TO_VERSION[build_no] + if build_no >= 22000: + return f"Windows 11 (Build {build_no})" + if build_no >= 10240: + return f"Windows 10 (Build {build_no})" + return f"Windows (Build {build_no})" + + def _identify_suspected_drivers(self, result: BSODResult) -> List[ModuleInfo]: + """ + Identify drivers likely involved in the crash based on bug check parameters. + + Different bug check codes encode driver information in different parameters: + - 0xD1 (DRIVER_IRQL_NOT_LESS_OR_EQUAL): param[1] is often the driver base + - 0x50 (PAGE_FAULT_IN_NONPAGED_AREA): param[3] often points to instruction + - 0x7E, 0x8E: driver name is encoded in crash parameters + - 0x9F (DRIVER_POWER_STATE_FAILURE): param[2] imaging device/driver + """ + suspected: List[ModuleInfo] = [] + + if not result.bug_check or not result.modules: + return suspected + + code = result.bug_check.code + params = result.bug_check.parameters + + # Collect addresses from parameters that might reference a driver + addresses_to_check: List[int] = [] + if code in (0x000000D1, 0x00000050, 0x000000BE, 0x000000CE, 0x000000D3): + if len(params) > 1: + addresses_to_check.append(params[1]) + addresses_to_check.append(params[1] - (params[1] % 0x1000)) # page-aligned + if code == 0x0000009F and len(params) > 2: + addresses_to_check.append(params[2]) + if code == 0x00000116 and len(params) > 0: # VIDEO_TDR_FAILURE + addresses_to_check.append(params[0]) + if code == 0x00000133 and len(params) > 2: # DPC_WATCHDOG_VIOLATION + addresses_to_check.append(params[2]) + + # Match addresses against loaded module ranges + for addr in addresses_to_check: + if addr == 0: + continue + for mod in result.modules: + if mod.base_address <= addr < mod.base_address + mod.size: + if mod not in suspected: + suspected.append(mod) + + return suspected + + def _lookup_known_fixes(self, result: BSODResult) -> List[Dict[str, Any]]: + """Look up known fixes for the identified bug check code.""" + fixes: List[Dict[str, Any]] = [] + if not result.bug_check: + return fixes + + name = result.bug_check.name + + # Direct lookup + if name in KNOWN_FIXES: + fixes.extend(KNOWN_FIXES[name]) + + # Try partial matches for subset codes + base_name = name.split("_")[0] if "_" in name else "" + for key in KNOWN_FIXES: + if key in name or name.startswith(key): + if key != name: + fixes.extend(KNOWN_FIXES[key]) + + # Fallback: check for driver-related names + if "DRIVER" in name: + driver_related = KNOWN_FIXES.get("DRIVER_IRQL_NOT_LESS_OR_EQUAL", []) + for fix in driver_related: + if fix not in fixes: + fixes.append(fix) + + return fixes + + def _generate_recommendations(self, result: BSODResult) -> List[str]: + """Generate actionable recommendations based on crash analysis.""" + recs: List[str] = [] + + if result.known_fixes: + recs.append("--- Most likely fixes for this BSOD ---") + for fix in result.known_fixes[:3]: # Top 3 + tool_tag = f" [{fix.get('tool', '?')}]" if fix.get('tool') else "" + recs.append(f" {fix['issue']}{tool_tag}: {fix['fix']}") + + if result.suspected_drivers: + recs.append("--- Suspected drivers ---") + for drv in result.suspected_drivers[:5]: + recs.append(f" {drv.name} (base: 0x{drv.base_address:x})") + + recs.append(f"\n--- Stop Code: {result.bug_check.name if result.bug_check else 'Unknown'} ---") + if result.bug_check and result.bug_check.parameters: + recs.append(f" Parameters: {', '.join(f'0x{p:x}' for p in result.bug_check.parameters)}") + if result.bug_check and result.bug_check.address: + recs.append(f" Faulting Address: 0x{result.bug_check.address:x}") + + # Add generic steps + recs.append("\n--- General troubleshooting steps ---") + recs.extend(GENERIC_RECOMMENDATIONS[:5]) + + return recs + + def _build_llm_prompt(self, result: BSODResult) -> str: + """ + Build a prompt for an LLM to interpret the BSOD. + + The prompt is structured to give the LLM all relevant crash context + and ask for a natural-language interpretation. + """ + bc = result.bug_check + prompt = f"""You are a Windows BSOD diagnostic expert. Analyze this crash and explain what happened in plain language. + +=== CRASH INFORMATION === +Stop Code: {bc.name if bc else 'Unknown'} (0x{bc.code:08X if bc else 0}) +Parameters: {', '.join(f'0x{p:x}' for p in (bc.parameters if bc else []))} +Faulting Address: {f'0x{bc.address:x}' if bc else 'N/A'} +Thread ID: {f'0x{bc.thread_id:x}' if bc else 'N/A'} + +=== SYSTEM === +OS: {result.system_info.os_version if result.system_info else 'Unknown'} +Architecture: {result.system_info.processor_arch if result.system_info else 'Unknown'} + +=== SUSPECTED DRIVERS === +{chr(10).join(f'- {d.name} (0x{d.base_address:x})' for d in result.suspected_drivers[:10]) if result.suspected_drivers else 'None identified'} + +=== LOADED MODULES (Top 20) === +{chr(10).join(f'- {m.name}' for m in result.modules[:20]) if result.modules else 'None'} + +=== KNOWN FIXES === +{chr(10).join(f'- {f["issue"]}: {f["fix"]}' for f in result.known_fixes[:5]) if result.known_fixes else 'No specific known fixes in database'} + +Please provide: +1. What happened — in plain English +2. Most likely root cause (driver, hardware, or software) +3. Recommended action steps, in priority order +4. Whether this is likely a one-time glitch or a recurring problem +5. If hardware related, which component is most likely at fault +""" + return prompt.strip() + + def report(self) -> str: + """ + Generate a human-readable report from the last analysis result. + + Returns: + Formatted report string. + """ + if not self._last_result: + return "No analysis has been run yet. Call .run(minidump_path) first." + + r = self._last_result + lines: List[str] = [] + lines.append("=" * 72) + lines.append(" BSOD Analyzer Report") + lines.append("=" * 72) + lines.append(f" File: {r.file_path}") + if r.parse_error: + lines.append(f" ERROR: {r.parse_error}") + return "\n".join(lines) + lines.append(f" Valid: {'Yes' if r.valid else 'No'}") + lines.append("") + + # Summary section + if r.bug_check: + lines.append("--- Bug Check ---") + lines.append(f" Code: 0x{r.bug_check.code:08X} ({r.bug_check.name})") + if r.bug_check.parameters: + for i, p in enumerate(r.bug_check.parameters): + lines.append(f" Param {i}: 0x{p:016X}") + if r.bug_check.address: + lines.append(f" Address: 0x{r.bug_check.address:016X}") + if r.bug_check.thread_id: + lines.append(f" Thread: {r.bug_check.thread_id}") + lines.append("") + + # System info + if r.system_info: + lines.append("--- System ---") + lines.append(f" OS: {r.system_info.os_version}") + lines.append(f" CPU Arch: {r.system_info.processor_arch}") + lines.append("") + + # Suspected drivers + if r.suspected_drivers: + lines.append("--- Suspected Drivers ---") + for d in r.suspected_drivers: + lines.append(f" {d.name} [base: 0x{d.base_address:x}, size: {d.size}]") + lines.append("") + + # Known fixes + if r.known_fixes: + lines.append("--- Known Fixes ---") + seen_issues: set = set() + for fix in r.known_fixes: + if fix["issue"] not in seen_issues: + lines.append(f" {fix['issue']}") + lines.append(f" Fix: {fix['fix']}") + if fix.get("tool"): + lines.append(f" Tool: {fix['tool']}") + seen_issues.add(fix["issue"]) + lines.append("") + + # Recommendations + if r.recommendations: + lines.append("--- Recommendations ---") + for rec in r.recommendations: + lines.append(f" {rec}") + lines.append("") + + # LLM prompt hint + if r.llm_prompt: + lines.append("--- LLM Prompt Available ---") + lines.append(" An LLM interpretation prompt was generated. Use the") + lines.append(" 'llm_prompt' attribute to send to any LLM.") + lines.append("") + lines.append("--- LLM Prompt (truncated) ---") + # Show first 500 chars + lines.append(r.llm_prompt[:500]) + if len(r.llm_prompt) > 500: + lines.append(" ... (truncated)") + lines.append("") + + lines.append("=" * 72) + return "\n".join(lines) + + +# --------------------------------------------------------------------------- +# Convenience functions +# --------------------------------------------------------------------------- + +def analyze_minidump(path: str, llm: bool = False) -> BSODResult: + """ + Convenience function: create a tester, run analysis, return result. + + Args: + path: Path to .dmp file + llm: Whether to generate an LLM prompt + + Returns: + BSODResult with analysis + """ + tester = BSODTester(llm_enabled=llm) + return tester.run(path) + + +# --------------------------------------------------------------------------- +# CLI entry point +# --------------------------------------------------------------------------- + +def main() -> None: + """CLI entry point for bsod-analyzer.""" + import argparse + + parser = argparse.ArgumentParser( + description="BSOD Analyzer — Parse Windows minidumps and interpret stop codes.", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python -m src.diagnostics.bsod C:\\Windows\\Minidump\\crash.dmp + python -m src.diagnostics.bsod crash.dmp --llm + python -m src.diagnostics.bsod crash.dmp --json + python -m src.diagnostics.bsod crash.dmp --json --pretty + """, + ) + + parser.add_argument("minidump", help="Path to .dmp minidump file") + parser.add_argument("--llm", action="store_true", + help="Generate LLM interpretation prompt") + parser.add_argument("--json", action="store_true", + help="Output as JSON instead of formatted report") + parser.add_argument("--pretty", action="store_true", + help="Pretty-print JSON output") + parser.add_argument("--log-level", default="WARNING", + choices=["DEBUG", "INFO", "WARNING", "ERROR"], + help="Set logging level") + + args = parser.parse_args() + + logging.basicConfig( + level=getattr(logging, args.log_level), + format="%(levelname)s: %(message)s", + ) + + result = analyze_minidump(args.minidump, llm=args.llm) + + if args.json: + indent = 2 if args.pretty else None + output = { + "file": result.file_path, + "valid": result.valid, + "error": result.parse_error, + "bug_check": asdict(result.bug_check) if result.bug_check else None, + "system_info": asdict(result.system_info) if result.system_info else None, + "suspected_drivers": [asdict(d) for d in result.suspected_drivers], + "known_fixes": result.known_fixes, + "recommendations": result.recommendations, + "llm_prompt": result.llm_prompt, + } + # Remove None values that break json dump + output = {k: v for k, v in output.items() if v is not None} + print(json.dumps(output, indent=indent, default=str)) + else: + print(result.report()) + + +if __name__ == "__main__": + main() diff --git a/src/diagnostics/drivers.py b/src/diagnostics/drivers.py new file mode 100644 index 0000000..86d73cb --- /dev/null +++ b/src/diagnostics/drivers.py @@ -0,0 +1,888 @@ +""" +Hermes Portable Rescue — Hardware-Aware Driver Scanner & Updater. + +Scans every PCI and USB device on the system, maps them to human-readable +names via the ``pci.ids`` / ``usb.ids`` databases, and cross-references +against a mounted Windows driver store (``DriverStore/FileRepository``) +when available. Produces a structured report suitable for LLM-guided +driver discovery and installation. + +Typical usage:: + + from diagnostics.drivers import DriverUpdater + + updater = DriverUpdater(windows_mount="/mnt/c") + report = updater.run() + print(updater.text_report()) + +Windows-mount-aware features +----------------------------- +* Scans ``%WINDIR%/System32/DriverStore/FileRepository/`` for installed + Windows driver ``.inf`` files. +* Matches each detected PCI/USB device to the best candidate driver + from the store by vendor + device ID. +* Flags devices with **no** matching Windows driver as missing. +* Reports driver date, version, and provider when available from the INF. + +Linux-only fallback +------------------- +If no Windows mount is found, also produces a full hardware inventory +with names and IDs — the ``driver_status`` field defaults to ``"unknown"`` +for every device. +""" + +from __future__ import annotations + +import csv +import io +import json +import logging +import os +import re +import shlex +import subprocess +import sys +from dataclasses import dataclass, field, fields, is_dataclass +from pathlib import Path +from typing import Any, Optional + +logger = logging.getLogger(__name__) + +# ── Helpers ────────────────────────────────────────────────────────────────── + +_PCI_IDS_PATHS = ["/usr/share/hwdata/pci.ids", "/usr/share/misc/pci.ids"] +_USB_IDS_PATHS = ["/usr/share/hwdata/usb.ids", "/usr/share/misc/usb.ids"] + +# Regex: vendor 1234 VendorName +# device 1234 DeviceName +# (indented by tab) +_RE_PCI_VENDOR = re.compile(r"^([0-9a-fA-F]{4})\s+(.+)$") +_RE_PCI_DEVICE = re.compile(r"^\t([0-9a-fA-F]{4})\s+(.+)$") +_RE_USB_VENDOR = re.compile(r"^([0-9a-fA-F]{4})\s+(.+)$") +_RE_USB_DEVICE = re.compile(r"^\t([0-9a-fA-F]{4})\s+(.+)$") + +# INF file vendor/device parsing +_INF_MANUFACTURER_RE = re.compile( + r'^%([^%]+)%\s*=\s*([^,]+)', re.IGNORECASE +) +_INF_STRINGS_SECTION = re.compile(r'^\[Strings\]', re.IGNORECASE) +_INF_STRING_RE = re.compile(r'^%([^%]+)%\s*=\s*"([^"]*)"') +_INF_VERSION_RE = re.compile( + r'^(DriverVer|DriverVersion|CatalogFile|Provider)\s*=\s*(.+)', + re.IGNORECASE, +) +_INF_MODEL_RE = re.compile( + r'^%([^%]+)%\s*=\s*\w+,\s*(PCI|USB|HDAUDIO|ACPI)\\([^,]+)', + re.IGNORECASE, +) + + +def _asdict(obj) -> dict[str, Any]: + """Recursive dataclass → dict, filtering None and empty containers.""" + if not is_dataclass(obj): + return obj + out = {} + for f in fields(obj): + val = getattr(obj, f.name) + if isinstance(val, list): + val = [_asdict(v) if is_dataclass(v) else v for v in val] + if not val: + continue + elif is_dataclass(val): + val = _asdict(val) + if not any(val.values()): + continue + elif val is None: + continue + out[f.name] = val + return out + + +def _run_cmd(cmd: list[str], timeout: int = 15) -> str | None: + """Run an external command, return stdout or None on failure.""" + try: + proc = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=timeout, + ) + if proc.returncode == 0: + return proc.stdout + return None + except (FileNotFoundError, subprocess.TimeoutExpired) as exc: + logger.debug("Command %s failed: %s", shlex.join(cmd), exc) + return None + + +# ── PCI ID Database ────────────────────────────────────────────────────────── + + +def _load_pci_ids() -> dict[str, dict[str, str]]: + """Parse ``pci.ids`` into ``{vendor: {device: name, '_name': vendor_name}}``. + + Each vendor dict has a ``_name`` key for the vendor's own name and + 4-hex-digit device ID keys mapping to human-readable device names. + """ + db: dict[str, dict[str, str]] = {} + current_vendor: str | None = None + for path in _PCI_IDS_PATHS: + if os.path.isfile(path): + break + else: + return db + + with open(path, encoding="utf-8", errors="replace") as fh: + for line in fh: + # Skip comments and blank lines + if not line or line[0] == "#" or line.strip() == "": + continue + m = _RE_PCI_VENDOR.match(line) + if m: + current_vendor = m.group(1).lower() + db[current_vendor] = {"_name": m.group(2).strip()} + continue + m = _RE_PCI_DEVICE.match(line) + if m and current_vendor: + dev_id = m.group(1).lower() + db[current_vendor][dev_id] = m.group(2).strip() + return db + + +def _load_usb_ids() -> dict[str, dict[str, str]]: + """Parse ``usb.ids`` into ``{vendor: {device: name, '_name': vendor_name}}``. + + Same structure as ``_load_pci_ids`` — 4-hex-digit vendor IDs mapping + to dicts of 4-hex-digit product IDs, plus a ``_name`` entry for the + vendor's own name. + """ + db: dict[str, dict[str, str]] = {} + current_vendor: str | None = None + for path in _USB_IDS_PATHS: + if os.path.isfile(path): + break + else: + return db + + with open(path, encoding="utf-8", errors="replace") as fh: + for line in fh: + if not line or line[0] == "#" or line.strip() == "": + continue + m = _RE_USB_VENDOR.match(line) + if m: + current_vendor = m.group(1).lower() + db[current_vendor] = {"_name": m.group(2).strip()} + continue + m = _RE_USB_DEVICE.match(line) + if m and current_vendor: + dev_id = m.group(1).lower() + db[current_vendor][dev_id] = m.group(2).strip() + return db + + +# ── PCI Device Scanner ─────────────────────────────────────────────────────── + + +def _parse_lspci_verbose(text: str) -> list[dict[str, Any]]: + """Parse detailed ``lspci -vvnn`` output into structured entries. + + Returns a list of dicts, one per PCI device, with keys: + + * ``slot`` — ``BB:DD.F`` + * ``class`` — device class name + * ``vendor`` — 4-hex vendor ID + * ``device`` — 4-hex device ID + * ``svendor`` — subsystem vendor (or empty) + * ``sdevice`` — subsystem device ID (or empty) + * ``rev`` — revision byte + * ``progif`` — programming interface byte + * ``description`` — short human label + """ + devices: list[dict[str, Any]] = [] + current: dict[str, Any] | None = None + + for line in text.splitlines(): + # Line starting with non-whitespace = new device header + # e.g. "00:02.0 VGA compatible controller [0300]: Intel Corporation [8086] ..." + if line and not line[0].isspace(): + if current is not None: + devices.append(current) + current = {} + parts = line.split(None, 2) + if len(parts) >= 2: + current["slot"] = parts[0].rstrip(":") + current["class"] = parts[1] + # Try to extract vendor:device from [xxxx:xxxx] pattern + bracket_ids = re.findall(r"\[([0-9a-fA-F]{4}):([0-9a-fA-F]{4})\]", line) + if bracket_ids: + current["vendor"] = bracket_ids[0][0].lower() + current["device"] = bracket_ids[0][1].lower() + # Subsystem vendor:device [xxxx:xxxx] + if len(bracket_ids) > 1: + current["svendor"] = bracket_ids[1][0].lower() + current["sdevice"] = bracket_ids[1][1].lower() + # Revision + rev_m = re.search(r"\(rev\s+([0-9a-fA-F]+)\)", line) + if rev_m: + current["rev"] = rev_m.group(1) + # ProgIF + prog_m = re.search(r"\(prog-if\s+([0-9a-fA-F]+)\)", line) + if prog_m: + current["progif"] = prog_m.group(1) + elif current is not None: + # Sub-lines: look for subsystem, flags, etc. + # e.g. "Subsystem: Dell [1028:07b5]" + sub_m = re.search( + r"Subsystem:\s+(.+?)\s+\[([0-9a-fA-F]{4}):([0-9a-fA-F]{4})\]", + line, + ) + if sub_m and "svendor" not in current: + current["svendor"] = sub_m.group(2).lower() + current["sdevice"] = sub_m.group(3).lower() + + if current is not None: + devices.append(current) + + return devices + + +# ── USB Device Scanner ─────────────────────────────────────────────────────── + + +def _parse_lsusb(text: str) -> list[dict[str, Any]]: + """Parse ``lsusb`` output into structured device entries. + + Each entry has: + * ``bus`` — bus number + * ``device`` — device number + * ``vendor`` — 4-hex vendor ID + * ``product`` — 4-hex product ID + * ``description`` — short human label + """ + devices: list[dict[str, Any]] = [] + for line in text.splitlines(): + m = re.match( + r"Bus\s+(\d+)\s+Device\s+(\d+):\s+ID\s+" + r"([0-9a-fA-F]{4}):([0-9a-fA-F]{4})\s+(.+)", + line, + ) + if m: + devices.append( + { + "bus": m.group(1), + "device_addr": m.group(2), + "vendor": m.group(3).lower(), + "product": m.group(4).lower(), + "description": m.group(5).strip(), + } + ) + return devices + + +# ── Windows Driver Store Scanner ────────────────────────────────────────────── + + +def _find_driver_store(mount_point: str) -> str | None: + """Locate the Windows DriverStore under *mount_point*. + + Checks the most common paths: + * ``C:/Windows/System32/DriverStore/FileRepository`` + * ``/mnt/c/Windows/System32/DriverStore/FileRepository`` + """ + candidates = [ + Path(mount_point) / "Windows" / "System32" / "DriverStore" / "FileRepository", + Path(mount_point) / "windows" / "system32" / "driverstore" / "filerepository", + ] + for cand in candidates: + if cand.is_dir(): + return str(cand) + return None + + +def _parse_inf_driver(inf_path: str) -> dict[str, Any]: + """Parse a single .inf file for driver metadata. + + Returns a dict with keys: *provider*, *driver_ver*, *date*, *hw_ids*. + Values default to empty strings / empty lists when not found. + """ + info: dict[str, Any] = { + "provider": "", + "driver_ver": "", + "date": "", + "hw_ids": [], + } + + try: + text = Path(inf_path).read_text(encoding="utf-8", errors="replace") + except OSError: + return info + + # Default provider from [Version] section or file header + provider = "" + driver_ver = "" + date = "" + + inside_strings = False + strings_map: dict[str, str] = {} + + for line in text.splitlines(): + # Track [Strings] section + if _INF_STRINGS_SECTION.match(line): + inside_strings = True + continue + if inside_strings and line.startswith("["): + inside_strings = False + + if inside_strings: + sm = _INF_STRING_RE.match(line) + if sm: + strings_map[sm.group(1).lower()] = sm.group(2) + continue # don't process string defs as model lines + + # [Version] or [Manufacturer] section entries + vm = _INF_VERSION_RE.match(line) + if vm: + key = vm.group(1).lower() + val = vm.group(2).strip() + if "provider" in key: + provider = val + elif "driverver" in key: + # "MM/DD/YYYY,version" or just version + date_ver = val.split(",") + if len(date_ver) == 2: + date = date_ver[0].strip() + driver_ver = date_ver[1].strip() + else: + driver_ver = val + + # Resolve strings in provider if it's a token + if provider.startswith("%") and provider.endswith("%"): + token = provider.strip("%").lower() + provider = strings_map.get(token, provider) + + info["provider"] = provider + info["driver_ver"] = driver_ver + info["date"] = date + + # Extract HW IDs from [Manufacturer] or [Models] sections + for line in text.splitlines(): + mm = _INF_MODEL_RE.match(line) + if mm: + hw_type = mm.group(2) + hw_id = mm.group(3) + info["hw_ids"].append(f"{hw_type}\\{hw_id}") + + return info + + +def _scan_driver_store(store_path: str) -> list[dict[str, Any]]: + """Scan a Windows DriverStore/FileRepository directory. + + Returns a list of driver entries, each with: + * ``dir_name`` — the repository subdirectory name + * ``inf_files`` — list of .inf files found + * ``parsed`` — structured metadata from the first .inf + """ + drivers: list[dict[str, Any]] = [] + repo = Path(store_path) + if not repo.is_dir(): + return drivers + + for entry in sorted(repo.iterdir()): + if not entry.is_dir(): + continue + infs = list(entry.glob("*.inf")) + if not infs: + continue + parsed = _parse_inf_driver(str(infs[0])) + drivers.append( + { + "dir_name": entry.name, + "inf_files": [str(p) for p in infs], + "parsed": parsed, + } + ) + + return drivers + + +# ── Dataclasses ────────────────────────────────────────────────────────────── + + +@dataclass +class PCIDeviceInfo: + """A single PCI device discovered on the bus.""" + + slot: str = "" + device_class: str = "" + vendor_id: str = "" + device_id: str = "" + vendor_name: str = "" + device_name: str = "" + subsystem_vendor_id: str = "" + subsystem_device_id: str = "" + revision: str = "" + progif: str = "" + driver_status: str = "unknown" # "present" | "missing" | "stale" | "unknown" + + +@dataclass +class USBDeviceInfo: + """A single USB device discovered on the bus.""" + + bus: str = "" + device_addr: str = "" + vendor_id: str = "" + product_id: str = "" + vendor_name: str = "" + product_name: str = "" + description: str = "" + driver_status: str = "unknown" + + +@dataclass +class WindowsDriverInfo: + """Metadata for a driver package found in the Windows DriverStore.""" + + dir_name: str = "" + inf_files: list[str] = field(default_factory=list) + provider: str = "" + driver_version: str = "" + date: str = "" + hw_ids: list[str] = field(default_factory=list) + + +@dataclass +class DriverReport: + """Complete driver inventory report output by ``DriverUpdater.run()``.""" + + pci_devices: list[PCIDeviceInfo] = field(default_factory=list) + usb_devices: list[USBDeviceInfo] = field(default_factory=list) + windows_drivers: list[WindowsDriverInfo] = field(default_factory=list) + windows_mount: str = "" + driver_count: int = 0 + device_count: int = 0 + missing_driver_count: int = 0 + errors: list[str] = field(default_factory=list) + + +# ── Main Class ─────────────────────────────────────────────────────────────── + + +class DriverUpdater: + """Scan all local hardware and cross-reference against Windows drivers. + + Call ``run()`` to collect the full inventory; subsequent calls return + the cached result. Use ``text_report()`` or ``to_json()`` for output. + + Parameters + ---------- + windows_mount : str + Path where a Windows partition is mounted + (e.g. ``"/mnt/c"`` or ``"/media/Windows"``). + Pass ``""`` or ``None`` to skip Windows driver-store scanning. + log_warnings : bool + If True, emit a Python log warning on each non-fatal probing issue. + """ + + def __init__( + self, + windows_mount: str | None = None, + log_warnings: bool = False, + ) -> None: + self._report: Optional[DriverReport] = None + self._windows_mount = (windows_mount or "").rstrip("/") + self._log_warnings = log_warnings + self._pci_db: dict[str, dict[str, str]] = {} + self._usb_db: dict[str, dict[str, str]] = {} + + # ── Public API ────────────────────────────────────────────────────────── + + def run(self) -> dict[str, Any]: + """Run the full scan and return a JSON-serialisable dict.""" + report = self._probe() + self._report = report + return _asdict(report) + + def text_report(self) -> str: + """Return a human-readable summary of the last ``run()`` result. + + Returns a fallback message when ``run()`` has not been called yet. + """ + if self._report is None: + return self._fallback_summary() + + r = self._report + lines: list[str] = [] + + lines.append("=" * 60) + lines.append(" DRIVER INVENTORY REPORT") + lines.append("=" * 60) + + lines.append("") + lines.append(f" Total devices: {r.device_count}") + lines.append(f" Missing drivers: {r.missing_driver_count}") + lines.append(f" Windows drivers in store: {r.driver_count}") + + if r.windows_mount: + lines.append(f" Windows mount: {r.windows_mount}") + else: + lines.append(" Windows mount: (none — Linux-only scan)") + + if r.pci_devices: + lines.append("") + lines.append("-- PCI DEVICES --") + for d in r.pci_devices: + name = d.device_name or d.device_id + vendor = d.vendor_name or d.vendor_id + icon = self._driver_icon(d.driver_status) + lines.append( + f" {icon} {d.slot} {vendor} {name}" + ) + + if r.usb_devices: + lines.append("") + lines.append("-- USB DEVICES --") + for d in r.usb_devices: + name = d.product_name or d.description or d.product_id + icon = self._driver_icon(d.driver_status) + lines.append( + f" {icon} Bus {d.bus} Dev {d.device_addr} {name}" + ) + + if r.windows_drivers: + lines.append("") + lines.append("-- WINDOWS DRIVER STORE PACKAGES --") + for d in r.windows_drivers: + ver = f" v{d.driver_version}" if d.driver_version else "" + prov = f" by {d.provider}" if d.provider else "" + lines.append(f" • {d.dir_name}{ver}{prov}") + + missing = [d for d in r.pci_devices if d.driver_status == "missing"] + if missing: + lines.append("") + lines.append("-- DEVICES WITHOUT MATCHING DRIVER --") + for d in missing: + name = d.device_name or d.device_id + vid = d.vendor_name or d.vendor_id + lines.append(f" !! {d.slot} {vid} {name}") + + if r.errors: + lines.append("") + lines.append("-- WARNINGS --") + for err in r.errors: + lines.append(f" !! {err}") + + lines.append("") + lines.append("=" * 60) + + return "\n".join(lines) + + def to_json(self, indent: int = 2) -> str: + """Return the full report as a JSON string.""" + return json.dumps(self.run(), indent=indent, default=str) + + def devices_without_drivers(self) -> dict[str, Any]: + """Convenience: return only devices whose driver_status is "missing".""" + if self._report is None: + return {} + r = self._report + missing_pci = [d for d in r.pci_devices if d.driver_status == "missing"] + missing_usb = [d for d in r.usb_devices if d.driver_status == "missing"] + return _asdict( + DriverReport( + pci_devices=missing_pci, + usb_devices=missing_usb, + windows_mount=r.windows_mount, + driver_count=r.driver_count, + device_count=len(missing_pci) + len(missing_usb), + missing_driver_count=len(missing_pci) + len(missing_usb), + errors=r.errors, + ) + ) + + # ── Internal ──────────────────────────────────────────────────────────── + + def _probe(self) -> DriverReport: + report = DriverReport() + err = report.errors + + # Load hardware ID databases + self._pci_db = _load_pci_ids() + self._usb_db = _load_usb_ids() + + # Scan PCI devices + pci_devices = self._scan_pci(err) + report.pci_devices = pci_devices + + # Scan USB devices + usb_devices = self._scan_usb(err) + report.usb_devices = usb_devices + + # Scan Windows driver store + if self._windows_mount: + store = _find_driver_store(self._windows_mount) + if store: + raw_drivers = _scan_driver_store(store) + report.windows_drivers = self._parse_driver_infos(raw_drivers) + report.windows_mount = self._windows_mount + report.driver_count = len(report.windows_drivers) + self._cross_reference(report) + else: + msg = ( + f"Windows mount '{self._windows_mount}' found but " + f"DriverStore/FileRepository not detected" + ) + err.append(msg) + if self._log_warnings: + logger.warning("driver updater: %s", msg) + else: + report.driver_count = 0 + + report.device_count = len(pci_devices) + len(usb_devices) + report.missing_driver_count = sum( + 1 for d in pci_devices if d.driver_status == "missing" + ) + sum( + 1 for d in usb_devices if d.driver_status == "missing" + ) + + if self._log_warnings and err: + for e in err: + logger.warning("driver updater: %s", e) + + return report + + def _scan_pci( + self, errors: list[str] + ) -> list[PCIDeviceInfo]: + """Run ``lspci`` and return structured device list.""" + out = _run_cmd(["lspci", "-vvnn"]) + if out is None: + out = _run_cmd(["lspci", "-vnn"]) + if out is None: + out = _run_cmd(["lspci"]) + if out is None: + errors.append("lspci not available — cannot enumerate PCI devices") + return [] + + raw = _parse_lspci_verbose(out) + devices: list[PCIDeviceInfo] = [] + for entry in raw: + vid = entry.get("vendor", "") + did = entry.get("device", "") + vendor_name = ( + self._pci_db.get(vid, {}).get("_name", "") + if vid in self._pci_db + else "" + ) or "" + device_name = ( + self._pci_db.get(vid, {}).get(did, "") + if vid in self._pci_db and did + else "" + ) or "" + + devices.append( + PCIDeviceInfo( + slot=entry.get("slot", ""), + device_class=entry.get("class", ""), + vendor_id=vid, + device_id=did, + vendor_name=vendor_name, + device_name=device_name, + subsystem_vendor_id=entry.get("svendor", ""), + subsystem_device_id=entry.get("sdevice", ""), + revision=entry.get("rev", ""), + progif=entry.get("progif", ""), + driver_status="unknown", + ) + ) + return devices + + def _scan_usb( + self, errors: list[str] + ) -> list[USBDeviceInfo]: + """Run ``lsusb`` and return structured device list.""" + out = _run_cmd(["lsusb"]) + if out is None: + errors.append("lsusb not available — cannot enumerate USB devices") + return [] + + raw = _parse_lsusb(out) + devices: list[USBDeviceInfo] = [] + for entry in raw: + vid = entry.get("vendor", "") + pid = entry.get("product", "") + vendor_name = ( + self._usb_db.get(vid, {}).get("_name", "") + if vid in self._usb_db + else "" + ) or "" + product_name = ( + self._usb_db.get(vid, {}).get(pid, "") + if vid in self._usb_db and pid + else "" + ) or "" + + devices.append( + USBDeviceInfo( + bus=entry.get("bus", ""), + device_addr=entry.get("device_addr", ""), + vendor_id=vid, + product_id=pid, + vendor_name=vendor_name, + product_name=product_name, + description=entry.get("description", ""), + driver_status="unknown", + ) + ) + return devices + + def _parse_driver_infos( + self, raw: list[dict[str, Any]] + ) -> list[WindowsDriverInfo]: + """Wrap raw driver-store entries into dataclass objects.""" + result: list[WindowsDriverInfo] = [] + for entry in raw: + parsed = entry.get("parsed", {}) + result.append( + WindowsDriverInfo( + dir_name=entry.get("dir_name", ""), + inf_files=entry.get("inf_files", []), + provider=parsed.get("provider", ""), + driver_version=parsed.get("driver_ver", ""), + date=parsed.get("date", ""), + hw_ids=parsed.get("hw_ids", []), + ) + ) + return result + + def _cross_reference(self, report: DriverReport) -> None: + """Match PCI and USB devices against installed Windows drivers. + + For each device, if any Windows driver's HWIDs contain the PCI + vendor:device pair, mark it as "present"; otherwise mark + "missing". + """ + # Build a set of known hw_ids from the Windows driver store + known_hwids: set[str] = set() + for drv in report.windows_drivers: + for hid in drv.hw_ids: + known_hwids.add(hid.upper()) + + for dev in report.pci_devices: + # Build candidate HWIDs the device would match + candidates = [ + f"PCI\\VEN_{dev.vendor_id.upper()}&DEV_{dev.device_id.upper()}", + f"PCI\\VEN_{dev.vendor_id.upper()}&DEV_{dev.device_id.upper()}&SUBSYS_{dev.subsystem_vendor_id.upper()}{dev.subsystem_device_id.upper()}", + f"PCI\\VEN_{dev.vendor_id.upper()}&DEV_{dev.device_id.upper()}&REV_{dev.revision.upper()}" if dev.revision else "", + ] + candidates = [c for c in candidates if c] + if any(c in known_hwids for c in candidates): + dev.driver_status = "present" + else: + dev.driver_status = "missing" + + for dev in report.usb_devices: + candidates = [ + f"USB\\VID_{dev.vendor_id.upper()}&PID_{dev.product_id.upper()}", + ] + if any(c in known_hwids for c in candidates): + dev.driver_status = "present" + else: + dev.driver_status = "missing" + + def _driver_icon(self, status: str) -> str: + """Return a visual icon for the device driver status.""" + return { + "present": "✓", + "missing": "✗", + "stale": "△", + "unknown": "·", + }.get(status, "?") + + def _fallback_summary(self) -> str: + """Return basic system info when no scan has been run yet.""" + lines = [ + "DriverUpdater -- no scan results yet.", + "Call .run() first to collect data.", + "", + ] + try: + lines.append(f"Python: {sys.version.split()[0]}") + lines.append(f"Platform: {sys.platform}") + uname = os.uname() + lines.append(f"Kernel: {uname.sysname} {uname.release} {uname.machine}") + except Exception: + pass + if self._windows_mount: + lines.append(f"Windows mount: {self._windows_mount}") + return "\n".join(lines) + + +# ── CLI Entry Point ────────────────────────────────────────────────────────── + + +def main() -> int: + """Command-line entry point for scanning drivers. + + Usage:: + + python -m diagnostics.drivers [--mount /mnt/c] [--json] [--missing-only] + """ + import argparse + + parser = argparse.ArgumentParser( + description="Hermes Portable Rescue — Driver Scanner", + ) + parser.add_argument( + "--mount", + default="", + help="Path to mounted Windows partition (e.g. /mnt/c)", + ) + parser.add_argument( + "--json", + action="store_true", + help="Output JSON instead of human-readable report", + ) + parser.add_argument( + "--missing-only", + action="store_true", + help="Show only devices without matching drivers", + ) + parser.add_argument( + "--verbose", "-v", + action="store_true", + help="Enable verbose (debug) logging", + ) + args = parser.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + + updater = DriverUpdater( + windows_mount=args.mount or None, + log_warnings=args.verbose, + ) + + if args.missing_only: + result = updater.run() + missing = updater.devices_without_drivers() + if args.json: + print(json.dumps(missing, indent=2, default=str)) + else: + report = DriverReport(**missing) + updater._report = report + print(updater.text_report()) + return 0 if missing.get("missing_driver_count", 0) == 0 else 1 + + if args.json: + print(updater.to_json()) + else: + updater.run() + print(updater.text_report()) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/diagnostics/hardware.py b/src/diagnostics/hardware.py index 50a69f5..4dbfc43 100644 --- a/src/diagnostics/hardware.py +++ b/src/diagnostics/hardware.py @@ -159,19 +159,28 @@ def _run(cmd: list[str], timeout: float = 15.0) -> str: def _parse_int(value: str | None) -> Optional[int]: if value is None: return None - try: - return int(value) - except ValueError: - return None + # Extract leading number from strings like "3200 MT/s" + value = value.strip() + m = re.match(r"(\d+)", value) + if m: + try: + return int(m.group(1)) + except ValueError: + return None + return None def _parse_float(value: str | None) -> Optional[float]: if value is None: return None - try: - return float(value) - except ValueError: - return None + value = value.strip() + m = re.match(r"(\d+(?:\.\d+)?)", value) + if m: + try: + return float(m.group(1)) + except ValueError: + return None + return None def _find_sys_block() -> list[Path]: @@ -581,3 +590,34 @@ class HardwareInventory: except Exception: pass return "\n".join(lines) + + +# ------------------------------------------------------------------- +# Run as script: python -m src.diagnostics.hardware +# ------------------------------------------------------------------- + + +def _main() -> None: + """Run the hardware inventory and print the report to stdout.""" + import argparse + + parser = argparse.ArgumentParser(description="Hardware inventory & health checks") + parser.add_argument("--json", action="store_true", help="Output JSON instead of text report") + parser.add_argument("--verbose", "-v", action="store_true", help="Log warnings during probing") + args = parser.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.WARNING) + + hw = HardwareInventory(log_warnings=args.verbose) + hw.run() + + if args.json: + print(hw.to_json()) + else: + print(hw.text_report()) + + +if __name__ == "__main__": + _main() + diff --git a/src/diagnostics/stress.py b/src/diagnostics/stress.py index 9e89169..096812f 100644 --- a/src/diagnostics/stress.py +++ b/src/diagnostics/stress.py @@ -817,6 +817,7 @@ class GPUStressTester: component="gpu", backend="+".join(backend_parts), status=status, + duration_seconds=0.0, # caller overwrites this with actual wall time summary="; ".join(summary_parts), details=details, errors=all_errors, @@ -874,21 +875,15 @@ def run_all_stress_tests( def print_report(results: Dict[str, StressResult]) -> None: """Pretty-print test results to stdout.""" - for component in ("ram", "cpu", "gpu"): + testers = { + "ram": RAMStressTester, + "cpu": CPUStressTester, + "gpu": GPUStressTester, + } + for component, tester_cls in testers.items(): result = results.get(component) if result is not None: - print() - print(StressResult.report.__doc__) # placeholder — will be replaced - # Actually use the testers' report methods - for component in ("ram", "cpu", "gpu"): - result = results.get(component) - if result is not None: - if component == "ram": - print(RAMStressTester().report(result)) - elif component == "cpu": - print(CPUStressTester().report(result)) - elif component == "gpu": - print(GPUStressTester().report(result)) + print(tester_cls().report(result)) print() diff --git a/src/diagnostics/tests/test_hardware.py b/src/diagnostics/tests/test_hardware.py new file mode 100644 index 0000000..1332128 --- /dev/null +++ b/src/diagnostics/tests/test_hardware.py @@ -0,0 +1,515 @@ +"""Tests for the hardware inventory module. + +Run with:: + + cd ~/nas-projects/hermes-portable-rescue + python -m pytest src/diagnostics/tests/test_hardware.py -v +""" + +import json +import sys +from pathlib import Path +from unittest.mock import patch + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + +from src.diagnostics.hardware import ( + HardwareInventory, + HardwareReport, + CPUInfo, + RAMInfo, + RAMModule, + GPUInfo, + DiskInfo, + DiskSMART, + _asdict, + _find_sys_block, + get_cpu_info, + get_ram_info, + get_gpu_info, + get_disk_info, +) + + +# --------------------------------------------------------------------------- +# Dataclass helpers +# --------------------------------------------------------------------------- + + +class TestAsdict: + """Verify that _asdict correctly filters empty/Nones from dataclasses.""" + + def test_filters_empty_strings(self): + info = CPUInfo() + d = _asdict(info) + # Empty strings ARE included (they are valid distinct-from-None values) + assert "model" in d + assert d["model"] == "" + + def test_filters_none_values(self): + info = CPUInfo(model="Test CPU", max_speed_mhz=None) + d = _asdict(info) + assert "max_speed_mhz" not in d + assert d["model"] == "Test CPU" + + def test_preserves_zero_and_false(self): + info = CPUInfo(cores=0, threads=0) + d = _asdict(info) + assert d["cores"] == 0 + assert d["threads"] == 0 + + def test_includes_non_empty_fields(self): + info = CPUInfo(model="Intel i7", cores=8, threads=16) + d = _asdict(info) + assert d["model"] == "Intel i7" + assert d["cores"] == 8 + assert d["threads"] == 16 + + def test_nested_dataclasses(self): + mod = RAMModule(size_gb=8, type="DDR4", slot="DIMM_A1") + ram = RAMInfo(total_gb=8, slots_used=1, slots_total=2, modules=[mod]) + d = _asdict(ram) + assert d["total_gb"] == 8 + assert len(d["modules"]) == 1 + assert d["modules"][0]["size_gb"] == 8 + + def test_empty_modules_list_filtered(self): + ram = RAMInfo(total_gb=0, slots_used=0, slots_total=0, modules=[]) + d = _asdict(ram) + assert "modules" not in d # empty list filtered + # All zero fields become empty dict after filtering + assert "total_gb" in d and d["total_gb"] == 0 + + +# --------------------------------------------------------------------------- +# HardwareInventory class +# --------------------------------------------------------------------------- + + +class TestHardwareInventory: + """Integration-style tests for the main HardwareInventory class.""" + + def test_run_returns_dict(self): + hw = HardwareInventory() + result = hw.run() + assert isinstance(result, dict) + assert "cpu" in result + assert "disks" in result + assert "errors" in result + + def test_run_includes_cpu(self): + hw = HardwareInventory() + result = hw.run() + cpu = result.get("cpu", {}) + # On *any* real or virtual machine, at minimum architecture is detected + assert isinstance(cpu, dict) + assert "architecture" in cpu # always present on Linux + + def test_text_report_after_run(self): + hw = HardwareInventory() + hw.run() + report = hw.text_report() + assert "HARDWARE INVENTORY REPORT" in report + assert "-- CPU --" in report + + def test_text_report_before_run_fallback(self): + hw = HardwareInventory() + report = hw.text_report() + assert "no probe results yet" in report + + def test_to_json(self): + hw = HardwareInventory() + hw.run() + j = hw.to_json() + parsed = json.loads(j) + assert "cpu" in parsed + assert "disks" in parsed + + def test_log_warnings_silent_by_default(self): + hw = HardwareInventory() + result = hw.run() + # Should not crash; errors collected silently + assert isinstance(result["errors"], list) + + def test_double_run_returns_same_structure(self): + hw = HardwareInventory() + r1 = hw.run() + r2 = hw.run() + assert r1.keys() == r2.keys() + + +# --------------------------------------------------------------------------- +# CPU collector (mock-based) +# --------------------------------------------------------------------------- + + +class TestGetCpuInfo: + """Test CPU collector with mocked lscpu output.""" + + LSCPU_OUTPUT = """\ +Architecture: x86_64 +CPU op-mode(s): 32-bit, 64-bit +Address sizes: 39 bits physical, 48 bits virtual +Byte Order: Little Endian +CPU(s): 12 +On-line CPU(s) list: 0-11 +Vendor ID: GenuineIntel +Model name: Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz +CPU family: 6 +Model: 158 +Thread(s) per core: 2 +Core(s) per socket: 6 +Socket(s): 1 +Stepping: 10 +CPU max MHz: 4700.0000 +CPU min MHz: 800.0000 +L1d cache: 32K +L1i cache: 32K +L2 cache: 256K +L3 cache: 12288K +Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single pti ssbd ibrs ibpb stibp tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx rdseed adx smap clflushopt intel_pt xsaveopt xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp md_clear flush_l1d arch_capabilities +""" + + def test_parses_lscpu_output(self): + errors: list[str] = [] + with patch( + "src.diagnostics.hardware._run", + return_value=self.LSCPU_OUTPUT, + ): + info = get_cpu_info(errors) + + assert info.model == "Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz" + assert info.architecture == "x86_64" + assert info.cores == 6 + assert info.threads == 12 + assert info.max_speed_mhz == 4700 + assert info.min_speed_mhz == 800 + assert info.cache_l2_kb == 256 + assert info.cache_l3_kb == 12288 + assert len(info.flags) > 20 + assert "ssse3" in info.flags + assert "avx2" in info.flags + + @patch("src.diagnostics.hardware._run", return_value="") + def test_fallback_to_proc_cpuinfo(self, mock_run): + errors: list[str] = [] + + proc_cpuinfo = """\ +processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +model : 158 +model name : Intel(R) Core(TM) i5-8400 CPU @ 2.80GHz +stepping : 10 +core id : 0 +cpu cores : 6 + +processor : 1 +core id : 1 + +processor : 2 +core id : 2 + +processor : 3 +core id : 3 + +processor : 4 +core id : 4 + +processor : 5 +core id : 5 +""" + + with patch.object( + Path, + "read_text", + return_value=proc_cpuinfo, + ): + info = get_cpu_info(errors) + + assert info.model == "Intel(R) Core(TM) i5-8400 CPU @ 2.80GHz" + assert info.cores == 6 + assert info.threads == 6 # no hyperthreading + + def test_empty_lscpu_returns_defaults(self): + errors: list[str] = [] + with patch("src.diagnostics.hardware._run", return_value=""): + with patch.object(Path, "read_text", side_effect=OSError): + info = get_cpu_info(errors) + assert info.model == "" + assert info.cores == 0 + + +# --------------------------------------------------------------------------- +# RAM collector (mock-based) +# --------------------------------------------------------------------------- + + +class TestGetRamInfo: + """Test RAM collector with mocked dmidecode output.""" + + DMIDECODE_TYPE17 = """\ +# dmidecode 3.5 +Getting SMBIOS data from sysfs. +SMBIOS 3.3.0 present. + +Handle 0x0013, DMI type 17, 40 bytes +Memory Device + Array Handle: 0x0012 + Error Information Handle: Not Provided + Total Width: 64 bits + Data Width: 64 bits + Size: 16384 MB + Form Factor: DIMM + Set: None + Locator: DIMM_A1 + Bank Locator: P0 CHANNEL A + Type: DDR4 + Type Detail: Synchronous Unbuffered (Unbuffered) + Speed: 3200 MT/s + Manufacturer: Kingston + Serial Number: 1234ABCD + Asset Tag: None + Part Number: KHX3200C16D4/16G + Rank: 2 + Configured Memory Speed: 3200 MT/s + Minimum Voltage: 1.2 V + Maximum Voltage: 1.2 V + Configured Voltage: 1.2 V + +Handle 0x0014, DMI type 17, 40 bytes +Memory Device + Array Handle: 0x0012 + Error Information Handle: Not Provided + Total Width: 64 bits + Data Width: 64 bits + Size: 16384 MB + Form Factor: DIMM + Set: None + Locator: DIMM_A2 + Bank Locator: P0 CHANNEL A + Type: DDR4 + Type Detail: Synchronous Unbuffered (Unbuffered) + Speed: 3200 MT/s + Manufacturer: Kingston + Serial Number: 5678EFGH + Asset Tag: None + Part Number: KHX3200C16D4/16G + Rank: 2 + Configured Memory Speed: 3200 MT/s + Minimum Voltage: 1.2 V + Maximum Voltage: 1.2 V + Configured Voltage: 1.2 V + +Handle 0x0015, DMI type 17, 40 bytes +Memory Device + Array Handle: 0x0012 + Error Information Handle: Not Provided + Total Width: Unknown + Data Width: Unknown + Size: No Module Installed + Form Factor: DIMM + Set: None + Locator: DIMM_B1 + Bank Locator: P0 CHANNEL B + Type: Unknown + Type Detail: Unknown + +Handle 0x0016, DMI type 17, 40 bytes +Memory Device + Array Handle: 0x0012 + Error Information Handle: Not Provided + Total Width: Unknown + Data Width: Unknown + Size: No Module Installed + Form Factor: DIMM + Set: None + Locator: DIMM_B2 + Bank Locator: P0 CHANNEL B + Type: Unknown + Type Detail: Unknown +""" + + DMIDECODE_TYPE16 = """\ +# dmidecode 3.5 +Getting SMBIOS data from sysfs. +SMBIOS 3.3.0 present. + +Handle 0x0012, DMI type 16, 23 bytes +Physical Memory Array + Location: System Board Or Motherboard + Use: System Memory + Error Correction Type: None + Maximum Capacity: 64 GB + Error Information Handle: Not Provided + Number Of Devices: 4 +""" + + def test_parses_dmidecode_output(self): + errors: list[str] = [] + + def mock_run(cmd, **kw): + if "--type" in cmd: + if "17" in cmd: + return self.DMIDECODE_TYPE17 + if "16" in cmd: + return self.DMIDECODE_TYPE16 + return "" + + with patch("src.diagnostics.hardware._run", side_effect=mock_run): + info = get_ram_info(errors) + + assert info.total_gb == 32.0 + assert info.slots_used == 2 + assert info.slots_total == 4 + assert len(info.modules) == 2 + + m1 = info.modules[0] + assert m1.size_gb == 16.0 + assert m1.type == "DDR4" + assert m1.speed_mhz == 3200 + assert m1.slot == "DIMM_A1" + assert m1.manufacturer == "Kingston" + assert m1.part_number == "KHX3200C16D4/16G" + assert m1.serial == "1234ABCD" + assert m1.voltage_v == 1.2 + + m2 = info.modules[1] + assert m2.size_gb == 16.0 + assert m2.slot == "DIMM_A2" + + @patch("src.diagnostics.hardware._run", return_value="") + def test_no_dmidecode_returns_defaults(self, mock_run): + errors: list[str] = [] + info = get_ram_info(errors) + assert info.total_gb == 0.0 + assert info.slots_used == 0 + assert len(errors) == 1 + assert "dmidecode" in errors[0] + + def test_with_only_size_data(self): + """Only Size is mandatory; everything else may be blank.""" + errors: list[str] = [] + minimal = """\ +Handle 0x0013, DMI type 17, 40 bytes +Memory Device + Size: 8192 MB + Locator: DIMM_A1 + Type: DDR4 + Speed: 2666 +""" + with patch("src.diagnostics.hardware._run", return_value=minimal): + info = get_ram_info(errors) + assert len(info.modules) == 1 + assert info.modules[0].size_gb == 8.0 + assert info.modules[0].speed_mhz == 2666 + + +# --------------------------------------------------------------------------- +# GPU collector (mock-based) +# --------------------------------------------------------------------------- + + +class TestGetGpuInfo: + """Test GPU collector with mocked lspci / lshw output.""" + + LSPCI_MM = ( + 'pci@0000:01:00.0 "VGA compatible controller" "NVIDIA Corporation"' + ' "GA104 [GeForce RTX 3080 Lite Hash Rate]"' + "\n" + 'pci@0000:00:02.0 "VGA compatible controller" "Intel Corporation"' + ' "CometLake-S GT2 [UHD Graphics 630]"' + "\n" + 'pci@0000:00:1f.6 "Ethernet controller" "Intel Corporation"' + ' "Ethernet Connection (12) I219-V"' + "\n" + ) + + def test_parses_lspci_output(self): + errors: list[str] = [] + with patch("src.diagnostics.hardware._run", return_value=self.LSPCI_MM): + gpus = get_gpu_info(errors) + + assert len(gpus) == 2 + assert gpus[0].model == "GA104 [GeForce RTX 3080 Lite Hash Rate]" + assert gpus[0].vendor == "NVIDIA Corporation" + assert gpus[0].pci_slot == "0000:01:00.0" + assert gpus[1].model == "CometLake-S GT2 [UHD Graphics 630]" + assert gpus[1].vendor == "Intel Corporation" + + @patch("src.diagnostics.hardware._run", return_value="") + def test_no_lspci_returns_empty(self, mock_run): + errors: list[str] = [] + gpus = get_gpu_info(errors) + assert gpus == [] + assert len(errors) == 1 + assert "lspci" in errors[0] + + +# --------------------------------------------------------------------------- +# Disk collector (mock-based) +# --------------------------------------------------------------------------- + + +class TestGetDiskInfo: + """Test disk collector with mocked sysfs structure.""" + + def test_find_sys_block_excludes_virtual(self): + """Verify _find_sys_block filters virtual devices.""" + devices = _find_sys_block() + for d in devices: + assert not d.name.startswith(("ram", "loop", "dm-", "zram", "sr", "md")) + + def test_empty_sys_block_returns_empty(self): + errors: list[str] = [] + with patch("src.diagnostics.hardware._find_sys_block", return_value=[]): + disks = get_disk_info(errors) + assert disks == [] + assert len(errors) == 1 + + def test_smartctl_unavailable_logs_warning(self): + """When smartctl fails, disk still appears with UNKNOWN SMART status.""" + errors: list[str] = [] + + # Build a fake sysfs entry using real Path objects with mocked methods + fake_dev = Path("/sys/block/sda") + fake_dev + + def fake_exists(self) -> bool: + path = str(self) + return path in ( + "/sys/block/sda/device", + "/sys/block/sda/size", + "/sys/block/sda/removable", + "/sys/block/sda/queue/rotational", + "/sys/block/sda/device/model", + "/sys/block/sda/device/serial", + ) + + def fake_read_text(self, *a, **kw) -> str: + mapping = { + "/sys/block/sda/size": "3907029168", + "/sys/block/sda/removable": "0", + "/sys/block/sda/queue/rotational": "1", + "/sys/block/sda/device/model": "WDC WD2003FYYS-02W0B1", + "/sys/block/sda/device/serial": "WD-WCAY01234567", + } + return mapping.get(str(self), "") + + with ( + patch("src.diagnostics.hardware._find_sys_block", return_value=[fake_dev]), + patch.object(Path, "exists", fake_exists), + patch.object(Path, "read_text", fake_read_text), + patch("src.diagnostics.hardware._run", return_value=""), + ): + disks = get_disk_info(errors) + + assert len(disks) == 1 + assert disks[0].device == "/dev/sda" + assert disks[0].smart.model == "WDC WD2003FYYS-02W0B1" + assert disks[0].smart.serial == "WD-WCAY01234567" + assert disks[0].smart.size_gb == pytest.approx(1863.0, rel=0.1) + assert disks[0].smart.interface == "HDD" + assert disks[0].smart.status == "UNKNOWN" # smartctl unavailable diff --git a/src/hermes/autorun.sh b/src/hermes/autorun.sh index 8075cb1..988e6e8 100755 --- a/src/hermes/autorun.sh +++ b/src/hermes/autorun.sh @@ -155,10 +155,10 @@ run_diagnostics() { info "Stress tests disabled (enable in config or set RUN_STRESS_TESTS=1)" fi - # Phase 5: Backup/restore (manual activation) - if [ -n "${RUN_BACKUP}" ]; then - run_script "${HERMES_DIR}/scripts/backup.py" "05-backup.txt" - fi + # Phase 5: Backup/restore (manual activation) — stub placeholder + # if [ -n "${RUN_BACKUP}" ]; then + # run_script "${HERMES_DIR}/scripts/backup.py" "05-backup.txt" + # fi echo "───────────────────────────────────────────────────────" >> "${LOG_FILE}" info "Diagnostic pipeline complete." diff --git a/src/hermes/config.yaml b/src/hermes/config.yaml index 40dbffa..75c90c6 100644 --- a/src/hermes/config.yaml +++ b/src/hermes/config.yaml @@ -1,55 +1,75 @@ -# Hermes Portable Rescue — Agent Configuration -# This file is deployed onto the USB and read by Hermes Agent at startup. -# Generated by src/build-hermes-runtime.sh +# Hermes Portable Rescue — ISO Config +# Used by both the autorun diagnostic pipeline and Hermes Agent + +# --- Hermes Agent config (standard format) --- +model: + default: deepseek-v4-flash + provider: opencode-go + base_url: https://opencode.ai/zen/go/v1 + providers: + opencode-go: + api_key: "" + base_url: https://opencode.ai/zen/go/v1 + lmstudio: + api_key: not-needed + base_url: http://127.0.0.1:11434/v1 + fallback_providers: + - lmstudio + credential_pool_strategies: + opencode-go: + fallback: [] + +credential_pool_strategies: + opencode-go: fill_first agent: name: "hermes-portable-rescue" - version: "1.0.0" - mode: "rescue" # rescue mode: diagnostic-first, no persistent chat + max_turns: 30 + tool_use_enforcement: auto logging: - level: "INFO" - file: "/tmp/hermes-rescue.log" - max_size_mb: 10 - backup_count: 3 + level: INFO + max_size_mb: 5 + backup_count: 1 -# Provider: uses local LLM if available, otherwise API fallback -llm: - mode: "auto" # auto = prefer local, fallback to api - local: - model_path: "${HERMES_ROOT}/models/q4_0.gguf" - context_size: 4096 - threads: 4 - api: - provider: "openrouter" - model: "openai/gpt-4o-mini" - timeout_seconds: 30 - health_check: - max_retries: 2 - fallback_to_local: true +display: + personality: concise + language: en + compact: true +auxiliary: + compression: + provider: auto + model: "" + vision: + provider: auto + model: "" + +memory: + memory_enabled: true + user_profile_enabled: false + memory_char_limit: 1000 + +network: + probe_on_start: true + timeout_seconds: 10 + +# --- Rescue diagnostic pipeline config (read by autorun.sh) --- rescue: - # Diagnostic sequence — ordered by dependency diagnostic_sequence: - - hardware_inventory # gather system specs first - - disk_health # SMART checks - - memory_test # RAM stress - - bsod_analyzer # minidump parsing - - cpu_gpu_stress # thermal/load tests - - driver_check # driver inventory - - # Storage paths on the USB + - hardware_inventory + - disk_health + - memory_test + - bsod_analyzer + - cpu_gpu_stress + - driver_check paths: tools: "${HERMES_ROOT}/tools" scripts: "${HERMES_ROOT}/scripts" models: "${HERMES_ROOT}/models" diagnostics: "${HERMES_ROOT}/diagnostics" reports: "/tmp/hermes-reports" - - # Auto-proceed — run the diagnostic sequence on boot without user input auto_diagnose: true - - # Report output report: format: "markdown" output_dir: "/tmp/hermes-reports" @@ -58,8 +78,3 @@ rescue: tools: manifest: "${HERMES_ROOT}/tool-manifest.json" timeout_seconds: 300 - -network: - # Optional: API-based LLM fallback needs network - probe_on_start: true - timeout_seconds: 10