sbpf-linker.rs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. use std::{env, ffi::CString, fs, path::PathBuf, str::FromStr};
  2. #[cfg(any(
  3. feature = "rust-llvm-19",
  4. feature = "rust-llvm-20",
  5. feature = "rust-llvm-21"
  6. ))]
  7. use aya_rustc_llvm_proxy as _;
  8. use bpf_linker::{Cpu, Linker, LinkerOptions, OptLevel, OutputType};
  9. use clap::{Parser, error::ErrorKind};
  10. use sbpf_linker::{SbpfLinkerError, link_program};
  11. #[derive(Debug, thiserror::Error)]
  12. enum CliError {
  13. #[error(
  14. "optimization level needs to be between 0-3, s or z (instead was `{0}`)"
  15. )]
  16. InvalidOptimization(String),
  17. #[error("SBPF Linker Error. Error detail: ({0}).")]
  18. SbpfLinkerError(#[from] SbpfLinkerError),
  19. #[error("Clap Error. Error detail: ({0}).")]
  20. ClapError(#[from] clap::error::Error),
  21. #[error("Program Read Error. Error detail: ({msg}).")]
  22. ProgramReadError { msg: String },
  23. #[error("Program Write Error. Error detail: ({msg}).")]
  24. ProgramWriteError { msg: String },
  25. // #[error("unknown emission type: `{0}` - expected one of: `llvm-bc`, `asm`, `llvm-ir`, `obj`")]
  26. // InvalidOutputType(String),
  27. }
  28. #[derive(Copy, Clone, Debug)]
  29. struct CliOptLevel(OptLevel);
  30. impl FromStr for CliOptLevel {
  31. type Err = CliError;
  32. fn from_str(s: &str) -> Result<Self, Self::Err> {
  33. Ok(Self(match s {
  34. "0" => OptLevel::No,
  35. "1" => OptLevel::Less,
  36. "2" => OptLevel::Default,
  37. "3" => OptLevel::Aggressive,
  38. "s" => OptLevel::Size,
  39. "z" => OptLevel::SizeMin,
  40. _ => return Err(CliError::InvalidOptimization(s.to_string())),
  41. }))
  42. }
  43. }
  44. #[derive(Debug, Parser)]
  45. #[command(version)]
  46. struct CommandLine {
  47. /// Target BPF processor. Can be one of `generic`, `probe`, `v1`, `v2`, `v3`
  48. #[clap(long, default_value = "generic")]
  49. cpu: Cpu,
  50. /// Write output to <output>
  51. #[clap(short, long)]
  52. output: PathBuf,
  53. /// Emit BTF information
  54. #[clap(long)]
  55. btf: bool,
  56. /// Permit automatic insertion of __`bpf_trap` calls.
  57. /// See: <https://github.com/llvm/llvm-project/commit/ab391beb11f733b526b86f9df23734a34657d876>
  58. #[clap(long)]
  59. allow_bpf_trap: bool,
  60. /// Add a directory to the library search path
  61. #[clap(short = 'L', number_of_values = 1)]
  62. libs: Vec<PathBuf>,
  63. /// Optimization level. 0-3, s, or z
  64. #[clap(short = 'O', default_value = "2")]
  65. optimize: Vec<CliOptLevel>,
  66. /// Export the symbols specified in the file `path`. The symbols must be separated by new lines
  67. #[clap(long, value_name = "path")]
  68. export_symbols: Option<PathBuf>,
  69. /// Try hard to unroll loops. Useful when targeting kernels that don't support loops
  70. #[clap(long)]
  71. unroll_loops: bool,
  72. /// Ignore `noinline`/`#[inline(never)]`. Useful when targeting kernels that don't support function calls
  73. #[clap(long)]
  74. ignore_inline_never: bool,
  75. /// Dump the final IR module to the given `path` before generating the code
  76. #[clap(long, value_name = "path")]
  77. dump_module: Option<PathBuf>,
  78. /// Extra command line arguments to pass to LLVM
  79. #[clap(long, value_name = "args", use_value_delimiter = true, action = clap::ArgAction::Append)]
  80. llvm_args: Vec<CString>,
  81. /// Disable passing --bpf-expand-memcpy-in-order to LLVM.
  82. #[clap(long)]
  83. disable_expand_memcpy_in_order: bool,
  84. /// Disable exporting `memcpy`, `memmove`, `memset`, `memcmp` and `bcmp`. Exporting
  85. /// those is commonly needed when LLVM does not manage to expand memory
  86. /// intrinsics to a sequence of loads and stores.
  87. #[clap(long)]
  88. disable_memory_builtins: bool,
  89. /// Input files. Can be object files or static libraries
  90. #[clap(required = true)]
  91. inputs: Vec<PathBuf>,
  92. /// Comma separated list of symbols to export. See also `--export-symbols`
  93. #[clap(long, value_name = "symbols", use_value_delimiter = true, action = clap::ArgAction::Append)]
  94. export: Vec<String>,
  95. /// Whether to treat LLVM errors as fatal.
  96. #[clap(long, action = clap::ArgAction::Set, default_value_t = true)]
  97. fatal_errors: bool,
  98. // The options below are for wasm-ld compatibility
  99. #[clap(long = "debug", hide = true)]
  100. _debug: bool,
  101. }
  102. fn main() -> Result<(), CliError> {
  103. let args = env::args().map(|arg| {
  104. if arg == "-flavor" { "--flavor".to_string() } else { arg }
  105. });
  106. let CommandLine {
  107. cpu,
  108. output,
  109. btf,
  110. allow_bpf_trap,
  111. libs,
  112. optimize,
  113. export_symbols,
  114. unroll_loops,
  115. ignore_inline_never,
  116. dump_module,
  117. llvm_args,
  118. disable_expand_memcpy_in_order,
  119. disable_memory_builtins,
  120. inputs,
  121. export,
  122. fatal_errors,
  123. _debug,
  124. } = match Parser::try_parse_from(args) {
  125. Ok(command_line) => command_line,
  126. Err(err) => match err.kind() {
  127. ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => {
  128. print!("{err}");
  129. return Ok(());
  130. }
  131. _ => return Err(err.into()),
  132. },
  133. };
  134. let export_symbols =
  135. export_symbols.map(fs::read_to_string).transpose().map_err(|e| {
  136. CliError::SbpfLinkerError(SbpfLinkerError::ObjectFileReadError(e))
  137. })?;
  138. // TODO: the data is owned by this call frame; we could make this zero-alloc.
  139. let export_symbols = export_symbols
  140. .as_deref()
  141. .into_iter()
  142. .flat_map(str::lines)
  143. .map(str::to_owned)
  144. .chain(export)
  145. .map(Into::into)
  146. .collect();
  147. let optimize = match *optimize.as_slice() {
  148. [] => unreachable!("emit has a default value"),
  149. [.., CliOptLevel(optimize)] => optimize,
  150. };
  151. let mut linker = Linker::new(LinkerOptions {
  152. target: Some("bpf".to_string()),
  153. cpu,
  154. cpu_features: String::new(),
  155. inputs,
  156. output: output.clone(),
  157. output_type: OutputType::Object,
  158. libs,
  159. optimize,
  160. export_symbols,
  161. unroll_loops,
  162. ignore_inline_never,
  163. dump_module,
  164. llvm_args: llvm_args
  165. .into_iter()
  166. .map(|cstring| cstring.into_string().unwrap_or_default())
  167. .collect(),
  168. disable_expand_memcpy_in_order,
  169. disable_memory_builtins,
  170. btf,
  171. allow_bpf_trap,
  172. });
  173. linker.link().map_err(|e| {
  174. CliError::SbpfLinkerError(SbpfLinkerError::LinkerError(e))
  175. })?;
  176. if fatal_errors && linker.has_errors() {
  177. return Err(CliError::SbpfLinkerError(
  178. SbpfLinkerError::LlvmDiagnosticError,
  179. ));
  180. }
  181. let program = std::fs::read(&output)
  182. .map_err(|e| CliError::ProgramReadError { msg: e.to_string() })?;
  183. let bytecode =
  184. link_program(&program).map_err(CliError::SbpfLinkerError)?;
  185. let src_name = std::path::Path::new(&output)
  186. .file_stem()
  187. .and_then(|s| s.to_str())
  188. .unwrap_or("main");
  189. let output_path = std::path::Path::new(&output)
  190. .parent()
  191. .unwrap_or_else(|| std::path::Path::new("."))
  192. .join(format!("{src_name}.so"));
  193. std::fs::write(output_path, bytecode)
  194. .map_err(|e| CliError::ProgramWriteError { msg: e.to_string() })?;
  195. Ok(())
  196. }