* chore: cargo +nightly clippy --fix -Z unstable-options (cherry picked from commit6514096a67
) # Conflicts: # core/src/banking_stage.rs # core/src/cost_model.rs # core/src/cost_tracker.rs # core/src/execute_cost_table.rs # core/src/replay_stage.rs # core/src/tvu.rs # ledger-tool/src/main.rs # programs/bpf_loader/build.rs # rbpf-cli/src/main.rs # sdk/cargo-build-bpf/src/main.rs # sdk/cargo-test-bpf/src/main.rs # sdk/src/secp256k1_instruction.rs * chore: cargo fmt (cherry picked from commit789f33e8db
) * Updates BPF program assert_instruction_count tests. (cherry picked from commitc1e03f3410
) # Conflicts: # programs/bpf/tests/programs.rs * Resolve conflicts Co-authored-by: Alexander Meißner <AlexanderMeissner@gmx.net> Co-authored-by: Michael Vines <mvines@gmail.com>
This commit is contained in:
253
rbpf-cli/src/main.rs
Normal file
253
rbpf-cli/src/main.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
use clap::{crate_version, App, Arg};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Result;
|
||||
use solana_bpf_loader_program::{
|
||||
create_vm, serialization::serialize_parameters, syscalls::register_syscalls, BpfError,
|
||||
ThisInstructionMeter,
|
||||
};
|
||||
use solana_rbpf::{
|
||||
assembler::assemble,
|
||||
static_analysis::Analysis,
|
||||
verifier::check,
|
||||
vm::{Config, DynamicAnalysis, Executable},
|
||||
};
|
||||
use solana_sdk::{
|
||||
account::AccountSharedData,
|
||||
bpf_loader,
|
||||
keyed_account::KeyedAccount,
|
||||
process_instruction::{InvokeContext, MockInvokeContext},
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
use std::{cell::RefCell, fs::File, io::Read, io::Seek, io::SeekFrom, path::Path};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct Account {
|
||||
lamports: u64,
|
||||
data: Vec<u8>,
|
||||
owner: Pubkey,
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Input {
|
||||
accounts: Vec<Account>,
|
||||
insndata: Vec<u8>,
|
||||
}
|
||||
fn load_accounts(path: &Path) -> Result<Input> {
|
||||
let file = File::open(path).unwrap();
|
||||
let input: Input = serde_json::from_reader(file)?;
|
||||
println!("Program input:");
|
||||
println!("accounts {:?}", &input.accounts);
|
||||
println!("insndata {:?}", &input.insndata);
|
||||
println!("----------------------------------------");
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
let matches = App::new("Solana BPF CLI")
|
||||
.version(crate_version!())
|
||||
.author("Solana Maintainers <maintainers@solana.foundation>")
|
||||
.about(
|
||||
r##"CLI to test and analyze eBPF programs.
|
||||
|
||||
The tool executes eBPF programs in a mocked environment.
|
||||
Some features, such as sysvars syscall and CPI, are not
|
||||
available for the programs executed by the CLI tool.
|
||||
|
||||
The input data for a program execution have to be in JSON format
|
||||
and the following fields are required
|
||||
{
|
||||
"accounts": [
|
||||
{
|
||||
"lamports": 1000,
|
||||
"data": [0, 0, 0, 3],
|
||||
"owner": [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
}
|
||||
],
|
||||
"insndata": []
|
||||
}
|
||||
"##,
|
||||
)
|
||||
.arg(
|
||||
Arg::new("PROGRAM")
|
||||
.about(
|
||||
"Program file to use. This is either an ELF shared-object file to be executed, \
|
||||
or an assembly file to be assembled and executed.",
|
||||
)
|
||||
.required(true)
|
||||
.index(1)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("input")
|
||||
.about(
|
||||
"Input for the program to run on, where FILE is a name of a JSON file \
|
||||
with input data, or BYTES is the number of 0-valued bytes to allocate for program parameters",
|
||||
)
|
||||
.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("memory")
|
||||
.value_name("BYTES")
|
||||
.takes_value(true)
|
||||
.default_value("0"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("use")
|
||||
.about(
|
||||
"Method of execution to use, where 'cfg' generates Control Flow Graph \
|
||||
of the program, 'disassembler' dumps disassembled code of the program, 'interpreter' runs \
|
||||
the program in the virtual machine's interpreter, and 'jit' precompiles the program to \
|
||||
native machine code before execting it in the virtual machine.",
|
||||
)
|
||||
.short('u')
|
||||
.long("use")
|
||||
.takes_value(true)
|
||||
.value_name("VALUE")
|
||||
.possible_values(&["cfg", "disassembler", "interpreter", "jit"])
|
||||
.default_value("interpreter"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("instruction limit")
|
||||
.about("Limit the number of instructions to execute")
|
||||
.short('l')
|
||||
.long("limit")
|
||||
.takes_value(true)
|
||||
.value_name("COUNT")
|
||||
.default_value(&std::i64::MAX.to_string()),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("trace")
|
||||
.about("Output trace to 'trace.out' file using tracing instrumentation")
|
||||
.short('t')
|
||||
.long("trace"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("profile")
|
||||
.about("Output profile to 'profile.dot' file using tracing instrumentation")
|
||||
.short('p')
|
||||
.long("profile"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("verify")
|
||||
.about("Run the verifier before execution or disassembly")
|
||||
.short('v')
|
||||
.long("verify"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let config = Config {
|
||||
enable_instruction_tracing: matches.is_present("trace") || matches.is_present("profile"),
|
||||
..Config::default()
|
||||
};
|
||||
let mut accounts = Vec::new();
|
||||
let mut account_refcells = Vec::new();
|
||||
let default_account = RefCell::new(AccountSharedData::default());
|
||||
let key = solana_sdk::pubkey::new_rand();
|
||||
let mut mem = match matches.value_of("input").unwrap().parse::<usize>() {
|
||||
Ok(allocate) => {
|
||||
accounts.push(KeyedAccount::new(&key, false, &default_account));
|
||||
vec![0u8; allocate]
|
||||
}
|
||||
Err(_) => {
|
||||
let input = load_accounts(Path::new(matches.value_of("input").unwrap())).unwrap();
|
||||
for acc in input.accounts {
|
||||
let asd = AccountSharedData::new_ref(acc.lamports, acc.data.len(), &acc.owner);
|
||||
asd.borrow_mut().set_data(acc.data);
|
||||
account_refcells.push(asd);
|
||||
}
|
||||
for acc in &account_refcells {
|
||||
accounts.push(KeyedAccount::new(&key, false, acc));
|
||||
}
|
||||
let lid = bpf_loader::id();
|
||||
let pid = Pubkey::new(&[0u8; 32]);
|
||||
let mut bytes = serialize_parameters(&lid, &pid, &accounts, &input.insndata).unwrap();
|
||||
Vec::from(bytes.as_slice_mut())
|
||||
}
|
||||
};
|
||||
let mut invoke_context = MockInvokeContext::new(accounts);
|
||||
let logger = invoke_context.logger.clone();
|
||||
let compute_meter = invoke_context.get_compute_meter();
|
||||
let mut instruction_meter = ThisInstructionMeter { compute_meter };
|
||||
|
||||
let program = matches.value_of("PROGRAM").unwrap();
|
||||
let mut file = File::open(&Path::new(program)).unwrap();
|
||||
let mut magic = [0u8; 4];
|
||||
file.read_exact(&mut magic).unwrap();
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
let mut contents = Vec::new();
|
||||
file.read_to_end(&mut contents).unwrap();
|
||||
let mut executable = if magic == [0x7f, 0x45, 0x4c, 0x46] {
|
||||
<dyn Executable<BpfError, ThisInstructionMeter>>::from_elf(&contents, None, config)
|
||||
.map_err(|err| format!("Executable constructor failed: {:?}", err))
|
||||
} else {
|
||||
assemble::<BpfError, ThisInstructionMeter>(
|
||||
std::str::from_utf8(contents.as_slice()).unwrap(),
|
||||
None,
|
||||
config,
|
||||
)
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
if matches.is_present("verify") {
|
||||
let (_, elf_bytes) = executable.get_text_bytes().unwrap();
|
||||
check(elf_bytes).unwrap();
|
||||
}
|
||||
executable.set_syscall_registry(register_syscalls(&mut invoke_context).unwrap());
|
||||
executable.jit_compile().unwrap();
|
||||
let analysis = Analysis::from_executable(executable.as_ref());
|
||||
|
||||
match matches.value_of("use") {
|
||||
Some("cfg") => {
|
||||
let mut file = File::create("cfg.dot").unwrap();
|
||||
analysis.visualize_graphically(&mut file, None).unwrap();
|
||||
return;
|
||||
}
|
||||
Some("disassembler") => {
|
||||
let stdout = std::io::stdout();
|
||||
analysis.disassemble(&mut stdout.lock()).unwrap();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let id = bpf_loader::id();
|
||||
let mut vm = create_vm(&id, executable.as_ref(), &mut mem, &mut invoke_context).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)
|
||||
};
|
||||
if logger.log.borrow().len() > 0 {
|
||||
println!("Program output:");
|
||||
for s in logger.log.borrow_mut().iter() {
|
||||
println!("{}", s);
|
||||
}
|
||||
println!("----------------------------------------");
|
||||
}
|
||||
println!("Result: {:?}", result);
|
||||
println!("Instruction Count: {}", vm.get_total_instruction_count());
|
||||
if matches.is_present("trace") {
|
||||
println!("Trace is saved in trace.out");
|
||||
let mut file = File::create("trace.out").unwrap();
|
||||
let analysis = Analysis::from_executable(executable.as_ref());
|
||||
vm.get_tracer().write(&mut file, &analysis).unwrap();
|
||||
}
|
||||
if matches.is_present("profile") {
|
||||
println!("Profile is saved in profile.dot");
|
||||
let tracer = &vm.get_tracer();
|
||||
let dynamic_analysis = DynamicAnalysis::new(tracer, &analysis);
|
||||
let mut file = File::create("profile.dot").unwrap();
|
||||
analysis
|
||||
.visualize_graphically(&mut file, Some(&dynamic_analysis))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user