use std::collections::BTreeMap;
pub const WAIT_OBJECT_HANDLE_BASE: u32 = 0x6B00_0000;
pub const PRIORITY_NORMAL: i32 = 0;
pub const INSTRUCTIONS_PER_MS: u64 = 1_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThreadStatus {
Ready,
Running,
Waiting,
Suspended,
Terminated,
}
#[derive(Debug, Clone)]
pub enum WaitCondition {
Sleep { resume_after_instructions: u64 },
Object {
handle: u32,
timeout_after: Option<u64>,
},
Multiple {
handles: Vec<u32>,
wait_all: bool,
timeout_after: Option<u64>,
},
}
#[derive(Debug, Clone)]
pub enum WaitObject {
Event { signaled: bool, manual_reset: bool },
Mutex { owner: Option<u32>, recursion: u32 },
Semaphore { count: u32, max: u32 },
Thread { tid: u32 },
Process { pid: u32 },
CriticalSection {
guest_addr: u32,
owner: Option<u32>,
recursion: u32,
},
Pipe { pipe_id: u32, is_read_end: bool },
}
#[derive(Debug, Clone)]
pub enum YieldRequest {
Wait(WaitCondition),
Yield,
Exit { code: u32 },
}
#[derive(Default)]
pub struct Scheduler {
pub objects: BTreeMap<u32, WaitObject>,
pub critical_sections: BTreeMap<u32, u32>,
pub named_objects: BTreeMap<String, u32>,
pub pipes: BTreeMap<u32, PipeBuffer>,
pub next_pipe_index: u32,
pub next_handle_index: u32,
pub instructions_global: u64,
pub quantum_default: u32,
}
#[derive(Debug, Clone, Default)]
pub struct PipeBuffer {
pub bytes: std::collections::VecDeque<u8>,
pub closed_ends: u8,
}
impl Scheduler {
#[must_use]
pub fn new() -> Self {
Scheduler {
objects: BTreeMap::new(),
critical_sections: BTreeMap::new(),
named_objects: BTreeMap::new(),
pipes: BTreeMap::new(),
next_pipe_index: 0,
next_handle_index: 0,
instructions_global: 0,
quantum_default: crate::win32::DEFAULT_QUANTUM,
}
}
pub fn insert_object(&mut self, object: WaitObject) -> u32 {
let handle = WAIT_OBJECT_HANDLE_BASE.wrapping_add(self.next_handle_index);
self.next_handle_index = self.next_handle_index.wrapping_add(1);
self.objects.insert(handle, object);
handle
}
#[must_use]
pub fn lookup_named(&self, name: &str) -> Option<u32> {
self.named_objects.get(&name.to_ascii_lowercase()).copied()
}
pub fn register_named(&mut self, name: &str, handle: u32) {
self.named_objects.insert(name.to_ascii_lowercase(), handle);
}
pub fn insert_pipe(&mut self) -> u32 {
let id = self.next_pipe_index;
self.next_pipe_index = self.next_pipe_index.wrapping_add(1);
self.pipes.insert(id, PipeBuffer::default());
id
}
#[must_use]
pub fn owns(&self, handle: u32) -> bool {
self.objects.contains_key(&handle)
}
pub fn critical_section_handle(&mut self, guest_addr: u32) -> u32 {
if let Some(&h) = self.critical_sections.get(&guest_addr) {
return h;
}
let h = self.insert_object(WaitObject::CriticalSection {
guest_addr,
owner: None,
recursion: 0,
});
self.critical_sections.insert(guest_addr, h);
h
}
}
#[must_use]
pub fn object_is_signaled(obj: &WaitObject) -> bool {
match obj {
WaitObject::Event { signaled, .. } => *signaled,
WaitObject::Mutex { owner, .. } => owner.is_none(),
WaitObject::Semaphore { count, .. } => *count > 0,
WaitObject::Thread { .. } | WaitObject::Process { .. } => {
false
}
WaitObject::CriticalSection { owner, .. } => owner.is_none(),
WaitObject::Pipe { .. } => true,
}
}
pub fn waiters_on(
threads: &std::collections::BTreeMap<u32, crate::win32::ThreadState>,
handle: u32,
) -> Vec<u32> {
let mut out: Vec<u32> = threads
.iter()
.filter(|(_, t)| matches!(t.status, ThreadStatus::Waiting))
.filter(|(_, t)| match &t.wait {
Some(WaitCondition::Object { handle: h, .. }) => *h == handle,
Some(WaitCondition::Multiple { handles, .. }) => handles.contains(&handle),
_ => false,
})
.map(|(tid, _)| *tid)
.collect();
out.sort_unstable();
out
}
pub fn consume_signal_if_auto_reset(obj: &mut WaitObject, waker_tid: u32) {
match obj {
WaitObject::Event {
signaled,
manual_reset,
} => {
if !*manual_reset {
*signaled = false;
}
}
WaitObject::Mutex { owner, recursion } => {
*owner = Some(waker_tid);
*recursion = 1;
}
WaitObject::Semaphore { count, .. } => {
*count = count.saturating_sub(1);
}
WaitObject::CriticalSection {
owner, recursion, ..
} => {
*owner = Some(waker_tid);
*recursion = 1;
}
WaitObject::Thread { .. } | WaitObject::Process { .. } => {
}
WaitObject::Pipe { .. } => {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn event_signaled_state_round_trip() {
let e_manual = WaitObject::Event {
signaled: true,
manual_reset: true,
};
let e_auto = WaitObject::Event {
signaled: true,
manual_reset: false,
};
assert!(object_is_signaled(&e_manual));
assert!(object_is_signaled(&e_auto));
let mut a = e_auto.clone();
consume_signal_if_auto_reset(&mut a, 1);
assert!(!object_is_signaled(&a));
let mut m = e_manual.clone();
consume_signal_if_auto_reset(&mut m, 1);
assert!(object_is_signaled(&m));
}
#[test]
fn mutex_signaled_iff_unowned() {
let mut m = WaitObject::Mutex {
owner: None,
recursion: 0,
};
assert!(object_is_signaled(&m));
consume_signal_if_auto_reset(&mut m, 7);
assert!(!object_is_signaled(&m));
match m {
WaitObject::Mutex { owner, recursion } => {
assert_eq!(owner, Some(7));
assert_eq!(recursion, 1);
}
_ => panic!("not a mutex"),
}
}
#[test]
fn semaphore_decrements_on_consume() {
let mut s = WaitObject::Semaphore { count: 3, max: 10 };
consume_signal_if_auto_reset(&mut s, 1);
match s {
WaitObject::Semaphore { count, .. } => assert_eq!(count, 2),
_ => panic!(),
}
}
#[test]
fn scheduler_insert_handles_are_unique() {
let mut s = Scheduler::new();
let h1 = s.insert_object(WaitObject::Event {
signaled: false,
manual_reset: false,
});
let h2 = s.insert_object(WaitObject::Mutex {
owner: None,
recursion: 0,
});
assert_ne!(h1, h2);
assert!(s.owns(h1));
assert!(s.owns(h2));
assert!(!s.owns(0xDEAD_BEEF));
}
#[test]
fn critical_section_minted_once_per_guest_addr() {
let mut s = Scheduler::new();
let a = s.critical_section_handle(0x1000);
let b = s.critical_section_handle(0x1000);
let c = s.critical_section_handle(0x2000);
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn sleep_yield_request_shape() {
let r = YieldRequest::Wait(WaitCondition::Sleep {
resume_after_instructions: 1234,
});
match r {
YieldRequest::Wait(WaitCondition::Sleep {
resume_after_instructions,
}) => {
assert_eq!(resume_after_instructions, 1234);
}
_ => panic!("unexpected variant shape"),
}
}
}