Skip to main content

zydis_rs/decoder/
opcode.rs

1//! Opcode table lookup and processing
2//!
3//! This module provides opcode map handling for x86/x64 instructions.
4
5/// Opcode map identifier
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[repr(u8)]
8#[derive(Default)]
9pub enum OpcodeMap {
10    /// Default (1-byte) opcode map
11    #[default]
12    Default = 0,
13    /// 0F two-byte opcode map
14    Map0F = 1,
15    /// 0F38 three-byte opcode map
16    Map0F38 = 2,
17    /// 0F3A three-byte opcode map
18    Map0F3A = 3,
19    /// Map 4 (unused in standard x86)
20    Map4 = 4,
21    /// Map 5 (unused in standard x86)
22    Map5 = 5,
23    /// Map 6 (unused in standard x86)
24    Map6 = 6,
25    /// Map 7 (unused in standard x86)
26    Map7 = 7,
27    /// XOP map 8
28    Xop8 = 8,
29    /// XOP map 9
30    Xop9 = 9,
31    /// XOP map 10 (0x0A)
32    XopA = 10,
33}
34
35impl OpcodeMap {
36    /// Get the opcode map from VEX/XOP m-mmmm field
37    #[must_use]
38    pub const fn from_vex_map(m: u8) -> Self {
39        match m {
40            1 => OpcodeMap::Map0F,
41            2 => OpcodeMap::Map0F38,
42            3 => OpcodeMap::Map0F3A,
43            8 => OpcodeMap::Xop8,
44            9 => OpcodeMap::Xop9,
45            10 => OpcodeMap::XopA,
46            _ => OpcodeMap::Default,
47        }
48    }
49
50    /// Check if this is a standard map (0-3)
51    #[must_use]
52    pub const fn is_standard(&self) -> bool {
53        matches!(
54            self,
55            OpcodeMap::Default | OpcodeMap::Map0F | OpcodeMap::Map0F38 | OpcodeMap::Map0F3A
56        )
57    }
58
59    /// Check if this is an XOP map
60    #[must_use]
61    pub const fn is_xop(&self) -> bool {
62        matches!(self, OpcodeMap::Xop8 | OpcodeMap::Xop9 | OpcodeMap::XopA)
63    }
64}
65
66/// Check if an opcode byte indicates a two-byte escape (0x0F)
67#[must_use]
68pub const fn is_two_byte_escape(byte: u8) -> bool {
69    byte == 0x0F
70}
71
72/// Check if bytes after 0x0F indicate a three-byte escape
73#[must_use]
74pub const fn is_three_byte_escape(byte: u8) -> Option<OpcodeMap> {
75    match byte {
76        0x38 => Some(OpcodeMap::Map0F38),
77        0x3A => Some(OpcodeMap::Map0F3A),
78        _ => None,
79    }
80}
81
82/// Opcode escape sequence detection
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum OpcodeEscape {
85    /// No escape, 1-byte opcode
86    None,
87    /// 0x0F two-byte escape
88    TwoByte,
89    /// 0x0F 0x38 three-byte escape
90    ThreeByte38,
91    /// 0x0F 0x3A three-byte escape
92    ThreeByte3A,
93}
94
95impl OpcodeEscape {
96    /// Get the opcode map for this escape
97    #[must_use]
98    pub const fn opcode_map(&self) -> OpcodeMap {
99        match self {
100            OpcodeEscape::None => OpcodeMap::Default,
101            OpcodeEscape::TwoByte => OpcodeMap::Map0F,
102            OpcodeEscape::ThreeByte38 => OpcodeMap::Map0F38,
103            OpcodeEscape::ThreeByte3A => OpcodeMap::Map0F3A,
104        }
105    }
106
107    /// Get the number of extra bytes consumed by this escape
108    #[must_use]
109    pub const fn extra_bytes(&self) -> u8 {
110        match self {
111            OpcodeEscape::None => 0,
112            OpcodeEscape::TwoByte => 1,
113            OpcodeEscape::ThreeByte38 | OpcodeEscape::ThreeByte3A => 2,
114        }
115    }
116}
117
118/// Check if the byte is a mandatory prefix that can also be part of instruction encoding
119#[must_use]
120pub const fn is_mandatory_prefix(byte: u8) -> bool {
121    matches!(byte, 0x66 | 0xF2 | 0xF3)
122}
123
124/// Opcode prefix (combines with opcode map)
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
126pub enum MandatoryPrefix {
127    /// No mandatory prefix
128    #[default]
129    None,
130    /// 0x66 prefix
131    P66,
132    /// 0xF2 prefix (REPNZ)
133    PF2,
134    /// 0xF3 prefix (REP)
135    PF3,
136}
137
138impl MandatoryPrefix {
139    /// Get from raw byte value
140    #[must_use]
141    pub const fn from_byte(byte: u8) -> Self {
142        match byte {
143            0x66 => MandatoryPrefix::P66,
144            0xF2 => MandatoryPrefix::PF2,
145            0xF3 => MandatoryPrefix::PF3,
146            _ => MandatoryPrefix::None,
147        }
148    }
149
150    /// Get the byte value
151    #[must_use]
152    pub const fn to_byte(&self) -> Option<u8> {
153        match self {
154            MandatoryPrefix::None => None,
155            MandatoryPrefix::P66 => Some(0x66),
156            MandatoryPrefix::PF2 => Some(0xF2),
157            MandatoryPrefix::PF3 => Some(0xF3),
158        }
159    }
160}
161
162/// Opcode descriptor combining map, prefix, and opcode
163#[derive(Debug, Clone, Copy)]
164pub struct OpcodeDesc {
165    /// The opcode map
166    pub map: OpcodeMap,
167    /// Mandatory prefix (if any)
168    pub prefix: MandatoryPrefix,
169    /// The opcode byte
170    pub opcode: u8,
171}
172
173impl OpcodeDesc {
174    /// Create a new opcode descriptor
175    #[must_use]
176    pub const fn new(map: OpcodeMap, prefix: MandatoryPrefix, opcode: u8) -> Self {
177        Self {
178            map,
179            prefix,
180            opcode,
181        }
182    }
183
184    /// Create from default map (1-byte opcodes)
185    #[must_use]
186    pub const fn default_map(opcode: u8) -> Self {
187        Self::new(OpcodeMap::Default, MandatoryPrefix::None, opcode)
188    }
189
190    /// Create from 0F map
191    #[must_use]
192    pub const fn map_0f(opcode: u8) -> Self {
193        Self::new(OpcodeMap::Map0F, MandatoryPrefix::None, opcode)
194    }
195
196    /// Create from 0F map with prefix
197    #[must_use]
198    pub const fn map_0f_with_prefix(opcode: u8, prefix: MandatoryPrefix) -> Self {
199        Self::new(OpcodeMap::Map0F, prefix, opcode)
200    }
201}
202
203impl Default for OpcodeDesc {
204    fn default() -> Self {
205        Self::default_map(0)
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_opcode_map_from_vex() {
215        assert_eq!(OpcodeMap::from_vex_map(1), OpcodeMap::Map0F);
216        assert_eq!(OpcodeMap::from_vex_map(2), OpcodeMap::Map0F38);
217        assert_eq!(OpcodeMap::from_vex_map(3), OpcodeMap::Map0F3A);
218        assert_eq!(OpcodeMap::from_vex_map(0), OpcodeMap::Default);
219    }
220
221    #[test]
222    fn test_is_two_byte_escape() {
223        assert!(is_two_byte_escape(0x0F));
224        assert!(!is_two_byte_escape(0x90));
225        assert!(!is_two_byte_escape(0x00));
226    }
227
228    #[test]
229    fn test_is_three_byte_escape() {
230        assert_eq!(is_three_byte_escape(0x38), Some(OpcodeMap::Map0F38));
231        assert_eq!(is_three_byte_escape(0x3A), Some(OpcodeMap::Map0F3A));
232        assert_eq!(is_three_byte_escape(0x00), None);
233    }
234
235    #[test]
236    fn test_opcode_escape_extra_bytes() {
237        assert_eq!(OpcodeEscape::None.extra_bytes(), 0);
238        assert_eq!(OpcodeEscape::TwoByte.extra_bytes(), 1);
239        assert_eq!(OpcodeEscape::ThreeByte38.extra_bytes(), 2);
240        assert_eq!(OpcodeEscape::ThreeByte3A.extra_bytes(), 2);
241    }
242
243    #[test]
244    fn test_mandatory_prefix() {
245        assert_eq!(MandatoryPrefix::from_byte(0x66), MandatoryPrefix::P66);
246        assert_eq!(MandatoryPrefix::from_byte(0xF2), MandatoryPrefix::PF2);
247        assert_eq!(MandatoryPrefix::from_byte(0xF3), MandatoryPrefix::PF3);
248        assert_eq!(MandatoryPrefix::from_byte(0x00), MandatoryPrefix::None);
249
250        assert_eq!(MandatoryPrefix::P66.to_byte(), Some(0x66));
251        assert_eq!(MandatoryPrefix::None.to_byte(), None);
252    }
253
254    #[test]
255    fn test_opcode_desc() {
256        let desc = OpcodeDesc::map_0f(0x90);
257        assert_eq!(desc.map, OpcodeMap::Map0F);
258        assert_eq!(desc.opcode, 0x90);
259        assert_eq!(desc.prefix, MandatoryPrefix::None);
260
261        let desc2 = OpcodeDesc::map_0f_with_prefix(0x12, MandatoryPrefix::P66);
262        assert_eq!(desc2.prefix, MandatoryPrefix::P66);
263    }
264}