1use crate::Program;
16use crate::encode::{SIG_BYTES, SIG_REGS, SIG_XREGS};
17use std::io::Write;
18use std::process::Command;
19
20const LOAD: u64 = 0x8000_0000;
22pub const SPIKE_ISA: &str = "rv64imc_zba_zbb_zbs_zicond";
24
25pub fn slot_to_xreg(slot: u8) -> u8 {
27 const X: [u8; 13] = [1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
28 X[slot as usize]
29}
30
31#[allow(clippy::too_many_arguments)]
33fn shdr(
34 v: &mut Vec<u8>,
35 name: u32,
36 typ: u32,
37 flags: u64,
38 addr: u64,
39 off: u64,
40 size: u64,
41 align: u64,
42) {
43 v.extend_from_slice(&name.to_le_bytes());
44 v.extend_from_slice(&typ.to_le_bytes());
45 v.extend_from_slice(&flags.to_le_bytes());
46 v.extend_from_slice(&addr.to_le_bytes());
47 v.extend_from_slice(&off.to_le_bytes());
48 v.extend_from_slice(&size.to_le_bytes());
49 v.extend_from_slice(&0u32.to_le_bytes()); v.extend_from_slice(&0u32.to_le_bytes()); v.extend_from_slice(&align.to_le_bytes());
52 v.extend_from_slice(&0u64.to_le_bytes()); }
54
55fn build_elf(code: &[u8]) -> Vec<u8> {
60 const EH: u64 = 64; const PH: u64 = 56; let code_off = EH + PH; let strtab: &[u8] = b"\0.text\0.shstrtab\0"; let strtab_off = code_off + code.len() as u64;
65 let shoff = strtab_off + strtab.len() as u64;
66
67 let mut v = Vec::new();
68 v.extend_from_slice(&[0x7f, b'E', b'L', b'F', 2, 1, 1, 0]); v.extend_from_slice(&[0u8; 8]); v.extend_from_slice(&2u16.to_le_bytes()); v.extend_from_slice(&243u16.to_le_bytes()); v.extend_from_slice(&1u32.to_le_bytes()); v.extend_from_slice(&LOAD.to_le_bytes()); v.extend_from_slice(&EH.to_le_bytes()); v.extend_from_slice(&shoff.to_le_bytes()); v.extend_from_slice(&0u32.to_le_bytes()); v.extend_from_slice(&(EH as u16).to_le_bytes()); v.extend_from_slice(&(PH as u16).to_le_bytes()); v.extend_from_slice(&1u16.to_le_bytes()); v.extend_from_slice(&64u16.to_le_bytes()); v.extend_from_slice(&3u16.to_le_bytes()); v.extend_from_slice(&2u16.to_le_bytes()); v.extend_from_slice(&1u32.to_le_bytes()); v.extend_from_slice(&5u32.to_le_bytes()); v.extend_from_slice(&code_off.to_le_bytes()); v.extend_from_slice(&LOAD.to_le_bytes()); v.extend_from_slice(&LOAD.to_le_bytes()); v.extend_from_slice(&(code.len() as u64).to_le_bytes()); v.extend_from_slice(&(code.len() as u64).to_le_bytes()); v.extend_from_slice(&0x1000u64.to_le_bytes()); debug_assert_eq!(v.len() as u64, code_off);
95
96 v.extend_from_slice(code);
97 v.extend_from_slice(strtab);
98 debug_assert_eq!(v.len() as u64, shoff);
99
100 shdr(&mut v, 0, 0, 0, 0, 0, 0, 0); shdr(&mut v, 1, 1, 6, LOAD, code_off, code.len() as u64, 4); shdr(&mut v, 7, 3, 0, 0, strtab_off, strtab.len() as u64, 1); v
105}
106
107fn temp_path(tag: &str) -> std::path::PathBuf {
110 use std::sync::atomic::{AtomicU64, Ordering};
111 static N: AtomicU64 = AtomicU64::new(0);
112 let n = N.fetch_add(1, Ordering::Relaxed);
113 std::env::temp_dir().join(format!("javm-fuzz-{}-{}-{tag}", std::process::id(), n))
114}
115
116fn abi_name(xreg: u8) -> &'static str {
119 match xreg {
120 1 => "ra",
121 2 => "sp",
122 5 => "t0",
123 6 => "t1",
124 7 => "t2",
125 8 => "s0",
126 9 => "s1",
127 10 => "a0",
128 11 => "a1",
129 12 => "a2",
130 13 => "a3",
131 14 => "a4",
132 15 => "a5",
133 _ => panic!("abi_name: x{xreg} is not in the captured signature set"),
134 }
135}
136
137pub fn spike_signature(prog: &Program) -> std::io::Result<[u8; SIG_BYTES]> {
150 let epilogue_len = crate::encode::signature_epilogue(crate::SIG_BASE).len();
153 let body_end = prog.code.len().saturating_sub(epilogue_len);
154 let body = &prog.code[..body_end];
155
156 let mut words: Vec<u32> = Vec::new();
157 for &xreg in &SIG_XREGS {
158 let val = if (10..=13).contains(&xreg) {
163 0
164 } else {
165 let slot = javm_exec::regs::reg_slot_or_ff(xreg);
166 prog.init_regs.get(&slot).copied().unwrap_or(0)
167 };
168 words.extend(crate::encode::li64(xreg, val));
169 }
170 words.extend_from_slice(body);
171 let code = crate::encode::enc(&words);
172 let end = LOAD + code.len() as u64; let elf_path = temp_path("elf");
175 let cmd_path = temp_path("cmd");
176 std::fs::write(&elf_path, build_elf(&code))?;
177
178 let mut cmd = String::new();
182 cmd.push_str(&format!("until pc 0 0x{end:016x}\n"));
183 for &xreg in &SIG_XREGS {
184 cmd.push_str(&format!("reg 0 {}\n", abi_name(xreg)));
185 }
186 cmd.push_str("quit\n");
187 std::fs::File::create(&cmd_path)?.write_all(cmd.as_bytes())?;
188
189 let out = Command::new("spike")
190 .arg("-d")
191 .arg(format!("--debug-cmd={}", cmd_path.display()))
192 .arg(format!("--isa={SPIKE_ISA}"))
193 .arg(&elf_path)
194 .output()?;
195
196 let _ = std::fs::remove_file(&elf_path);
197 let _ = std::fs::remove_file(&cmd_path);
198
199 let text = format!(
203 "{}{}",
204 String::from_utf8_lossy(&out.stdout),
205 String::from_utf8_lossy(&out.stderr)
206 );
207 let vals = parse_last_n_hex(&text, SIG_REGS).ok_or_else(|| {
208 std::io::Error::other(format!(
209 "could not parse {SIG_REGS} registers from spike output:\n{text}"
210 ))
211 })?;
212 let mut sig = [0u8; SIG_BYTES];
213 for (i, v) in vals.iter().enumerate() {
214 sig[i * 8..i * 8 + 8].copy_from_slice(&v.to_le_bytes());
215 }
216 Ok(sig)
217}
218
219fn parse_last_n_hex(text: &str, n: usize) -> Option<Vec<u64>> {
223 let all: Vec<u64> = text
224 .split(|c: char| !c.is_ascii_hexdigit() && c != 'x' && c != 'X')
225 .filter_map(|tok| tok.strip_prefix("0x").or_else(|| tok.strip_prefix("0X")))
226 .filter_map(|h| u64::from_str_radix(h, 16).ok())
227 .collect();
228 if all.len() < n {
229 return None;
230 }
231 Some(all[all.len() - n..].to_vec())
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use crate::encode;
238 use std::collections::BTreeMap;
239
240 #[test]
245 #[ignore = "needs spike on PATH"]
246 fn spike_computes_addi() {
247 let mut init = BTreeMap::new();
248 init.insert(javm_exec::regs::reg_slot_or_ff(8), 10u64); let prog = Program {
250 code: vec![encode::addi(8, 8, 5)],
251 init_regs: init,
252 init_mem: None,
253 };
254 let sig = spike_signature(&prog).unwrap();
255 let s = javm_exec::regs::reg_slot_or_ff(8) as usize;
257 let val = u64::from_le_bytes(sig[s * 8..s * 8 + 8].try_into().unwrap());
258 assert_eq!(val, 15);
259 }
260}