From a1247438a143733018edc0b696834fbc679aeb8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Thu, 23 Feb 2023 11:00:38 +0000 Subject: [PATCH] Adds tests. --- Cargo.lock | 13 + programs/loader-v3/Cargo.toml | 3 + programs/loader-v3/src/lib.rs | 757 +++++++++++++++++++++ programs/loader-v3/test_elfs/out/noop.so | Bin 0 -> 1768 bytes programs/loader-v3/test_elfs/out/rodata.so | Bin 0 -> 1904 bytes 5 files changed, 773 insertions(+) create mode 100755 programs/loader-v3/test_elfs/out/noop.so create mode 100755 programs/loader-v3/test_elfs/out/rodata.so diff --git a/Cargo.lock b/Cargo.lock index ad27e1e33b5368..b51ea6a6a12a88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5811,6 +5811,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "solana-loader-v3-program" +version = "1.16.0" +dependencies = [ + "bincode", + "log", + "rand 0.8.5", + "solana-measure", + "solana-program-runtime", + "solana-sdk 1.16.0", + "solana_rbpf", +] + [[package]] name = "solana-local-cluster" version = "1.16.0" diff --git a/programs/loader-v3/Cargo.toml b/programs/loader-v3/Cargo.toml index d3ebc230428970..4a5a79ce74ccd9 100644 --- a/programs/loader-v3/Cargo.toml +++ b/programs/loader-v3/Cargo.toml @@ -17,6 +17,9 @@ solana-program-runtime = { path = "../../program-runtime", version = "=1.16.0" } solana-sdk = { path = "../../sdk", version = "=1.16.0" } solana_rbpf = "=0.2.39" +[dev-dependencies] +bincode = "1.3.3" + [lib] crate-type = ["lib"] name = "solana_loader_v3_program" diff --git a/programs/loader-v3/src/lib.rs b/programs/loader-v3/src/lib.rs index fee91ec63bd6ee..cb8c09ca9f29e9 100644 --- a/programs/loader-v3/src/lib.rs +++ b/programs/loader-v3/src/lib.rs @@ -534,3 +534,760 @@ pub fn process_instruction(invoke_context: &mut InvokeContext) -> Result<(), Ins } } } + +#[cfg(test)] +mod tests { + use { + super::*, + solana_sdk::{ + account::{ + create_account_shared_data_for_test, AccountSharedData, ReadableAccount, + WritableAccount, + }, + account_utils::StateMut, + native_loader, + slot_history::Slot, + sysvar::{clock, rent}, + transaction_context::{IndexOfAccount, InstructionAccount, TransactionContext}, + }, + std::{fs::File, io::Read, path::Path}, + }; + + fn process_instruction( + mut program_indices: Vec, + instruction_data: &[u8], + mut transaction_accounts: Vec<(Pubkey, AccountSharedData)>, + instruction_accounts: &[(IndexOfAccount, bool, bool)], + expected_result: Result<(), InstructionError>, + ) -> Vec { + program_indices.insert(0, transaction_accounts.len() as IndexOfAccount); + let processor_account = AccountSharedData::new(0, 0, &native_loader::id()); + transaction_accounts.push((loader_v3::id(), processor_account)); + let compute_budget = ComputeBudget::default(); + let mut transaction_context = TransactionContext::new( + transaction_accounts, + Some(rent::Rent::default()), + compute_budget.max_invoke_stack_height, + compute_budget.max_instruction_trace_length, + ); + transaction_context.enable_cap_accounts_data_allocations_per_transaction(); + let instruction_accounts = instruction_accounts + .iter() + .enumerate() + .map( + |(instruction_account_index, (index_in_transaction, is_signer, is_writable))| { + InstructionAccount { + index_in_transaction: *index_in_transaction, + index_in_caller: *index_in_transaction, + index_in_callee: instruction_account_index as IndexOfAccount, + is_signer: *is_signer, + is_writable: *is_writable, + } + }, + ) + .collect::>(); + let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); + invoke_context + .transaction_context + .get_next_instruction_context() + .unwrap() + .configure(&program_indices, &instruction_accounts, instruction_data); + let result = invoke_context + .push() + .and_then(|_| super::process_instruction(&mut invoke_context)); + let pop_result = invoke_context.pop(); + assert_eq!(result.and(pop_result), expected_result); + let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap(); + transaction_accounts.pop(); + transaction_accounts + } + + fn load_program_account_from_elf( + is_deployed: bool, + authority_address: Option, + path: &str, + ) -> AccountSharedData { + let path = Path::new("test_elfs/out/").join(path).with_extension("so"); + let mut file = File::open(path).expect("file open failed"); + let mut elf_bytes = Vec::new(); + file.read_to_end(&mut elf_bytes).unwrap(); + let rent = rent::Rent::default(); + let account_size = + loader_v3::LoaderV3State::program_data_offset().saturating_add(elf_bytes.len()); + let mut program_account = AccountSharedData::new( + rent.minimum_balance(account_size), + account_size, + &loader_v3::id(), + ); + program_account + .set_state(&loader_v3::LoaderV3State { + slot: 0, + is_deployed, + authority_address, + }) + .unwrap(); + program_account.data_mut()[loader_v3::LoaderV3State::program_data_offset()..] + .copy_from_slice(&elf_bytes); + program_account + } + + fn clock(slot: Slot) -> AccountSharedData { + let clock = clock::Clock { + slot, + ..clock::Clock::default() + }; + create_account_shared_data_for_test(&clock) + } + + #[test] + fn test_loader_instruction_general_errors() { + let authority_address = Pubkey::new_unique(); + let transaction_accounts = vec![ + ( + Pubkey::new_unique(), + load_program_account_from_elf(true, Some(authority_address), "noop"), + ), + ( + authority_address, + AccountSharedData::new(0, 0, &Pubkey::new_unique()), + ), + ( + Pubkey::new_unique(), + AccountSharedData::new(0, 0, &loader_v3::id()), + ), + ( + Pubkey::new_unique(), + load_program_account_from_elf(false, None, "noop"), + ), + ( + clock::id(), + create_account_shared_data_for_test(&clock::Clock::default()), + ), + ( + rent::id(), + create_account_shared_data_for_test(&rent::Rent::default()), + ), + ]; + + // Error: Missing program account + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Retract).unwrap(), + transaction_accounts.clone(), + &[], + Err(InstructionError::NotEnoughAccountKeys), + ); + + // Error: Missing authority account + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Retract).unwrap(), + transaction_accounts.clone(), + &[(0, false, true)], + Err(InstructionError::NotEnoughAccountKeys), + ); + + // Error: Program not owned by loader + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Retract).unwrap(), + transaction_accounts.clone(), + &[(1, false, true), (1, true, false)], + Err(InstructionError::InvalidAccountOwner), + ); + + // Error: Program is uninitialized + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Retract).unwrap(), + transaction_accounts.clone(), + &[(2, false, true), (1, true, false)], + Err(InstructionError::InvalidAccountData), + ); + + // Error: Program is not writeable + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Retract).unwrap(), + transaction_accounts.clone(), + &[(0, false, false), (1, true, false)], + Err(InstructionError::InvalidArgument), + ); + + // Error: Authority did not sign + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Retract).unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, false, false)], + Err(InstructionError::MissingRequiredSignature), + ); + + // Error: Program is finalized + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Retract).unwrap(), + transaction_accounts.clone(), + &[(3, false, true), (1, true, false)], + Err(InstructionError::Immutable), + ); + + // Error: Incorrect authority provided + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Retract).unwrap(), + transaction_accounts, + &[(0, false, true), (2, true, false)], + Err(InstructionError::IncorrectAuthority), + ); + } + + #[test] + fn test_loader_instruction_write() { + let authority_address = Pubkey::new_unique(); + let mut transaction_accounts = vec![ + ( + Pubkey::new_unique(), + AccountSharedData::new(0, 0, &loader_v3::id()), + ), + ( + authority_address, + AccountSharedData::new(0, 0, &Pubkey::new_unique()), + ), + ( + Pubkey::new_unique(), + AccountSharedData::new(10000000, 0, &loader_v3::id()), + ), + ( + Pubkey::new_unique(), + load_program_account_from_elf(true, Some(authority_address), "noop"), + ), + ( + clock::id(), + create_account_shared_data_for_test(&clock::Clock::default()), + ), + ( + rent::id(), + create_account_shared_data_for_test(&rent::Rent::default()), + ), + ]; + + // Initialize account by first write + let accounts = process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Write { + offset: 0, + bytes: vec![0, 1, 2, 3], + }) + .unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false), (2, true, true)], + Ok(()), + ); + assert_eq!( + accounts[0].data().len(), + loader_v3::LoaderV3State::program_data_offset().saturating_add(4), + ); + assert_eq!(accounts[0].lamports(), 1252800); + assert_eq!( + accounts[2].lamports(), + transaction_accounts[2] + .1 + .lamports() + .saturating_sub(accounts[0].lamports()), + ); + + // Extend account by writing at the end + transaction_accounts[0].1 = accounts[0].clone(); + let accounts = process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Write { + offset: 4, + bytes: vec![4, 5, 6, 7], + }) + .unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false), (2, true, true)], + Ok(()), + ); + assert_eq!( + accounts[0].data().len(), + loader_v3::LoaderV3State::program_data_offset().saturating_add(8), + ); + assert_eq!( + accounts[0].lamports(), + transaction_accounts[0].1.lamports().saturating_add(27840), + ); + assert_eq!( + accounts[2].lamports(), + transaction_accounts[2].1.lamports().saturating_sub( + accounts[0] + .lamports() + .saturating_sub(transaction_accounts[0].1.lamports()), + ), + ); + + // Overwrite existing data (no payer required) + transaction_accounts[0].1 = accounts[0].clone(); + let accounts = process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Write { + offset: 2, + bytes: vec![8, 8, 8, 8], + }) + .unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false)], + Ok(()), + ); + assert_eq!( + accounts[0].data().len(), + loader_v3::LoaderV3State::program_data_offset().saturating_add(8), + ); + assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports()); + + // Error: Program is not retracted + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Write { + offset: 8, + bytes: vec![8, 8, 8, 8], + }) + .unwrap(), + transaction_accounts.clone(), + &[(3, false, true), (1, true, false), (2, true, true)], + Err(InstructionError::InvalidArgument), + ); + + // Error: Payer did not sign + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Write { + offset: 8, + bytes: vec![8, 8, 8, 8], + }) + .unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false), (2, false, true)], + Err(InstructionError::MissingRequiredSignature), + ); + + // Error: Payer is not writeable + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Write { + offset: 8, + bytes: vec![8, 8, 8, 8], + }) + .unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false), (2, true, false)], + Err(InstructionError::InvalidArgument), + ); + + // Error: Write out of bounds + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Write { + offset: 9, + bytes: vec![8, 8, 8, 8], + }) + .unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false), (2, true, true)], + Err(InstructionError::AccountDataTooSmall), + ); + + // Error: Insufficient funds (Bankrupt payer account) + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Write { + offset: 8, + bytes: vec![8, 8, 8, 8], + }) + .unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false), (1, true, true)], + Err(InstructionError::InsufficientFunds), + ); + + // Error: Insufficient funds (No payer account) + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Write { + offset: 8, + bytes: vec![8, 8, 8, 8], + }) + .unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false)], + Err(InstructionError::InsufficientFunds), + ); + } + + #[test] + fn test_loader_instruction_truncate() { + let authority_address = Pubkey::new_unique(); + let transaction_accounts = vec![ + ( + Pubkey::new_unique(), + load_program_account_from_elf(false, Some(authority_address), "noop"), + ), + ( + authority_address, + AccountSharedData::new(0, 0, &Pubkey::new_unique()), + ), + ( + Pubkey::new_unique(), + AccountSharedData::new(0, 0, &loader_v3::id()), + ), + ( + Pubkey::new_unique(), + load_program_account_from_elf(true, Some(authority_address), "noop"), + ), + ( + Pubkey::new_unique(), + AccountSharedData::new(0, 0, &loader_v3::id()), + ), + ( + clock::id(), + create_account_shared_data_for_test(&clock::Clock::default()), + ), + ( + rent::id(), + create_account_shared_data_for_test(&rent::Rent::default()), + ), + ]; + + // Cut the end off + let accounts = process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Truncate { offset: 4 }).unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false), (2, false, true)], + Ok(()), + ); + assert_eq!( + accounts[0].data().len(), + loader_v3::LoaderV3State::program_data_offset().saturating_add(4), + ); + assert_eq!(accounts[0].lamports(), 1252800); + assert_eq!( + accounts[2].lamports(), + transaction_accounts[0] + .1 + .lamports() + .saturating_sub(accounts[0].lamports()), + ); + + // Close program account + let accounts = process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Truncate { offset: 0 }).unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false), (2, false, true)], + Ok(()), + ); + assert_eq!(accounts[0].data().len(), 0); + assert_eq!( + accounts[2].lamports(), + transaction_accounts[0] + .1 + .lamports() + .saturating_sub(accounts[0].lamports()), + ); + + // Error: Program is not retracted + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Truncate { offset: 0 }).unwrap(), + transaction_accounts.clone(), + &[(3, false, true), (1, true, false), (2, false, true)], + Err(InstructionError::InvalidArgument), + ); + + // Error: Truncate out of bounds + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Truncate { offset: 10000 }).unwrap(), + transaction_accounts, + &[(0, false, true), (1, true, false), (2, false, true)], + Err(InstructionError::AccountDataTooSmall), + ); + } + + #[test] + fn test_loader_instruction_deploy() { + let authority_address = Pubkey::new_unique(); + let mut transaction_accounts = vec![ + ( + Pubkey::new_unique(), + load_program_account_from_elf(false, Some(authority_address), "rodata"), + ), + ( + authority_address, + AccountSharedData::new(0, 0, &Pubkey::new_unique()), + ), + ( + Pubkey::new_unique(), + load_program_account_from_elf(false, Some(authority_address), "noop"), + ), + ( + Pubkey::new_unique(), + load_program_account_from_elf(true, Some(authority_address), "noop"), + ), + (clock::id(), clock(1000)), + ( + rent::id(), + create_account_shared_data_for_test(&rent::Rent::default()), + ), + ]; + + // Deploy from its own data + let accounts = process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false)], + Ok(()), + ); + transaction_accounts[0].1 = accounts[0].clone(); + assert_eq!( + accounts[0].data().len(), + transaction_accounts[0].1.data().len(), + ); + assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports()); + + // Redeploy: Retract, then replace data by other source + let accounts = process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Retract).unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false)], + Ok(()), + ); + transaction_accounts[0].1 = accounts[0].clone(); + transaction_accounts[4].1 = clock(2000); + let accounts = process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false), (2, false, true)], + Ok(()), + ); + transaction_accounts[0].1 = accounts[0].clone(); + assert_eq!( + accounts[0].data().len(), + transaction_accounts[2].1.data().len(), + ); + assert_eq!(accounts[2].data().len(), 0,); + assert_eq!( + accounts[2].lamports(), + transaction_accounts[2].1.lamports().saturating_sub( + accounts[0] + .lamports() + .saturating_sub(transaction_accounts[0].1.lamports()) + ), + ); + + // Error: Source program is not retracted + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(), + transaction_accounts.clone(), + &[(2, false, true), (1, true, false), (3, false, true)], + Err(InstructionError::InvalidArgument), + ); + + // Error: Program is deployed already + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(), + transaction_accounts.clone(), + &[(3, false, true), (1, true, false)], + Err(InstructionError::InvalidArgument), + ); + + // Error: Program was deployed recently, cooldown still in effect + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false)], + Err(InstructionError::InvalidArgument), + ); + } + + #[test] + fn test_loader_instruction_retract() { + let authority_address = Pubkey::new_unique(); + let transaction_accounts = vec![ + ( + Pubkey::new_unique(), + load_program_account_from_elf(true, Some(authority_address), "rodata"), + ), + ( + authority_address, + AccountSharedData::new(0, 0, &Pubkey::new_unique()), + ), + ( + Pubkey::new_unique(), + load_program_account_from_elf(false, Some(authority_address), "rodata"), + ), + ( + clock::id(), + create_account_shared_data_for_test(&clock::Clock::default()), + ), + ( + rent::id(), + create_account_shared_data_for_test(&rent::Rent::default()), + ), + ]; + + // Retract program + let accounts = process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Retract).unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false)], + Ok(()), + ); + assert_eq!( + accounts[0].data().len(), + transaction_accounts[0].1.data().len(), + ); + assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports()); + + // Error: Program is not deployed + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::Retract).unwrap(), + transaction_accounts, + &[(2, false, true), (1, true, false)], + Err(InstructionError::InvalidArgument), + ); + } + + #[test] + fn test_loader_instruction_transfer_authority() { + let authority_address = Pubkey::new_unique(); + let transaction_accounts = vec![ + ( + Pubkey::new_unique(), + load_program_account_from_elf(true, Some(authority_address), "rodata"), + ), + ( + authority_address, + AccountSharedData::new(0, 0, &Pubkey::new_unique()), + ), + ( + Pubkey::new_unique(), + AccountSharedData::new(0, 0, &Pubkey::new_unique()), + ), + ( + clock::id(), + create_account_shared_data_for_test(&clock::Clock::default()), + ), + ( + rent::id(), + create_account_shared_data_for_test(&rent::Rent::default()), + ), + ]; + + // Transfer authority + let accounts = process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::TransferAuthority).unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false), (2, true, false)], + Ok(()), + ); + assert_eq!( + accounts[0].data().len(), + transaction_accounts[0].1.data().len(), + ); + assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports()); + + // Finalize program + let accounts = process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::TransferAuthority).unwrap(), + transaction_accounts.clone(), + &[(0, false, true), (1, true, false)], + Ok(()), + ); + assert_eq!( + accounts[0].data().len(), + transaction_accounts[0].1.data().len(), + ); + assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports()); + + // Error: New authority did not sign + process_instruction( + vec![], + &bincode::serialize(&LoaderV3Instruction::TransferAuthority).unwrap(), + transaction_accounts, + &[(0, false, true), (1, true, false), (2, false, false)], + Err(InstructionError::MissingRequiredSignature), + ); + } + + #[test] + fn test_invoke_program() { + let program_address = Pubkey::new_unique(); + let transaction_accounts = vec![ + ( + program_address, + load_program_account_from_elf(true, None, "rodata"), + ), + ( + Pubkey::new_unique(), + AccountSharedData::new(10000000, 32, &program_address), + ), + ( + Pubkey::new_unique(), + AccountSharedData::new(0, 0, &loader_v3::id()), + ), + ( + Pubkey::new_unique(), + load_program_account_from_elf(false, None, "rodata"), + ), + ]; + + // Invoke program + process_instruction( + vec![0], + &[0, 1, 2, 3], + transaction_accounts.clone(), + &[(1, false, true)], + Err(InstructionError::Custom(42)), + ); + + // Error: Program not owned by loader + process_instruction( + vec![1], + &[0, 1, 2, 3], + transaction_accounts.clone(), + &[(1, false, true)], + Err(InstructionError::InvalidAccountOwner), + ); + + // Error: Program is uninitialized + process_instruction( + vec![2], + &[0, 1, 2, 3], + transaction_accounts.clone(), + &[(1, false, true)], + Err(InstructionError::InvalidAccountData), + ); + + // Error: Program is not deployed + process_instruction( + vec![3], + &[0, 1, 2, 3], + transaction_accounts, + &[(1, false, true)], + Err(InstructionError::InvalidArgument), + ); + } +} diff --git a/programs/loader-v3/test_elfs/out/noop.so b/programs/loader-v3/test_elfs/out/noop.so new file mode 100755 index 0000000000000000000000000000000000000000..7e74e2d7a78cde3e2a2a7dda703855bccd0eb505 GIT binary patch literal 1768 zcmbVM!D6L3Gn%6f4n1R}}ayXW0^-@KdG_d53*OG{4HbSvfTyAhj) zADi9@0GsZXYvT8eTXj?(!%%!3BdpP)@)tknyhGhG#sf=T$5kJO3}&WlV0s*5*K|kW z36rlQUO7k=*ob?-#h86Q+$*roEy*)SfqGeTfkX0~1&U;yjNiW69v48`dk?Ok&-Y)5*1FCK$~>s9Lvlf8a+ zJLq)U!L=LVMtHNj`nOO0vimWwuax)QXfEE7)|vmG^@Mr?=#HVw407bxD*#6Jdd9P2a`PWo=2CY={W3G-_o=8lgC?+ zw(shA|BbqP_6|+=EsRR`&yT)?m=!qO?vBkZ@+nZ@s5?> z+TQVW=^%b4#y25vorRQku@Kc?-@@^rZCa8)0C9>LcZsp7(YV zL;3@AgsQWy7k>v@_PF%wQ=#gVzK&bavL@XV+|oYThWh^zulj`#v7p?FA$~D3GLrYm mdsh8A*N*WPwt$vBh~9$nj`E6r3P*289Pyozm#ZZFy8bs<*>lGL literal 0 HcmV?d00001 diff --git a/programs/loader-v3/test_elfs/out/rodata.so b/programs/loader-v3/test_elfs/out/rodata.so new file mode 100755 index 0000000000000000000000000000000000000000..9b8a8b8ed028086865511620166e94704ff3d223 GIT binary patch literal 1904 zcmb_cO=}cE5Uu^tM5CAwl%ODU^OIo{4TxS6jDoUy$jN{p)TB!)FCUZsayAhj)jkz3z2?qIKMzVogtmkqBN z5`G>}6Ziv*=f1vLpK<>}Y-d?2<-G+axS+TsRavzCGJlthFYn3XaelgzjpxN9G*6xRmaD$_>5t_REnEInd9aL*&-EkcEH7k#}zgc-iJ%ji2c+L2~@)%~F z{Y=6%X-8QkVLOkbey>$H!@Tb>OC{WkKZMpKmm#5sXVF{5?l4Q8 z=TO|_VQc24z1iM`9GLZ@-ITe!O8mw~6VA`=|66qMrK-lw-{zl<%I%caJfcP47DTy{YZj zbeeE|{s)M6Et2nn>vMv2b&!q^I;T{6z$x};yL!`dMStlL3v KoF>)x^?v|FRc+S* literal 0 HcmV?d00001