fix bsod analyse (half)
This commit is contained in:
@@ -9,7 +9,8 @@ use sysinfo::{System, Disks};
|
||||
use wmi::{COMLibrary, WMIConnection};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::io::Read; // [新增] 用于读取文件头
|
||||
use std::io::Read;
|
||||
use std::process::Command;
|
||||
use tauri::Emitter;
|
||||
use chrono::{Duration, FixedOffset, Local, NaiveDate, TimeZone, DateTime};
|
||||
use minidump::{Minidump, MinidumpException, MinidumpSystemInfo};
|
||||
@@ -154,7 +155,7 @@ fn translate_bugcheck_u32(code: u32) -> (String, String) {
|
||||
),
|
||||
0x00000124 => (
|
||||
"WHEA_UNCORRECTABLE_ERROR (0x124)".to_string(),
|
||||
"硬件发生无法纠正的物理错误。这是纯硬件故障。通常是 CPU 电压不足、超频失败、过热,或者主板/PCIe设备(如 NVMe 硬盘)故障。"
|
||||
"硬件发生无法纠正的物理错误。这是纯硬件故障。通常是 CPU 电压不足、超频失败、过热,或者主板/PCIe设备故障。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000116 => (
|
||||
@@ -184,13 +185,21 @@ fn translate_bugcheck_u32(code: u32) -> (String, String) {
|
||||
),
|
||||
_ => (
|
||||
format!("未知错误代码: 0x{:X}", code),
|
||||
"请尝试在搜索引擎中搜索此错误代码。通用建议:更新驱动、检查内存、扫描病毒。".to_string(),
|
||||
"建议手动搜索此错误代码。通用建议:更新驱动、检查内存、扫描病毒。".to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// [公共逻辑] 泛型函数,适配文件和内存
|
||||
fn analyze_dump_data<T>(dump: Minidump<T>) -> Result<BsodAnalysisReport, String>
|
||||
fn translate_bugcheck_str(code_str: &str) -> (String, String) {
|
||||
let clean_code = code_str.trim().replace("0x", "");
|
||||
if let Ok(code) = u32::from_str_radix(&clean_code, 16) {
|
||||
return translate_bugcheck_u32(code);
|
||||
}
|
||||
(format!("错误代码: {}", code_str), "无法识别的错误代码,建议手动搜索。".to_string())
|
||||
}
|
||||
|
||||
// [公共逻辑] Rust 原生分析
|
||||
fn analyze_dump_data_native<T>(dump: Minidump<T>) -> Result<BsodAnalysisReport, String>
|
||||
where T: Deref<Target = [u8]>
|
||||
{
|
||||
let (exception_code, exception_address) = match dump.get_stream::<MinidumpException>() {
|
||||
@@ -206,44 +215,106 @@ where T: Deref<Target = [u8]>
|
||||
Err(_) => "Unknown OS".to_string(),
|
||||
};
|
||||
|
||||
let (reason_str, recommend) = if exception_code != 0 {
|
||||
translate_bugcheck_u32(exception_code)
|
||||
} else {
|
||||
("未找到异常记录".to_string(), "该文件有效但未包含标准异常流。可能是手动生成的 Dump 或被截断。".to_string())
|
||||
};
|
||||
if exception_code == 0 {
|
||||
return Err("未找到异常记录".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(),
|
||||
human_analysis: "通过 Rust 原生库分析得出。".to_string(),
|
||||
recommendation: recommend,
|
||||
})
|
||||
}
|
||||
|
||||
// [新增] 辅助函数:检查文件头签名
|
||||
fn check_dump_signature(sig: &[u8]) -> Result<(), String> {
|
||||
if sig.len() < 4 {
|
||||
return Err("文件太小,无效的 Dump 文件。".to_string());
|
||||
}
|
||||
// [修复] BlueScreenView 外部调用分析 (使用 CSV crate 解析)
|
||||
fn analyze_with_bluescreenview(dump_path: &Path) -> Result<BsodAnalysisReport, String> {
|
||||
let bsv_exe = "BlueScreenView.exe";
|
||||
|
||||
// 1. 检查标准 Minidump (MDMP)
|
||||
if sig.starts_with(b"MDMP") {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 2. 检查 Kernel Dump (PAGE / PAGEDU64)
|
||||
// 即使文件很小,只要头是 PAGE,它就是 Kernel Dump 格式,minidump crate 无法解析
|
||||
if sig.starts_with(b"PAGE") {
|
||||
return Err("不支持的文件格式:检测到 '内核转储' (PAGEDU64/PAGE)。".to_string());
|
||||
let mut temp_csv_path = std::env::temp_dir();
|
||||
temp_csv_path.push(format!("bsod_report_{}.csv", chrono::Utc::now().timestamp_millis()));
|
||||
|
||||
let status = Command::new(bsv_exe)
|
||||
.arg("/LoadFrom")
|
||||
.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());
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"文件签名错误!期望 'MDMP', 实际收到: {:02X?} ('{}')。\n这可能不是一个有效的蓝屏文件。",
|
||||
&sig[0..4],
|
||||
String::from_utf8_lossy(&sig[0..4]).replace('\0', "")
|
||||
))
|
||||
// 读取字节并转为 String (lossy 模式,防止 GBK/ANSI 乱码导致 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);
|
||||
|
||||
// 使用 csv crate 解析
|
||||
let mut rdr = csv::ReaderBuilder::new()
|
||||
.has_headers(false) // 命令行模式可能没有表头,或者我们手动处理
|
||||
.from_reader(content.as_bytes());
|
||||
|
||||
for result in rdr.records() {
|
||||
if let Ok(record) = result {
|
||||
// BlueScreenView 的列索引通常是:
|
||||
// 0: Dump File
|
||||
// 1: Crash Time
|
||||
// 2: Bug Check String
|
||||
// 3: Bug Check Code
|
||||
// ...
|
||||
// 8: Caused By Driver (关键列)
|
||||
// ...
|
||||
// 15: Crash Address (可能位置)
|
||||
|
||||
// 我们至少需要前9列才能提供有意义的信息
|
||||
if record.len() >= 9 {
|
||||
// 跳过可能的表头行
|
||||
if &record[0] == "Dump File" { continue; }
|
||||
|
||||
let bug_check_string = &record[2];
|
||||
let bug_check_code = &record[3];
|
||||
let caused_by_driver = &record[8]; // Index 8 是最常见的 Caused By Driver 位置
|
||||
|
||||
// 尝试获取崩溃地址,如果不够长就填 N/A
|
||||
let crash_addr = if record.len() > 15 { &record[15] } else { "N/A" };
|
||||
|
||||
let (human, recommend) = translate_bugcheck_str(bug_check_code);
|
||||
|
||||
// 优先使用 Bug Check String,如果为空则使用翻译结果
|
||||
let final_reason = if !bug_check_string.is_empty() { bug_check_string.to_string() } else { human };
|
||||
|
||||
return Ok(BsodAnalysisReport {
|
||||
crash_reason: final_reason,
|
||||
crash_address: crash_addr.to_string(),
|
||||
bug_check_code: bug_check_code.to_string(),
|
||||
crashing_thread: Some(format!("Driver: {}", caused_by_driver)),
|
||||
human_analysis: format!("BlueScreenView 分析结果:崩溃可能由驱动程序 [{}] 引起。", caused_by_driver),
|
||||
recommendation: recommend,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err("BlueScreenView 输出为空或未能解析到有效列(文件可能已损坏)。".to_string())
|
||||
}
|
||||
|
||||
// 辅助函数:检查文件头签名并返回类型
|
||||
enum DumpType {
|
||||
Minidump,
|
||||
KernelDump, // PAGEDU64 / PAGE
|
||||
Unknown,
|
||||
}
|
||||
|
||||
fn check_dump_signature(sig: &[u8]) -> DumpType {
|
||||
if sig.len() < 4 { return DumpType::Unknown; }
|
||||
if sig.starts_with(b"MDMP") { return DumpType::Minidump; }
|
||||
if sig.starts_with(b"PAGE") { return DumpType::KernelDump; }
|
||||
DumpType::Unknown
|
||||
}
|
||||
|
||||
// --- 命令:列出 Minidump 文件 ---
|
||||
@@ -286,34 +357,79 @@ async fn list_minidumps() -> Result<Vec<BsodFileItem>, String> {
|
||||
async fn analyze_minidump(filepath: String) -> Result<BsodAnalysisReport, String> {
|
||||
let path = Path::new(&filepath);
|
||||
|
||||
// [优化] 先读取头部进行校验,避免 minidump 库报晦涩错误
|
||||
let mut file = fs::File::open(path).map_err(|e| format!("无法打开文件: {}", e))?;
|
||||
let mut header = [0u8; 4];
|
||||
if let Ok(_) = file.read_exact(&mut header) {
|
||||
check_dump_signature(&header)?;
|
||||
}
|
||||
|
||||
let dump_type = if let Ok(_) = file.read_exact(&mut header) {
|
||||
check_dump_signature(&header)
|
||||
} else {
|
||||
DumpType::Unknown
|
||||
};
|
||||
|
||||
// 重新读取完整文件 (minidump crate 需要完整路径重新打开)
|
||||
let dump = Minidump::read_path(path).map_err(|e| format!("解析错误: {}", e))?;
|
||||
analyze_dump_data(dump)
|
||||
match dump_type {
|
||||
DumpType::Minidump => {
|
||||
if let Ok(dump) = Minidump::read_path(path) {
|
||||
if let Ok(report) = analyze_dump_data_native(dump) {
|
||||
return Ok(report);
|
||||
}
|
||||
}
|
||||
analyze_with_bluescreenview(path)
|
||||
},
|
||||
DumpType::KernelDump => {
|
||||
analyze_with_bluescreenview(path)
|
||||
},
|
||||
DumpType::Unknown => Err("无效的文件签名。不是有效的 Windows Dump 文件。".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// --- 命令:分析二进制内容的 Minidump ---
|
||||
// [修复] 命令:分析二进制内容的 Minidump
|
||||
#[tauri::command]
|
||||
async fn analyze_minidump_bytes(file_content: Vec<u8>) -> Result<BsodAnalysisReport, String> {
|
||||
// [优化] 头部校验
|
||||
check_dump_signature(&file_content)?;
|
||||
if file_content.is_empty() {
|
||||
return Err("导入的文件内容为空".to_string());
|
||||
}
|
||||
|
||||
let dump = Minidump::read(file_content).map_err(|e| format!("解析器拒绝处理: {}", e))?;
|
||||
analyze_dump_data(dump)
|
||||
let dump_type = check_dump_signature(&file_content);
|
||||
|
||||
match dump_type {
|
||||
DumpType::Minidump => {
|
||||
// [修复] 先将 Minidump::read 错误转为 String,确保链式调用类型一致
|
||||
let native_result = Minidump::read(file_content.clone())
|
||||
.map_err(|e| e.to_string())
|
||||
.and_then(|dump| analyze_dump_data_native(dump));
|
||||
|
||||
if let Ok(report) = native_result {
|
||||
return Ok(report);
|
||||
}
|
||||
// 失败回退到 BSV
|
||||
},
|
||||
DumpType::KernelDump => {
|
||||
// 直接回退
|
||||
},
|
||||
DumpType::Unknown => return Err("无效的文件签名。请确认这是 .dmp 文件。".to_string())
|
||||
}
|
||||
|
||||
// --- 回退流程:写入临时文件调用 BlueScreenView ---
|
||||
let mut temp_dump_path = std::env::temp_dir();
|
||||
temp_dump_path.push(format!("temp_dump_{}.dmp", chrono::Utc::now().timestamp_millis()));
|
||||
|
||||
// 既然上面判断文件有效,这里大概率可以写入
|
||||
if let Err(_) = fs::write(&temp_dump_path, &file_content) {
|
||||
return Err("无法写入临时文件进行分析".to_string());
|
||||
}
|
||||
|
||||
let bsv_result = analyze_with_bluescreenview(&temp_dump_path);
|
||||
|
||||
let _ = fs::remove_file(temp_dump_path);
|
||||
|
||||
bsv_result
|
||||
}
|
||||
|
||||
// --- 现有命令:run_diagnosis (保持不变) ---
|
||||
#[tauri::command]
|
||||
async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
|
||||
std::thread::spawn(move || {
|
||||
// ... (保持原有逻辑不变) ...
|
||||
// 占位符
|
||||
// ... (保持原有逻辑不变,占位符) ...
|
||||
{
|
||||
let mut sys = System::new();
|
||||
sys.refresh_memory();
|
||||
|
||||
Reference in New Issue
Block a user