giit_rbpf/
x86.rs

1use crate::{
2    error::{EbpfError, UserDefinedError},
3    jit::{emit, emit_variable_length, JitCompiler, OperandSize},
4};
5
6pub const RAX: u8 = 0;
7pub const RCX: u8 = 1;
8pub const RDX: u8 = 2;
9pub const RBX: u8 = 3;
10pub const RSP: u8 = 4;
11pub const RBP: u8 = 5;
12pub const RSI: u8 = 6;
13pub const RDI: u8 = 7;
14pub const R8: u8 = 8;
15pub const R9: u8 = 9;
16pub const R10: u8 = 10;
17pub const R11: u8 = 11;
18pub const R12: u8 = 12;
19pub const R13: u8 = 13;
20pub const R14: u8 = 14;
21pub const R15: u8 = 15;
22
23// System V AMD64 ABI
24// Works on: Linux, macOS, BSD and Solaris but not on Windows
25pub const ARGUMENT_REGISTERS: [u8; 6] = [RDI, RSI, RDX, RCX, R8, R9];
26pub const CALLER_SAVED_REGISTERS: [u8; 9] = [RAX, RCX, RDX, RSI, RDI, R8, R9, R10, R11];
27pub const CALLEE_SAVED_REGISTERS: [u8; 6] = [RBP, RBX, R12, R13, R14, R15];
28
29struct X86Rex {
30    w: bool,
31    r: bool,
32    x: bool,
33    b: bool,
34}
35
36struct X86ModRm {
37    mode: u8,
38    r: u8,
39    m: u8,
40}
41
42struct X86Sib {
43    scale: u8,
44    index: u8,
45    base: u8,
46}
47
48#[derive(PartialEq, Eq, Copy, Clone)]
49pub enum X86IndirectAccess {
50    /// [second_operand + offset]
51    Offset(i32),
52    /// [second_operand + offset + index << shift]
53    OffsetIndexShift(i32, u8, u8),
54}
55
56#[allow(dead_code)]
57#[derive(PartialEq, Eq, Copy, Clone)]
58pub enum FenceType {
59    /// lfence
60    Load = 5,
61    /// mfence
62    All = 6,
63    /// sfence
64    Store = 7,
65}
66
67#[derive(PartialEq, Eq, Copy, Clone)]
68pub struct X86Instruction {
69    pub size: OperandSize,
70    pub opcode_escape_sequence: u8,
71    pub opcode: u8,
72    pub modrm: bool,
73    pub indirect: Option<X86IndirectAccess>,
74    pub first_operand: u8,
75    pub second_operand: u8,
76    pub immediate_size: OperandSize,
77    pub immediate: i64,
78}
79
80impl Default for X86Instruction {
81    fn default() -> Self {
82        Self {
83            size: OperandSize::S64,
84            opcode_escape_sequence: 0,
85            opcode: 0,
86            modrm: true,
87            indirect: None,
88            first_operand: 0,
89            second_operand: 0,
90            immediate_size: OperandSize::S0,
91            immediate: 0,
92        }
93    }
94}
95
96impl X86Instruction {
97    pub fn emit<E: UserDefinedError>(&self, jit: &mut JitCompiler) -> Result<(), EbpfError<E>> {
98        let mut rex = X86Rex {
99            w: self.size == OperandSize::S64,
100            r: self.first_operand & 0b1000 != 0,
101            x: false,
102            b: self.second_operand & 0b1000 != 0,
103        };
104        let mut modrm = X86ModRm {
105            mode: 0,
106            r: self.first_operand & 0b111,
107            m: self.second_operand & 0b111,
108        };
109        let mut sib = X86Sib {
110            scale: 0,
111            index: 0,
112            base: 0,
113        };
114        let mut displacement_size = OperandSize::S0;
115        let mut displacement = 0;
116        if self.modrm {
117            match self.indirect {
118                Some(X86IndirectAccess::Offset(offset)) => {
119                    displacement = offset;
120                    debug_assert_ne!(self.second_operand & 0b111, RSP); // Reserved for SIB addressing
121                    if (displacement >= -128 && displacement <= 127)
122                        || (displacement == 0 && self.second_operand & 0b111 == RBP)
123                    {
124                        displacement_size = OperandSize::S8;
125                        modrm.mode = 1;
126                    } else {
127                        displacement_size = OperandSize::S32;
128                        modrm.mode = 2;
129                    }
130                }
131                Some(X86IndirectAccess::OffsetIndexShift(offset, index, shift)) => {
132                    displacement = offset;
133                    displacement_size = OperandSize::S32;
134                    modrm.mode = 2;
135                    modrm.m = RSP;
136                    rex.x = index & 0b1000 != 0;
137                    sib.scale = shift & 0b11;
138                    sib.index = index & 0b111;
139                    sib.base = self.second_operand & 0b111;
140                }
141                None => {
142                    modrm.mode = 3;
143                }
144            }
145        }
146        jit.emit_random_noop()?;
147        if self.size == OperandSize::S16 {
148            emit::<u8, E>(jit, 0x66)?;
149        }
150        let rex =
151            ((rex.w as u8) << 3) | ((rex.r as u8) << 2) | ((rex.x as u8) << 1) | (rex.b as u8);
152        if rex != 0 {
153            emit::<u8, E>(jit, 0x40 | rex)?;
154        }
155        match self.opcode_escape_sequence {
156            1 => emit::<u8, E>(jit, 0x0f)?,
157            2 => emit::<u16, E>(jit, 0x0f38)?,
158            3 => emit::<u16, E>(jit, 0x0f3a)?,
159            _ => {}
160        }
161        emit::<u8, E>(jit, self.opcode)?;
162        if self.modrm {
163            emit::<u8, E>(jit, (modrm.mode << 6) | (modrm.r << 3) | modrm.m)?;
164            let sib = (sib.scale << 6) | (sib.index << 3) | sib.base;
165            if sib != 0 {
166                emit::<u8, E>(jit, sib)?;
167            }
168            emit_variable_length(jit, displacement_size, displacement as u64)?;
169        }
170        emit_variable_length(jit, self.immediate_size, self.immediate as u64)
171    }
172
173    /// Move source to destination
174    pub fn mov(size: OperandSize, source: u8, destination: u8) -> Self {
175        Self {
176            size,
177            opcode: 0x89,
178            first_operand: source,
179            second_operand: destination,
180            ..Self::default()
181        }
182    }
183
184    /// Swap source and destination
185    pub fn xchg(size: OperandSize, source: u8, destination: u8) -> Self {
186        Self {
187            size,
188            opcode: 0x87,
189            first_operand: source,
190            second_operand: destination,
191            ..Self::default()
192        }
193    }
194
195    /// Swap byte order of destination
196    pub fn bswap(size: OperandSize, destination: u8) -> Self {
197        match size {
198            OperandSize::S16 => Self {
199                size,
200                opcode: 0xc1,
201                second_operand: destination,
202                immediate_size: OperandSize::S8,
203                immediate: 8,
204                ..Self::default()
205            },
206            OperandSize::S32 | OperandSize::S64 => Self {
207                size,
208                opcode_escape_sequence: 1,
209                opcode: 0xc8 | (destination & 0b111),
210                modrm: false,
211                second_operand: destination,
212                ..Self::default()
213            },
214            _ => unimplemented!(),
215        }
216    }
217
218    /// Sign extend source i32 to destination i64
219    pub fn sign_extend_i32_to_i64(source: u8, destination: u8) -> Self {
220        Self {
221            opcode: 0x63,
222            first_operand: source,
223            second_operand: destination,
224            ..Self::default()
225        }
226    }
227
228    /// Test source and destination
229    pub fn test(
230        size: OperandSize,
231        source: u8,
232        destination: u8,
233        indirect: Option<X86IndirectAccess>,
234    ) -> Self {
235        Self {
236            size,
237            opcode: 0x85,
238            first_operand: source,
239            second_operand: destination,
240            indirect,
241            ..Self::default()
242        }
243    }
244
245    /// Test immediate and destination
246    pub fn test_immediate(
247        size: OperandSize,
248        destination: u8,
249        immediate: i64,
250        indirect: Option<X86IndirectAccess>,
251    ) -> Self {
252        Self {
253            size,
254            opcode: 0xf7,
255            first_operand: RAX,
256            second_operand: destination,
257            immediate_size: OperandSize::S32,
258            immediate,
259            indirect,
260            ..Self::default()
261        }
262    }
263
264    /// Compare source and destination
265    pub fn cmp(
266        size: OperandSize,
267        source: u8,
268        destination: u8,
269        indirect: Option<X86IndirectAccess>,
270    ) -> Self {
271        Self {
272            size,
273            opcode: 0x39,
274            first_operand: source,
275            second_operand: destination,
276            indirect,
277            ..Self::default()
278        }
279    }
280
281    /// Compare immediate and destination
282    pub fn cmp_immediate(
283        size: OperandSize,
284        destination: u8,
285        immediate: i64,
286        indirect: Option<X86IndirectAccess>,
287    ) -> Self {
288        Self {
289            size,
290            opcode: 0x81,
291            first_operand: RDI,
292            second_operand: destination,
293            immediate_size: OperandSize::S32,
294            immediate,
295            indirect,
296            ..Self::default()
297        }
298    }
299
300    /// Load effective address of source into destination
301    pub fn lea(
302        size: OperandSize,
303        source: u8,
304        destination: u8,
305        indirect: Option<X86IndirectAccess>,
306    ) -> Self {
307        Self {
308            size,
309            opcode: 0x8d,
310            first_operand: destination,
311            second_operand: source,
312            indirect,
313            ..Self::default()
314        }
315    }
316
317    /// Load destination from [source + offset]
318    pub fn load(
319        size: OperandSize,
320        source: u8,
321        destination: u8,
322        indirect: X86IndirectAccess,
323    ) -> Self {
324        Self {
325            size: if size == OperandSize::S64 {
326                OperandSize::S64
327            } else {
328                OperandSize::S32
329            },
330            opcode_escape_sequence: if size == OperandSize::S8 || size == OperandSize::S16 {
331                1
332            } else {
333                0
334            },
335            opcode: match size {
336                OperandSize::S8 => 0xb6,
337                OperandSize::S16 => 0xb7,
338                _ => 0x8b,
339            },
340            first_operand: destination,
341            second_operand: source,
342            indirect: Some(indirect),
343            ..Self::default()
344        }
345    }
346
347    /// Store source in [destination + offset]
348    pub fn store(
349        size: OperandSize,
350        source: u8,
351        destination: u8,
352        indirect: X86IndirectAccess,
353    ) -> Self {
354        Self {
355            size,
356            opcode: match size {
357                OperandSize::S8 => 0x88,
358                _ => 0x89,
359            },
360            first_operand: source,
361            second_operand: destination,
362            indirect: Some(indirect),
363            ..Self::default()
364        }
365    }
366
367    /// Load destination from sign-extended immediate
368    pub fn load_immediate(size: OperandSize, destination: u8, immediate: i64) -> Self {
369        let immediate_size =
370            if immediate >= std::i32::MIN as i64 && immediate <= std::i32::MAX as i64 {
371                OperandSize::S32
372            } else {
373                OperandSize::S64
374            };
375        match immediate_size {
376            OperandSize::S32 => Self {
377                size,
378                opcode: 0xc7,
379                second_operand: destination,
380                immediate_size: OperandSize::S32,
381                immediate,
382                ..Self::default()
383            },
384            OperandSize::S64 => Self {
385                size,
386                opcode: 0xb8 | (destination & 0b111),
387                modrm: false,
388                second_operand: destination,
389                immediate_size: OperandSize::S64,
390                immediate,
391                ..Self::default()
392            },
393            _ => unimplemented!(),
394        }
395    }
396
397    /// Store sign-extended immediate in destination
398    pub fn store_immediate(
399        size: OperandSize,
400        destination: u8,
401        indirect: X86IndirectAccess,
402        immediate: i64,
403    ) -> Self {
404        Self {
405            size,
406            opcode: match size {
407                OperandSize::S8 => 0xc6,
408                _ => 0xc7,
409            },
410            second_operand: destination,
411            indirect: Some(indirect),
412            immediate_size: if size == OperandSize::S64 {
413                OperandSize::S32
414            } else {
415                size
416            },
417            immediate,
418            ..Self::default()
419        }
420    }
421
422    /// Push source onto the stack
423    pub fn push(source: u8) -> Self {
424        Self {
425            size: OperandSize::S32,
426            opcode: 0x50 | (source & 0b111),
427            modrm: false,
428            second_operand: source,
429            ..Self::default()
430        }
431    }
432
433    /// Pop from the stack into destination
434    pub fn pop(destination: u8) -> Self {
435        Self {
436            size: OperandSize::S32,
437            opcode: 0x58 | (destination & 0b111),
438            modrm: false,
439            second_operand: destination,
440            ..Self::default()
441        }
442    }
443
444    /// Push RIP and jump to destination
445    pub fn call_reg(
446        size: OperandSize,
447        destination: u8,
448        indirect: Option<X86IndirectAccess>,
449    ) -> Self {
450        Self {
451            size,
452            opcode: 0xff,
453            first_operand: 2,
454            second_operand: destination,
455            indirect,
456            ..Self::default()
457        }
458    }
459
460    /// Pop RIP
461    pub fn return_near() -> Self {
462        Self {
463            size: OperandSize::S32,
464            opcode: 0xc3,
465            modrm: false,
466            ..Self::default()
467        }
468    }
469
470    /// No operation
471    #[allow(dead_code)]
472    pub fn noop() -> Self {
473        Self {
474            size: OperandSize::S32,
475            opcode: 0x90,
476            modrm: false,
477            ..Self::default()
478        }
479    }
480
481    /// Trap / software interrupt
482    #[allow(dead_code)]
483    pub fn interrupt(immediate: u8) -> Self {
484        if immediate == 3 {
485            Self {
486                size: OperandSize::S32,
487                opcode: 0xcc,
488                modrm: false,
489                ..Self::default()
490            }
491        } else {
492            Self {
493                size: OperandSize::S32,
494                opcode: 0xcd,
495                modrm: false,
496                immediate_size: OperandSize::S8,
497                immediate: immediate as i64,
498                ..Self::default()
499            }
500        }
501    }
502
503    /// rdtsc
504    #[allow(dead_code)]
505    pub fn cycle_count() -> Self {
506        Self {
507            size: OperandSize::S32,
508            opcode_escape_sequence: 1,
509            opcode: 0x31,
510            modrm: false,
511            ..Self::default()
512        }
513    }
514
515    /// lfence / sfence / mfence
516    #[allow(dead_code)]
517    pub fn fence(fence_type: FenceType) -> Self {
518        Self {
519            size: OperandSize::S32,
520            opcode_escape_sequence: 1,
521            opcode: 0xae,
522            first_operand: fence_type as u8,
523            ..Self::default()
524        }
525    }
526}