Răsfoiți Sursa

update common Instruction struct

bidhan-a 1 lună în urmă
părinte
comite
0ece1ccf12

+ 6 - 0
crates/common/Cargo.toml

@@ -15,3 +15,9 @@ name = "sbpf_common"
 [dependencies]
 [dependencies]
 num-derive = { workspace = true }
 num-derive = { workspace = true }
 num-traits = { workspace = true }
 num-traits = { workspace = true }
+thiserror = { workspace = true }
+phf = "0.13.1"
+phf_macros = "0.13.1"
+
+[dev-dependencies]
+hex-literal = "1.0.0"

+ 28 - 0
crates/common/src/errors.rs

@@ -0,0 +1,28 @@
+use std::ops::Range;
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum SBPFError {
+    #[error("Bytecode error: {error}")]
+    BytecodeError {
+        error: String,
+        span: Range<usize>,
+        custom_label: Option<String>,
+    },
+}
+
+impl SBPFError {
+    pub fn label(&self) -> &str {
+        match self {
+            Self::BytecodeError { custom_label, .. } => {
+                custom_label.as_deref().unwrap_or("Bytecode error")
+            }
+        }
+    }
+
+    pub fn span(&self) -> &Range<usize> {
+        match self {
+            Self::BytecodeError { span, .. } => span,
+        }
+    }
+}

+ 685 - 0
crates/common/src/instruction.rs

@@ -1,4 +1,6 @@
+use crate::errors::SBPFError;
 use crate::opcode::Opcode;
 use crate::opcode::Opcode;
+use crate::syscall::SYSCALLS;
 
 
 use core::fmt;
 use core::fmt;
 use core::ops::Range;
 use core::ops::Range;
@@ -20,6 +22,15 @@ pub enum Number {
     Addr(i64),
     Addr(i64),
 }
 }
 
 
+impl fmt::Display for Number {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Number::Int(i) => write!(f, "{}", i),
+            Number::Addr(a) => write!(f, "{}", a),
+        }
+    }
+}
+
 #[derive(Debug, Clone)]
 #[derive(Debug, Clone)]
 pub struct Instruction {
 pub struct Instruction {
     pub opcode: Opcode,
     pub opcode: Opcode,
@@ -29,3 +40,677 @@ pub struct Instruction {
     pub imm: Option<Number>,
     pub imm: Option<Number>,
     pub span: Range<usize>,
     pub span: Range<usize>,
 }
 }
+
+impl Instruction {
+    pub fn get_size(&self) -> u64 {
+        match self.opcode {
+            Opcode::Lddw => 16,
+            _ => 8,
+        }
+    }
+
+    pub fn is_jump(&self) -> bool {
+        matches!(
+            self.opcode,
+            Opcode::Ja
+                | Opcode::JeqImm
+                | Opcode::JgtImm
+                | Opcode::JgeImm
+                | Opcode::JltImm
+                | Opcode::JleImm
+                | Opcode::JsetImm
+                | Opcode::JneImm
+                | Opcode::JsgtImm
+                | Opcode::JsgeImm
+                | Opcode::JsltImm
+                | Opcode::JsleImm
+                | Opcode::JeqReg
+                | Opcode::JgtReg
+                | Opcode::JgeReg
+                | Opcode::JltReg
+                | Opcode::JleReg
+                | Opcode::JsetReg
+                | Opcode::JneReg
+                | Opcode::JsgtReg
+                | Opcode::JsgeReg
+                | Opcode::JsltReg
+                | Opcode::JsleReg
+        )
+    }
+
+    pub fn off_str(&self) -> String {
+        match self.off {
+            Some(off) => {
+                if off.is_negative() {
+                    off.to_string()
+                } else {
+                    format!("+{}", off)
+                }
+            }
+            None => "0".to_string(),
+        }
+    }
+
+    pub fn dst_off(&self) -> String {
+        match &self.dst {
+            Some(dst) => format!("[r{}{}]", dst.n, self.off_str()),
+            None => format!("[r0{}]", self.off_str()),
+        }
+    }
+
+    pub fn src_off(&self) -> String {
+        match &self.src {
+            Some(src) => format!("[r{}{}]", src.n, self.off_str()),
+            None => format!("[r0{}]", self.off_str()),
+        }
+    }
+
+    pub fn op_imm_bits(&self) -> Result<String, SBPFError> {
+        match &self.imm {
+            Some(Number::Int(imm)) => match *imm {
+                16 => Ok(format!("{}16", self.opcode)),
+                32 => Ok(format!("{}32", self.opcode)),
+                64 => Ok(format!("{}64", self.opcode)),
+                _ => Err(SBPFError::BytecodeError {
+                    error: format!(
+                        "Invalid immediate value: {:?} for opcode: {:?}",
+                        self.imm, self.opcode
+                    ),
+                    span: self.span.clone(),
+                    custom_label: None,
+                }),
+            },
+            _ => Err(SBPFError::BytecodeError {
+                error: format!("Expected immediate value for opcode: {:?}", self.opcode),
+                span: self.span.clone(),
+                custom_label: None,
+            }),
+        }
+    }
+
+    pub fn from_bytes(bytes: &[u8]) -> Result<Self, SBPFError> {
+        let span = 0..bytes.len();
+
+        let opcode = Opcode::from_u8(bytes[0]).unwrap();
+        let reg = bytes[1];
+        let src = reg >> 4;
+        let dst = reg & 0x0f;
+        let off = i16::from_le_bytes([bytes[2], bytes[3]]);
+        let imm = match opcode {
+            Opcode::Lddw => {
+                let imm_low = i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
+                let imm_high = i32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
+
+                ((imm_high as i64) << 32) | (imm_low as u32 as i64)
+            }
+            _ => i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as i64,
+        };
+
+        let mut out_dst: Option<Register> = None;
+        let mut out_src: Option<Register> = None;
+        let mut out_off: Option<i16> = None;
+        let mut out_imm: Option<Number> = None;
+
+        match opcode {
+            Opcode::Lddw => {
+                if src != 0 || off != 0 {
+                    return Err(SBPFError::BytecodeError {
+                        error: format!(
+                            "Lddw instruction expects src and off to be 0, but got src: {}, off: {}",
+                            src, off
+                        ),
+                        span: span.clone(),
+                        custom_label: None,
+                    });
+                }
+                out_dst = Some(Register { n: dst });
+                out_imm = Some(Number::Int(imm));
+            }
+
+            Opcode::Call => {
+                if SYSCALLS.get(&(imm as u32)).is_some() {
+                    if reg != 0 || off != 0 {
+                        return Err(SBPFError::BytecodeError {
+                            error: format!(
+                                "Call instruction with syscall expects reg and off to be 0, but got reg: {}, off: {}",
+                                reg, off
+                            ),
+                            span: span.clone(),
+                            custom_label: None,
+                        });
+                    }
+                    out_imm = Some(Number::Int(imm));
+                } else {
+                    if reg != 16 || off != 0 {
+                        return Err(SBPFError::BytecodeError {
+                            error: format!(
+                                "Call instruction with immediate expects reg to be 16 and off to be 0, but got reg: {}, off: {}",
+                                reg, off
+                            ),
+                            span: span.clone(),
+                            custom_label: None,
+                        });
+                    }
+                    out_imm = Some(Number::Int(imm));
+                }
+            }
+
+            Opcode::Callx => {
+                if src != 0 || off != 0 || imm != 0 {
+                    return Err(SBPFError::BytecodeError {
+                        error: format!(
+                            "Callx instruction expects src, off, and imm to be 0, but got src: {}, off: {}, imm: {}",
+                            src, off, imm
+                        ),
+                        span: span.clone(),
+                        custom_label: None,
+                    });
+                }
+                // callx destination register is encoded in the dst field
+                out_dst = Some(Register { n: dst });
+            }
+
+            Opcode::Ja => {
+                if reg != 0 || imm != 0 {
+                    return Err(SBPFError::BytecodeError {
+                        error: format!(
+                            "Ja instruction expects reg and imm to be 0, but got reg: {}, imm: {}",
+                            reg, imm
+                        ),
+                        span: span.clone(),
+                        custom_label: None,
+                    });
+                }
+                out_off = Some(off);
+            }
+
+            Opcode::JeqImm
+            | Opcode::JgtImm
+            | Opcode::JgeImm
+            | Opcode::JltImm
+            | Opcode::JleImm
+            | Opcode::JsetImm
+            | Opcode::JneImm
+            | Opcode::JsgtImm
+            | Opcode::JsgeImm
+            | Opcode::JsltImm
+            | Opcode::JsleImm => {
+                if src != 0 {
+                    return Err(SBPFError::BytecodeError {
+                        error: format!(
+                            "Jump instruction with immediate expects src to be 0, but got src: {}",
+                            src
+                        ),
+                        span: span.clone(),
+                        custom_label: None,
+                    });
+                }
+                out_dst = Some(Register { n: dst });
+                out_imm = Some(Number::Int(imm));
+                out_off = Some(off);
+            }
+
+            Opcode::JeqReg
+            | Opcode::JgtReg
+            | Opcode::JgeReg
+            | Opcode::JltReg
+            | Opcode::JleReg
+            | Opcode::JsetReg
+            | Opcode::JneReg
+            | Opcode::JsgtReg
+            | Opcode::JsgeReg
+            | Opcode::JsltReg
+            | Opcode::JsleReg => {
+                if imm != 0 {
+                    return Err(SBPFError::BytecodeError {
+                        error: format!(
+                            "Jump instruction with register expects imm to be 0, but got imm: {}",
+                            imm
+                        ),
+                        span: span.clone(),
+                        custom_label: None,
+                    });
+                }
+                out_dst = Some(Register { n: dst });
+                out_src = Some(Register { n: src });
+                out_off = Some(off);
+            }
+
+            // Arithmetic instructions with immediate values
+            Opcode::Add32Imm
+            | Opcode::Sub32Imm
+            | Opcode::Mul32Imm
+            | Opcode::Div32Imm
+            | Opcode::Or32Imm
+            | Opcode::And32Imm
+            | Opcode::Lsh32Imm
+            | Opcode::Rsh32Imm
+            | Opcode::Mod32Imm
+            | Opcode::Xor32Imm
+            | Opcode::Mov32Imm
+            | Opcode::Arsh32Imm
+            | Opcode::Lmul32Imm
+            | Opcode::Udiv32Imm
+            | Opcode::Urem32Imm
+            | Opcode::Sdiv32Imm
+            | Opcode::Srem32Imm
+            | Opcode::Add64Imm
+            | Opcode::Sub64Imm
+            | Opcode::Mul64Imm
+            | Opcode::Div64Imm
+            | Opcode::Or64Imm
+            | Opcode::And64Imm
+            | Opcode::Lsh64Imm
+            | Opcode::Rsh64Imm
+            | Opcode::Mod64Imm
+            | Opcode::Xor64Imm
+            | Opcode::Mov64Imm
+            | Opcode::Arsh64Imm
+            | Opcode::Hor64Imm
+            | Opcode::Lmul64Imm
+            | Opcode::Uhmul64Imm
+            | Opcode::Udiv64Imm
+            | Opcode::Urem64Imm
+            | Opcode::Shmul64Imm
+            | Opcode::Sdiv64Imm
+            | Opcode::Srem64Imm
+            | Opcode::Be
+            | Opcode::Le => {
+                if src != 0 || off != 0 {
+                    return Err(SBPFError::BytecodeError {
+                        error: format!(
+                            "Arithmetic instruction with immediate expects src and off to be 0, but got src: {}, off: {}",
+                            src, off
+                        ),
+                        span: span.clone(),
+                        custom_label: None,
+                    });
+                }
+                out_dst = Some(Register { n: dst });
+                out_imm = Some(Number::Int(imm));
+            }
+
+            // Arithmetic instructions with register operands
+            Opcode::Add32Reg
+            | Opcode::Sub32Reg
+            | Opcode::Mul32Reg
+            | Opcode::Div32Reg
+            | Opcode::Or32Reg
+            | Opcode::And32Reg
+            | Opcode::Lsh32Reg
+            | Opcode::Rsh32Reg
+            | Opcode::Mod32Reg
+            | Opcode::Xor32Reg
+            | Opcode::Mov32Reg
+            | Opcode::Arsh32Reg
+            | Opcode::Lmul32Reg
+            | Opcode::Udiv32Reg
+            | Opcode::Urem32Reg
+            | Opcode::Sdiv32Reg
+            | Opcode::Srem32Reg
+            | Opcode::Add64Reg
+            | Opcode::Sub64Reg
+            | Opcode::Mul64Reg
+            | Opcode::Div64Reg
+            | Opcode::Or64Reg
+            | Opcode::And64Reg
+            | Opcode::Lsh64Reg
+            | Opcode::Rsh64Reg
+            | Opcode::Mod64Reg
+            | Opcode::Xor64Reg
+            | Opcode::Mov64Reg
+            | Opcode::Arsh64Reg
+            | Opcode::Lmul64Reg
+            | Opcode::Uhmul64Reg
+            | Opcode::Udiv64Reg
+            | Opcode::Urem64Reg
+            | Opcode::Shmul64Reg
+            | Opcode::Sdiv64Reg
+            | Opcode::Srem64Reg => {
+                if off != 0 || imm != 0 {
+                    return Err(SBPFError::BytecodeError {
+                        error: format!(
+                            "Arithmetic instruction with register expects off and imm to be 0, but got off: {}, imm: {}",
+                            off, imm
+                        ),
+                        span: span.clone(),
+                        custom_label: None,
+                    });
+                }
+                out_dst = Some(Register { n: dst });
+                out_src = Some(Register { n: src });
+            }
+
+            Opcode::Ldxw | Opcode::Ldxh | Opcode::Ldxb | Opcode::Ldxdw => {
+                if imm != 0 {
+                    return Err(SBPFError::BytecodeError {
+                        error: format!(
+                            "Load instruction expects imm to be 0, but got imm: {}",
+                            imm
+                        ),
+                        span: span.clone(),
+                        custom_label: None,
+                    });
+                }
+                out_dst = Some(Register { n: dst });
+                out_src = Some(Register { n: src });
+                out_off = Some(off);
+            }
+
+            Opcode::Stw | Opcode::Sth | Opcode::Stb | Opcode::Stdw => {
+                if src != 0 {
+                    return Err(SBPFError::BytecodeError {
+                        error: format!(
+                            "Store instruction expects src to be 0, but got src: {}",
+                            src
+                        ),
+                        span: span.clone(),
+                        custom_label: None,
+                    });
+                }
+                out_dst = Some(Register { n: dst });
+                out_off = Some(off);
+                out_imm = Some(Number::Int(imm));
+            }
+
+            Opcode::Stxb | Opcode::Stxh | Opcode::Stxw | Opcode::Stxdw => {
+                if imm != 0 {
+                    return Err(SBPFError::BytecodeError {
+                        error: format!(
+                            "Store instruction with register expects imm to be 0, but got imm: {}",
+                            imm
+                        ),
+                        span: span.clone(),
+                        custom_label: None,
+                    });
+                }
+                out_dst = Some(Register { n: dst });
+                out_src = Some(Register { n: src });
+                out_off = Some(off);
+            }
+
+            // Unary operations
+            Opcode::Neg32 | Opcode::Neg64 | Opcode::Exit => {
+                if src != 0 || off != 0 || imm != 0 {
+                    return Err(SBPFError::BytecodeError {
+                        error: format!(
+                            "Unary operation expects src, off, and imm to be 0, but got src: {}, off: {}, imm: {}",
+                            src, off, imm
+                        ),
+                        span: span.clone(),
+                        custom_label: None,
+                    });
+                }
+                out_dst = Some(Register { n: dst });
+            }
+
+            _ => {
+                return Err(SBPFError::BytecodeError {
+                    error: format!("Unsupported opcode: {:?}", opcode),
+                    span: span.clone(),
+                    custom_label: None,
+                });
+            }
+        }
+
+        Ok(Instruction {
+            opcode,
+            dst: out_dst,
+            src: out_src,
+            off: out_off,
+            imm: out_imm,
+            span,
+        })
+    }
+
+    pub fn to_bytes(&self) -> Vec<u8> {
+        let src_val = self.src.as_ref().map(|r| r.n).unwrap_or(0);
+        let dst_val = self.dst.as_ref().map(|r| r.n).unwrap_or(0);
+        let off_val = self.off.unwrap_or(0);
+        let imm_val = match &self.imm {
+            Some(Number::Int(imm)) | Some(Number::Addr(imm)) => *imm,
+            None => 0,
+        };
+
+        let mut b = vec![self.opcode.to_bytecode(), src_val << 4 | dst_val];
+        b.extend_from_slice(&off_val.to_le_bytes());
+        b.extend_from_slice(&(imm_val as i32).to_le_bytes());
+        if self.opcode == Opcode::Lddw {
+            b.extend_from_slice(&[0; 4]);
+            b.extend_from_slice(&((imm_val >> 32) as i32).to_le_bytes());
+        }
+        b
+    }
+
+    pub fn to_asm(&self) -> Result<String, SBPFError> {
+        Ok(match self.opcode {
+            // lddw - (load double word) takes up two instructions. The 64 bit value
+            // is made up of two halves with the upper half being the immediate
+            // of the lddw value and the lower half being the immediate of the
+            // following instruction
+            Opcode::Lddw => {
+                match (&self.dst, &self.imm) {
+                    (Some(dst), Some(imm)) => format!("{} r{}, {}", self.opcode, dst.n, imm),
+                    _ => format!("{} r0, 0", self.opcode),
+                }
+            },
+            // ldx - (load x) store a 8/16/32/64 bit (byte/half/word/double word)
+            // value in a register
+            Opcode::Ldxb |
+            Opcode::Ldxh |
+            Opcode::Ldxw |
+            Opcode::Ldxdw => {
+                match &self.dst {
+                    Some(dst) => format!("{} r{}, {}", self.opcode, dst.n, self.src_off()),
+                    None => format!("{} r0, {}", self.opcode, self.src_off()),
+                }
+            },
+            // stb - these instructions are deprecated
+            Opcode::Stb |
+            Opcode::Sth |
+            Opcode::Stw |
+            Opcode::Stdw => {
+                match &self.imm {
+                    Some(imm) => format!("{} {}, {}", self.opcode, self.dst_off(), imm),
+                    None => format!("{} {}, 0", self.opcode, self.dst_off()),
+                }
+            },
+            // stx - store a 8/16/32/64 bit value from a source register into the offset
+            // of the destination register
+            Opcode::Stxb |
+            Opcode::Stxh |
+            Opcode::Stxw |
+            Opcode::Stxdw => {
+                match &self.src {
+                    Some(src) => format!("{} {}, r{}", self.opcode, self.dst_off(), src.n),
+                    None => format!("{} {}, r0", self.opcode, self.dst_off()),
+                }
+            },
+            // Math
+            Opcode::Neg32 | // Deprecated in SBFv2
+            Opcode::Neg64 => {
+                match &self.dst {
+                    Some(dst) => format!("{} r{}", self.opcode, dst.n),
+                    None => format!("{} r0", self.opcode),
+                }
+            },
+            // LE and BE OpCodes act a little differently to others. In assembly form, they are
+            // notated as be16, be32 and b64. In byte form, the bit length of the operation is 
+            // determined by the immedate value of its parent instruction, 0x10, 0x20 and 0x40
+            // accordingly (the hex of 16/32/64)
+            Opcode::Le |
+            Opcode::Be => {
+                match &self.dst {
+                    Some(dst) => format!("{}{}", self.op_imm_bits()?, dst.n),
+                    None => format!("{}0", self.op_imm_bits()?),
+                }
+            }, // Docs for this seem wrong //DC01000010000000 DC01000020000000 DC01000040000000
+            // Immedate
+            Opcode::Add32Imm |
+            Opcode::Sub32Imm |
+            Opcode::Mul32Imm |
+            Opcode::Div32Imm |
+            Opcode::Or32Imm |
+            Opcode::And32Imm |
+            Opcode::Lsh32Imm |
+            Opcode::Rsh32Imm |
+            Opcode::Mod32Imm |
+            Opcode::Xor32Imm |
+            Opcode::Arsh32Imm |
+            Opcode::Mov32Imm |
+            Opcode::Lmul32Imm |
+            Opcode::Udiv32Imm |
+            Opcode::Urem32Imm |
+            Opcode::Sdiv32Imm |
+            Opcode::Srem32Imm |
+            Opcode::Add64Imm |
+            Opcode::Sub64Imm |
+            Opcode::Mul64Imm |
+            Opcode::Div64Imm |
+            Opcode::Or64Imm |
+            Opcode::And64Imm |
+            Opcode::Lsh64Imm |
+            Opcode::Rsh64Imm |
+            Opcode::Mod64Imm |
+            Opcode::Xor64Imm |
+            Opcode::Mov64Imm |
+            Opcode::Arsh64Imm |
+            Opcode::Hor64Imm |
+            Opcode::Lmul64Imm |
+            Opcode::Uhmul64Imm |
+            Opcode::Udiv64Imm |
+            Opcode::Urem64Imm |
+            Opcode::Shmul64Imm |
+            Opcode::Sdiv64Imm |
+            Opcode::Srem64Imm => {
+                match (&self.dst, &self.imm) {
+                    (Some(dst), Some(imm)) => format!("{} r{}, {}", self.opcode, dst.n, imm),
+                    _ => format!("{} r0, 0", self.opcode),
+                }
+            },
+            // Register
+            Opcode::Add32Reg |
+            Opcode::Sub32Reg |
+            Opcode::Mul32Reg |
+            Opcode::Div32Reg |
+            Opcode::Or32Reg |
+            Opcode::And32Reg |
+            Opcode::Lsh32Reg |
+            Opcode::Rsh32Reg |
+            Opcode::Mod32Reg |
+            Opcode::Xor32Reg |
+            Opcode::Mov32Reg |
+            Opcode::Arsh32Reg |
+            Opcode::Lmul32Reg |
+            Opcode::Udiv32Reg |
+            Opcode::Urem32Reg |
+            Opcode::Sdiv32Reg |
+            Opcode::Srem32Reg |
+            Opcode::Add64Reg |
+            Opcode::Sub64Reg |
+            Opcode::Mul64Reg |
+            Opcode::Div64Reg |
+            Opcode::Or64Reg |
+            Opcode::And64Reg |
+            Opcode::Lsh64Reg |
+            Opcode::Rsh64Reg |
+            Opcode::Mod64Reg |
+            Opcode::Xor64Reg |
+            Opcode::Mov64Reg |
+            Opcode::Arsh64Reg |
+            Opcode::Lmul64Reg |
+            Opcode::Uhmul64Reg |
+            Opcode::Udiv64Reg |
+            Opcode::Urem64Reg |
+            Opcode::Shmul64Reg |
+            Opcode::Sdiv64Reg |
+            Opcode::Srem64Reg => {
+                match (&self.dst, &self.src) {
+                    (Some(dst), Some(src)) => format!("{} r{}, r{}", self.opcode, dst.n, src.n),
+                    _ => format!("{} r0, r0", self.opcode),
+                }
+            },
+
+            // Jumps
+            Opcode::Ja => format!("{} {}", self.opcode, self.off_str()),
+
+            // Immediates
+            Opcode::JeqImm |
+            Opcode::JgtImm |
+            Opcode::JgeImm |
+            Opcode::JltImm |
+            Opcode::JleImm |
+            Opcode::JsetImm |
+            Opcode::JneImm |
+            Opcode::JsgtImm |
+            Opcode::JsgeImm |
+            Opcode::JsltImm |
+            Opcode::JsleImm => {
+                match (&self.dst, &self.imm) {
+                    (Some(dst), Some(imm)) => format!("{} r{}, {}, {}", self.opcode, dst.n, imm, self.off_str()),
+                    _ => format!("{} r0, 0, {}", self.opcode, self.off_str()),
+                }
+            },
+            // Registers
+            Opcode::JeqReg |
+            Opcode::JgtReg |
+            Opcode::JgeReg |
+            Opcode::JltReg |
+            Opcode::JleReg |
+            Opcode::JsetReg |
+            Opcode::JneReg |
+            Opcode::JsgtReg |
+            Opcode::JsgeReg |
+            Opcode::JsltReg |
+            Opcode::JsleReg => {
+                match (&self.dst, &self.src) {
+                    (Some(dst), Some(src)) => format!("{} r{}, r{}, {}", self.opcode, dst.n, src.n, self.off_str()),
+                    _ => format!("{} r0, r0, {}", self.opcode, self.off_str()),
+                }
+            },
+
+
+            // Calls
+            Opcode::Call => {
+                match &self.imm {
+                    Some(imm) => format!("call {}", imm),
+                    None => "call 0".to_string(),
+                }
+            },
+            Opcode::Callx => {
+                match &self.src {
+                    Some(src) => format!("call r{}", src.n),
+                    None => "call r0".to_string(),
+                }
+            },
+            Opcode::Exit => format!("{}", self.opcode),
+
+            _ => return Err(SBPFError::BytecodeError {
+                error: format!("Unsupported opcode: {:?}", self.opcode),
+                span: self.span.clone(),
+                custom_label: None,
+            })
+        })
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use hex_literal::hex;
+
+    use crate::instruction::Instruction;
+
+    #[test]
+    fn serialize_e2e() {
+        let b = hex!("9700000000000000");
+        let i = Instruction::from_bytes(&b).unwrap();
+        assert_eq!(i.to_bytes(), &b);
+    }
+
+    #[test]
+    fn serialize_e2e_lddw() {
+        let b = hex!("18010000000000000000000000000000");
+        let i = Instruction::from_bytes(&b).unwrap();
+        assert_eq!(i.to_bytes(), &b);
+    }
+}

+ 2 - 0
crates/common/src/lib.rs

@@ -1,2 +1,4 @@
+pub mod errors;
 pub mod instruction;
 pub mod instruction;
 pub mod opcode;
 pub mod opcode;
+pub mod syscall;

+ 35 - 0
crates/common/src/syscall.rs

@@ -0,0 +1,35 @@
+use phf::Map;
+use phf_macros::phf_map;
+
+pub static SYSCALLS: Map<u32, &'static str> = phf_map! {
+    0xb6fc1a11u32 => "abort",
+    0x686093bbu32 => "sol_panic_",
+    0x207559bdu32 => "sol_log_",
+    0x5c2a3178u32 => "sol_log_64_",
+    0x52ba5096u32 => "sol_log_compute_units_",
+    0x7ef088cau32 => "sol_log_pubkey",
+    0x9377323cu32 => "sol_create_program_address",
+    0x48504a38u32 => "sol_try_find_program_address",
+    0x11f49d86u32 => "sol_sha256",
+    0xd7793abbu32 => "sol_keccak256",
+    0x17e40350u32 => "sol_secp256k1_recover",
+    0x174c5122u32 => "sol_blake3",
+    0xaa2607cau32 => "sol_curve_validate_point",
+    0xdd1c41a6u32 => "sol_curve_group_op",
+    0xd56b5fe9u32 => "sol_get_clock_sysvar",
+    0x23a29a61u32 => "sol_get_epoch_schedule_sysvar",
+    0x3b97b73cu32 => "sol_get_fees_sysvar",
+    0xbf7188f6u32 => "sol_get_rent_sysvar",
+    0x717cc4a3u32 => "sol_memcpy_",
+    0x434371f8u32 => "sol_memmove_",
+    0x5fdcde31u32 => "sol_memcmp_",
+    0x3770fb22u32 => "sol_memset_",
+    0xa22b9c85u32 => "sol_invoke_signed_c",
+    0xd7449092u32 => "sol_invoke_signed_rust",
+    0x83f00e8fu32 => "sol_alloc_free_",
+    0xa226d3ebu32 => "sol_set_return_data",
+    0x5d2245e4u32 => "sol_get_return_data",
+    0x7317b434u32 => "sol_log_data",
+    0xadb8efc8u32 => "sol_get_processed_sibling_instruction",
+    0x85532d94u32 => "sol_get_stack_height",
+};