Преглед на файлове

feat: catch error from instruction byte parse

Claire xyz преди 1 месец
родител
ревизия
33d3e4954c
променени са 9 файла, в които са добавени 173 реда и са изтрити 111 реда
  1. 0 1
      Cargo.lock
  2. 5 1
      Cargo.toml
  3. 21 0
      LICENSE
  4. 11 6
      Readme.md
  5. 3 0
      rustfmt.toml
  6. 18 20
      src/bin/sbpf-link.rs
  7. 47 40
      src/bin/sbpf-linker.rs
  8. 49 34
      src/byteparser.rs
  9. 19 9
      src/lib.rs

+ 0 - 1
Cargo.lock

@@ -841,7 +841,6 @@ dependencies = [
 name = "sbpf-linker"
 version = "0.1.3"
 dependencies = [
- "anyhow",
  "aya-rustc-llvm-proxy",
  "bpf-linker",
  "clap",

+ 5 - 1
Cargo.toml

@@ -5,6 +5,11 @@ version = "0.1.3"
 authors = ["Claire Fan <claire@blueshift.gg>"]
 description = "Upstream BPF linker for SBPF V0 programs"
 license = "MIT"
+readme = "README.md"
+homepage = "https://github.com/blueshift-gg/sbpf-linker"
+repository = "https://github.com/blueshift-gg/sbpf-linker"
+keywords = ["cli", "sbpf", "linker", "solana"]
+categories = ["command-line-utilities"]
 
 [lib]
 crate-type = ["cdylib", "lib"]
@@ -16,7 +21,6 @@ sbpf-assembler = "0.1.2"
 sbpf-common = "0.1.2"
 clap = { version = "4.5.13", features = ["derive"] }
 object = "0.37.3"
-anyhow = "1.0"
 bpf-linker = "0.9.15"
 thiserror = "2.0.12"
 

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Claire Fan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 11 - 6
Readme.md

@@ -1,19 +1,24 @@
-# SBPF Linker
+<h1 align="center">
+  SBPF Linker
+</h1>
+<p align="center">
+  An upstream BPF linker to relink upstream BPF binaries into an SBPF V0 compatible binary format.
+</p>
 
-An upstream BPF linker to relink upstream BPF binaries into an SBPF V0 compatible binary format.
+### Install
 
-### Usage
-Install with:
 ```sh
 cargo install sbpf-linker
 ```
 
-Create a new program template with
+### Generate a Program
+
 ```sh
 cargo generate --git https://github.com/blueshift-gg/solana-upstream-bpf-template
 ```
 
-Build program with:
+### Build
+
 ```sh
 cargo build-bpf
 ```

+ 3 - 0
rustfmt.toml

@@ -0,0 +1,3 @@
+max_width = 79
+use_small_heuristics = "Max"
+edition = "2024"

+ 18 - 20
src/bin/sbpf-link.rs

@@ -1,17 +1,7 @@
-use std::path::{Path, PathBuf};
-use std::fs;
 use clap::Parser;
-use sbpf_linker::link_program;
-
-/// Links an object file by reading it from the given path and processing its bytecode
-fn link_object_file<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, String> {
-    // Read the object file into a byte array
-    let bytes = fs::read(path.as_ref())
-        .map_err(|e| format!("Failed to read object file: {}", e))?;
-    
-    // Call link_program on the bytes
-    link_program(&bytes)
-}
+use sbpf_linker::{SbpfLinkerError, link_program};
+use std::fs;
+use std::path::{Path, PathBuf};
 
 #[derive(Debug, Parser)]
 #[command(
@@ -25,20 +15,18 @@ struct Args {
     input: PathBuf,
 }
 
-fn main() -> Result<(), Box<dyn std::error::Error>> {
+fn main() -> Result<(), SbpfLinkerError> {
     let args = Args::parse();
 
     // Link the object file
     println!("Linking: {}", args.input.display());
-    let linked_bytecode = link_object_file(&args.input)
-        .map_err(|e| format!("Failed to link object file: {}", e))?;
+    let linked_bytecode = link_object_file(&args.input)?;
 
     // Determine output path in same directory with .so extension
     let parent = args.input.parent().unwrap_or_else(|| Path::new("."));
-    let stem = args.input.file_stem()
-        .and_then(|s| s.to_str())
-        .unwrap_or("output");
-    let output = parent.join(format!("{}.so", stem));
+    let stem =
+        args.input.file_stem().and_then(|s| s.to_str()).unwrap_or("output");
+    let output = parent.join(format!("{stem}.so"));
 
     // Write the output
     println!("Writing output to: {}", output.display());
@@ -48,3 +36,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     Ok(())
 }
 
+/// Links an object file by reading it from the given path and processing its bytecode
+fn link_object_file<P: AsRef<Path>>(
+    path: P,
+) -> Result<Vec<u8>, SbpfLinkerError> {
+    // Read the object file into a byte array
+    let bytes = fs::read(path.as_ref())?;
+
+    // Call link_program on the bytes
+    link_program(&bytes)
+}

+ 47 - 40
src/bin/sbpf-linker.rs

@@ -1,9 +1,4 @@
-use std::{
-    env, fs,
-    ffi::CString,
-    path::PathBuf,
-    str::FromStr,
-};
+use std::{env, ffi::CString, fs, path::PathBuf, str::FromStr};
 
 #[cfg(any(
     feature = "rust-llvm-19",
@@ -11,21 +6,26 @@ use std::{
     feature = "rust-llvm-21"
 ))]
 use aya_rustc_llvm_proxy as _;
-use anyhow::Context;
-use thiserror::Error;
 use bpf_linker::{Cpu, Linker, LinkerOptions, OptLevel, OutputType};
-use clap::{
-    error::ErrorKind,
-    Parser,
-};
-use sbpf_linker::link_program;
+use clap::{Parser, error::ErrorKind};
+use sbpf_linker::{SbpfLinkerError, link_program};
 
-#[derive(Debug, Error)]
+#[derive(Debug, thiserror::Error)]
 enum CliError {
-    #[error("optimization level needs to be between 0-3, s or z (instead was `{0}`)")]
+    #[error(
+        "optimization level needs to be between 0-3, s or z (instead was `{0}`)"
+    )]
     InvalidOptimization(String),
-//     #[error("unknown emission type: `{0}` - expected one of: `llvm-bc`, `asm`, `llvm-ir`, `obj`")]
-//     InvalidOutputType(String),
+    #[error("SBPF Linker Error. Error detail: ({0}).")]
+    SbpfLinkerError(#[from] SbpfLinkerError),
+    #[error("Clap Error. Error detail: ({0}).")]
+    ClapError(#[from] clap::error::Error),
+    #[error("Program Read Error. Error detail: ({msg}).")]
+    ProgramReadError { msg: String },
+    #[error("Program Write Error. Error detail: ({msg}).")]
+    ProgramWriteError { msg: String },
+    //     #[error("unknown emission type: `{0}` - expected one of: `llvm-bc`, `asm`, `llvm-ir`, `obj`")]
+    //     InvalidOutputType(String),
 }
 
 #[derive(Copy, Clone, Debug)]
@@ -53,7 +53,7 @@ struct CommandLine {
     /// Target BPF processor. Can be one of `generic`, `probe`, `v1`, `v2`, `v3`
     #[clap(long, default_value = "generic")]
     cpu: Cpu,
-    
+
     /// Write output to <output>
     #[clap(short, long)]
     output: PathBuf,
@@ -62,8 +62,8 @@ struct CommandLine {
     #[clap(long)]
     btf: bool,
 
-    /// Permit automatic insertion of __bpf_trap calls.
-    /// See: https://github.com/llvm/llvm-project/commit/ab391beb11f733b526b86f9df23734a34657d876
+    /// Permit automatic insertion of `__bpf_trap` calls.
+    /// See: <https://github.com/llvm/llvm-project/commit/ab391beb11f733b526b86f9df23734a34657d876>
     #[clap(long)]
     allow_bpf_trap: bool,
 
@@ -99,7 +99,7 @@ struct CommandLine {
     #[clap(long)]
     disable_expand_memcpy_in_order: bool,
 
-    /// Disable exporting memcpy, memmove, memset, memcmp and bcmp. Exporting
+    /// Disable exporting `memcpy`, `memmove`, `memset`, `memcmp` and `bcmp`. Exporting
     /// those is commonly needed when LLVM does not manage to expand memory
     /// intrinsics to a sequence of loads and stores.
     #[clap(long)]
@@ -122,13 +122,9 @@ struct CommandLine {
     _debug: bool,
 }
 
-fn main() -> anyhow::Result<()> {
+fn main() -> Result<(), CliError> {
     let args = env::args().map(|arg| {
-        if arg == "-flavor" {
-            "--flavor".to_string()
-        } else {
-            arg
-        }
+        if arg == "-flavor" { "--flavor".to_string() } else { arg }
     });
 
     let CommandLine {
@@ -160,7 +156,10 @@ fn main() -> anyhow::Result<()> {
         },
     };
 
-    let export_symbols = export_symbols.map(fs::read_to_string).transpose()?;
+    let export_symbols =
+        export_symbols.map(fs::read_to_string).transpose().map_err(|e| {
+            CliError::SbpfLinkerError(SbpfLinkerError::ObjectFileReadError(e))
+        })?;
 
     // TODO: the data is owned by this call frame; we could make this zero-alloc.
     let export_symbols = export_symbols
@@ -171,16 +170,16 @@ fn main() -> anyhow::Result<()> {
         .chain(export)
         .map(Into::into)
         .collect();
-    
+
     let optimize = match *optimize.as_slice() {
         [] => unreachable!("emit has a default value"),
         [.., CliOptLevel(optimize)] => optimize,
     };
-    
+
     let mut linker = Linker::new(LinkerOptions {
         target: Some("bpf".to_string()),
         cpu,
-        cpu_features: "".to_string(),
+        cpu_features: String::new(),
         inputs,
         output: output.clone(),
         output_type: OutputType::Object,
@@ -190,24 +189,31 @@ fn main() -> anyhow::Result<()> {
         unroll_loops,
         ignore_inline_never,
         dump_module,
-        llvm_args: llvm_args.into_iter().map(|cstring| cstring.into_string().unwrap_or_default()).collect(),
+        llvm_args: llvm_args
+            .into_iter()
+            .map(|cstring| cstring.into_string().unwrap_or_default())
+            .collect(),
         disable_expand_memcpy_in_order,
         disable_memory_builtins,
         btf,
         allow_bpf_trap,
     });
 
-    linker.link()?;
+    linker.link().map_err(|e| {
+        CliError::SbpfLinkerError(SbpfLinkerError::LinkerError(e))
+    })?;
 
     if fatal_errors && linker.has_errors() {
-        return Err(anyhow::anyhow!(
-            "LLVM issued diagnostic with error severity"
+        return Err(CliError::SbpfLinkerError(
+            SbpfLinkerError::LlvmDiagnosticError,
         ));
     }
 
-    let program = std::fs::read(&output).context("Failed to read bytecode")?;
-    let bytecode = link_program(&program)
-        .map_err(|e| anyhow::anyhow!("Link error: {}", e))?;
+    let program = std::fs::read(&output)
+        .map_err(|e| CliError::ProgramReadError { msg: e.to_string() })?;
+    let bytecode =
+        link_program(&program).map_err(CliError::SbpfLinkerError)?;
+
     let src_name = std::path::Path::new(&output)
         .file_stem()
         .and_then(|s| s.to_str())
@@ -215,8 +221,9 @@ fn main() -> anyhow::Result<()> {
     let output_path = std::path::Path::new(&output)
         .parent()
         .unwrap_or_else(|| std::path::Path::new("."))
-        .join(format!("{}.so", src_name));
-    std::fs::write(output_path, bytecode)?;
+        .join(format!("{src_name}.so"));
+    std::fs::write(output_path, bytecode)
+        .map_err(|e| CliError::ProgramWriteError { msg: e.to_string() })?;
 
     Ok(())
 }

+ 49 - 34
src/byteparser.rs

@@ -5,38 +5,47 @@ use sbpf_assembler::lexer::{ImmediateValue, Token};
 use sbpf_assembler::parser::ParseResult;
 use sbpf_common::opcode::Opcode;
 
-use object::{File, Object, ObjectSection, ObjectSymbol};
 use object::RelocationTarget::Symbol;
+use object::{File, Object as _, ObjectSection as _, ObjectSymbol as _};
 
-use anyhow::Result;
 use std::collections::HashMap;
 
-pub fn parse_bytecode(bytes: &[u8]) -> Result<ParseResult, String> {
+use crate::SbpfLinkerError;
+
+pub fn parse_bytecode(bytes: &[u8]) -> Result<ParseResult, SbpfLinkerError> {
     let mut ast = AST::new();
 
-    let obj = File::parse(bytes).map_err(|e| e.to_string())?;
+    let obj = File::parse(bytes)?;
     let mut rodata_table = HashMap::new();
     if let Some(ro_section) = obj.section_by_name(".rodata") {
         // only handle symbols in the .rodata section for now
-        // let ro_section = ro_section.unwrap();
         let mut rodata_offset = 0;
         for symbol in obj.symbols() {
-            if symbol.section_index() == Some(ro_section.index()) && symbol.size() > 0 {
+            if symbol.section_index() == Some(ro_section.index())
+                && symbol.size() > 0
+            {
                 let mut bytes = Vec::new();
                 for i in 0..symbol.size() {
-                    bytes.push(ImmediateValue::Int(
-                        ro_section.data().unwrap()[(symbol.address() + i) as usize] as i64));
+                    bytes.push(ImmediateValue::Int(i64::from(
+                        ro_section.data().unwrap()
+                            [(symbol.address() + i) as usize],
+                    )));
                 }
                 ast.rodata_nodes.push(ASTNode::ROData {
                     rodata: ROData {
-                        name: symbol.name().unwrap().to_string(),
-                        args: vec![Token::Directive(String::from("byte"), 0..1) //
-                                , Token::VectorLiteral(bytes.clone(), 0..1)],
+                        name: symbol.name().unwrap().to_owned(),
+                        args: vec![
+                            Token::Directive(String::from("byte"), 0..1), //
+                            Token::VectorLiteral(bytes.clone(), 0..1),
+                        ],
                         span: 0..1,
                     },
                     offset: rodata_offset,
                 });
-                rodata_table.insert(symbol.address(), symbol.name().unwrap().to_string());
+                rodata_table.insert(
+                    symbol.address(),
+                    symbol.name().unwrap().to_owned(),
+                );
                 rodata_offset += symbol.size();
             }
         }
@@ -49,10 +58,11 @@ pub fn parse_bytecode(bytes: &[u8]) -> Result<ParseResult, String> {
             // lddw takes 16 bytes, other instructions take 8 bytes
             let mut offset = 0;
             while offset < section.data().unwrap().len() {
-                let node_len = match Opcode::from_u8(section.data().unwrap()[offset]) {
-                    Some(Opcode::Lddw) => 16,
-                    _ => 8,
-                };
+                let node_len =
+                    match Opcode::from_u8(section.data().unwrap()[offset]) {
+                        Some(Opcode::Lddw) => 16,
+                        _ => 8,
+                    };
                 let node = &section.data().unwrap()[offset..offset + node_len];
                 let instruction = Instruction::from_bytes(node);
                 if let Err(error) = instruction {
@@ -71,26 +81,36 @@ pub fn parse_bytecode(bytes: &[u8]) -> Result<ParseResult, String> {
                 for rel in section.relocations() {
                     // only handle relocations for symbols in the .rodata section for now
                     let symbol = match rel.1.target() {
-                        Symbol(sym) => Some(obj.symbol_by_index(sym).unwrap()), 
-                        _ => None
+                        Symbol(sym) => Some(obj.symbol_by_index(sym).unwrap()),
+                        _ => None,
                     };
                 
                     if symbol.unwrap().section_index() == Some(ro_section.index()) {
                         // addend is not explicit in the relocation entry, but implicitly encoded
                         // as the immediate value of the instruction
-                        let addend = //
-                            match ast.get_instruction_at_offset(rel.0 as u64).unwrap()
-                                .operands.last().unwrap().clone() {
-                                    Token::ImmediateValue(ImmediateValue::Int(val), _) => val,
-                                    _ => 0 
-                            };
+                        let addend = match ast
+                            .get_instruction_at_offset(rel.0)
+                            .unwrap()
+                            .operands
+                            .last()
+                            .unwrap()
+                            .clone()
+                        {
+                            Token::ImmediateValue(
+                                ImmediateValue::Int(val),
+                                _,
+                            ) => val,
+                            _ => 0,
+                        };
 
                         // Replace the immediate value with the rodata label
-                        let ro_label = rodata_table.get(&(addend as u64)).unwrap();
+                        let ro_label = &rodata_table[&(addend as u64)];
                         let ro_label_name = ro_label.clone();
-                        let node: &mut Instruction = ast.get_instruction_at_offset(rel.0 as u64).unwrap();
+                        let node: &mut Instruction =
+                            ast.get_instruction_at_offset(rel.0).unwrap();
                         let last_idx = node.operands.len() - 1;
-                        node.operands[last_idx] = Token::Identifier(ro_label_name, 0..1);
+                        node.operands[last_idx] =
+                            Token::Identifier(ro_label_name, 0..1);
                     }
                 }
             }
@@ -98,11 +118,6 @@ pub fn parse_bytecode(bytes: &[u8]) -> Result<ParseResult, String> {
         }
     }
 
-    let parse_result = ast.build_program();
-    if let Ok(parse_result) = parse_result {
-        Ok(parse_result)
-    } else {
-        // TODO: handle errors
-        Err("Failed to build program".to_string())
-    }
+    ast.build_program()
+        .map_err(|errors| SbpfLinkerError::BuildProgramError { errors })
 }

+ 19 - 9
src/lib.rs

@@ -1,19 +1,29 @@
 pub mod byteparser;
+use std::io;
 
+use bpf_linker::LinkerError;
 use byteparser::parse_bytecode;
 
-use sbpf_assembler::Program;
+use sbpf_assembler::{CompileError, Program};
 
-use anyhow::Result;
+#[derive(thiserror::Error, Debug)]
+pub enum SbpfLinkerError {
+    #[error("Error opening object file. Error detail: ({0}).")]
+    ObjectFileOpenError(#[from] object::Error),
+    #[error("Error reading object file. Error detail: ({0}).")]
+    ObjectFileReadError(#[from] io::Error),
+    #[error("Linker Error. Error detail: ({0}).")]
+    LinkerError(#[from] LinkerError),
+    #[error("LLVM issued diagnostic with error severity.")]
+    LlvmDiagnosticError,
+    #[error("Build Program Error. Error details: {errors:?}.")]
+    BuildProgramError { errors: Vec<CompileError> },
+}
 
-pub fn link_program(source: &Vec<u8>) -> Result<Vec<u8>, String> {
-    let parse_result = match parse_bytecode(source) {
-        Ok(program) => program,
-        Err(errors) => {
-            return Err(errors);
-        }
-    };
+pub fn link_program(source: &[u8]) -> Result<Vec<u8>, SbpfLinkerError> {
+    let parse_result = parse_bytecode(source)?;
     let program = Program::from_parse_result(parse_result);
     let bytecode = program.emit_bytecode();
+
     Ok(bytecode)
 }