Compare commits

..

3 Commits

Author SHA1 Message Date
Julian Freeman
4be0fdcb74 more preset bsod 2025-11-27 09:18:42 -04:00
Julian Freeman
93164a86d8 output change 2025-11-27 09:03:02 -04:00
Julian Freeman
0ff1014e62 fix bsod 2025-11-27 08:50:18 -04:00
6 changed files with 117 additions and 76 deletions

11
src-tauri/Cargo.lock generated
View File

@@ -872,15 +872,6 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "encoding_rs_io"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83"
dependencies = [
"encoding_rs",
]
[[package]]
name = "endi"
version = "1.1.0"
@@ -3718,8 +3709,6 @@ version = "0.1.0"
dependencies = [
"chrono",
"csv",
"encoding_rs",
"encoding_rs_io",
"minidump",
"serde",
"serde_json",

View File

@@ -1,19 +1,12 @@
[package]
name = "system-doctor"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
description = "System diagnosis"
authors = ["Julian"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "system_doctor_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
@@ -27,11 +20,7 @@ wmi = "0.13"
chrono = { version = "0.4", features = ["serde"] }
tokio = { version = "1", features = ["full"] }
minidump = "0.19"
# [新增] 用于解析 BlueScreenView 导出的 CSV
csv = "1.3"
# [新增] 用于处理可能的文件编码问题
encoding_rs = "0.8"
encoding_rs_io = "0.1"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem

View File

@@ -1,14 +0,0 @@
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -1,5 +1,3 @@
// src-tauri/src/main.rs
// 全局允许非标准命名风格
#![allow(non_camel_case_types, non_snake_case)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
@@ -9,6 +7,7 @@ use sysinfo::{System, Disks};
use wmi::{COMLibrary, WMIConnection};
use std::fs;
use std::path::Path;
// [修复] 移除了未使用的 Write
use std::io::Read;
use std::process::Command;
use tauri::Emitter;
@@ -131,61 +130,112 @@ fn format_wmi_time(wmi_str: &str) -> String {
}
}
// [扩展] 翻译 BugCheck Code (u32) - 增加了更多常见代码
fn translate_bugcheck_u32(code: u32) -> (String, String) {
match code {
// 驱动与内存相关
0x0000000A => (
"IRQL_NOT_LESS_OR_EQUAL (0x0A)".to_string(),
"驱动程序使用了不正确的内存地址。通常由有缺陷的驱动程序(如杀毒软件、虚拟光驱驱动)或硬件兼容性问题引起。建议更新所有驱动程序。"
.to_string(),
),
0x000000D1 => (
"DRIVER_IRQL_NOT_LESS_OR_EQUAL (0xD1)".to_string(),
"驱动程序使用了不正确的内存地址。通常是驱动程序冲突或损坏。请检查最近安装的硬件驱动(显卡、网卡等),尝试回滚或更新驱动"
"驱动程序尝试访问未分页内存。这是最常见的蓝屏之一,通常是驱动程序冲突或损坏。请检查最近安装的硬件驱动(显卡、网卡等)。"
.to_string(),
),
0x0000007E | 0x1000007E => (
"SYSTEM_THREAD_EXCEPTION_NOT_HANDLED (0x7E)".to_string(),
"系统线程抛出了未捕获的异常。可能是显卡驱动不兼容或者BIOS设置问题。建议重装显卡驱动恢复BIOS默认设置"
"系统线程抛出了未捕获的异常。可能是显卡驱动不兼容或者BIOS设置问题。建议重装显卡驱动更新 BIOS。"
.to_string(),
),
0x0000001A => (
"MEMORY_MANAGEMENT (0x1A)".to_string(),
"严重的内存管理错误。高度怀疑内存条物理故障。建议立即运行 Windows 内存诊断工具,或者尝试拔插内存条"
.to_string(),
),
0x000000EF => (
"CRITICAL_PROCESS_DIED (0xEF)".to_string(),
"Windows 核心进程意外终止。通常是系统文件损坏或硬盘故障。建议运行 'sfc /scannow' 修复系统,并检查硬盘健康度。"
.to_string(),
),
0x00000124 => (
"WHEA_UNCORRECTABLE_ERROR (0x124)".to_string(),
"硬件发生无法纠正的物理错误。这是纯硬件故障。通常是 CPU 电压不足、超频失败、过热,或者主板/PCIe设备故障。"
.to_string(),
),
0x00000116 => (
"VIDEO_TDR_FAILURE (0x116)".to_string(),
"显卡响应超时。显卡驱动崩溃或显卡过热。如果你在玩游戏,可能是显卡超频不稳定或散热硅脂干了。"
0x0000001E => (
"KMODE_EXCEPTION_NOT_HANDLED (0x1E)".to_string(),
"内核模式程序生成了处理器无法捕获的异常。通常是硬件兼容性问题或驱动程序错误。建议检查是否有新安装的硬件"
.to_string(),
),
0x00000050 => (
"PAGE_FAULT_IN_NONPAGED_AREA (0x50)".to_string(),
"试图访问无效的内存地址。可能是内存条故障,或者是防病毒软件/驱动程序冲突。建议检查内存"
"试图访问无效的内存地址。极大概率是内存条故障(接触不良或损坏),或者是防病毒软件/驱动程序冲突。建议运行内存诊断"
.to_string(),
),
0x0000000A => (
"IRQL_NOT_LESS_OR_EQUAL (0x0A)".to_string(),
"驱动程序使用了不正确的内存地址。通常由有缺陷的驱动程序或硬件兼容性问题引起"
0x0000001A => (
"MEMORY_MANAGEMENT (0x1A)".to_string(),
"严重的内存管理错误。高度怀疑内存条物理故障。建议立即运行 Windows 内存诊断工具,或者尝试拔插/更换内存条"
.to_string(),
),
0x0000003B => (
"SYSTEM_SERVICE_EXCEPTION (0x3B)".to_string(),
"系统服务执行异常。通常与图形驱动程序或过时的系统文件有关。"
// 硬件与系统核心相关
0x00000124 => (
"WHEA_UNCORRECTABLE_ERROR (0x124)".to_string(),
"硬件发生无法纠正的物理错误。这是纯硬件故障。通常是 CPU 电压不足(缩缸)、超频失败、过热,或者主板/PCIe设备故障。"
.to_string(),
),
0x00000101 => (
"CLOCK_WATCHDOG_TIMEOUT (0x101)".to_string(),
"CPU 核心死锁。系统检测到某个 CPU 核心在规定时间内没有响应。通常是因为 CPU 超频不稳定、电压不足或过热。"
.to_string(),
),
0x000000EF => (
"CRITICAL_PROCESS_DIED (0xEF)".to_string(),
"Windows 核心进程意外终止。通常是系统文件严重损坏或硬盘出现坏道导致无法读取系统文件。建议运行 'sfc /scannow'。"
.to_string(),
),
0x00000133 => (
"DPC_WATCHDOG_VIOLATION (0x133)".to_string(),
"DPC 看门狗超时。通常是 SSD 固件过旧或无线网卡驱动冲突导致系统卡死时间过长"
"DPC 看门狗超时。系统被某个驱动程序(通常是 SSD 固件、SATA 驱动或无线网卡驱动)卡死太久。建议更新 SSD 固件和主板芯片组驱动"
.to_string(),
),
0x00000139 => (
"KERNEL_SECURITY_CHECK_FAILURE (0x139)".to_string(),
"内核检测到关键数据结构损坏。通常由内存错误、驱动程序 Bug 或病毒导致。建议检查内存和更新驱动。"
.to_string(),
),
// 显卡与电源相关
0x00000116 => (
"VIDEO_TDR_FAILURE (0x116)".to_string(),
"显卡响应超时。显卡驱动崩溃且无法恢复。如果你在玩游戏,可能是显卡超频不稳定、电源功率不足或散热硅脂干了。"
.to_string(),
),
0x00000119 => (
"VIDEO_SCHEDULER_INTERNAL_ERROR (0x119)".to_string(),
"显卡调度器内部错误。这表明显卡驱动发送了无效数据或者是显卡显存VRAM出现故障。"
.to_string(),
),
0x0000009F => (
"DRIVER_POWER_STATE_FAILURE (0x9F)".to_string(),
"驱动程序电源状态故障。通常发生在电脑休眠、睡眠或唤醒时。某个设备的驱动程序没有正确响应电源指令。建议检查电源管理设置。"
.to_string(),
),
// 存储与启动相关
0x0000007B => (
"INACCESSIBLE_BOOT_DEVICE (0x7B)".to_string(),
"无法访问启动设备。通常是 BIOS 中的硬盘模式设置错误(如 AHCI/RAID 切换)或引导扇区损坏。也可能是硬盘物理连接松动。"
.to_string(),
),
0x00000024 => (
"NTFS_FILE_SYSTEM (0x24)".to_string(),
"NTFS 文件系统损坏。这通常意味着硬盘出现坏道或文件系统逻辑错误。建议立即运行 'chkdsk /f /r' 修复磁盘。"
.to_string(),
),
0x00000154 => (
"UNEXPECTED_STORE_EXCEPTION (0x154)".to_string(),
"系统存储组件检测到意外异常。通常指向系统盘SSD/HDD出现故障或连接线松动导致系统无法读取关键数据。"
.to_string(),
),
// 系统服务异常
0x0000003B => (
"SYSTEM_SERVICE_EXCEPTION (0x3B)".to_string(),
"系统服务执行异常。通常与图形驱动程序、系统文件损坏或过时的驱动有关。"
.to_string(),
),
_ => (
format!("未知错误代码: 0x{:X}", code),
"建议手动搜索此错误代码。通用建议:更新驱动、检查内存、扫描病毒。".to_string(),
"此代码较为罕见建议手动在搜索引擎中搜索此错误代码。通用排查步骤1. 更新所有驱动2. 运行内存诊断3. 检查系统文件完整性。"
.to_string(),
),
}
}
@@ -231,34 +281,61 @@ where T: Deref<Target = [u8]>
})
}
// BlueScreenView 外部调用分析
// [修复] BlueScreenView 外部调用分析 (使用有损 UTF-8 转换)
fn analyze_with_bluescreenview(dump_path: &Path) -> Result<BsodAnalysisReport, String> {
let bsv_exe = "BlueScreenView.exe";
let mut temp_csv_path = std::env::temp_dir();
temp_csv_path.push(format!("bsod_report_{}.csv", chrono::Utc::now().timestamp_millis()));
// BlueScreenView.exe /LoadFrom 3 /SingleDumpFile <DumpFilePath> /scomma <OutFile>
let status = Command::new(bsv_exe)
.arg("/LoadFrom")
.arg("3")
.arg("/SingleDumpFile")
.arg(dump_path.to_string_lossy().to_string())
.arg("/scomma")
.arg(temp_csv_path.to_string_lossy().to_string())
.status();
if status.is_err() {
return Err("未找到 BlueScreenView.exe无法分析内核转储文件。请将 BlueScreenView.exe 放入程序目录".to_string());
return Err("未找到 BlueScreenView.exe或没有以管理员运行".to_string());
}
// [修改] 使用 fs::read 读取原始字节,然后有损转换为 String
// 这样即使文件是 UTF-16 或者包含非法 UTF-8 字符,也不会 panic
let content_bytes = fs::read(&temp_csv_path).map_err(|_| "BlueScreenView 分析未生成有效数据。".to_string())?;
let _ = fs::remove_file(temp_csv_path);
let content = String::from_utf8_lossy(&content_bytes);
// 1. 简单判断:如果包含很多 0 字节,可能是 UTF-16尝试手动过滤 0 (简单粗暴但对 CSV 有效)
// 2. 或者直接 lossy 转换,虽然 UTF-16 会乱码,但 CSV crate 有时能容忍乱码分隔符
// 最稳妥的方式其实是先尝试 UTF-16LE 转换逻辑(但我们刚才移除了 encoding_rs
// 所以这里我们用一个简单的技巧:如果文件头两个字节是 FF FE手动跳过 BOM
let content_string = if content_bytes.len() > 2 && content_bytes[0] == 0xFF && content_bytes[1] == 0xFE {
// 简易 UTF-16LE 转 UTF-8 (只针对 ASCII 字符有效,对中文可能会乱码,但在这个场景下够用了)
// BlueScreenView 的列名和关键 hex 代码都是 ASCII
let mut s = String::new();
for i in (2..content_bytes.len()).step_by(2) {
if i+1 < content_bytes.len() {
// 取低位字节,忽略高位 0x00
let c = content_bytes[i] as char;
s.push(c);
}
}
s
} else {
String::from_utf8_lossy(&content_bytes).to_string()
};
let mut rdr = csv::ReaderBuilder::new()
.has_headers(false)
.from_reader(content.as_bytes());
.from_reader(content_string.as_bytes());
for result in rdr.records() {
if let Ok(record) = result {
if record.len() > 15 {
// 跳过 Header 行
if &record[0] == "Dump File" { continue; }
let bug_check_string = &record[2];

View File

@@ -12,7 +12,7 @@
"app": {
"windows": [
{
"title": "system-doctor",
"title": "系统检查 v0.1",
"width": 1200,
"height": 800
}

View File

@@ -12,7 +12,7 @@
:class="{ active: currentTab === 'overview' }"
@click="currentTab = 'overview'"
>
<span class="nav-icon">📊</span> 静态概览
<span class="nav-icon">📊</span> 健康概览
</button>
<button
class="nav-item"
@@ -30,7 +30,7 @@
</button>
</nav>
<div class="sidebar-footer">
<span class="version">Pro v1.2</span>
<span class="version">Generated by AI</span>
</div>
</aside>