use std::io::{Read, Write};
use crate::dbg_backend::{BugcheckInfo, DebugLog};
use crate::error::Result;
use crate::kd::framing::{KdFraming, PACKET_TYPE_KD_DEBUG_IO};
use crate::kd::wire::{read_u16, read_u32, write_u16, write_u32};
use super::{
DBGKD_DEBUG_IO_HEADER_SIZE, DBGKD_DEBUG_IO_MIN_HEADER_SIZE, DBGKD_GET_STRING_API,
DBGKD_PRINT_STRING_API, KD_REFRESH_MESSAGE,
};
pub enum DebugIo<'a> {
PrintString {
text: &'a [u8],
},
GetString {
processor_level: u16,
processor: u16,
prompt: &'a [u8],
},
}
#[derive(Default)]
pub struct BugcheckCapture {
text: String,
info: Option<BugcheckInfo>,
pending_driver: Option<String>,
}
impl BugcheckCapture {
pub fn observe_debug_text(&mut self, text: &[u8]) {
let text = String::from_utf8_lossy(text);
self.text.push_str(&text);
if self.text.len() > 8192 {
let mut keep_from = self.text.len() - 8192;
while !self.text.is_char_boundary(keep_from) {
keep_from += 1;
}
self.text.drain(..keep_from);
}
if self.info.is_none()
&& let Some((code, parameters)) = parse_bugcheck_fatal_system_error(&self.text)
{
self.info = Some(BugcheckInfo {
code,
parameters,
driver: self.pending_driver.clone(),
});
}
if let Some(driver) = parse_bugcheck_driver(&self.text) {
self.pending_driver = Some(driver.clone());
if let Some(info) = self.info.as_mut() {
info.driver = Some(driver);
}
}
}
pub fn finish(&self) -> Option<BugcheckInfo> {
self.info.clone()
}
pub fn code(&self) -> Option<u32> {
self.info.as_ref().map(|info| info.code)
}
fn should_suppress_output(&self) -> bool {
self.text.contains("*** Fatal System Error:") || self.text.contains("Driver at fault:")
}
}
fn parse_first_hex_u64(text: &str) -> Option<u64> {
let marker = text
.find("0x")
.or_else(|| text.find("0X"))
.map(|idx| idx + 2)?;
let hex: String = text[marker..]
.chars()
.take_while(|ch| ch.is_ascii_hexdigit())
.collect();
if hex.is_empty() {
return None;
}
u64::from_str_radix(&hex, 16).ok()
}
fn parse_hex_u64(text: &str) -> Option<u64> {
let trimmed = text.trim();
let hex = trimmed
.strip_prefix("0x")
.or_else(|| trimmed.strip_prefix("0X"))
.unwrap_or(trimmed);
let hex: String = hex
.chars()
.take_while(|ch| ch.is_ascii_hexdigit())
.collect();
if hex.is_empty() {
return None;
}
u64::from_str_radix(&hex, 16).ok()
}
fn parse_bugcheck_fatal_system_error(text: &str) -> Option<(u32, [u64; 4])> {
let marker = "*** Fatal System Error:";
let rest = &text[text.find(marker)? + marker.len()..];
let code = parse_first_hex_u64(rest)? as u32;
let params_start = rest.find('(')? + 1;
let params_end = rest[params_start..].find(')')? + params_start;
let mut params = [0u64; 4];
let mut count = 0usize;
for (slot, value) in params
.iter_mut()
.zip(rest[params_start..params_end].split(','))
{
*slot = parse_hex_u64(value)?;
count += 1;
}
(count == 4).then_some((code, params))
}
fn parse_bugcheck_driver(text: &str) -> Option<String> {
let marker = "Driver at fault:";
let rest = &text[text.find(marker)? + marker.len()..];
let line = rest.lines().next().unwrap_or(rest);
let driver = line.trim().trim_end_matches('.').trim();
(!driver.is_empty()).then(|| driver.to_string())
}
pub fn handle_debug_io<T: Read + Write>(
framing: &mut KdFraming<T>,
payload: &[u8],
detect_kd_refresh: bool,
) -> Result<bool> {
let mut stderr = std::io::stderr();
handle_debug_io_with_output(
framing,
payload,
detect_kd_refresh,
None,
false,
None,
&mut stderr,
)
}
pub fn handle_debug_io_with_output<T: Read + Write, W: Write>(
framing: &mut KdFraming<T>,
payload: &[u8],
detect_kd_refresh: bool,
bugcheck_capture: Option<&mut BugcheckCapture>,
suppress_bugcheck_text: bool,
debug_log: Option<&DebugLog>,
output: &mut W,
) -> Result<bool> {
let mut kd_refresh_seen = false;
match parse_debug_io(payload) {
Some(DebugIo::PrintString { text }) => {
if let Some(log) = debug_log {
log.record(text);
}
let mut suppress_output = false;
if let Some(capture) = bugcheck_capture {
capture.observe_debug_text(text);
suppress_output = suppress_bugcheck_text && capture.should_suppress_output();
}
if !suppress_output {
output.write_all(text)?;
}
kd_refresh_seen = detect_kd_refresh
&& text
.windows(KD_REFRESH_MESSAGE.len())
.any(|w| w == KD_REFRESH_MESSAGE);
if kd_refresh_seen {
kd_trace!("kd: debug_io: refresh print read and ACKed");
}
}
Some(DebugIo::GetString {
processor_level,
processor,
prompt,
}) => {
output.write_all(prompt)?;
let response = get_string_auto_response(prompt);
send_debug_io_response(framing, processor_level, processor, response)?;
}
None => {}
}
Ok(kd_refresh_seen)
}
fn debug_io_data(payload: &[u8], len: usize) -> &[u8] {
let offset = if payload.len() >= DBGKD_DEBUG_IO_HEADER_SIZE + len {
DBGKD_DEBUG_IO_HEADER_SIZE
} else {
DBGKD_DEBUG_IO_MIN_HEADER_SIZE.min(payload.len())
};
let end = offset.saturating_add(len).min(payload.len());
&payload[offset..end]
}
pub fn parse_debug_io(payload: &[u8]) -> Option<DebugIo<'_>> {
if payload.len() < DBGKD_DEBUG_IO_MIN_HEADER_SIZE {
return None;
}
let api = read_u32(payload, 0);
let processor_level = read_u16(payload, 4);
let processor = read_u16(payload, 6);
match api {
DBGKD_PRINT_STRING_API => {
let len = read_u32(payload, 8) as usize;
Some(DebugIo::PrintString {
text: debug_io_data(payload, len),
})
}
DBGKD_GET_STRING_API if payload.len() >= DBGKD_DEBUG_IO_HEADER_SIZE => {
let len = read_u32(payload, 8) as usize;
Some(DebugIo::GetString {
processor_level,
processor,
prompt: debug_io_data(payload, len),
})
}
_ => None,
}
}
fn get_string_auto_response(prompt: &[u8]) -> &'static [u8] {
let text = std::str::from_utf8(prompt).unwrap_or("");
if text.contains("(bic)") || text.contains("(BiC)") {
b"b"
} else {
b""
}
}
fn send_debug_io_response<T: Read + Write>(
framing: &mut KdFraming<T>,
processor_level: u16,
processor: u16,
response: &[u8],
) -> Result<()> {
let mut reply = vec![0u8; DBGKD_DEBUG_IO_HEADER_SIZE + response.len()];
write_u32(&mut reply, 0, DBGKD_GET_STRING_API);
write_u16(&mut reply, 4, processor_level);
write_u16(&mut reply, 6, processor);
write_u32(&mut reply, 12, response.len() as u32);
reply[DBGKD_DEBUG_IO_HEADER_SIZE..].copy_from_slice(response);
framing.send_data(PACKET_TYPE_KD_DEBUG_IO, &reply)
}