use std::{
collections::HashMap,
fmt,
sync::{Arc, RwLock},
};
use crate::{
emulation::{
capture::CaptureContext,
engine::{EmulationError, SyntheticMethodBody},
exception::ThreadExceptionState,
fakeobjects::SharedFakeObjects,
filesystem::VirtualFs,
memory::{AddressSpace, DelegateEntry, HeapObject, ManagedHeap},
process::EmulationConfig,
runtime::RuntimeState,
thread::ThreadContext,
value::PointerTarget,
ArgumentStorage, EmValue, EvaluationStack, HeapRef, LocalVariables, ManagedPointer,
ThreadId,
},
metadata::{
token::Token,
typesystem::{CilFlavor, PointerSize},
},
CilObject, Error, Result,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ThreadState {
#[default]
Ready,
Running,
Waiting(WaitReason),
Completed,
Faulted,
Aborted,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WaitReason {
Monitor(HeapRef),
Event(HeapRef),
Thread(ThreadId),
Sleep {
until_instruction: u64,
},
Mutex(HeapRef),
Semaphore(HeapRef),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum ThreadPriority {
Lowest = 0,
BelowNormal = 1,
#[default]
Normal = 2,
AboveNormal = 3,
Highest = 4,
}
#[derive(Clone, Debug)]
pub struct ThreadCallFrame {
method: Token,
return_method: Option<Token>,
return_offset: u32,
locals: LocalVariables,
arguments: ArgumentStorage,
instruction_offset: u32,
expects_return: bool,
caller_stack: Vec<EmValue>,
saved_leave_target: Option<u32>,
is_reflection_invoke: bool,
is_cctor: bool,
type_type_args: Option<Vec<Token>>,
method_type_args: Option<Vec<Token>>,
call_id: u64,
assembly_index: Option<u8>,
}
impl ThreadCallFrame {
#[must_use]
pub fn new(
method: Token,
return_method: Option<Token>,
return_offset: u32,
local_types: Vec<CilFlavor>,
args: Vec<(EmValue, CilFlavor)>,
expects_return: bool,
) -> Self {
let (arg_values, arg_types): (Vec<_>, Vec<_>) = args.into_iter().unzip();
Self {
method,
return_method,
return_offset,
locals: LocalVariables::new(local_types),
arguments: ArgumentStorage::new(arg_values, arg_types),
instruction_offset: 0,
expects_return,
caller_stack: Vec::new(),
saved_leave_target: None,
is_reflection_invoke: false,
is_cctor: false,
type_type_args: None,
method_type_args: None,
call_id: 0,
assembly_index: None,
}
}
#[must_use]
pub fn method(&self) -> Token {
self.method
}
#[must_use]
pub fn return_method(&self) -> Option<Token> {
self.return_method
}
#[must_use]
pub fn return_offset(&self) -> u32 {
self.return_offset
}
#[must_use]
pub fn ip(&self) -> u32 {
self.instruction_offset
}
pub fn set_ip(&mut self, offset: u32) {
self.instruction_offset = offset;
}
pub fn advance_ip(&mut self, delta: u32) {
self.instruction_offset += delta;
}
#[must_use]
pub fn expects_return(&self) -> bool {
self.expects_return
}
#[must_use]
pub fn local_count(&self) -> usize {
self.locals.count()
}
#[must_use]
pub fn get_local(&self, index: u16) -> Option<&EmValue> {
self.locals.get(index as usize).ok()
}
pub fn set_local(&mut self, index: u16, value: EmValue) -> bool {
self.locals.set(index as usize, value).is_ok()
}
#[must_use]
pub fn local_type(&self, index: u16) -> Option<&CilFlavor> {
self.locals.get_type(index as usize).ok()
}
#[must_use]
pub fn argument_count(&self) -> usize {
self.arguments.count()
}
#[must_use]
pub fn get_argument(&self, index: u16) -> Option<&EmValue> {
self.arguments.get(index as usize).ok()
}
pub fn set_argument(&mut self, index: u16, value: EmValue) -> bool {
self.arguments.set(index as usize, value).is_ok()
}
#[must_use]
pub fn argument_type(&self, index: u16) -> Option<&CilFlavor> {
self.arguments.get_type(index as usize).ok()
}
pub fn save_caller_stack(&mut self, stack: Vec<EmValue>) {
self.caller_stack = stack;
}
pub fn take_caller_stack(&mut self) -> Vec<EmValue> {
std::mem::take(&mut self.caller_stack)
}
#[must_use]
pub fn caller_stack(&self) -> &[EmValue] {
&self.caller_stack
}
pub fn save_leave_target(&mut self, target: Option<u32>) {
self.saved_leave_target = target;
}
pub fn take_saved_leave_target(&mut self) -> Option<u32> {
self.saved_leave_target.take()
}
pub fn set_reflection_invoke(&mut self) {
self.is_reflection_invoke = true;
}
#[must_use]
pub fn is_reflection_invoke(&self) -> bool {
self.is_reflection_invoke
}
pub fn set_is_cctor(&mut self) {
self.is_cctor = true;
}
#[must_use]
pub fn is_cctor(&self) -> bool {
self.is_cctor
}
pub fn set_type_type_args(&mut self, args: Vec<Token>) {
self.type_type_args = Some(args);
}
#[must_use]
pub fn type_type_args(&self) -> Option<&[Token]> {
self.type_type_args.as_deref()
}
pub fn set_method_type_args(&mut self, args: Vec<Token>) {
self.method_type_args = Some(args);
}
#[must_use]
pub fn method_type_args(&self) -> Option<&[Token]> {
self.method_type_args.as_deref()
}
pub fn set_call_id(&mut self, call_id: u64) {
self.call_id = call_id;
}
#[must_use]
pub fn call_id(&self) -> u64 {
self.call_id
}
pub fn set_assembly_index(&mut self, index: Option<u8>) {
self.assembly_index = index;
}
#[must_use]
pub fn assembly_index(&self) -> Option<u8> {
self.assembly_index
}
#[must_use]
pub fn locals(&self) -> &LocalVariables {
&self.locals
}
pub fn locals_mut(&mut self) -> &mut LocalVariables {
&mut self.locals
}
#[must_use]
pub fn arguments(&self) -> &ArgumentStorage {
&self.arguments
}
pub fn arguments_mut(&mut self) -> &mut ArgumentStorage {
&mut self.arguments
}
}
pub struct EmulationThread {
id: ThreadId,
name: Option<String>,
priority: ThreadPriority,
state: ThreadState,
call_stack: Vec<ThreadCallFrame>,
eval_stack: EvaluationStack,
exception_state: ThreadExceptionState,
tls: HashMap<Token, EmValue>,
context: Arc<ThreadContext>,
instructions_executed: u64,
return_value: Option<EmValue>,
multicast_state: Option<MulticastState>,
}
#[derive(Debug, Clone)]
pub struct MulticastState {
pub remaining_entries: Vec<DelegateEntry>,
pub delegate_args: Vec<EmValue>,
pub dispatch_depth: usize,
}
#[derive(Debug, Clone)]
pub struct ReflectionInvokeRequest {
pub method_token: Token,
pub this_ref: Option<EmValue>,
pub args: Vec<EmValue>,
pub method_type_args: Option<Vec<Token>>,
}
impl fmt::Debug for EmulationThread {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EmulationThread")
.field("id", &self.id)
.field("name", &self.name)
.field("priority", &self.priority)
.field("state", &self.state)
.field("call_stack", &self.call_stack)
.field("eval_stack", &self.eval_stack)
.field("exception_state", &self.exception_state)
.field("tls", &self.tls)
.field("context", &"...")
.field("instructions_executed", &self.instructions_executed)
.field("return_value", &self.return_value)
.finish_non_exhaustive()
}
}
impl EmulationThread {
pub fn new(id: ThreadId, context: Arc<ThreadContext>) -> Self {
Self {
id,
name: None,
priority: ThreadPriority::Normal,
state: ThreadState::Ready,
call_stack: Vec::new(),
eval_stack: EvaluationStack::new(1000),
exception_state: ThreadExceptionState::new(),
tls: HashMap::new(),
context,
instructions_executed: 0,
return_value: None,
multicast_state: None,
}
}
pub fn main(context: Arc<ThreadContext>) -> Self {
let mut thread = Self::new(ThreadId::MAIN, context);
thread.name = Some("Main".to_string());
thread
}
#[must_use]
pub fn context(&self) -> &Arc<ThreadContext> {
&self.context
}
#[must_use]
pub fn fake_objects(&self) -> &SharedFakeObjects {
&self.context.fake_objects
}
#[must_use]
pub fn virtual_fs(&self) -> &Arc<VirtualFs> {
&self.context.virtual_fs
}
#[must_use]
pub fn runtime_state(&self) -> &Arc<RwLock<RuntimeState>> {
&self.context.runtime
}
#[must_use]
pub fn config(&self) -> &EmulationConfig {
&self.context.config
}
#[must_use]
pub fn capture(&self) -> &Arc<CaptureContext> {
&self.context.capture
}
#[must_use]
pub fn assembly(&self) -> Option<&Arc<CilObject>> {
self.context.assembly.as_ref()
}
pub fn register_synthetic_method(&self, body: SyntheticMethodBody) -> Token {
self.context.register_synthetic_method(body)
}
#[must_use]
pub fn type_token_to_cil_flavor(&self, token: Token) -> Option<CilFlavor> {
let asm = self.context.assembly.as_ref()?;
asm.types().get(&token).map(|t| t.flavor().clone())
}
#[must_use]
pub fn resolve_type_token(&self, namespace: &str, name: &str) -> Option<Token> {
if let Some(asm) = &self.context.assembly {
let fullname = if namespace.is_empty() {
name.to_string()
} else {
format!("{namespace}.{name}")
};
if let Some(cil_type) = asm.types().get_by_fullname(&fullname, true) {
return Some(cil_type.token);
}
}
if let Ok(state) = self.context.runtime.read() {
if let Some((_, token)) = state
.app_domain()
.find_type_across_assemblies(namespace, name)
{
return Some(token);
}
}
None
}
#[must_use]
pub fn id(&self) -> ThreadId {
self.id
}
#[must_use]
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn set_name(&mut self, name: impl Into<String>) {
self.name = Some(name.into());
}
#[must_use]
pub fn priority(&self) -> ThreadPriority {
self.priority
}
pub fn set_priority(&mut self, priority: ThreadPriority) {
self.priority = priority;
}
#[must_use]
pub fn state(&self) -> ThreadState {
self.state
}
pub fn set_state(&mut self, state: ThreadState) {
self.state = state;
}
#[must_use]
pub fn is_ready(&self) -> bool {
matches!(self.state, ThreadState::Ready | ThreadState::Running)
}
#[must_use]
pub fn is_completed(&self) -> bool {
matches!(
self.state,
ThreadState::Completed | ThreadState::Faulted | ThreadState::Aborted
)
}
#[must_use]
pub fn current_frame(&self) -> Option<&ThreadCallFrame> {
self.call_stack.last()
}
pub fn current_frame_mut(&mut self) -> Option<&mut ThreadCallFrame> {
self.call_stack.last_mut()
}
#[must_use]
pub fn call_depth(&self) -> usize {
self.call_stack.len()
}
#[must_use]
pub fn get_frame_at(&self, depth: usize) -> Option<&ThreadCallFrame> {
self.call_stack.get(depth)
}
pub fn get_frame_at_mut(&mut self, depth: usize) -> Option<&mut ThreadCallFrame> {
self.call_stack.get_mut(depth)
}
pub fn resolve_frame_mut(&mut self, depth: usize) -> Option<&mut ThreadCallFrame> {
let len = self.call_stack.len();
if depth < len {
self.call_stack.get_mut(depth)
} else {
self.call_stack.last_mut()
}
}
pub fn push_frame(&mut self, frame: ThreadCallFrame) {
self.call_stack.push(frame);
}
pub fn pop_frame(&mut self) -> Option<ThreadCallFrame> {
self.call_stack.pop()
}
#[must_use]
pub fn stack(&self) -> &EvaluationStack {
&self.eval_stack
}
pub fn stack_mut(&mut self) -> &mut EvaluationStack {
&mut self.eval_stack
}
pub fn push(&mut self, value: EmValue) -> Result<()> {
self.eval_stack.push(value)
}
pub fn pop(&mut self) -> Result<EmValue> {
self.eval_stack.pop()
}
pub fn pop_args(&mut self, count: usize) -> Result<Vec<EmValue>> {
let mut args = Vec::with_capacity(count);
for _ in 0..count {
args.push(self.eval_stack.pop()?);
}
args.reverse();
Ok(args)
}
pub fn peek_args(&self, count: usize) -> Result<Vec<EmValue>> {
let mut args = Vec::with_capacity(count);
for i in 0..count {
args.push(self.eval_stack.peek_at(count - 1 - i)?.clone());
}
Ok(args)
}
#[must_use]
pub fn exception_state(&self) -> &ThreadExceptionState {
&self.exception_state
}
pub fn exception_state_mut(&mut self) -> &mut ThreadExceptionState {
&mut self.exception_state
}
pub fn set_multicast_state(&mut self, state: MulticastState) {
self.multicast_state = Some(state);
}
pub fn take_multicast_state(&mut self) -> Option<MulticastState> {
self.multicast_state.take()
}
#[must_use]
pub fn has_multicast_state(&self) -> bool {
self.multicast_state.is_some()
}
#[must_use]
pub fn address_space(&self) -> &AddressSpace {
&self.context.address_space
}
pub fn pin_array_element(
&self,
array: HeapRef,
index: usize,
offset: u32,
ptr_size: PointerSize,
) -> Result<u64> {
let heap = self.context.address_space.managed_heap();
let length = heap.get_array_length(array).unwrap_or(0);
if length == 0 {
return Ok(self.context.address_space.reserve_address_range(1));
}
let elem_size = if let Ok(elem_type) = heap.get_array_element_type(array) {
elem_type.byte_size(ptr_size).unwrap_or(ptr_size.bytes())
} else {
match heap.get_array_element(array, 0) {
Ok(EmValue::I32(_)) => 4usize,
Ok(EmValue::I64(_) | EmValue::NativeInt(_)) => 8,
Ok(EmValue::NativeUInt(_)) => ptr_size.bytes(),
Ok(EmValue::F32(_)) => 4,
Ok(EmValue::F64(_)) => 8,
_ => ptr_size.bytes(),
}
};
let total_size = length * elem_size;
let base_addr = self
.context
.address_space
.reserve_address_range(total_size.max(1));
self.context
.address_space
.register_pinned_array(base_addr, array, elem_size, length)?;
let element_addr = base_addr + (index * elem_size) as u64 + u64::from(offset);
Ok(element_addr)
}
#[must_use]
pub fn get_tls(&self, key: Token) -> Option<&EmValue> {
self.tls.get(&key)
}
pub fn set_tls(&mut self, key: Token, value: EmValue) {
self.tls.insert(key, value);
}
#[must_use]
pub fn instructions_executed(&self) -> u64 {
self.instructions_executed
}
pub fn increment_instructions(&mut self) {
self.instructions_executed += 1;
}
#[must_use]
pub fn current_method(&self) -> Option<Token> {
self.current_frame().map(|f| f.method)
}
#[must_use]
pub fn current_offset(&self) -> Option<u32> {
self.current_frame().map(|f| f.instruction_offset)
}
pub fn set_return_value(&mut self, value: Option<EmValue>) {
self.return_value = value;
self.state = ThreadState::Completed;
}
#[must_use]
pub fn return_value(&self) -> Option<&EmValue> {
self.return_value.as_ref()
}
pub fn take_return_value(&mut self) -> Option<EmValue> {
self.return_value.take()
}
pub fn fault(&mut self) {
self.state = ThreadState::Faulted;
}
pub fn abort(&mut self) {
self.state = ThreadState::Aborted;
}
pub fn start_method(
&mut self,
method: Token,
locals: Vec<CilFlavor>,
args: Vec<(EmValue, CilFlavor)>,
expects_return: bool,
) {
let frame = ThreadCallFrame::new(method, None, 0, locals, args, expects_return);
self.push_frame(frame);
self.state = ThreadState::Running;
}
pub fn get_local(&self, index: usize) -> Result<&EmValue> {
self.current_frame()
.ok_or_else(|| {
Error::from(EmulationError::InternalError {
description: "no active call frame".to_string(),
})
})?
.locals
.get(index)
}
pub fn set_local(&mut self, index: usize, value: EmValue) -> Result<()> {
self.current_frame_mut()
.ok_or_else(|| {
Error::from(EmulationError::InternalError {
description: "no active call frame".to_string(),
})
})?
.locals
.set(index, value)
}
pub fn get_arg(&self, index: usize) -> Result<&EmValue> {
self.current_frame()
.ok_or_else(|| {
Error::from(EmulationError::InternalError {
description: "no active call frame".to_string(),
})
})?
.arguments
.get(index)
}
pub fn set_arg(&mut self, index: usize, value: EmValue) -> Result<()> {
self.current_frame_mut()
.ok_or_else(|| {
Error::from(EmulationError::InternalError {
description: "no active call frame".to_string(),
})
})?
.arguments
.set(index, value)
}
#[must_use]
pub fn heap(&self) -> &ManagedHeap {
self.context.address_space.managed_heap()
}
#[must_use]
pub fn heap_mut(&self) -> &ManagedHeap {
self.context.address_space.managed_heap()
}
pub fn get_heap_object(&self, heap_ref: HeapRef) -> Result<HeapObject> {
self.context.address_space.managed_heap().get(heap_ref)
}
pub fn deref_pointer(&self, ptr: &ManagedPointer) -> Result<EmValue> {
match &ptr.target {
PointerTarget::Local(index) => {
let idx = usize::from(*index);
let frame = self
.get_frame_at(ptr.frame_depth)
.or_else(|| self.current_frame())
.ok_or_else(|| EmulationError::InternalError {
description: "empty call stack".into(),
})?;
frame.locals().get(idx).cloned()
}
PointerTarget::Argument(index) => {
let idx = usize::from(*index);
let frame = self
.get_frame_at(ptr.frame_depth)
.or_else(|| self.current_frame())
.ok_or_else(|| EmulationError::InternalError {
description: "empty call stack".into(),
})?;
frame.arguments().get(idx).cloned()
}
PointerTarget::ArrayElement { array, index } => self
.context
.address_space
.managed_heap()
.get_array_element(*array, *index),
PointerTarget::ObjectField { object, field } => self
.context
.address_space
.managed_heap()
.get_field(*object, *field)
.map_err(|_| {
EmulationError::TypeMismatch {
operation: "ldind",
expected: "valid field",
found: "unknown field",
}
.into()
}),
PointerTarget::StaticField(field_token) => self
.context
.address_space
.statics()
.get(*field_token)?
.ok_or_else(|| {
EmulationError::TypeMismatch {
operation: "ldind (static field)",
expected: "initialized static field",
found: "uninitialized static field",
}
.into()
}),
}
}
pub fn store_through_pointer(&mut self, ptr: &ManagedPointer, value: EmValue) -> Result<()> {
match &ptr.target {
PointerTarget::Local(index) => {
let idx = usize::from(*index);
let frame = self.resolve_frame_mut(ptr.frame_depth).ok_or_else(|| {
EmulationError::InternalError {
description: "empty call stack".into(),
}
})?;
frame.locals_mut().set(idx, value)?;
Ok(())
}
PointerTarget::Argument(index) => {
let idx = usize::from(*index);
let frame = self.resolve_frame_mut(ptr.frame_depth).ok_or_else(|| {
EmulationError::InternalError {
description: "empty call stack".into(),
}
})?;
frame.arguments_mut().set(idx, value)?;
Ok(())
}
PointerTarget::ArrayElement { array, index } => self
.context
.address_space
.managed_heap()
.set_array_element(*array, *index, value),
PointerTarget::ObjectField { object, field } => self
.context
.address_space
.managed_heap()
.set_field(*object, *field, value),
PointerTarget::StaticField(field_token) => {
self.context
.address_space
.statics()
.set(*field_token, value)?;
Ok(())
}
}
}
pub fn configure_locals(&mut self, local_types: Vec<CilFlavor>) {
if let Some(frame) = self.current_frame_mut() {
frame.locals = LocalVariables::new(local_types);
}
}
pub fn configure_arguments(&mut self, arg_values: Vec<EmValue>, arg_types: Vec<CilFlavor>) {
if let Some(frame) = self.current_frame_mut() {
frame.arguments = ArgumentStorage::new(arg_values, arg_types);
}
}
pub fn restore_stack(&mut self, values: Vec<EmValue>) -> Result<()> {
self.eval_stack.clear();
for value in values {
self.eval_stack.push(value)?;
}
Ok(())
}
pub fn clear_stack(&mut self) {
self.eval_stack.clear();
}
pub fn take_stack(&mut self) -> Vec<EmValue> {
let values = self.eval_stack.snapshot();
self.eval_stack.clear();
values
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::emulation::{create_test_context, create_test_thread};
#[test]
fn test_thread_creation() {
let thread = create_test_thread();
assert_eq!(thread.id(), ThreadId::MAIN);
assert_eq!(thread.state(), ThreadState::Ready);
assert!(thread.is_ready());
assert!(!thread.is_completed());
}
#[test]
fn test_thread_main() {
let ctx = create_test_context();
let thread = EmulationThread::main(ctx);
assert_eq!(thread.id(), ThreadId::MAIN);
assert_eq!(thread.name(), Some("Main"));
}
#[test]
fn test_call_stack() {
let mut thread = create_test_thread();
assert_eq!(thread.call_depth(), 0);
assert!(thread.current_frame().is_none());
thread.start_method(
Token::new(0x06000001),
vec![CilFlavor::I4],
vec![(EmValue::I32(42), CilFlavor::I4)],
true,
);
assert_eq!(thread.call_depth(), 1);
assert!(thread.current_frame().is_some());
assert_eq!(thread.current_method(), Some(Token::new(0x06000001)));
let frame = ThreadCallFrame::new(
Token::new(0x06000002),
Some(Token::new(0x06000001)), 0x10,
vec![],
vec![],
false,
);
thread.push_frame(frame);
assert_eq!(thread.call_depth(), 2);
let popped = thread.pop_frame().unwrap();
assert_eq!(popped.method(), Token::new(0x06000002));
assert_eq!(thread.call_depth(), 1);
}
#[test]
fn test_evaluation_stack() {
let mut thread = create_test_thread();
thread.push(EmValue::I32(10)).unwrap();
thread.push(EmValue::I32(20)).unwrap();
assert_eq!(thread.stack().depth(), 2);
let val = thread.pop().unwrap();
assert_eq!(val, EmValue::I32(20));
}
#[test]
fn test_thread_completion() {
let mut thread = create_test_thread();
thread.set_return_value(Some(EmValue::I32(42)));
assert!(thread.is_completed());
assert_eq!(thread.state(), ThreadState::Completed);
let ret = thread.take_return_value();
assert_eq!(ret, Some(EmValue::I32(42)));
}
#[test]
fn test_thread_priority() {
let mut thread = create_test_thread();
assert_eq!(thread.priority(), ThreadPriority::Normal);
thread.set_priority(ThreadPriority::Highest);
assert_eq!(thread.priority(), ThreadPriority::Highest);
}
#[test]
fn test_tls() {
let mut thread = create_test_thread();
let key = Token::new(0x04000001);
assert!(thread.get_tls(key).is_none());
thread.set_tls(key, EmValue::I32(100));
assert_eq!(thread.get_tls(key), Some(&EmValue::I32(100)));
}
}