Refactor - Syscalls in RBPF CLI (#31109)

* Makes members of SyscallContext public.
Removes check_aligned and check_size from SyscallContext.

* Replaces InvokeContext::set_syscall_context() in tests with mock_create_vm!().

* Passes SyscallContext directly to InvokeContext::set_syscall_context().

* Merges TraceLogStackFrame into SyscallContext.

* Removes the create_vm!() macro.

* Moves BpfAllocator from bpf_loader into program_runtime.

* Frees BpfAllocator from Rc<RefCell<>>.

* Removes unused code from BpfAllocator.

* Consume CUs for heap before doing the allocation.

* Exposes syscalls in rbpf-cli.

* Adds debugging_features parameter to load_program_from_bytes() and load_program_from_account().

* Removes test_bpf_loader_non_terminating_program() as that is already tested in RBPF.

* Moves stack and heap allocation back onto the program runtime stack.
This commit is contained in:
Alexander Meißner 2023-04-14 15:20:08 +02:00 committed by GitHub
parent 592b3ce17e
commit 8eb31f6cfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 265 additions and 442 deletions

View File

@ -11,12 +11,13 @@ use {
timings::{ExecuteDetailsTimings, ExecuteTimings},
},
solana_measure::measure::Measure,
solana_rbpf::vm::ContextObject,
solana_rbpf::{ebpf::MM_HEAP_START, vm::ContextObject},
solana_sdk::{
account::{AccountSharedData, ReadableAccount},
bpf_loader_deprecated,
feature_set::{
enable_early_verification_of_account_modifications, native_programs_consume_cu,
FeatureSet,
check_slice_translation_size, enable_early_verification_of_account_modifications,
native_programs_consume_cu, FeatureSet,
},
hash::Hash,
instruction::{AccountMeta, InstructionError},
@ -87,15 +88,16 @@ impl std::fmt::Debug for BuiltinProgram {
impl<'a> ContextObject for InvokeContext<'a> {
fn trace(&mut self, state: [u64; 12]) {
self.trace_log_stack
self.syscall_context
.last_mut()
.expect("Inconsistent trace log stack")
.unwrap()
.as_mut()
.unwrap()
.trace_log
.push(state);
}
fn consume(&mut self, amount: u64) {
self.log_consumed_bpf_units(amount);
// 1 to 1 instruction to compute unit mapping
// ignore overflow, Ebpf will bail if exceeded
let mut compute_meter = self.compute_meter.borrow_mut();
@ -107,32 +109,46 @@ impl<'a> ContextObject for InvokeContext<'a> {
}
}
/// Based loosely on the unstable std::alloc::Alloc trait
pub trait Alloc {
fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr>;
fn dealloc(&mut self, addr: u64, layout: Layout);
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct AllocErr;
impl fmt::Display for AllocErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Error: Memory allocation failed")
}
}
struct SyscallContext {
check_aligned: bool,
check_size: bool,
orig_account_lengths: Vec<usize>,
allocator: Rc<RefCell<dyn Alloc>>,
pub struct BpfAllocator {
len: u64,
pos: u64,
}
#[derive(Default)]
pub struct TraceLogStackFrame {
impl BpfAllocator {
pub fn new(len: u64) -> Self {
Self { len, pos: 0 }
}
pub fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
if self
.pos
.saturating_add(bytes_to_align)
.saturating_add(layout.size() as u64)
<= self.len
{
self.pos = self.pos.saturating_add(bytes_to_align);
let addr = MM_HEAP_START.saturating_add(self.pos);
self.pos = self.pos.saturating_add(layout.size() as u64);
Ok(addr)
} else {
Err(AllocErr)
}
}
}
pub struct SyscallContext {
pub allocator: BpfAllocator,
pub orig_account_lengths: Vec<usize>,
pub trace_log: Vec<[u64; 12]>,
pub consumed_bpf_units: RefCell<Vec<(usize, u64)>>,
}
pub struct InvokeContext<'a> {
@ -141,7 +157,6 @@ pub struct InvokeContext<'a> {
pre_accounts: Vec<PreAccount>,
builtin_programs: &'a [BuiltinProgram],
sysvar_cache: &'a SysvarCache,
pub trace_log_stack: Vec<TraceLogStackFrame>,
log_collector: Option<Rc<RefCell<LogCollector>>>,
compute_budget: ComputeBudget,
current_compute_budget: ComputeBudget,
@ -152,8 +167,7 @@ pub struct InvokeContext<'a> {
pub timings: ExecuteDetailsTimings,
pub blockhash: Hash,
pub lamports_per_signature: u64,
syscall_context: Vec<Option<SyscallContext>>,
pub enable_instruction_tracing: bool,
pub syscall_context: Vec<Option<SyscallContext>>,
}
impl<'a> InvokeContext<'a> {
@ -177,7 +191,6 @@ impl<'a> InvokeContext<'a> {
pre_accounts: Vec::new(),
builtin_programs,
sysvar_cache,
trace_log_stack: vec![TraceLogStackFrame::default()],
log_collector,
current_compute_budget: compute_budget,
compute_budget,
@ -189,7 +202,6 @@ impl<'a> InvokeContext<'a> {
blockhash,
lamports_per_signature,
syscall_context: Vec::new(),
enable_instruction_tracing: false,
}
}
@ -273,14 +285,12 @@ impl<'a> InvokeContext<'a> {
}
}
self.trace_log_stack.push(TraceLogStackFrame::default());
self.syscall_context.push(None);
self.transaction_context.push()
}
/// Pop a stack frame from the invocation stack
pub fn pop(&mut self) -> Result<(), InstructionError> {
self.trace_log_stack.pop();
self.syscall_context.pop();
self.transaction_context.pop()
}
@ -764,7 +774,6 @@ impl<'a> InvokeContext<'a> {
/// Consume compute units
pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
self.log_consumed_bpf_units(amount);
let mut compute_meter = self.compute_meter.borrow_mut();
let exceeded = *compute_meter < amount;
*compute_meter = compute_meter.saturating_sub(amount);
@ -796,75 +805,53 @@ impl<'a> InvokeContext<'a> {
self.sysvar_cache
}
// Set this instruction syscall context
pub fn set_syscall_context(
&mut self,
check_aligned: bool,
check_size: bool,
orig_account_lengths: Vec<usize>,
allocator: Rc<RefCell<dyn Alloc>>,
) -> Result<(), InstructionError> {
*self
.syscall_context
.last_mut()
.ok_or(InstructionError::CallDepth)? = Some(SyscallContext {
check_aligned,
check_size,
orig_account_lengths,
allocator,
});
Ok(())
}
// Should alignment be enforced during user pointer translation
pub fn get_check_aligned(&self) -> bool {
self.syscall_context
.last()
.and_then(|context| context.as_ref())
.map(|context| context.check_aligned)
self.transaction_context
.get_current_instruction_context()
.and_then(|instruction_context| {
let program_account =
instruction_context.try_borrow_last_program_account(self.transaction_context);
debug_assert!(program_account.is_ok());
program_account
})
.map(|program_account| *program_account.get_owner() != bpf_loader_deprecated::id())
.unwrap_or(true)
}
// Set should type size be checked during user pointer translation
pub fn get_check_size(&self) -> bool {
self.syscall_context
.last()
.and_then(|context| context.as_ref())
.map(|context| context.check_size)
.unwrap_or(true)
self.feature_set
.is_active(&check_slice_translation_size::id())
}
/// Get the original account lengths
pub fn get_orig_account_lengths(&self) -> Result<&[usize], InstructionError> {
// Set this instruction syscall context
pub fn set_syscall_context(
&mut self,
syscall_context: SyscallContext,
) -> Result<(), InstructionError> {
*self
.syscall_context
.last_mut()
.ok_or(InstructionError::CallDepth)? = Some(syscall_context);
Ok(())
}
// Get this instruction's SyscallContext
pub fn get_syscall_context(&self) -> Result<&SyscallContext, InstructionError> {
self.syscall_context
.last()
.and_then(|context| context.as_ref())
.map(|context| context.orig_account_lengths.as_slice())
.and_then(|syscall_context| syscall_context.as_ref())
.ok_or(InstructionError::CallDepth)
}
// Get this instruction's memory allocator
pub fn get_allocator(&self) -> Result<Rc<RefCell<dyn Alloc>>, InstructionError> {
// Get this instruction's SyscallContext
pub fn get_syscall_context_mut(&mut self) -> Result<&mut SyscallContext, InstructionError> {
self.syscall_context
.last()
.and_then(|context| context.as_ref())
.map(|context| context.allocator.clone())
.last_mut()
.and_then(|syscall_context| syscall_context.as_mut())
.ok_or(InstructionError::CallDepth)
}
fn log_consumed_bpf_units(&self, amount: u64) {
if self.enable_instruction_tracing && amount != 0 {
let trace_log_stack_frame = self
.trace_log_stack
.last()
.expect("Inconsistent trace log stack");
trace_log_stack_frame.consumed_bpf_units.borrow_mut().push((
trace_log_stack_frame.trace_log.len().saturating_sub(1),
amount,
));
}
}
}
#[macro_export]

View File

@ -1,55 +0,0 @@
#![allow(clippy::integer_arithmetic)]
use {
solana_program_runtime::invoke_context::{Alloc, AllocErr},
solana_rbpf::{aligned_memory::AlignedMemory, ebpf::HOST_ALIGN},
std::alloc::Layout,
};
#[derive(Debug)]
pub struct BpfAllocator {
#[allow(dead_code)]
heap: AlignedMemory<HOST_ALIGN>,
start: u64,
len: u64,
pos: u64,
}
impl BpfAllocator {
pub fn new(heap: AlignedMemory<HOST_ALIGN>, virtual_address: u64) -> Self {
let len = heap.len() as u64;
Self {
heap,
start: virtual_address,
len,
pos: 0,
}
}
pub fn heap_mut(&mut self) -> &mut AlignedMemory<HOST_ALIGN> {
&mut self.heap
}
}
impl Alloc for BpfAllocator {
fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr> {
let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
if self
.pos
.saturating_add(layout.size() as u64)
.saturating_add(bytes_to_align)
<= self.len
{
self.pos += bytes_to_align;
let addr = self.start + self.pos;
self.pos += layout.size() as u64;
Ok(addr)
} else {
Err(AllocErr)
}
}
fn dealloc(&mut self, _addr: u64, _layout: Layout) {
// It's a bump allocator, free not supported
}
}

View File

@ -1,7 +1,6 @@
#![deny(clippy::integer_arithmetic)]
#![deny(clippy::indexing_slicing)]
pub mod allocator_bump;
pub mod deprecated;
pub mod serialization;
pub mod syscalls;
@ -10,13 +9,12 @@ pub mod upgradeable_with_jit;
pub mod with_jit;
use {
crate::allocator_bump::BpfAllocator,
solana_measure::measure::Measure,
solana_program_runtime::{
compute_budget::ComputeBudget,
executor_cache::TransactionExecutorCache,
ic_logger_msg, ic_msg,
invoke_context::InvokeContext,
invoke_context::{BpfAllocator, InvokeContext, SyscallContext},
loaded_programs::{LoadProgramMetrics, LoadedProgram, LoadedProgramType},
log_collector::LogCollector,
stable_log,
@ -37,11 +35,10 @@ use {
entrypoint::SUCCESS,
feature_set::{
cap_accounts_data_allocations_per_transaction, cap_bpf_program_instruction_accounts,
check_slice_translation_size, delay_visibility_of_program_deployment,
disable_deploy_of_alloc_free_syscall, enable_bpf_loader_extend_program_ix,
enable_bpf_loader_set_authority_checked_ix, enable_program_redeployment_cooldown,
limit_max_instruction_trace_length, native_programs_consume_cu,
remove_bpf_loader_incorrect_program_id, round_up_heap_size, FeatureSet,
delay_visibility_of_program_deployment, disable_deploy_of_alloc_free_syscall,
enable_bpf_loader_extend_program_ix, enable_bpf_loader_set_authority_checked_ix,
enable_program_redeployment_cooldown, limit_max_instruction_trace_length,
native_programs_consume_cu, remove_bpf_loader_incorrect_program_id, FeatureSet,
},
instruction::{AccountMeta, InstructionError},
loader_instruction::LoaderInstruction,
@ -84,6 +81,7 @@ pub fn load_program_from_bytes(
deployment_slot: Slot,
use_jit: bool,
reject_deployment_of_broken_elfs: bool,
debugging_features: bool,
) -> Result<LoadedProgram, InstructionError> {
let mut register_syscalls_time = Measure::start("register_syscalls_time");
let disable_deploy_of_alloc_free_syscall = reject_deployment_of_broken_elfs
@ -93,7 +91,7 @@ pub fn load_program_from_bytes(
compute_budget,
reject_deployment_of_broken_elfs,
disable_deploy_of_alloc_free_syscall,
false,
debugging_features,
)
.map_err(|e| {
ic_logger_msg!(log_collector, "Failed to register syscalls: {}", e);
@ -163,6 +161,7 @@ pub fn load_program_from_account(
program: &BorrowedAccount,
programdata: &BorrowedAccount,
use_jit: bool,
debugging_features: bool,
) -> Result<(Arc<LoadedProgram>, Option<LoadProgramMetrics>), InstructionError> {
if !check_loader_id(program.get_owner()) {
ic_logger_msg!(
@ -211,6 +210,7 @@ pub fn load_program_from_account(
deployment_slot,
use_jit,
false, /* reject_deployment_of_broken_elfs */
debugging_features,
)?);
if let Some(mut tx_executor_cache) = tx_executor_cache {
tx_executor_cache.set(
@ -242,7 +242,8 @@ macro_rules! deploy_program {
$account_size,
$slot,
$use_jit,
true,
true, /* reject_deployment_of_broken_elfs */
false, /* debugging_features */
)?;
if let Some(old_entry) = $invoke_context.tx_executor_cache.borrow().get(&$program_id) {
let usage_counter = old_entry.usage_counter.load(Ordering::Relaxed);
@ -292,7 +293,8 @@ fn check_loader_id(id: &Pubkey) -> bool {
|| bpf_loader_upgradeable::check_id(id)
}
fn calculate_heap_cost(heap_size: u64, heap_cost: u64, enable_rounding_fix: bool) -> u64 {
/// Only used in macro, do not use directly!
pub fn calculate_heap_cost(heap_size: u64, heap_cost: u64, enable_rounding_fix: bool) -> u64 {
const KIBIBYTE: u64 = 1024;
const PAGE_SIZE_KB: u64 = 32;
let mut rounded_heap_size = heap_size;
@ -306,83 +308,96 @@ fn calculate_heap_cost(heap_size: u64, heap_cost: u64, enable_rounding_fix: bool
.saturating_mul(heap_cost)
}
/// Create the SBF virtual machine
pub fn create_ebpf_vm<'a, 'b>(
/// Only used in macro, do not use directly!
pub fn create_vm<'a, 'b>(
program: &'a VerifiedExecutable<RequisiteVerifier, InvokeContext<'b>>,
stack: &'a mut AlignedMemory<HOST_ALIGN>,
heap: AlignedMemory<HOST_ALIGN>,
regions: Vec<MemoryRegion>,
orig_account_lengths: Vec<usize>,
invoke_context: &'a mut InvokeContext<'b>,
stack: &mut AlignedMemory<HOST_ALIGN>,
heap: &mut AlignedMemory<HOST_ALIGN>,
) -> Result<EbpfVm<'a, RequisiteVerifier, InvokeContext<'b>>, Box<dyn std::error::Error>> {
let round_up_heap_size = invoke_context
.feature_set
.is_active(&round_up_heap_size::id());
let heap_cost_result = invoke_context.consume_checked(calculate_heap_cost(
heap.len() as u64,
invoke_context.get_compute_budget().heap_cost,
round_up_heap_size,
));
if round_up_heap_size {
heap_cost_result?;
}
let check_aligned = bpf_loader_deprecated::id()
!= invoke_context
.transaction_context
.get_current_instruction_context()
.and_then(|instruction_context| {
instruction_context
.try_borrow_last_program_account(invoke_context.transaction_context)
})
.map(|program_account| *program_account.get_owner())?;
let check_size = invoke_context
.feature_set
.is_active(&check_slice_translation_size::id());
let allocator = Rc::new(RefCell::new(BpfAllocator::new(heap, MM_HEAP_START)));
invoke_context.set_syscall_context(
check_aligned,
check_size,
let stack_size = stack.len();
let heap_size = heap.len();
let memory_mapping =
create_memory_mapping(program.get_executable(), stack, heap, regions, None)?;
invoke_context.set_syscall_context(SyscallContext {
allocator: BpfAllocator::new(heap_size as u64),
orig_account_lengths,
allocator.clone(),
)?;
let stack_len = stack.len();
let memory_mapping = create_memory_mapping(
program.get_executable(),
stack,
allocator.borrow_mut().heap_mut(),
regions,
None,
)?;
trace_log: Vec::new(),
})?;
Ok(EbpfVm::new(
program,
invoke_context,
memory_mapping,
stack_len,
stack_size,
))
}
/// Create the SBF virtual machine
#[macro_export]
macro_rules! create_vm {
($vm_name:ident, $executable:expr, $stack:ident, $heap:ident, $additional_regions:expr, $orig_account_lengths:expr, $invoke_context:expr) => {
let mut $stack = solana_rbpf::aligned_memory::AlignedMemory::<
{ solana_rbpf::ebpf::HOST_ALIGN },
>::zero_filled($executable.get_executable().get_config().stack_size());
// this is needed if the caller passes "&mut invoke_context" to the
// macro. The lint complains that (&mut invoke_context).get_compute_budget()
// does an unnecessary mutable borrow
#[allow(clippy::unnecessary_mut_passed)]
let heap_size = $invoke_context
($vm:ident, $program:expr, $regions:expr, $orig_account_lengths:expr, $invoke_context:expr $(,)?) => {
let invoke_context = &*$invoke_context;
let stack_size = $program.get_executable().get_config().stack_size();
let heap_size = invoke_context
.get_compute_budget()
.heap_size
.unwrap_or(solana_sdk::entrypoint::HEAP_LENGTH);
let $heap = solana_rbpf::aligned_memory::AlignedMemory::<{ solana_rbpf::ebpf::HOST_ALIGN }>::zero_filled(heap_size);
let round_up_heap_size = invoke_context
.feature_set
.is_active(&solana_sdk::feature_set::round_up_heap_size::id());
let mut heap_cost_result = invoke_context.consume_checked($crate::calculate_heap_cost(
heap_size as u64,
invoke_context.get_compute_budget().heap_cost,
round_up_heap_size,
));
if !round_up_heap_size {
heap_cost_result = Ok(());
}
let mut allocations = None;
let $vm = heap_cost_result.and_then(|_| {
let mut stack = solana_rbpf::aligned_memory::AlignedMemory::<
{ solana_rbpf::ebpf::HOST_ALIGN },
>::zero_filled(stack_size);
let mut heap = solana_rbpf::aligned_memory::AlignedMemory::<
{ solana_rbpf::ebpf::HOST_ALIGN },
>::zero_filled(heap_size);
let vm = $crate::create_vm(
$program,
$regions,
$orig_account_lengths,
$invoke_context,
&mut stack,
&mut heap,
);
allocations = Some((stack, heap));
vm
});
};
}
let $vm_name = create_ebpf_vm(
$executable,
&mut $stack,
$heap,
#[macro_export]
macro_rules! mock_create_vm {
($vm:ident, $additional_regions:expr, $orig_account_lengths:expr, $invoke_context:expr $(,)?) => {
let loader = std::sync::Arc::new(BuiltInProgram::new_loader(
solana_rbpf::vm::Config::default(),
));
let function_registry = solana_rbpf::vm::FunctionRegistry::default();
let executable = solana_rbpf::elf::Executable::<InvokeContext>::from_text_bytes(
&[0x95, 0, 0, 0, 0, 0, 0, 0],
loader,
function_registry,
)
.unwrap();
let verified_executable = solana_rbpf::vm::VerifiedExecutable::<
solana_rbpf::verifier::RequisiteVerifier,
InvokeContext,
>::from_executable(executable)
.unwrap();
$crate::create_vm!(
$vm,
&verified_executable,
$additional_regions,
$orig_account_lengths,
$invoke_context,
@ -409,7 +424,7 @@ fn create_memory_mapping<'a, 'b, C: ContextObject>(
0
},
),
MemoryRegion::new_writable(heap.as_slice_mut(), ebpf::MM_HEAP_START),
MemoryRegion::new_writable(heap.as_slice_mut(), MM_HEAP_START),
]
.into_iter()
.chain(additional_regions.into_iter())
@ -580,6 +595,7 @@ fn process_instruction_inner(
&program_account,
programdata_account.as_ref().unwrap_or(&program_account),
use_jit,
false, /* debugging_features */
)?;
drop(program_account);
drop(programdata_account);
@ -1553,11 +1569,9 @@ fn execute<'a, 'b: 'a>(
executable,
)
},
stack,
heap,
regions,
account_lengths,
invoke_context
invoke_context,
);
let mut vm = match vm {
Ok(info) => info,
@ -1620,7 +1634,7 @@ fn execute<'a, 'b: 'a>(
.transaction_context
.get_current_instruction_context()?,
parameter_bytes,
invoke_context.get_orig_account_lengths()?,
&invoke_context.get_syscall_context()?.orig_account_lengths,
)
}
@ -1652,10 +1666,8 @@ mod tests {
invoke_context::mock_process_instruction, with_mock_invoke_context,
},
solana_rbpf::{
ebpf::MM_INPUT_START,
elf::Executable,
verifier::{Verifier, VerifierError},
vm::{BuiltInProgram, Config, ContextObject, FunctionRegistry},
vm::{Config, ContextObject, FunctionRegistry},
},
solana_sdk::{
account::{
@ -1729,54 +1741,6 @@ mod tests {
}
}
#[test]
#[should_panic(expected = "ExceededMaxInstructions(31)")]
fn test_bpf_loader_non_terminating_program() {
#[rustfmt::skip]
let program = &[
0x07, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // r6 + 1
0x05, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, // goto -2
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let mut input_mem = [0x00];
let bpf_functions = std::collections::BTreeMap::<u32, (usize, String)>::new();
let executable = Executable::<TestContextObject>::from_text_bytes(
program,
Arc::new(BuiltInProgram::new_loader(Config::default())),
bpf_functions,
)
.unwrap();
let verified_executable =
VerifiedExecutable::<TautologyVerifier, TestContextObject>::from_executable(executable)
.unwrap();
let input_region = MemoryRegion::new_writable(&mut input_mem, MM_INPUT_START);
let mut context_object = TestContextObject { remaining: 10 };
let mut stack = AlignedMemory::zero_filled(
verified_executable
.get_executable()
.get_config()
.stack_size(),
);
let mut heap = AlignedMemory::with_capacity(0);
let stack_len = stack.len();
let memory_mapping = create_memory_mapping(
verified_executable.get_executable(),
&mut stack,
&mut heap,
vec![input_region],
None,
)
.unwrap();
let mut vm = EbpfVm::new(
&verified_executable,
&mut context_object,
memory_mapping,
stack_len,
);
vm.execute_program(true).1.unwrap();
}
#[test]
#[should_panic(expected = "LDDWCannotBeLast")]
fn test_bpf_loader_check_load_dw() {

View File

@ -675,7 +675,10 @@ where
// unwrapping here is fine: we're in a syscall and the method below fails
// only outside syscalls
let orig_data_lens = invoke_context.get_orig_account_lengths().unwrap();
let orig_data_lens = &invoke_context
.get_syscall_context()
.unwrap()
.orig_account_lengths;
for (instruction_account_index, instruction_account) in instruction_accounts.iter().enumerate()
{
@ -1083,12 +1086,9 @@ fn update_caller_account(
mod tests {
use {
super::*,
crate::allocator_bump::BpfAllocator,
crate::mock_create_vm,
solana_program_runtime::with_mock_invoke_context,
solana_rbpf::{
aligned_memory::AlignedMemory, ebpf::MM_INPUT_START, memory_region::MemoryRegion,
vm::Config,
},
solana_rbpf::{ebpf::MM_INPUT_START, memory_region::MemoryRegion, vm::Config},
solana_sdk::{
account::{Account, AccountSharedData},
clock::Epoch,
@ -1502,17 +1502,12 @@ mod tests {
&[0],
&[1, 1]
);
invoke_context
.set_syscall_context(
true,
true,
vec![original_data_len],
Rc::new(RefCell::new(BpfAllocator::new(
AlignedMemory::with_capacity(0),
0,
))),
)
.unwrap();
mock_create_vm!(
_vm,
Vec::new(),
vec![original_data_len],
&mut invoke_context
);
let vm_addr = MM_INPUT_START;
let (_mem, region) = MockAccountInfo::new(key, &account).into_region(vm_addr);

View File

@ -530,11 +530,6 @@ declare_syscall!(
_arg5: u64,
_memory_mapping: &mut MemoryMapping,
) -> Result<u64, Error> {
let allocator = invoke_context.get_allocator()?;
let mut allocator = allocator
.try_borrow_mut()
.map_err(|_| SyscallError::InvokeContextBorrowFailed)?;
let align = if invoke_context.get_check_aligned() {
BPF_ALIGN_OF_U128
} else {
@ -546,13 +541,14 @@ declare_syscall!(
return Ok(0);
}
};
let allocator = &mut invoke_context.get_syscall_context_mut()?.allocator;
if free_addr == 0 {
match allocator.alloc(layout) {
Ok(addr) => Ok(addr),
Err(_) => Ok(0),
}
} else {
allocator.dealloc(free_addr, layout);
// Unimplemented
Ok(0)
}
}
@ -1802,12 +1798,10 @@ mod tests {
use solana_sdk::sysvar::fees::Fees;
use {
super::*,
crate::BpfAllocator,
crate::mock_create_vm,
core::slice,
solana_program_runtime::{invoke_context::InvokeContext, with_mock_invoke_context},
solana_rbpf::{
aligned_memory::AlignedMemory,
ebpf::{self, HOST_ALIGN},
error::EbpfError,
memory_region::MemoryRegion,
vm::{BuiltInFunction, Config},
@ -2289,63 +2283,46 @@ mod tests {
#[test]
fn test_syscall_sol_alloc_free() {
let config = Config::default();
// large alloc
{
prepare_mockup!(invoke_context, program_id, bpf_loader::id());
let mut heap = AlignedMemory::<HOST_ALIGN>::zero_filled(100);
let mut memory_mapping = MemoryMapping::new(
vec![
MemoryRegion::new_readonly(&[], ebpf::MM_PROGRAM_START),
MemoryRegion::new_writable_gapped(&mut [], ebpf::MM_STACK_START, 4096),
MemoryRegion::new_writable(heap.as_slice_mut(), ebpf::MM_HEAP_START),
MemoryRegion::new_writable(&mut [], ebpf::MM_INPUT_START),
],
&config,
)
.unwrap();
invoke_context
.set_syscall_context(
true,
true,
vec![],
Rc::new(RefCell::new(BpfAllocator::new(heap, ebpf::MM_HEAP_START))),
)
.unwrap();
mock_create_vm!(vm, Vec::new(), Vec::new(), &mut invoke_context);
let mut vm = vm.unwrap();
let invoke_context = &mut vm.env.context_object_pointer;
let memory_mapping = &mut vm.env.memory_mapping;
let mut result = ProgramResult::Ok(0);
SyscallAllocFree::call(
&mut invoke_context,
100,
invoke_context,
solana_sdk::entrypoint::HEAP_LENGTH as u64,
0,
0,
0,
0,
&mut memory_mapping,
memory_mapping,
&mut result,
);
assert_ne!(result.unwrap(), 0);
let mut result = ProgramResult::Ok(0);
SyscallAllocFree::call(
&mut invoke_context,
100,
invoke_context,
solana_sdk::entrypoint::HEAP_LENGTH as u64,
0,
0,
0,
0,
&mut memory_mapping,
memory_mapping,
&mut result,
);
assert_eq!(result.unwrap(), 0);
let mut result = ProgramResult::Ok(0);
SyscallAllocFree::call(
&mut invoke_context,
invoke_context,
u64::MAX,
0,
0,
0,
0,
&mut memory_mapping,
memory_mapping,
&mut result,
);
assert_eq!(result.unwrap(), 0);
@ -2354,48 +2331,25 @@ mod tests {
// many small unaligned allocs
{
prepare_mockup!(invoke_context, program_id, bpf_loader::id());
let mut heap = AlignedMemory::<HOST_ALIGN>::zero_filled(100);
let mut memory_mapping = MemoryMapping::new(
vec![
MemoryRegion::new_readonly(&[], ebpf::MM_PROGRAM_START),
MemoryRegion::new_writable_gapped(&mut [], ebpf::MM_STACK_START, 4096),
MemoryRegion::new_writable(heap.as_slice_mut(), ebpf::MM_HEAP_START),
MemoryRegion::new_writable(&mut [], ebpf::MM_INPUT_START),
],
&config,
)
.unwrap();
invoke_context
.set_syscall_context(
false,
true,
vec![],
Rc::new(RefCell::new(BpfAllocator::new(heap, ebpf::MM_HEAP_START))),
)
.unwrap();
invoke_context.feature_set = Arc::new(FeatureSet::default());
mock_create_vm!(vm, Vec::new(), Vec::new(), &mut invoke_context);
let mut vm = vm.unwrap();
let invoke_context = &mut vm.env.context_object_pointer;
let memory_mapping = &mut vm.env.memory_mapping;
for _ in 0..100 {
let mut result = ProgramResult::Ok(0);
SyscallAllocFree::call(
&mut invoke_context,
1,
0,
0,
0,
0,
&mut memory_mapping,
&mut result,
);
SyscallAllocFree::call(invoke_context, 1, 0, 0, 0, 0, memory_mapping, &mut result);
assert_ne!(result.unwrap(), 0);
}
let mut result = ProgramResult::Ok(0);
SyscallAllocFree::call(
&mut invoke_context,
100,
invoke_context,
solana_sdk::entrypoint::HEAP_LENGTH as u64,
0,
0,
0,
0,
&mut memory_mapping,
memory_mapping,
&mut result,
);
assert_eq!(result.unwrap(), 0);
@ -2404,48 +2358,24 @@ mod tests {
// many small aligned allocs
{
prepare_mockup!(invoke_context, program_id, bpf_loader::id());
let mut heap = AlignedMemory::<HOST_ALIGN>::zero_filled(100);
let mut memory_mapping = MemoryMapping::new(
vec![
MemoryRegion::new_readonly(&[], ebpf::MM_PROGRAM_START),
MemoryRegion::new_writable_gapped(&mut [], ebpf::MM_STACK_START, 4096),
MemoryRegion::new_writable(heap.as_slice_mut(), ebpf::MM_HEAP_START),
MemoryRegion::new_writable(&mut [], ebpf::MM_INPUT_START),
],
&config,
)
.unwrap();
invoke_context
.set_syscall_context(
true,
true,
vec![],
Rc::new(RefCell::new(BpfAllocator::new(heap, ebpf::MM_HEAP_START))),
)
.unwrap();
mock_create_vm!(vm, Vec::new(), Vec::new(), &mut invoke_context);
let mut vm = vm.unwrap();
let invoke_context = &mut vm.env.context_object_pointer;
let memory_mapping = &mut vm.env.memory_mapping;
for _ in 0..12 {
let mut result = ProgramResult::Ok(0);
SyscallAllocFree::call(
&mut invoke_context,
1,
0,
0,
0,
0,
&mut memory_mapping,
&mut result,
);
SyscallAllocFree::call(invoke_context, 1, 0, 0, 0, 0, memory_mapping, &mut result);
assert_ne!(result.unwrap(), 0);
}
let mut result = ProgramResult::Ok(0);
SyscallAllocFree::call(
&mut invoke_context,
100,
invoke_context,
solana_sdk::entrypoint::HEAP_LENGTH as u64,
0,
0,
0,
0,
&mut memory_mapping,
memory_mapping,
&mut result,
);
assert_eq!(result.unwrap(), 0);
@ -2455,35 +2385,19 @@ mod tests {
fn aligned<T>() {
prepare_mockup!(invoke_context, program_id, bpf_loader::id());
let mut heap = AlignedMemory::<HOST_ALIGN>::zero_filled(100);
let config = Config::default();
let mut memory_mapping = MemoryMapping::new(
vec![
MemoryRegion::new_readonly(&[], ebpf::MM_PROGRAM_START),
MemoryRegion::new_writable_gapped(&mut [], ebpf::MM_STACK_START, 4096),
MemoryRegion::new_writable(heap.as_slice_mut(), ebpf::MM_HEAP_START),
MemoryRegion::new_writable(&mut [], ebpf::MM_INPUT_START),
],
&config,
)
.unwrap();
invoke_context
.set_syscall_context(
true,
true,
vec![],
Rc::new(RefCell::new(BpfAllocator::new(heap, ebpf::MM_HEAP_START))),
)
.unwrap();
mock_create_vm!(vm, Vec::new(), Vec::new(), &mut invoke_context);
let mut vm = vm.unwrap();
let invoke_context = &mut vm.env.context_object_pointer;
let memory_mapping = &mut vm.env.memory_mapping;
let mut result = ProgramResult::Ok(0);
SyscallAllocFree::call(
&mut invoke_context,
invoke_context,
size_of::<T>() as u64,
0,
0,
0,
0,
&mut memory_mapping,
memory_mapping,
&mut result,
);
let address = result.unwrap();

View File

@ -455,7 +455,7 @@ pub fn process_instruction_deploy(
invoke_context.get_log_collector(),
buffer,
use_jit,
false,
false, /* debugging_features */
)?;
load_program_metrics.submit_datapoint(&mut invoke_context.timings);
if let Some(mut source_program) = source_program {
@ -588,7 +588,7 @@ pub fn process_instruction(
invoke_context.get_log_collector(),
&program,
use_jit,
false,
false, /* debugging_features */
)?;
load_program_metrics.submit_datapoint(&mut invoke_context.timings);
get_or_create_executor_time.stop();

View File

@ -12,7 +12,7 @@ extern crate solana_bpf_loader_program;
use {
byteorder::{ByteOrder, LittleEndian, WriteBytesExt},
solana_bpf_loader_program::{
create_ebpf_vm, create_vm, serialization::serialize_parameters, syscalls::create_loader,
create_vm, serialization::serialize_parameters, syscalls::create_loader,
},
solana_measure::measure::Measure,
solana_program_runtime::{compute_budget::ComputeBudget, invoke_context::InvokeContext},
@ -131,11 +131,9 @@ fn bench_program_alu(bencher: &mut Bencher) {
create_vm!(
vm,
&verified_executable,
stack,
heap,
vec![MemoryRegion::new_writable(&mut inner_iter, MM_INPUT_START)],
vec![],
&mut invoke_context
&mut invoke_context,
);
let mut vm = vm.unwrap();
@ -258,13 +256,11 @@ fn bench_create_vm(bencher: &mut Bencher) {
create_vm!(
vm,
&verified_executable,
stack,
heap,
clone_regions(&regions),
account_lengths.clone(),
&mut invoke_context
&mut invoke_context,
);
let _ = vm.unwrap();
vm.unwrap();
});
}
@ -303,11 +299,9 @@ fn bench_instruction_count_tuner(_bencher: &mut Bencher) {
create_vm!(
vm,
&verified_executable,
stack,
heap,
regions,
account_lengths,
&mut invoke_context
&mut invoke_context,
);
let mut vm = vm.unwrap();

View File

@ -3,9 +3,14 @@ use {
serde::{Deserialize, Serialize},
serde_json::Result,
solana_bpf_loader_program::{
create_ebpf_vm, create_vm, serialization::serialize_parameters, syscalls::create_loader,
create_vm, load_program_from_bytes, serialization::serialize_parameters,
syscalls::create_loader,
},
solana_program_runtime::{
invoke_context::InvokeContext,
loaded_programs::{LoadProgramMetrics, LoadedProgramType},
with_mock_invoke_context,
},
solana_program_runtime::{invoke_context::InvokeContext, with_mock_invoke_context},
solana_rbpf::{
assembler::assemble, elf::Executable, static_analysis::Analysis,
verifier::RequisiteVerifier, vm::VerifiedExecutable,
@ -14,6 +19,7 @@ use {
account::AccountSharedData,
bpf_loader,
pubkey::Pubkey,
slot_history::Slot,
transaction_context::{IndexOfAccount, InstructionAccount},
},
std::{
@ -216,7 +222,6 @@ before execting it in the virtual machine.",
}
};
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
invoke_context.enable_instruction_tracing = true;
invoke_context
.transaction_context
.get_next_instruction_context()
@ -240,28 +245,45 @@ before execting it in the virtual machine.",
file.rewind().unwrap();
let mut contents = Vec::new();
file.read_to_end(&mut contents).unwrap();
let loader = create_loader(
&invoke_context.feature_set,
&ComputeBudget::default(),
true,
true,
true,
)
.unwrap();
let executable = if magic == [0x7f, 0x45, 0x4c, 0x46] {
Executable::<InvokeContext>::from_elf(&contents, loader)
.map_err(|err| format!("Executable constructor failed: {err:?}"))
let mut verified_executable = if magic == [0x7f, 0x45, 0x4c, 0x46] {
let mut load_program_metrics = LoadProgramMetrics::default();
let result = load_program_from_bytes(
&invoke_context.feature_set,
invoke_context.get_compute_budget(),
None,
&mut load_program_metrics,
&contents,
&bpf_loader::id(),
contents.len(),
Slot::default(),
false, /* use_jit */
true, /* reject_deployment_of_broken_elfs */
true, /* debugging_features */
);
match result {
Ok(loaded_program) => match loaded_program.program {
LoadedProgramType::LegacyV1(program) => Ok(unsafe { std::mem::transmute(program) }),
_ => unreachable!(),
},
Err(err) => Err(format!("Loading executable failed: {err:?}")),
}
} else {
assemble::<InvokeContext>(std::str::from_utf8(contents.as_slice()).unwrap(), loader)
let loader = create_loader(
&invoke_context.feature_set,
invoke_context.get_compute_budget(),
true,
true,
true,
)
.unwrap();
let executable =
assemble::<InvokeContext>(std::str::from_utf8(contents.as_slice()).unwrap(), loader)
.unwrap();
VerifiedExecutable::<RequisiteVerifier, InvokeContext>::from_executable(executable)
.map_err(|err| format!("Assembling executable failed: {err:?}"))
}
.unwrap();
#[allow(unused_mut)]
let mut verified_executable =
VerifiedExecutable::<RequisiteVerifier, InvokeContext>::from_executable(executable)
.map_err(|err| format!("Executable verifier failed: {err:?}"))
.unwrap();
#[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
verified_executable.jit_compile().unwrap();
let mut analysis = LazyAnalysis::new(verified_executable.get_executable());
@ -285,11 +307,9 @@ before execting it in the virtual machine.",
create_vm!(
vm,
&verified_executable,
stack,
heap,
regions,
account_lengths,
&mut invoke_context
&mut invoke_context,
);
let mut vm = vm.unwrap();
let start_time = Instant::now();
@ -299,14 +319,17 @@ before execting it in the virtual machine.",
let (instruction_count, result) = vm.execute_program(matches.value_of("use").unwrap() != "jit");
let duration = Instant::now() - start_time;
if matches.occurrences_of("trace") > 0 {
for (frame, trace) in vm
for (frame, syscall_context) in vm
.env
.context_object_pointer
.trace_log_stack
.syscall_context
.iter()
.enumerate()
{
let trace_log = trace.trace_log.as_slice();
if syscall_context.is_none() {
continue;
}
let trace_log = syscall_context.as_ref().unwrap().trace_log.as_slice();
if matches.value_of("trace").unwrap() == "stdout" {
writeln!(&mut std::io::stdout(), "Frame {frame}").unwrap();
analysis

View File

@ -4271,6 +4271,7 @@ impl Bank {
&program,
programdata.as_ref().unwrap_or(&program),
self.runtime_config.bpf_jit,
false, /* debugging_features */
)
.map(|(loaded_program, _create_executor_metrics)| loaded_program)
.map_err(|err| TransactionError::InstructionError(0, err))