use std::{fs::File, io::Write};
use sha2::{Digest, Sha256};
use solana_address::Address;
use solana_program_runtime::invoke_context::{Executable, InvokeContext, RegisterTrace};
use solana_transaction::sanitized::SanitizedTransaction;
use solana_transaction_context::{IndexOfAccount, InstructionContext};
use crate::{HPSVM, InvocationInspectCallback};
const DEFAULT_PATH: &str = "target/sbf/trace";
#[derive(Debug)]
pub struct DefaultRegisterTracingCallback {
pub sbf_trace_dir: String,
pub sbf_trace_disassemble: bool,
}
impl Default for DefaultRegisterTracingCallback {
fn default() -> Self {
Self {
sbf_trace_dir: std::env::var("SBF_TRACE_DIR").unwrap_or(DEFAULT_PATH.to_string()),
sbf_trace_disassemble: std::env::var("SBF_TRACE_DISASSEMBLE").is_ok(),
}
}
}
impl DefaultRegisterTracingCallback {
pub fn disassemble_register_trace<W: std::io::Write>(
&self,
writer: &mut W,
program_id: &Address,
executable: &Executable,
register_trace: RegisterTrace<'_>,
) {
match solana_program_runtime::solana_sbpf::static_analysis::Analysis::from_executable(
executable,
) {
Ok(analysis) => {
if let Err(e) = analysis.disassemble_register_trace(writer, register_trace) {
eprintln!("Can't disassemble register trace for {program_id}: {e:#?}");
}
}
Err(e) => {
eprintln!("Can't create trace disassemble analysis for {program_id}: {e:#?}")
}
}
}
pub fn handler(
&self,
svm: &HPSVM,
instruction_context: InstructionContext<'_, '_>,
executable: &Executable,
register_trace: RegisterTrace<'_>,
) -> Result<(), Box<dyn std::error::Error>> {
if register_trace.is_empty() {
return Ok(());
}
let current_dir = std::env::current_dir()?;
let sbf_trace_dir = current_dir.join(&self.sbf_trace_dir);
std::fs::create_dir_all(&sbf_trace_dir)?;
let trace_digest = compute_hash(as_bytes(register_trace));
let base_fname = sbf_trace_dir.join(&trace_digest[..16]);
let mut regs_file = File::create(base_fname.with_extension("regs"))?;
let mut insns_file = File::create(base_fname.with_extension("insns"))?;
let mut program_id_file = File::create(base_fname.with_extension("program_id"))?;
let program_id = instruction_context.get_program_key()?;
if self.sbf_trace_disassemble {
let mut trace_disassemble_file = File::create(base_fname.with_extension("trace"))?;
self.disassemble_register_trace(
&mut trace_disassemble_file,
program_id,
executable,
register_trace,
);
}
let _ = program_id_file.write(program_id.to_string().as_bytes());
if let Ok(elf_data) = svm.accounts().try_program_elf_bytes(program_id) {
let mut so_hash_file = File::create(base_fname.with_extension("exec.sha256"))?;
let _ = so_hash_file.write(compute_hash(elf_data).as_bytes());
}
let (_, program) = executable.get_text_bytes();
for regs in register_trace.iter() {
let pc = regs[11];
let insn =
solana_program_runtime::solana_sbpf::ebpf::get_insn_unchecked(program, pc as usize)
.to_array();
let _ = regs_file.write(as_bytes(regs.as_slice()))?;
let _ = insns_file.write(insn.as_slice())?;
}
Ok(())
}
}
impl InvocationInspectCallback for DefaultRegisterTracingCallback {
fn before_invocation(
&self,
_: &HPSVM,
_: &SanitizedTransaction,
_: &[IndexOfAccount],
_: &InvokeContext<'_, '_>,
) {
}
fn after_invocation(
&self,
svm: &HPSVM,
invoke_context: &InvokeContext<'_, '_>,
register_tracing_enabled: bool,
) {
if register_tracing_enabled {
invoke_context.iterate_vm_traces(
&|instruction_context: InstructionContext<'_, '_>,
executable: &Executable,
register_trace: RegisterTrace<'_>| {
if let Err(e) =
self.handler(svm, instruction_context, executable, register_trace)
{
eprintln!("Error collecting the register tracing: {}", e);
}
},
);
}
}
}
pub(crate) fn as_bytes<T>(slice: &[T]) -> &[u8] {
unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const u8, std::mem::size_of_val(slice)) }
}
fn compute_hash(slice: &[u8]) -> String {
hex::encode(Sha256::digest(slice).as_slice())
}