Skip to content

Commit

Permalink
CLI Tool (#121)
Browse files Browse the repository at this point in the history
* Implements the CLI tool for easier debugging and analysis.
  • Loading branch information
Lichtso authored Nov 20, 2020
1 parent b6ae590 commit 4216549
Show file tree
Hide file tree
Showing 8 changed files with 784 additions and 14 deletions.
510 changes: 510 additions & 0 deletions cli/Cargo.lock

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "rbpf_cli"
version = "0.2.1"
description = "CLI to test and analyze eBPF programs"
authors = ["Solana Maintainers <[email protected]>"]
repository = "https://github.com/solana-labs/rbpf"
homepage = "https://solana.com/"
keywords = ["BPF", "eBPF", "interpreter", "JIT"]
edition = "2018"

[dependencies]
solana_rbpf = { path = "../" }
test_utils = { path = "../test_utils/" }
clap = "3.0.0-beta.2"
rustc-demangle = "0.1"
211 changes: 211 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
use clap::{App, Arg};
use rustc_demangle::demangle;
use solana_rbpf::{
assembler::assemble,
disassembler::to_insn_vec,
ebpf,
memory_region::{MemoryMapping, MemoryRegion},
user_error::UserError,
vm::{Config, EbpfVm, Executable, SyscallObject, SyscallRegistry},
};
use std::fs::File;
use std::io::Read;
use std::path::Path;
use test_utils::{Result, TestInstructionMeter};

pub struct MockSyscall {
name: String,
}
impl SyscallObject<UserError> for MockSyscall {
fn call(
&mut self,
arg1: u64,
arg2: u64,
arg3: u64,
arg4: u64,
arg5: u64,
_memory_mapping: &MemoryMapping,
result: &mut Result,
) {
println!(
"Syscall {}: {:#x}, {:#x}, {:#x}, {:#x}, {:#x}",
self.name, arg1, arg2, arg3, arg4, arg5,
);
*result = Result::Ok(0);
}
}

fn main() {
let matches = App::new("Solana RBPF CLI")
.version("0.2.1")
.author("Solana Maintainers <[email protected]>")
.about("CLI to test and analyze eBPF programs")
.arg(
Arg::new("assembler")
.about("Assemble and load eBPF executable")
.short('a')
.long("asm")
.value_name("FILE")
.takes_value(true)
.required_unless_present("elf"),
)
.arg(
Arg::new("elf")
.about("Load ELF as eBPF executable")
.short('e')
.long("elf")
.value_name("FILE")
.takes_value(true)
.required_unless_present("assembler"),
)
.arg(
Arg::new("input")
.about("Input for the program to run on")
.short('i')
.long("input")
.value_name("FILE / BYTES")
.takes_value(true)
.default_value("0"),
)
.arg(
Arg::new("memory")
.about("Heap memory for the program to run on")
.short('m')
.long("mem")
.value_name("BYTES")
.takes_value(true)
.default_value("0"),
)
.arg(
Arg::new("use")
.about("Method of execution to use")
.short('u')
.long("use")
.takes_value(true)
.possible_values(&["disassembler", "interpreter", "jit"])
.required(true),
)
.arg(
Arg::new("instruction limit")
.about("Limit the number of instructions to execute")
.short('l')
.long("lim")
.takes_value(true)
.value_name("COUNT")
.default_value(&std::i64::MAX.to_string()),
)
.arg(
Arg::new("trace")
.about("Enables tracing instrumentation")
.short('t')
.long("trace"),
)
.get_matches();

let mut config = Config::default();
config.enable_instruction_tracing = matches.is_present("trace");
let mut executable = match matches.value_of("assembler") {
Some(asm_file_name) => {
let mut file = File::open(&Path::new(asm_file_name)).unwrap();
let mut source = Vec::new();
file.read_to_end(&mut source).unwrap();
let program = assemble(std::str::from_utf8(source.as_slice()).unwrap()).unwrap();
Executable::<UserError, TestInstructionMeter>::from_text_bytes(&program, None, config)
}
None => {
let mut file = File::open(&Path::new(matches.value_of("elf").unwrap())).unwrap();
let mut elf = Vec::new();
file.read_to_end(&mut elf).unwrap();
Executable::<UserError, TestInstructionMeter>::from_elf(&elf, None, config)
}
}
.unwrap();

let (syscalls, bpf_functions) = executable.get_symbols();
let mut syscall_registry = SyscallRegistry::default();
for hash in syscalls.keys() {
let _ = syscall_registry.register_syscall_by_hash(*hash, MockSyscall::call);
}
executable.set_syscall_registry(syscall_registry);

match matches.value_of("use") {
Some("disassembler") => {
let (_program_vm_addr, program) = executable.get_text_bytes().unwrap();
for insn in to_insn_vec(program).iter() {
if let Some(bpf_function) =
bpf_functions.get(&((insn.ptr + ebpf::ELF_INSN_DUMP_OFFSET) as u64 * 8))
{
println!("{}:", demangle(&bpf_function.0));
}
print!("{:5} ", insn.ptr);
if insn.name == "call" {
if let Some(syscall_name) = syscalls.get(&(insn.imm as u32)) {
println!("syscall {}", syscall_name);
} else if let Some(target_pc) = executable.lookup_bpf_call(insn.imm as u32) {
if let Some(bpf_function) = bpf_functions
.get(&((target_pc + ebpf::ELF_INSN_DUMP_OFFSET) as u64 * 8))
{
println!("call {}", demangle(&bpf_function.0));
} else {
println!("call {} # unresolved symbol", target_pc);
}
} else {
println!("call {:x} # unresolved relocation", insn.imm);
}
} else {
println!("{}", insn.desc);
}
}
return;
}
Some("jit") => {
executable.jit_compile().unwrap();
}
_ => {}
}

let mut mem = match matches.value_of("input").unwrap().parse::<usize>() {
Ok(allocate) => vec![0u8; allocate],
Err(_) => {
let mut file = File::open(&Path::new(matches.value_of("input").unwrap())).unwrap();
let mut memory = Vec::new();
file.read_to_end(&mut memory).unwrap();
memory
}
};
let mut instruction_meter = TestInstructionMeter {
remaining: matches
.value_of("instruction limit")
.unwrap()
.parse::<u64>()
.unwrap(),
};
let heap = vec![
0_u8;
matches
.value_of("memory")
.unwrap()
.parse::<usize>()
.unwrap()
];
let heap_region = MemoryRegion::new_from_slice(&heap, ebpf::MM_HEAP_START, 0, true);
let mut vm = EbpfVm::new(executable.as_ref(), &mut mem, &[heap_region]).unwrap();
for (hash, name) in &syscalls {
vm.bind_syscall_context_object(Box::new(MockSyscall { name: name.clone() }), Some(*hash))
.unwrap();
}
let result = if matches.value_of("use").unwrap() == "interpreter" {
vm.execute_program_interpreted(&mut instruction_meter)
} else {
vm.execute_program_jit(&mut instruction_meter)
};
println!("Result: {:?}", result);
println!("Instruction Count: {}", vm.get_total_instruction_count());
if config.enable_instruction_tracing {
let mut tracer_display = String::new();
vm.get_tracer()
.write(&mut tracer_display, vm.get_program())
.unwrap();
println!("Trace:\n{}", tracer_display);
}
}
2 changes: 1 addition & 1 deletion examples/uptime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ fn main() {
let mut vm =
EbpfVm::<UserError, DefaultInstructionMeter>::new(executable.as_ref(), &mut [], &[])
.unwrap();
vm.bind_syscall_context_object(Box::new(syscalls::BpfTimeGetNs {}))
vm.bind_syscall_context_object(Box::new(syscalls::BpfTimeGetNs {}), None)
.unwrap();

let time;
Expand Down
24 changes: 24 additions & 0 deletions src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,30 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> for EBpfElf<E, I
)
.into())
}

/// Get syscalls and BPF functions (if debug symbols are not stripped)
fn get_symbols(&self) -> (HashMap<u32, String>, HashMap<u64, (String, u64)>) {
let mut syscalls = HashMap::new();
let mut bpf_functions = HashMap::new();
if let Ok(elf) = Elf::parse(&self.elf_bytes) {
for symbol in &elf.dynsyms {
if symbol.st_info != 0x10 {
continue;
}
let name = elf.dynstrtab.get(symbol.st_name).unwrap().unwrap();
let hash = ebpf::hash_symbol_name(&name.as_bytes());
syscalls.insert(hash, name.to_string());
}
for symbol in &elf.syms {
if symbol.st_info & 0xEF != 0x02 {
continue;
}
let name = elf.strtab.get(symbol.st_name).unwrap().unwrap();
bpf_functions.insert(symbol.st_value, (name.to_string(), symbol.st_size));
}
}
(syscalls, bpf_functions)
}
}

impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf<E, I> {
Expand Down
30 changes: 20 additions & 10 deletions src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,13 @@ impl SyscallRegistry {
},
)
.is_some()
|| self
.context_object_slots
.insert(function, context_object_slot)
.is_some()
{
Err(EbpfError::SycallAlreadyRegistered)
} else {
assert!(self
.context_object_slots
.insert(function, context_object_slot)
.is_none());
Ok(())
}
}
Expand Down Expand Up @@ -210,6 +210,8 @@ pub trait Executable<E: UserDefinedError, I: InstructionMeter>: Send + Sync {
fn jit_compile(&mut self) -> Result<(), EbpfError<E>>;
/// Report information on a symbol that failed to be resolved
fn report_unresolved_symbol(&self, insn_offset: usize) -> Result<u64, EbpfError<E>>;
/// Get syscalls and BPF functions (if debug symbols are not stripped)
fn get_symbols(&self) -> (HashMap<u32, String>, HashMap<u64, (String, u64)>);
}

/// Static constructors for Executable
Expand Down Expand Up @@ -492,18 +494,26 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> {
/// executable.set_syscall_registry(syscall_registry);
/// let mut vm = EbpfVm::<UserError, DefaultInstructionMeter>::new(executable.as_ref(), &mut [], &[]).unwrap();
/// // Bind a context object instance to the previously registered syscall
/// vm.bind_syscall_context_object(Box::new(BpfTracePrintf {}));
/// vm.bind_syscall_context_object(Box::new(BpfTracePrintf {}), None);
/// ```
pub fn bind_syscall_context_object(
&mut self,
syscall_context_object: Box<dyn SyscallObject<E> + 'a>,
hash: Option<u32>,
) -> Result<(), EbpfError<E>> {
let fat_ptr: DynTraitFatPointer = unsafe { std::mem::transmute(&*syscall_context_object) };
let slot = self
.executable
.get_syscall_registry()
.lookup_context_object_slot(fat_ptr.vtable.methods[0] as u64)
.unwrap();
let syscall_registry = self.executable.get_syscall_registry();
let slot = match hash {
Some(hash) => {
syscall_registry
.lookup_syscall(hash)
.unwrap()
.context_object_slot
}
None => syscall_registry
.lookup_context_object_slot(fat_ptr.vtable.methods[0] as u64)
.unwrap(),
};
if !self.syscall_context_objects[SYSCALL_CONTEXT_OBJECTS_OFFSET + slot].is_null() {
Err(EbpfError::SycallAlreadyBound)
} else {
Expand Down
4 changes: 2 additions & 2 deletions tests/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ fn test_fuzz_execute() {
&[],
)
.unwrap();
vm.bind_syscall_context_object(Box::new(BpfSyscallString {}))
vm.bind_syscall_context_object(Box::new(BpfSyscallString {}), None)
.unwrap();
vm.bind_syscall_context_object(Box::new(BpfSyscallU64 {}))
vm.bind_syscall_context_object(Box::new(BpfSyscallU64 {}), None)
.unwrap();
let _ = vm.execute_program_interpreted(&mut DefaultInstructionMeter {});
}
Expand Down
2 changes: 1 addition & 1 deletion tests/ubpf_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ macro_rules! test_interpreter_and_jit {
$($syscall_registry.register_syscall_by_hash::<UserError, _>($location, $syscall_function).unwrap();)*
};
(2, $vm:expr, $($location:expr => $syscall_function:expr; $syscall_context_object:expr),*) => {
$($vm.bind_syscall_context_object(Box::new($syscall_context_object)).unwrap();)*
$($vm.bind_syscall_context_object(Box::new($syscall_context_object), None).unwrap();)*
};
( $executable:expr, $mem:tt, ($($location:expr => $syscall_function:expr; $syscall_context_object:expr),* $(,)?), $check:block, $expected_instruction_count:expr ) => {
let check_closure = $check;
Expand Down

0 comments on commit 4216549

Please sign in to comment.