#![allow(dead_code)]
use std::io::{Read, Write};
use crate::error::{Error, Result};
use crate::kd::context;
use crate::kd::{
framing::{DataPacket, KdFraming, PACKET_TYPE_KD_DEBUG_IO, PACKET_TYPE_KD_STATE_MANIPULATE},
print_debug_io,
};
pub const DBGKD_READ_VIRTUAL_MEMORY: u32 = 0x0000_3130;
pub const DBGKD_WRITE_VIRTUAL_MEMORY: u32 = 0x0000_3131;
pub const DBGKD_GET_CONTEXT: u32 = 0x0000_3132;
pub const DBGKD_SET_CONTEXT: u32 = 0x0000_3133;
pub const DBGKD_WRITE_BREAKPOINT: u32 = 0x0000_3134;
pub const DBGKD_RESTORE_BREAKPOINT: u32 = 0x0000_3135;
pub const DBGKD_READ_CONTROL_SPACE: u32 = 0x0000_3137;
pub const DBGKD_CONTINUE_API2: u32 = 0x0000_313C;
pub const DBGKD_GET_VERSION: u32 = 0x0000_3146;
pub const DBGKD_SWITCH_PROCESSOR: u32 = 0x0000_3150;
pub const MANIPULATE_HEADER_SIZE: usize = 56;
const UNION_OFFSET: usize = 16;
pub const DBG_CONTINUE: u32 = 0x0001_0002;
pub const STATUS_SUCCESS: u32 = 0x0000_0000;
fn write_u16(buf: &mut [u8], offset: usize, value: u16) {
buf[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
}
fn write_u32(buf: &mut [u8], offset: usize, value: u32) {
buf[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
}
fn write_u64(buf: &mut [u8], offset: usize, value: u64) {
buf[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
}
fn read_u16(buf: &[u8], offset: usize) -> u16 {
u16::from_le_bytes(buf[offset..offset + 2].try_into().unwrap())
}
fn read_u32(buf: &[u8], offset: usize) -> u32 {
u32::from_le_bytes(buf[offset..offset + 4].try_into().unwrap())
}
fn read_u64(buf: &[u8], offset: usize) -> u64 {
u64::from_le_bytes(buf[offset..offset + 8].try_into().unwrap())
}
fn make_header(api_number: u32, processor: u16) -> [u8; MANIPULATE_HEADER_SIZE] {
let mut hdr = [0u8; MANIPULATE_HEADER_SIZE];
write_u32(&mut hdr, 0, api_number);
write_u16(&mut hdr, 6, processor);
hdr
}
#[derive(Debug, Clone, Copy)]
pub struct ManipulateHeader {
pub api_number: u32,
pub processor: u16,
pub return_status: u32,
}
impl ManipulateHeader {
fn decode(buf: &[u8]) -> Result<Self> {
if buf.len() < MANIPULATE_HEADER_SIZE {
return Err(Error::Kd(format!(
"manipulate header too short: {} bytes",
buf.len()
)));
}
Ok(Self {
api_number: read_u32(buf, 0),
processor: read_u16(buf, 6),
return_status: read_u32(buf, 8),
})
}
}
fn send_manipulate(
framing: &mut KdFraming<impl Read + Write>,
header: &[u8; MANIPULATE_HEADER_SIZE],
data: &[u8],
) -> Result<(ManipulateHeader, Vec<u8>, Vec<u8>)> {
let requested_processor = read_u16(header, 6);
let mut payload = Vec::with_capacity(MANIPULATE_HEADER_SIZE + data.len());
payload.extend_from_slice(header);
payload.extend_from_slice(data);
framing.send_data(PACKET_TYPE_KD_STATE_MANIPULATE, &payload)?;
loop {
let pkt = framing.recv_data()?;
match pkt.packet_type {
PACKET_TYPE_KD_STATE_MANIPULATE => {
if pkt.payload.len() < MANIPULATE_HEADER_SIZE {
return Err(Error::Kd(format!(
"short manipulate reply: {} bytes",
pkt.payload.len()
)));
}
let parsed = ManipulateHeader::decode(&pkt.payload)?;
if parsed.processor != requested_processor {
return Err(Error::Kd(format!(
"reply processor mismatch: expected {}, got {}",
requested_processor, parsed.processor
)));
}
let reply_header = pkt.payload[..MANIPULATE_HEADER_SIZE].to_vec();
let reply_data = pkt.payload[MANIPULATE_HEADER_SIZE..].to_vec();
return Ok((parsed, reply_header, reply_data));
}
PACKET_TYPE_KD_DEBUG_IO => {
print_debug_io(&pkt.payload);
continue;
}
other => {
return Err(Error::Kd(format!(
"unexpected packet type while awaiting manipulate reply: {}",
other
)));
}
}
}
}
fn check_status(header: &ManipulateHeader, api: u32) -> Result<()> {
if header.api_number != api {
return Err(Error::Kd(format!(
"reply api mismatch: expected {:#x}, got {:#x}",
api, header.api_number
)));
}
if (header.return_status & 0x8000_0000) != 0 {
return Err(Error::Kd(format!(
"kernel returned NTSTATUS {:#x} for api {:#x}",
header.return_status, api
)));
}
Ok(())
}
pub fn recv_packet<T: Read + Write>(framing: &mut KdFraming<T>) -> Result<DataPacket> {
framing.recv_data()
}
#[derive(Debug, Clone, Copy)]
pub struct Version {
pub major: u16,
pub minor: u16,
pub protocol_version: u8,
pub kd_secondary_version: u8,
pub flags: u16,
pub machine_type: u16,
pub max_packet_type: u8,
pub max_state_change: u8,
pub max_manipulate: u8,
pub simulation: u8,
pub kern_base: u64,
pub ps_loaded_module_list: u64,
pub debugger_data_list: u64,
}
pub fn get_version<T: Read + Write>(framing: &mut KdFraming<T>, processor: u16) -> Result<Version> {
let header = make_header(DBGKD_GET_VERSION, processor);
let (parsed, reply_header, _) = send_manipulate(framing, &header, &[])?;
check_status(&parsed, DBGKD_GET_VERSION)?;
let u = UNION_OFFSET;
Ok(Version {
major: read_u16(&reply_header, u + 0),
minor: read_u16(&reply_header, u + 2),
protocol_version: reply_header[u + 4],
kd_secondary_version: reply_header[u + 5],
flags: read_u16(&reply_header, u + 6),
machine_type: read_u16(&reply_header, u + 8),
max_packet_type: reply_header[u + 10],
max_state_change: reply_header[u + 11],
max_manipulate: reply_header[u + 12],
simulation: reply_header[u + 13],
kern_base: read_u64(&reply_header, u + 16),
ps_loaded_module_list: read_u64(&reply_header, u + 24),
debugger_data_list: read_u64(&reply_header, u + 32),
})
}
pub fn get_context<T: Read + Write>(framing: &mut KdFraming<T>, processor: u16) -> Result<Vec<u8>> {
let mut header = make_header(DBGKD_GET_CONTEXT, processor);
write_u32(&mut header, UNION_OFFSET, context::CONTEXT_ALL);
let (parsed, _, data) = send_manipulate(framing, &header, &[])?;
check_status(&parsed, DBGKD_GET_CONTEXT)?;
Ok(data)
}
pub fn set_context<T: Read + Write>(
framing: &mut KdFraming<T>,
processor: u16,
context: &[u8],
) -> Result<()> {
let mut header = make_header(DBGKD_SET_CONTEXT, processor);
if context.len() >= 0x34 {
let flags = u32::from_le_bytes(context[0x30..0x34].try_into().unwrap());
write_u32(&mut header, UNION_OFFSET, flags);
}
let (parsed, _, _) = send_manipulate(framing, &header, context)?;
check_status(&parsed, DBGKD_SET_CONTEXT)?;
Ok(())
}
pub fn read_virtual_memory<T: Read + Write>(
framing: &mut KdFraming<T>,
processor: u16,
addr: u64,
len: u32,
) -> Result<Vec<u8>> {
let mut header = make_header(DBGKD_READ_VIRTUAL_MEMORY, processor);
write_u64(&mut header, UNION_OFFSET, addr);
write_u32(&mut header, UNION_OFFSET + 8, len);
let (parsed, _, data) = send_manipulate(framing, &header, &[])?;
check_status(&parsed, DBGKD_READ_VIRTUAL_MEMORY)?;
Ok(data)
}
pub fn read_control_space<T: Read + Write>(
framing: &mut KdFraming<T>,
processor: u16,
base: u64,
len: u32,
) -> Result<Vec<u8>> {
let mut header = make_header(DBGKD_READ_CONTROL_SPACE, processor);
write_u64(&mut header, UNION_OFFSET, base);
write_u32(&mut header, UNION_OFFSET + 8, len);
let (parsed, _, data) = send_manipulate(framing, &header, &[])?;
check_status(&parsed, DBGKD_READ_CONTROL_SPACE)?;
Ok(data)
}
pub fn write_virtual_memory<T: Read + Write>(
framing: &mut KdFraming<T>,
processor: u16,
addr: u64,
data: &[u8],
) -> Result<u32> {
let mut header = make_header(DBGKD_WRITE_VIRTUAL_MEMORY, processor);
write_u64(&mut header, UNION_OFFSET, addr);
write_u32(&mut header, UNION_OFFSET + 8, data.len() as u32);
let (parsed, reply_header, _) = send_manipulate(framing, &header, data)?;
check_status(&parsed, DBGKD_WRITE_VIRTUAL_MEMORY)?;
Ok(read_u32(&reply_header, UNION_OFFSET + 12))
}
pub fn write_breakpoint<T: Read + Write>(
framing: &mut KdFraming<T>,
processor: u16,
addr: u64,
) -> Result<u32> {
let mut header = make_header(DBGKD_WRITE_BREAKPOINT, processor);
write_u64(&mut header, UNION_OFFSET, addr);
let (parsed, reply_header, _) = send_manipulate(framing, &header, &[])?;
check_status(&parsed, DBGKD_WRITE_BREAKPOINT)?;
Ok(read_u32(&reply_header, UNION_OFFSET + 8))
}
pub fn restore_breakpoint<T: Read + Write>(
framing: &mut KdFraming<T>,
processor: u16,
handle: u32,
) -> Result<()> {
let mut header = make_header(DBGKD_RESTORE_BREAKPOINT, processor);
write_u32(&mut header, UNION_OFFSET, handle);
let (parsed, _, _) = send_manipulate(framing, &header, &[])?;
check_status(&parsed, DBGKD_RESTORE_BREAKPOINT)?;
Ok(())
}
pub fn continue_api2<T: Read + Write>(
framing: &mut KdFraming<T>,
processor: u16,
continue_status: u32,
trace: bool,
) -> Result<()> {
let mut header = make_header(DBGKD_CONTINUE_API2, processor);
write_u32(&mut header, UNION_OFFSET, continue_status);
write_u32(&mut header, UNION_OFFSET + 4, if trace { 1 } else { 0 });
let payload_len = MANIPULATE_HEADER_SIZE;
let mut payload = Vec::with_capacity(payload_len);
payload.extend_from_slice(&header);
framing.send_data(PACKET_TYPE_KD_STATE_MANIPULATE, &payload)?;
Ok(())
}
pub fn switch_processor<T: Read + Write>(framing: &mut KdFraming<T>, target: u16) -> Result<()> {
let header = make_header(DBGKD_SWITCH_PROCESSOR, target);
framing.send_data(PACKET_TYPE_KD_STATE_MANIPULATE, &header)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Cursor, Read, Write};
use crate::kd::framing::{
KdFraming, PACKET_TYPE_KD_ACKNOWLEDGE, PACKET_TYPE_KD_STATE_MANIPULATE,
};
struct Loopback {
inbound: Cursor<Vec<u8>>,
outbound: Vec<u8>,
}
impl Loopback {
fn new(inbound: Vec<u8>) -> Self {
Self {
inbound: Cursor::new(inbound),
outbound: Vec::new(),
}
}
}
impl Read for Loopback {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.inbound.read(buf)
}
}
impl Write for Loopback {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.outbound.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
fn ack_then_reply(outbound_id: u32, reply_id: u32, reply_payload: &[u8]) -> Vec<u8> {
let mut stream = Vec::new();
let ack_hdr = [
0x69,
0x69,
0x69,
0x69,
PACKET_TYPE_KD_ACKNOWLEDGE.to_le_bytes()[0],
PACKET_TYPE_KD_ACKNOWLEDGE.to_le_bytes()[1],
0,
0,
outbound_id.to_le_bytes()[0],
outbound_id.to_le_bytes()[1],
outbound_id.to_le_bytes()[2],
outbound_id.to_le_bytes()[3],
0,
0,
0,
0,
];
stream.extend_from_slice(&ack_hdr);
let checksum: u32 = reply_payload
.iter()
.fold(0u32, |acc, &b| acc.wrapping_add(b as u32));
let mut data_hdr = [0u8; 16];
data_hdr[0..4].copy_from_slice(&0x30303030u32.to_le_bytes());
data_hdr[4..6].copy_from_slice(&PACKET_TYPE_KD_STATE_MANIPULATE.to_le_bytes());
data_hdr[6..8].copy_from_slice(&(reply_payload.len() as u16).to_le_bytes());
data_hdr[8..12].copy_from_slice(&reply_id.to_le_bytes());
data_hdr[12..16].copy_from_slice(&checksum.to_le_bytes());
stream.extend_from_slice(&data_hdr);
stream.extend_from_slice(reply_payload);
stream.push(0xAA);
stream
}
fn build_reply(api: u32, processor: u16, union_body: &[u8], data: &[u8]) -> Vec<u8> {
let mut payload = vec![0u8; MANIPULATE_HEADER_SIZE];
write_u32(&mut payload, 0, api);
write_u16(&mut payload, 6, processor);
let end = (UNION_OFFSET + union_body.len()).min(payload.len());
payload[UNION_OFFSET..end].copy_from_slice(&union_body[..end - UNION_OFFSET]);
payload.extend_from_slice(data);
payload
}
const INITIAL_PACKET_ID: u32 = 0x80800000;
const SYNC_PACKET_ID: u32 = 0x00000800;
#[test]
fn make_header_sets_fixed_prefix() {
let h = make_header(0x3132, 2);
assert_eq!(read_u32(&h, 0), 0x3132);
assert_eq!(read_u16(&h, 6), 2);
assert_eq!(h.len(), MANIPULATE_HEADER_SIZE);
}
#[test]
fn get_version_round_trip() {
let mut union_body = vec![0u8; 40];
union_body[0..2].copy_from_slice(&10u16.to_le_bytes()); union_body[2..4].copy_from_slice(&0u16.to_le_bytes()); union_body[4] = 6; union_body[5] = 0; union_body[8..10].copy_from_slice(&0x8664u16.to_le_bytes()); union_body[16..24].copy_from_slice(&0xfffff80012345000u64.to_le_bytes()); union_body[24..32].copy_from_slice(&0xfffff80087654321u64.to_le_bytes()); union_body[32..40].copy_from_slice(&0xfffff800deadbeefu64.to_le_bytes());
let reply = build_reply(DBGKD_GET_VERSION, 0, &union_body, &[]);
let stream = ack_then_reply(
(INITIAL_PACKET_ID | SYNC_PACKET_ID) & !SYNC_PACKET_ID,
INITIAL_PACKET_ID,
&reply,
);
let mut framing = KdFraming::new(Loopback::new(stream));
let v = get_version(&mut framing, 0).unwrap();
assert_eq!(v.major, 10);
assert_eq!(v.protocol_version, 6);
assert_eq!(v.machine_type, 0x8664);
assert_eq!(v.kern_base, 0xfffff80012345000);
assert_eq!(v.ps_loaded_module_list, 0xfffff80087654321);
assert_eq!(v.debugger_data_list, 0xfffff800deadbeef);
}
#[test]
fn get_context_returns_reply_data() {
let ctx_bytes: Vec<u8> = (0..1232u32).map(|i| (i & 0xff) as u8).collect();
let reply = build_reply(DBGKD_GET_CONTEXT, 0, &[], &ctx_bytes);
let stream = ack_then_reply(
(INITIAL_PACKET_ID | SYNC_PACKET_ID) & !SYNC_PACKET_ID,
INITIAL_PACKET_ID,
&reply,
);
let mut framing = KdFraming::new(Loopback::new(stream));
let ctx = get_context(&mut framing, 0).unwrap();
assert_eq!(ctx, ctx_bytes);
let out = &framing.transport_ref().outbound;
let req_header = &out[16..16 + MANIPULATE_HEADER_SIZE];
assert_eq!(read_u32(req_header, UNION_OFFSET), context::CONTEXT_ALL);
}
#[test]
fn read_control_space_returns_reply_data() {
let special_bytes: Vec<u8> = (0..168u32).map(|i| (255 - (i & 0xff)) as u8).collect();
let reply = build_reply(DBGKD_READ_CONTROL_SPACE, 1, &[], &special_bytes);
let stream = ack_then_reply(
(INITIAL_PACKET_ID | SYNC_PACKET_ID) & !SYNC_PACKET_ID,
INITIAL_PACKET_ID,
&reply,
);
let mut framing = KdFraming::new(Loopback::new(stream));
let data = read_control_space(&mut framing, 1, 2, 168).unwrap();
assert_eq!(data, special_bytes);
let out = &framing.transport_ref().outbound;
let req_header = &out[16..16 + MANIPULATE_HEADER_SIZE];
assert_eq!(read_u32(req_header, 0), DBGKD_READ_CONTROL_SPACE);
assert_eq!(read_u16(req_header, 6), 1);
assert_eq!(read_u64(req_header, UNION_OFFSET), 2);
assert_eq!(read_u32(req_header, UNION_OFFSET + 8), 168);
}
#[test]
fn write_breakpoint_returns_handle() {
let mut union_body = vec![0u8; 12];
union_body[0..8].copy_from_slice(&0xfffff80000123456u64.to_le_bytes());
union_body[8..12].copy_from_slice(&7u32.to_le_bytes());
let reply = build_reply(DBGKD_WRITE_BREAKPOINT, 0, &union_body, &[]);
let stream = ack_then_reply(
(INITIAL_PACKET_ID | SYNC_PACKET_ID) & !SYNC_PACKET_ID,
INITIAL_PACKET_ID,
&reply,
);
let mut framing = KdFraming::new(Loopback::new(stream));
let handle = write_breakpoint(&mut framing, 0, 0xfffff80000123456).unwrap();
assert_eq!(handle, 7);
let out = &framing.transport_ref().outbound;
let payload_start = 16;
let req_header = &out[payload_start..payload_start + MANIPULATE_HEADER_SIZE];
assert_eq!(read_u32(req_header, 0), DBGKD_WRITE_BREAKPOINT);
assert_eq!(read_u64(req_header, UNION_OFFSET), 0xfffff80000123456);
}
#[test]
fn continue_api2_sends_request_without_waiting_for_reply() {
let ack = {
let outbound_id = (INITIAL_PACKET_ID | SYNC_PACKET_ID) & !SYNC_PACKET_ID;
let mut hdr = [0u8; 16];
hdr[0..4].copy_from_slice(&0x69696969u32.to_le_bytes());
hdr[4..6].copy_from_slice(&PACKET_TYPE_KD_ACKNOWLEDGE.to_le_bytes());
hdr[8..12].copy_from_slice(&outbound_id.to_le_bytes());
hdr.to_vec()
};
let mut framing = KdFraming::new(Loopback::new(ack));
continue_api2(&mut framing, 0, DBG_CONTINUE, false).unwrap();
let out = &framing.transport_ref().outbound;
let req_header = &out[16..16 + MANIPULATE_HEADER_SIZE];
assert_eq!(read_u32(req_header, 0), DBGKD_CONTINUE_API2);
assert_eq!(read_u32(req_header, UNION_OFFSET), DBG_CONTINUE);
assert_eq!(read_u32(req_header, UNION_OFFSET + 4), 0); }
#[test]
fn continue_api2_with_trace_sets_trace_flag() {
let ack = {
let outbound_id = (INITIAL_PACKET_ID | SYNC_PACKET_ID) & !SYNC_PACKET_ID;
let mut hdr = [0u8; 16];
hdr[0..4].copy_from_slice(&0x69696969u32.to_le_bytes());
hdr[4..6].copy_from_slice(&PACKET_TYPE_KD_ACKNOWLEDGE.to_le_bytes());
hdr[8..12].copy_from_slice(&outbound_id.to_le_bytes());
hdr.to_vec()
};
let mut framing = KdFraming::new(Loopback::new(ack));
continue_api2(&mut framing, 0, DBG_CONTINUE, true).unwrap();
let out = &framing.transport_ref().outbound;
let req_header = &out[16..16 + MANIPULATE_HEADER_SIZE];
assert_eq!(read_u32(req_header, UNION_OFFSET + 4), 1);
}
#[test]
fn manipulate_reply_rejects_wrong_processor() {
let reply = build_reply(DBGKD_GET_CONTEXT, 1, &[], &[]);
let stream = ack_then_reply(
(INITIAL_PACKET_ID | SYNC_PACKET_ID) & !SYNC_PACKET_ID,
INITIAL_PACKET_ID,
&reply,
);
let mut framing = KdFraming::new(Loopback::new(stream));
let err = get_context(&mut framing, 0).unwrap_err();
match err {
Error::Kd(msg) => assert!(msg.contains("processor mismatch")),
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn check_status_rejects_negative_ntstatus() {
let h = ManipulateHeader {
api_number: DBGKD_GET_CONTEXT,
processor: 0,
return_status: 0xC000_0005, };
let err = check_status(&h, DBGKD_GET_CONTEXT).unwrap_err();
match err {
Error::Kd(msg) => assert!(msg.contains("NTSTATUS")),
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn check_status_rejects_api_mismatch() {
let h = ManipulateHeader {
api_number: DBGKD_GET_VERSION,
processor: 0,
return_status: STATUS_SUCCESS,
};
assert!(check_status(&h, DBGKD_GET_CONTEXT).is_err());
}
}