use owo_colors::OwoColorize;
use crate::bugchecks::{
BUGCHECK_DATA_SLOTS, BugcheckFault, CurrentBugcheckFailure, CurrentBugcheckResolution,
resolve_current_bugcheck,
};
use crate::dbg_backend::BugcheckInfo;
use crate::target::Target;
use crate::ui;
pub use crate::bugchecks::{
BugcheckAnalysis, BugcheckTrapFrame, CURRENT_KERNEL_RELOAD_WINDOW, analyze_bugcheck,
bugcheck_fault_ip, bugcheck_site, current_bugcheck, looks_like_kernel_pointer,
plausible_bugcheck_code,
};
pub use crate::trapframe::KtrapFrame;
use super::disasm::{format_rflags, print_event_children, wrap_prose};
fn print_unresolved_bugcheck_data(failure: &CurrentBugcheckFailure) {
fn slots_line(label: &str, data: &[u64; BUGCHECK_DATA_SLOTS]) -> String {
format!(
"{} [{:#x}, {:#x}, {:#x}, {:#x}, {:#x}]",
ui::muted(label),
data[0],
data[1],
data[2],
data[3],
data[4]
)
}
outln!(
"{} unable to resolve nt!KiBugCheckData at {:#x}: {}",
ui::badge("BUGCHECK"),
failure.address,
failure.reason
);
let mut children: Vec<String> = Vec::new();
if let Some(data) = &failure.slots {
children.push(slots_line("raw slots", data));
}
if let Some(data) = &failure.dereferenced_slots {
children.push(slots_line("dereferenced slots", data));
}
print_event_children(" ", &children);
}
fn wrapped_dim_tail(prefix: String, hang: usize, prose: &str, col: usize) -> String {
let lines = wrap_prose(prose, col);
let mut out = format!("{prefix}{}", ui::muted(&lines[0]));
for line in &lines[1..] {
out.push_str(&format!("\n{}{}", " ".repeat(hang), ui::muted(line)));
}
out
}
pub fn format_arg_value(value: u64) -> String {
ui::addr(value)
}
pub fn print_bugcheck_info(debugger: &Target, info: &BugcheckInfo) {
print_bugcheck_analysis(&analyze_bugcheck(debugger, info));
}
fn format_bugcheck_fault(fault: &BugcheckFault) -> String {
let address = ui::addr(fault.ip);
if fault.symbol.starts_with("0x") {
address
} else {
format!("{address} {}", ui::symbol(&fault.symbol))
}
}
pub fn print_bugcheck_analysis(analysis: &BugcheckAnalysis) {
outln!(
"{}{}",
ui::badge("BUGCHECK"),
ui::plate(&format!(" {} ({:#010x}) ", analysis.name, analysis.code))
);
let mut children: Vec<String> = Vec::new();
if let Some(driver) = &analysis.driver {
children.push(format!("{} {}", ui::muted("module"), driver.bright_blue()));
}
if let Some(fault) = &analysis.fault {
children.push(format!(
"{} {}",
ui::muted("fault "),
format_bugcheck_fault(fault)
));
}
if let Some(description) = &analysis.description {
children.push(wrapped_dim_tail(
format!("{} ", ui::muted("reason")),
7,
description,
11,
));
}
if let Some(source) = &analysis.source {
children.push(format!("{} {}", ui::muted("source"), ui::muted(source)));
}
children.push(ui::muted("args"));
print_event_children(" ", &children);
let args: Vec<String> = analysis
.args
.iter()
.enumerate()
.map(|(idx, arg)| {
let prefix = format!(
"{} {}",
ui::muted(&format!("#{}", idx + 1)),
format_arg_value(arg.value)
);
if arg.description.is_empty() {
prefix
} else {
wrapped_dim_tail(format!("{prefix} "), 21, &arg.description, 28)
}
})
.collect();
print_event_children(" ", &args);
for trap_frame in &analysis.trap_frames {
outln!();
print_bugcheck_trap_frame(trap_frame);
}
}
pub fn print_bugcheck_trap_frame(trap_frame: &BugcheckTrapFrame) {
match &trap_frame.frame {
Some(frame) => print_ktrap_frame(frame, trap_frame.rip_symbol.as_deref()),
None => outln!(
"{} @ {} (unable to decode: {})",
"trap frame".bold(),
ui::addr(trap_frame.address),
trap_frame.error.as_deref().unwrap_or("unknown error")
),
}
}
pub fn print_ktrap_frame(frame: &KtrapFrame, rip_symbol: Option<&str>) {
outln!("{} @ {}", "trap frame".bold(), ui::addr(frame.address));
outln!(
" rax {} rbx {} rcx {}",
ui::addr(frame.rax),
ui::addr(frame.rbx),
ui::addr(frame.rcx)
);
outln!(
" rdx {} rsi {} rdi {}",
ui::addr(frame.rdx),
ui::addr(frame.rsi),
ui::addr(frame.rdi)
);
outln!(
" rsp {} rbp {} rip {}",
ui::addr(frame.rsp),
ui::addr(frame.rbp),
ui::addr(frame.rip)
);
outln!(
" r8 {} r9 {} r10 {}",
ui::addr(frame.r8),
ui::addr(frame.r9),
ui::addr(frame.r10)
);
outln!(
" r11 {} rfl {}{}",
ui::addr(frame.r11),
ui::addr(frame.eflags as u64),
format_rflags(frame.eflags as u64)
);
outln!(
" cs {:04x} ss {:04x} error code {:#x} irql {} previous mode {}",
frame.cs,
frame.ss,
frame.error_code,
frame.previous_irql,
if frame.previous_mode == 0 {
"kernel"
} else {
"user"
}
);
if let Some(symbol) = rip_symbol {
outln!(" rip => {}", ui::symbol(symbol));
}
}
pub fn print_bugcheck_summary(debugger: &Target, info: Option<&BugcheckInfo>) {
if let Some(info) = info {
print_bugcheck_info(debugger, info);
return;
}
print_bugcheck_summary_from_memory(debugger);
}
pub fn print_bugcheck_summary_from_memory(debugger: &Target) {
match resolve_current_bugcheck(debugger) {
CurrentBugcheckResolution::Resolved(analysis) => print_bugcheck_analysis(&analysis),
CurrentBugcheckResolution::SymbolUnavailable => outln!(
"{} guest is bugchecking (symbol nt!KiBugCheckData unavailable)",
ui::badge("BUGCHECK")
),
CurrentBugcheckResolution::Unresolved(failure) => {
print_unresolved_bugcheck_data(&failure);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn formats_resolved_bugcheck_fault_site() {
let line = format_bugcheck_fault(&BugcheckFault {
ip: 0xffff_f805_f339_1730,
symbol: "myfault+0x1730".to_string(),
driver: Some("myfault.sys".to_string()),
});
assert!(line.contains("fffff805f3391730"));
assert!(line.contains("myfault"));
assert!(line.contains("+0x1730"));
}
#[test]
fn raw_bugcheck_fault_site_is_not_repeated() {
let line = format_bugcheck_fault(&BugcheckFault {
ip: 0xffff_f805_f339_1730,
symbol: "0xfffff805f3391730".to_string(),
driver: None,
});
assert_eq!(line.matches("fffff805f3391730").count(), 1);
}
}