Skip to content

Commit 91ca36d

Browse files
0xrinegadeclaude
andcommitted
feat(ovsm): Add sBPF compiler and decompiler
Implement bidirectional compilation between OVSM LISP and Solana BPF: Compiler (OVSM → sBPF): - Type checker with inference - Three-address code IR generation - Optimization passes (constant folding, dead code elimination) - sBPF codegen with Murmur3 syscall hashing - ELF writer for .so output - Verifier (instruction limits, jump bounds, div-by-zero) - Runtime support (stack frames, heap allocation, string/array ops) Decompiler (sBPF → OVSM): - Disassembler with full opcode support - Control flow graph recovery - Anchor IDL integration for semantic naming - OVSM LISP code emitter 101 tests passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 1ff7c3c commit 91ca36d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+9015
-294
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ imageproc = "0.25"
102102
ab_glyph = "0.2"
103103
# Terminal emulation for capturing alternate screen buffer
104104
vt100 = "0.15"
105+
tui-nodes = "0.9.0"
105106

106107
[target.'cfg(unix)'.dependencies]
107108
libc = "0.2"

crates/ovsm/src/compiler/elf.rs

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
//! # ELF Writer for sBPF Programs
2+
//!
3+
//! Packages sBPF bytecode into an ELF shared object (.so) file
4+
//! that can be deployed to Solana.
5+
6+
use super::sbpf_codegen::SbpfInstruction;
7+
use crate::{Result, Error};
8+
9+
/// ELF magic number
10+
const ELF_MAGIC: [u8; 4] = [0x7f, b'E', b'L', b'F'];
11+
12+
/// ELF class: 64-bit
13+
const ELFCLASS64: u8 = 2;
14+
15+
/// ELF data encoding: little-endian
16+
const ELFDATA2LSB: u8 = 1;
17+
18+
/// ELF version
19+
const EV_CURRENT: u8 = 1;
20+
21+
/// ELF OS/ABI: None (ELFOSABI_NONE)
22+
const ELFOSABI_NONE: u8 = 0;
23+
24+
/// ELF type: Shared object (ET_DYN)
25+
const ET_DYN: u16 = 3;
26+
27+
/// ELF machine: eBPF
28+
const EM_BPF: u16 = 247;
29+
30+
/// Section header types
31+
const SHT_NULL: u32 = 0;
32+
const SHT_PROGBITS: u32 = 1;
33+
const SHT_STRTAB: u32 = 3;
34+
35+
/// Section flags
36+
const SHF_ALLOC: u64 = 0x2;
37+
const SHF_EXECINSTR: u64 = 0x4;
38+
39+
/// ELF writer for sBPF programs
40+
pub struct ElfWriter {
41+
/// String table
42+
strtab: Vec<u8>,
43+
}
44+
45+
impl ElfWriter {
46+
pub fn new() -> Self {
47+
Self {
48+
strtab: vec![0], // Start with null byte
49+
}
50+
}
51+
52+
/// Write sBPF program to ELF format
53+
pub fn write(&mut self, program: &[SbpfInstruction], debug_info: bool) -> Result<Vec<u8>> {
54+
let _ = debug_info; // TODO: Add debug info support
55+
56+
// Encode all instructions
57+
let mut text_section: Vec<u8> = Vec::new();
58+
for instr in program {
59+
text_section.extend_from_slice(&instr.encode());
60+
}
61+
62+
if text_section.is_empty() {
63+
return Err(Error::runtime("Cannot create ELF with empty program"));
64+
}
65+
66+
// Build string table
67+
let shstrtab_name_idx = self.add_string(".shstrtab");
68+
let text_name_idx = self.add_string(".text");
69+
70+
// Calculate offsets
71+
let ehdr_size = 64; // ELF64 header size
72+
let shdr_size = 64; // Section header size
73+
let num_sections = 3; // NULL, .text, .shstrtab
74+
75+
let text_offset = ehdr_size;
76+
let text_size = text_section.len();
77+
78+
let shstrtab_offset = text_offset + text_size;
79+
let shstrtab_size = self.strtab.len();
80+
81+
let shdr_offset = shstrtab_offset + shstrtab_size;
82+
// Align to 8 bytes
83+
let shdr_offset_aligned = (shdr_offset + 7) & !7;
84+
85+
// Build ELF
86+
let mut elf = Vec::new();
87+
88+
// ==================== ELF Header ====================
89+
// e_ident (16 bytes)
90+
elf.extend_from_slice(&ELF_MAGIC); // Magic
91+
elf.push(ELFCLASS64); // 64-bit
92+
elf.push(ELFDATA2LSB); // Little-endian
93+
elf.push(EV_CURRENT); // Version
94+
elf.push(ELFOSABI_NONE); // OS/ABI
95+
elf.extend_from_slice(&[0u8; 8]); // Padding
96+
97+
// e_type (2 bytes) - ET_DYN
98+
elf.extend_from_slice(&ET_DYN.to_le_bytes());
99+
100+
// e_machine (2 bytes) - EM_BPF
101+
elf.extend_from_slice(&EM_BPF.to_le_bytes());
102+
103+
// e_version (4 bytes)
104+
elf.extend_from_slice(&1u32.to_le_bytes());
105+
106+
// e_entry (8 bytes) - entry point
107+
elf.extend_from_slice(&(text_offset as u64).to_le_bytes());
108+
109+
// e_phoff (8 bytes) - program header offset (none)
110+
elf.extend_from_slice(&0u64.to_le_bytes());
111+
112+
// e_shoff (8 bytes) - section header offset
113+
elf.extend_from_slice(&(shdr_offset_aligned as u64).to_le_bytes());
114+
115+
// e_flags (4 bytes)
116+
elf.extend_from_slice(&0u32.to_le_bytes());
117+
118+
// e_ehsize (2 bytes)
119+
elf.extend_from_slice(&(ehdr_size as u16).to_le_bytes());
120+
121+
// e_phentsize (2 bytes)
122+
elf.extend_from_slice(&0u16.to_le_bytes());
123+
124+
// e_phnum (2 bytes)
125+
elf.extend_from_slice(&0u16.to_le_bytes());
126+
127+
// e_shentsize (2 bytes)
128+
elf.extend_from_slice(&(shdr_size as u16).to_le_bytes());
129+
130+
// e_shnum (2 bytes)
131+
elf.extend_from_slice(&(num_sections as u16).to_le_bytes());
132+
133+
// e_shstrndx (2 bytes) - index of .shstrtab
134+
elf.extend_from_slice(&2u16.to_le_bytes());
135+
136+
assert_eq!(elf.len(), ehdr_size);
137+
138+
// ==================== .text Section ====================
139+
elf.extend_from_slice(&text_section);
140+
141+
// ==================== .shstrtab Section ====================
142+
elf.extend_from_slice(&self.strtab);
143+
144+
// ==================== Padding ====================
145+
while elf.len() < shdr_offset_aligned {
146+
elf.push(0);
147+
}
148+
149+
// ==================== Section Headers ====================
150+
151+
// Section 0: NULL
152+
elf.extend_from_slice(&[0u8; 64]);
153+
154+
// Section 1: .text
155+
// sh_name (4 bytes)
156+
elf.extend_from_slice(&(text_name_idx as u32).to_le_bytes());
157+
// sh_type (4 bytes)
158+
elf.extend_from_slice(&SHT_PROGBITS.to_le_bytes());
159+
// sh_flags (8 bytes)
160+
elf.extend_from_slice(&(SHF_ALLOC | SHF_EXECINSTR).to_le_bytes());
161+
// sh_addr (8 bytes)
162+
elf.extend_from_slice(&0u64.to_le_bytes());
163+
// sh_offset (8 bytes)
164+
elf.extend_from_slice(&(text_offset as u64).to_le_bytes());
165+
// sh_size (8 bytes)
166+
elf.extend_from_slice(&(text_size as u64).to_le_bytes());
167+
// sh_link (4 bytes)
168+
elf.extend_from_slice(&0u32.to_le_bytes());
169+
// sh_info (4 bytes)
170+
elf.extend_from_slice(&0u32.to_le_bytes());
171+
// sh_addralign (8 bytes)
172+
elf.extend_from_slice(&8u64.to_le_bytes());
173+
// sh_entsize (8 bytes)
174+
elf.extend_from_slice(&0u64.to_le_bytes());
175+
176+
// Section 2: .shstrtab
177+
// sh_name (4 bytes)
178+
elf.extend_from_slice(&(shstrtab_name_idx as u32).to_le_bytes());
179+
// sh_type (4 bytes)
180+
elf.extend_from_slice(&SHT_STRTAB.to_le_bytes());
181+
// sh_flags (8 bytes)
182+
elf.extend_from_slice(&0u64.to_le_bytes());
183+
// sh_addr (8 bytes)
184+
elf.extend_from_slice(&0u64.to_le_bytes());
185+
// sh_offset (8 bytes)
186+
elf.extend_from_slice(&(shstrtab_offset as u64).to_le_bytes());
187+
// sh_size (8 bytes)
188+
elf.extend_from_slice(&(shstrtab_size as u64).to_le_bytes());
189+
// sh_link (4 bytes)
190+
elf.extend_from_slice(&0u32.to_le_bytes());
191+
// sh_info (4 bytes)
192+
elf.extend_from_slice(&0u32.to_le_bytes());
193+
// sh_addralign (8 bytes)
194+
elf.extend_from_slice(&1u64.to_le_bytes());
195+
// sh_entsize (8 bytes)
196+
elf.extend_from_slice(&0u64.to_le_bytes());
197+
198+
Ok(elf)
199+
}
200+
201+
/// Add a string to the string table, return its index
202+
fn add_string(&mut self, s: &str) -> usize {
203+
let idx = self.strtab.len();
204+
self.strtab.extend_from_slice(s.as_bytes());
205+
self.strtab.push(0); // Null terminator
206+
idx
207+
}
208+
}
209+
210+
impl Default for ElfWriter {
211+
fn default() -> Self {
212+
Self::new()
213+
}
214+
}
215+
216+
/// Validate an ELF file is a valid sBPF program
217+
pub fn validate_sbpf_elf(data: &[u8]) -> Result<()> {
218+
if data.len() < 64 {
219+
return Err(Error::runtime("ELF file too small"));
220+
}
221+
222+
// Check magic
223+
if data[0..4] != ELF_MAGIC {
224+
return Err(Error::runtime("Invalid ELF magic number"));
225+
}
226+
227+
// Check class (64-bit)
228+
if data[4] != ELFCLASS64 {
229+
return Err(Error::runtime("ELF must be 64-bit"));
230+
}
231+
232+
// Check endianness
233+
if data[5] != ELFDATA2LSB {
234+
return Err(Error::runtime("ELF must be little-endian"));
235+
}
236+
237+
// Check machine type
238+
let machine = u16::from_le_bytes([data[18], data[19]]);
239+
if machine != EM_BPF {
240+
return Err(Error::runtime(format!(
241+
"ELF machine type must be BPF ({}), got {}",
242+
EM_BPF, machine
243+
)));
244+
}
245+
246+
Ok(())
247+
}
248+
249+
#[cfg(test)]
250+
mod tests {
251+
use super::*;
252+
253+
#[test]
254+
fn test_elf_writer() {
255+
let mut writer = ElfWriter::new();
256+
257+
let program = vec![
258+
SbpfInstruction::alu64_imm(0xb0, 0, 42), // mov64 r0, 42
259+
SbpfInstruction::exit(),
260+
];
261+
262+
let elf = writer.write(&program, false).unwrap();
263+
264+
// Validate the ELF
265+
assert_eq!(&elf[0..4], &ELF_MAGIC);
266+
assert!(validate_sbpf_elf(&elf).is_ok());
267+
}
268+
269+
#[test]
270+
fn test_empty_program_error() {
271+
let mut writer = ElfWriter::new();
272+
let result = writer.write(&[], false);
273+
assert!(result.is_err());
274+
}
275+
}

0 commit comments

Comments
 (0)