1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
use crate::emu::Emu;
use log;
use std::io::Write;
/// Thread scheduling and management functions for the emulator
pub struct ThreadScheduler;
impl ThreadScheduler {
/// Schedule and switch to the next runnable thread (round-robin)
/// Always tries to switch to give each thread equal time
/// Returns true if a thread switch occurred, false otherwise
pub fn schedule_next_thread(emu: &mut Emu) -> bool {
// Single thread case - no scheduling needed
if emu.threads.len() <= 1 {
return false;
}
let current_tick = emu.tick;
let current_thread_id = emu.current_thread_id;
// Round-robin: always try to switch to the next thread
// This ensures fair scheduling - each thread gets one instruction
for i in 1..=emu.threads.len() {
let thread_idx = (current_thread_id + i) % emu.threads.len();
// Skip back to current thread only if no other threads are runnable
if thread_idx == current_thread_id {
// We've checked all other threads, none are runnable
// Check if current thread can continue
if Self::is_thread_runnable(emu, current_thread_id) {
return false; // Stay on current thread
}
// Current thread also can't run
break;
}
if Self::is_thread_runnable(emu, thread_idx) {
// Found a runnable thread - switch to it
/*log::debug!(
"🔄 Thread switch: {} -> {} at step {}",
current_thread_id,
thread_idx,
emu.pos
);
log::debug!(
" From RIP: 0x{:x} -> To RIP: 0x{:x}",
emu.threads[current_thread_id].regs_x86().rip,
emu.threads[thread_idx].regs_x86().rip
);*/
Self::switch_to_thread(emu, thread_idx);
return true;
}
}
// No threads are runnable (including current)
// Try to advance time if threads are just sleeping
if Self::advance_to_next_wake(emu) {
// Recursively try scheduling again after time advance
return Self::schedule_next_thread(emu);
}
// All threads are permanently blocked
Self::log_thread_states(emu);
log::error!("⚠️ All threads blocked or suspended - deadlock detected");
false
}
/// Check if a specific thread is runnable
fn is_thread_runnable(emu: &Emu, thread_idx: usize) -> bool {
if thread_idx >= emu.threads.len() {
return false;
}
let thread = &emu.threads[thread_idx];
!thread.suspended && thread.wake_tick <= emu.tick && thread.blocked_on_cs.is_none()
}
/// Advance emulator tick to the next thread wake time
/// Returns true if time was advanced, false if no threads are waiting
fn advance_to_next_wake(emu: &mut Emu) -> bool {
let current_tick = emu.tick;
let mut next_wake = usize::MAX;
// Find the earliest wake time among suspended threads
for thread in &emu.threads {
if !thread.suspended && thread.wake_tick > current_tick {
next_wake = next_wake.min(thread.wake_tick);
}
}
if next_wake != usize::MAX && next_wake > current_tick {
log::trace!(
"⏰ Advancing tick from {} to {} (all threads sleeping)",
current_tick,
next_wake
);
emu.tick = next_wake;
return true;
}
false
}
/// Log the current state of all threads for debugging
pub fn log_thread_states(emu: &Emu) {
log::trace!("=== Thread States ===");
for (i, thread) in emu.threads.iter().enumerate() {
let status = Self::get_thread_status_string(emu, i);
let marker = if i == emu.current_thread_id {
">>>"
} else {
" "
};
let thread_pc = match &thread.arch {
crate::threading::context::ArchThreadState::X86 { regs, .. } => regs.rip,
crate::threading::context::ArchThreadState::AArch64 { regs, .. } => regs.pc,
};
log::trace!(
"{} Thread[{}]: ID=0x{:x}, PC=0x{:x}, Status={}",
marker,
i,
thread.id,
thread_pc,
status
);
}
log::trace!("Current tick: {}", emu.tick);
}
/// Get a human-readable status string for a thread
fn get_thread_status_string(emu: &Emu, thread_idx: usize) -> String {
let thread = &emu.threads[thread_idx];
if thread.suspended {
"SUSPENDED".to_string()
} else if thread.wake_tick > emu.tick {
format!("SLEEPING(wake={})", thread.wake_tick)
} else if thread.blocked_on_cs.is_some() {
"BLOCKED_CS".to_string()
} else {
"RUNNABLE".to_string()
}
}
/// Switch execution context to a different thread
pub fn switch_to_thread(emu: &mut Emu, thread_id: usize) -> bool {
if thread_id >= emu.threads.len() {
log::error!("Invalid thread ID: {}", thread_id);
return false;
}
if thread_id == emu.current_thread_id {
return true; // Already on this thread
}
// Switch to new thread (FPU state is inside the ArchThreadState enum,
// so it moves with the thread automatically)
emu.current_thread_id = thread_id;
// Don't set force_reload - we want the thread to continue from its current position
// force_reload would prevent IP advancement which causes instructions to execute twice
/*log::trace!(
"Switched to thread {} (ID: 0x{:x})",
thread_id,
emu.threads[thread_id].id
);*/
true
}
/// Execute a single instruction for a specific thread.
/// Uses the arch-dispatched decode_and_execute() and advance_pc() on Emu.
pub fn execute_thread_instruction(emu: &mut Emu, thread_id: usize) -> bool {
// Switch to target thread if needed
if emu.current_thread_id != thread_id {
if !Self::switch_to_thread(emu, thread_id) {
return false;
}
}
let pc = emu.pc();
// Decode and execute
let (sz, result_ok) = emu.decode_and_execute();
if sz == 0 {
return false;
}
// Post instruction hook (fires for both arches via DecodedInstruction)
if let Some(mut hook_fn) = emu.hooks.hook_on_post_instruction.take() {
let decoded = emu.last_decoded.unwrap();
hook_fn(emu, pc, &decoded, sz, result_ok);
emu.hooks.hook_on_post_instruction = Some(hook_fn);
}
// Advance instruction pointer
emu.advance_pc(sz);
result_ok
}
/// Main thread scheduling step - replaces the complex logic in step()
pub fn step_with_scheduling(emu: &mut Emu) -> bool {
emu.pos += 1;
// Check exit condition
if emu.cfg.exit_position != 0 && emu.pos == emu.cfg.exit_position {
log::trace!("Exit position reached");
Self::handle_exit(emu);
return false;
}
// If only one thread, execute it directly
if emu.threads.len() == 1 {
if Self::is_thread_runnable(emu, 0) {
return Self::execute_thread_instruction(emu, 0);
} else {
log::error!("Single thread is not runnable");
return false;
}
}
// Multi-threaded execution with scheduling
// First, try to continue with current thread if it's still runnable
if Self::is_thread_runnable(emu, emu.current_thread_id) {
// Give current thread another timeslice
return Self::execute_thread_instruction(emu, emu.current_thread_id);
}
// Current thread can't run, find another
for i in 1..emu.threads.len() {
let thread_idx = (emu.current_thread_id + i) % emu.threads.len();
if Self::is_thread_runnable(emu, thread_idx) {
log::debug!(
"Switching from thread {} to {}",
emu.current_thread_id,
thread_idx
);
return Self::execute_thread_instruction(emu, thread_idx);
}
}
// No threads are immediately runnable - try advancing time
if Self::advance_to_next_wake(emu) {
// Time advanced, try again
return Self::step_with_scheduling(emu);
}
// All threads are blocked
Self::log_thread_states(emu);
log::error!("All threads are blocked or suspended");
false
}
/// Handle emulator exit
fn handle_exit(emu: &mut Emu) {
if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() {
crate::serialization::Serialization::dump(
emu,
emu.cfg.dump_filename.as_ref().unwrap(),
);
}
if emu.cfg.trace_regs && emu.cfg.trace_filename.is_some() {
emu.trace_file
.as_ref()
.unwrap()
.flush()
.expect("failed to flush trace file");
}
}
}