Add memory operation syscalls (#16447)

This commit is contained in:
Jack May 2021-06-01 15:33:17 -07:00 committed by GitHub
parent 38d71c37da
commit 2b50529265
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 605 additions and 145 deletions

View File

@ -2958,6 +2958,16 @@ name = "solana-bpf-rust-mem"
version = "1.8.0"
dependencies = [
"solana-program 1.8.0",
"solana-program-test",
"solana-sdk",
]
[[package]]
name = "solana-bpf-rust-membuiltins"
version = "1.8.0"
dependencies = [
"solana-bpf-rust-mem",
"solana-program 1.8.0",
]
[[package]]

View File

@ -64,6 +64,7 @@ members = [
"rust/many_args",
"rust/many_args_dep",
"rust/mem",
"rust/membuiltins",
"rust/noop",
"rust/panic",
"rust/param_passing",

View File

@ -78,6 +78,7 @@ fn main() {
"iter",
"many_args",
"mem",
"membuiltins",
"noop",
"panic",
"param_passing",

View File

@ -9,11 +9,18 @@ homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-bpf-rust-mem"
edition = "2018"
[features]
no-entrypoint = []
[dependencies]
solana-program = { path = "../../../../sdk/program", version = "=1.8.0" }
[dev-dependencies]
solana-program-test = { path = "../../../../program-test", version = "=1.8.0" }
solana-sdk = { path = "../../../../sdk", version = "=1.8.0" }
[lib]
crate-type = ["cdylib"]
crate-type = ["cdylib", "lib"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -0,0 +1,39 @@
//! @brief Test mem functions
use crate::{run_mem_tests, MemOps};
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
program_memory::{sol_memcmp, sol_memcpy, sol_memmove, sol_memset},
pubkey::Pubkey,
};
entrypoint!(process_instruction);
#[allow(clippy::unnecessary_wraps)]
pub fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
// Via syscalls
#[derive(Default)]
struct MemOpSyscalls();
impl MemOps for MemOpSyscalls {
fn memcpy(&self, dst: &mut [u8], src: &[u8], n: usize) {
sol_memcpy(dst, src, n)
}
unsafe fn memmove(&self, dst: *mut u8, src: *mut u8, n: usize) {
sol_memmove(dst, src, n)
}
fn memset(&self, s: &mut [u8], c: u8, n: usize) {
sol_memset(s, c, n)
}
fn memcmp(&self, s1: &[u8], s2: &[u8], n: usize) -> i32 {
sol_memcmp(s1, s2, n)
}
}
run_mem_tests(MemOpSyscalls::default());
Ok(())
}

View File

@ -1,190 +1,165 @@
//! @brief Test builtin mem functions
//! @brief Test mem functions
#![cfg(target_arch = "bpf")]
#![feature(rustc_private)]
#[cfg(not(feature = "no-entrypoint"))]
pub mod entrypoint;
extern crate compiler_builtins;
use solana_program::{custom_panic_default, entrypoint::SUCCESS};
pub trait MemOps {
fn memcpy(&self, dst: &mut [u8], src: &[u8], n: usize);
/// # Safety
unsafe fn memmove(&self, dst: *mut u8, src: *mut u8, n: usize);
fn memset(&self, s: &mut [u8], c: u8, n: usize);
fn memcmp(&self, s1: &[u8], s2: &[u8], n: usize) -> i32;
}
#[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> u64 {
pub fn run_mem_tests<T: MemOps>(mem_ops: T) {
// memcpy
let src = &[1_u8; 18];
let dst = &mut [0_u8; 1];
mem_ops.memcpy(dst, src, 1);
assert_eq!(&src[..1], dst);
let dst = &mut [0_u8; 3];
mem_ops.memcpy(dst, src, 3);
assert_eq!(&src[..3], dst);
let dst = &mut [0_u8; 8];
mem_ops.memcpy(dst, src, 8);
assert_eq!(&src[..8], dst);
let dst = &mut [0_u8; 9];
mem_ops.memcpy(dst, src, 9);
assert_eq!(&src[..9], dst);
let dst = &mut [0_u8; 16];
mem_ops.memcpy(dst, src, 16);
assert_eq!(&src[..16], dst);
let dst = &mut [0_u8; 18];
mem_ops.memcpy(dst, src, 18);
assert_eq!(&src[..18], dst);
let dst = &mut [0_u8; 18];
mem_ops.memcpy(dst, &src[1..], 17);
assert_eq!(&src[1..], &dst[..17]);
let dst = &mut [0_u8; 18];
mem_ops.memcpy(&mut dst[1..], &src[1..], 17);
assert_eq!(&src[1..], &dst[1..]);
// memmove
unsafe {
// memcpy
let src = &mut [1_u8; 18];
let dst = &mut [0_u8; 1];
compiler_builtins::mem::memcpy(&mut src[0] as *mut u8, &mut dst[0] as *mut u8, 1);
assert_eq!(&src[..1], dst);
let dst = &mut [0_u8; 3];
compiler_builtins::mem::memcpy(&mut src[0] as *mut u8, &mut dst[0] as *mut u8, 3);
assert_eq!(&src[..3], dst);
let dst = &mut [0_u8; 8];
compiler_builtins::mem::memcpy(&mut src[0] as *mut u8, &mut dst[0] as *mut u8, 8);
assert_eq!(&src[..8], dst);
let dst = &mut [0_u8; 9];
compiler_builtins::mem::memcpy(&mut src[0] as *mut u8, &mut dst[0] as *mut u8, 9);
assert_eq!(&src[..9], dst);
let dst = &mut [0_u8; 16];
compiler_builtins::mem::memcpy(&mut src[0] as *mut u8, &mut dst[0] as *mut u8, 16);
assert_eq!(&src[..16], dst);
let dst = &mut [0_u8; 18];
compiler_builtins::mem::memcpy(&mut src[0] as *mut u8, &mut dst[0] as *mut u8, 18);
assert_eq!(&src[..18], dst);
let dst = &mut [0_u8; 18];
compiler_builtins::mem::memcpy(&mut src[1] as *mut u8, &mut dst[0] as *mut u8, 17);
assert_eq!(&src[1..], &dst[1..]);
let dst = &mut [0_u8; 18];
compiler_builtins::mem::memcpy(&mut src[1] as *mut u8, &mut dst[1] as *mut u8, 17);
assert_eq!(&src[1..], &dst[..17]);
// memmove
let buf = &mut [1_u8, 0];
compiler_builtins::mem::memmove(&mut buf[0] as *mut u8, &mut buf[1] as *mut u8, 1);
mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[1] as *mut u8, 1);
assert_eq!(buf[0], buf[1]);
let buf = &mut [1_u8, 0];
compiler_builtins::mem::memmove(&mut buf[1] as *mut u8, &mut buf[0] as *mut u8, 1);
mem_ops.memmove(&mut buf[1] as *mut u8, &mut buf[0] as *mut u8, 1);
assert_eq!(buf[0], buf[1]);
let buf = &mut [1_u8, 1, 1, 0, 0, 0];
compiler_builtins::mem::memmove(&mut buf[0] as *mut u8, &mut buf[3] as *mut u8, 3);
mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[3] as *mut u8, 3);
assert_eq!(buf[..3], buf[3..]);
let buf = &mut [1_u8, 1, 1, 0, 0, 0];
compiler_builtins::mem::memmove(&mut buf[3] as *mut u8, &mut buf[0] as *mut u8, 3);
mem_ops.memmove(&mut buf[3] as *mut u8, &mut buf[0] as *mut u8, 3);
assert_eq!(buf[..3], buf[3..]);
let buf = &mut [1_u8, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0];
compiler_builtins::mem::memmove(&mut buf[0] as *mut u8, &mut buf[8] as *mut u8, 8);
mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[8] as *mut u8, 8);
assert_eq!(buf[..8], buf[8..]);
let buf = &mut [1_u8, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0];
compiler_builtins::mem::memmove(&mut buf[8] as *mut u8, &mut buf[0] as *mut u8, 8);
mem_ops.memmove(&mut buf[8] as *mut u8, &mut buf[0] as *mut u8, 8);
assert_eq!(buf[..8], buf[8..]);
let buf = &mut [1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0];
compiler_builtins::mem::memmove(&mut buf[0] as *mut u8, &mut buf[9] as *mut u8, 9);
mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[9] as *mut u8, 9);
assert_eq!(buf[..9], buf[9..]);
let buf = &mut [0_u8, 1, 2, 3, 4, 5, 6, 7, 8, 9];
compiler_builtins::mem::memmove(&mut buf[1] as *mut u8, &mut buf[0] as *mut u8, 9);
mem_ops.memmove(&mut buf[1] as *mut u8, &mut buf[0] as *mut u8, 9);
assert_eq!(&mut [0_u8, 0, 1, 2, 3, 4, 5, 6, 7, 8], buf);
let buf = &mut [1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0];
compiler_builtins::mem::memmove(&mut buf[9] as *mut u8, &mut buf[0] as *mut u8, 9);
mem_ops.memmove(&mut buf[9] as *mut u8, &mut buf[0] as *mut u8, 9);
assert_eq!(buf[..9], buf[9..]);
let buf = &mut [
1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,
];
compiler_builtins::mem::memmove(&mut buf[0] as *mut u8, &mut buf[16] as *mut u8, 16);
mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[16] as *mut u8, 16);
assert_eq!(buf[..16], buf[16..]);
let buf = &mut [
1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,
];
compiler_builtins::mem::memmove(&mut buf[16] as *mut u8, &mut buf[0] as *mut u8, 16);
mem_ops.memmove(&mut buf[16] as *mut u8, &mut buf[0] as *mut u8, 16);
assert_eq!(buf[..16], buf[16..]);
let buf = &mut [
1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
compiler_builtins::mem::memmove(&mut buf[0] as *mut u8, &mut buf[18] as *mut u8, 18);
mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[18] as *mut u8, 18);
assert_eq!(buf[..18], buf[18..]);
let buf = &mut [
1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
compiler_builtins::mem::memmove(&mut buf[18] as *mut u8, &mut buf[0] as *mut u8, 18);
mem_ops.memmove(&mut buf[18] as *mut u8, &mut buf[0] as *mut u8, 18);
assert_eq!(buf[..18], buf[18..]);
let buf = &mut [
1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
compiler_builtins::mem::memmove(&mut buf[1] as *mut u8, &mut buf[18] as *mut u8, 17);
mem_ops.memmove(&mut buf[1] as *mut u8, &mut buf[18] as *mut u8, 17);
assert_eq!(buf[1..17], buf[18..34]);
let buf = &mut [
1_u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
compiler_builtins::mem::memmove(&mut buf[19] as *mut u8, &mut buf[1] as *mut u8, 17);
mem_ops.memmove(&mut buf[19] as *mut u8, &mut buf[1] as *mut u8, 17);
assert_eq!(buf[..17], buf[19..]);
// memset
let exp = &[1_u8; 18];
let buf = &mut [0_u8; 18];
compiler_builtins::mem::memset(&mut buf[0] as *mut u8, 1, 1);
assert_eq!(exp[..1], buf[..1]);
compiler_builtins::mem::memset(&mut buf[0] as *mut u8, 1, 3);
assert_eq!(exp[..3], buf[..3]);
compiler_builtins::mem::memset(&mut buf[0] as *mut u8, 1, 8);
assert_eq!(exp[..8], buf[..8]);
compiler_builtins::mem::memset(&mut buf[0] as *mut u8, 1, 9);
assert_eq!(exp[..9], buf[..9]);
compiler_builtins::mem::memset(&mut buf[0] as *mut u8, 1, 16);
assert_eq!(exp[..16], buf[..16]);
compiler_builtins::mem::memset(&mut buf[0] as *mut u8, 1, 18);
assert_eq!(exp[..18], buf[..18]);
compiler_builtins::mem::memset(&mut buf[1] as *mut u8, 1, 17);
assert_eq!(exp[1..18], buf[1..18]);
// memcmp
assert_eq!(
-1,
compiler_builtins::mem::memcmp(&[0_u8] as *const u8, &[1_u8] as *const u8, 1)
);
assert_eq!(
-1,
compiler_builtins::mem::memcmp(
&[0_u8, 0, 0] as *const u8,
&[0_u8, 0, 1] as *const u8,
3
)
);
assert_eq!(
0,
compiler_builtins::mem::memcmp(
&[0_u8, 0, 0, 0, 0, 0, 0, 0, 0] as *const u8,
&[0_u8, 0, 0, 0, 0, 0, 0, 0, 0] as *const u8,
9
)
);
assert_eq!(
-1,
compiler_builtins::mem::memcmp(
&[0_u8, 0, 0, 0, 0, 0, 0, 0, 0] as *const u8,
&[0_u8, 0, 0, 0, 0, 0, 0, 0, 1] as *const u8,
9
)
);
assert_eq!(
-1,
compiler_builtins::mem::memcmp(
&[0_u8, 0, 0, 0, 0, 0, 0, 0, 0, 0] as *const u8,
&[0_u8, 0, 0, 0, 0, 0, 0, 0, 0, 1] as *const u8,
10
)
);
assert_eq!(
0,
compiler_builtins::mem::memcmp(&[0_u8; 8] as *const u8, &[0_u8; 8] as *const u8, 8)
);
assert_eq!(
-1,
compiler_builtins::mem::memcmp(&[0_u8; 8] as *const u8, &[1_u8; 8] as *const u8, 8)
);
assert_eq!(
-1,
compiler_builtins::mem::memcmp(&[0_u8; 16] as *const u8, &[1_u8; 16] as *const u8, 16)
);
assert_eq!(
-1,
compiler_builtins::mem::memcmp(&[0_u8; 18] as *const u8, &[1_u8; 18] as *const u8, 18)
);
let one = &[0_u8; 18];
let two = &[1_u8; 18];
assert_eq!(
-1,
compiler_builtins::mem::memcmp(&one[1] as *const u8, &two[0] as *const u8, 17)
);
assert_eq!(
-1,
compiler_builtins::mem::memcmp(&one[1] as *const u8, &two[1] as *const u8, 17)
);
let buf = &mut [0_u8, 0, 0, 1, 1, 1, 1, 1, 0];
mem_ops.memmove(&mut buf[0] as *mut u8, &mut buf[3] as *mut u8, 5);
assert_eq!(buf, &mut [1, 1, 1, 1, 1, 1, 1, 1, 0]);
}
SUCCESS
}
// memset
let exp = &[1_u8; 18];
let buf = &mut [0_u8; 18];
mem_ops.memset(&mut buf[0..], 1, 1);
assert_eq!(exp[..1], buf[..1]);
mem_ops.memset(&mut buf[0..], 1, 3);
assert_eq!(exp[..3], buf[..3]);
mem_ops.memset(&mut buf[0..], 1, 8);
assert_eq!(exp[..8], buf[..8]);
mem_ops.memset(&mut buf[0..], 1, 9);
assert_eq!(exp[..9], buf[..9]);
mem_ops.memset(&mut buf[0..], 1, 16);
assert_eq!(exp[..16], buf[..16]);
mem_ops.memset(&mut buf[0..], 1, 18);
assert_eq!(exp[..18], buf[..18]);
mem_ops.memset(&mut buf[1..], 1, 17);
assert_eq!(exp[1..18], buf[1..18]);
custom_panic_default!();
// memcmp
assert_eq!(-1, mem_ops.memcmp(&[0_u8], &[1_u8], 1));
assert_eq!(-1, mem_ops.memcmp(&[0_u8, 0, 0], &[0_u8, 0, 1], 3));
assert_eq!(
0,
mem_ops.memcmp(
&[0_u8, 0, 0, 0, 0, 0, 0, 0, 0],
&[0_u8, 0, 0, 0, 0, 0, 0, 0, 0],
9
)
);
assert_eq!(
-1,
mem_ops.memcmp(
&[0_u8, 0, 0, 0, 0, 0, 0, 0, 0],
&[0_u8, 0, 0, 0, 0, 0, 0, 0, 1],
9
)
);
assert_eq!(
-1,
mem_ops.memcmp(
&[0_u8, 0, 0, 0, 0, 0, 0, 0, 0, 0],
&[0_u8, 0, 0, 0, 0, 0, 0, 0, 0, 1],
10
)
);
assert_eq!(0, mem_ops.memcmp(&[0_u8; 8], &[0_u8; 8], 8));
assert_eq!(-1, mem_ops.memcmp(&[0_u8; 8], &[1_u8; 8], 8));
assert_eq!(-1, mem_ops.memcmp(&[0_u8; 16], &[1_u8; 16], 16));
assert_eq!(-1, mem_ops.memcmp(&[0_u8; 18], &[1_u8; 18], 18));
let one = &[0_u8; 18];
let two = &[1_u8; 18];
assert_eq!(-1, mem_ops.memcmp(&one[1..], &two[0..], 17));
assert_eq!(-1, mem_ops.memcmp(&one[1..], &two[1..], 17));
}

View File

@ -0,0 +1,23 @@
use solana_bpf_rust_mem::entrypoint::process_instruction;
use solana_program_test::*;
use solana_sdk::{
instruction::Instruction, pubkey::Pubkey, signature::Signer, transaction::Transaction,
};
#[tokio::test]
async fn test_mem() {
let program_id = Pubkey::new_unique();
let program_test = ProgramTest::new(
"solana_bpf_rust_mem",
program_id,
processor!(process_instruction),
);
let (mut banks_client, payer, recent_blockhash) = program_test.start().await;
let mut transaction = Transaction::new_with_payer(
&[Instruction::new_with_bincode(program_id, &(), vec![])],
Some(&payer.pubkey()),
);
transaction.sign(&[&payer], recent_blockhash);
banks_client.process_transaction(transaction).await.unwrap();
}

View File

@ -0,0 +1,20 @@
[package]
name = "solana-bpf-rust-membuiltins"
version = "1.8.0"
description = "Solana BPF test program written in Rust"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-bpf-rust-mem"
edition = "2018"
[dependencies]
solana-bpf-rust-mem = { path = "../mem", version = "=1.8.0", features = [ "no-entrypoint" ] }
solana-program = { path = "../../../../sdk/program", version = "=1.8.0" }
[lib]
crate-type = ["cdylib"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -0,0 +1,39 @@
//! @brief Test builtin mem functions
#![cfg(target_arch = "bpf")]
#![feature(rustc_private)]
extern crate compiler_builtins;
use solana_bpf_rust_mem::{run_mem_tests, MemOps};
use solana_program::{custom_panic_default, entrypoint::SUCCESS};
#[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> u64 {
#[derive(Default)]
struct MemOpSyscalls();
impl MemOps for MemOpSyscalls {
fn memcpy(&self, dst: &mut [u8], src: &[u8], n: usize) {
unsafe {
compiler_builtins::mem::memcpy(dst.as_mut_ptr(), src.as_ptr(), n);
}
}
unsafe fn memmove(&self, dst: *mut u8, src: *mut u8, n: usize) {
compiler_builtins::mem::memmove(dst, src, n);
}
fn memset(&self, s: &mut [u8], c: u8, n: usize) {
unsafe {
compiler_builtins::mem::memset(s.as_mut_ptr(), c as i32, n);
}
}
fn memcmp(&self, s1: &[u8], s2: &[u8], n: usize) -> i32 {
unsafe { compiler_builtins::mem::memcmp(s1.as_ptr(), s2.as_ptr(), n) }
}
}
let mem_ops = MemOpSyscalls::default();
run_mem_tests(mem_ops);
SUCCESS
}
custom_panic_default!();

View File

@ -12,7 +12,7 @@ use solana_sdk::{
};
#[tokio::test]
async fn test_noop() {
async fn test_sysvars() {
let program_id = Pubkey::new_unique();
let program_test = ProgramTest::new(
"solana_bpf_rust_sysvar",

View File

@ -454,7 +454,7 @@ fn test_program_bpf_sanity() {
("solana_bpf_rust_external_spend", false),
("solana_bpf_rust_iter", true),
("solana_bpf_rust_many_args", true),
("solana_bpf_rust_mem", true),
("solana_bpf_rust_membuiltins", true),
("solana_bpf_rust_noop", true),
("solana_bpf_rust_panic", false),
("solana_bpf_rust_param_passing", true),
@ -1276,7 +1276,8 @@ fn assert_instruction_count() {
("solana_bpf_rust_external_spend", 521),
("solana_bpf_rust_iter", 724),
("solana_bpf_rust_many_args", 237),
("solana_bpf_rust_mem", 2297),
("solana_bpf_rust_mem", 3143),
("solana_bpf_rust_membuiltins", 4069),
("solana_bpf_rust_noop", 495),
("solana_bpf_rust_param_passing", 46),
("solana_bpf_rust_rand", 498),

View File

@ -20,8 +20,8 @@ use solana_sdk::{
epoch_schedule::EpochSchedule,
feature_set::{
cpi_data_cost, demote_sysvar_write_locks, enforce_aligned_host_addrs,
keccak256_syscall_enabled, set_upgrade_authority_via_cpi_enabled, sysvar_via_syscall,
update_data_on_realloc,
keccak256_syscall_enabled, memory_ops_syscalls, set_upgrade_authority_via_cpi_enabled,
sysvar_via_syscall, update_data_on_realloc,
},
hash::{Hasher, HASH_BYTES},
ic_msg,
@ -74,6 +74,8 @@ pub enum SyscallError {
InstructionTooLarge(usize, usize),
#[error("Too many accounts passed to inner instruction")]
TooManyAccounts,
#[error("Overlapping copy")]
CopyOverlapping,
}
impl From<SyscallError> for EbpfError<BpfError> {
fn from(error: SyscallError) -> Self {
@ -145,10 +147,20 @@ pub fn register_syscalls(
.register_syscall_by_name(b"sol_get_rent_sysvar", SyscallGetRentSysvar::call)?;
}
if invoke_context.is_feature_active(&memory_ops_syscalls::id()) {
syscall_registry.register_syscall_by_name(b"sol_memcpy_", SyscallMemcpy::call)?;
syscall_registry.register_syscall_by_name(b"sol_memmove_", SyscallMemmove::call)?;
syscall_registry.register_syscall_by_name(b"sol_memcmp_", SyscallMemcmp::call)?;
syscall_registry.register_syscall_by_name(b"sol_memset_", SyscallMemset::call)?;
}
// Cross-program invocation syscalls
syscall_registry
.register_syscall_by_name(b"sol_invoke_signed_c", SyscallInvokeSignedC::call)?;
syscall_registry
.register_syscall_by_name(b"sol_invoke_signed_rust", SyscallInvokeSignedRust::call)?;
// Memory allocator
syscall_registry.register_syscall_by_name(b"sol_alloc_free_", SyscallAllocFree::call)?;
Ok(syscall_registry)
@ -268,6 +280,43 @@ pub fn bind_syscall_context_objects<'a>(
}),
);
bind_feature_gated_syscall_context_object!(
vm,
invoke_context.is_feature_active(&memory_ops_syscalls::id()),
Box::new(SyscallMemcpy {
cost: invoke_context.get_bpf_compute_budget().cpi_bytes_per_unit,
compute_meter: invoke_context.get_compute_meter(),
loader_id,
}),
);
bind_feature_gated_syscall_context_object!(
vm,
invoke_context.is_feature_active(&memory_ops_syscalls::id()),
Box::new(SyscallMemmove {
cost: invoke_context.get_bpf_compute_budget().cpi_bytes_per_unit,
compute_meter: invoke_context.get_compute_meter(),
loader_id,
}),
);
bind_feature_gated_syscall_context_object!(
vm,
invoke_context.is_feature_active(&memory_ops_syscalls::id()),
Box::new(SyscallMemcmp {
cost: invoke_context.get_bpf_compute_budget().cpi_bytes_per_unit,
compute_meter: invoke_context.get_compute_meter(),
loader_id,
}),
);
bind_feature_gated_syscall_context_object!(
vm,
invoke_context.is_feature_active(&memory_ops_syscalls::id()),
Box::new(SyscallMemset {
cost: invoke_context.get_bpf_compute_budget().cpi_bytes_per_unit,
compute_meter: invoke_context.get_compute_meter(),
loader_id,
}),
);
let is_sysvar_via_syscall_active = invoke_context.is_feature_active(&sysvar_via_syscall::id());
let invoke_context = Rc::new(RefCell::new(invoke_context));
@ -322,7 +371,6 @@ pub fn bind_syscall_context_objects<'a>(
)?;
// Memory allocator
vm.bind_syscall_context_object(
Box::new(SyscallAllocFree {
aligned: *loader_id != bpf_loader_deprecated::id(),
@ -1137,6 +1185,150 @@ impl<'a> SyscallObject<BpfError> for SyscallKeccak256<'a> {
}
}
/// memcpy
pub struct SyscallMemcpy<'a> {
cost: u64,
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
loader_id: &'a Pubkey,
}
impl<'a> SyscallObject<BpfError> for SyscallMemcpy<'a> {
fn call(
&mut self,
dst_addr: u64,
src_addr: u64,
n: u64,
_arg4: u64,
_arg5: u64,
memory_mapping: &MemoryMapping,
result: &mut Result<u64, EbpfError<BpfError>>,
) {
// cannot be overlapping
if dst_addr + n > src_addr && src_addr > dst_addr {
*result = Err(SyscallError::CopyOverlapping.into());
return;
}
question_mark!(self.compute_meter.consume(n / self.cost), result);
let dst = question_mark!(
translate_slice_mut::<u8>(memory_mapping, dst_addr, n, self.loader_id, true),
result
);
let src = question_mark!(
translate_slice::<u8>(memory_mapping, src_addr, n, self.loader_id, true),
result
);
unsafe {
std::ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), n as usize);
}
*result = Ok(0);
}
}
/// memcpy
pub struct SyscallMemmove<'a> {
cost: u64,
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
loader_id: &'a Pubkey,
}
impl<'a> SyscallObject<BpfError> for SyscallMemmove<'a> {
fn call(
&mut self,
dst_addr: u64,
src_addr: u64,
n: u64,
_arg4: u64,
_arg5: u64,
memory_mapping: &MemoryMapping,
result: &mut Result<u64, EbpfError<BpfError>>,
) {
question_mark!(self.compute_meter.consume(n / self.cost), result);
let dst = question_mark!(
translate_slice_mut::<u8>(memory_mapping, dst_addr, n, self.loader_id, true),
result
);
let src = question_mark!(
translate_slice::<u8>(memory_mapping, src_addr, n, self.loader_id, true),
result
);
unsafe {
std::ptr::copy(src.as_ptr(), dst.as_mut_ptr(), n as usize);
}
*result = Ok(0);
}
}
/// memcmp
pub struct SyscallMemcmp<'a> {
cost: u64,
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
loader_id: &'a Pubkey,
}
impl<'a> SyscallObject<BpfError> for SyscallMemcmp<'a> {
fn call(
&mut self,
s1_addr: u64,
s2_addr: u64,
n: u64,
cmp_result_addr: u64,
_arg5: u64,
memory_mapping: &MemoryMapping,
result: &mut Result<u64, EbpfError<BpfError>>,
) {
question_mark!(self.compute_meter.consume(n / self.cost), result);
let s1 = question_mark!(
translate_slice::<u8>(memory_mapping, s1_addr, n, self.loader_id, true),
result
);
let s2 = question_mark!(
translate_slice::<u8>(memory_mapping, s2_addr, n, self.loader_id, true),
result
);
let cmp_result = question_mark!(
translate_type_mut::<i32>(memory_mapping, cmp_result_addr, self.loader_id, true),
result
);
let mut i = 0;
while i < n as usize {
let a = s1[i];
let b = s2[i];
if a != b {
*cmp_result = a as i32 - b as i32;
*result = Ok(0);
return;
}
i += 1;
}
*cmp_result = 0;
*result = Ok(0);
}
}
/// memset
pub struct SyscallMemset<'a> {
cost: u64,
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
loader_id: &'a Pubkey,
}
impl<'a> SyscallObject<BpfError> for SyscallMemset<'a> {
fn call(
&mut self,
s_addr: u64,
c: u64,
n: u64,
_arg4: u64,
_arg5: u64,
memory_mapping: &MemoryMapping,
result: &mut Result<u64, EbpfError<BpfError>>,
) {
question_mark!(self.compute_meter.consume(n / self.cost), result);
let s = question_mark!(
translate_slice_mut::<u8>(memory_mapping, s_addr, n, self.loader_id, true),
result
);
for val in s.iter_mut().take(n as usize) {
*val = c as u8;
}
*result = Ok(0);
}
}
// Cross-program invocation syscalls
struct AccountReferences<'a> {

View File

@ -30,6 +30,7 @@ pub mod native_token;
pub mod nonce;
pub mod program;
pub mod program_error;
pub mod program_memory;
pub mod program_option;
pub mod program_pack;
pub mod program_stubs;

View File

@ -0,0 +1,89 @@
//! @brief Solana Rust-based BPF memory operations
/// Memcpy
///
/// @param dst - Destination
/// @param src - Source
/// @param n - Number of bytes to copy
#[inline]
pub fn sol_memcpy(dst: &mut [u8], src: &[u8], n: usize) {
#[cfg(target_arch = "bpf")]
{
extern "C" {
fn sol_memcpy_(dst: *mut u8, src: *const u8, n: u64);
}
unsafe {
sol_memcpy_(dst.as_mut_ptr(), src.as_ptr(), n as u64);
}
}
#[cfg(not(target_arch = "bpf"))]
crate::program_stubs::sol_memcpy(dst.as_mut_ptr(), src.as_ptr(), n);
}
/// Memmove
///
/// @param dst - Destination
/// @param src - Source
/// @param n - Number of bytes to copy
///
/// # Safety
#[inline]
pub unsafe fn sol_memmove(dst: *mut u8, src: *mut u8, n: usize) {
#[cfg(target_arch = "bpf")]
{
extern "C" {
fn sol_memmove_(dst: *mut u8, src: *const u8, n: u64);
}
sol_memmove_(dst, src, n as u64);
}
#[cfg(not(target_arch = "bpf"))]
crate::program_stubs::sol_memmove(dst, src, n);
}
/// Memcmp
///
/// @param s1 - Slice to be compared
/// @param s2 - Slice to be compared
/// @param n - Number of bytes to compare
#[inline]
pub fn sol_memcmp(s1: &[u8], s2: &[u8], n: usize) -> i32 {
let mut result = 0;
#[cfg(target_arch = "bpf")]
{
extern "C" {
fn sol_memcmp_(s1: *const u8, s2: *const u8, n: u64, result: *mut i32);
}
unsafe {
sol_memcmp_(s1.as_ptr(), s2.as_ptr(), n as u64, &mut result as *mut i32);
}
}
#[cfg(not(target_arch = "bpf"))]
crate::program_stubs::sol_memcmp(s1.as_ptr(), s2.as_ptr(), n, &mut result as *mut i32);
result
}
/// Memset
///
/// @param s1 - Slice to be compared
/// @param s2 - Slice to be compared
/// @param n - Number of bytes to compare
#[inline]
pub fn sol_memset(s: &mut [u8], c: u8, n: usize) {
#[cfg(target_arch = "bpf")]
{
extern "C" {
fn sol_memset_(s: *mut u8, c: u8, n: u64);
}
unsafe {
sol_memset_(s.as_mut_ptr(), c, n as u64);
}
}
#[cfg(not(target_arch = "bpf"))]
crate::program_stubs::sol_memset(s.as_mut_ptr(), c, n);
}

View File

@ -12,12 +12,13 @@ lazy_static::lazy_static! {
static ref SYSCALL_STUBS: Arc<RwLock<Box<dyn SyscallStubs>>> = Arc::new(RwLock::new(Box::new(DefaultSyscallStubs {})));
}
// The default syscall stubs don't do much, but `set_syscalls()` can be used to swap in
// alternatives
// The default syscall stubs may not do much, but `set_syscalls()` can be used
// to swap in alternatives
pub fn set_syscall_stubs(syscall_stubs: Box<dyn SyscallStubs>) -> Box<dyn SyscallStubs> {
std::mem::replace(&mut SYSCALL_STUBS.write().unwrap(), syscall_stubs)
}
#[allow(clippy::integer_arithmetic)]
pub trait SyscallStubs: Sync + Send {
fn sol_log(&self, message: &str) {
println!("{}", message);
@ -34,7 +35,6 @@ pub trait SyscallStubs: Sync + Send {
sol_log("SyscallStubs: sol_invoke_signed() not available");
Ok(())
}
fn sol_get_clock_sysvar(&self, _var_addr: *mut u8) -> u64 {
UNSUPPORTED_SYSVAR
}
@ -47,6 +47,39 @@ pub trait SyscallStubs: Sync + Send {
fn sol_get_rent_sysvar(&self, _var_addr: *mut u8) -> u64 {
UNSUPPORTED_SYSVAR
}
/// # Safety
unsafe fn sol_memcpy(&self, dst: *mut u8, src: *const u8, n: usize) {
// cannot be overlapping
if dst as usize + n > src as usize && src as usize > dst as usize {
panic!("memcpy does not support oveerlapping regions");
}
std::ptr::copy_nonoverlapping(src, dst, n as usize);
}
/// # Safety
unsafe fn sol_memmove(&self, dst: *mut u8, src: *const u8, n: usize) {
std::ptr::copy(src, dst, n as usize);
}
/// # Safety
unsafe fn sol_memcmp(&self, s1: *const u8, s2: *const u8, n: usize, result: *mut i32) {
let mut i = 0;
while i < n {
let a = *s1.add(i);
let b = *s2.add(i);
if a != b {
*result = a as i32 - b as i32;
return;
}
i += 1;
}
*result = 0
}
/// # Safety
unsafe fn sol_memset(&self, s: *mut u8, c: u8, n: usize) {
let s = std::slice::from_raw_parts_mut(s, n);
for val in s.iter_mut().take(n) {
*val = c;
}
}
}
struct DefaultSyscallStubs {}
@ -96,3 +129,27 @@ pub(crate) fn sol_get_fees_sysvar(var_addr: *mut u8) -> u64 {
pub(crate) fn sol_get_rent_sysvar(var_addr: *mut u8) -> u64 {
SYSCALL_STUBS.read().unwrap().sol_get_rent_sysvar(var_addr)
}
pub(crate) fn sol_memcpy(dst: *mut u8, src: *const u8, n: usize) {
unsafe {
SYSCALL_STUBS.read().unwrap().sol_memcpy(dst, src, n);
}
}
pub(crate) fn sol_memmove(dst: *mut u8, src: *const u8, n: usize) {
unsafe {
SYSCALL_STUBS.read().unwrap().sol_memmove(dst, src, n);
}
}
pub(crate) fn sol_memcmp(s1: *const u8, s2: *const u8, n: usize, result: *mut i32) {
unsafe {
SYSCALL_STUBS.read().unwrap().sol_memcmp(s1, s2, n, result);
}
}
pub(crate) fn sol_memset(s: *mut u8, c: u8, n: usize) {
unsafe {
SYSCALL_STUBS.read().unwrap().sol_memset(s, c, n);
}
}

View File

@ -135,6 +135,10 @@ pub mod stake_program_v4 {
solana_sdk::declare_id!("Dc7djyhP9aLfdq2zktpvskeAjpG56msCU1yexpxXiWZb");
}
pub mod memory_ops_syscalls {
solana_sdk::declare_id!("ENQi37wsVhTvFz2gUiZAAbqFEWGN2jwFsqdEDTE8A4MU");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -169,6 +173,7 @@ lazy_static! {
(update_data_on_realloc::id(), "Retain updated data values modified after realloc via CPI"),
(keccak256_syscall_enabled::id(), "keccak256 syscall"),
(stake_program_v4::id(), "solana_stake_program v4"),
(memory_ops_syscalls::id(), "add syscalls for memory operations"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()