solana_program_profiler/
analyzer.rs

1use crate::error::ProfilerError;
2use solana_rbpf::{
3    ebpf,
4    elf::Executable,
5    program::BuiltinProgram,
6    static_analysis::Analysis,
7    vm::TestContextObject, // 添加 TestContextObject 导入
8};
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    // 加载 ELF 文件
22    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()); // 指定 TestContextObject
29    let executable = Executable::load(&elf_bytes, loader)
30        .map_err(|e| ProfilerError::ElfError(e.to_string()))?;
31
32    // 分析指令
33    let analysis = Analysis::from_executable(&executable)
34        .map_err(|e| ProfilerError::AnalysisError(e.to_string()))?;
35
36    // 估算 CU 和收集优化建议
37    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        // 高成本指令检测
45        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        // 冗余加载检测
58        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        // 循环检测
69        if insn.opc == ebpf::JA && insn.off < 0 {
70            suggestions.push((pc, "检测到循环,建议对小型循环进行展开。".to_string()));
71        }
72    }
73
74    // 生成 JSON 输出
75    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}