import minidump analyze
This commit is contained in:
@@ -9,11 +9,10 @@ use sysinfo::{System, Disks};
|
|||||||
use wmi::{COMLibrary, WMIConnection};
|
use wmi::{COMLibrary, WMIConnection};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::io::Read; // [新增] 用于读取文件头
|
||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
use chrono::{Duration, FixedOffset, Local, NaiveDate, TimeZone, DateTime};
|
use chrono::{Duration, FixedOffset, Local, NaiveDate, TimeZone, DateTime};
|
||||||
// 只引入 minidump 基础库
|
|
||||||
use minidump::{Minidump, MinidumpException, MinidumpSystemInfo};
|
use minidump::{Minidump, MinidumpException, MinidumpSystemInfo};
|
||||||
// [新增] 引入 Deref 用于泛型约束
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
// --- 1. 数据结构 (保持不变) ---
|
// --- 1. 数据结构 (保持不变) ---
|
||||||
@@ -190,22 +189,28 @@ fn translate_bugcheck_u32(code: u32) -> (String, String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [修复] 抽离公共分析逻辑,使用泛型 T 适配不同数据源
|
// [公共逻辑] 泛型函数,适配文件和内存
|
||||||
fn analyze_dump_data<T>(dump: Minidump<T>) -> Result<BsodAnalysisReport, String>
|
fn analyze_dump_data<T>(dump: Minidump<T>) -> Result<BsodAnalysisReport, String>
|
||||||
where T: Deref<Target = [u8]>
|
where T: Deref<Target = [u8]>
|
||||||
{
|
{
|
||||||
let exception_stream = dump.get_stream::<MinidumpException>()
|
let (exception_code, exception_address) = match dump.get_stream::<MinidumpException>() {
|
||||||
.map_err(|_| "无法找到异常信息流 (No Exception Stream),可能是非标准 Dump 文件。".to_string())?;
|
Ok(stream) => (
|
||||||
|
stream.raw.exception_record.exception_code,
|
||||||
let exception_code = exception_stream.raw.exception_record.exception_code;
|
stream.raw.exception_record.exception_address
|
||||||
let exception_address = exception_stream.raw.exception_record.exception_address;
|
),
|
||||||
|
Err(_) => (0, 0)
|
||||||
|
};
|
||||||
|
|
||||||
let sys_info_str = match dump.get_stream::<MinidumpSystemInfo>() {
|
let sys_info_str = match dump.get_stream::<MinidumpSystemInfo>() {
|
||||||
Ok(info) => format!("Windows Build {}", info.raw.build_number),
|
Ok(info) => format!("Windows Build {}", info.raw.build_number),
|
||||||
Err(_) => "Unknown OS".to_string(),
|
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 {
|
Ok(BsodAnalysisReport {
|
||||||
crash_reason: reason_str,
|
crash_reason: reason_str,
|
||||||
@@ -217,6 +222,30 @@ where T: Deref<Target = [u8]>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [新增] 辅助函数:检查文件头签名
|
||||||
|
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 文件 ---
|
// --- 命令:列出 Minidump 文件 ---
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn list_minidumps() -> Result<Vec<BsodFileItem>, String> {
|
async fn list_minidumps() -> Result<Vec<BsodFileItem>, String> {
|
||||||
@@ -256,14 +285,26 @@ async fn list_minidumps() -> Result<Vec<BsodFileItem>, String> {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn analyze_minidump(filepath: String) -> Result<BsodAnalysisReport, String> {
|
async fn analyze_minidump(filepath: String) -> Result<BsodAnalysisReport, String> {
|
||||||
let path = Path::new(&filepath);
|
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)
|
analyze_dump_data(dump)
|
||||||
}
|
}
|
||||||
|
|
||||||
// [新增] 命令:分析二进制内容的 Minidump (用于前端导入)
|
// --- 命令:分析二进制内容的 Minidump ---
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn analyze_minidump_bytes(file_content: Vec<u8>) -> Result<BsodAnalysisReport, String> {
|
async fn analyze_minidump_bytes(file_content: Vec<u8>) -> Result<BsodAnalysisReport, String> {
|
||||||
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)
|
analyze_dump_data(dump)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,14 +312,13 @@ async fn analyze_minidump_bytes(file_content: Vec<u8>) -> Result<BsodAnalysisRep
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
|
async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
// ... (run_diagnosis 的内容保持不变,这里省略以节省篇幅,请确保保留原有逻辑) ...
|
// ... (保持原有逻辑不变) ...
|
||||||
// 为了确保代码完整性,这里包含 run_diagnosis 的第一部分作为占位,实际应用中请保持原样
|
// 占位符
|
||||||
{
|
{
|
||||||
let mut sys = System::new();
|
let mut sys = System::new();
|
||||||
sys.refresh_memory();
|
sys.refresh_memory();
|
||||||
sys.refresh_cpu();
|
sys.refresh_cpu();
|
||||||
let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).ok();
|
let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).ok();
|
||||||
// ... 硬件概览逻辑 ...
|
|
||||||
let mut bios_ver = "Unknown".to_string();
|
let mut bios_ver = "Unknown".to_string();
|
||||||
let mut mobo_vendor = "Unknown".to_string();
|
let mut mobo_vendor = "Unknown".to_string();
|
||||||
let mut mobo_product = "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 _ = window.emit("report-hardware", hardware);
|
||||||
}
|
}
|
||||||
// ... (其他部分保持不变) ...
|
|
||||||
let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).ok();
|
let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).ok();
|
||||||
{
|
{
|
||||||
let mut storage = Vec::new();
|
let mut storage = Vec::new();
|
||||||
|
|||||||
35
src/App.vue
35
src/App.vue
@@ -322,6 +322,13 @@ async function loadMinidumps() { bsodLoading.value = true; try { bsodList.value
|
|||||||
|
|
||||||
async function analyzeBsod(file) {
|
async function analyzeBsod(file) {
|
||||||
if (bsodAnalyzing.value) return;
|
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;
|
selectedBsod.value = file;
|
||||||
bsodResult.value = null;
|
bsodResult.value = null;
|
||||||
bsodAnalyzing.value = true;
|
bsodAnalyzing.value = true;
|
||||||
@@ -339,7 +346,16 @@ async function analyzeBsod(file) {
|
|||||||
bsodResult.value = await invoke('analyze_minidump', { filepath: file.path });
|
bsodResult.value = await invoke('analyze_minidump', { filepath: file.path });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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 {
|
} finally {
|
||||||
bsodAnalyzing.value = false;
|
bsodAnalyzing.value = false;
|
||||||
}
|
}
|
||||||
@@ -352,26 +368,31 @@ function handleBsodFileImport(event) {
|
|||||||
if (!files || files.length === 0) return;
|
if (!files || files.length === 0) return;
|
||||||
|
|
||||||
let importedCount = 0;
|
let importedCount = 0;
|
||||||
// 倒序遍历,这样添加到数组头部后顺序是正确的
|
|
||||||
for (let i = files.length - 1; i >= 0; i--) {
|
for (let i = files.length - 1; i >= 0; i--) {
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
// 构造一个符合列表格式的虚拟对象
|
|
||||||
|
// [新增] 简单的后缀名检查
|
||||||
|
if (!file.name.toLowerCase().endsWith('.dmp')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const newItem = {
|
const newItem = {
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
// 使用一个特殊的 path 标识,防止 key 冲突
|
|
||||||
path: `imported-${file.name}-${Date.now()}-${i}`,
|
path: `imported-${file.name}-${Date.now()}-${i}`,
|
||||||
size_kb: Math.round(file.size / 1024),
|
size_kb: Math.round(file.size / 1024),
|
||||||
// 使用浏览器读取到的修改时间
|
|
||||||
created_time: new Date(file.lastModified).toLocaleString(),
|
created_time: new Date(file.lastModified).toLocaleString(),
|
||||||
// 关键:保存文件引用,以便点击时读取
|
|
||||||
fileRef: file
|
fileRef: file
|
||||||
};
|
};
|
||||||
bsodList.value.unshift(newItem);
|
bsodList.value.unshift(newItem);
|
||||||
importedCount++;
|
importedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (importedCount > 0) {
|
||||||
triggerToast('导入成功', `已添加 ${importedCount} 个文件到列表`, 'success');
|
triggerToast('导入成功', `已添加 ${importedCount} 个文件到列表`, 'success');
|
||||||
event.target.value = ''; // 重置,允许再次导入同名文件
|
} else {
|
||||||
|
triggerToast('导入忽略', '未选择有效的 .dmp 文件', 'error');
|
||||||
|
}
|
||||||
|
event.target.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(currentTab, (newVal) => { if (newVal === 'bsod' && bsodList.value.length === 0) loadMinidumps(); });
|
watch(currentTab, (newVal) => { if (newVal === 'bsod' && bsodList.value.length === 0) loadMinidumps(); });
|
||||||
|
|||||||
Reference in New Issue
Block a user