214 lines
9.4 KiB
Rust
214 lines
9.4 KiB
Rust
use crate::models::CrashReportInfo;
|
|
use std::path::Path;
|
|
use std::process::Command;
|
|
|
|
pub struct CrashScanner;
|
|
|
|
impl CrashScanner {
|
|
pub fn scan() -> Vec<CrashReportInfo> {
|
|
let mut crashes = Vec::new();
|
|
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
Self::scan_linux_coredumps(&mut crashes);
|
|
Self::scan_linux_var_crash(&mut crashes);
|
|
Self::scan_linux_dmesg_segfaults(&mut crashes);
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
Self::scan_windows_minidumps(&mut crashes);
|
|
Self::scan_windows_event_crashes(&mut crashes);
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
Self::scan_macos_diagnostic_reports(&mut crashes);
|
|
}
|
|
|
|
// Generic fallback if empty (e.g. BSD or systems without coredumpctl)
|
|
if crashes.is_empty() {
|
|
Self::scan_generic_system_logs(&mut crashes);
|
|
}
|
|
|
|
// Limit to 20 most recent crashes
|
|
crashes.truncate(20);
|
|
crashes
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn scan_linux_coredumps(crashes: &mut Vec<CrashReportInfo>) {
|
|
if let Ok(output) = Command::new("coredumpctl")
|
|
.args(["list", "--no-legend", "-n", "10"])
|
|
.output()
|
|
{
|
|
let text = String::from_utf8_lossy(&output.stdout);
|
|
for line in text.lines() {
|
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
|
if parts.len() >= 5 {
|
|
let time = parts[0..parts.len().saturating_sub(6)].join(" ");
|
|
let pid = parts.iter().find_map(|p| p.parse::<u32>().ok());
|
|
let signal = parts.iter().find(|p| p.starts_with("SIG") || **p == "11" || **p == "6").copied().unwrap_or("SIGSEGV");
|
|
let exe = parts.last().unwrap_or(&"unknown");
|
|
|
|
crashes.push(CrashReportInfo {
|
|
timestamp: if time.is_empty() { "Недавний".to_string() } else { time },
|
|
process_name: exe.to_string(),
|
|
pid,
|
|
signal_or_code: signal.to_string(),
|
|
fault_module: None,
|
|
stack_trace_snippet: format!("Core dump сохранен systemd-coredump для исполняемого файла {}", exe),
|
|
dump_path: Some(format!("coredumpctl info {}", pid.unwrap_or(0))),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn scan_linux_var_crash(crashes: &mut Vec<CrashReportInfo>) {
|
|
let crash_dir = Path::new("/var/crash");
|
|
if let Ok(entries) = std::fs::read_dir(crash_dir) {
|
|
for entry in entries.flatten() {
|
|
let path = entry.path();
|
|
let filename = path.file_name().unwrap_or_default().to_string_lossy().to_string();
|
|
if filename.ends_with(".crash") {
|
|
let proc_name = filename.trim_end_matches(".crash").to_string();
|
|
let metadata = std::fs::metadata(&path).ok();
|
|
let time_str = metadata
|
|
.and_then(|m| m.modified().ok())
|
|
.map(|t| format!("{:?}", t))
|
|
.unwrap_or_else(|| "Архивный".to_string());
|
|
|
|
crashes.push(CrashReportInfo {
|
|
timestamp: time_str,
|
|
process_name: proc_name,
|
|
pid: None,
|
|
signal_or_code: "Crash Dump".to_string(),
|
|
fault_module: None,
|
|
stack_trace_snippet: format!("Дамп аварийного завершения в /var/crash/{}", filename),
|
|
dump_path: Some(path.to_string_lossy().to_string()),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn scan_linux_dmesg_segfaults(crashes: &mut Vec<CrashReportInfo>) {
|
|
// Поиск segfaults в dmesg через неинтерактивный вызов
|
|
if let Ok(output) = Command::new("dmesg").args(["-T", "-l", "err,warn"]).output() {
|
|
let text = String::from_utf8_lossy(&output.stdout);
|
|
for line in text.lines() {
|
|
if line.contains("segfault at") || line.contains("general protection fault") {
|
|
let mut proc = "kernel".to_string();
|
|
let mut fault_mod = None;
|
|
|
|
if let Some(idx) = line.find("[") {
|
|
if let Some(_end) = line[idx..].find("]") {
|
|
let candidate = line[..idx].trim().split_whitespace().last().unwrap_or("");
|
|
if !candidate.is_empty() {
|
|
proc = candidate.to_string();
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(in_idx) = line.find(" in ") {
|
|
let after_in = &line[in_idx + 4..];
|
|
let mod_name = after_in.split_whitespace().next().unwrap_or("");
|
|
if !mod_name.is_empty() {
|
|
fault_mod = Some(mod_name.to_string());
|
|
}
|
|
}
|
|
|
|
crashes.push(CrashReportInfo {
|
|
timestamp: "Из dmesg".to_string(),
|
|
process_name: proc,
|
|
pid: None,
|
|
signal_or_code: "SIGSEGV (Segmentation Fault)".to_string(),
|
|
fault_module: fault_mod,
|
|
stack_trace_snippet: line.trim().to_string(),
|
|
dump_path: None,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
fn scan_windows_minidumps(crashes: &mut Vec<CrashReportInfo>) {
|
|
let minidump_dir = Path::new(r"C:\Windows\Minidump");
|
|
if let Ok(entries) = std::fs::read_dir(minidump_dir) {
|
|
for entry in entries.flatten() {
|
|
let path = entry.path();
|
|
let name = path.file_name().unwrap_or_default().to_string_lossy().to_string();
|
|
if name.ends_with(".dmp") {
|
|
crashes.push(CrashReportInfo {
|
|
timestamp: "BSOD Crash Dump".to_string(),
|
|
process_name: "ntoskrnl.exe (Kernel BSOD)".to_string(),
|
|
pid: Some(4),
|
|
signal_or_code: "BUGCHECK_DUMP".to_string(),
|
|
fault_module: Some("Kernel".to_string()),
|
|
stack_trace_snippet: format!("Дамп аварийного синего экрана смерти: {}", path.display()),
|
|
dump_path: Some(path.to_string_lossy().to_string()),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
fn scan_windows_event_crashes(crashes: &mut Vec<CrashReportInfo>) {
|
|
let ps_cmd = "Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='Application Error'} -MaxEvents 5 -ErrorAction SilentlyContinue | Select-Object TimeCreated, Message | ConvertTo-Json -Compress";
|
|
if let Ok(output) = Command::new("powershell").args(["-NoProfile", "-NonInteractive", "-Command", ps_cmd]).output() {
|
|
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&String::from_utf8_lossy(&output.stdout)) {
|
|
let items = match val {
|
|
serde_json::Value::Array(a) => a,
|
|
serde_json::Value::Object(_) => vec![val],
|
|
_ => vec![],
|
|
};
|
|
for item in items {
|
|
let msg = item["Message"].as_str().unwrap_or("");
|
|
let time = item["TimeCreated"].as_str().unwrap_or("Недавно");
|
|
crashes.push(CrashReportInfo {
|
|
timestamp: time.to_string(),
|
|
process_name: "Application Error".to_string(),
|
|
pid: None,
|
|
signal_or_code: "0xC0000005 (Access Violation)".to_string(),
|
|
fault_module: None,
|
|
stack_trace_snippet: msg.trim().to_string(),
|
|
dump_path: None,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
fn scan_macos_diagnostic_reports(crashes: &mut Vec<CrashReportInfo>) {
|
|
let reports_dir = Path::new("/Library/Logs/DiagnosticReports");
|
|
if let Ok(entries) = std::fs::read_dir(reports_dir) {
|
|
for entry in entries.flatten() {
|
|
let path = entry.path();
|
|
let name = path.file_name().unwrap_or_default().to_string_lossy().to_string();
|
|
if name.ends_with(".ips") || name.ends_with(".crash") {
|
|
let proc = name.split(['_', '-']).next().unwrap_or(&name).to_string();
|
|
crashes.push(CrashReportInfo {
|
|
timestamp: "Diagnostic Report".to_string(),
|
|
process_name: proc,
|
|
pid: None,
|
|
signal_or_code: "EXC_BAD_ACCESS / Crash".to_string(),
|
|
fault_module: None,
|
|
stack_trace_snippet: format!("macOS Crash Report: {}", path.display()),
|
|
dump_path: Some(path.to_string_lossy().to_string()),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn scan_generic_system_logs(_crashes: &mut Vec<CrashReportInfo>) {
|
|
// Fallback placeholder
|
|
}
|
|
}
|