Переглянути джерело

move code from ezbpf to disassembler crate

bidhan-a 1 місяць тому
батько
коміт
d23d5ccff0

+ 3 - 1
Cargo.toml

@@ -19,9 +19,10 @@ codespan-reporting = "0.12.0"
 termcolor = "1.4"
 
 sbpf-assembler = { workspace = true }
+sbpf-disassembler = { workspace = true }
 
 [workspace]
-members = ["crates/assembler"]
+members = ["crates/assembler", "crates/disassembler"]
 exclude = ["examples"]
 
 [workspace.package]
@@ -34,3 +35,4 @@ thiserror = "2.0.12"
 anyhow = "1.0.86"
 
 sbpf-assembler = { path = "crates/assembler" }
+sbpf-disassembler = { path = "crates/disassembler" }

+ 23 - 0
crates/disassembler/Cargo.toml

@@ -0,0 +1,23 @@
+[package]
+name = "sbpf-disassembler"
+description = "SBPF Disassembler"
+edition = "2024"
+version.workspace = true
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "sbpf_disassembler"
+
+[dependencies]
+anyhow = { workspace = true }
+hex = "0.4.3"
+thiserror = "2.0.17"
+serde = { version = "1.0.228", features = ["derive"] }
+serde_json = "1.0.145"
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+wasm-bindgen = { version = "0.2.92", features = ["serde-serialize"] }
+serde-wasm-bindgen = "0.6.5"
+
+[dev-dependencies]
+hex-literal = "1.0.0"

+ 222 - 0
crates/disassembler/src/cursor.rs

@@ -0,0 +1,222 @@
+use std::io::{Cursor, Read, Seek, SeekFrom};
+
+use crate::{
+    elf_header::{
+        E_MACHINE, E_MACHINE_SBPF, E_TYPE, E_VERSION, EI_ABIVERSION, EI_CLASS, EI_DATA, EI_MAGIC,
+        EI_OSABI, EI_PAD, EI_VERSION, ELFHeader,
+    },
+    errors::EZBpfError,
+    instructions::Ix,
+    opcodes::OpCode,
+    program_header::{ProgramFlags, ProgramHeader, ProgramType},
+    section_header::{SectionHeader, SectionHeaderType},
+};
+
+pub trait ELFCursor {
+    fn read_elf_header(&mut self) -> Result<ELFHeader, EZBpfError>;
+    fn read_program_header(&mut self) -> Result<ProgramHeader, EZBpfError>;
+    fn read_section_header(&mut self) -> Result<SectionHeader, EZBpfError>;
+    fn read_ix(&mut self) -> Result<Ix, EZBpfError>;
+    fn read_lddw_imm(&mut self) -> Result<i64, EZBpfError>;
+    fn read_u8(&mut self) -> Result<u8, EZBpfError>;
+    fn read_i16(&mut self) -> Result<i16, EZBpfError>;
+    fn read_u16(&mut self) -> Result<u16, EZBpfError>;
+    fn read_i32(&mut self) -> Result<i32, EZBpfError>;
+    fn read_u32(&mut self) -> Result<u32, EZBpfError>;
+    fn read_u64(&mut self) -> Result<u64, EZBpfError>;
+    fn read_bytes(&mut self, l: usize) -> Result<Vec<u8>, EZBpfError>;
+    fn remainder(&mut self) -> u64;
+}
+
+impl ELFCursor for Cursor<&[u8]> {
+    fn read_elf_header(&mut self) -> Result<ELFHeader, EZBpfError> {
+        let ei_magic = self.read_u32()?.to_le_bytes();
+        let ei_class = self.read_u8()?;
+        let ei_data = self.read_u8()?;
+        let ei_version = self.read_u8()?;
+        let ei_osabi = self.read_u8()?;
+        let ei_abiversion = self.read_u8()?;
+        let mut ei_pad = [0u8; 7];
+        ei_pad.clone_from_slice(&self.read_bytes(7)?);
+        let e_type = self.read_u16()?;
+        let e_machine = self.read_u16()?;
+        let e_version = self.read_u32()?;
+        if ei_magic.ne(&EI_MAGIC)
+            || ei_class.ne(&EI_CLASS)
+            || ei_data.ne(&EI_DATA)
+            || ei_version.ne(&EI_VERSION)
+            || ei_osabi.ne(&EI_OSABI)
+            || ei_abiversion.ne(&EI_ABIVERSION)
+            || ei_pad.ne(&EI_PAD)
+            || e_type.ne(&E_TYPE)
+            || (e_machine.ne(&E_MACHINE) && e_machine.ne(&E_MACHINE_SBPF))
+            || e_version.ne(&E_VERSION)
+        {
+            return Err(EZBpfError::NonStandardElfHeader);
+        }
+
+        let e_entry = self.read_u64()?;
+        let e_phoff = self.read_u64()?;
+        let e_shoff = self.read_u64()?;
+        let e_flags = self.read_u32()?;
+        let e_ehsize = self.read_u16()?;
+        let e_phentsize = self.read_u16()?;
+        let e_phnum = self.read_u16()?;
+        let e_shentsize = self.read_u16()?;
+        let e_shnum = self.read_u16()?;
+        let e_shstrndx = self.read_u16()?;
+        Ok(ELFHeader {
+            ei_magic,
+            ei_class,
+            ei_data,
+            ei_version,
+            ei_osabi,
+            ei_abiversion,
+            ei_pad,
+            e_type,
+            e_machine,
+            e_version,
+            e_entry,
+            e_phoff,
+            e_shoff,
+            e_flags,
+            e_ehsize,
+            e_phentsize,
+            e_phnum,
+            e_shentsize,
+            e_shnum,
+            e_shstrndx,
+        })
+    }
+
+    fn read_program_header(&mut self) -> Result<ProgramHeader, EZBpfError> {
+        let p_type = ProgramType::try_from(self.read_u32()?)?;
+        let p_flags = ProgramFlags::from(self.read_u32()?);
+        let p_offset = self.read_u64()?;
+        let p_vaddr = self.read_u64()?;
+        let p_paddr = self.read_u64()?;
+        let p_filesz = self.read_u64()?;
+        let p_memsz = self.read_u64()?;
+        let p_align = self.read_u64()?;
+        Ok(ProgramHeader {
+            p_type,
+            p_flags,
+            p_offset,
+            p_vaddr,
+            p_paddr,
+            p_filesz,
+            p_memsz,
+            p_align,
+        })
+    }
+
+    fn read_section_header(&mut self) -> Result<SectionHeader, EZBpfError> {
+        let sh_name = self.read_u32()?;
+        let sh_type = SectionHeaderType::try_from(self.read_u32()?)?;
+        let sh_flags = self.read_u64()?;
+        let sh_addr = self.read_u64()?;
+        let sh_offset = self.read_u64()?;
+        let sh_size = self.read_u64()?;
+        let sh_link = self.read_u32()?;
+        let sh_info = self.read_u32()?;
+        let sh_addralign = self.read_u64()?;
+        let sh_entsize = self.read_u64()?;
+        Ok(SectionHeader {
+            sh_name,
+            sh_type,
+            sh_flags,
+            sh_addr,
+            sh_offset,
+            sh_size,
+            sh_link,
+            sh_info,
+            sh_addralign,
+            sh_entsize,
+        })
+    }
+
+    fn read_u8(&mut self) -> Result<u8, EZBpfError> {
+        let mut b = [0u8];
+        self.read_exact(&mut b)
+            .map_err(|_| EZBpfError::CursorError)?;
+        Ok(b[0])
+    }
+
+    fn read_u16(&mut self) -> Result<u16, EZBpfError> {
+        let mut b = [0u8; 2];
+        self.read_exact(&mut b)
+            .map_err(|_| EZBpfError::CursorError)?;
+        Ok(u16::from_le_bytes(b))
+    }
+
+    fn read_i16(&mut self) -> Result<i16, EZBpfError> {
+        let mut b = [0u8; 2];
+        self.read_exact(&mut b)
+            .map_err(|_| EZBpfError::CursorError)?;
+        Ok(i16::from_le_bytes(b))
+    }
+
+    fn read_u32(&mut self) -> Result<u32, EZBpfError> {
+        let mut b = [0u8; 4];
+        self.read_exact(&mut b)
+            .map_err(|_| EZBpfError::CursorError)?;
+        Ok(u32::from_le_bytes(b))
+    }
+
+    fn read_i32(&mut self) -> Result<i32, EZBpfError> {
+        let mut b = [0u8; 4];
+        self.read_exact(&mut b)
+            .map_err(|_| EZBpfError::CursorError)?;
+        Ok(i32::from_le_bytes(b))
+    }
+
+    fn read_u64(&mut self) -> Result<u64, EZBpfError> {
+        let mut b = [0u8; 8];
+        self.read_exact(&mut b)
+            .map_err(|_| EZBpfError::CursorError)?;
+        Ok(u64::from_le_bytes(b))
+    }
+
+    fn read_lddw_imm(&mut self) -> Result<i64, EZBpfError> {
+        let mut b = [0u8; 8];
+        b[0..4].clone_from_slice(&self.read_bytes(4)?);
+        if self.read_u32()? != 0 {
+            return Err(EZBpfError::InvalidImmediate);
+        }
+        b[4..8].clone_from_slice(&self.read_bytes(4)?);
+        Ok(i64::from_le_bytes(b))
+    }
+
+    fn read_ix(&mut self) -> Result<Ix, EZBpfError> {
+        let op = OpCode::try_from(self.read_u8()?)?;
+        let reg = self.read_u8()?;
+        let src = reg >> 4;
+        let dst = reg & 0x0f;
+        let off = self.read_i16()?;
+        let imm = match op {
+            OpCode::Lddw => self.read_lddw_imm()?,
+            _ => self.read_i32()? as i64,
+        };
+        Ok(Ix {
+            op,
+            src,
+            dst,
+            off,
+            imm,
+        })
+    }
+
+    fn read_bytes(&mut self, l: usize) -> Result<Vec<u8>, EZBpfError> {
+        let mut v = vec![0_u8; l];
+        self.read_exact(&mut v)
+            .map_err(|_| EZBpfError::CursorError)?;
+        Ok(v)
+    }
+
+    fn remainder(&mut self) -> u64 {
+        let pos = self.position();
+        let end = self.seek(SeekFrom::End(0)).unwrap();
+        self.seek(SeekFrom::Start(pos)).unwrap();
+        end - pos
+    }
+}

+ 109 - 0
crates/disassembler/src/elf_header.rs

@@ -0,0 +1,109 @@
+use std::io::Cursor;
+use std::str;
+
+use serde::{Deserialize, Serialize, Serializer};
+
+use crate::{cursor::ELFCursor, errors::EZBpfError};
+
+pub const EI_MAGIC: [u8; 4] = *b"\x7fELF"; // ELF magic
+pub const EI_CLASS: u8 = 0x02; // 64-bit
+pub const EI_DATA: u8 = 0x01; // Little endian
+pub const EI_VERSION: u8 = 0x01; // Version 1
+pub const EI_OSABI: u8 = 0x00; // System V
+pub const EI_ABIVERSION: u8 = 0x00; // No ABI version
+pub const EI_PAD: [u8; 7] = [0u8; 7]; // Padding
+pub const E_TYPE: u16 = 0x03; // ET_DYN - shared object
+pub const E_MACHINE: u16 = 0xf7; // Berkeley Packet Filter
+pub const E_MACHINE_SBPF: u16 = 0x0107; // Solana Berkeley Packet Filter
+pub const E_VERSION: u32 = 0x01; // Original version of BPF
+
+fn elf_magic<S>(magic: &[u8; 4], serializer: S) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    let s = String::from_utf8_lossy(magic);
+    serializer.serialize_str(&s)
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct ELFHeader {
+    #[serde(serialize_with = "elf_magic")]
+    pub ei_magic: [u8; 4],
+    pub ei_class: u8,
+    pub ei_data: u8,
+    pub ei_version: u8,
+    pub ei_osabi: u8,
+    pub ei_abiversion: u8,
+    pub ei_pad: [u8; 7],
+    pub e_type: u16,
+    pub e_machine: u16,
+    pub e_version: u32,
+    pub e_entry: u64,
+    pub e_phoff: u64,
+    pub e_shoff: u64,
+    pub e_flags: u32,
+    pub e_ehsize: u16,
+    pub e_phentsize: u16,
+    pub e_phnum: u16,
+    pub e_shentsize: u16,
+    pub e_shnum: u16,
+    pub e_shstrndx: u16,
+}
+
+impl ELFHeader {
+    pub fn from_bytes(b: &[u8]) -> Result<Self, EZBpfError> {
+        let mut c = Cursor::new(b);
+        c.read_elf_header()
+    }
+
+    pub fn to_bytes(&self) -> Vec<u8> {
+        let mut b = self.ei_magic.to_vec();
+        b.extend_from_slice(&[
+            self.ei_class,
+            self.ei_data,
+            self.ei_version,
+            self.ei_osabi,
+            self.ei_abiversion,
+        ]);
+        b.extend_from_slice(&self.ei_pad);
+        b.extend_from_slice(&self.e_type.to_le_bytes());
+        b.extend_from_slice(&self.e_machine.to_le_bytes());
+        b.extend_from_slice(&self.e_version.to_le_bytes());
+        b.extend_from_slice(&self.e_entry.to_le_bytes());
+        b.extend_from_slice(&self.e_phoff.to_le_bytes());
+        b.extend_from_slice(&self.e_shoff.to_le_bytes());
+        b.extend_from_slice(&self.e_flags.to_le_bytes());
+        b.extend_from_slice(&self.e_ehsize.to_le_bytes());
+        b.extend_from_slice(&self.e_phentsize.to_le_bytes());
+        b.extend_from_slice(&self.e_phnum.to_le_bytes());
+        b.extend_from_slice(&self.e_shentsize.to_le_bytes());
+        b.extend_from_slice(&self.e_shnum.to_le_bytes());
+        b.extend_from_slice(&self.e_shstrndx.to_le_bytes());
+        b
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use hex_literal::hex;
+
+    use super::ELFHeader;
+
+    #[test]
+    fn serialize_e2e() {
+        let b = hex!(
+            "7F454C460201010000000000000000000300F7000100000078000000000000004000000000000000900000000000000000000000400038000100400003000200"
+        );
+        let h = ELFHeader::from_bytes(&b).unwrap();
+        assert_eq!(h.to_bytes(), &b)
+    }
+
+    #[test]
+    fn serialize_sbpf_machine_e2e() {
+        let b = hex!(
+            "7F454C46020101000000000000000000030007010100000020010000000000004000000000000000680200000000000000000000400038000300400006000500"
+        );
+        let h = ELFHeader::from_bytes(&b).unwrap();
+        assert_eq!(h.to_bytes(), &b)
+    }
+}

+ 21 - 0
crates/disassembler/src/errors.rs

@@ -0,0 +1,21 @@
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum EZBpfError {
+    #[error("Failed to read from cursor")]
+    CursorError,
+    #[error("Non-standard ELF header")]
+    NonStandardElfHeader,
+    #[error("Invalid Program Type")]
+    InvalidProgramType,
+    #[error("Invalid Section Header Type")]
+    InvalidSectionHeaderType,
+    #[error("Invalid OpCode")]
+    InvalidOpcode,
+    #[error("Invalid Immediate")]
+    InvalidImmediate,
+    #[error("Invalid data length")]
+    InvalidDataLength,
+    #[error("Invalid string")]
+    InvalidString,
+}

+ 224 - 0
crates/disassembler/src/instructions.rs

@@ -0,0 +1,224 @@
+use std::io::Cursor;
+
+use serde::{Deserialize, Serialize};
+
+use crate::{cursor::ELFCursor, errors::EZBpfError, opcodes::OpCode};
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+pub struct Ix {
+    pub op: OpCode,
+    pub dst: u8,
+    pub src: u8,
+    pub off: i16,
+    pub imm: i64,
+}
+
+impl Ix {
+    pub fn off_str(&self) -> String {
+        match self.off.is_negative() {
+            true => self.off.to_string(),
+            false => format!("+{}", self.off),
+        }
+    }
+
+    pub fn dst_off(&self) -> String {
+        format!("[r{}{}]", self.dst, self.off_str())
+    }
+
+    pub fn src_off(&self) -> String {
+        format!("[r{}{}]", self.src, self.off_str())
+    }
+
+    pub fn op_imm_bits(&self) -> Result<String, EZBpfError> {
+        Ok(match self.imm {
+            16 => format!("{}16", self.op),
+            32 => format!("{}32", self.op),
+            64 => format!("{}64", self.op),
+            _ => return Err(EZBpfError::InvalidImmediate),
+        })
+    }
+}
+
+impl Ix {
+    pub fn from_bytes(b: &[u8]) -> Result<Self, EZBpfError> {
+        let mut c = Cursor::new(b);
+        c.read_ix()
+    }
+
+    pub fn to_bytes(&self) -> Vec<u8> {
+        let mut b = vec![self.op.clone() as u8, self.src << 4 | self.dst];
+        b.extend_from_slice(&self.off.to_le_bytes());
+        b.extend_from_slice(&self.imm.to_le_bytes()[..4]);
+        if self.op == OpCode::Lddw {
+            b.extend_from_slice(&[0; 4]);
+            b.extend_from_slice(&self.imm.to_le_bytes()[4..]);
+        }
+        b
+    }
+
+    pub fn to_asm(&self) -> Result<String, EZBpfError> {
+        Ok(match self.op {
+            // 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 => format!("{} r{}, {}", self.op, self.dst, self.imm),
+            // 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 => format!("{} r{}, {}", self.op, self.dst, self.src_off()),
+            // stb - these instructions are deprecated
+            OpCode::Stb |
+            OpCode::Sth |
+            OpCode::Stw |
+            OpCode::Stdw => format!("{} {}, {}", self.op, self.dst_off(), self.imm),
+            // 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 => format!("{} {}, r{}", self.op, self.dst_off(), self.src),
+            // Math
+            OpCode::Neg32 | // Deprecated in SBFv2
+            OpCode::Neg64 => format!("{} r{}", self.op, self.dst),
+            // 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 => format!("{}{}", self.op_imm_bits()?, self.dst), // 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 => format!("{} r{}, {}", self.op, self.dst, self.imm),
+            // 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 => format!("{} r{}, r{}", self.op, self.dst, self.src),
+
+            // Jumps
+            OpCode::Ja => format!("{} {}", self.op, 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 => format!("{} r{}, {}, {}", self.op, self.dst, self.imm, 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 => format!("{} r{}, r{}, {}", self.op, self.dst, self.src, self.off_str()),
+
+
+            // Calls
+            OpCode::Call => format!("call {}", self.imm),
+            OpCode::Callx => format!("call r{}", self.src),
+            OpCode::Exit => format!("{}", self.op),
+        })
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use hex_literal::hex;
+
+    use crate::instructions::Ix;
+
+    #[test]
+    fn serialize_e2e() {
+        let b = hex!("9700000000000000");
+        let i = Ix::from_bytes(&b).unwrap();
+        assert_eq!(i.to_bytes(), &b);
+    }
+
+    #[test]
+    fn serialize_e2e_lddw() {
+        let b = hex!("18010000000000000000000000000000");
+        let i = Ix::from_bytes(&b).unwrap();
+        assert_eq!(i.to_bytes(), &b);
+    }
+}

+ 12 - 0
crates/disassembler/src/lib.rs

@@ -0,0 +1,12 @@
+pub mod cursor;
+pub mod elf_header;
+pub mod errors;
+pub mod instructions;
+pub mod opcodes;
+pub mod program;
+pub mod program_header;
+pub mod section_header;
+pub mod section_header_entry;
+
+#[cfg(target_arch = "wasm32")]
+pub mod wasm;

+ 388 - 0
crates/disassembler/src/opcodes.rs

@@ -0,0 +1,388 @@
+use core::str;
+use std::fmt::Display;
+
+use serde::{Deserialize, Serialize};
+
+use crate::errors::EZBpfError;
+
+#[repr(u8)]
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
+pub enum OpCode {
+    Lddw = 0x18,
+    Ldxb = 0x71,
+    Ldxh = 0x69,
+    Ldxw = 0x61,
+    Ldxdw = 0x79,
+    Stb = 0x72,
+    Sth = 0x6a,
+    Stw = 0x62,
+    Stdw = 0x7a,
+    Stxb = 0x73,
+    Stxh = 0x6b,
+    Stxw = 0x63,
+    Stxdw = 0x7b,
+    Add32Imm = 0x04,
+    Add32Reg = 0x0c,
+    Sub32Imm = 0x14,
+    Sub32Reg = 0x1c,
+    Mul32Imm = 0x24,
+    Mul32Reg = 0x2c,
+    Div32Imm = 0x34,
+    Div32Reg = 0x3c,
+    Or32Imm = 0x44,
+    Or32Reg = 0x4c,
+    And32Imm = 0x54,
+    And32Reg = 0x5c,
+    Lsh32Imm = 0x64,
+    Lsh32Reg = 0x6c,
+    Rsh32Imm = 0x74,
+    Rsh32Reg = 0x7c,
+    Neg32 = 0x84,
+    Mod32Imm = 0x94,
+    Mod32Reg = 0x9c,
+    Xor32Imm = 0xa4,
+    Xor32Reg = 0xac,
+    Mov32Imm = 0xb4,
+    Mov32Reg = 0xbc,
+    Arsh32Imm = 0xc4,
+    Arsh32Reg = 0xcc,
+    Lmul32Imm = 0x86,
+    Lmul32Reg = 0x8e,
+    Udiv32Imm = 0x46,
+    Udiv32Reg = 0x4e,
+    Urem32Imm = 0x66,
+    Urem32Reg = 0x6e,
+    Sdiv32Imm = 0xc6,
+    Sdiv32Reg = 0xce,
+    Srem32Imm = 0xe6,
+    Srem32Reg = 0xee,
+    Le = 0xd4,
+    Be = 0xdc,
+    Add64Imm = 0x07,
+    Add64Reg = 0x0f,
+    Sub64Imm = 0x17,
+    Sub64Reg = 0x1f,
+    Mul64Imm = 0x27,
+    Mul64Reg = 0x2f,
+    Div64Imm = 0x37,
+    Div64Reg = 0x3f,
+    Or64Imm = 0x47,
+    Or64Reg = 0x4f,
+    And64Imm = 0x57,
+    And64Reg = 0x5f,
+    Lsh64Imm = 0x67,
+    Lsh64Reg = 0x6f,
+    Rsh64Imm = 0x77,
+    Rsh64Reg = 0x7f,
+    Neg64 = 0x87,
+    Mod64Imm = 0x97,
+    Mod64Reg = 0x9f,
+    Xor64Imm = 0xa7,
+    Xor64Reg = 0xaf,
+    Mov64Imm = 0xb7,
+    Mov64Reg = 0xbf,
+    Arsh64Imm = 0xc7,
+    Arsh64Reg = 0xcf,
+    Hor64Imm = 0xf7,
+    Lmul64Imm = 0x96,
+    Lmul64Reg = 0x9e,
+    Uhmul64Imm = 0x36,
+    Uhmul64Reg = 0x3e,
+    Udiv64Imm = 0x56,
+    Udiv64Reg = 0x5e,
+    Urem64Imm = 0x76,
+    Urem64Reg = 0x7e,
+    Shmul64Imm = 0xb6,
+    Shmul64Reg = 0xbe,
+    Sdiv64Imm = 0xd6,
+    Sdiv64Reg = 0xde,
+    Srem64Imm = 0xf6,
+    Srem64Reg = 0xfe,
+    Ja = 0x05,
+    JeqImm = 0x15,
+    JeqReg = 0x1d,
+    JgtImm = 0x25,
+    JgtReg = 0x2d,
+    JgeImm = 0x35,
+    JgeReg = 0x3d,
+    JltImm = 0xa5,
+    JltReg = 0xad,
+    JleImm = 0xb5,
+    JleReg = 0xbd,
+    JsetImm = 0x45,
+    JsetReg = 0x4d,
+    JneImm = 0x55,
+    JneReg = 0x5d,
+    JsgtImm = 0x65,
+    JsgtReg = 0x6d,
+    JsgeImm = 0x75,
+    JsgeReg = 0x7d,
+    JsltImm = 0xc5,
+    JsltReg = 0xcd,
+    JsleImm = 0xd5,
+    JsleReg = 0xdd,
+    Call = 0x85,
+    Callx = 0x8d,
+    Exit = 0x95,
+}
+
+impl Display for OpCode {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(Into::<&str>::into(self.clone()))
+    }
+}
+
+impl TryFrom<u8> for OpCode {
+    type Error = EZBpfError;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        Ok(match value {
+            0x18 => OpCode::Lddw,
+            0x71 => OpCode::Ldxb,
+            0x69 => OpCode::Ldxh,
+            0x61 => OpCode::Ldxw,
+            0x79 => OpCode::Ldxdw,
+            0x72 => OpCode::Stb,
+            0x6a => OpCode::Sth,
+            0x62 => OpCode::Stw,
+            0x7a => OpCode::Stdw,
+            0x73 => OpCode::Stxb,
+            0x6b => OpCode::Stxh,
+            0x63 => OpCode::Stxw,
+            0x7b => OpCode::Stxdw,
+            0x04 => OpCode::Add32Imm,
+            0x0c => OpCode::Add32Reg,
+            0x14 => OpCode::Sub32Imm,
+            0x1c => OpCode::Sub32Reg,
+            0x24 => OpCode::Mul32Imm,
+            0x2c => OpCode::Mul32Reg,
+            0x34 => OpCode::Div32Imm,
+            0x3c => OpCode::Div32Reg,
+            0x44 => OpCode::Or32Imm,
+            0x4c => OpCode::Or32Reg,
+            0x54 => OpCode::And32Imm,
+            0x5c => OpCode::And32Reg,
+            0x64 => OpCode::Lsh32Imm,
+            0x6c => OpCode::Lsh32Reg,
+            0x74 => OpCode::Rsh32Imm,
+            0x7c => OpCode::Rsh32Reg,
+            0x84 => OpCode::Neg32,
+            0x94 => OpCode::Mod32Imm,
+            0x9c => OpCode::Mod32Reg,
+            0xa4 => OpCode::Xor32Imm,
+            0xac => OpCode::Xor32Reg,
+            0xb4 => OpCode::Mov32Imm,
+            0xbc => OpCode::Mov32Reg,
+            0xc4 => OpCode::Arsh32Imm,
+            0xcc => OpCode::Arsh32Reg,
+            0x86 => OpCode::Lmul32Imm,
+            0x8e => OpCode::Lmul32Reg,
+            0x46 => OpCode::Udiv32Imm,
+            0x4e => OpCode::Udiv32Reg,
+            0x66 => OpCode::Urem32Imm,
+            0x6e => OpCode::Urem32Reg,
+            0xc6 => OpCode::Sdiv32Imm,
+            0xce => OpCode::Sdiv32Reg,
+            0xe6 => OpCode::Srem32Imm,
+            0xee => OpCode::Srem32Reg,
+            0xd4 => OpCode::Le,
+            0xdc => OpCode::Be,
+            0x07 => OpCode::Add64Imm,
+            0x0f => OpCode::Add64Reg,
+            0x17 => OpCode::Sub64Imm,
+            0x1f => OpCode::Sub64Reg,
+            0x27 => OpCode::Mul64Imm,
+            0x2f => OpCode::Mul64Reg,
+            0x37 => OpCode::Div64Imm,
+            0x3f => OpCode::Div64Reg,
+            0x47 => OpCode::Or64Imm,
+            0x4f => OpCode::Or64Reg,
+            0x57 => OpCode::And64Imm,
+            0x5f => OpCode::And64Reg,
+            0x67 => OpCode::Lsh64Imm,
+            0x6f => OpCode::Lsh64Reg,
+            0x77 => OpCode::Rsh64Imm,
+            0x7f => OpCode::Rsh64Reg,
+            0x87 => OpCode::Neg64,
+            0x97 => OpCode::Mod64Imm,
+            0x9f => OpCode::Mod64Reg,
+            0xa7 => OpCode::Xor64Imm,
+            0xaf => OpCode::Xor64Reg,
+            0xb7 => OpCode::Mov64Imm,
+            0xbf => OpCode::Mov64Reg,
+            0xc7 => OpCode::Arsh64Imm,
+            0xcf => OpCode::Arsh64Reg,
+            0xf7 => OpCode::Hor64Imm,
+            0x96 => OpCode::Lmul64Imm,
+            0x9e => OpCode::Lmul64Reg,
+            0x36 => OpCode::Uhmul64Imm,
+            0x3e => OpCode::Uhmul64Reg,
+            0x56 => OpCode::Udiv64Imm,
+            0x5e => OpCode::Udiv64Reg,
+            0x76 => OpCode::Urem64Imm,
+            0x7e => OpCode::Urem64Reg,
+            0xb6 => OpCode::Shmul64Imm,
+            0xbe => OpCode::Shmul64Reg,
+            0xd6 => OpCode::Sdiv64Imm,
+            0xde => OpCode::Sdiv64Reg,
+            0xf6 => OpCode::Srem64Imm,
+            0xfe => OpCode::Srem64Reg,
+            0x05 => OpCode::Ja,
+            0x15 => OpCode::JeqImm,
+            0x1d => OpCode::JeqReg,
+            0x25 => OpCode::JgtImm,
+            0x2d => OpCode::JgtReg,
+            0x35 => OpCode::JgeImm,
+            0x3d => OpCode::JgeReg,
+            0xa5 => OpCode::JltImm,
+            0xad => OpCode::JltReg,
+            0xb5 => OpCode::JleImm,
+            0xbd => OpCode::JleReg,
+            0x45 => OpCode::JsetImm,
+            0x4d => OpCode::JsetReg,
+            0x55 => OpCode::JneImm,
+            0x5d => OpCode::JneReg,
+            0x65 => OpCode::JsgtImm,
+            0x6d => OpCode::JsgtReg,
+            0x75 => OpCode::JsgeImm,
+            0x7d => OpCode::JsgeReg,
+            0xc5 => OpCode::JsltImm,
+            0xcd => OpCode::JsltReg,
+            0xd5 => OpCode::JsleImm,
+            0xdd => OpCode::JsleReg,
+            0x85 => OpCode::Call,
+            0x8d => OpCode::Callx,
+            0x95 => OpCode::Exit,
+            _ => return Err(EZBpfError::InvalidOpcode),
+        })
+    }
+}
+
+impl From<OpCode> for u8 {
+    fn from(val: OpCode) -> Self {
+        val as u8
+    }
+}
+
+impl From<OpCode> for &str {
+    fn from(val: OpCode) -> Self {
+        match val {
+            OpCode::Lddw => "lddw",
+            OpCode::Ldxb => "ldxb",
+            OpCode::Ldxh => "ldxh",
+            OpCode::Ldxw => "ldxw",
+            OpCode::Ldxdw => "ldxdw",
+            OpCode::Stb => "stb",
+            OpCode::Sth => "sth",
+            OpCode::Stw => "stw",
+            OpCode::Stdw => "stdw",
+            OpCode::Stxb => "stxb",
+            OpCode::Stxh => "stxh",
+            OpCode::Stxw => "stxw",
+            OpCode::Stxdw => "stxdw",
+            OpCode::Add32Imm => "add32",
+            OpCode::Add32Reg => "add32",
+            OpCode::Sub32Imm => "sub32",
+            OpCode::Sub32Reg => "sub32",
+            OpCode::Mul32Imm => "mul32",
+            OpCode::Mul32Reg => "mul32",
+            OpCode::Div32Imm => "div32",
+            OpCode::Div32Reg => "div32",
+            OpCode::Or32Imm => "or32",
+            OpCode::Or32Reg => "or32",
+            OpCode::And32Imm => "and32",
+            OpCode::And32Reg => "and32",
+            OpCode::Lsh32Imm => "lsh32",
+            OpCode::Lsh32Reg => "lsh32",
+            OpCode::Rsh32Imm => "rsh32",
+            OpCode::Rsh32Reg => "rsh32",
+            OpCode::Neg32 => "neg32",
+            OpCode::Mod32Imm => "mod32",
+            OpCode::Mod32Reg => "mod32",
+            OpCode::Xor32Imm => "xor32",
+            OpCode::Xor32Reg => "xor32",
+            OpCode::Mov32Imm => "mov32",
+            OpCode::Mov32Reg => "mov32",
+            OpCode::Arsh32Imm => "arsh32",
+            OpCode::Arsh32Reg => "arsh32",
+            OpCode::Lmul32Imm => "lmul32",
+            OpCode::Lmul32Reg => "lmul32",
+            OpCode::Udiv32Imm => "udiv32",
+            OpCode::Udiv32Reg => "udiv32",
+            OpCode::Urem32Imm => "urem32",
+            OpCode::Urem32Reg => "urem32",
+            OpCode::Sdiv32Imm => "sdiv32",
+            OpCode::Sdiv32Reg => "sdiv32",
+            OpCode::Srem32Imm => "srem32",
+            OpCode::Srem32Reg => "srem32",
+            OpCode::Le => "le",
+            OpCode::Be => "be",
+            OpCode::Add64Imm => "add64",
+            OpCode::Add64Reg => "add64",
+            OpCode::Sub64Imm => "sub64",
+            OpCode::Sub64Reg => "sub64",
+            OpCode::Mul64Imm => "mul64",
+            OpCode::Mul64Reg => "mul64",
+            OpCode::Div64Imm => "div64",
+            OpCode::Div64Reg => "div64",
+            OpCode::Or64Imm => "or64",
+            OpCode::Or64Reg => "or64",
+            OpCode::And64Imm => "and64",
+            OpCode::And64Reg => "and64",
+            OpCode::Lsh64Imm => "lsh64",
+            OpCode::Lsh64Reg => "lsh64",
+            OpCode::Rsh64Imm => "rsh64",
+            OpCode::Rsh64Reg => "rsh64",
+            OpCode::Neg64 => "neg64",
+            OpCode::Mod64Imm => "mod64",
+            OpCode::Mod64Reg => "mod64",
+            OpCode::Xor64Imm => "xor64",
+            OpCode::Xor64Reg => "xor64",
+            OpCode::Mov64Imm => "mov64",
+            OpCode::Mov64Reg => "mov64",
+            OpCode::Arsh64Imm => "arsh64",
+            OpCode::Arsh64Reg => "arsh64",
+            OpCode::Hor64Imm => "hor64",
+            OpCode::Lmul64Imm => "lmul64",
+            OpCode::Lmul64Reg => "lmul64",
+            OpCode::Uhmul64Imm => "uhmul64",
+            OpCode::Uhmul64Reg => "uhmul64",
+            OpCode::Udiv64Imm => "udiv64",
+            OpCode::Udiv64Reg => "udiv64",
+            OpCode::Urem64Imm => "urem64",
+            OpCode::Urem64Reg => "urem64",
+            OpCode::Shmul64Imm => "shmul64",
+            OpCode::Shmul64Reg => "shmul64",
+            OpCode::Sdiv64Imm => "sdiv64",
+            OpCode::Sdiv64Reg => "sdiv64",
+            OpCode::Srem64Imm => "srem64",
+            OpCode::Srem64Reg => "srem64",
+            OpCode::Ja => "ja",
+            OpCode::JeqImm => "jeq",
+            OpCode::JeqReg => "jeq",
+            OpCode::JgtImm => "jgt",
+            OpCode::JgtReg => "jgt",
+            OpCode::JgeImm => "jge",
+            OpCode::JgeReg => "jge",
+            OpCode::JltImm => "jlt",
+            OpCode::JltReg => "jlt",
+            OpCode::JleImm => "jle",
+            OpCode::JleReg => "jle",
+            OpCode::JsetImm => "jset",
+            OpCode::JsetReg => "jset",
+            OpCode::JneImm => "jne",
+            OpCode::JneReg => "jne",
+            OpCode::JsgtImm => "jsgt",
+            OpCode::JsgtReg => "jsgt",
+            OpCode::JsgeImm => "jsge",
+            OpCode::JsgeReg => "jsge",
+            OpCode::JsltImm => "jslt",
+            OpCode::JsltReg => "jslt",
+            OpCode::JsleImm => "jsle",
+            OpCode::JsleReg => "jsle",
+            OpCode::Call => "call",
+            OpCode::Callx => "callx",
+            OpCode::Exit => "exit",
+        }
+    }
+}

+ 79 - 0
crates/disassembler/src/program.rs

@@ -0,0 +1,79 @@
+use std::io::Cursor;
+
+use anyhow::Result;
+use serde::{Deserialize, Serialize};
+
+use crate::{
+    cursor::ELFCursor, elf_header::ELFHeader, errors::EZBpfError, program_header::ProgramHeader,
+    section_header::SectionHeader, section_header_entry::SectionHeaderEntry,
+};
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Program {
+    pub elf_header: ELFHeader,
+    pub program_headers: Vec<ProgramHeader>,
+    pub section_headers: Vec<SectionHeader>,
+    pub section_header_entries: Vec<SectionHeaderEntry>,
+}
+
+impl Program {
+    pub fn from_bytes(b: &[u8]) -> Result<Self, EZBpfError> {
+        let mut c = Cursor::new(b);
+        let elf_header = c.read_elf_header()?;
+
+        c.set_position(elf_header.e_phoff);
+        let program_headers = (0..elf_header.e_phnum)
+            .map(|_| c.read_program_header())
+            .collect::<Result<Vec<_>, _>>()?;
+        c.set_position(elf_header.e_shoff);
+        let section_headers = (0..elf_header.e_shnum)
+            .map(|_| c.read_section_header())
+            .collect::<Result<Vec<_>, _>>()?;
+
+        let shstrndx = &section_headers[elf_header.e_shstrndx as usize];
+        let shstrndx_value = b
+            [shstrndx.sh_offset as usize..shstrndx.sh_offset as usize + shstrndx.sh_size as usize]
+            .to_vec();
+
+        let mut indices: Vec<u32> = section_headers.iter().map(|h| h.sh_name).collect();
+        indices.push(shstrndx.sh_size as u32);
+        indices.sort_unstable();
+
+        let section_header_entries = section_headers
+            .iter()
+            .map(|s| {
+                let current_offset = s.sh_name as usize;
+                let next_index = indices.binary_search(&s.sh_name).unwrap() + 1;
+                let next_offset =
+                    *indices.get(next_index).ok_or(EZBpfError::InvalidString)? as usize;
+
+                let label = String::from_utf8(shstrndx_value[current_offset..next_offset].to_vec())
+                    .unwrap_or("default".to_string());
+                let data =
+                    b[s.sh_offset as usize..s.sh_offset as usize + s.sh_size as usize].to_vec();
+
+                SectionHeaderEntry::new(label, s.sh_offset as usize, data)
+            })
+            .collect::<Result<Vec<_>, _>>()?;
+
+        Ok(Self {
+            elf_header,
+            program_headers,
+            section_headers,
+            section_header_entries,
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use hex_literal::hex;
+
+    use crate::program::Program;
+
+    #[test]
+    fn try_deserialize_program() {
+        let program = Program::from_bytes(&hex!("7F454C460201010000000000000000000300F700010000002001000000000000400000000000000028020000000000000000000040003800030040000600050001000000050000002001000000000000200100000000000020010000000000003000000000000000300000000000000000100000000000000100000004000000C001000000000000C001000000000000C0010000000000003C000000000000003C000000000000000010000000000000020000000600000050010000000000005001000000000000500100000000000070000000000000007000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007912A000000000007911182900000000B7000000010000002D21010000000000B70000000000000095000000000000001E0000000000000004000000000000000600000000000000C0010000000000000B0000000000000018000000000000000500000000000000F0010000000000000A000000000000000C00000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000120001002001000000000000300000000000000000656E747279706F696E7400002E74657874002E64796E737472002E64796E73796D002E64796E616D6963002E73687374727461620000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000010000000600000000000000200100000000000020010000000000003000000000000000000000000000000008000000000000000000000000000000170000000600000003000000000000005001000000000000500100000000000070000000000000000400000000000000080000000000000010000000000000000F0000000B0000000200000000000000C001000000000000C001000000000000300000000000000004000000010000000800000000000000180000000000000007000000030000000200000000000000F001000000000000F0010000000000000C00000000000000000000000000000001000000000000000000000000000000200000000300000000000000000000000000000000000000FC010000000000002A00000000000000000000000000000001000000000000000000000000000000")).unwrap();
+        println!("{:?}", program.section_header_entries);
+    }
+}

+ 148 - 0
crates/disassembler/src/program_header.rs

@@ -0,0 +1,148 @@
+use std::{fmt::Debug, io::Cursor};
+
+use serde::{Deserialize, Serialize};
+
+use crate::{cursor::ELFCursor, errors::EZBpfError};
+
+// Program Segment Flags
+pub const PF_X: u8 = 0x01;
+pub const PF_W: u8 = 0x02;
+pub const PF_R: u8 = 0x04;
+
+#[allow(non_camel_case_types)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[repr(u32)]
+pub enum ProgramType {
+    PT_NULL = 0x00,    // Program header table entry unused.
+    PT_LOAD = 0x01,    // Loadable segment.
+    PT_DYNAMIC = 0x02, // Dynamic linking information.
+    PT_INTERP = 0x03,  // Interpreter information.
+    PT_NOTE = 0x04,    // Auxiliary information.
+    PT_SHLIB = 0x05,   // Reserved.
+    PT_PHDR = 0x06,    // Segment containing program header table itself.
+    PT_TLS = 0x07,     // Thread-Local Storage template.
+}
+
+impl TryFrom<u32> for ProgramType {
+    type Error = EZBpfError;
+
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        Ok(match value {
+            0 => Self::PT_NULL,
+            1 => Self::PT_LOAD,
+            2 => Self::PT_DYNAMIC,
+            3 => Self::PT_INTERP,
+            4 => Self::PT_NOTE,
+            5 => Self::PT_SHLIB,
+            6 => Self::PT_PHDR,
+            7 => Self::PT_TLS,
+            _ => return Err(EZBpfError::InvalidProgramType),
+        })
+    }
+}
+
+impl From<ProgramType> for u32 {
+    fn from(val: ProgramType) -> Self {
+        match val {
+            ProgramType::PT_NULL => 0,
+            ProgramType::PT_LOAD => 1,
+            ProgramType::PT_DYNAMIC => 2,
+            ProgramType::PT_INTERP => 3,
+            ProgramType::PT_NOTE => 4,
+            ProgramType::PT_SHLIB => 5,
+            ProgramType::PT_PHDR => 6,
+            ProgramType::PT_TLS => 7,
+        }
+    }
+}
+
+impl From<ProgramType> for &str {
+    fn from(val: ProgramType) -> Self {
+        match val {
+            ProgramType::PT_NULL => "PT_NULL",
+            ProgramType::PT_LOAD => "PT_LOAD",
+            ProgramType::PT_DYNAMIC => "PT_DYNAMIC",
+            ProgramType::PT_INTERP => "PT_INTERP",
+            ProgramType::PT_NOTE => "PT_NOTE",
+            ProgramType::PT_SHLIB => "PT_SHLIB",
+            ProgramType::PT_PHDR => "PT_PHDR",
+            ProgramType::PT_TLS => "PT_TLS",
+        }
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct ProgramFlags(pub u32);
+
+impl From<u32> for ProgramFlags {
+    fn from(value: u32) -> Self {
+        Self(value & 7)
+    }
+}
+
+impl std::fmt::Display for ProgramFlags {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let x = match self.0 & PF_X as u32 == PF_X as u32 {
+            true => "X",
+            false => "*",
+        };
+
+        let r = match self.0 & PF_R as u32 == PF_R as u32 {
+            true => "R",
+            false => "*",
+        };
+
+        let w = match self.0 & PF_W as u32 == PF_W as u32 {
+            true => "W",
+            false => "*",
+        };
+        f.write_str(&format!("{}/{}/{}", r, w, x))
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct ProgramHeader {
+    pub p_type: ProgramType, // An offset to a string in the .shstrtab section that represents the name of this section.
+    pub p_flags: ProgramFlags, // Identifies the type of this header.
+    pub p_offset: u64,       // Offset of the segment in the file image.
+    pub p_vaddr: u64,        // Virtual address of the segment in memory.
+    pub p_paddr: u64, // On systems where physical address is relevant, reserved for segment's physical address.
+    pub p_filesz: u64, // Size in bytes of the section in the file image. May be 0.
+    pub p_memsz: u64, // Size in bytes of the segment in memory. May be 0.
+    pub p_align: u64, // 0 and 1 specify no alignment. Otherwise should be a positive, integral power of 2, with p_vaddr equating p_offset modulus p_align.
+}
+
+impl ProgramHeader {
+    pub fn from_bytes(b: &[u8]) -> Result<Self, EZBpfError> {
+        let mut c = Cursor::new(b);
+        c.read_program_header()
+    }
+
+    pub fn to_bytes(&self) -> Vec<u8> {
+        let mut b = (self.p_type.clone() as u32).to_le_bytes().to_vec();
+        b.extend_from_slice(&self.p_flags.0.to_le_bytes());
+        b.extend_from_slice(&self.p_offset.to_le_bytes());
+        b.extend_from_slice(&self.p_vaddr.to_le_bytes());
+        b.extend_from_slice(&self.p_paddr.to_le_bytes());
+        b.extend_from_slice(&self.p_filesz.to_le_bytes());
+        b.extend_from_slice(&self.p_memsz.to_le_bytes());
+        b.extend_from_slice(&self.p_align.to_le_bytes());
+        b
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use hex_literal::hex;
+
+    use crate::program_header::ProgramHeader;
+
+    #[test]
+    fn serialize_e2e() {
+        let b = hex!(
+            "0100000005000000780000000000000078000000000000007800000000000000080000000000000008000000000000000010000000000000"
+        );
+        let h = ProgramHeader::from_bytes(&b).unwrap();
+        assert_eq!(h.to_bytes(), &b)
+    }
+}

+ 162 - 0
crates/disassembler/src/section_header.rs

@@ -0,0 +1,162 @@
+use std::{fmt::Debug, fmt::Display, io::Cursor};
+
+use serde::{Deserialize, Serialize};
+
+use crate::{cursor::ELFCursor, errors::EZBpfError, instructions::Ix};
+
+#[allow(non_camel_case_types)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[repr(u32)]
+pub enum SectionHeaderType {
+    SHT_NULL = 0x00,          // Section header table entry unused
+    SHT_PROGBITS = 0x01,      // Program data
+    SHT_SYMTAB = 0x02,        // Symbol table
+    SHT_STRTAB = 0x03,        // String table
+    SHT_RELA = 0x04,          // Relocation entries with addends
+    SHT_HASH = 0x05,          // Symbol hash table
+    SHT_DYNAMIC = 0x06,       // Dynamic linking information
+    SHT_NOTE = 0x07,          // Notes
+    SHT_NOBITS = 0x08,        // Program space with no data (bss)
+    SHT_REL = 0x09,           // Relocation entries, no addends
+    SHT_SHLIB = 0x0A,         // Reserved
+    SHT_DYNSYM = 0x0B,        // Dynamic linker symbol table
+    SHT_INIT_ARRAY = 0x0E,    // Array of constructors
+    SHT_FINI_ARRAY = 0x0F,    // Array of destructors
+    SHT_PREINIT_ARRAY = 0x10, // Array of pre-constructors
+    SHT_GROUP = 0x11,         // Section group
+    SHT_SYMTAB_SHNDX = 0x12,  //	Extended section indices
+    SHT_NUM = 0x13,           // Number of defined types.
+}
+
+impl TryFrom<u32> for SectionHeaderType {
+    type Error = EZBpfError;
+
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        Ok(match value {
+            0x00 => Self::SHT_NULL,
+            0x01 => Self::SHT_PROGBITS,
+            0x02 => Self::SHT_SYMTAB,
+            0x03 => Self::SHT_STRTAB,
+            0x04 => Self::SHT_RELA,
+            0x05 => Self::SHT_HASH,
+            0x06 => Self::SHT_DYNAMIC,
+            0x07 => Self::SHT_NOTE,
+            0x08 => Self::SHT_NOBITS,
+            0x09 => Self::SHT_REL,
+            0x0A => Self::SHT_SHLIB,
+            0x0B => Self::SHT_DYNSYM,
+            0x0E => Self::SHT_INIT_ARRAY,
+            0x0F => Self::SHT_FINI_ARRAY,
+            0x10 => Self::SHT_PREINIT_ARRAY,
+            0x11 => Self::SHT_GROUP,
+            0x12 => Self::SHT_SYMTAB_SHNDX,
+            0x13 => Self::SHT_NUM,
+            _ => return Err(EZBpfError::InvalidSectionHeaderType),
+        })
+    }
+}
+
+impl Display for SectionHeaderType {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(Into::<&str>::into(self.clone()))
+    }
+}
+
+impl From<SectionHeaderType> for &str {
+    fn from(val: SectionHeaderType) -> Self {
+        match val {
+            SectionHeaderType::SHT_NULL => "SHT_NULL",
+            SectionHeaderType::SHT_PROGBITS => "SHT_PROGBITS",
+            SectionHeaderType::SHT_SYMTAB => "SHT_SYMTAB",
+            SectionHeaderType::SHT_STRTAB => "SHT_STRTAB",
+            SectionHeaderType::SHT_RELA => "SHT_RELA",
+            SectionHeaderType::SHT_HASH => "SHT_HASH",
+            SectionHeaderType::SHT_DYNAMIC => "SHT_DYNAMIC",
+            SectionHeaderType::SHT_NOTE => "SHT_NOTE",
+            SectionHeaderType::SHT_NOBITS => "SHT_NOBITS",
+            SectionHeaderType::SHT_REL => "SHT_REL",
+            SectionHeaderType::SHT_SHLIB => "SHT_SHLIB",
+            SectionHeaderType::SHT_DYNSYM => "SHT_DYNSYM",
+            SectionHeaderType::SHT_INIT_ARRAY => "SHT_INIT_ARRAY",
+            SectionHeaderType::SHT_FINI_ARRAY => "SHT_FINI_ARRAY",
+            SectionHeaderType::SHT_PREINIT_ARRAY => "SHT_PREINIT_ARRAY",
+            SectionHeaderType::SHT_GROUP => "SHT_GROUP",
+            SectionHeaderType::SHT_SYMTAB_SHNDX => "SHT_SYMTAB_SHNDX",
+            SectionHeaderType::SHT_NUM => "SHT_NUM",
+        }
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct SectionHeader {
+    pub sh_name: u32, // An offset to a string in the .shstrtab section that represents the name of this section.
+    pub sh_type: SectionHeaderType, // Identifies the type of this header.
+    pub sh_flags: u64, // Identifies the attributes of the section.
+    pub sh_addr: u64, // Virtual address of the section in memory, for sections that are loaded.
+    pub sh_offset: u64, // Offset of the section in the file image.
+    pub sh_size: u64, // Size in bytes of the section in the file image. May be 0.
+    pub sh_link: u32, // Contains the section index of an associated section. This field is used for several purposes, depending on the type of section.
+    pub sh_info: u32, // Contains extra information about the section. This field is used for several purposes, depending on the type of section.
+    pub sh_addralign: u64, // Contains the required alignment of the section. This field must be a power of two.
+    pub sh_entsize: u64, // Contains the size, in bytes, of each entry, for sections that contain fixed-size entries. Otherwise, this field contains zero.
+}
+
+impl SectionHeader {
+    pub fn from_bytes(b: &[u8]) -> Result<Self, EZBpfError> {
+        let mut c = Cursor::new(b);
+        c.read_section_header()
+    }
+
+    pub fn to_bytes(&self) -> Vec<u8> {
+        let mut b = self.sh_name.to_le_bytes().to_vec();
+        b.extend_from_slice(&(self.sh_type.clone() as u32).to_le_bytes());
+        b.extend_from_slice(&self.sh_flags.to_le_bytes());
+        b.extend_from_slice(&self.sh_addr.to_le_bytes());
+        b.extend_from_slice(&self.sh_offset.to_le_bytes());
+        b.extend_from_slice(&self.sh_size.to_le_bytes());
+        b.extend_from_slice(&self.sh_link.to_le_bytes());
+        b.extend_from_slice(&self.sh_info.to_le_bytes());
+        b.extend_from_slice(&self.sh_addralign.to_le_bytes());
+        b.extend_from_slice(&self.sh_entsize.to_le_bytes());
+        b
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct SectionHeaderEntry {
+    pub label: String,
+    pub offset: usize,
+    pub data: Vec<u8>,
+}
+
+impl SectionHeaderEntry {
+    pub fn to_ixs(&self) -> Result<Vec<Ix>, EZBpfError> {
+        if self.data.len() % 8 != 0 {
+            return Err(EZBpfError::InvalidDataLength);
+        }
+        let mut ixs: Vec<Ix> = vec![];
+        if self.data.len() >= 8 {
+            let mut c = Cursor::new(self.data.as_slice());
+            while let Ok(ix) = c.read_ix() {
+                ixs.push(ix)
+            }
+        }
+        Ok(ixs)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use hex_literal::hex;
+
+    use crate::section_header::SectionHeader;
+
+    #[test]
+    fn serialize_e2e() {
+        let b = hex!(
+            "07000000030000000000000000000000000000000000000080000000000000000A00000000000000000000000000000001000000000000000000000000000000"
+        );
+        let h = SectionHeader::from_bytes(&b).unwrap();
+        assert_eq!(h.to_bytes(), &b)
+    }
+}

+ 101 - 0
crates/disassembler/src/section_header_entry.rs

@@ -0,0 +1,101 @@
+use std::{fmt::Debug, io::Cursor};
+
+use serde::{Deserialize, Serialize};
+
+use crate::{cursor::ELFCursor, errors::EZBpfError, instructions::Ix};
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct SectionHeaderEntry {
+    pub label: String,
+    pub offset: usize,
+    pub data: Vec<u8>,
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub ixs: Vec<Ix>,
+    #[serde(skip_serializing_if = "String::is_empty")]
+    pub utf8: String,
+}
+
+impl SectionHeaderEntry {
+    pub fn new(label: String, offset: usize, data: Vec<u8>) -> Result<Self, EZBpfError> {
+        let mut h = SectionHeaderEntry {
+            label,
+            offset,
+            data,
+            ixs: vec![],
+            utf8: String::new(),
+        };
+
+        if &h.label == ".text\0" {
+            h.ixs = h.to_ixs()?;
+        }
+
+        if let Ok(utf8) = String::from_utf8(h.data.clone()) {
+            h.utf8 = utf8;
+        }
+        Ok(h)
+    }
+
+    pub fn offset(&self) -> usize {
+        self.offset
+    }
+
+    pub fn to_ixs(&self) -> Result<Vec<Ix>, EZBpfError> {
+        if self.data.len() % 8 != 0 {
+            return Err(EZBpfError::InvalidDataLength);
+        }
+        let mut ixs: Vec<Ix> = vec![];
+        if self.data.len() >= 8 {
+            let mut c = Cursor::new(self.data.as_slice());
+            while let Ok(ix) = c.read_ix() {
+                ixs.push(ix);
+            }
+        }
+        Ok(ixs)
+    }
+
+    pub fn to_bytes(&self) -> Vec<u8> {
+        self.data.clone()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::{instructions::Ix, opcodes::OpCode, section_header_entry::SectionHeaderEntry};
+
+    #[test]
+    fn serialize_e2e() {
+        let data = vec![
+            0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        ];
+
+        let h = SectionHeaderEntry::new(".text\0".to_string(), 128, data.clone()).unwrap();
+
+        let ixs = vec![
+            Ix {
+                op: OpCode::Lddw,
+                dst: 1,
+                src: 0,
+                off: 0,
+                imm: 0,
+            },
+            Ix {
+                op: OpCode::Exit,
+                dst: 0,
+                src: 0,
+                off: 0,
+                imm: 0,
+            },
+        ];
+        assert_eq!(ixs, h.to_ixs().unwrap());
+
+        assert_eq!(
+            data,
+            h.to_ixs()
+                .expect("Invalid IX")
+                .into_iter()
+                .flat_map(|i| i.to_bytes())
+                .collect::<Vec<u8>>()
+        )
+    }
+}

+ 25 - 0
crates/disassembler/src/wasm.rs

@@ -0,0 +1,25 @@
+use crate::program::Program as EBPFProgram;
+use serde::{Deserialize, Serialize};
+use serde_wasm_bindgen::to_value;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Program {
+    inner: EBPFProgram,
+}
+
+#[wasm_bindgen]
+impl Program {
+    #[wasm_bindgen]
+    pub fn from_bytes(b: &[u8]) -> Result<Program, JsValue> {
+        EBPFProgram::from_bytes(b)
+            .map(|program| Program { inner: program })
+            .map_err(|e| JsValue::from_str(&format!("{:?}", e)))
+    }
+
+    #[wasm_bindgen]
+    pub fn to_json(&self) -> Result<JsValue, JsValue> {
+        to_value(&self.inner).map_err(|e| JsValue::from_str(&e.to_string()))
+    }
+}

+ 34 - 0
src/commands/disassemble.rs

@@ -0,0 +1,34 @@
+use std::fs::File;
+use std::io::Read;
+
+use anyhow::{Error, Result};
+use sbpf_disassembler::program::Program;
+
+pub fn disassemble(filename: String, asm: bool) -> Result<(), Error> {
+    let mut file = File::open(filename)?;
+    let mut b = vec![];
+    file.read_to_end(&mut b)?;
+    let program = Program::from_bytes(b.as_ref())?;
+
+    if asm {
+        println!(
+            "{}",
+            program
+                .section_header_entries
+                .iter()
+                .map(|h| h.ixs.clone())
+                .filter(|ixs| !ixs.is_empty())
+                .map(|ixs| ixs
+                    .iter()
+                    .map(|i| i.to_asm().unwrap())
+                    .collect::<Vec<String>>()
+                    .join("\n"))
+                .collect::<Vec<String>>()
+                .join("\n")
+        );
+    } else {
+        println!("{}", serde_json::to_string_pretty(&program)?);
+    }
+
+    Ok(())
+}

+ 3 - 0
src/commands/mod.rs

@@ -13,4 +13,7 @@ pub use test::*;
 pub mod clean;
 pub use clean::*;
 
+pub mod disassemble;
+pub use disassemble::*;
+
 pub mod common;

+ 11 - 1
src/main.rs

@@ -1,7 +1,7 @@
 pub mod commands;
 use anyhow::Error;
 use clap::{Args, Parser, Subcommand};
-use commands::{build, clean, deploy, init, test};
+use commands::{build, clean, deploy, disassemble, init, test};
 
 #[derive(Parser)]
 #[command(version, about, long_about = None)]
@@ -25,6 +25,8 @@ enum Commands {
     E2E(DeployArgs),
     #[command(about = "Clean up build and deploy artifacts")]
     Clean,
+    #[command(about = "Disassemble a Solana program executable")]
+    Disassemble(DisassembleArgs),
 }
 
 #[derive(Args)]
@@ -44,6 +46,13 @@ struct DeployArgs {
     url: Option<String>,
 }
 
+#[derive(Args)]
+struct DisassembleArgs {
+    filename: String,
+    #[arg(short, long)]
+    asm: bool,
+}
+
 fn main() -> Result<(), Error> {
     let cli = Cli::parse();
 
@@ -59,5 +68,6 @@ fn main() -> Result<(), Error> {
             test()
         }
         Commands::Clean => clean(),
+        Commands::Disassemble(args) => disassemble(args.filename.clone(), args.asm),
     }
 }