From 461ae40eea470219c5b3f355d03e7414d748ec79 Mon Sep 17 00:00:00 2001 From: Jack May Date: Mon, 9 Nov 2020 13:40:26 -0800 Subject: [PATCH] provide full Rust panic messages in BPF and add memory optimizations (#13455) --- .../deployed-programs/developing-rust.md | 59 +++++++++++++++++++ programs/bpf/rust/panic/Cargo.toml | 4 ++ programs/bpf/rust/panic/src/lib.rs | 24 ++++++-- programs/bpf/tests/programs.rs | 22 +++---- programs/bpf_loader/src/syscalls.rs | 2 +- sdk/bpf/scripts/install.sh | 2 +- sdk/program/src/entrypoint.rs | 20 ++++++- 7 files changed, 113 insertions(+), 20 deletions(-) diff --git a/docs/src/developing/deployed-programs/developing-rust.md b/docs/src/developing/deployed-programs/developing-rust.md index 5b384a3967..a67b695af3 100644 --- a/docs/src/developing/deployed-programs/developing-rust.md +++ b/docs/src/developing/deployed-programs/developing-rust.md @@ -270,6 +270,65 @@ info!(&format!("Some varialbe: {:?}", variable)); The [debugging](debugging.md#logging) section has more information about working with program logs. +## Panicking + +Rust's `panic!`, `assert!`, and internal panic results are printed to the +[program logs](debugging.md#logging) by default. + +``` +INFO solana_runtime::message_processor] Finalized account CGLhHSuWsp1gT4B7MY2KACqp9RUwQRhcUFfVSuxpSajZ +INFO solana_runtime::message_processor] Call BPF program CGLhHSuWsp1gT4B7MY2KACqp9RUwQRhcUFfVSuxpSajZ +INFO solana_runtime::message_processor] Program log: Panicked at: 'assertion failed: `(left == right)` + left: `1`, + right: `2`', rust/panic/src/lib.rs:22:5 +INFO solana_runtime::message_processor] BPF program consumed 5453 of 200000 units +INFO solana_runtime::message_processor] BPF program CGLhHSuWsp1gT4B7MY2KACqp9RUwQRhcUFfVSuxpSajZ failed: BPF program panicked +``` + +### Custom Panic Handler + +Programs can override the default panic handler by providing their own +implementation. + +First define the `custom-panic` feature in the program's `Cargo.toml` + +```toml +[features] +default = ["custom-panic"] +custom-panic = [] +``` + +Then provide a custom implementation of the panic handler: + +```rust +#[cfg(all(feature = "custom-panic", target_arch = "bpf"))] +#[no_mangle] +fn custom_panic(info: &core::panic::PanicInfo<'_>) { + solana_program::info!("program custom panic enabled"); + solana_program::info!(&format!("{}", info)); +} +``` + +In the above snippit, the default implementation is shown, but developers may +replace that with something that better suits their needs. + +One of the side effects of supporting full panic messages by default is that +programs incur the cost of pulling in more of Rust's `libstd` implementation +into program's shared object. Typical programs will already be pulling in a +fair amount of `libstd` and may not notice much of an increase in the shared +object size. But programs that explicitly attempt to be very small by avoiding +`libstd` may take a significant impact (~25kb). To eliminate that impact, +programs can provide their own custom panic handler with an empty +implementation. + +```rust +#[cfg(all(feature = "custom-panic", target_arch = "bpf"))] +#[no_mangle] +fn custom_panic(info: &core::panic::PanicInfo<'_>) { + // Do nothing to save space +} +``` + ## Compute Budget Use the system call diff --git a/programs/bpf/rust/panic/Cargo.toml b/programs/bpf/rust/panic/Cargo.toml index 9d5748535f..7f6dcb2185 100644 --- a/programs/bpf/rust/panic/Cargo.toml +++ b/programs/bpf/rust/panic/Cargo.toml @@ -11,6 +11,10 @@ edition = "2018" [dependencies] solana-program = { path = "../../../../sdk/program", version = "1.5.0" } +[features] +default = ["custom-panic"] +custom-panic = [] + [lib] name = "solana_bpf_rust_panic" crate-type = ["cdylib"] diff --git a/programs/bpf/rust/panic/src/lib.rs b/programs/bpf/rust/panic/src/lib.rs index 0d579f205c..f15c2b799a 100644 --- a/programs/bpf/rust/panic/src/lib.rs +++ b/programs/bpf/rust/panic/src/lib.rs @@ -1,8 +1,24 @@ //! @brief Example Rust-based BPF program that panics -extern crate solana_program; - +#[cfg(all(feature = "custom-panic", target_arch = "bpf"))] #[no_mangle] -pub extern "C" fn entrypoint(_input: *mut u8) -> u64 { - panic!(); +fn custom_panic(info: &core::panic::PanicInfo<'_>) { + // Note: Full panic reporting is included here for testing purposes + solana_program::info!("program custom panic enabled"); + solana_program::info!(&format!("{}", info)); +} + +extern crate solana_program; +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + assert_eq!(1, 2); + Ok(()) } diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 16dd13b112..27ebae82d3 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -533,11 +533,7 @@ fn test_program_bpf_invoke() { &[TEST_SUCCESS, bump_seed1, bump_seed2, bump_seed3], account_metas.clone(), ); - let noop_instruction = Instruction::new( - noop_program_id, - &(), - vec![] - ); + let noop_instruction = Instruction::new(noop_program_id, &(), vec![]); let message = Message::new(&[instruction, noop_instruction], Some(&mint_pubkey)); let tx = Transaction::new( &[ @@ -763,8 +759,8 @@ fn assert_instruction_count() { ("multiple_static", 8), ("noop", 57), ("relative_call", 10), - ("sanity", 1140), - ("sanity++", 1140), + ("sanity", 176), + ("sanity++", 176), ("struct_pass", 8), ("struct_ret", 22), ]); @@ -772,16 +768,16 @@ fn assert_instruction_count() { #[cfg(feature = "bpf_rust")] { programs.extend_from_slice(&[ - ("solana_bpf_rust_128bit", 543), - ("solana_bpf_rust_alloc", 19082), + ("solana_bpf_rust_128bit", 572), + ("solana_bpf_rust_alloc", 12777), ("solana_bpf_rust_dep_crate", 2), ("solana_bpf_rust_external_spend", 538), - ("solana_bpf_rust_iter", 723), - ("solana_bpf_rust_many_args", 231), + ("solana_bpf_rust_iter", 724), + ("solana_bpf_rust_many_args", 237), ("solana_bpf_rust_noop", 488), - ("solana_bpf_rust_param_passing", 46), + ("solana_bpf_rust_param_passing", 48), ("solana_bpf_rust_ristretto", 19399), - ("solana_bpf_rust_sanity", 1965), + ("solana_bpf_rust_sanity", 894), ]); } diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 635bf6c391..eb613c4881 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -41,7 +41,7 @@ use thiserror::Error as ThisError; pub enum SyscallError { #[error("{0}: {1:?}")] InvalidString(Utf8Error, Vec), - #[error("BPF program called abort()!")] + #[error("BPF program panicked")] Abort, #[error("BPF program Panicked in {0} at {1}:{2}")] Panic(String, u64, u64), diff --git a/sdk/bpf/scripts/install.sh b/sdk/bpf/scripts/install.sh index 464fff22e2..88a5808d31 100755 --- a/sdk/bpf/scripts/install.sh +++ b/sdk/bpf/scripts/install.sh @@ -174,7 +174,7 @@ if [[ ! -e rust-bpf-$machine-$version.md || ! -e rust-bpf-$machine ]]; then fi # Install Rust-BPF Sysroot sources -version=v0.12 +version=v0.13 if [[ ! -e rust-bpf-sysroot-$version.md || ! -e rust-bpf-sysroot ]]; then ( set -e diff --git a/sdk/program/src/entrypoint.rs b/sdk/program/src/entrypoint.rs index 5d979db348..bbef71c3d2 100644 --- a/sdk/program/src/entrypoint.rs +++ b/sdk/program/src/entrypoint.rs @@ -45,13 +45,31 @@ pub const HEAP_LENGTH: usize = 32 * 1024; #[macro_export] macro_rules! entrypoint { ($process_instruction:ident) => { - #[cfg(all(not(feature = "custom-heap"), not(test)))] + /// A program can provide their own custom heap implementation by adding + /// a `custom-heap` feature to `Cargo.toml` and implementing their own + /// `global_allocator`. + #[cfg(all(not(feature = "custom-heap"), target_arch = "bpf"))] #[global_allocator] static A: $crate::entrypoint::BumpAllocator = $crate::entrypoint::BumpAllocator { start: $crate::entrypoint::HEAP_START_ADDRESS, len: $crate::entrypoint::HEAP_LENGTH, }; + /// A program can provide their own custom panic implementation by + /// adding a `custom-panic` feature to `Cargo.toml` and implementing + /// their own `custom_panic`. + /// + /// A good way to reduce the final size of the program is to provide a + /// `custom_panic` implementation that does nothing. Doing so will cut + /// ~25kb from a noop program. That number goes down the more the + /// programs pulls in Rust's libstd for other purposes. + #[cfg(all(not(feature = "custom-panic"), target_arch = "bpf"))] + #[no_mangle] + fn custom_panic(info: &core::panic::PanicInfo<'_>) { + // Full panic reporting + $crate::info!(&format!("{}", info)); + } + /// # Safety #[no_mangle] pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {