use std::{
env, fs,
ffi::CString,
path::PathBuf,
str::FromStr,
};
#[cfg(any(
feature = "rust-llvm-19",
feature = "rust-llvm-20",
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;
#[derive(Debug, Error)]
enum CliError {
#[error("optimization level needs to be between 0-3, s or z (instead was `{0}`)")]
InvalidOptimization(String),
}
#[derive(Copy, Clone, Debug)]
struct CliOptLevel(OptLevel);
impl FromStr for CliOptLevel {
type Err = CliError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(match s {
"0" => OptLevel::No,
"1" => OptLevel::Less,
"2" => OptLevel::Default,
"3" => OptLevel::Aggressive,
"s" => OptLevel::Size,
"z" => OptLevel::SizeMin,
_ => return Err(CliError::InvalidOptimization(s.to_string())),
}))
}
}
#[derive(Debug, Parser)]
#[command(version)]
struct CommandLine {
#[clap(long, default_value = "generic")]
cpu: Cpu,
#[clap(short, long)]
output: PathBuf,
#[clap(long)]
btf: bool,
#[clap(long)]
allow_bpf_trap: bool,
#[clap(short = 'L', number_of_values = 1)]
libs: Vec<PathBuf>,
#[clap(short = 'O', default_value = "2")]
optimize: Vec<CliOptLevel>,
#[clap(long, value_name = "path")]
export_symbols: Option<PathBuf>,
#[clap(long)]
unroll_loops: bool,
#[clap(long)]
ignore_inline_never: bool,
#[clap(long, value_name = "path")]
dump_module: Option<PathBuf>,
#[clap(long, value_name = "args", use_value_delimiter = true, action = clap::ArgAction::Append)]
llvm_args: Vec<CString>,
#[clap(long)]
disable_expand_memcpy_in_order: bool,
#[clap(long)]
disable_memory_builtins: bool,
#[clap(required = true)]
inputs: Vec<PathBuf>,
#[clap(long, value_name = "symbols", use_value_delimiter = true, action = clap::ArgAction::Append)]
export: Vec<String>,
#[clap(long, action = clap::ArgAction::Set, default_value_t = true)]
fatal_errors: bool,
#[clap(long = "debug", hide = true)]
_debug: bool,
}
fn main() -> anyhow::Result<()> {
let args = env::args().map(|arg| {
if arg == "-flavor" {
"--flavor".to_string()
} else {
arg
}
});
let CommandLine {
cpu,
output,
btf,
allow_bpf_trap,
libs,
optimize,
export_symbols,
unroll_loops,
ignore_inline_never,
dump_module,
llvm_args,
disable_expand_memcpy_in_order,
disable_memory_builtins,
inputs,
export,
fatal_errors,
_debug,
} = match Parser::try_parse_from(args) {
Ok(command_line) => command_line,
Err(err) => match err.kind() {
ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => {
print!("{err}");
return Ok(());
}
_ => return Err(err.into()),
},
};
let export_symbols = export_symbols.map(fs::read_to_string).transpose()?;
let export_symbols = export_symbols
.as_deref()
.into_iter()
.flat_map(str::lines)
.map(str::to_owned)
.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(),
inputs,
output: output.clone(),
output_type: OutputType::Object,
libs,
optimize,
export_symbols,
unroll_loops,
ignore_inline_never,
dump_module,
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()?;
if fatal_errors && linker.has_errors() {
return Err(anyhow::anyhow!(
"LLVM issued diagnostic with error severity"
));
}
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 src_name = std::path::Path::new(&output)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("main");
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)?;
Ok(())
}