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
use iced_x86::Code;
/// Architecture-neutral decoded instruction.
///
/// Wraps the native instruction type for each supported ISA so that hooks,
/// display, tracing, and the run loop can operate on a single type without
/// caring which architecture is active.
///
/// The enum is `Copy` (both variants are small value types) and imposes no
/// heap allocation. It is cheap to construct, but the hottest no-observer
/// execution path avoids constructing it until hooks, tracing, or display code
/// need the architecture-neutral view.
#[derive(Clone, Copy)]
pub enum DecodedInstruction {
X86(iced_x86::Instruction),
AArch64(yaxpeax_arm::armv8::a64::Instruction),
}
impl Default for DecodedInstruction {
/// Returns a fully zeroed x86 instruction. This is used only as a
/// placeholder to fill the pre-allocated `InstructionCache` slot and is
/// never emulated. AArch64 paths always overwrite the slot before
/// execution; the variant choice exists only to satisfy the cache's
/// `Default` bound.
fn default() -> Self {
DecodedInstruction::X86(iced_x86::Instruction::default())
}
}
impl DecodedInstruction {
/// Address of the instruction (IP / PC).
///
/// For x86 this comes from `ins.ip()` which the decoder sets.
/// For aarch64 the decoder doesn't embed the PC in the instruction,
/// so callers must use the PC value they already know. We store 0
/// here as a sentinel; the run loop should prefer `self.pc()` for
/// the canonical address.
#[inline]
pub fn address(&self) -> u64 {
match self {
DecodedInstruction::X86(ins) => ins.ip(),
DecodedInstruction::AArch64(_) => 0, // caller should use emu.pc()
}
}
/// Size of the encoded instruction in bytes.
#[inline]
pub fn size(&self) -> usize {
match self {
DecodedInstruction::X86(ins) => ins.len(),
DecodedInstruction::AArch64(_) => 4, // fixed-width ISA
}
}
/// Returns `true` if this is a return instruction.
#[inline]
pub fn is_return(&self) -> bool {
match self {
DecodedInstruction::X86(ins) => {
matches!(
ins.code(),
Code::Retnw
| Code::Retnd
| Code::Retnq
| Code::Retnw_imm16
| Code::Retnd_imm16
| Code::Retnq_imm16
)
}
DecodedInstruction::AArch64(ins) => ins.opcode == yaxpeax_arm::armv8::a64::Opcode::RET,
}
}
/// Unwrap as an x86 instruction. Panics on aarch64.
#[inline]
pub fn as_x86(&self) -> &iced_x86::Instruction {
match self {
DecodedInstruction::X86(ins) => ins,
DecodedInstruction::AArch64(_) => {
unreachable!("as_x86() called on aarch64 instruction")
}
}
}
/// Unwrap as an aarch64 instruction. Panics on x86.
#[inline]
pub fn as_aarch64(&self) -> &yaxpeax_arm::armv8::a64::Instruction {
match self {
DecodedInstruction::AArch64(ins) => ins,
DecodedInstruction::X86(_) => unreachable!("as_aarch64() called on x86 instruction"),
}
}
/// Returns `true` if this is an x86 instruction.
#[inline]
pub fn is_x86(&self) -> bool {
matches!(self, DecodedInstruction::X86(_))
}
/// Returns `true` if this is an aarch64 instruction.
#[inline]
pub fn is_aarch64(&self) -> bool {
matches!(self, DecodedInstruction::AArch64(_))
}
}
impl std::fmt::Debug for DecodedInstruction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DecodedInstruction::X86(ins) => write!(f, "X86({:?})", ins),
DecodedInstruction::AArch64(ins) => write!(f, "AArch64({})", ins),
}
}
}