solana_program_profiler/
analyzer.rs1use crate::error::ProfilerError;
2use solana_rbpf::{
3 ebpf,
4 elf::Executable,
5 program::BuiltinProgram,
6 static_analysis::Analysis,
7 vm::TestContextObject, };
9use std::io::Read;
10use std::path::Path;
11use std::sync::Arc;
12use serde_json::{json, to_string_pretty};
13
14pub struct AnalysisResult {
15 pub total_cu: u64,
16 pub suggestions: Vec<(usize, String)>,
17 pub json_output: String,
18}
19
20pub fn analyze_program(file_path: &Path) -> Result<AnalysisResult, ProfilerError> {
21 let mut file = std::fs::File::open(file_path)
23 .map_err(|e| ProfilerError::IoError(file_path.display().to_string(), e))?;
24 let mut elf_bytes = Vec::new();
25 file.read_to_end(&mut elf_bytes)
26 .map_err(|e| ProfilerError::IoError(file_path.display().to_string(), e))?;
27
28 let loader = Arc::new(BuiltinProgram::<TestContextObject>::new_mock()); let executable = Executable::load(&elf_bytes, loader)
30 .map_err(|e| ProfilerError::ElfError(e.to_string()))?;
31
32 let analysis = Analysis::from_executable(&executable)
34 .map_err(|e| ProfilerError::AnalysisError(e.to_string()))?;
35
36 let mut total_cu = 0;
38 let mut suggestions = Vec::new();
39
40 for (pc, insn) in analysis.instructions.iter().enumerate() {
41 let cu = get_cu_cost(insn);
42 total_cu += cu;
43
44 if insn.opc == ebpf::CALL_REG {
46 suggestions.push((
47 pc,
48 "检测到系统调用,建议减少或批量处理系统调用。".to_string(),
49 ));
50 } else if insn.opc == ebpf::DIV64_IMM {
51 suggestions.push((
52 pc,
53 "除法操作成本高,考虑使用位移操作替代。".to_string(),
54 ));
55 }
56
57 if pc < analysis.instructions.len() - 1 {
59 let next = &analysis.instructions[pc + 1];
60 if insn.opc == ebpf::LD_DW_IMM
61 && next.opc == ebpf::LD_DW_IMM
62 && insn.imm == next.imm
63 {
64 suggestions.push((pc, "检测到冗余加载,建议合并为单次加载。".to_string()));
65 }
66 }
67
68 if insn.opc == ebpf::JA && insn.off < 0 {
70 suggestions.push((pc, "检测到循环,建议对小型循环进行展开。".to_string()));
71 }
72 }
73
74 let json_output = to_json(&analysis, total_cu, &suggestions)?;
76
77 Ok(AnalysisResult {
78 total_cu,
79 suggestions,
80 json_output,
81 })
82}
83
84fn get_cu_cost(insn: &ebpf::Insn) -> u64 {
85 match insn.opc {
86 ebpf::ADD64_IMM | ebpf::SUB64_IMM => 1,
87 ebpf::LD_DW_IMM => 4,
88 ebpf::CALL_IMM => 5,
89 ebpf::CALL_REG => 10,
90 _ => 2,
91 }
92}
93
94fn to_json(
95 analysis: &Analysis,
96 total_cu: u64,
97 suggestions: &[(usize, String)],
98) -> Result<String, ProfilerError> {
99 let mut json_insns = vec![];
100 for (pc, insn) in analysis.instructions.iter().enumerate() {
101 json_insns.push(json!({
102 "pc": pc,
103 "opc": format!("{:#x}", insn.opc),
104 "dst": format!("{:#x}", insn.dst),
105 "src": format!("{:#x}", insn.src),
106 "off": format!("{:#x}", insn.off),
107 "imm": format!("{:#x}", insn.imm as i32),
108 "desc": analysis.disassemble_instruction(insn),
109 }));
110 }
111
112 let json_suggestions = suggestions
113 .iter()
114 .map(|(pc, s)| json!({ "pc": *pc, "suggestion": s }))
115 .collect::<Vec<_>>();
116
117 let json_output = json!({
118 "size": json_insns.len(),
119 "cu": total_cu,
120 "instructions": json_insns,
121 "suggestions": json_suggestions
122 });
123
124 to_string_pretty(&json_output)
125 .map_err(|e| ProfilerError::AnalysisError(format!("JSON serialization failed: {}", e)))
126}