2021-12-03 09:00:31 -08:00
|
|
|
use {
|
|
|
|
clap::{crate_version, App, Arg},
|
|
|
|
serde::{Deserialize, Serialize},
|
|
|
|
serde_json::Result,
|
|
|
|
solana_bpf_loader_program::{
|
|
|
|
create_vm, serialization::serialize_parameters, syscalls::register_syscalls, BpfError,
|
|
|
|
ThisInstructionMeter,
|
|
|
|
},
|
|
|
|
solana_program_runtime::invoke_context::{prepare_mock_invoke_context, InvokeContext},
|
|
|
|
solana_rbpf::{
|
|
|
|
assembler::assemble,
|
|
|
|
elf::Executable,
|
|
|
|
static_analysis::Analysis,
|
|
|
|
verifier::check,
|
|
|
|
vm::{Config, DynamicAnalysis},
|
|
|
|
},
|
2021-12-17 05:01:12 -08:00
|
|
|
solana_sdk::{
|
|
|
|
account::AccountSharedData, bpf_loader, instruction::AccountMeta, pubkey::Pubkey,
|
|
|
|
},
|
2021-12-03 09:00:31 -08:00
|
|
|
std::{
|
|
|
|
fs::File,
|
|
|
|
io::{Read, Seek, SeekFrom},
|
|
|
|
path::Path,
|
|
|
|
},
|
|
|
|
time::Instant,
|
2021-05-28 04:28:30 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct Account {
|
2021-11-04 13:47:32 -07:00
|
|
|
key: Pubkey,
|
|
|
|
owner: Pubkey,
|
|
|
|
is_signer: bool,
|
|
|
|
is_writable: bool,
|
2021-05-28 04:28:30 -07:00
|
|
|
lamports: u64,
|
|
|
|
data: Vec<u8>,
|
|
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
struct Input {
|
|
|
|
accounts: Vec<Account>,
|
2021-11-04 13:47:32 -07:00
|
|
|
instruction_data: Vec<u8>,
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
|
|
|
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);
|
2021-11-04 13:47:32 -07:00
|
|
|
println!("instruction_data {:?}", &input.instruction_data);
|
2021-05-28 04:28:30 -07:00
|
|
|
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": [
|
|
|
|
{
|
2021-11-04 13:47:32 -07:00
|
|
|
"key": [
|
|
|
|
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
|
|
|
|
],
|
2021-05-28 04:28:30 -07:00
|
|
|
"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
|
2021-11-04 13:47:32 -07:00
|
|
|
],
|
|
|
|
"is_signer": false,
|
|
|
|
"is_writable": true,
|
|
|
|
"lamports": 1000,
|
|
|
|
"data": [0, 0, 0, 3]
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
|
|
|
],
|
2021-11-04 13:47:32 -07:00
|
|
|
"instruction_data": []
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
|
|
|
"##,
|
|
|
|
)
|
|
|
|
.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"])
|
2021-07-14 13:30:38 -07:00
|
|
|
.default_value("jit"),
|
2021-05-28 04:28:30 -07:00
|
|
|
)
|
|
|
|
.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"),
|
2021-12-03 06:45:25 -08:00
|
|
|
enable_symbol_and_section_labels: true,
|
2021-05-28 04:28:30 -07:00
|
|
|
..Config::default()
|
|
|
|
};
|
2021-11-04 13:47:32 -07:00
|
|
|
let loader_id = bpf_loader::id();
|
2021-12-17 05:01:12 -08:00
|
|
|
let mut transaction_accounts = vec![
|
2021-11-04 13:47:32 -07:00
|
|
|
(
|
|
|
|
loader_id,
|
2021-12-17 05:01:12 -08:00
|
|
|
AccountSharedData::new(0, 0, &solana_sdk::native_loader::id()),
|
2021-11-04 13:47:32 -07:00
|
|
|
),
|
|
|
|
(
|
|
|
|
Pubkey::new_unique(),
|
2021-12-17 05:01:12 -08:00
|
|
|
AccountSharedData::new(0, 0, &loader_id),
|
2021-11-04 13:47:32 -07:00
|
|
|
),
|
|
|
|
];
|
2021-12-17 05:01:12 -08:00
|
|
|
let mut instruction_accounts = Vec::new();
|
2021-11-04 13:47:32 -07:00
|
|
|
let instruction_data = match matches.value_of("input").unwrap().parse::<usize>() {
|
|
|
|
Ok(allocation_size) => {
|
2021-12-17 05:01:12 -08:00
|
|
|
let pubkey = Pubkey::new_unique();
|
|
|
|
transaction_accounts.push((
|
|
|
|
pubkey,
|
|
|
|
AccountSharedData::new(0, allocation_size, &Pubkey::new_unique()),
|
2021-11-04 13:47:32 -07:00
|
|
|
));
|
2021-12-17 05:01:12 -08:00
|
|
|
instruction_accounts.push(AccountMeta {
|
|
|
|
pubkey,
|
|
|
|
is_signer: false,
|
|
|
|
is_writable: true,
|
|
|
|
});
|
2021-11-04 13:47:32 -07:00
|
|
|
vec![]
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
|
|
|
Err(_) => {
|
2021-06-18 06:34:46 -07:00
|
|
|
let input = load_accounts(Path::new(matches.value_of("input").unwrap())).unwrap();
|
2021-12-17 05:01:12 -08:00
|
|
|
for account_info in input.accounts {
|
|
|
|
let mut account = AccountSharedData::new(
|
|
|
|
account_info.lamports,
|
|
|
|
account_info.data.len(),
|
|
|
|
&account_info.owner,
|
2021-11-04 13:47:32 -07:00
|
|
|
);
|
2021-12-17 05:01:12 -08:00
|
|
|
account.set_data(account_info.data);
|
|
|
|
instruction_accounts.push(AccountMeta {
|
|
|
|
pubkey: account_info.key,
|
|
|
|
is_signer: account_info.is_signer,
|
|
|
|
is_writable: account_info.is_writable,
|
|
|
|
});
|
|
|
|
transaction_accounts.push((account_info.key, account));
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
2021-11-04 13:47:32 -07:00
|
|
|
input.instruction_data
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
|
|
|
};
|
2021-12-24 07:17:55 -08:00
|
|
|
let preparation = prepare_mock_invoke_context(transaction_accounts, instruction_accounts);
|
2021-11-04 13:47:32 -07:00
|
|
|
let program_indices = [0, 1];
|
2021-12-24 07:17:55 -08:00
|
|
|
let mut invoke_context = InvokeContext::new_mock(&preparation.transaction_accounts, &[]);
|
2021-11-04 13:47:32 -07:00
|
|
|
invoke_context
|
2021-12-24 07:17:55 -08:00
|
|
|
.push(&preparation.instruction_accounts, &program_indices)
|
2021-11-04 13:47:32 -07:00
|
|
|
.unwrap();
|
|
|
|
let keyed_accounts = invoke_context.get_keyed_accounts().unwrap();
|
|
|
|
let (mut parameter_bytes, account_lengths) = serialize_parameters(
|
|
|
|
keyed_accounts[0].unsigned_key(),
|
|
|
|
keyed_accounts[1].unsigned_key(),
|
|
|
|
&keyed_accounts[2..],
|
|
|
|
&instruction_data,
|
|
|
|
)
|
|
|
|
.unwrap();
|
2021-05-28 04:28:30 -07:00
|
|
|
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();
|
2021-07-07 00:50:11 -07:00
|
|
|
let syscall_registry = register_syscalls(&mut invoke_context).unwrap();
|
2021-05-28 04:28:30 -07:00
|
|
|
let mut executable = if magic == [0x7f, 0x45, 0x4c, 0x46] {
|
2021-11-30 07:26:36 -08:00
|
|
|
Executable::<BpfError, ThisInstructionMeter>::from_elf(
|
2021-07-07 00:50:11 -07:00
|
|
|
&contents,
|
|
|
|
None,
|
|
|
|
config,
|
|
|
|
syscall_registry,
|
|
|
|
)
|
|
|
|
.map_err(|err| format!("Executable constructor failed: {:?}", err))
|
2021-05-28 04:28:30 -07:00
|
|
|
} else {
|
|
|
|
assemble::<BpfError, ThisInstructionMeter>(
|
|
|
|
std::str::from_utf8(contents.as_slice()).unwrap(),
|
|
|
|
None,
|
|
|
|
config,
|
2021-07-07 00:50:11 -07:00
|
|
|
syscall_registry,
|
2021-05-28 04:28:30 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
if matches.is_present("verify") {
|
2021-08-04 00:50:28 -07:00
|
|
|
let text_bytes = executable.get_text_bytes().1;
|
|
|
|
check(text_bytes, &config).unwrap();
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
2021-12-14 07:51:23 -08:00
|
|
|
Executable::<BpfError, ThisInstructionMeter>::jit_compile(&mut executable).unwrap();
|
2021-11-30 07:26:36 -08:00
|
|
|
let analysis = Analysis::from_executable(&executable);
|
2021-05-28 04:28:30 -07:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
2021-09-28 01:13:03 -07:00
|
|
|
let mut vm = create_vm(
|
2021-11-30 07:26:36 -08:00
|
|
|
&executable,
|
2021-11-04 13:47:32 -07:00
|
|
|
parameter_bytes.as_slice_mut(),
|
2021-09-28 01:13:03 -07:00
|
|
|
&mut invoke_context,
|
|
|
|
&account_lengths,
|
|
|
|
)
|
|
|
|
.unwrap();
|
2021-11-04 13:47:32 -07:00
|
|
|
println!("Program output:");
|
2021-07-13 19:00:56 -07:00
|
|
|
let start_time = Instant::now();
|
2021-05-28 04:28:30 -07:00
|
|
|
let result = if matches.value_of("use").unwrap() == "interpreter" {
|
|
|
|
vm.execute_program_interpreted(&mut instruction_meter)
|
|
|
|
} else {
|
|
|
|
vm.execute_program_jit(&mut instruction_meter)
|
|
|
|
};
|
2021-07-13 19:00:56 -07:00
|
|
|
let duration = Instant::now() - start_time;
|
2021-05-28 04:28:30 -07:00
|
|
|
println!("Result: {:?}", result);
|
|
|
|
println!("Instruction Count: {}", vm.get_total_instruction_count());
|
2021-07-13 19:00:56 -07:00
|
|
|
println!("Execution time: {} us", duration.whole_microseconds());
|
2021-05-28 04:28:30 -07:00
|
|
|
if matches.is_present("trace") {
|
|
|
|
println!("Trace is saved in trace.out");
|
|
|
|
let mut file = File::create("trace.out").unwrap();
|
2021-11-30 07:26:36 -08:00
|
|
|
let analysis = Analysis::from_executable(&executable);
|
2021-05-28 04:28:30 -07:00
|
|
|
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();
|
2021-06-18 06:34:46 -07:00
|
|
|
let dynamic_analysis = DynamicAnalysis::new(tracer, &analysis);
|
2021-05-28 04:28:30 -07:00
|
|
|
let mut file = File::create("profile.dot").unwrap();
|
|
|
|
analysis
|
|
|
|
.visualize_graphically(&mut file, Some(&dynamic_analysis))
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
}
|