Skip to main content

sbpf_debugger/
syscalls.rs

1use {
2    crate::execution_cost::ExecutionCost,
3    blake3::Hasher as Blake3Hasher,
4    sbpf_vm::{
5        compute::ComputeMeter,
6        errors::{SbpfVmError, SbpfVmResult},
7        memory::Memory,
8        syscalls::SyscallHandler,
9    },
10    sha2::{Digest, Sha256},
11    sha3::Keccak256,
12    solana_address::Address,
13    solana_clock::Clock,
14    solana_epoch_schedule::EpochSchedule,
15    solana_rent::Rent,
16    std::mem::size_of,
17};
18
19const MAX_SEED_LEN: usize = 32;
20const MAX_SEEDS: usize = 16;
21
22/// Debugger syscall handler
23#[derive(Debug)]
24pub struct DebuggerSyscallHandler {
25    pub costs: ExecutionCost,
26    pub current_program_id: Address,
27    pub clock: Clock,
28    pub rent: Rent,
29    pub epoch_schedule: EpochSchedule,
30}
31
32impl DebuggerSyscallHandler {
33    pub fn new(costs: ExecutionCost, current_program_id: Address) -> Self {
34        Self {
35            costs,
36            current_program_id,
37            clock: Clock::default(),
38            rent: Rent::default(),
39            epoch_schedule: EpochSchedule::default(),
40        }
41    }
42
43    fn sol_log(
44        &mut self,
45        registers: [u64; 5],
46        memory: &Memory,
47        compute: &ComputeMeter,
48    ) -> SbpfVmResult<u64> {
49        let msg_ptr = registers[0];
50        let msg_len = registers[1];
51
52        let cost = self.costs.syscall_base_cost.max(msg_len);
53        compute.consume(cost)?;
54
55        let msg_bytes = memory.read_bytes(msg_ptr, msg_len as usize)?;
56        let msg = String::from_utf8_lossy(msg_bytes);
57        println!("Program log: {}", msg);
58        Ok(0)
59    }
60
61    fn sol_log_64(&mut self, registers: [u64; 5], compute: &ComputeMeter) -> SbpfVmResult<u64> {
62        let cost = self.costs.log_64_units;
63        compute.consume(cost)?;
64        println!(
65            "Program log: {:#x}, {:#x}, {:#x}, {:#x}, {:#x}",
66            registers[0], registers[1], registers[2], registers[3], registers[4]
67        );
68        Ok(0)
69    }
70
71    fn sol_log_pubkey(
72        &mut self,
73        registers: [u64; 5],
74        memory: &Memory,
75        compute: &ComputeMeter,
76    ) -> SbpfVmResult<u64> {
77        let cost = self.costs.log_pubkey_units;
78        compute.consume(cost)?;
79
80        let pubkey_ptr = registers[0];
81        let pubkey_bytes = memory.read_bytes(pubkey_ptr, 32)?;
82        let pubkey_base58 = bs58::encode(pubkey_bytes).into_string();
83        println!("Program log: {}", pubkey_base58);
84        Ok(0)
85    }
86
87    fn sol_log_compute_units(&mut self, compute: &ComputeMeter) -> SbpfVmResult<u64> {
88        let cost = self.costs.syscall_base_cost;
89        compute.consume(cost)?;
90
91        let remaining = compute.get_remaining();
92        println!("Program consumption: {} units remaining", remaining);
93        Ok(0)
94    }
95
96    fn sol_remaining_compute_units(&mut self, compute: &ComputeMeter) -> SbpfVmResult<u64> {
97        let cost = self.costs.syscall_base_cost;
98        compute.consume(cost)?;
99        Ok(compute.get_remaining())
100    }
101
102    fn mem_op_consume(&self, n: u64, compute: &ComputeMeter) -> SbpfVmResult<()> {
103        let cost = self.costs.mem_op_base_cost.max(
104            n.checked_div(self.costs.cpi_bytes_per_unit)
105                .unwrap_or(u64::MAX),
106        );
107        compute.consume(cost)
108    }
109
110    fn is_nonoverlapping(src: u64, src_len: u64, dst: u64, dst_len: u64) -> bool {
111        if src > dst {
112            src.saturating_sub(dst) >= dst_len
113        } else {
114            dst.saturating_sub(src) >= src_len
115        }
116    }
117
118    fn sol_memcpy(
119        &mut self,
120        registers: [u64; 5],
121        memory: &mut Memory,
122        compute: &ComputeMeter,
123    ) -> SbpfVmResult<u64> {
124        let dst = registers[0];
125        let src = registers[1];
126        let n = registers[2];
127
128        self.mem_op_consume(n, compute)?;
129
130        if !Self::is_nonoverlapping(src, n, dst, n) {
131            return Err(SbpfVmError::OverlappingMemoryRegions);
132        }
133
134        let data = memory.read_bytes(src, n as usize)?.to_vec();
135        memory.write_bytes(dst, &data)?;
136        Ok(0)
137    }
138
139    fn sol_memmove(
140        &mut self,
141        registers: [u64; 5],
142        memory: &mut Memory,
143        compute: &ComputeMeter,
144    ) -> SbpfVmResult<u64> {
145        let dst = registers[0];
146        let src = registers[1];
147        let n = registers[2];
148
149        self.mem_op_consume(n, compute)?;
150
151        let data = memory.read_bytes(src, n as usize)?.to_vec();
152        memory.write_bytes(dst, &data)?;
153
154        Ok(0)
155    }
156
157    fn sol_memset(
158        &mut self,
159        registers: [u64; 5],
160        memory: &mut Memory,
161        compute: &ComputeMeter,
162    ) -> SbpfVmResult<u64> {
163        let dst = registers[0];
164        let c = registers[1] as u8;
165        let n = registers[2];
166
167        self.mem_op_consume(n, compute)?;
168
169        let data = vec![c; n as usize];
170        memory.write_bytes(dst, &data)?;
171
172        Ok(0)
173    }
174
175    fn sol_memcmp(
176        &mut self,
177        registers: [u64; 5],
178        memory: &mut Memory,
179        compute: &ComputeMeter,
180    ) -> SbpfVmResult<u64> {
181        let s1 = registers[0];
182        let s2 = registers[1];
183        let n = registers[2];
184        let result_ptr = registers[3];
185
186        self.mem_op_consume(n, compute)?;
187
188        let s1_bytes = memory.read_bytes(s1, n as usize)?;
189        let s2_bytes = memory.read_bytes(s2, n as usize)?;
190
191        let mut result: i32 = 0;
192        for i in 0..n as usize {
193            if s1_bytes[i] != s2_bytes[i] {
194                result = (s1_bytes[i] as i32).saturating_sub(s2_bytes[i] as i32);
195                break;
196            }
197        }
198
199        memory.write_u32(result_ptr, result as u32)?;
200
201        Ok(0)
202    }
203
204    fn abort(&mut self) -> SbpfVmResult<u64> {
205        Err(SbpfVmError::Abort)
206    }
207
208    fn sol_panic(
209        &mut self,
210        registers: [u64; 5],
211        memory: &Memory,
212        compute: &ComputeMeter,
213    ) -> SbpfVmResult<u64> {
214        let file_ptr = registers[0];
215        let file_len = registers[1];
216        let line = registers[2];
217        let column = registers[3];
218
219        compute.consume(file_len)?;
220
221        let file_bytes = memory.read_bytes(file_ptr, file_len as usize)?;
222        let file = String::from_utf8_lossy(file_bytes);
223
224        eprintln!("Program panicked at {}:{}:{}", file, line, column);
225
226        Err(SbpfVmError::Abort)
227    }
228
229    fn read_slices(
230        &self,
231        memory: &Memory,
232        vals_addr: u64,
233        vals_len: u64,
234    ) -> SbpfVmResult<Vec<(u64, u64)>> {
235        let mut slices = Vec::with_capacity(vals_len as usize);
236        for i in 0..vals_len {
237            let slice_addr = vals_addr.saturating_add(i.saturating_mul(16));
238            let ptr = memory.read_u64(slice_addr)?;
239            let len = memory.read_u64(slice_addr.saturating_add(8))?;
240            slices.push((ptr, len));
241        }
242        Ok(slices)
243    }
244
245    fn hash_slices<H: Digest>(
246        &mut self,
247        memory: &mut Memory,
248        compute: &ComputeMeter,
249        vals_addr: u64,
250        vals_len: u64,
251        result_addr: u64,
252    ) -> SbpfVmResult<u64> {
253        if vals_len > self.costs.sha256_max_slices {
254            return Err(SbpfVmError::TooManySlices);
255        }
256
257        compute.consume(self.costs.sha256_base_cost)?;
258
259        let mut hasher = H::new();
260        if vals_len > 0 {
261            for (ptr, len) in self.read_slices(memory, vals_addr, vals_len)? {
262                let cost = self
263                    .costs
264                    .mem_op_base_cost
265                    .max(self.costs.sha256_byte_cost.saturating_mul(len / 2));
266                compute.consume(cost)?;
267                hasher.update(memory.read_bytes(ptr, len as usize)?);
268            }
269        }
270
271        memory.write_bytes(result_addr, &hasher.finalize())?;
272        Ok(0)
273    }
274
275    fn sol_sha256(
276        &mut self,
277        registers: [u64; 5],
278        memory: &mut Memory,
279        compute: &ComputeMeter,
280    ) -> SbpfVmResult<u64> {
281        self.hash_slices::<Sha256>(memory, compute, registers[0], registers[1], registers[2])
282    }
283
284    fn sol_keccak256(
285        &mut self,
286        registers: [u64; 5],
287        memory: &mut Memory,
288        compute: &ComputeMeter,
289    ) -> SbpfVmResult<u64> {
290        self.hash_slices::<Keccak256>(memory, compute, registers[0], registers[1], registers[2])
291    }
292
293    fn sol_blake3(
294        &mut self,
295        registers: [u64; 5],
296        memory: &mut Memory,
297        compute: &ComputeMeter,
298    ) -> SbpfVmResult<u64> {
299        self.hash_slices::<Blake3Hasher>(memory, compute, registers[0], registers[1], registers[2])
300    }
301
302    fn read_seeds(
303        &self,
304        memory: &Memory,
305        seeds_addr: u64,
306        seeds_len: u64,
307    ) -> SbpfVmResult<Vec<Vec<u8>>> {
308        if seeds_len as usize > MAX_SEEDS {
309            return Err(SbpfVmError::MaxSeedLengthExceeded);
310        }
311
312        let mut seeds = Vec::with_capacity(seeds_len as usize);
313        for i in 0..seeds_len {
314            let slice_addr = seeds_addr.saturating_add(i.saturating_mul(16));
315            let ptr = memory.read_u64(slice_addr)?;
316            let len = memory.read_u64(slice_addr.saturating_add(8))?;
317
318            if len as usize > MAX_SEED_LEN {
319                return Err(SbpfVmError::MaxSeedLengthExceeded);
320            }
321
322            let seed = memory.read_bytes(ptr, len as usize)?.to_vec();
323            seeds.push(seed);
324        }
325        Ok(seeds)
326    }
327
328    fn sol_create_program_address(
329        &mut self,
330        registers: [u64; 5],
331        memory: &mut Memory,
332        compute: &ComputeMeter,
333    ) -> SbpfVmResult<u64> {
334        let seeds_addr = registers[0];
335        let seeds_len = registers[1];
336        let program_id_addr = registers[2];
337        let address_addr = registers[3];
338
339        let cost = self.costs.create_program_address_units;
340        compute.consume(cost)?;
341
342        let seeds = self.read_seeds(memory, seeds_addr, seeds_len)?;
343        let program_id = Address::from(
344            <[u8; 32]>::try_from(memory.read_bytes(program_id_addr, 32)?)
345                .map_err(|_| SbpfVmError::InvalidSliceConversion)?,
346        );
347
348        let seed_slices: Vec<&[u8]> = seeds.iter().map(|s| s.as_slice()).collect();
349        match Address::create_program_address(&seed_slices, &program_id) {
350            Ok(addr) => {
351                memory.write_bytes(address_addr, addr.as_ref())?;
352                Ok(0)
353            }
354            Err(_) => Ok(1),
355        }
356    }
357
358    fn sol_try_find_program_address(
359        &mut self,
360        registers: [u64; 5],
361        memory: &mut Memory,
362        compute: &ComputeMeter,
363    ) -> SbpfVmResult<u64> {
364        let seeds_addr = registers[0];
365        let seeds_len = registers[1];
366        let program_id_addr = registers[2];
367        let address_addr = registers[3];
368        let bump_seed_addr = registers[4];
369
370        let cost = self.costs.create_program_address_units;
371        compute.consume(cost)?;
372
373        let seeds = self.read_seeds(memory, seeds_addr, seeds_len)?;
374        let program_id = Address::from(
375            <[u8; 32]>::try_from(memory.read_bytes(program_id_addr, 32)?)
376                .map_err(|_| SbpfVmError::InvalidSliceConversion)?,
377        );
378
379        let seed_slices: Vec<&[u8]> = seeds.iter().map(|s| s.as_slice()).collect();
380        match Address::try_find_program_address(&seed_slices, &program_id) {
381            Some((addr, bump)) => {
382                memory.write_u8(bump_seed_addr, bump)?;
383                memory.write_bytes(address_addr, addr.as_ref())?;
384                Ok(0)
385            }
386            None => Ok(1),
387        }
388    }
389
390    fn write_sysvar_bytes<T>(
391        &self,
392        memory: &mut Memory,
393        addr: u64,
394        sysvar: &T,
395    ) -> SbpfVmResult<()> {
396        let bytes =
397            unsafe { std::slice::from_raw_parts(sysvar as *const T as *const u8, size_of::<T>()) };
398        memory.write_bytes(addr, bytes)
399    }
400
401    fn sol_get_clock_sysvar(
402        &mut self,
403        registers: [u64; 5],
404        memory: &mut Memory,
405        compute: &ComputeMeter,
406    ) -> SbpfVmResult<u64> {
407        let cost = self
408            .costs
409            .sysvar_base_cost
410            .saturating_add(size_of::<Clock>() as u64);
411        compute.consume(cost)?;
412        let clock = self.clock.clone();
413        self.write_sysvar_bytes(memory, registers[0], &clock)?;
414        Ok(0)
415    }
416
417    fn sol_get_rent_sysvar(
418        &mut self,
419        registers: [u64; 5],
420        memory: &mut Memory,
421        compute: &ComputeMeter,
422    ) -> SbpfVmResult<u64> {
423        let cost = self
424            .costs
425            .sysvar_base_cost
426            .saturating_add(size_of::<Rent>() as u64);
427        compute.consume(cost)?;
428        let rent = self.rent.clone();
429        self.write_sysvar_bytes(memory, registers[0], &rent)?;
430        Ok(0)
431    }
432
433    fn sol_get_epoch_schedule_sysvar(
434        &mut self,
435        registers: [u64; 5],
436        memory: &mut Memory,
437        compute: &ComputeMeter,
438    ) -> SbpfVmResult<u64> {
439        let cost = self
440            .costs
441            .sysvar_base_cost
442            .saturating_add(size_of::<EpochSchedule>() as u64);
443        compute.consume(cost)?;
444        let epoch_schedule = self.epoch_schedule.clone();
445        self.write_sysvar_bytes(memory, registers[0], &epoch_schedule)?;
446        Ok(0)
447    }
448}
449
450impl SyscallHandler for DebuggerSyscallHandler {
451    fn handle(
452        &mut self,
453        name: &str,
454        registers: [u64; 5],
455        memory: &mut Memory,
456        compute: ComputeMeter,
457    ) -> SbpfVmResult<u64> {
458        match name {
459            // Logging
460            "sol_log_" => self.sol_log(registers, memory, &compute),
461            "sol_log_64_" => self.sol_log_64(registers, &compute),
462            "sol_log_pubkey" => self.sol_log_pubkey(registers, memory, &compute),
463            "sol_log_compute_units_" => self.sol_log_compute_units(&compute),
464            "sol_remaining_compute_units" => self.sol_remaining_compute_units(&compute),
465
466            // Memory
467            "sol_memcpy_" => self.sol_memcpy(registers, memory, &compute),
468            "sol_memmove_" => self.sol_memmove(registers, memory, &compute),
469            "sol_memset_" => self.sol_memset(registers, memory, &compute),
470            "sol_memcmp_" => self.sol_memcmp(registers, memory, &compute),
471
472            // Abort
473            "abort" => self.abort(),
474            "sol_panic_" => self.sol_panic(registers, memory, &compute),
475
476            // Hashing
477            "sol_sha256" => self.sol_sha256(registers, memory, &compute),
478            "sol_keccak256" => self.sol_keccak256(registers, memory, &compute),
479            "sol_blake3" => self.sol_blake3(registers, memory, &compute),
480
481            // PDA
482            "sol_create_program_address" => {
483                self.sol_create_program_address(registers, memory, &compute)
484            }
485            "sol_try_find_program_address" => {
486                self.sol_try_find_program_address(registers, memory, &compute)
487            }
488
489            // Sysvars
490            "sol_get_clock_sysvar" => self.sol_get_clock_sysvar(registers, memory, &compute),
491            "sol_get_rent_sysvar" => self.sol_get_rent_sysvar(registers, memory, &compute),
492            "sol_get_epoch_schedule_sysvar" => {
493                self.sol_get_epoch_schedule_sysvar(registers, memory, &compute)
494            }
495
496            // Unknown syscall
497            _ => {
498                let cost = self.costs.syscall_base_cost;
499                compute.consume(cost)?;
500                eprintln!("Unknown syscall: {}", name);
501                Ok(0)
502            }
503        }
504    }
505}
506
507#[cfg(test)]
508mod tests {
509    use super::*;
510
511    fn test_handler() -> DebuggerSyscallHandler {
512        DebuggerSyscallHandler::new(ExecutionCost::default(), Address::new_unique())
513    }
514
515    #[test]
516    fn test_sol_log_64_cu_consumption() {
517        let mut handler = test_handler();
518        let compute = ComputeMeter::new(1000);
519        handler.sol_log_64([1, 2, 3, 4, 5], &compute).unwrap();
520        assert_eq!(compute.borrow().consumed, 100);
521    }
522
523    #[test]
524    fn test_sol_log_64_budget_exceeded() {
525        let mut handler = test_handler();
526        let compute = ComputeMeter::new(50);
527        let result = handler.sol_log_64([1, 2, 3, 4, 5], &compute);
528        assert!(matches!(
529            result,
530            Err(SbpfVmError::ComputeBudgetExceeded { .. })
531        ));
532    }
533}