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
//! Stateless Neo VM bytecode decoder used by the decompiler and CLI.
//! Converts raw byte buffers into structured instructions with operands.
use std::fmt;
use crate::error::{DisassemblyError, Result};
use crate::instruction::{Instruction, OpCode};
mod operand;
/// How to handle unknown opcode bytes encountered during disassembly.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnknownHandling {
/// Surface an error as soon as an unknown opcode is encountered.
Error,
/// Emit an `Unknown` instruction and continue disassembling subsequent bytes.
Permit,
}
/// Stateless helper that decodes Neo VM bytecode into structured instructions.
///
/// The disassembler maintains no state between calls; configuration only
/// controls how unknown opcode bytes are handled.
#[derive(Debug, Clone, Copy)]
pub struct Disassembler {
unknown: UnknownHandling,
}
/// Disassembly output including any non-fatal warnings.
#[derive(Debug, Clone)]
pub struct DisassemblyOutput {
/// Decoded instructions.
pub instructions: Vec<Instruction>,
/// Non-fatal warnings encountered during decoding.
pub warnings: Vec<DisassemblyWarning>,
}
/// Warning emitted during disassembly when configured to tolerate issues.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DisassemblyWarning {
/// An unknown opcode was encountered; output may be desynchronized.
UnknownOpcode {
/// The raw opcode byte.
opcode: u8,
/// Offset where the opcode byte was encountered.
offset: usize,
},
}
impl fmt::Display for DisassemblyWarning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DisassemblyWarning::UnknownOpcode { opcode, offset } => write!(
f,
"disassembly: unknown opcode 0x{opcode:02X} at 0x{offset:04X}; continuing may desynchronize output"
),
}
}
}
impl Default for Disassembler {
fn default() -> Self {
Self::new()
}
}
impl Disassembler {
/// Create a disassembler that permits unknown opcodes.
///
/// Equivalent to `Disassembler::with_unknown_handling(UnknownHandling::Permit)`.
#[must_use]
pub fn new() -> Self {
Self {
unknown: UnknownHandling::Permit,
}
}
/// Create a disassembler configured with the desired unknown-opcode policy.
///
/// See [`UnknownHandling`] for the available strategies.
#[must_use]
pub fn with_unknown_handling(unknown: UnknownHandling) -> Self {
Self { unknown }
}
/// Disassemble an entire bytecode buffer.
///
/// # Errors
/// Returns an error if the bytecode stream is truncated, contains an operand
/// that exceeds the supported maximum size, or contains an unknown opcode
/// while configured with [`UnknownHandling::Error`].
///
/// Any non-fatal warnings are discarded; call [`Self::disassemble_with_warnings`]
/// to inspect them.
pub fn disassemble(&self, bytecode: &[u8]) -> Result<Vec<Instruction>> {
Ok(self.disassemble_with_warnings(bytecode)?.instructions)
}
/// Disassemble an entire bytecode buffer, returning any non-fatal warnings.
///
/// # Errors
/// Returns an error if the bytecode stream is truncated, contains an operand
/// that exceeds the supported maximum size, or contains an unknown opcode
/// while configured with [`UnknownHandling::Error`].
pub fn disassemble_with_warnings(&self, bytecode: &[u8]) -> Result<DisassemblyOutput> {
let mut instructions = Vec::new();
let mut warnings = Vec::new();
let mut pc = 0usize;
while pc < bytecode.len() {
let opcode_byte = *bytecode
.get(pc)
.ok_or(DisassemblyError::UnexpectedEof { offset: pc })?;
let opcode = OpCode::from_byte(opcode_byte);
if let OpCode::Unknown(_) = opcode {
match self.unknown {
UnknownHandling::Permit => {
warnings.push(DisassemblyWarning::UnknownOpcode {
opcode: opcode_byte,
offset: pc,
});
instructions.push(Instruction::new(pc, opcode, None));
pc += 1;
continue;
}
UnknownHandling::Error => {
return Err(DisassemblyError::UnknownOpcode {
opcode: opcode_byte,
offset: pc,
}
.into());
}
}
}
let (instruction, size) = self.decode_known_instruction(bytecode, pc, opcode)?;
instructions.push(instruction);
pc += size;
}
Ok(DisassemblyOutput {
instructions,
warnings,
})
}
fn decode_known_instruction(
&self,
bytecode: &[u8],
offset: usize,
opcode: OpCode,
) -> Result<(Instruction, usize)> {
let (operand, consumed) = self.read_operand(opcode, bytecode, offset)?;
Ok((Instruction::new(offset, opcode, operand), 1 + consumed))
}
}
#[cfg(test)]
mod tests;