Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add instruction test suite #564

Closed
wants to merge 16 commits into from
145 changes: 145 additions & 0 deletions src/bin/sbpf_instr_test/exec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use solana_rbpf::{
ebpf::{self, Insn},
elf::Executable,
interpreter::Interpreter,
memory_region::{MemoryMapping, MemoryRegion},
program::{BuiltinProgram, FunctionRegistry, SBPFVersion},
verifier::{RequisiteVerifier, Verifier},
vm::{Config, EbpfVm, TestContextObject},
};
use std::sync::Arc;

use crate::types::*;

pub fn run_input(input: &Input) -> Effects {
let vm_config = Config {
max_call_depth: 16,
enable_address_translation: true,
enable_stack_frame_gaps: true,
instruction_meter_checkpoint_distance: 10000,
enable_instruction_meter: true,
enable_instruction_tracing: false,
enable_symbol_and_section_labels: false,
reject_broken_elfs: true, // TODO confirm
noop_instruction_rate: 256,
sanitize_user_provided_values: true,
external_internal_function_hash_collision: false,
reject_callx_r10: false,
enable_sbpf_v1: true,
enable_sbpf_v2: false,
optimize_rodata: false,
new_elf_parser: true,
aligned_memory_mapping: false,
..Config::default()
};
let sbpf_version = SBPFVersion::V1;
let function_registry_usize = FunctionRegistry::default();
let function_registry_typed = FunctionRegistry::default();

let mut text = Vec::<u8>::with_capacity(8 * 3);

text.extend_from_slice(&input.encode_instruction().to_le_bytes());
if let Some(ext) = input.encode_instruction_ext() {
text.extend_from_slice(&ext.to_le_bytes());
} else if input.imm > u32::MAX as u64 {
panic!("imm overflow");
}
text.extend_from_slice(
&Insn {
opc: ebpf::EXIT,
..Insn::default()
}
.to_array(),
);

let verify_fail =
RequisiteVerifier::verify(&text, &vm_config, &sbpf_version, &function_registry_usize)
.is_err();
if verify_fail {
return Effects {
status: Status::VerifyFail,
..Effects::default()
};
}

// Set up VM

let loader = Arc::new(BuiltinProgram::new_loader(
vm_config,
function_registry_typed,
));

let executable = Executable::new_from_text_bytes(
&text,
Arc::clone(&loader),
sbpf_version.clone(),
function_registry_usize,
)
.unwrap();

let cus = 100u64;
let mut context_object = TestContextObject::new(cus);
let mut input_data = input.input.clone();

let regions: Vec<MemoryRegion> = vec![
MemoryRegion::new_readonly(&[], ebpf::MM_PROGRAM_START),
MemoryRegion::new_readonly(&[], ebpf::MM_STACK_START),
MemoryRegion::new_readonly(&[], ebpf::MM_HEAP_START),
MemoryRegion::new_writable(&mut input_data, ebpf::MM_INPUT_START),
];

let memory_mapping = MemoryMapping::new(regions, &vm_config, &sbpf_version).unwrap();
let stack_len = 0usize;

let mut vm = EbpfVm::new(
loader,
&sbpf_version,
&mut context_object,
memory_mapping,
stack_len,
);
vm.previous_instruction_meter = cus;

let mut interpreter = Interpreter::new(&mut vm, &executable, input.regs);
while interpreter.step() {}
let post_reg = interpreter.reg;
if vm.program_result.is_err() {
return Effects {
status: Status::Fault,
..Effects::default()
};
}

Effects {
status: Status::Ok,
regs: post_reg,
}
}

pub fn run_fixture(fixture: &Fixture, source_file: &str) -> bool {
let mut fail = false;
let actual = run_input(&fixture.input);
let expected = &fixture.effects;
if expected.status != actual.status {
eprintln!(
"FAIL {}:{}: Expected status {:?}, got {:?}",
source_file, fixture.line, fixture.effects.status, actual.status
);
fail = true;
}
if expected.status != Status::Ok || actual.status != Status::Ok {
return fail;
}
for i in 0..=9 {
let reg_expected = expected.regs[i];
let reg_actual = actual.regs[i];
if reg_expected != reg_actual {
eprintln!(
"FAIL {}:{}: Expected r{} = {:#x}, got {:#x}",
source_file, fixture.line, i, reg_expected, reg_actual
);
fail = true;
}
}
fail
}
60 changes: 60 additions & 0 deletions src/bin/sbpf_instr_test/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
pub(crate) mod types;
pub use types::*;
mod exec;
mod parse;

fn handle_file(file_path: &str) -> bool {
eprintln!("++++ {}", file_path);
let file = std::fs::read(file_path).unwrap();
let parser = crate::parse::Parser::new(file_path, &file);
let mut fail = false;
for fixture in parser {
fail |= crate::exec::run_fixture(&fixture, file_path);
}
fail
}

fn main() {
let mut args = std::env::args();
args.next();
let mut fail = false;
for arg in args {
fail |= handle_file(&arg);
}
if fail {
std::process::exit(1);
}
}

#[cfg(test)]
mod tests {
#[test]
fn test_instr_bitwise() {
assert!(!super::handle_file("tests/instr/bitwise.instr"));
}

#[test]
fn test_instr_int_math() {
assert!(!super::handle_file("tests/instr/int_math.instr"));
}

#[test]
fn test_instr_jump() {
assert!(!super::handle_file("tests/instr/jump.instr"));
}

#[test]
fn test_instr_load() {
assert!(!super::handle_file("tests/instr/load.instr"));
}

#[test]
fn test_instr_opcode() {
assert!(!super::handle_file("tests/instr/opcode.instr"));
}

#[test]
fn test_instr_shift() {
assert!(!super::handle_file("tests/instr/shift.instr"));
}
}
Loading
Loading