Skip to content

Commit cb26672

Browse files
0xrinegadeclaude
andcommitted
feat: Enhance OVSM compiler ELF generation and stream filtering
**OVSM Compiler Improvements:** - Fix ELF virtual addresses (0x1000 → 0x120) to match Solana's working binaries - Adjust .text and .rodata section alignment for SBPFv1 compatibility - Remove redundant DT_TEXTREL from dynamic section (use DT_FLAGS instead) - Add comprehensive debugging examples for ELF parsing and instruction decode **Stream Service Enhancements:** - Add program alias resolution (pumpfun, raydium, jupiter, etc.) - Add token symbol resolution (USDC, BONK, SOL, etc.) - Add --list-programs and --list-tokens flags for discovery - Support liquidity pool filtering (--pools) - Enhanced event filtering with human-readable aliases **New Command Infrastructure:** - Add invoke command scaffolding for direct program invocation - Add program_aliases utility module for ergonomic program/token references **TUI & Research Agent:** - Refine real-time network stats streaming - Improve transaction feed rendering - Optimize research agent token usage This makes blockchain investigation more accessible with aliases instead of requiring full 44-character base58 addresses. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent ddd7070 commit cb26672

28 files changed

+3289
-262
lines changed

Cargo.lock

Lines changed: 1 addition & 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
@@ -32,6 +32,7 @@ solana-sdk = "3.0.0"
3232
tokio = { version = "1.47.1", features = ["rt-multi-thread", "macros", "sync", "time", "net", "io-util", "fs", "process", "signal"] }
3333
tokio-stream = { version = "0.1", features = ["sync"] }
3434
tokio-vsock = "0.4"
35+
futures-util = "0.3"
3536
axum = { version = "0.7", features = ["ws"] }
3637
tower-http = { version = "0.6", features = ["cors"] }
3738
vsock = "0.3"
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Compare how RBPF parses our ELF vs Solana's working ELF
2+
use solana_rbpf::{
3+
elf::Executable,
4+
program::BuiltinProgram,
5+
vm::TestContextObject,
6+
};
7+
use std::sync::Arc;
8+
9+
fn parse_and_report(name: &str, path: &str) {
10+
println!("\n{}", "=".repeat(60));
11+
println!("Parsing {}: {}", name, path);
12+
println!("{}\n", "=".repeat(60));
13+
14+
let elf_bytes = std::fs::read(path).expect("Failed to read ELF");
15+
println!("📦 File size: {} bytes", elf_bytes.len());
16+
17+
// Check header
18+
println!("\n📋 ELF Header:");
19+
let e_machine = u16::from_le_bytes(elf_bytes[18..20].try_into().unwrap());
20+
let e_flags = u32::from_le_bytes(elf_bytes[48..52].try_into().unwrap());
21+
let e_shnum = u16::from_le_bytes(elf_bytes[60..62].try_into().unwrap());
22+
let e_shstrndx = u16::from_le_bytes(elf_bytes[62..64].try_into().unwrap());
23+
24+
println!(" Machine: 0x{:x}", e_machine);
25+
println!(" Flags: 0x{:x}", e_flags);
26+
println!(" Section count: {}", e_shnum);
27+
println!(" shstrtab index: {}", e_shstrndx);
28+
29+
// Try to parse with RBPF
30+
println!("\n🔍 RBPF Parsing:");
31+
let loader = Arc::new(BuiltinProgram::new_mock());
32+
33+
match Executable::<TestContextObject>::load(&elf_bytes, loader.clone()) {
34+
Ok(executable) => {
35+
println!(" ✅ Parsed successfully!");
36+
37+
// Get some info about the executable
38+
println!("\n📊 Executable Info:");
39+
println!(" SBPF Version: {:?}", executable.get_sbpf_version());
40+
let (_text_offset, text_bytes) = executable.get_text_bytes();
41+
println!(" Text section: 0x{:x} bytes", text_bytes.len());
42+
println!(" RO section: {} bytes", executable.get_ro_section().len());
43+
44+
// Check function registry
45+
let function_registry = executable.get_function_registry();
46+
println!("\n📞 Function registry:");
47+
for (hash, value) in function_registry.iter().take(5) {
48+
println!(" Hash: 0x{:08x}, Value: (pc bytes, flags)", hash);
49+
}
50+
}
51+
Err(e) => {
52+
println!(" ❌ Failed to parse!");
53+
println!(" Error: {:?}", e);
54+
55+
// Try to get more details about the error
56+
let error_str = format!("{:?}", e);
57+
58+
if error_str.contains("StringTooLong") {
59+
println!("\n🔍 String length issue detected!");
60+
61+
// Check shstrtab
62+
let e_shoff = u64::from_le_bytes(elf_bytes[40..48].try_into().unwrap());
63+
let shstrtab_offset = e_shoff + (e_shstrndx as u64 * 64);
64+
65+
if shstrtab_offset + 64 <= elf_bytes.len() as u64 {
66+
let sh_name = u32::from_le_bytes(
67+
elf_bytes[shstrtab_offset as usize..shstrtab_offset as usize + 4]
68+
.try_into().unwrap()
69+
);
70+
let sh_offset = u64::from_le_bytes(
71+
elf_bytes[shstrtab_offset as usize + 24..shstrtab_offset as usize + 32]
72+
.try_into().unwrap()
73+
);
74+
let sh_size = u64::from_le_bytes(
75+
elf_bytes[shstrtab_offset as usize + 32..shstrtab_offset as usize + 40]
76+
.try_into().unwrap()
77+
);
78+
79+
println!(" shstrtab section header:");
80+
println!(" sh_name: 0x{:x}", sh_name);
81+
println!(" sh_offset: 0x{:x}", sh_offset);
82+
println!(" sh_size: 0x{:x}", sh_size);
83+
84+
// Check if we can read the name
85+
let name_start = sh_offset as usize + sh_name as usize;
86+
if name_start < elf_bytes.len() {
87+
let available = elf_bytes.len() - name_start;
88+
println!(" Bytes available from name start: {}", available);
89+
90+
// Try to find the null terminator
91+
let mut null_found = false;
92+
for i in 0..available.min(20) {
93+
if elf_bytes[name_start + i] == 0 {
94+
println!(" Null terminator found at offset {}", i);
95+
null_found = true;
96+
break;
97+
}
98+
}
99+
if !null_found {
100+
println!(" ⚠️ No null terminator found in first 20 bytes!");
101+
}
102+
}
103+
}
104+
}
105+
}
106+
}
107+
}
108+
109+
fn main() {
110+
// Parse Solana's working ELF
111+
parse_and_report(
112+
"Solana's syscall_reloc_64_32.so",
113+
"/home/larp/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/solana_rbpf-0.8.5/tests/elfs/syscall_reloc_64_32.so"
114+
);
115+
116+
// Parse our original ELF that fails
117+
parse_and_report(
118+
"Our minimal_sbpf.so (V2 attempt)",
119+
"/tmp/minimal_sbpf.so"
120+
);
121+
122+
// Parse our new working V1 ELF
123+
parse_and_report(
124+
"Our solana_v1.so (WORKING!)",
125+
"/tmp/solana_v1.so"
126+
);
127+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Debug RBPF instruction decoding to find the exact issue
2+
use std::fs;
3+
4+
fn main() {
5+
let elf_bytes = fs::read("/tmp/minimal_syscall.so").expect("Failed to read ELF");
6+
7+
println!("🔍 Debugging RBPF instruction decode at .text (0x120)\n");
8+
9+
// The .text section starts at 0x120 according to our ELF
10+
let text_start = 0x120;
11+
let text_size = 48;
12+
13+
println!("📝 Instructions in .text:");
14+
15+
let mut pc = 0;
16+
while pc < text_size {
17+
let offset = text_start + pc;
18+
19+
if offset >= elf_bytes.len() {
20+
println!(" ❌ Offset 0x{:x} out of bounds!", offset);
21+
break;
22+
}
23+
24+
let opcode = elf_bytes[offset];
25+
let regs = if offset + 1 < elf_bytes.len() { elf_bytes[offset + 1] } else { 0 };
26+
let dst = regs & 0xf;
27+
let src = (regs >> 4) & 0xf;
28+
29+
// Read immediate values based on instruction class
30+
let imm = if offset + 4 < elf_bytes.len() {
31+
i32::from_le_bytes([
32+
elf_bytes[offset + 4],
33+
elf_bytes[offset + 5],
34+
elf_bytes[offset + 6],
35+
elf_bytes[offset + 7],
36+
])
37+
} else {
38+
0
39+
};
40+
41+
// Decode instruction
42+
print!(" [{:2}] 0x{:04x}: 0x{:02x} ", pc / 8, offset, opcode);
43+
44+
match opcode {
45+
0x18 => {
46+
// LDDW - 16 byte instruction
47+
let imm64 = if offset + 12 < elf_bytes.len() {
48+
let low = imm as u64;
49+
let high = i32::from_le_bytes([
50+
elf_bytes[offset + 12],
51+
elf_bytes[offset + 13],
52+
elf_bytes[offset + 14],
53+
elf_bytes[offset + 15],
54+
]) as u64;
55+
(high << 32) | (low as u32 as u64)
56+
} else {
57+
imm as u64
58+
};
59+
println!("LDDW r{}, 0x{:x}", dst, imm64);
60+
pc += 16; // LDDW is 16 bytes
61+
}
62+
0x85 => {
63+
println!("CALL {}", imm);
64+
// Check if this is a relative jump
65+
if imm != 0 {
66+
let target = (pc as i32 + imm * 8) as usize;
67+
println!(" → Jump target would be: 0x{:x} (relative offset {})", target, imm);
68+
if target >= text_size {
69+
println!(" ❌ JUMP OUT OF BOUNDS! Text size is only 0x{:x}", text_size);
70+
}
71+
}
72+
pc += 8;
73+
}
74+
0x95 => {
75+
println!("EXIT");
76+
pc += 8;
77+
}
78+
0xb7 => {
79+
println!("MOV r{}, {}", dst, imm);
80+
pc += 8;
81+
}
82+
0x05 => {
83+
let offset = imm as i16;
84+
println!("JA {}", offset);
85+
let target = (pc as i32 + (offset as i32) * 8) as usize;
86+
println!(" → Jump target: 0x{:x}", target);
87+
if target >= text_size {
88+
println!(" ❌ JUMP OUT OF BOUNDS!");
89+
}
90+
pc += 8;
91+
}
92+
_ => {
93+
println!("UNKNOWN (0x{:02x})", opcode);
94+
pc += 8;
95+
}
96+
}
97+
}
98+
99+
// Now analyze the actual bytes at position 5
100+
println!("\n🔍 Instruction at position 5 (byte offset 40 = 0x28 in .text):");
101+
let instr_offset = text_start + 40;
102+
if instr_offset + 8 <= elf_bytes.len() {
103+
let bytes = &elf_bytes[instr_offset..instr_offset + 8];
104+
println!(" Bytes: {:02x?}", bytes);
105+
println!(" Opcode: 0x{:02x}", bytes[0]);
106+
107+
// Check if it's interpreted as a jump
108+
if bytes[0] & 0x07 == 0x05 {
109+
let imm = i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
110+
println!(" ❌ This is being interpreted as a JUMP with offset {}", imm);
111+
}
112+
}
113+
114+
// Also check what RBPF might be seeing differently
115+
println!("\n🎯 Theory: RBPF might be starting from a different offset or seeing corrupted data");
116+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Debug why PT_DYNAMIC parsing might fail
2+
use std::fs;
3+
use std::mem;
4+
5+
fn main() {
6+
let elf_bytes = fs::read("/tmp/hello_final.so").expect("Failed to read ELF");
7+
8+
println!("🔍 Debugging PT_DYNAMIC parsing\n");
9+
10+
// Find PT_DYNAMIC
11+
let e_phoff = u64::from_le_bytes(elf_bytes[32..40].try_into().unwrap());
12+
let e_phnum = u16::from_le_bytes(elf_bytes[56..58].try_into().unwrap());
13+
14+
for i in 0..e_phnum {
15+
let phdr_offset = e_phoff as usize + (i as usize * 56);
16+
let p_type = u32::from_le_bytes(elf_bytes[phdr_offset..phdr_offset+4].try_into().unwrap());
17+
18+
if p_type == 2 { // PT_DYNAMIC
19+
println!("📊 Found PT_DYNAMIC at program header {}", i);
20+
21+
let p_offset = u64::from_le_bytes(elf_bytes[phdr_offset+8..phdr_offset+16].try_into().unwrap());
22+
let p_filesz = u64::from_le_bytes(elf_bytes[phdr_offset+32..phdr_offset+40].try_into().unwrap());
23+
24+
println!(" p_offset: 0x{:x} ({})", p_offset, p_offset);
25+
println!(" p_filesz: 0x{:x} ({})", p_filesz, p_filesz);
26+
27+
// Check what slice_from_program_header would do
28+
println!("\n🔍 slice_from_program_header checks:");
29+
30+
let start = p_offset as usize;
31+
let end = start + p_filesz as usize;
32+
33+
println!(" Range: 0x{:x}..0x{:x}", start, end);
34+
println!(" File size: 0x{:x} ({})", elf_bytes.len(), elf_bytes.len());
35+
36+
// Check 1: Range within bounds?
37+
if end > elf_bytes.len() {
38+
println!(" ❌ FAIL: Range exceeds file size!");
39+
return;
40+
}
41+
println!(" ✅ Range is within bounds");
42+
43+
// Check 2: Size divisible by sizeof(Elf64Dyn) = 16?
44+
let entry_size = 16;
45+
if p_filesz % entry_size != 0 {
46+
println!(" ❌ FAIL: Size not divisible by {}", entry_size);
47+
return;
48+
}
49+
println!(" ✅ Size is divisible by {} (entries = {})", entry_size, p_filesz / entry_size);
50+
51+
// Check 3: Alignment
52+
let slice = &elf_bytes[start..end];
53+
let ptr = slice.as_ptr();
54+
let alignment = mem::align_of::<u64>();
55+
56+
if (ptr as usize) % alignment != 0 {
57+
println!(" ❌ FAIL: Pointer not aligned!");
58+
println!(" ptr=0x{:x}, alignment={}", ptr as usize, alignment);
59+
return;
60+
}
61+
println!(" ✅ Pointer is aligned (ptr=0x{:x})", ptr as usize);
62+
63+
// If we get here, slice_from_program_header should succeed
64+
println!("\n✅ PT_DYNAMIC slice_from_program_header should succeed!");
65+
66+
// Let's also check if the content is valid
67+
println!("\n📊 PT_DYNAMIC content:");
68+
for j in 0..(p_filesz / entry_size) {
69+
let entry_offset = start + (j as usize * entry_size as usize);
70+
let d_tag = u64::from_le_bytes(elf_bytes[entry_offset..entry_offset+8].try_into().unwrap());
71+
let d_val = u64::from_le_bytes(elf_bytes[entry_offset+8..entry_offset+16].try_into().unwrap());
72+
73+
let tag_name = match d_tag {
74+
0 => "DT_NULL",
75+
5 => "DT_STRTAB",
76+
6 => "DT_SYMTAB",
77+
10 => "DT_STRSZ",
78+
11 => "DT_SYMENT",
79+
17 => "DT_REL",
80+
18 => "DT_RELSZ",
81+
19 => "DT_RELENT",
82+
30 => "DT_FLAGS",
83+
0x6ffffffa => "DT_RELCOUNT",
84+
_ => "Unknown"
85+
};
86+
87+
println!(" Entry {}: {} (0x{:x}) = 0x{:x}", j, tag_name, d_tag, d_val);
88+
89+
if d_tag == 0 { break; } // DT_NULL terminates
90+
}
91+
92+
// Now check if this matches what's in SHT_DYNAMIC
93+
println!("\n🔍 Comparing with SHT_DYNAMIC:");
94+
95+
let e_shoff = u64::from_le_bytes(elf_bytes[40..48].try_into().unwrap());
96+
let e_shnum = u16::from_le_bytes(elf_bytes[60..62].try_into().unwrap());
97+
98+
for k in 0..e_shnum {
99+
let shdr_offset = e_shoff as usize + (k as usize * 64);
100+
let sh_type = u32::from_le_bytes(elf_bytes[shdr_offset+4..shdr_offset+8].try_into().unwrap());
101+
102+
if sh_type == 6 { // SHT_DYNAMIC
103+
let sh_offset = u64::from_le_bytes(elf_bytes[shdr_offset+24..shdr_offset+32].try_into().unwrap());
104+
let sh_size = u64::from_le_bytes(elf_bytes[shdr_offset+32..shdr_offset+40].try_into().unwrap());
105+
106+
println!(" SHT_DYNAMIC section header:");
107+
println!(" sh_offset: 0x{:x}", sh_offset);
108+
println!(" sh_size: 0x{:x}", sh_size);
109+
110+
if sh_offset == p_offset && sh_size == p_filesz {
111+
println!(" ✅ PT_DYNAMIC and SHT_DYNAMIC point to same data!");
112+
} else {
113+
println!(" ⚠️ PT_DYNAMIC and SHT_DYNAMIC differ!");
114+
println!(" PT_DYNAMIC: offset=0x{:x}, size=0x{:x}", p_offset, p_filesz);
115+
println!(" SHT_DYNAMIC: offset=0x{:x}, size=0x{:x}", sh_offset, sh_size);
116+
}
117+
break;
118+
}
119+
}
120+
121+
return;
122+
}
123+
}
124+
125+
println!("❌ No PT_DYNAMIC found!");
126+
}

0 commit comments

Comments
 (0)