From 4fe4150069beecaa3f2d77749dbc2b1a9ad2dd06 Mon Sep 17 00:00:00 2001 From: Julian Freeman Date: Wed, 26 Nov 2025 10:18:40 -0400 Subject: [PATCH] import minidump analyze --- src-tauri/src/main.rs | 71 +++++++++++++++++++++++++++++++++---------- src/App.vue | 37 +++++++++++++++++----- 2 files changed, 84 insertions(+), 24 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 0017b2c..57ba985 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -9,11 +9,10 @@ use sysinfo::{System, Disks}; use wmi::{COMLibrary, WMIConnection}; use std::fs; use std::path::Path; +use std::io::Read; // [新增] 用于读取文件头 use tauri::Emitter; use chrono::{Duration, FixedOffset, Local, NaiveDate, TimeZone, DateTime}; -// 只引入 minidump 基础库 use minidump::{Minidump, MinidumpException, MinidumpSystemInfo}; -// [新增] 引入 Deref 用于泛型约束 use std::ops::Deref; // --- 1. 数据结构 (保持不变) --- @@ -190,22 +189,28 @@ fn translate_bugcheck_u32(code: u32) -> (String, String) { } } -// [修复] 抽离公共分析逻辑,使用泛型 T 适配不同数据源 +// [公共逻辑] 泛型函数,适配文件和内存 fn analyze_dump_data(dump: Minidump) -> Result where T: Deref { - let exception_stream = dump.get_stream::() - .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 (exception_code, exception_address) = match dump.get_stream::() { + Ok(stream) => ( + stream.raw.exception_record.exception_code, + stream.raw.exception_record.exception_address + ), + Err(_) => (0, 0) + }; let sys_info_str = match dump.get_stream::() { Ok(info) => format!("Windows Build {}", info.raw.build_number), Err(_) => "Unknown OS".to_string(), }; - let (reason_str, recommend) = translate_bugcheck_u32(exception_code); + let (reason_str, recommend) = if exception_code != 0 { + translate_bugcheck_u32(exception_code) + } else { + ("未找到异常记录".to_string(), "该文件有效但未包含标准异常流。可能是手动生成的 Dump 或被截断。".to_string()) + }; Ok(BsodAnalysisReport { crash_reason: reason_str, @@ -217,6 +222,30 @@ where T: Deref }) } +// [新增] 辅助函数:检查文件头签名 +fn check_dump_signature(sig: &[u8]) -> Result<(), String> { + if sig.len() < 4 { + return Err("文件太小,无效的 Dump 文件。".to_string()); + } + + // 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()); + } + + Err(format!( + "文件签名错误!期望 'MDMP', 实际收到: {:02X?} ('{}')。\n这可能不是一个有效的蓝屏文件。", + &sig[0..4], + String::from_utf8_lossy(&sig[0..4]).replace('\0', "") + )) +} + // --- 命令:列出 Minidump 文件 --- #[tauri::command] async fn list_minidumps() -> Result, String> { @@ -256,14 +285,26 @@ async fn list_minidumps() -> Result, String> { #[tauri::command] async fn analyze_minidump(filepath: String) -> Result { let path = Path::new(&filepath); - let dump = Minidump::read_path(path).map_err(|e| format!("无法读取文件: {}", e))?; + + // [优化] 先读取头部进行校验,避免 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)?; + } + + // 重新读取完整文件 (minidump crate 需要完整路径重新打开) + let dump = Minidump::read_path(path).map_err(|e| format!("解析错误: {}", e))?; analyze_dump_data(dump) } -// [新增] 命令:分析二进制内容的 Minidump (用于前端导入) +// --- 命令:分析二进制内容的 Minidump --- #[tauri::command] async fn analyze_minidump_bytes(file_content: Vec) -> Result { - let dump = Minidump::read(file_content).map_err(|e| format!("无法解析文件内容: {}", e))?; + // [优化] 头部校验 + check_dump_signature(&file_content)?; + + let dump = Minidump::read(file_content).map_err(|e| format!("解析器拒绝处理: {}", e))?; analyze_dump_data(dump) } @@ -271,14 +312,13 @@ async fn analyze_minidump_bytes(file_content: Vec) -> Result Result<(), String> { std::thread::spawn(move || { - // ... (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(); @@ -323,7 +363,6 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> { }; let _ = window.emit("report-hardware", hardware); } - // ... (其他部分保持不变) ... let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).ok(); { let mut storage = Vec::new(); diff --git a/src/App.vue b/src/App.vue index 9c47746..e5f618a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -322,6 +322,13 @@ async function loadMinidumps() { bsodLoading.value = true; try { bsodList.value async function analyzeBsod(file) { if (bsodAnalyzing.value) return; + + // [新增] 提前检查文件大小,避免读取几百MB的 Kernel Dump 导致卡死 + if (file.fileRef && file.fileRef.size > 5 * 1024 * 1024) { // 5MB 限制 + triggerToast('文件过大', '仅支持 <5MB 的 Minidump 文件,不支持完整的 MEMORY.DMP', 'error'); + return; + } + selectedBsod.value = file; bsodResult.value = null; bsodAnalyzing.value = true; @@ -339,7 +346,16 @@ async function analyzeBsod(file) { bsodResult.value = await invoke('analyze_minidump', { filepath: file.path }); } } catch (e) { - triggerToast('分析失败', e, 'error'); + // [修改] 优化错误提示:拦截 "Header mismatch" 等晦涩的报错 + let errorMsg = e; + if (typeof e === 'string') { + if (e.includes("Header mismatch")) { + errorMsg = "格式错误:这不是有效的 Minidump。请勿上传 MEMORY.DMP (完整转储)。"; + } else if (e.includes("No Exception Stream")) { + errorMsg = "无法提取错误:该文件可能已损坏或不包含异常流。"; + } + } + triggerToast('分析失败', errorMsg, 'error'); } finally { bsodAnalyzing.value = false; } @@ -352,26 +368,31 @@ function handleBsodFileImport(event) { if (!files || files.length === 0) return; let importedCount = 0; - // 倒序遍历,这样添加到数组头部后顺序是正确的 for (let i = files.length - 1; i >= 0; i--) { const file = files[i]; - // 构造一个符合列表格式的虚拟对象 + + // [新增] 简单的后缀名检查 + if (!file.name.toLowerCase().endsWith('.dmp')) { + continue; + } + const newItem = { filename: file.name, - // 使用一个特殊的 path 标识,防止 key 冲突 path: `imported-${file.name}-${Date.now()}-${i}`, size_kb: Math.round(file.size / 1024), - // 使用浏览器读取到的修改时间 created_time: new Date(file.lastModified).toLocaleString(), - // 关键:保存文件引用,以便点击时读取 fileRef: file }; bsodList.value.unshift(newItem); importedCount++; } - triggerToast('导入成功', `已添加 ${importedCount} 个文件到列表`, 'success'); - event.target.value = ''; // 重置,允许再次导入同名文件 + if (importedCount > 0) { + triggerToast('导入成功', `已添加 ${importedCount} 个文件到列表`, 'success'); + } else { + triggerToast('导入忽略', '未选择有效的 .dmp 文件', 'error'); + } + event.target.value = ''; } watch(currentTab, (newVal) => { if (newVal === 'bsod' && bsodList.value.length === 0) loadMinidumps(); });