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
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
use std::io::Write as _;
use std::sync::atomic;
use crate::color;
use crate::debug::console::Console;
use crate::emu::decoded_instruction::DecodedInstruction;
use crate::engine;
use crate::err::MwemuError;
use crate::serialization;
use crate::windows::constants;
use super::{Emu, assert_aarch64_arch};
impl Emu {
/// AArch64 cached single-thread run loop. Owns AArch64-only behavior:
/// fixed four-byte cache/decode progression, `Opcode::RET` recognition,
/// AArch64 verbose instruction output, AArch64 register tracing, and
/// `engine::aarch64::emulate_instruction`. Panics if the configured
/// architecture is not AArch64.
pub fn run_single_threaded_aarch64(
&mut self,
end_addr: Option<u64>,
) -> Result<u64, MwemuError> {
assert_aarch64_arch(self, "run_single_threaded_aarch64");
if self.process_terminated {
return Err(MwemuError::new("process terminated (NtTerminateProcess)"));
}
self.ensure_run_start_pc_mapped(self.pc())?;
self.is_running.store(1, atomic::Ordering::Relaxed);
self.install_ctrlc_handler_if_enabled();
// Cache booleans that drive hot-path gating. The config is effectively
// immutable during a run, so evaluating these once up front lets the
// inner loop skip entire branches when no observer/debug mode is on.
let has_runtime_limits = self.cfg.max_instructions.is_some()
|| self.cfg.timeout_secs.is_some()
|| self.cfg.max_faults.is_some();
let has_verbose_control = self.cfg.verbose_at.is_some() || self.cfg.verbose_start != 0;
let has_pre_trace = self.cfg.trace_regs
|| self.cfg.trace_reg
|| self.cfg.trace_flags
|| self.cfg.trace_string;
let has_post_trace = self.cfg.inspect || self.cfg.trace_regs;
let has_execution_breakpoints = self.exp != u64::MAX
|| self.cfg.console2
|| !self.bp.addr.is_empty()
|| !self.bp.instruction.is_empty();
let mut looped: Vec<u64> = Vec::new();
let mut prev_addr: u64 = 0;
let mut repeat_counter: u32 = 0;
let mut aarch64_ins = yaxpeax_arm::armv8::a64::Instruction::default();
let mut block: Vec<u8> = Vec::with_capacity(constants::BLOCK_LEN + 1);
block.resize(constants::BLOCK_LEN, 0x0);
loop {
while self.is_running.load(atomic::Ordering::Relaxed) == 1 {
let pc = self.pc();
// Outer-loop limit checks: must run BEFORE attempting to fetch code,
// otherwise PC sitting one past the end (e.g. after final loop iteration
// under run_to) errors out as "unmapped" instead of cleanly stopping.
if let Some(limit_pc) = self.reached_outer_run_limit(pc, end_addr) {
return Ok(limit_pc);
}
super::decode::ensure_instruction_cache_populated_aarch64(self, pc, &mut block)?;
// Inner decode loop
let mut sz: usize = 0;
let mut addr: u64 = 0;
let mut inner_running = self.instruction_cache_can_decode();
let mut aarch64_decode_offset: u64 = 0;
while inner_running {
// Ctrl-C (--handle): drop into the console at a clean
// instruction boundary (not mid-REP), then re-fetch. Gated on
// the plain `enabled_ctrlc` bool so normal runs never touch
// the atomic on the per-instruction hot path.
if self.enabled_ctrlc
&& self.rep.is_none()
&& self.ctrlc_console.load(atomic::Ordering::Relaxed) == 1
{
self.ctrlc_console.store(0, atomic::Ordering::Relaxed);
Console::spawn_console(self);
break; // re-fetch from current PC (console may have stepped)
}
// Decode next instruction from cache
if self.rep.is_none() {
self.aarch64_instruction_cache()
.decode_out_aarch64_into(&mut aarch64_ins);
sz = 4;
addr = pc + aarch64_decode_offset;
aarch64_decode_offset += 4;
if end_addr.is_some() && Some(addr) == end_addr {
return Ok(self.pc());
}
if self.max_pos.is_some() && Some(self.pos) >= self.max_pos {
return Ok(self.pc());
}
}
// the hot path.
if self.last_decoded.is_some() {
self.clear_last_decoded_instruction();
}
self.memory_operations.clear();
self.pos += 1;
self.instruction_count += 1;
// --- Limits ---
if has_runtime_limits {
if let Some(limit_pc) = self.check_runtime_limits(addr) {
return Ok(limit_pc);
}
}
// --- Verbose-at / verbose-range activation ---
if has_verbose_control {
self.update_verbose_at();
self.update_verbose_range();
}
let decoded: Option<DecodedInstruction> =
if self.needs_decoded_instruction_for_observers() {
Some(self.last_decoded_aarch64(addr, aarch64_ins))
} else {
None
};
// --- Exit position ---
if self.cfg.exit_position != 0 && self.pos == self.cfg.exit_position {
log::trace!("exit position reached");
if self.cfg.dump_on_exit && self.cfg.dump_filename.is_some() {
serialization::Serialization::dump(
self,
self.cfg.dump_filename.as_ref().unwrap(),
);
}
if self.cfg.trace_regs && self.cfg.trace_filename.is_some() {
self.trace_file
.as_ref()
.unwrap()
.flush()
.expect("failed to flush trace file");
}
return Ok(self.pc());
}
// --- Breakpoints ---
if has_execution_breakpoints
&& ((self.exp != u64::MAX && self.exp == self.pos)
|| self.bp.is_bp_instruction(self.pos)
|| self.bp.is_bp(addr)
|| (self.cfg.console2 && self.cfg.console_addr == addr))
{
if self.running_script {
return Ok(self.pc());
}
self.cfg.console2 = false;
if self.cfg.verbose >= 2 {
log::trace!("-------");
log::trace!("{} 0x{:x}: {}", self.pos, addr, aarch64_ins);
}
let pc_before_console = self.pc();
Console::spawn_console(self);
if self.force_break {
self.force_break = false;
break;
}
if self.pc() != pc_before_console {
break;
}
}
// --- Loop detection ---
if self.rep.is_none() {
self.observe_loop_progress(
addr,
&mut prev_addr,
&mut repeat_counter,
&mut looped,
"infinite loop found",
)?;
}
// --- Pre-instruction tracing ---
if has_pre_trace {
self.trace_pre_step_state(self.pos);
}
// --- Pre-instruction hook ---
if let Some(mut hook_fn) = self.hooks.hook_on_pre_instruction.take() {
let decoded =
decoded.unwrap_or_else(|| DecodedInstruction::AArch64(aarch64_ins));
let hook_pc = self.pc();
let skip = !hook_fn(self, hook_pc, &decoded, sz);
self.hooks.hook_on_pre_instruction = Some(hook_fn);
if skip {
inner_running = self.instruction_cache_can_decode();
continue;
}
}
// --- Entropy ---
if self.cfg.entropy && self.pos % 10000 == 0 {
self.update_entropy();
}
// --- Verbose output ---
// Use `show_instruction` so the line gets the same color
// as the post-mortem dump and the x86 path.
if self.cfg.verbose >= 2 {
let decoded =
decoded.unwrap_or_else(|| DecodedInstruction::AArch64(aarch64_ins));
self.show_instruction(color!("Cyan"), &decoded);
}
let should_stop_after_return = self.run_until_ret
&& aarch64_ins.opcode == yaxpeax_arm::armv8::a64::Opcode::RET;
// --- Emulate ---
let emulation_ok = engine::aarch64::emulate_instruction(self, &aarch64_ins);
self.last_instruction_size = sz;
if self.is_running.load(atomic::Ordering::Relaxed) == 0 {
return Ok(self.pc());
}
// --- Post-instruction hook ---
if let Some(mut hook_fn) = self.hooks.hook_on_post_instruction.take() {
let decoded =
decoded.unwrap_or_else(|| DecodedInstruction::AArch64(aarch64_ins));
let hook_pc = self.pc();
hook_fn(self, hook_pc, &decoded, sz, emulation_ok);
self.hooks.hook_on_post_instruction = Some(hook_fn);
}
// --- Post-execution tracing ---
if has_post_trace {
if self.cfg.inspect {
self.trace_memory_inspection();
}
if self.cfg.trace_regs
&& self.cfg.trace_filename.is_some()
&& self.pos >= self.cfg.trace_start
{
self.capture_post_op();
self.write_to_trace_file();
}
}
// --- Register trace ---
if self.cfg.trace_regs {
let regs = self.regs_aarch64();
log::trace!(
" x0=0x{:x} x1=0x{:x} x2=0x{:x} x3=0x{:x} x8=0x{:x} x9=0x{:x} sp=0x{:x} lr=0x{:x}",
regs.x[0],
regs.x[1],
regs.x[2],
regs.x[3],
regs.x[8],
regs.x[9],
regs.sp,
regs.x[30]
);
}
// --- Failure handling ---
if !emulation_ok {
self.fault_count += 1;
if self.cfg.console_enabled {
Console::spawn_console(self);
} else if self.running_script {
return Ok(self.pc());
} else {
return Err(MwemuError::new(&format!(
"emulation error at pos = {} pc = 0x{:x}",
self.pos, addr
)));
}
}
// --- PC advance ---
if self.force_reload {
self.force_reload = false;
if should_stop_after_return {
return Ok(self.pc());
}
break; // break inner loop to re-fetch from new PC
}
self.advance_pc_aarch64(4);
// RET is fully emulated before run_until_ret stops at its architectural target.
if should_stop_after_return {
return Ok(self.pc());
}
if self.force_break {
self.force_break = false;
break;
}
// Check can_decode for next iteration
inner_running = self.instruction_cache_can_decode();
} // end inner decode loop
if self.is_api_run && self.is_break_on_api {
self.is_api_run = false;
break;
}
} // end running loop
if self.is_break_on_api {
return Ok(0);
}
self.is_running.store(1, atomic::Ordering::Relaxed);
Console::spawn_console(self);
} // end infinite loop
} // end run_single_threaded_aarch64
}