add custom bsod

This commit is contained in:
Julian Freeman
2025-11-26 09:51:30 -04:00
parent c01c1a33d4
commit 936c58dcc8
2 changed files with 101 additions and 135 deletions

View File

@@ -7,11 +7,11 @@
use serde::Serialize;
use sysinfo::{System, Disks};
use wmi::{COMLibrary, WMIConnection};
use std::fs;
use std::{fs, ops::Deref};
use std::path::Path;
use tauri::Emitter;
use chrono::{Duration, FixedOffset, Local, NaiveDate, TimeZone, DateTime};
// [修改] 只引入 minidump 基础库,移除 processor 依赖以避免 API 冲突
// 只引入 minidump 基础库
use minidump::{Minidump, MinidumpException, MinidumpSystemInfo};
// --- 1. 数据结构 (保持不变) ---
@@ -129,9 +129,7 @@ fn format_wmi_time(wmi_str: &str) -> String {
}
}
// --- 新增功能:翻译蓝屏代码为人话 (手动映射常见代码) ---
fn translate_bugcheck_u32(code: u32) -> (String, String) {
// 这些是 Windows 最常见的 BSOD 代码
match code {
0x000000D1 => (
"DRIVER_IRQL_NOT_LESS_OR_EQUAL (0xD1)".to_string(),
@@ -190,6 +188,31 @@ fn translate_bugcheck_u32(code: u32) -> (String, String) {
}
}
// [新增] 抽离公共分析逻辑
fn analyze_dump_data<T: Deref<Target = [u8]>>(dump: Minidump<T>) -> Result<BsodAnalysisReport, String> {
let exception_stream = dump.get_stream::<MinidumpException>()
.map_err(|_| "无法找到异常信息流 (No Exception Stream),可能是非标准 Dump 文件。".to_string())?;
let exception_code = exception_stream.raw.exception_record.exception_code;
let exception_address = exception_stream.raw.exception_record.exception_address;
let sys_info_str = match dump.get_stream::<MinidumpSystemInfo>() {
Ok(info) => format!("Windows Build {}", info.raw.build_number),
Err(_) => "Unknown OS".to_string(),
};
let (reason_str, recommend) = translate_bugcheck_u32(exception_code);
Ok(BsodAnalysisReport {
crash_reason: reason_str,
crash_address: format!("0x{:X}", exception_address),
bug_check_code: format!("0x{:X} ({})", exception_code, sys_info_str),
crashing_thread: None,
human_analysis: "根据错误代码自动匹配的分析结果。".to_string(),
recommendation: recommend,
})
}
// --- 命令:列出 Minidump 文件 ---
#[tauri::command]
async fn list_minidumps() -> Result<Vec<BsodFileItem>, String> {
@@ -200,17 +223,15 @@ async fn list_minidumps() -> Result<Vec<BsodFileItem>, String> {
if let Ok(entries) = fs::read_dir(path) {
for entry in entries {
if let Ok(entry) = entry {
// [修复] 使用 .as_ref() 防止 metadata 所有权被移动
let metadata = entry.metadata().ok();
let created = metadata.as_ref()
.and_then(|m| m.modified().ok()) // 通常用修改时间
.and_then(|m| m.modified().ok())
.map(|t| {
let dt: DateTime<Local> = t.into();
dt.format("%Y-%m-%d %H:%M:%S").to_string()
})
.unwrap_or("Unknown".to_string());
// [修复] 使用 .as_ref() 再次访问 metadata
let size = metadata.as_ref().map(|m| m.len() / 1024).unwrap_or(0);
files.push(BsodFileItem {
@@ -223,65 +244,42 @@ async fn list_minidumps() -> Result<Vec<BsodFileItem>, String> {
}
}
}
// 按时间倒序排列
files.sort_by(|a, b| b.created_time.cmp(&a.created_time));
Ok(files)
}
// --- 命令:分析指定的 Minidump 文件 ---
// --- 命令:分析指定路径的 Minidump ---
#[tauri::command]
async fn analyze_minidump(filepath: String) -> Result<BsodAnalysisReport, String> {
let path = Path::new(&filepath);
// 1. 读取文件 (使用基础 minidump 库)
let dump = Minidump::read_path(path).map_err(|e| format!("无法读取文件: {}", e))?;
// 2. 直接获取异常流 (Exception Stream)
let exception_stream = dump.get_stream::<MinidumpException>()
.map_err(|_| "无法找到异常信息流 (No Exception Stream)".to_string())?;
analyze_dump_data(dump)
}
// [修复] 使用 .raw 访问内部原始结构
let exception_code = exception_stream.raw.exception_record.exception_code;
let exception_address = exception_stream.raw.exception_record.exception_address;
// 3. 尝试获取系统信息 (OS Version)
let sys_info_str = match dump.get_stream::<MinidumpSystemInfo>() {
// [修复] 使用 .raw 访问 build_number
Ok(info) => format!("Windows Build {}", info.raw.build_number),
Err(_) => "Unknown OS".to_string(),
};
// 4. 翻译
let (reason_str, recommend) = translate_bugcheck_u32(exception_code);
Ok(BsodAnalysisReport {
crash_reason: reason_str,
crash_address: format!("0x{:X}", exception_address),
bug_check_code: format!("0x{:X} ({})", exception_code, sys_info_str),
crashing_thread: None, // 基础解析不包含线程栈回溯
human_analysis: "根据错误代码自动匹配的分析结果。".to_string(),
recommendation: recommend,
})
// [新增] 命令:分析二进制内容的 Minidump (用于前端导入)
#[tauri::command]
async fn analyze_minidump_bytes(file_content: Vec<u8>) -> Result<BsodAnalysisReport, String> {
let dump = Minidump::read(file_content).map_err(|e| format!("无法解析文件内容: {}", e))?;
analyze_dump_data(dump)
}
// --- 现有命令run_diagnosis (保持不变) ---
#[tauri::command]
async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
std::thread::spawn(move || {
// 1. 硬件概览
// ... (run_diagnosis 的内容保持不变,这里省略以节省篇幅,请确保保留原有逻辑) ...
// 为了确保代码完整性,这里包含 run_diagnosis 的第一部分作为占位,实际应用中请保持原样
{
let mut sys = System::new();
sys.refresh_memory();
sys.refresh_cpu();
let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).ok();
// ... 硬件概览逻辑 ...
let mut bios_ver = "Unknown".to_string();
let mut mobo_vendor = "Unknown".to_string();
let mut mobo_product = "Unknown".to_string();
let mut sys_vendor = "Unknown".to_string();
let mut sys_product = "Unknown".to_string();
if let Some(con) = &wmi_con {
if let Ok(results) = con.raw_query::<Win32_BIOS>("SELECT SMBIOSBIOSVersion FROM Win32_BIOS") {
if let Some(bios) = results.first() { bios_ver = bios.SMBIOSBIOSVersion.clone().unwrap_or_default(); }
@@ -299,7 +297,6 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
}
}
}
let mut c_total = 0u64;
let mut c_used = 0u64;
let disks = Disks::new_with_refreshed_list();
@@ -311,29 +308,19 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
break;
}
}
let cpu_brand = if let Some(cpu) = sys.cpus().first() {
cpu.brand().trim().to_string()
} else {
"Unknown CPU".to_string()
};
let cpu_brand = if let Some(cpu) = sys.cpus().first() { cpu.brand().trim().to_string() } else { "Unknown CPU".to_string() };
let hardware = HardwareSummary {
cpu_name: cpu_brand,
sys_vendor, sys_product, mobo_vendor, mobo_product,
cpu_name: cpu_brand, sys_vendor, sys_product, mobo_vendor, mobo_product,
memory_total_gb: sys.total_memory() / 1024 / 1024 / 1024,
memory_used_gb: sys.used_memory() / 1024 / 1024 / 1024,
os_version: System::long_os_version().unwrap_or("Unknown".to_string()),
bios_version: bios_ver,
c_drive_total_gb: c_total,
c_drive_used_gb: c_used,
c_drive_total_gb: c_total, c_drive_used_gb: c_used,
};
let _ = window.emit("report-hardware", hardware);
}
// ... (其他部分保持不变) ...
let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).ok();
// 2. 存储设备
{
let mut storage = Vec::new();
if let Some(con) = &wmi_con {
@@ -357,8 +344,6 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
}
let _ = window.emit("report-storage", storage);
}
// 3. 驱动
{
let mut driver_issues = Vec::new();
if let Some(con) = &wmi_con {
@@ -374,32 +359,25 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
};
driver_issues.push(DriverIssue {
device_name: dev.Name.unwrap_or("未知设备".to_string()),
error_code: code,
description: desc.to_string(),
error_code: code, description: desc.to_string(),
});
}
}
}
let _ = window.emit("report-drivers", driver_issues);
}
// 4. Minidump
{
let mut minidump = MinidumpInfo { found: false, count: 0, explanation: "无蓝屏记录".to_string() };
if let Ok(entries) = fs::read_dir("C:\\Windows\\Minidump") {
let count = entries.count();
if count > 0 {
minidump = MinidumpInfo {
found: true,
count,
explanation: format!("发现 {} 次蓝屏崩溃", count),
found: true, count, explanation: format!("发现 {} 次蓝屏崩溃", count),
};
}
}
let _ = window.emit("report-minidumps", minidump);
}
// 5. 电池
{
let mut battery_info = None;
if let Some(con) = &wmi_con {
@@ -411,15 +389,9 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
if design > 0 {
let health = ((full as f64 / design as f64) * 100.0) as u32;
let ac_plugged = status == 2 || status == 6 || status == 1;
let explain = if health < 60 {
"电池老化严重,建议更换,否则可能导致供电不稳。".to_string()
} else {
"电池状态良好。".to_string()
};
let explain = if health < 60 { "电池老化严重,建议更换,否则可能导致供电不稳。".to_string() } else { "电池状态良好。".to_string() };
battery_info = Some(BatteryInfo {
health_percentage: health,
is_ac_connected: ac_plugged,
explanation: explain,
health_percentage: health, is_ac_connected: ac_plugged, explanation: explain,
});
}
}
@@ -427,8 +399,6 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
}
let _ = window.emit("report-battery", battery_info);
}
// 6. 日志
{
let mut events = Vec::new();
if let Some(con) = &wmi_con {
@@ -437,12 +407,10 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
"SELECT TimeGenerated, EventCode, SourceName, Message FROM Win32_NTLogEvent WHERE Logfile = 'System' AND TimeGenerated >= '{}' AND (EventCode = 41 OR EventCode = 18 OR EventCode = 19 OR EventCode = 7 OR EventCode = 1001 OR EventCode = 4101)",
start_time_str
);
if let Ok(results) = con.raw_query::<Win32_NTLogEvent>(&query) {
for event in results {
let mut is_target_event = false;
let mut hint = String::new();
if event.EventCode == 41 && event.SourceName == "Microsoft-Windows-Kernel-Power" {
is_target_event = true; hint = "系统意外断电 (电源/强关)".to_string();
} else if (event.EventCode == 18 || event.EventCode == 19) && event.SourceName == "Microsoft-Windows-WHEA-Logger" {
@@ -454,7 +422,6 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
} else if event.EventCode == 4101 && event.SourceName == "Display" {
is_target_event = true; hint = "显卡驱动停止响应并已恢复 (TDR)".to_string();
}
if is_target_event {
events.push(SystemEvent {
time_generated: format_wmi_time(&event.TimeGenerated),
@@ -470,17 +437,14 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
}
let _ = window.emit("report-events", events);
}
// 7. 全部完成信号
let _ = window.emit("diagnosis-finished", ());
});
Ok(())
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![run_diagnosis, list_minidumps, analyze_minidump])
.invoke_handler(tauri::generate_handler![run_diagnosis, list_minidumps, analyze_minidump, analyze_minidump_bytes])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}