├── .gitignore ├── rustfmt.toml ├── src ├── llvm │ ├── types │ │ ├── mod.rs │ │ ├── memory_buffer.rs │ │ ├── module.rs │ │ ├── target_machine.rs │ │ ├── context.rs │ │ ├── ir.rs │ │ └── di.rs │ ├── iter.rs │ ├── mod.rs │ └── di.rs ├── lib.rs ├── bin │ └── bpf-linker.rs └── linker.rs ├── tests ├── assembly │ ├── auxiliary │ │ ├── loop-panic-handler.rs │ │ ├── dep-section.rs │ │ └── dep-exports.rs │ ├── unroll-loop.rs │ ├── bin.rs │ ├── elf-sections.rs │ ├── ignore-inline-never.rs │ ├── di_generics.rs │ └── exported-symbols.rs ├── btf │ └── assembly │ │ ├── auxiliary │ │ ├── loop-panic-handler.rs │ │ └── dep-exports.rs │ │ ├── basic.rs │ │ ├── anon_rust.rs │ │ ├── data-carrying-enum.rs │ │ └── exported-symbols.rs ├── c │ └── anon.c ├── nightly │ └── btf │ │ └── assembly │ │ └── anon_struct_c.rs └── tests.rs ├── .cargo └── config.toml ├── xtask ├── Cargo.toml └── src │ └── main.rs ├── .github ├── dependabot.yml ├── actions │ └── report-disk-usage │ │ └── action.yml └── workflows │ ├── llvm.yml │ ├── release.yml │ └── ci.yml ├── LICENSE-MIT ├── Cargo.toml ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode/settings.json 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "StdExternalCrate" 2 | imports_granularity = "Crate" 3 | reorder_imports = true 4 | unstable_features = true 5 | ignore = [ 6 | "third-party", 7 | ] 8 | -------------------------------------------------------------------------------- /src/llvm/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub(super) mod context; 2 | pub(super) mod di; 3 | pub(super) mod ir; 4 | pub(super) mod memory_buffer; 5 | pub(super) mod module; 6 | pub(super) mod target_machine; 7 | -------------------------------------------------------------------------------- /tests/assembly/auxiliary/loop-panic-handler.rs: -------------------------------------------------------------------------------- 1 | // no-prefer-dynamic 2 | // compile-flags: --crate-type rlib 3 | #![no_std] 4 | 5 | #[panic_handler] 6 | fn panic_impl(_: &core::panic::PanicInfo) -> ! { 7 | loop {} 8 | } 9 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | 4 | # On Linux we let clang drive the link so it contributes the right system 5 | # search paths. 6 | [target.'cfg(target_os = "linux")'] 7 | linker = "clang" 8 | -------------------------------------------------------------------------------- /tests/assembly/auxiliary/dep-section.rs: -------------------------------------------------------------------------------- 1 | // no-prefer-dynamic 2 | // compile-flags: --crate-type rlib 3 | #![no_std] 4 | 5 | #[no_mangle] 6 | #[link_section = "uprobe/dep"] 7 | pub fn dep() -> u64 { 8 | 42 9 | } 10 | -------------------------------------------------------------------------------- /tests/btf/assembly/auxiliary/loop-panic-handler.rs: -------------------------------------------------------------------------------- 1 | // no-prefer-dynamic 2 | // compile-flags: --crate-type rlib 3 | #![no_std] 4 | 5 | #[panic_handler] 6 | fn panic_impl(_: &core::panic::PanicInfo) -> ! { 7 | loop {} 8 | } 9 | -------------------------------------------------------------------------------- /tests/c/anon.c: -------------------------------------------------------------------------------- 1 | /** 2 | * An anonymous struct aliased with a typedef. 3 | */ 4 | typedef struct { 5 | int foo; 6 | } incognito; 7 | 8 | int incognito_foo(incognito *i) { 9 | return __builtin_preserve_access_index(i->foo); 10 | } 11 | -------------------------------------------------------------------------------- /tests/assembly/auxiliary/dep-exports.rs: -------------------------------------------------------------------------------- 1 | // no-prefer-dynamic 2 | // compile-flags: --crate-type rlib 3 | #![no_std] 4 | 5 | pub fn dep_public_symbol() -> u8 { 6 | // read_volatile stops LTO inlining the function in the calling crate 7 | unsafe { core::ptr::read_volatile(core::ptr::dangling()) } 8 | } 9 | 10 | #[no_mangle] 11 | pub fn dep_no_mangle() -> u8 { 12 | dep_public_symbol() 13 | } 14 | -------------------------------------------------------------------------------- /tests/btf/assembly/auxiliary/dep-exports.rs: -------------------------------------------------------------------------------- 1 | // no-prefer-dynamic 2 | // compile-flags: --crate-type rlib -C debuginfo=2 3 | #![no_std] 4 | 5 | #[inline(never)] 6 | pub fn dep_public_symbol() -> u8 { 7 | // read_volatile stops LTO inlining the function in the calling crate 8 | unsafe { core::ptr::read_volatile(core::ptr::dangling()) } 9 | } 10 | 11 | #[no_mangle] 12 | pub fn dep_no_mangle() -> u8 { 13 | dep_public_symbol() 14 | } 15 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | 6 | [dependencies] 7 | anyhow = { workspace = true } 8 | clap = { workspace = true } 9 | reqwest = { workspace = true, features = ["blocking", "json", "rustls-tls"] } 10 | rustc-build-sysroot = { workspace = true } 11 | serde = { workspace = true, features = ["derive"] } 12 | walkdir = { workspace = true } 13 | 14 | [lints] 15 | workspace = true 16 | -------------------------------------------------------------------------------- /tests/btf/assembly/basic.rs: -------------------------------------------------------------------------------- 1 | // assembly-output: bpf-linker 2 | // no-prefer-dynamic 3 | // compile-flags: --crate-type bin -C link-arg=--emit=obj -C link-arg=--btf -C debuginfo=2 4 | 5 | #![no_std] 6 | #![no_main] 7 | 8 | #[no_mangle] 9 | #[link_section = "uprobe/connect"] 10 | pub fn connect() {} 11 | 12 | #[panic_handler] 13 | fn panic(_info: &core::panic::PanicInfo) -> ! { 14 | loop {} 15 | } 16 | 17 | // We check the BTF dump out of btfdump 18 | // CHECK: 'connect' --> global 19 | -------------------------------------------------------------------------------- /tests/assembly/unroll-loop.rs: -------------------------------------------------------------------------------- 1 | // ignore-test 2 | // assembly-output: bpf-linker 3 | // compile-flags: --crate-type cdylib -C link-arg=--unroll-loops -C link-arg=-O3 4 | #![no_std] 5 | // Recent kernels starting from 5.2 support some bounded loops. Older kernels need all loops to be 6 | // unrolled. The linker provides the --unroll-loops flag to aggressively try and unroll. 7 | 8 | // aux-build: loop-panic-handler.rs 9 | extern crate loop_panic_handler; 10 | 11 | #[no_mangle] 12 | fn foo(arg: &mut u64) { 13 | for i in 0..=200 { 14 | *arg += *arg + i; 15 | // CHECK: r{{[1-9]}} += 200 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "cargo" 7 | directory: "/" 8 | schedule: 9 | interval: "monthly" 10 | groups: 11 | cargo-crates: 12 | patterns: 13 | - "*" 14 | - package-ecosystem: "github-actions" 15 | directory: "/" 16 | schedule: 17 | interval: "monthly" 18 | groups: 19 | github-actions: 20 | patterns: 21 | - "*" 22 | -------------------------------------------------------------------------------- /tests/assembly/bin.rs: -------------------------------------------------------------------------------- 1 | // assembly-output: bpf-linker 2 | // compile-flags: --crate-type bin 3 | 4 | // Same as bpf-sections.rs, but using bin as crate-type. 5 | 6 | #![no_std] 7 | #![no_main] 8 | 9 | // aux-build: loop-panic-handler.rs 10 | extern crate loop_panic_handler; 11 | 12 | // aux-build: dep-section.rs 13 | extern crate dep_section; 14 | 15 | #[no_mangle] 16 | #[link_section = "uprobe/connect"] 17 | pub fn connect() {} 18 | 19 | #[no_mangle] 20 | #[link_section = "maps/counter"] 21 | static mut COUNTER: u32 = 0; 22 | 23 | // CHECK: .section "uprobe/connect","ax" 24 | // CHECK: .section "uprobe/dep","ax" 25 | // CHECK: .section "maps/counter","aw" 26 | -------------------------------------------------------------------------------- /tests/assembly/elf-sections.rs: -------------------------------------------------------------------------------- 1 | // assembly-output: bpf-linker 2 | // compile-flags: --crate-type cdylib 3 | 4 | // BPF crates export programs (functions) and maps (kernel data structures) through ELF sections. 5 | // Verify that the linker correctly outputs those sections. 6 | #![no_std] 7 | 8 | // aux-build: loop-panic-handler.rs 9 | extern crate loop_panic_handler; 10 | 11 | // aux-build: dep-section.rs 12 | extern crate dep_section; 13 | 14 | #[no_mangle] 15 | #[link_section = "uprobe/connect"] 16 | pub fn connect() { 17 | } 18 | 19 | #[no_mangle] 20 | #[link_section = "maps/counter"] 21 | static mut COUNTER: u32 = 0; 22 | 23 | // CHECK: .section "uprobe/connect","ax" 24 | // CHECK: .section "uprobe/dep","ax" 25 | // CHECK: .section "maps/counter","aw" 26 | -------------------------------------------------------------------------------- /tests/btf/assembly/anon_rust.rs: -------------------------------------------------------------------------------- 1 | // assembly-output: bpf-linker 2 | // compile-flags: --crate-type cdylib -C link-arg=--emit=obj -C link-arg=--btf -C debuginfo=2 3 | 4 | #![no_std] 5 | 6 | use core::marker::PhantomData; 7 | 8 | #[repr(transparent)] 9 | pub struct AyaBtfMapMarker(PhantomData<()>); 10 | 11 | pub struct Foo { 12 | // Anonymize the stuct. 13 | _anon: AyaBtfMapMarker, 14 | 15 | pub ayy: u32, 16 | pub lmao: u32, 17 | } 18 | 19 | #[no_mangle] 20 | static FOO: Foo = Foo { 21 | _anon: AyaBtfMapMarker(PhantomData), 22 | 23 | ayy: 0, 24 | lmao: 0, 25 | }; 26 | 27 | #[panic_handler] 28 | fn panic(_info: &core::panic::PanicInfo) -> ! { 29 | loop {} 30 | } 31 | 32 | // CHECK: '' sz:8 n:2 33 | // CHECK-NEXT: 'ayy' off:0 --> [{{[0-9]+}}] 34 | // CHECK-NEXT: 'lmao' off:32 --> [{{[0-9]+}}] 35 | -------------------------------------------------------------------------------- /.github/actions/report-disk-usage/action.yml: -------------------------------------------------------------------------------- 1 | name: Report disk usage 2 | description: Print disk usage for key directories on the runner 3 | runs: 4 | using: composite 5 | steps: 6 | - name: Report disk usage 7 | shell: bash 8 | run: | 9 | set -euo pipefail 10 | 11 | printf "\n== Filesystems ==\n" 12 | df -h 13 | 14 | report_dir() { 15 | local dir="$1" 16 | local depth="$2" 17 | 18 | [[ -d "$dir" ]] || return 19 | 20 | printf "\n== %s ==\n" "$dir" 21 | sudo -n du -x -h -d "$depth" "$dir" 2>/dev/null | sort -h -r | head -n 20 22 | } 23 | 24 | report_dir "${{ github.workspace }}" 2 25 | report_dir "$HOME/.cargo" 2 26 | report_dir "$HOME/.rustup" 1 27 | report_dir /tmp 1 28 | if [[ "${RUNNER_OS:-}" == "Linux" ]]; then 29 | report_dir /var/cache/apt/archives 1 30 | fi 31 | -------------------------------------------------------------------------------- /tests/assembly/ignore-inline-never.rs: -------------------------------------------------------------------------------- 1 | // assembly-output: bpf-linker 2 | // compile-flags: --crate-type cdylib -C link-args=--ignore-inline-never 3 | // only-bpfel 4 | #![no_std] 5 | 6 | // Kernels prior to 5.8 (August 2020) don't support function calls. In order to support those 7 | // kernels, which are the vast majority of versions deployed today, the linker provides the 8 | // --ignore-inline-never option to ignore #[inline(never)] attributes. The flag is useful when 9 | // something in a dependency crate is marked #[inline(never)], like core::panicking::*. 10 | 11 | // aux-build: loop-panic-handler.rs 12 | extern crate loop_panic_handler; 13 | 14 | #[inline(never)] 15 | fn actually_inlined(a: u64) -> u64 { 16 | a + 42 17 | } 18 | 19 | #[no_mangle] 20 | #[link_section = "uprobe/fun"] 21 | pub extern "C" fn fun(a: u64) -> u64 { 22 | // CHECK-LABEL: fun: 23 | actually_inlined(a) 24 | // CHECK-DAG: r{{[0-9]}} = r{{[0-9]}} 25 | // CHECK-DAG: r{{[0-9]}} += 42 26 | } 27 | -------------------------------------------------------------------------------- /src/llvm/types/memory_buffer.rs: -------------------------------------------------------------------------------- 1 | use core::slice; 2 | 3 | use llvm_sys::{ 4 | core::{LLVMDisposeMemoryBuffer, LLVMGetBufferSize, LLVMGetBufferStart}, 5 | prelude::LLVMMemoryBufferRef, 6 | }; 7 | 8 | pub(crate) struct MemoryBuffer { 9 | pub(super) memory_buffer: LLVMMemoryBufferRef, 10 | } 11 | 12 | impl MemoryBuffer { 13 | /// Gets a byte slice of this `MemoryBuffer`. 14 | pub(crate) fn as_slice(&self) -> &[u8] { 15 | unsafe { 16 | let start = LLVMGetBufferStart(self.memory_buffer); 17 | 18 | slice::from_raw_parts(start.cast(), self.get_size()) 19 | } 20 | } 21 | 22 | /// Gets the byte size of this `MemoryBuffer`. 23 | pub(crate) fn get_size(&self) -> usize { 24 | unsafe { LLVMGetBufferSize(self.memory_buffer) } 25 | } 26 | } 27 | 28 | impl Drop for MemoryBuffer { 29 | fn drop(&mut self) { 30 | unsafe { 31 | LLVMDisposeMemoryBuffer(self.memory_buffer); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Alessandro Decina 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /tests/btf/assembly/data-carrying-enum.rs: -------------------------------------------------------------------------------- 1 | // assembly-output: bpf-linker 2 | // compile-flags: --crate-type cdylib -C link-arg=--emit=obj -C link-arg=--btf -C debuginfo=2 3 | 4 | #![no_std] 5 | 6 | pub enum SimpleEnum { 7 | First, 8 | Second, 9 | Third, 10 | } 11 | 12 | pub enum DataCarryingEnum { 13 | First { a: u32, b: i32 }, 14 | Second(u32, i32), 15 | Third(u32), 16 | } 17 | 18 | #[no_mangle] 19 | pub static A: SimpleEnum = SimpleEnum::First; 20 | #[no_mangle] 21 | pub static B: SimpleEnum = SimpleEnum::Second; 22 | #[no_mangle] 23 | pub static C: SimpleEnum = SimpleEnum::Third; 24 | 25 | #[no_mangle] 26 | pub static X: DataCarryingEnum = DataCarryingEnum::First { a: 54, b: -23 }; 27 | #[no_mangle] 28 | pub static Y: DataCarryingEnum = DataCarryingEnum::Second(54, -23); 29 | #[no_mangle] 30 | pub static Z: DataCarryingEnum = DataCarryingEnum::Third(36); 31 | 32 | #[panic_handler] 33 | fn panic(_info: &core::panic::PanicInfo) -> ! { 34 | loop {} 35 | } 36 | 37 | // The data-carrying enum should be not included in BTF. 38 | 39 | // CHECK: 'SimpleEnum' sz:1 n:3 40 | // CHECK-NEXT: First = 0 41 | // CHECK-NEXT: Second = 1 42 | // CHECK-NEXT: Third = 2 43 | // CHECK-NOT: 'DataCarryingEnum' 44 | -------------------------------------------------------------------------------- /tests/nightly/btf/assembly/anon_struct_c.rs: -------------------------------------------------------------------------------- 1 | //! Check if bpf-linker is able to link bitcode which provides anonymous structs 2 | //! exposed by named typedefs. The corresponding C code is available in 3 | //! tests/c/anon.c. 4 | 5 | // assembly-output: bpf-linker 6 | // compile-flags: --crate-type bin -C link-arg=--emit=obj -C link-arg=--btf -C debuginfo=2 -Z unstable-options -L native=target/bitcode -l link-arg=target/bitcode/anon.bc 7 | 8 | #![no_std] 9 | #![no_main] 10 | 11 | #[no_mangle] 12 | static EXPECTED_FOO: i32 = 0; 13 | 14 | /// A binding to the struct from C code. 15 | /// 16 | /// In Rust, there is no concept of anonymous structs and typedef aliases 17 | /// (`type` in Rust works differently and also produces different debug info). 18 | /// Just defining a named struct is a correct way of creating a binding and 19 | /// that's exactly what bindgen does. 20 | #[repr(C)] 21 | #[derive(Debug, Copy, Clone)] 22 | pub struct Incognito { 23 | pub foo: i32, 24 | } 25 | 26 | extern "C" { 27 | pub fn incognito_foo(i: *const Incognito) -> i32; 28 | } 29 | 30 | #[no_mangle] 31 | pub fn get_foo(i: *const Incognito) -> i32 { 32 | unsafe { incognito_foo(i) } 33 | } 34 | 35 | #[panic_handler] 36 | fn panic(_info: &core::panic::PanicInfo) -> ! { 37 | loop {} 38 | } 39 | 40 | // CHECK: 'incognito' --> [10] 41 | // CHECK: '' sz:4 n:1 42 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // The following macros are adapted from from https://github.com/TheDan64/inkwell, 2 | // licensed under Apache-2.0. 3 | // Original source: https://github.com/TheDan64/inkwell/blob/0b0a2c0b2eb5e458767093c2ab8c56cbd05ec4c9/src/lib.rs#L85-L112 4 | 5 | #![expect(unused_crate_dependencies, reason = "used in bin")] 6 | 7 | macro_rules! assert_unique_features { 8 | () => {}; 9 | ($first:tt $(,$rest:tt)*) => { 10 | $( 11 | #[cfg(all(feature = $first, feature = $rest))] 12 | compile_error!(concat!("features \"", $first, "\" and \"", $rest, "\" cannot be used together")); 13 | )* 14 | assert_unique_features!($($rest),*); 15 | } 16 | } 17 | 18 | macro_rules! assert_used_features { 19 | ($($all:tt),*) => { 20 | #[cfg(not(any($(feature = $all),*)))] 21 | compile_error!(concat!("One of the LLVM feature flags must be provided: ", $($all, " "),*)); 22 | } 23 | } 24 | 25 | macro_rules! assert_unique_used_features { 26 | ($($all:tt),*) => { 27 | assert_unique_features!($($all),*); 28 | assert_used_features!($($all),*); 29 | } 30 | } 31 | 32 | assert_unique_used_features! { 33 | "llvm-20", 34 | "llvm-21" 35 | } 36 | 37 | #[cfg(feature = "llvm-20")] 38 | pub extern crate llvm_sys_20 as llvm_sys; 39 | #[cfg(feature = "llvm-21")] 40 | pub extern crate llvm_sys_21 as llvm_sys; 41 | 42 | mod linker; 43 | mod llvm; 44 | 45 | pub use linker::*; 46 | -------------------------------------------------------------------------------- /tests/btf/assembly/exported-symbols.rs: -------------------------------------------------------------------------------- 1 | // assembly-output: bpf-linker 2 | // compile-flags: --crate-type bin -C link-arg=--emit=obj -C debuginfo=2 -C link-arg=--btf 3 | #![no_std] 4 | #![no_main] 5 | 6 | // aux-build: loop-panic-handler.rs 7 | extern crate loop_panic_handler; 8 | 9 | // aux-build: dep-exports.rs 10 | extern crate dep_exports as dep; 11 | 12 | pub use dep::dep_public_symbol as local_re_exported; 13 | 14 | #[no_mangle] 15 | fn local_no_mangle() -> u8 { 16 | local_public(1, 2) 17 | } 18 | 19 | #[inline(never)] 20 | pub fn local_public(_arg1: u32, _arg2: u32) -> u8 { 21 | // bind v so we create a debug variable which needs its scope to be fixed 22 | let v = dep::dep_public_symbol(); 23 | // call inline functions so we get inlinedAt scopes to be fixed 24 | inline_function_1(v) + inline_function_2(v) 25 | } 26 | 27 | #[inline(always)] 28 | fn inline_function_1(v: u8) -> u8 { 29 | unsafe { core::ptr::read_volatile(v as *const u8) } 30 | } 31 | 32 | #[inline(always)] 33 | fn inline_function_2(v: u8) -> u8 { 34 | inline_function_1(v) 35 | } 36 | 37 | // #[no_mangle] functions keep linkage=global 38 | // CHECK-DAG: 'local_no_mangle' --> global [{{[0-9]+}} 39 | 40 | // check that parameter names are preserved 41 | // CHECK: 42 | // CHECK-NEXT: _arg1 43 | // CHECK-NEXT: _arg2 44 | 45 | // public functions get static linkage 46 | // CHECK-DAG: '{{.*}}local_public{{.*}}' --> static 47 | // CHECK-DAG: '{{.*}}dep_public_symbol{{.*}}' --> static 48 | 49 | // #[no_mangle] is honored for dep functions 50 | // CHECK-DAG: 'dep_no_mangle' --> global 51 | -------------------------------------------------------------------------------- /tests/assembly/di_generics.rs: -------------------------------------------------------------------------------- 1 | // assembly-output: bpf-linker 2 | // compile-flags: --crate-type cdylib -C link-arg=--emit=llvm-ir -C link-arg=--btf -C debuginfo=2 3 | 4 | // Verify that the linker correctly massages map names. 5 | #![no_std] 6 | 7 | // aux-build: loop-panic-handler.rs 8 | extern crate loop_panic_handler; 9 | 10 | struct Foo { 11 | x: T, 12 | } 13 | 14 | #[no_mangle] 15 | #[link_section = "maps"] 16 | static mut FOO: Foo = Foo { x: 0 }; 17 | 18 | struct Bar { 19 | x: T, 20 | } 21 | 22 | #[no_mangle] 23 | #[link_section = "maps"] 24 | static mut BAR: Bar> = Bar { x: Foo { x: 0 } }; 25 | 26 | // NOTE(vadorovsky): I couldn't come up with any simpler example of function 27 | // with generic which wouldn't get inlined. 28 | 29 | #[no_mangle] 30 | #[link_section = "uprobe/connect"] 31 | pub fn connect() -> u32 { 32 | my_function(1, 2) 33 | } 34 | 35 | pub trait Add { 36 | type Output; 37 | 38 | fn add(self, rhs: Rhs) -> Self::Output; 39 | } 40 | 41 | impl Add for u32 { 42 | type Output = Self; 43 | 44 | fn add(self, other: Self) -> Self::Output { 45 | self + other 46 | } 47 | } 48 | 49 | #[inline(never)] 50 | pub fn my_function + Copy>(x: T, y: T) -> T { 51 | x.add(y) 52 | .add(x) 53 | .add(y) 54 | .add(x) 55 | .add(y) 56 | .add(x) 57 | .add(y) 58 | .add(x) 59 | .add(y) 60 | .add(x) 61 | .add(y) 62 | .add(x) 63 | .add(y) 64 | } 65 | 66 | // CHECK-DAG: name: "Foo_3C_u32_3E_" 67 | // CHECK-DAG: name: "Bar_3C_di_generics_3A__3A_Foo_3C_u32_3E__3E_" 68 | // CHECK-DAG: name: "my_function_3C_u32_3E_" 69 | -------------------------------------------------------------------------------- /tests/assembly/exported-symbols.rs: -------------------------------------------------------------------------------- 1 | // assembly-output: bpf-linker 2 | // revisions: cdylib bin 3 | // [cdylib]compile-flags: --crate-type cdylib 4 | // [bin]compile-flags: --crate-type bin 5 | // 6 | // When compiling cdylibs or bins, only #[no_mangle] symbols are exported. 7 | // 8 | // Dylibs aren't supported, since they re-export all public symbols of all statically linked 9 | // dependencies. In practice that means that all of the core crate is re-exported, which crashes the 10 | // LLVM BPF target because of varargs, > 5 arguments, and all the other usual reasons. See 11 | // https://github.com/rust-lang/rust/blob/0039d73/compiler/rustc_codegen_ssa/src/back/linker.rs#L1685 12 | // 13 | // staticlib and rlib don't link, they just store bitcode in .a/.rlib so they are not relevant. 14 | // 15 | // 16 | #![no_std] 17 | #![no_main] 18 | 19 | // aux-build: loop-panic-handler.rs 20 | extern crate loop_panic_handler; 21 | 22 | // aux-build: dep-exports.rs 23 | extern crate dep_exports as dep; 24 | 25 | pub use dep::dep_public_symbol as local_re_exported; 26 | 27 | #[no_mangle] 28 | fn local_no_mangle() -> u8 { 29 | dep::dep_public_symbol() 30 | } 31 | 32 | pub fn local_public() -> u8 { 33 | dep::dep_public_symbol() 34 | } 35 | 36 | // #[no_mangle] symbols are exported 37 | // CHECK,cdylib: .globl local_no_mangle 38 | // CHECK,cdylib: .globl dep_no_mangle 39 | // CHECK,bin: .globl local_no_mangle 40 | // CHECK,bin: .globl dep_no_mangle 41 | 42 | // public symbols are not exported 43 | // public symbols of dependencies are not exported 44 | // re-exported symbols are not exported 45 | // CHECK,cdylib-NOT: .globl local_public 46 | // CHECK,cdylib-NOT: .globl dep_public_symbol 47 | // CHECK,cdylib-NOT: .globl local_re_exported 48 | // CHECK,bin-NOT: .globl local_public 49 | // CHECK,bin-NOT: .globl dep_public_symbol 50 | // CHECK,bin-NOT: .globl local_re_exported 51 | -------------------------------------------------------------------------------- /.github/workflows/llvm.yml: -------------------------------------------------------------------------------- 1 | name: LLVM 2 | 3 | on: 4 | workflow_call: 5 | outputs: 6 | cache-key: 7 | value: ${{ jobs.llvm.outputs.cache-key }} 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | llvm: 13 | runs-on: ubuntu-22.04 14 | name: llvm 15 | outputs: 16 | cache-key: ${{ steps.cache-key.outputs.cache-key }} 17 | steps: 18 | - id: ls-remote 19 | run: | 20 | set -euxo pipefail 21 | value=$(git ls-remote https://github.com/aya-rs/llvm-project.git refs/heads/rustc/21.1-2025-08-01 | cut -f1) 22 | echo "sha=$value" >> "$GITHUB_OUTPUT" 23 | 24 | - id: cache-key 25 | run: echo "cache-key=llvm-${{ steps.ls-remote.outputs.sha }}-1" >> "$GITHUB_OUTPUT" 26 | 27 | - name: Cache 28 | id: cache-llvm 29 | uses: actions/cache@v4 30 | with: 31 | path: llvm-install 32 | key: ${{ steps.cache-key.outputs.cache-key }} 33 | lookup-only: true 34 | 35 | - uses: actions/checkout@v6 36 | if: steps.cache-llvm.outputs.cache-hit != 'true' 37 | 38 | - uses: dtolnay/rust-toolchain@stable 39 | if: steps.cache-llvm.outputs.cache-hit != 'true' 40 | 41 | - name: Install Tools 42 | if: steps.cache-llvm.outputs.cache-hit != 'true' 43 | run: | 44 | set -euxo pipefail 45 | wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | \ 46 | gpg --dearmor - | \ 47 | sudo tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null 48 | echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ jammy main' | \ 49 | sudo tee /etc/apt/sources.list.d/kitware.list >/dev/null 50 | 51 | sudo apt update 52 | sudo apt -y install cmake ninja-build clang lld 53 | 54 | - name: Checkout LLVM Source 55 | if: steps.cache-llvm.outputs.cache-hit != 'true' 56 | uses: actions/checkout@v6 57 | with: 58 | repository: aya-rs/llvm-project 59 | ref: ${{ steps.ls-remote.outputs.sha }} 60 | path: llvm-project 61 | 62 | - name: Build LLVM 63 | if: steps.cache-llvm.outputs.cache-hit != 'true' 64 | run: | 65 | set -euxo pipefail 66 | cargo xtask build-llvm \ 67 | --src-dir llvm-project \ 68 | --build-dir llvm-build \ 69 | --install-prefix "${{ github.workspace }}/llvm-install" 70 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | pull_request: 5 | 6 | release: 7 | types: [published] 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | upload-bins: 13 | runs-on: ${{ matrix.platform.os }} 14 | strategy: 15 | matrix: 16 | platform: 17 | - os: macos-latest 18 | target: aarch64-apple-darwin 19 | - os: macos-15-intel 20 | target: x86_64-apple-darwin 21 | - os: ubuntu-22.04 22 | target: x86_64-unknown-linux-musl 23 | - os: ubuntu-22.04-arm 24 | target: aarch64-unknown-linux-musl 25 | env: 26 | RUST_CI_LLVM_INSTALL_DIR: /tmp/rustc-llvm 27 | steps: 28 | - uses: actions/checkout@v6 29 | - uses: Swatinem/rust-cache@v2 30 | 31 | - name: Install prerequisites 32 | if: runner.os == 'macOS' 33 | # macOS does not provide any static libraries. Homebrew does provide 34 | # them, but in custom paths that the system-wide clang is not aware of. 35 | # Point build.rs to them by setting environment variables. 36 | # 37 | # We install llvm package only for libc++. 38 | run: | 39 | brew install llvm zlib 40 | echo "CXXSTDLIB_PATH=$(brew --prefix llvm)/lib/c++" >> $GITHUB_ENV 41 | echo "ZLIB_PATH=$(brew --prefix zlib)/lib" >> $GITHUB_ENV 42 | 43 | - name: Install LLVM from Rust CI 44 | run: | 45 | set -euxo pipefail 46 | sudo mkdir -p $RUST_CI_LLVM_INSTALL_DIR 47 | rustc_date="$(curl -s https://static.rust-lang.org/dist/channel-rust-nightly-date.txt)" 48 | rustc_sha="$(curl -s "https://static.rust-lang.org/dist/$rustc_date/channel-rust-nightly-git-commit-hash.txt")" 49 | wget -q -O - "https://ci-artifacts.rust-lang.org/rustc-builds/$rustc_sha/rust-dev-nightly-${{ matrix.platform.target }}.tar.xz" | \ 50 | sudo tar -xJ --strip-components 2 -C $RUST_CI_LLVM_INSTALL_DIR 51 | echo "${RUST_CI_LLVM_INSTALL_DIR}/bin" >> $GITHUB_PATH 52 | 53 | - uses: taiki-e/upload-rust-binary-action@v1 54 | with: 55 | bin: bpf-linker 56 | features: llvm-21,llvm-link-static 57 | no-default-features: true 58 | dry-run: ${{ github.event_name != 'release' }} 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | 62 | - name: Report disk usage 63 | if: ${{ always() }} 64 | uses: ./.github/actions/report-disk-usage 65 | -------------------------------------------------------------------------------- /src/llvm/types/module.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CStr, marker::PhantomData}; 2 | 3 | use libc::c_char; 4 | use llvm_sys::{ 5 | bit_writer::LLVMWriteBitcodeToFile, 6 | core::{ 7 | LLVMCreateMemoryBufferWithMemoryRangeCopy, LLVMDisposeMessage, LLVMDisposeModule, 8 | LLVMGetTarget, LLVMPrintModuleToFile, LLVMPrintModuleToString, 9 | }, 10 | debuginfo::LLVMStripModuleDebugInfo, 11 | prelude::LLVMModuleRef, 12 | }; 13 | 14 | use crate::llvm::{MemoryBuffer, Message, types::context::LLVMContext}; 15 | 16 | pub(crate) struct LLVMModule<'ctx> { 17 | pub(super) module: LLVMModuleRef, 18 | pub(super) _marker: PhantomData<&'ctx LLVMContext>, 19 | } 20 | 21 | impl LLVMModule<'_> { 22 | /// Returns an unsafe mutable pointer to the LLVM module. 23 | /// 24 | /// The caller must ensure that the [`LLVMModule`] outlives the pointer this 25 | /// function returns, or else it will end up dangling. 26 | pub(in crate::llvm) const fn as_mut_ptr(&self) -> LLVMModuleRef { 27 | self.module 28 | } 29 | 30 | pub(crate) fn get_target(&self) -> *const c_char { 31 | unsafe { LLVMGetTarget(self.module) } 32 | } 33 | 34 | pub(crate) fn write_bitcode_to_path(&self, path: &CStr) -> Result<(), std::io::Error> { 35 | if unsafe { LLVMWriteBitcodeToFile(self.module, path.as_ptr()) } != 0 { 36 | return Err(std::io::Error::last_os_error()); 37 | } 38 | 39 | Ok(()) 40 | } 41 | 42 | pub(crate) fn write_bitcode_to_memory(&self) -> MemoryBuffer { 43 | let buf = unsafe { llvm_sys::bit_writer::LLVMWriteBitcodeToMemoryBuffer(self.module) }; 44 | 45 | MemoryBuffer { memory_buffer: buf } 46 | } 47 | 48 | pub(crate) fn write_ir_to_path(&self, path: &CStr) -> Result<(), String> { 49 | let (ret, message) = unsafe { 50 | Message::with(|message| LLVMPrintModuleToFile(self.module, path.as_ptr(), message)) 51 | }; 52 | 53 | if ret == 0 { 54 | Ok(()) 55 | } else { 56 | Err(message.as_string_lossy().to_string()) 57 | } 58 | } 59 | 60 | pub(crate) fn write_ir_to_memory(&self) -> MemoryBuffer { 61 | // Format the module to a string, then copy into a MemoryBuffer. We do the extra copy to keep the 62 | // internal API simpler, as all the other codegen methods output a MemoryBuffer. 63 | unsafe { 64 | let ptr = LLVMPrintModuleToString(self.module); 65 | let cstr = CStr::from_ptr(ptr); 66 | let bytes = cstr.to_bytes(); 67 | 68 | let buffer_name = c"mem_buffer"; 69 | 70 | // Copy bytes into a new LLVMMemoryBuffer so we can safely dispose the message. 71 | let memory_buffer = LLVMCreateMemoryBufferWithMemoryRangeCopy( 72 | bytes.as_ptr().cast(), 73 | bytes.len(), 74 | buffer_name.as_ptr(), 75 | ); 76 | LLVMDisposeMessage(ptr); 77 | 78 | MemoryBuffer { memory_buffer } 79 | } 80 | } 81 | 82 | /// strips debug information, returns true if DI got stripped 83 | pub(crate) fn strip_debug_info(&mut self) -> bool { 84 | unsafe { LLVMStripModuleDebugInfo(self.module) != 0 } 85 | } 86 | } 87 | 88 | impl Drop for LLVMModule<'_> { 89 | fn drop(&mut self) { 90 | unsafe { LLVMDisposeModule(self.module) }; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/llvm/types/target_machine.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | 3 | use llvm_sys::target_machine::{ 4 | LLVMCodeGenFileType, LLVMCodeGenOptLevel, LLVMCodeModel, LLVMCreateTargetMachine, 5 | LLVMDisposeTargetMachine, LLVMRelocMode, LLVMTargetMachineEmitToFile, 6 | LLVMTargetMachineEmitToMemoryBuffer, LLVMTargetMachineRef, LLVMTargetRef, 7 | }; 8 | 9 | use crate::llvm::{MemoryBuffer, Message, types::module::LLVMModule}; 10 | 11 | pub(crate) struct LLVMTargetMachine { 12 | target_machine: LLVMTargetMachineRef, 13 | } 14 | 15 | impl LLVMTargetMachine { 16 | pub(crate) fn new( 17 | target: LLVMTargetRef, 18 | triple: &CStr, 19 | cpu: &CStr, 20 | features: &CStr, 21 | ) -> Option { 22 | let tm = unsafe { 23 | LLVMCreateTargetMachine( 24 | target, 25 | triple.as_ptr(), 26 | cpu.as_ptr(), 27 | features.as_ptr(), 28 | LLVMCodeGenOptLevel::LLVMCodeGenLevelAggressive, 29 | LLVMRelocMode::LLVMRelocDefault, 30 | LLVMCodeModel::LLVMCodeModelDefault, 31 | ) 32 | }; 33 | if tm.is_null() { 34 | None 35 | } else { 36 | Some(Self { target_machine: tm }) 37 | } 38 | } 39 | 40 | /// Returns an unsafe mutable pointer to the LLVM target machine. 41 | /// 42 | /// The caller must ensure that the [`LLVMTargetMachine`] outlives the pointer this 43 | /// function returns, or else it will end up dangling. 44 | pub(in crate::llvm) const fn as_mut_ptr(&self) -> LLVMTargetMachineRef { 45 | self.target_machine 46 | } 47 | 48 | pub(crate) fn emit_to_file( 49 | &self, 50 | module: &LLVMModule<'_>, 51 | path: &CStr, 52 | output_type: LLVMCodeGenFileType, 53 | ) -> Result<(), String> { 54 | let (ret, message) = unsafe { 55 | Message::with(|message| { 56 | LLVMTargetMachineEmitToFile( 57 | self.target_machine, 58 | module.module, 59 | path.as_ptr(), 60 | output_type, 61 | message, 62 | ) 63 | }) 64 | }; 65 | if ret == 0 { 66 | Ok(()) 67 | } else { 68 | Err(message.as_string_lossy().to_string()) 69 | } 70 | } 71 | 72 | pub(crate) fn emit_to_memory_buffer( 73 | &self, 74 | module: &LLVMModule<'_>, 75 | output_type: LLVMCodeGenFileType, 76 | ) -> Result { 77 | let mut out_buf = std::ptr::null_mut(); 78 | let (ret, message) = Message::with(|message| unsafe { 79 | LLVMTargetMachineEmitToMemoryBuffer( 80 | self.target_machine, 81 | module.module, 82 | output_type, 83 | message, 84 | &mut out_buf, 85 | ) 86 | }); 87 | if ret != 0 { 88 | return Err(message.as_string_lossy().to_string()); 89 | } 90 | 91 | Ok(MemoryBuffer { 92 | memory_buffer: out_buf, 93 | }) 94 | } 95 | } 96 | 97 | impl Drop for LLVMTargetMachine { 98 | fn drop(&mut self) { 99 | unsafe { 100 | LLVMDisposeTargetMachine(self.target_machine); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/llvm/iter.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use llvm_sys::{ 4 | core::{ 5 | LLVMGetFirstBasicBlock, LLVMGetFirstFunction, LLVMGetFirstGlobal, LLVMGetFirstGlobalAlias, 6 | LLVMGetFirstInstruction, LLVMGetLastBasicBlock, LLVMGetLastFunction, LLVMGetLastGlobal, 7 | LLVMGetLastGlobalAlias, LLVMGetLastInstruction, LLVMGetNextBasicBlock, LLVMGetNextFunction, 8 | LLVMGetNextGlobal, LLVMGetNextGlobalAlias, LLVMGetNextInstruction, 9 | }, 10 | prelude::{LLVMBasicBlockRef, LLVMModuleRef, LLVMValueRef}, 11 | }; 12 | 13 | macro_rules! llvm_iterator { 14 | ($trait_name:ident, $iterator_name:ident, $iterable:ty, $method_name:ident, $item_ty:ty, $first:expr, $last:expr, $next:expr $(,)?) => { 15 | pub(crate) trait $trait_name { 16 | fn $method_name(&self) -> $iterator_name<'_>; 17 | } 18 | 19 | pub(crate) struct $iterator_name<'a> { 20 | lifetime: PhantomData<&'a $iterable>, 21 | next: $item_ty, 22 | last: $item_ty, 23 | } 24 | 25 | impl $trait_name for $iterable { 26 | fn $method_name(&self) -> $iterator_name<'_> { 27 | let first = unsafe { $first(*self) }; 28 | let last = unsafe { $last(*self) }; 29 | assert_eq!(first.is_null(), last.is_null()); 30 | $iterator_name { 31 | lifetime: PhantomData, 32 | next: first, 33 | last, 34 | } 35 | } 36 | } 37 | 38 | impl Iterator for $iterator_name<'_> { 39 | type Item = $item_ty; 40 | 41 | fn next(&mut self) -> Option { 42 | let Self { 43 | lifetime: _, 44 | next, 45 | last, 46 | } = self; 47 | if next.is_null() { 48 | return None; 49 | } 50 | let last = *next == *last; 51 | let item = *next; 52 | *next = unsafe { $next(*next) }; 53 | assert_eq!(next.is_null(), last); 54 | Some(item) 55 | } 56 | } 57 | }; 58 | } 59 | 60 | llvm_iterator! { 61 | IterModuleGlobals, 62 | GlobalsIter, 63 | LLVMModuleRef, 64 | globals_iter, 65 | LLVMValueRef, 66 | LLVMGetFirstGlobal, 67 | LLVMGetLastGlobal, 68 | LLVMGetNextGlobal, 69 | } 70 | 71 | llvm_iterator! { 72 | IterModuleGlobalAliases, 73 | GlobalAliasesIter, 74 | LLVMModuleRef, 75 | global_aliases_iter, 76 | LLVMValueRef, 77 | LLVMGetFirstGlobalAlias, 78 | LLVMGetLastGlobalAlias, 79 | LLVMGetNextGlobalAlias, 80 | } 81 | 82 | llvm_iterator! { 83 | IterModuleFunctions, 84 | FunctionsIter, 85 | LLVMModuleRef, 86 | functions_iter, 87 | LLVMValueRef, 88 | LLVMGetFirstFunction, 89 | LLVMGetLastFunction, 90 | LLVMGetNextFunction, 91 | } 92 | 93 | llvm_iterator!( 94 | IterBasicBlocks, 95 | BasicBlockIter, 96 | LLVMValueRef, 97 | basic_blocks_iter, 98 | LLVMBasicBlockRef, 99 | LLVMGetFirstBasicBlock, 100 | LLVMGetLastBasicBlock, 101 | LLVMGetNextBasicBlock 102 | ); 103 | 104 | llvm_iterator!( 105 | IterInstructions, 106 | InstructionsIter, 107 | LLVMBasicBlockRef, 108 | instructions_iter, 109 | LLVMValueRef, 110 | LLVMGetFirstInstruction, 111 | LLVMGetLastInstruction, 112 | LLVMGetNextInstruction 113 | ); 114 | -------------------------------------------------------------------------------- /src/llvm/types/context.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | any::Any, 3 | ffi::{CStr, c_void}, 4 | marker::PhantomData, 5 | pin::Pin, 6 | ptr, 7 | rc::Rc, 8 | }; 9 | 10 | use llvm_sys::{ 11 | core::{ 12 | LLVMContextCreate, LLVMContextDispose, LLVMContextSetDiagnosticHandler, 13 | LLVMGetDiagInfoDescription, LLVMGetDiagInfoSeverity, LLVMModuleCreateWithNameInContext, 14 | }, 15 | prelude::{LLVMContextRef, LLVMDiagnosticInfoRef}, 16 | }; 17 | 18 | use crate::llvm::{LLVMDiagnosticHandler, Message, types::module::LLVMModule}; 19 | 20 | pub(crate) struct LLVMContext { 21 | context: LLVMContextRef, 22 | /// Optional diagnostic handler set for the context. 23 | /// 24 | /// The diagnostic handler pointer must remain valid until either 25 | /// a new handler is installed or the context is disposed. 26 | /// To guarantee this, we keep a strong reference to the handler 27 | /// inside the wrapper. 28 | /// The type of the diagnostic handler is erased to make the 29 | /// context wrapper non generic. 30 | diagnostic_handler: Option, 31 | } 32 | 33 | impl LLVMContext { 34 | pub(crate) fn new() -> Self { 35 | let context = unsafe { LLVMContextCreate() }; 36 | Self { 37 | context, 38 | diagnostic_handler: None, 39 | } 40 | } 41 | 42 | /// Returns an unsafe mutable pointer to the LLVM context. 43 | /// 44 | /// The caller must ensure that the [`LLVMContext`] outlives the pointer this 45 | /// function returns, or else it will end up dangling. 46 | pub(in crate::llvm) const fn as_mut_ptr(&self) -> LLVMContextRef { 47 | self.context 48 | } 49 | 50 | pub(crate) fn create_module<'ctx>(&'ctx self, name: &CStr) -> Option> { 51 | let module = unsafe { LLVMModuleCreateWithNameInContext(name.as_ptr(), self.context) }; 52 | 53 | if module.is_null() { 54 | return None; 55 | } 56 | 57 | Some(LLVMModule { 58 | module, 59 | _marker: PhantomData, 60 | }) 61 | } 62 | 63 | /// Install a context-local diagnostic handler. 64 | pub(crate) fn set_diagnostic_handler(&mut self, handler: T) -> InstalledDiagnosticHandler 65 | where 66 | T: LLVMDiagnosticHandler + 'static, 67 | { 68 | // Heap-allocate and pin the handler so its address is stable 69 | // for the C API 70 | let pinrc = Rc::pin(handler); 71 | 72 | // Get a opaque raw pointer to the new memory stable object 73 | let handler_ptr = ptr::from_ref(Pin::as_ref(&pinrc).get_ref()) as *mut c_void; 74 | 75 | unsafe { 76 | LLVMContextSetDiagnosticHandler( 77 | self.context, 78 | Some(diagnostic_handler::), 79 | handler_ptr, 80 | ) 81 | }; 82 | 83 | // Keep the handler alive for at least as long as the context 84 | // by storing a type-erased pinned clone in the context. This 85 | // guards against the handler being dropped while LLVM still 86 | // holds the callback pointer. 87 | self.diagnostic_handler = Some(StoredHandler { 88 | _handler: pinrc.clone(), 89 | }); 90 | 91 | // Return a typed handle that keeps a strong, pinned reference to `T`. 92 | // 93 | // This lets the caller interact with the installed diagnostic handler 94 | // directly (via `with_view`) without needing to query the context or 95 | // deal with an Option. It also contributes to keeping the handler alive 96 | // for as long as the handle (or the context-held clone) exists. 97 | InstalledDiagnosticHandler { inner: pinrc } 98 | } 99 | } 100 | 101 | impl Drop for LLVMContext { 102 | fn drop(&mut self) { 103 | unsafe { 104 | LLVMContextDispose(self.context); 105 | } 106 | } 107 | } 108 | 109 | struct StoredHandler { 110 | _handler: Pin>, 111 | } 112 | 113 | #[derive(Clone)] 114 | pub(crate) struct InstalledDiagnosticHandler { 115 | inner: Pin>, 116 | } 117 | 118 | impl InstalledDiagnosticHandler { 119 | pub(crate) fn with_view R>(&self, f: F) -> R { 120 | f(Pin::as_ref(&self.inner).get_ref()) 121 | } 122 | } 123 | 124 | extern "C" fn diagnostic_handler( 125 | info: LLVMDiagnosticInfoRef, 126 | handler: *mut c_void, 127 | ) { 128 | let severity = unsafe { LLVMGetDiagInfoSeverity(info) }; 129 | let message = Message { 130 | ptr: unsafe { LLVMGetDiagInfoDescription(info) }, 131 | }; 132 | let handler = handler.cast::(); 133 | unsafe { &mut *handler }.handle_diagnostic(severity, message.as_string_lossy()); 134 | } 135 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bpf-linker" 3 | version = "0.9.15" 4 | authors = ["Alessandro Decina "] 5 | description = "BPF static linker" 6 | license = "MIT OR Apache-2.0" 7 | keywords = ["BPF", "eBPF", "linker", "llvm"] 8 | categories = [ 9 | "development-tools", 10 | "command-line-utilities", 11 | "no-std", 12 | "os::linux-apis", 13 | ] 14 | repository = "https://github.com/aya-rs/bpf-linker" 15 | readme = "README.md" 16 | edition.workspace = true 17 | 18 | [dependencies] 19 | # cli deps 20 | anyhow = { workspace = true } 21 | clap = { workspace = true } 22 | tracing-appender = "0.2" 23 | tracing-subscriber = { version = "0.3", features = ["env-filter", "registry"] } 24 | tracing-tree = "0.4" 25 | 26 | # lib deps 27 | ar = { version = "0.9.0" } 28 | aya-rustc-llvm-proxy = { version = "0.9.5", optional = true } 29 | gimli = { version = "0.32.0" } 30 | libc = { version = "0.2.174" } 31 | llvm-sys-20 = { package = "llvm-sys", features = [ 32 | "disable-alltargets-init", 33 | "no-llvm-linking", 34 | ], version = "201.0.1", optional = true } 35 | llvm-sys-21 = { package = "llvm-sys", features = [ 36 | "disable-alltargets-init", 37 | "no-llvm-linking", 38 | ], version = "211.0.0", optional = true } 39 | log = { version = "0.4.27" } 40 | thiserror = { version = "2.0.12" } 41 | tracing = "0.1" 42 | 43 | [build-dependencies] 44 | anyhow = { workspace = true } 45 | object = { version = "0.37", default-features = false, features = [ 46 | "archive", 47 | "elf", 48 | "macho", 49 | "read_core", 50 | "unaligned", 51 | ] } 52 | 53 | [dev-dependencies] 54 | compiletest_rs = { version = "0.11.0" } 55 | regex = { version = "1.11.1", default-features = false } 56 | rustc-build-sysroot = { workspace = true } 57 | which = { version = "8.0.0", default-features = false, features = ["real-sys", "regex"] } 58 | 59 | [lints] 60 | workspace = true 61 | 62 | [[bin]] 63 | name = "bpf-linker" 64 | 65 | [features] 66 | llvm-20 = ["dep:llvm-sys-20"] 67 | llvm-21 = ["dep:llvm-sys-21"] 68 | 69 | # Use libLLVM shared library provided by Rust. Works only on 70 | # x86_64-unknown-linux-gnu. 71 | rust-llvm-20 = [ 72 | "dep:aya-rustc-llvm-proxy", 73 | "llvm-20", 74 | "no-llvm-linking", 75 | ] 76 | rust-llvm-21 = [ 77 | "dep:aya-rustc-llvm-proxy", 78 | "llvm-21", 79 | "no-llvm-linking", 80 | ] 81 | 82 | # Link LLVM and its dependencies statically. When not enabled, they're linked 83 | # dynamically. 84 | llvm-link-static = [] 85 | # Skip LLVM linking. 86 | no-llvm-linking = [] 87 | 88 | default = [ 89 | "llvm-21", 90 | "rust-llvm-21", 91 | ] 92 | 93 | [workspace] 94 | members = ["xtask"] 95 | 96 | [workspace.package] 97 | edition = "2024" 98 | 99 | [workspace.dependencies] 100 | # cli deps 101 | anyhow = { version = "1.0.100", default-features = false } 102 | clap = { version = "4.5.53", features = ["derive", "env"] } 103 | # dev deps 104 | reqwest = { version = "0.12.24", default-features = false } 105 | rustc-build-sysroot = { version = "0.5.11", default-features = false } 106 | serde = { version = "1.0.228", default-features = false } 107 | walkdir = { version = "2.5.0", default-features = false } 108 | 109 | [workspace.lints.clippy] 110 | all = "warn" 111 | as_ptr_cast_mut = "warn" 112 | as_underscore = "warn" 113 | cast_lossless = "warn" 114 | cast_possible_truncation = "warn" 115 | cast_possible_wrap = "warn" 116 | cast_precision_loss = "warn" 117 | cast_sign_loss = "warn" 118 | char_lit_as_u8 = "warn" 119 | fn_to_numeric_cast = "warn" 120 | fn_to_numeric_cast_with_truncation = "warn" 121 | mut_mut = "warn" 122 | needless_bitwise_bool = "warn" 123 | needless_lifetimes = "warn" 124 | no_mangle_with_rust_abi = "warn" 125 | ptr_as_ptr = "warn" 126 | ptr_cast_constness = "warn" 127 | ref_as_ptr = "warn" 128 | unnecessary_cast = "warn" 129 | unused_trait_names = "warn" 130 | use_self = "warn" 131 | 132 | [workspace.lints.rust] 133 | absolute_paths_not_starting_with_crate = "warn" 134 | deprecated_in_future = "warn" 135 | elided_lifetimes_in_paths = "warn" 136 | explicit_outlives_requirements = "warn" 137 | ffi_unwind_calls = "warn" 138 | keyword_idents = "warn" 139 | let_underscore_drop = "warn" 140 | macro_use_extern_crate = "warn" 141 | meta_variable_misuse = "warn" 142 | missing_abi = "warn" 143 | missing_copy_implementations = "warn" 144 | non_ascii_idents = "warn" 145 | noop_method_call = "warn" 146 | single_use_lifetimes = "warn" 147 | trivial_numeric_casts = "warn" 148 | unreachable_pub = "warn" 149 | unsafe_op_in_unsafe_fn = "warn" 150 | unstable_features = "warn" 151 | unused_crate_dependencies = "warn" 152 | unused_extern_crates = "warn" 153 | unused_import_braces = "warn" 154 | unused_lifetimes = "warn" 155 | unused_macro_rules = "warn" 156 | unused_qualifications = "warn" 157 | unused_results = "warn" 158 | 159 | [profile.release] 160 | debug = true 161 | 162 | [patch.crates-io] 163 | compiletest_rs = { git = "https://github.com/Manishearth/compiletest-rs.git" } 164 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | #![expect(unused_crate_dependencies, reason = "used in lib/bin")] 2 | 3 | use std::{ 4 | env, 5 | ffi::{OsStr, OsString}, 6 | fs, 7 | path::{Path, PathBuf}, 8 | process::Command, 9 | }; 10 | 11 | fn rustc_cmd() -> Command { 12 | Command::new(env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"))) 13 | } 14 | 15 | fn find_binary(binary_re_str: &str) -> PathBuf { 16 | let binary_re = regex::Regex::new(binary_re_str).unwrap(); 17 | let mut binary = which::which_re(binary_re).expect(binary_re_str); 18 | binary 19 | .next() 20 | .unwrap_or_else(|| panic!("could not find {binary_re_str}")) 21 | } 22 | 23 | fn run_mode(target: &str, mode: &str, sysroot: &Path, cfg: Option) 24 | where 25 | F: Fn(&mut compiletest_rs::Config), 26 | { 27 | let target_rustcflags = format!( 28 | "-C linker={} --sysroot {}", 29 | env!("CARGO_BIN_EXE_bpf-linker"), 30 | sysroot.to_str().unwrap() 31 | ); 32 | 33 | let llvm_filecheck = Some(find_binary(r"^FileCheck(-\d+)?$")); 34 | 35 | let mode = mode.parse().expect("Invalid mode"); 36 | let mut config = compiletest_rs::Config { 37 | target: target.to_owned(), 38 | target_rustcflags: Some(target_rustcflags), 39 | llvm_filecheck, 40 | mode, 41 | src_base: PathBuf::from(format!("tests/{mode}")), 42 | ..Default::default() 43 | }; 44 | config.link_deps(); 45 | 46 | if let Some(cfg) = cfg { 47 | cfg(&mut config); 48 | } 49 | 50 | compiletest_rs::run_tests(&config); 51 | } 52 | 53 | /// Builds LLVM bitcode files from LLVM IR files located in a specified directory. 54 | fn build_bitcode

(src_dir: P, dst_dir: P) 55 | where 56 | P: AsRef, 57 | { 58 | fs::create_dir_all(dst_dir.as_ref()).expect("failed to create a build directory for bitcode"); 59 | for entry in fs::read_dir(src_dir.as_ref()).expect("failed to read the directory") { 60 | let entry = entry.expect("failed to read the entry"); 61 | let path = entry.path(); 62 | 63 | if path.is_file() && path.extension() == Some(OsStr::new("c")) { 64 | let bc_dst = dst_dir 65 | .as_ref() 66 | .join(path.with_extension("bc").file_name().unwrap()); 67 | clang_build(path, bc_dst); 68 | } 69 | } 70 | } 71 | 72 | /// Compiles C code into an LLVM bitcode file. 73 | fn clang_build

(src: P, dst: P) 74 | where 75 | P: AsRef, 76 | { 77 | let clang = find_binary(r"^clang(-\d+)?$"); 78 | let output = Command::new(clang) 79 | .arg("-target") 80 | .arg("bpf") 81 | .arg("-g") 82 | .arg("-c") 83 | .arg("-emit-llvm") 84 | .arg("-o") 85 | .arg(dst.as_ref()) 86 | .arg(src.as_ref()) 87 | .output() 88 | .expect("failed to execute clang"); 89 | 90 | if !output.status.success() { 91 | panic!( 92 | "clang failed with code {:?}\nstdout: {}\nstderr: {}", 93 | output.status.code(), 94 | String::from_utf8_lossy(&output.stdout), 95 | String::from_utf8_lossy(&output.stderr) 96 | ); 97 | } 98 | } 99 | 100 | fn is_nightly() -> bool { 101 | let output = rustc_cmd() 102 | .arg("--version") 103 | .output() 104 | .expect("failed to determine rustc version"); 105 | if !output.status.success() { 106 | panic!("failed to determine rustc version: {output:?}"); 107 | } 108 | const NIGHTLY: &[u8] = b"nightly"; 109 | output.stdout.windows(NIGHTLY.len()).any(|b| NIGHTLY.eq(b)) 110 | } 111 | 112 | fn btf_dump(src: &Path, dst: &Path) { 113 | let dst = fs::File::create(dst) 114 | .unwrap_or_else(|err| panic!("could not open btf dump file '{}': {err}", dst.display())); 115 | let mut btf = Command::new("btf"); 116 | let status = btf 117 | .arg("dump") 118 | .arg(src) 119 | .stdout(dst) 120 | .status() 121 | .unwrap_or_else(|err| panic!("could not run {btf:?}: {err}")); 122 | assert_eq!(status.code(), Some(0), "{btf:?} failed"); 123 | } 124 | 125 | #[test] 126 | fn compile_test() { 127 | let target = "bpfel-unknown-none"; 128 | let root_dir = env::var_os("CARGO_MANIFEST_DIR") 129 | .expect("could not determine the root directory of the project"); 130 | let root_dir = Path::new(&root_dir); 131 | let bpf_sysroot = if let Some(bpf_sysroot) = env::var_os("BPFEL_SYSROOT_DIR") { 132 | PathBuf::from(bpf_sysroot) 133 | } else { 134 | let rustc = Command::new(env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"))); 135 | let rustc_src = rustc_build_sysroot::rustc_sysroot_src(rustc) 136 | .expect("could not determine sysroot source directory"); 137 | let directory = root_dir.join("target/sysroot"); 138 | match rustc_build_sysroot::SysrootBuilder::new(&directory, target) 139 | .build_mode(rustc_build_sysroot::BuildMode::Build) 140 | .sysroot_config(rustc_build_sysroot::SysrootConfig::NoStd) 141 | // to be able to thoroughly test DI we need to build sysroot with debuginfo 142 | // this is necessary to compile rust core with DI 143 | .rustflag("-Cdebuginfo=2") 144 | .build_from_source(&rustc_src) 145 | .expect("failed to build sysroot") 146 | { 147 | rustc_build_sysroot::SysrootStatus::AlreadyCached => {} 148 | rustc_build_sysroot::SysrootStatus::SysrootBuilt => {} 149 | } 150 | directory 151 | }; 152 | 153 | build_bitcode(root_dir.join("tests/c"), root_dir.join("target/bitcode")); 154 | 155 | run_mode( 156 | target, 157 | "assembly", 158 | &bpf_sysroot, 159 | None::, 160 | ); 161 | run_mode( 162 | target, 163 | "assembly", 164 | &bpf_sysroot, 165 | Some(|cfg: &mut compiletest_rs::Config| { 166 | cfg.src_base = PathBuf::from("tests/btf"); 167 | cfg.llvm_filecheck_preprocess = Some(btf_dump); 168 | }), 169 | ); 170 | // The `tests/nightly` directory contains tests which require unstable compiler 171 | // features through the `-Z` argument in `compile-flags`. 172 | if is_nightly() { 173 | run_mode( 174 | target, 175 | "assembly", 176 | &bpf_sysroot, 177 | Some(|cfg: &mut compiletest_rs::Config| { 178 | cfg.src_base = PathBuf::from("tests/nightly/btf"); 179 | cfg.llvm_filecheck_preprocess = Some(btf_dump); 180 | }), 181 | ); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BPF Linker 🔗 2 | 3 | bpf-linker aims to simplify building modern BPF programs while still supporting 4 | older, more restrictive kernels. 5 | 6 | [![Build status][build-badge]][build-url] 7 | 8 | [build-badge]: https://img.shields.io/github/actions/workflow/status/aya-rs/bpf-linker/ci.yml 9 | [build-url]: https://github.com/aya-rs/bpf-linker/actions/workflows/ci.yml 10 | 11 | ## Overview 12 | 13 | bpf-linker can be used to statically link multiple BPF object files together 14 | and optionally perform optimizations needed to target older kernels. It 15 | operates on LLVM bitcode, so the inputs must be bitcode files (.bc) or object 16 | files with embedded bitcode (.o), optionally stored inside ar archives (.a). 17 | 18 | ## Installation 19 | 20 | The linker requires LLVM 21. It can use the same LLVM used by the rust compiler, 21 | or it can use an external LLVM installation. 22 | 23 | If your target is `aarch64-unknown-linux-gnu` (i.e. Linux on Apple Silicon) you 24 | will have to use the *external LLVM* method. 25 | 26 | ### Using LLVM provided by rustc 27 | 28 | All you need to do is run: 29 | 30 | ```sh 31 | cargo install bpf-linker 32 | ``` 33 | 34 | ### Using external LLVM 35 | 36 | #### System packages 37 | 38 | On Debian based distributions you need to install the `llvm-21-dev` and `libclang-21-dev` 39 | packages, from the official LLVM repo at https://apt.llvm.org. 40 | 41 | You may need to build LLVM from source if a recent version is not available 42 | through any package manager that supports your platform. 43 | 44 | Once you have installed LLVM 21 you can install the linker running: 45 | 46 | ```sh 47 | cargo install bpf-linker --no-default-features --features llvm-21 48 | ``` 49 | 50 | #### Building LLVM from source 51 | 52 | LLVM can be built from source using the `xtask build-llvm` subcommand, included 53 | in bpf-linker sources. 54 | 55 | First, the LLVM sources offered in [our fork][llvm-fork] need to be cloned, 56 | using the branch matching the Rust toolchain you want to use. For current 57 | nightly: 58 | 59 | ```sh 60 | git clone -b rustc/21.1-2025-08-01 https://github.com/aya-rs/llvm-project ./llvm-project 61 | ``` 62 | 63 | If in doubt about which branch to use, check the LLVM version used by your Rust 64 | compiler: 65 | 66 | ```sh 67 | rustc [+toolchain] --version -v | grep LLVM 68 | ``` 69 | 70 | When the sources are ready, the LLVM artifacts can be built and installed in 71 | the directory provided in the `--install-prefix` argument, using `--build-dir` 72 | to store the state of the build. 73 | 74 | ```sh 75 | cargo xtask llvm build \ 76 | --src-dir ./llvm-project \ 77 | --build-dir ./llvm-build \ 78 | --install-prefix ./llvm-install 79 | ``` 80 | 81 | After that, bpf-linker can be built with the `LLVM_SYS_211_PREFIX` environment 82 | variable pointing to that directory: 83 | 84 | ```sh 85 | LLVM_SYS_211_PREFIX=./llvm-install cargo install --path . 86 | ``` 87 | 88 | If you don't have cargo you can get it from https://rustup.rs or from your distro's package manager. 89 | 90 | [llvm-fork]: https://github.com/aya-rs/llvm-project 91 | 92 | ## Usage 93 | 94 | ### Rust 95 | 96 | #### Nightly 97 | 98 | To compile your eBPF crate just run: 99 | 100 | ```sh 101 | cargo +nightly build --target=bpfel-unknown-none -Z build-std=core --release 102 | ``` 103 | 104 | If you don't want to have to pass the `target` and `build-std` options every 105 | time, you can put them in `.cargo/config.toml` under the crate's root folder: 106 | 107 | ```toml 108 | [build] 109 | target = "bpfel-unknown-none" 110 | 111 | [unstable] 112 | build-std = ["core"] 113 | ``` 114 | 115 | ##### (Experimental) BTF support 116 | 117 | To emit [BTF debug information](https://www.kernel.org/doc/html/next/bpf/btf.html), 118 | set the following rustflags: 119 | 120 | ``` 121 | -C debuginfo=2 -C link-arg=--btf 122 | ``` 123 | 124 | These flags will work only for the eBPF targets (`bpfeb-unknown-none`, 125 | `bpfel-unknown-none`). Make sure you are specifying them only for eBPF crates, 126 | not for the user-space ones! 127 | 128 | When compiling an eBPF crate directly with `cargo +nightly build`, they can be 129 | defined through the `RUSTFLAGS` environment variable: 130 | 131 | ```sh 132 | RUSTFLAGS="-C debuginfo=2 -C link-arg=--btf" cargo +nightly build --target=bpfel-unknown-none -Z build-std=core --release 133 | ``` 134 | 135 | To avoid specifying them manually, you can put them in `.cargo/config.toml`: 136 | 137 | ```toml 138 | [build] 139 | target = "bpfel-unknown-none" 140 | rustflags = "-C debuginfo=2 -C link-arg=--btf" 141 | 142 | [unstable] 143 | build-std = ["core"] 144 | ``` 145 | 146 | After that, the BPF object file present in `target/bpfel-unknown-none/release` 147 | should contain a BTF section. 148 | 149 | ### Clang 150 | 151 | For a simple example of how to use the linker with clang see [this 152 | gist](https://gist.github.com/alessandrod/ed6f11ba41bcd8a19d8655e57a00350b). In 153 | the example 154 | [lib.c](https://gist.github.com/alessandrod/ed6f11ba41bcd8a19d8655e57a00350b#file-lib-c) 155 | is compiled as a static library which is then linked by 156 | [program.c](https://gist.github.com/alessandrod/ed6f11ba41bcd8a19d8655e57a00350b#file-program-c). 157 | The 158 | [Makefile](https://gist.github.com/alessandrod/ed6f11ba41bcd8a19d8655e57a00350b#file-makefile) 159 | shows how to compile the C code and then link it. 160 | 161 | ### CLI syntax 162 | 163 | ``` 164 | bpf-linker 165 | 166 | USAGE: 167 | bpf-linker [FLAGS] [OPTIONS] --output [--] [inputs]... 168 | 169 | FLAGS: 170 | --disable-expand-memcpy-in-order Disable passing --bpf-expand-memcpy-in-order to LLVM 171 | --disable-memory-builtins Disble exporting memcpy, memmove, memset, memcmp and bcmp. Exporting those 172 | is commonly needed when LLVM does not manage to expand memory intrinsics to 173 | a sequence of loads and stores 174 | -h, --help Prints help information 175 | --ignore-inline-never Ignore `noinline`/`#[inline(never)]`. Useful when targeting kernels that 176 | don't support function calls 177 | --unroll-loops Try hard to unroll loops. Useful when targeting kernels that don't support 178 | loops 179 | -V, --version Prints version information 180 | 181 | OPTIONS: 182 | --cpu Target BPF processor. Can be one of `generic`, `probe`, `v1`, `v2`, `v3` [default: 183 | generic] 184 | --cpu-features Enable or disable CPU features. The available features are: alu32, dummy, dwarfris. 185 | Use +feature to enable a feature, or -feature to disable it. For example --cpu- 186 | features=+alu32,-dwarfris [default: ] 187 | --dump-module Dump the final IR module to the given `path` before generating the code 188 | --emit Output type. Can be one of `llvm-bc`, `asm`, `llvm-ir`, `obj` [default: obj] 189 | --export ... Comma separated list of symbols to export. See also `--export-symbols` 190 | --export-symbols Export the symbols specified in the file `path`. The symbols must be separated by 191 | new lines 192 | -L ... Add a directory to the library search path 193 | --llvm-args ... Extra command line arguments to pass to LLVM 194 | --log-file Output logs to the given `path` 195 | --log-level Set the log level. Can be one of `off`, `info`, `warn`, `debug`, `trace` 196 | -O ... Optimization level. 0-3, s, or z [default: 2] 197 | -o, --output Write output to 198 | --target LLVM target triple. When not provided, the target is inferred from the inputs 199 | 200 | ARGS: 201 | ... Input files. Can be object files or static libraries 202 | ``` 203 | 204 | ## License 205 | 206 | bpf-linker is licensed under either of 207 | 208 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 209 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 210 | 211 | at your option. 212 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Alessandro Decina 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/llvm/mod.rs: -------------------------------------------------------------------------------- 1 | mod di; 2 | mod iter; 3 | mod types; 4 | 5 | use std::{ 6 | borrow::Cow, 7 | collections::HashSet, 8 | ffi::{CStr, CString}, 9 | os::raw::c_char, 10 | ptr, slice, str, 11 | }; 12 | 13 | pub(crate) use di::DISanitizer; 14 | use iter::{IterModuleFunctions as _, IterModuleGlobalAliases as _, IterModuleGlobals as _}; 15 | use llvm_sys::{ 16 | LLVMAttributeFunctionIndex, LLVMLinkage, LLVMVisibility, 17 | bit_reader::LLVMParseBitcodeInContext2, 18 | core::{ 19 | LLVMCreateMemoryBufferWithMemoryRange, LLVMDisposeMemoryBuffer, LLVMDisposeMessage, 20 | LLVMGetEnumAttributeKindForName, LLVMGetMDString, LLVMGetModuleInlineAsm, LLVMGetTarget, 21 | LLVMGetValueName2, LLVMRemoveEnumAttributeAtIndex, LLVMSetLinkage, LLVMSetModuleInlineAsm2, 22 | LLVMSetVisibility, 23 | }, 24 | error::{ 25 | LLVMDisposeErrorMessage, LLVMGetErrorMessage, LLVMGetErrorTypeId, LLVMGetStringErrorTypeId, 26 | }, 27 | linker::LLVMLinkModules2, 28 | object::{ 29 | LLVMCreateBinary, LLVMDisposeBinary, LLVMDisposeSectionIterator, LLVMGetSectionContents, 30 | LLVMGetSectionName, LLVMGetSectionSize, LLVMMoveToNextSection, 31 | LLVMObjectFileCopySectionIterator, LLVMObjectFileIsSectionIteratorAtEnd, 32 | }, 33 | prelude::{LLVMModuleRef, LLVMValueRef}, 34 | support::LLVMParseCommandLineOptions, 35 | target::{ 36 | LLVMInitializeBPFAsmParser, LLVMInitializeBPFAsmPrinter, LLVMInitializeBPFDisassembler, 37 | LLVMInitializeBPFTarget, LLVMInitializeBPFTargetInfo, LLVMInitializeBPFTargetMC, 38 | }, 39 | target_machine::{LLVMGetTargetFromTriple, LLVMTargetRef}, 40 | transforms::pass_builder::{ 41 | LLVMCreatePassBuilderOptions, LLVMDisposePassBuilderOptions, LLVMRunPasses, 42 | }, 43 | }; 44 | use tracing::{debug, error}; 45 | pub(crate) use types::{ 46 | context::{InstalledDiagnosticHandler, LLVMContext}, 47 | memory_buffer::MemoryBuffer, 48 | module::LLVMModule, 49 | target_machine::LLVMTargetMachine, 50 | }; 51 | 52 | use crate::OptLevel; 53 | 54 | pub(crate) fn init(args: &[Cow<'_, CStr>], overview: &CStr) { 55 | unsafe { 56 | LLVMInitializeBPFTarget(); 57 | LLVMInitializeBPFTargetMC(); 58 | LLVMInitializeBPFTargetInfo(); 59 | LLVMInitializeBPFAsmPrinter(); 60 | LLVMInitializeBPFAsmParser(); 61 | LLVMInitializeBPFDisassembler(); 62 | } 63 | 64 | let c_ptrs = args.iter().map(|s| s.as_ptr()).collect::>(); 65 | unsafe { 66 | LLVMParseCommandLineOptions( 67 | c_ptrs.len().try_into().unwrap(), 68 | c_ptrs.as_ptr(), 69 | overview.as_ptr(), 70 | ) 71 | }; 72 | } 73 | 74 | pub(crate) fn find_embedded_bitcode( 75 | context: &LLVMContext, 76 | data: &[u8], 77 | ) -> Result>, String> { 78 | let buffer_name = c"mem_buffer"; 79 | let buffer = unsafe { 80 | LLVMCreateMemoryBufferWithMemoryRange( 81 | data.as_ptr().cast(), 82 | data.len(), 83 | buffer_name.as_ptr(), 84 | 0, 85 | ) 86 | }; 87 | 88 | let (bin, message) = 89 | Message::with(|message| unsafe { LLVMCreateBinary(buffer, context.as_mut_ptr(), message) }); 90 | if bin.is_null() { 91 | return Err(message.as_string_lossy().to_string()); 92 | } 93 | 94 | let mut ret = None; 95 | let iter = unsafe { LLVMObjectFileCopySectionIterator(bin) }; 96 | while unsafe { LLVMObjectFileIsSectionIteratorAtEnd(bin, iter) } == 0 { 97 | let name = unsafe { LLVMGetSectionName(iter) }; 98 | if !name.is_null() { 99 | let name = unsafe { CStr::from_ptr(name) }; 100 | if name == c".llvmbc" { 101 | let buf = unsafe { LLVMGetSectionContents(iter) }; 102 | let size = unsafe { LLVMGetSectionSize(iter) }.try_into().unwrap(); 103 | ret = Some(unsafe { slice::from_raw_parts(buf.cast(), size).to_vec() }); 104 | break; 105 | } 106 | } 107 | unsafe { LLVMMoveToNextSection(iter) }; 108 | } 109 | unsafe { LLVMDisposeSectionIterator(iter) }; 110 | unsafe { LLVMDisposeBinary(bin) }; 111 | unsafe { LLVMDisposeMemoryBuffer(buffer) }; 112 | 113 | Ok(ret) 114 | } 115 | 116 | #[must_use] 117 | pub(crate) fn link_bitcode_buffer<'ctx>( 118 | context: &'ctx LLVMContext, 119 | module: &mut LLVMModule<'ctx>, 120 | buffer: &[u8], 121 | ) -> bool { 122 | let mut linked = false; 123 | let buffer_name = c"mem_buffer"; 124 | let buffer = unsafe { 125 | LLVMCreateMemoryBufferWithMemoryRange( 126 | buffer.as_ptr().cast(), 127 | buffer.len(), 128 | buffer_name.as_ptr(), 129 | 0, 130 | ) 131 | }; 132 | 133 | let mut temp_module = ptr::null_mut(); 134 | 135 | if unsafe { LLVMParseBitcodeInContext2(context.as_mut_ptr(), buffer, &mut temp_module) } == 0 { 136 | linked = unsafe { LLVMLinkModules2(module.as_mut_ptr(), temp_module) } == 0; 137 | } 138 | 139 | unsafe { LLVMDisposeMemoryBuffer(buffer) }; 140 | 141 | linked 142 | } 143 | 144 | pub(crate) fn target_from_triple(triple: &CStr) -> Result { 145 | let mut target = ptr::null_mut(); 146 | let (ret, message) = Message::with(|message| unsafe { 147 | LLVMGetTargetFromTriple(triple.as_ptr(), &mut target, message) 148 | }); 149 | if ret == 0 { 150 | Ok(target) 151 | } else { 152 | Err(message.as_string_lossy().to_string()) 153 | } 154 | } 155 | 156 | pub(crate) fn target_from_module(module: &LLVMModule<'_>) -> Result { 157 | let triple = unsafe { LLVMGetTarget(module.as_mut_ptr()) }; 158 | unsafe { target_from_triple(CStr::from_ptr(triple)) } 159 | } 160 | 161 | pub(crate) fn optimize( 162 | tm: &LLVMTargetMachine, 163 | module: &mut LLVMModule<'_>, 164 | opt_level: OptLevel, 165 | ignore_inline_never: bool, 166 | export_symbols: &HashSet>, 167 | ) -> Result<(), String> { 168 | if module_asm_is_probestack(module.as_mut_ptr()) { 169 | unsafe { LLVMSetModuleInlineAsm2(module.as_mut_ptr(), ptr::null_mut(), 0) }; 170 | } 171 | 172 | for sym in module.as_mut_ptr().globals_iter() { 173 | internalize(sym, symbol_name(sym), export_symbols); 174 | } 175 | for sym in module.as_mut_ptr().global_aliases_iter() { 176 | internalize(sym, symbol_name(sym), export_symbols); 177 | } 178 | 179 | for function in module.as_mut_ptr().functions_iter() { 180 | let name = symbol_name(function); 181 | if !name.starts_with(b"llvm.") { 182 | if ignore_inline_never { 183 | remove_attribute(function, "noinline"); 184 | } 185 | internalize(function, name, export_symbols); 186 | } 187 | } 188 | 189 | let passes = [ 190 | // NB: "default<_>" must be the first pass in the list, otherwise it will be ignored. 191 | match opt_level { 192 | // Pretty much nothing compiles with -O0 so make it an alias for -O1. 193 | OptLevel::No | OptLevel::Less => "default", 194 | OptLevel::Default => "default", 195 | OptLevel::Aggressive => "default", 196 | OptLevel::Size => "default", 197 | OptLevel::SizeMin => "default", 198 | }, 199 | // NB: This seems to be included in most default pipelines, but not obviously all of them. 200 | // See 201 | // https://github.com/llvm/llvm-project/blob/bbe2887f/llvm/lib/Passes/PassBuilderPipelines.cpp#L2011-L2012 202 | // for a case which includes DCE only conditionally. Better safe than sorry; include it always. 203 | "dce", 204 | ]; 205 | 206 | let passes = passes.join(","); 207 | debug!("running passes: {passes}"); 208 | let passes = CString::new(passes).unwrap(); 209 | let options = unsafe { LLVMCreatePassBuilderOptions() }; 210 | let error = unsafe { 211 | LLVMRunPasses( 212 | module.as_mut_ptr(), 213 | passes.as_ptr(), 214 | tm.as_mut_ptr(), 215 | options, 216 | ) 217 | }; 218 | unsafe { LLVMDisposePassBuilderOptions(options) }; 219 | // Handle the error and print it to stderr. 220 | if !error.is_null() { 221 | let error_type_id = unsafe { LLVMGetErrorTypeId(error) }; 222 | // This is the only error type that exists currently, but there might be more in the future. 223 | assert_eq!(error_type_id, unsafe { LLVMGetStringErrorTypeId() }); 224 | let error_message = unsafe { LLVMGetErrorMessage(error) }; 225 | let error_string = unsafe { CStr::from_ptr(error_message) } 226 | .to_string_lossy() 227 | .to_string(); 228 | unsafe { LLVMDisposeErrorMessage(error_message) }; 229 | return Err(error_string); 230 | } 231 | 232 | Ok(()) 233 | } 234 | 235 | pub(crate) fn module_asm_is_probestack(module: LLVMModuleRef) -> bool { 236 | let mut len = 0; 237 | let ptr = unsafe { LLVMGetModuleInlineAsm(module, &mut len) }; 238 | if ptr.is_null() { 239 | return false; 240 | } 241 | 242 | let needle = b"__rust_probestack"; 243 | let haystack: &[u8] = unsafe { slice::from_raw_parts(ptr.cast(), len) }; 244 | haystack.windows(needle.len()).any(|w| w == needle) 245 | } 246 | 247 | pub(crate) fn symbol_name<'a>(value: *mut llvm_sys::LLVMValue) -> &'a [u8] { 248 | let mut name_len = 0; 249 | let ptr = unsafe { LLVMGetValueName2(value, &mut name_len) }; 250 | unsafe { slice::from_raw_parts(ptr.cast(), name_len) } 251 | } 252 | 253 | pub(crate) fn remove_attribute(function: *mut llvm_sys::LLVMValue, name: &str) { 254 | let attr_kind = unsafe { LLVMGetEnumAttributeKindForName(name.as_ptr().cast(), name.len()) }; 255 | unsafe { LLVMRemoveEnumAttributeAtIndex(function, LLVMAttributeFunctionIndex, attr_kind) }; 256 | } 257 | 258 | pub(crate) fn internalize( 259 | value: LLVMValueRef, 260 | name: &[u8], 261 | export_symbols: &HashSet>, 262 | ) { 263 | if !name.starts_with(b"llvm.") && !export_symbols.contains(name) { 264 | unsafe { LLVMSetLinkage(value, LLVMLinkage::LLVMInternalLinkage) }; 265 | unsafe { LLVMSetVisibility(value, LLVMVisibility::LLVMDefaultVisibility) }; 266 | } 267 | } 268 | 269 | pub(crate) trait LLVMDiagnosticHandler { 270 | fn handle_diagnostic( 271 | &mut self, 272 | severity: llvm_sys::LLVMDiagnosticSeverity, 273 | message: Cow<'_, str>, 274 | ); 275 | } 276 | 277 | pub(crate) extern "C" fn fatal_error(reason: *const c_char) { 278 | error!("fatal error: {:?}", unsafe { CStr::from_ptr(reason) }) 279 | } 280 | 281 | struct Message { 282 | ptr: *mut c_char, 283 | } 284 | 285 | impl Message { 286 | fn with T>(f: F) -> (T, Self) { 287 | let mut ptr = ptr::null_mut(); 288 | let t = f(&mut ptr); 289 | (t, Self { ptr }) 290 | } 291 | 292 | fn as_c_str(&self) -> Option<&CStr> { 293 | let Self { ptr } = self; 294 | let ptr = *ptr; 295 | (!ptr.is_null()).then(|| unsafe { CStr::from_ptr(ptr) }) 296 | } 297 | 298 | fn as_string_lossy(&self) -> Cow<'_, str> { 299 | self.as_c_str() 300 | .map(CStr::to_bytes) 301 | .map(String::from_utf8_lossy) 302 | .unwrap_or("".into()) 303 | } 304 | } 305 | 306 | impl Drop for Message { 307 | fn drop(&mut self) { 308 | let Self { ptr } = self; 309 | let ptr = *ptr; 310 | if !ptr.is_null() { 311 | unsafe { 312 | LLVMDisposeMessage(ptr); 313 | } 314 | } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/bin/bpf-linker.rs: -------------------------------------------------------------------------------- 1 | #![expect(unused_crate_dependencies, reason = "used in lib")] 2 | 3 | use std::{ 4 | env, 5 | ffi::CString, 6 | fs, io, 7 | path::{Component, Path, PathBuf}, 8 | str::FromStr, 9 | }; 10 | 11 | #[cfg(any(feature = "rust-llvm-20", feature = "rust-llvm-21"))] 12 | use aya_rustc_llvm_proxy as _; 13 | use bpf_linker::{Cpu, Linker, LinkerInput, LinkerOptions, OptLevel, OutputType}; 14 | use clap::{ 15 | Parser, 16 | builder::{PathBufValueParser, TypedValueParser as _}, 17 | error::ErrorKind, 18 | }; 19 | use thiserror::Error; 20 | use tracing::{Level, info}; 21 | use tracing_subscriber::{EnvFilter, fmt::MakeWriter, prelude::*}; 22 | use tracing_tree::HierarchicalLayer; 23 | 24 | #[derive(Debug, Error)] 25 | enum CliError { 26 | #[error("optimization level needs to be between 0-3, s or z (instead was `{0}`)")] 27 | InvalidOptimization(String), 28 | #[error("unknown emission type: `{0}` - expected one of: `llvm-bc`, `asm`, `llvm-ir`, `obj`")] 29 | InvalidOutputType(String), 30 | } 31 | 32 | #[derive(Copy, Clone, Debug)] 33 | struct CliOptLevel(OptLevel); 34 | 35 | impl FromStr for CliOptLevel { 36 | type Err = CliError; 37 | 38 | fn from_str(s: &str) -> Result { 39 | Ok(Self(match s { 40 | "0" => OptLevel::No, 41 | "1" => OptLevel::Less, 42 | "2" => OptLevel::Default, 43 | "3" => OptLevel::Aggressive, 44 | "s" => OptLevel::Size, 45 | "z" => OptLevel::SizeMin, 46 | _ => return Err(CliError::InvalidOptimization(s.to_string())), 47 | })) 48 | } 49 | } 50 | 51 | #[derive(Copy, Clone, Debug)] 52 | struct CliOutputType(OutputType); 53 | 54 | impl FromStr for CliOutputType { 55 | type Err = CliError; 56 | 57 | fn from_str(s: &str) -> Result { 58 | Ok(Self(match s { 59 | "llvm-bc" => OutputType::Bitcode, 60 | "asm" => OutputType::Assembly, 61 | "llvm-ir" => OutputType::LlvmAssembly, 62 | "obj" => OutputType::Object, 63 | _ => return Err(CliError::InvalidOutputType(s.to_string())), 64 | })) 65 | } 66 | } 67 | 68 | fn parent_and_file_name(p: PathBuf) -> anyhow::Result<(PathBuf, PathBuf)> { 69 | let mut comps = p.components(); 70 | let file_name = comps 71 | .next_back() 72 | .map(|p| match p { 73 | Component::Normal(p) => Ok(p), 74 | p => Err(anyhow::anyhow!("unexpected path component {:?}", p)), 75 | }) 76 | .transpose()? 77 | .ok_or_else(|| anyhow::anyhow!("unexpected empty path"))?; 78 | let parent = comps.as_path(); 79 | Ok((parent.to_path_buf(), Path::new(file_name).to_path_buf())) 80 | } 81 | 82 | #[derive(Debug, Parser)] 83 | #[command(version)] 84 | struct CommandLine { 85 | /// LLVM target triple. When not provided, the target is inferred from the inputs 86 | #[clap(long)] 87 | target: Option, 88 | 89 | /// Target BPF processor. Can be one of `generic`, `probe`, `v1`, `v2`, `v3` 90 | #[clap(long, default_value = "generic")] 91 | cpu: Cpu, 92 | 93 | /// Enable or disable CPU features. The available features are: alu32, dummy, dwarfris. Use 94 | /// +feature to enable a feature, or -feature to disable it. For example 95 | /// --cpu-features=+alu32,-dwarfris 96 | #[clap(long, value_name = "features", default_value = "")] 97 | cpu_features: CString, 98 | 99 | /// Write output to 100 | #[clap(short, long)] 101 | output: PathBuf, 102 | 103 | /// Output type. Can be one of `llvm-bc`, `asm`, `llvm-ir`, `obj` 104 | #[clap(long, default_value = "obj")] 105 | emit: Vec, 106 | 107 | /// Emit BTF information 108 | #[clap(long)] 109 | btf: bool, 110 | 111 | /// Permit automatic insertion of __bpf_trap calls. 112 | /// See: https://github.com/llvm/llvm-project/commit/ab391beb11f733b526b86f9df23734a34657d876 113 | #[clap(long)] 114 | allow_bpf_trap: bool, 115 | 116 | /// UNUSED: it only exists for compatibility with rustc 117 | #[clap(short = 'L', number_of_values = 1)] 118 | _libs: Vec, 119 | 120 | /// Optimization level. 0-3, s, or z 121 | #[clap(short = 'O', default_value = "2")] 122 | optimize: Vec, 123 | 124 | /// Export the symbols specified in the file `path`. The symbols must be separated by new lines 125 | #[clap(long, value_name = "path")] 126 | export_symbols: Option, 127 | 128 | /// Output logs to the given `path` 129 | #[clap( 130 | long, 131 | value_name = "path", 132 | value_parser = PathBufValueParser::new().try_map(parent_and_file_name), 133 | )] 134 | log_file: Option<(PathBuf, PathBuf)>, 135 | 136 | /// Set the log level. If not specified, no logging is used. Can be one of 137 | /// `error`, `warn`, `info`, `debug`, `trace`. 138 | #[clap(long, value_name = "level")] 139 | log_level: Option, 140 | 141 | /// Try hard to unroll loops. Useful when targeting kernels that don't support loops 142 | #[clap(long)] 143 | unroll_loops: bool, 144 | 145 | /// Ignore `noinline`/`#[inline(never)]`. Useful when targeting kernels that don't support function calls 146 | #[clap(long)] 147 | ignore_inline_never: bool, 148 | 149 | /// Dump the final IR module to the given `path` before generating the code 150 | #[clap(long, value_name = "path")] 151 | dump_module: Option, 152 | 153 | /// Extra command line arguments to pass to LLVM 154 | #[clap(long, value_name = "args", use_value_delimiter = true, action = clap::ArgAction::Append)] 155 | llvm_args: Vec, 156 | 157 | /// Disable passing --bpf-expand-memcpy-in-order to LLVM. 158 | #[clap(long)] 159 | disable_expand_memcpy_in_order: bool, 160 | 161 | /// Disable exporting memcpy, memmove, memset, memcmp and bcmp. Exporting 162 | /// those is commonly needed when LLVM does not manage to expand memory 163 | /// intrinsics to a sequence of loads and stores. 164 | #[clap(long)] 165 | disable_memory_builtins: bool, 166 | 167 | /// Input files. Can be object files or static libraries 168 | #[clap(required = true)] 169 | inputs: Vec, 170 | 171 | /// Comma separated list of symbols to export. See also `--export-symbols` 172 | #[clap(long, value_name = "symbols", use_value_delimiter = true, action = clap::ArgAction::Append)] 173 | export: Vec, 174 | 175 | /// Whether to treat LLVM errors as fatal. 176 | #[clap(long, action = clap::ArgAction::Set, default_value_t = true)] 177 | fatal_errors: bool, 178 | 179 | // The options below are for wasm-ld compatibility 180 | #[clap(long = "debug", hide = true)] 181 | _debug: bool, 182 | } 183 | 184 | /// Returns a [`HierarchicalLayer`](tracing_tree::HierarchicalLayer) for the 185 | /// given `writer`. 186 | fn tracing_layer(writer: W) -> HierarchicalLayer 187 | where 188 | W: for<'writer> MakeWriter<'writer> + 'static, 189 | { 190 | const TRACING_IDENT: usize = 2; 191 | HierarchicalLayer::new(TRACING_IDENT) 192 | .with_indent_lines(true) 193 | .with_writer(writer) 194 | } 195 | fn main() -> anyhow::Result<()> { 196 | let args = env::args().map(|arg| { 197 | if arg == "-flavor" { 198 | "--flavor".to_string() 199 | } else { 200 | arg 201 | } 202 | }); 203 | let CommandLine { 204 | target, 205 | cpu, 206 | cpu_features, 207 | output, 208 | emit, 209 | btf, 210 | allow_bpf_trap, 211 | optimize, 212 | export_symbols, 213 | log_file, 214 | log_level, 215 | unroll_loops, 216 | ignore_inline_never, 217 | dump_module, 218 | llvm_args, 219 | disable_expand_memcpy_in_order, 220 | disable_memory_builtins, 221 | inputs, 222 | export, 223 | fatal_errors, 224 | _debug, 225 | _libs, 226 | } = match Parser::try_parse_from(args) { 227 | Ok(command_line) => command_line, 228 | Err(err) => match err.kind() { 229 | ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => { 230 | print!("{err}"); 231 | return Ok(()); 232 | } 233 | _ => return Err(err.into()), 234 | }, 235 | }; 236 | 237 | // Configure tracing. 238 | let _guard = { 239 | let filter = EnvFilter::from_default_env(); 240 | let filter = match log_level { 241 | None => filter, 242 | Some(log_level) => filter.add_directive(log_level.into()), 243 | }; 244 | let subscriber_registry = tracing_subscriber::registry().with(filter); 245 | match log_file { 246 | Some((parent, file_name)) => { 247 | let file_appender = tracing_appender::rolling::never(parent, file_name); 248 | let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); 249 | let subscriber = subscriber_registry 250 | .with(tracing_layer(io::stdout)) 251 | .with(tracing_layer(non_blocking)); 252 | tracing::subscriber::set_global_default(subscriber)?; 253 | Some(guard) 254 | } 255 | None => { 256 | let subscriber = subscriber_registry.with(tracing_layer(io::stderr)); 257 | tracing::subscriber::set_global_default(subscriber)?; 258 | None 259 | } 260 | } 261 | }; 262 | 263 | info!( 264 | "command line: {:?}", 265 | env::args().collect::>().join(" ") 266 | ); 267 | 268 | let export_symbols = export_symbols.map(fs::read_to_string).transpose()?; 269 | 270 | let export_symbols = export_symbols 271 | .as_deref() 272 | .into_iter() 273 | .flat_map(str::lines) 274 | .chain(export.iter().map(String::as_str)); 275 | 276 | let output_type = match *emit.as_slice() { 277 | [] => unreachable!("emit has a default value"), 278 | [CliOutputType(output_type), ..] => output_type, 279 | }; 280 | let optimize = match *optimize.as_slice() { 281 | [] => unreachable!("emit has a default value"), 282 | [.., CliOptLevel(optimize)] => optimize, 283 | }; 284 | 285 | let mut linker = Linker::new(LinkerOptions { 286 | target, 287 | cpu, 288 | cpu_features, 289 | optimize, 290 | unroll_loops, 291 | ignore_inline_never, 292 | llvm_args, 293 | disable_expand_memcpy_in_order, 294 | disable_memory_builtins, 295 | btf, 296 | allow_bpf_trap, 297 | }); 298 | 299 | if let Some(path) = dump_module { 300 | linker.set_dump_module_path(path); 301 | } 302 | 303 | let inputs = inputs 304 | .iter() 305 | .map(|p| LinkerInput::new_from_file(p.as_path())); 306 | 307 | linker.link_to_file(inputs, &output, output_type, export_symbols)?; 308 | 309 | if fatal_errors && linker.has_errors() { 310 | return Err(anyhow::anyhow!( 311 | "LLVM issued diagnostic with error severity" 312 | )); 313 | } 314 | 315 | Ok(()) 316 | } 317 | 318 | #[cfg(test)] 319 | mod test { 320 | use super::*; 321 | 322 | // Test made to reproduce the following bug: 323 | // https://github.com/aya-rs/bpf-linker/issues/27 324 | // where --export argument followed by positional arguments resulted in 325 | // parsing the positional args as `export`, not as `inputs`. 326 | // There can be multiple exports, but they always have to be preceded by 327 | // `--export` flag. 328 | #[test] 329 | fn test_export_input_args() { 330 | let args = [ 331 | "bpf-linker", 332 | "--export", 333 | "foo", 334 | "--export", 335 | "bar", 336 | "symbols.o", // this should be parsed as `input`, not `export` 337 | "rcgu.o", // this should be parsed as `input`, not `export` 338 | "-L", 339 | "target/debug/deps", 340 | "-L", 341 | "target/debug", 342 | "-L", 343 | "/home/foo/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib", 344 | "-o", 345 | "/tmp/bin.s", 346 | "--target=bpf", 347 | "--emit=asm", 348 | ]; 349 | let CommandLine { inputs, export, .. } = Parser::parse_from(args); 350 | assert_eq!(export, ["foo", "bar"]); 351 | assert_eq!( 352 | inputs, 353 | [PathBuf::from("symbols.o"), PathBuf::from("rcgu.o")] 354 | ); 355 | } 356 | 357 | #[test] 358 | fn test_export_delimiter() { 359 | let args = [ 360 | "bpf-linker", 361 | "--export", 362 | "foo,bar", 363 | "--export=ayy,lmao", 364 | "symbols.o", // this should be parsed as `input`, not `export` 365 | "--export=lol", 366 | "--export", 367 | "rotfl", 368 | "rcgu.o", // this should be parsed as `input`, not `export` 369 | "-L", 370 | "target/debug/deps", 371 | "-L", 372 | "target/debug", 373 | "-L", 374 | "/home/foo/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib", 375 | "-o", 376 | "/tmp/bin.s", 377 | "--target=bpf", 378 | "--emit=asm", 379 | ]; 380 | let CommandLine { inputs, export, .. } = Parser::parse_from(args); 381 | assert_eq!(export, ["foo", "bar", "ayy", "lmao", "lol", "rotfl"]); 382 | assert_eq!( 383 | inputs, 384 | [PathBuf::from("symbols.o"), PathBuf::from("rcgu.o")] 385 | ); 386 | } 387 | } 388 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | ffi::{OsStr, OsString}, 4 | fs, 5 | io::{self, Write as _}, 6 | os::unix::ffi::OsStrExt as _, 7 | path::PathBuf, 8 | process::Command, 9 | }; 10 | 11 | use anyhow::{Context as _, Result, anyhow}; 12 | use reqwest::{ 13 | blocking::Client, 14 | header::{ACCEPT, AUTHORIZATION, HeaderMap, USER_AGENT}, 15 | }; 16 | use rustc_build_sysroot::{BuildMode, SysrootConfig, SysrootStatus}; 17 | use serde::Deserialize; 18 | use walkdir::WalkDir; 19 | 20 | #[derive(Clone, clap::ValueEnum)] 21 | enum Target { 22 | BpfebUnknownNone, 23 | BpfelUnknownNone, 24 | } 25 | 26 | impl Target { 27 | fn as_str(&self) -> &'static str { 28 | match self { 29 | Self::BpfebUnknownNone => "bpfeb-unknown-none", 30 | Self::BpfelUnknownNone => "bpfel-unknown-none", 31 | } 32 | } 33 | } 34 | 35 | #[derive(clap::Parser)] 36 | struct BuildStd { 37 | #[arg(long)] 38 | rustc_src: PathBuf, 39 | 40 | #[arg(long)] 41 | sysroot_dir: PathBuf, 42 | 43 | #[arg(long, value_enum)] 44 | target: Target, 45 | } 46 | 47 | #[derive(clap::Parser)] 48 | struct BuildLlvm { 49 | /// Source directory. 50 | #[arg(long)] 51 | src_dir: PathBuf, 52 | /// Build directory. 53 | #[arg(long)] 54 | build_dir: PathBuf, 55 | /// Directory in which the built LLVM artifacts are installed. 56 | #[arg(long)] 57 | install_prefix: PathBuf, 58 | } 59 | 60 | #[derive(clap::Args)] 61 | struct RustcLlvmCommitOptions { 62 | /// GitHub token used for API requests. Reads from `GITHUB_TOKEN` when unset. 63 | #[arg(long = "github-token", env = "GITHUB_TOKEN")] 64 | github_token: String, 65 | } 66 | 67 | #[derive(clap::Subcommand)] 68 | enum XtaskSubcommand { 69 | /// Builds the Rust standard library for the given target in the current 70 | /// toolchain's sysroot. 71 | BuildStd(BuildStd), 72 | /// Manages and builds LLVM. 73 | BuildLlvm(BuildLlvm), 74 | /// Finds the commit in github.com/rust-lang/rust that can be used for 75 | /// downloading LLVM for the current Rust toolchain. 76 | RustcLlvmCommit(RustcLlvmCommitOptions), 77 | } 78 | 79 | /// Additional build commands for bpf-linker. 80 | #[derive(clap::Parser)] 81 | struct CommandLine { 82 | #[command(subcommand)] 83 | subcommand: XtaskSubcommand, 84 | } 85 | 86 | fn build_std(options: BuildStd) -> Result<()> { 87 | let BuildStd { 88 | rustc_src, 89 | sysroot_dir, 90 | target, 91 | } = options; 92 | 93 | let target = target.as_str(); 94 | let sysroot_status = 95 | match rustc_build_sysroot::SysrootBuilder::new(sysroot_dir.as_path(), target) 96 | // Do a full sysroot build. 97 | .build_mode(BuildMode::Build) 98 | // We want only `core`, not `std`. 99 | .sysroot_config(SysrootConfig::NoStd) 100 | // Include debug symbols in order to generate correct BTF types for 101 | // the core types as well. 102 | .rustflag("-Cdebuginfo=2") 103 | .build_from_source(&rustc_src)? 104 | { 105 | SysrootStatus::AlreadyCached => "was already built", 106 | SysrootStatus::SysrootBuilt => "built successfully", 107 | }; 108 | println!( 109 | "Standard library for target {target} {sysroot_status}: {}", 110 | sysroot_dir.display() 111 | ); 112 | Ok(()) 113 | } 114 | 115 | fn build_llvm(options: BuildLlvm) -> Result<()> { 116 | let BuildLlvm { 117 | src_dir, 118 | build_dir, 119 | install_prefix, 120 | } = options; 121 | 122 | let mut install_arg = OsString::from("-DCMAKE_INSTALL_PREFIX="); 123 | install_arg.push(install_prefix.as_os_str()); 124 | let mut cmake_configure = Command::new("cmake"); 125 | let cmake_configure = cmake_configure 126 | .arg("-S") 127 | .arg(src_dir.join("llvm")) 128 | .arg("-B") 129 | .arg(&build_dir) 130 | .args([ 131 | "-G", 132 | "Ninja", 133 | "-DCMAKE_BUILD_TYPE=RelWithDebInfo", 134 | "-DCMAKE_C_COMPILER=clang", 135 | "-DCMAKE_CXX_COMPILER=clang++", 136 | "-DLLVM_BUILD_LLVM_DYLIB=ON", 137 | "-DLLVM_ENABLE_ASSERTIONS=ON", 138 | "-DLLVM_ENABLE_PROJECTS=", 139 | "-DLLVM_ENABLE_RUNTIMES=", 140 | "-DLLVM_INSTALL_UTILS=ON", 141 | "-DLLVM_LINK_LLVM_DYLIB=ON", 142 | "-DLLVM_TARGETS_TO_BUILD=BPF", 143 | "-DLLVM_USE_LINKER=lld", 144 | ]) 145 | .arg(install_arg); 146 | println!("Configuring LLVM with command {cmake_configure:?}"); 147 | let status = cmake_configure.status().with_context(|| { 148 | format!("failed to configure LLVM build with command {cmake_configure:?}") 149 | })?; 150 | if !status.success() { 151 | anyhow::bail!("failed to configure LLVM build with command {cmake_configure:?}: {status}"); 152 | } 153 | 154 | let mut cmake_build = Command::new("cmake"); 155 | let cmake_build = cmake_build 156 | .arg("--build") 157 | .arg(build_dir) 158 | .args(["--target", "install"]) 159 | // Create symlinks rather than copies to conserve disk space, 160 | // especially on GitHub-hosted runners. 161 | // 162 | // Since the LLVM build creates a bunch of symlinks (and this setting 163 | // does not turn those into symlinks-to-symlinks), use absolute 164 | // symlinks so we can distinguish the two cases. 165 | .env("CMAKE_INSTALL_MODE", "ABS_SYMLINK"); 166 | println!("Building LLVM with command {cmake_build:?}"); 167 | let status = cmake_build 168 | .status() 169 | .with_context(|| format!("failed to build LLVM with command {cmake_configure:?}"))?; 170 | if !status.success() { 171 | anyhow::bail!("failed to configure LLVM build with command {cmake_configure:?}: {status}"); 172 | } 173 | 174 | // Move targets over the symlinks that point to them. 175 | // 176 | // This whole dance would be simpler if CMake supported 177 | // `CMAKE_INSTALL_MODE=MOVE`. 178 | for entry in WalkDir::new(&install_prefix).follow_links(false) { 179 | let entry = entry.with_context(|| { 180 | format!( 181 | "failed to read filesystem entry while traversing install prefix {}", 182 | install_prefix.display() 183 | ) 184 | })?; 185 | if !entry.file_type().is_symlink() { 186 | continue; 187 | } 188 | 189 | let link_path = entry.path(); 190 | let target = fs::read_link(link_path) 191 | .with_context(|| format!("failed to read the link {}", link_path.display()))?; 192 | if target.is_absolute() { 193 | fs::rename(&target, link_path).with_context(|| { 194 | format!( 195 | "failed to move the target file {} to the location of the symlink {}", 196 | target.display(), 197 | link_path.display() 198 | ) 199 | })?; 200 | } 201 | } 202 | 203 | Ok(()) 204 | } 205 | 206 | #[derive(Deserialize)] 207 | struct SearchIssuesResponse { 208 | items: Vec, 209 | } 210 | 211 | #[derive(Deserialize)] 212 | struct IssueItem { 213 | number: u64, 214 | title: String, 215 | } 216 | 217 | #[derive(Deserialize)] 218 | struct PullRequest { 219 | merge_commit_sha: Option, 220 | } 221 | 222 | fn expect_single<'a>( 223 | slice: &'a [&'a [u8]], 224 | what: &'a str, 225 | cmd: &'a Command, 226 | cmd_output: &'a [u8], 227 | ) -> Result<&'a [u8]> { 228 | match slice { 229 | [] => anyhow::bail!( 230 | "failed to find `{}` line in {:?} output: {}", 231 | what, 232 | cmd, 233 | OsStr::from_bytes(cmd_output).display(), 234 | ), 235 | [one] => Ok(one), 236 | _ => anyhow::bail!( 237 | "found multiple `{}` lines in {:?} output: {}", 238 | what, 239 | cmd, 240 | OsStr::from_bytes(cmd_output).display(), 241 | ), 242 | } 243 | } 244 | 245 | /// Finds a commit in the [Rust GitHub repository][rust-repo] that corresponds 246 | /// to an update of LLVM and can be used to download libLLVM from Rust CI. 247 | /// 248 | /// [rust-repo]: https://github.com/rust-lang/rust 249 | fn rustc_llvm_commit(options: RustcLlvmCommitOptions) -> Result<()> { 250 | let RustcLlvmCommitOptions { github_token } = options; 251 | let toolchain = env::var_os("RUSTUP_TOOLCHAIN"); 252 | 253 | let mut rustc_cmd = Command::new("rustc"); 254 | if let Some(toolchain) = toolchain { 255 | let mut toolchain_arg = OsString::new(); 256 | toolchain_arg.push(toolchain); 257 | let _: &mut Command = rustc_cmd.arg(toolchain_arg); 258 | } 259 | let output = rustc_cmd 260 | .args(["--version", "--verbose"]) 261 | .output() 262 | .with_context(|| format!("failed to run {rustc_cmd:?}"))?; 263 | 264 | if !output.status.success() { 265 | return Err(anyhow!( 266 | "{rustc_cmd:?} failed with status {}", 267 | output.status 268 | )); 269 | } 270 | 271 | // `rustc --version --verbose` output should contain lines starting from: 272 | // 273 | // - `commit-hash:` 274 | // - `release:` 275 | // - `LLVM version:` 276 | // 277 | // Example: 278 | // 279 | // ``` 280 | // commit-hash: 31010ca61c3ff019e1480dda0a7ef16bd2bd51c0 281 | // release: 1.94.0-nightly 282 | // LLVM version: 21.1.8 283 | // ``` 284 | let mut commit_hashes = Vec::new(); 285 | let mut rust_versions = Vec::new(); 286 | let mut llvm_versions = Vec::new(); 287 | for line in output.stdout.split(|&b| b == b'\n') { 288 | if let Some(commit_hash) = line.strip_prefix(b"commit-hash: ") { 289 | commit_hashes.push(commit_hash); 290 | } 291 | if let Some(rust_version) = line.strip_prefix(b"release: ") { 292 | rust_versions.push(rust_version); 293 | } 294 | if let Some(llvm_version) = line.strip_prefix(b"LLVM version: ") { 295 | llvm_versions.push(llvm_version) 296 | } 297 | } 298 | let rust_version = expect_single(&rust_versions, "release:", &rustc_cmd, &output.stdout)?; 299 | 300 | if rust_version.ends_with(b"nightly") { 301 | // For nightly Rust, CI publishes LLVM tarballs for each recent commit. 302 | // We can therefore use the Rust commit hash directly. 303 | let commit_hash = 304 | expect_single(&commit_hashes, "commit-hash:", &rustc_cmd, &output.stdout)?; 305 | let mut stdout = io::stdout().lock(); 306 | stdout.write_all(commit_hash)?; 307 | stdout.write_all(b"\n")?; 308 | } else { 309 | // For stable Rust, CI does not publish LLVM tarballs per commit. 310 | // Instead, we must locate the merge commit that introduced the 311 | // corresponding LLVM version. 312 | 313 | let llvm_version = 314 | expect_single(&llvm_versions, "LLVM version:", &rustc_cmd, &output.stdout)?; 315 | 316 | // reqwest does not accept raw bytes. 317 | let llvm_version = str::from_utf8(llvm_version).with_context(|| { 318 | format!( 319 | "llvm version is not valid UTF-8: {}", 320 | OsStr::from_bytes(llvm_version).display() 321 | ) 322 | })?; 323 | 324 | let pr_title = format!("Update LLVM to {llvm_version}"); 325 | let query = format!(r#"repo:rust-lang/rust is:pr is:closed in:title "{pr_title}""#); 326 | 327 | let headers: HeaderMap = [ 328 | // GitHub requires a User-Agent header; requests without one get a 403. 329 | // Any non-empty value works, but we provide an identifier for this tool. 330 | (USER_AGENT, "bpf-linker-xtask/0.1".parse().unwrap()), 331 | (ACCEPT, "application/vnd.github+json".parse().unwrap()), 332 | ( 333 | AUTHORIZATION, 334 | format!("Bearer {github_token}").parse().unwrap(), 335 | ), 336 | ] 337 | .into_iter() 338 | .collect(); 339 | let client = Client::builder() 340 | .default_headers(headers) 341 | .build() 342 | .with_context(|| "failed to build an HTTP client")?; 343 | 344 | const ISSUES_URL: &str = "https://api.github.com/search/issues"; 345 | let resp = client 346 | .get(ISSUES_URL) 347 | .query(&[("q", query)]) 348 | .send() 349 | .with_context(|| format!("failed to send the request to {ISSUES_URL}"))? 350 | .error_for_status() 351 | .with_context(|| format!("HTTP request to {ISSUES_URL} returned an error status"))?; 352 | 353 | let body: SearchIssuesResponse = resp.json()?; 354 | let pr = body 355 | .items 356 | .into_iter() 357 | .find(|item| item.title == pr_title) 358 | .ok_or_else(|| anyhow!("failed to find an LLVM bump PR titled \"{pr_title}\""))?; 359 | let pr_number = pr.number; 360 | 361 | let url = format!("https://api.github.com/repos/rust-lang/rust/pulls/{pr_number}"); 362 | let resp = client 363 | .get(&url) 364 | .send() 365 | .with_context(|| format!("failed to send the request to {url}"))? 366 | .error_for_status() 367 | .with_context(|| format!("HTTP request to {url} returned an error status"))?; 368 | let pr: PullRequest = resp.json()?; 369 | 370 | let bors_sha = pr 371 | .merge_commit_sha 372 | .ok_or_else(|| anyhow!("PR #{pr_number} has no merge_commit_sha"))?; 373 | println!("{bors_sha}"); 374 | } 375 | 376 | Ok(()) 377 | } 378 | 379 | fn main() -> Result<()> { 380 | let CommandLine { subcommand } = clap::Parser::parse(); 381 | match subcommand { 382 | XtaskSubcommand::BuildStd(options) => build_std(options), 383 | XtaskSubcommand::BuildLlvm(options) => build_llvm(options), 384 | XtaskSubcommand::RustcLlvmCommit(options) => rustc_llvm_commit(options), 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /src/llvm/types/ir.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use llvm_sys::{ 4 | core::{ 5 | LLVMCountParams, LLVMDisposeValueMetadataEntries, LLVMGetNumOperands, LLVMGetOperand, 6 | LLVMGetParam, LLVMGlobalCopyAllMetadata, LLVMIsAFunction, LLVMIsAGlobalObject, 7 | LLVMIsAInstruction, LLVMIsAMDNode, LLVMIsAUser, LLVMMDNodeInContext2, 8 | LLVMMDStringInContext2, LLVMMetadataAsValue, LLVMPrintValueToString, 9 | LLVMReplaceMDNodeOperandWith, LLVMValueAsMetadata, LLVMValueMetadataEntriesGetKind, 10 | LLVMValueMetadataEntriesGetMetadata, 11 | }, 12 | debuginfo::{LLVMGetMetadataKind, LLVMGetSubprogram, LLVMMetadataKind, LLVMSetSubprogram}, 13 | prelude::{ 14 | LLVMBasicBlockRef, LLVMContextRef, LLVMMetadataRef, LLVMValueMetadataEntry, LLVMValueRef, 15 | }, 16 | }; 17 | 18 | use crate::llvm::{ 19 | Message, 20 | iter::IterBasicBlocks as _, 21 | symbol_name, 22 | types::di::{DICompositeType, DIDerivedType, DISubprogram, DIType}, 23 | }; 24 | 25 | pub(crate) fn replace_name( 26 | value_ref: LLVMValueRef, 27 | context: LLVMContextRef, 28 | name_operand_index: u32, 29 | name: &[u8], 30 | ) { 31 | let name = unsafe { LLVMMDStringInContext2(context, name.as_ptr().cast(), name.len()) }; 32 | unsafe { LLVMReplaceMDNodeOperandWith(value_ref, name_operand_index, name) }; 33 | } 34 | 35 | #[derive(Clone)] 36 | pub(crate) enum Value<'ctx> { 37 | MDNode(MDNode<'ctx>), 38 | Function(Function<'ctx>), 39 | Other(LLVMValueRef), 40 | } 41 | 42 | impl std::fmt::Debug for Value<'_> { 43 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 44 | let value_to_string = |value| { 45 | Message { 46 | ptr: unsafe { LLVMPrintValueToString(value) }, 47 | } 48 | .as_string_lossy() 49 | .to_string() 50 | }; 51 | match self { 52 | Self::MDNode(node) => f 53 | .debug_struct("MDNode") 54 | .field("value", &value_to_string(node.value_ref)) 55 | .finish(), 56 | Self::Function(fun) => f 57 | .debug_struct("Function") 58 | .field("value", &value_to_string(fun.value_ref)) 59 | .finish(), 60 | Self::Other(value) => f 61 | .debug_struct("Other") 62 | .field("value", &value_to_string(*value)) 63 | .finish(), 64 | } 65 | } 66 | } 67 | 68 | impl Value<'_> { 69 | pub(crate) fn new(value: LLVMValueRef) -> Self { 70 | if unsafe { !LLVMIsAMDNode(value).is_null() } { 71 | let mdnode = unsafe { MDNode::from_value_ref(value) }; 72 | return Value::MDNode(mdnode); 73 | } else if unsafe { !LLVMIsAFunction(value).is_null() } { 74 | return Value::Function(unsafe { Function::from_value_ref(value) }); 75 | } 76 | Value::Other(value) 77 | } 78 | 79 | pub(crate) fn metadata_entries(&self) -> Option { 80 | let value = match self { 81 | Value::MDNode(node) => node.value_ref, 82 | Value::Function(f) => f.value_ref, 83 | Value::Other(value) => *value, 84 | }; 85 | MetadataEntries::new(value) 86 | } 87 | 88 | pub(crate) fn operands(&self) -> Option> { 89 | let value = match self { 90 | Value::MDNode(node) => Some(node.value_ref), 91 | Value::Function(f) => Some(f.value_ref), 92 | Value::Other(value) if unsafe { !LLVMIsAUser(*value).is_null() } => Some(*value), 93 | _ => None, 94 | }; 95 | 96 | value.map(|value| unsafe { 97 | (0..LLVMGetNumOperands(value)).map(move |i| LLVMGetOperand(value, i.cast_unsigned())) 98 | }) 99 | } 100 | } 101 | 102 | pub(crate) enum Metadata<'ctx> { 103 | DICompositeType(DICompositeType<'ctx>), 104 | DIDerivedType(DIDerivedType<'ctx>), 105 | DISubprogram(DISubprogram<'ctx>), 106 | Other(#[expect(dead_code)] LLVMValueRef), 107 | } 108 | 109 | impl Metadata<'_> { 110 | /// Constructs a new [`Metadata`] from the given `value`. 111 | /// 112 | /// # Safety 113 | /// 114 | /// This method assumes that the provided `value` corresponds to a valid 115 | /// instance of [LLVM `Metadata`](https://llvm.org/doxygen/classllvm_1_1Metadata.html). 116 | /// It's the caller's responsibility to ensure this invariant, as this 117 | /// method doesn't perform any valiation checks. 118 | pub(crate) unsafe fn from_value_ref(value: LLVMValueRef) -> Self { 119 | unsafe { 120 | let metadata = LLVMValueAsMetadata(value); 121 | 122 | match LLVMGetMetadataKind(metadata) { 123 | LLVMMetadataKind::LLVMDICompositeTypeMetadataKind => { 124 | let di_composite_type = DICompositeType::from_value_ref(value); 125 | Metadata::DICompositeType(di_composite_type) 126 | } 127 | LLVMMetadataKind::LLVMDIDerivedTypeMetadataKind => { 128 | let di_derived_type = DIDerivedType::from_value_ref(value); 129 | Metadata::DIDerivedType(di_derived_type) 130 | } 131 | LLVMMetadataKind::LLVMDISubprogramMetadataKind => { 132 | let di_subprogram = DISubprogram::from_value_ref(value); 133 | Metadata::DISubprogram(di_subprogram) 134 | } 135 | LLVMMetadataKind::LLVMDIGlobalVariableMetadataKind 136 | | LLVMMetadataKind::LLVMDICommonBlockMetadataKind 137 | | LLVMMetadataKind::LLVMMDStringMetadataKind 138 | | LLVMMetadataKind::LLVMConstantAsMetadataMetadataKind 139 | | LLVMMetadataKind::LLVMLocalAsMetadataMetadataKind 140 | | LLVMMetadataKind::LLVMDistinctMDOperandPlaceholderMetadataKind 141 | | LLVMMetadataKind::LLVMMDTupleMetadataKind 142 | | LLVMMetadataKind::LLVMDILocationMetadataKind 143 | | LLVMMetadataKind::LLVMDIExpressionMetadataKind 144 | | LLVMMetadataKind::LLVMDIGlobalVariableExpressionMetadataKind 145 | | LLVMMetadataKind::LLVMGenericDINodeMetadataKind 146 | | LLVMMetadataKind::LLVMDISubrangeMetadataKind 147 | | LLVMMetadataKind::LLVMDIEnumeratorMetadataKind 148 | | LLVMMetadataKind::LLVMDIBasicTypeMetadataKind 149 | | LLVMMetadataKind::LLVMDISubroutineTypeMetadataKind 150 | | LLVMMetadataKind::LLVMDIFileMetadataKind 151 | | LLVMMetadataKind::LLVMDICompileUnitMetadataKind 152 | | LLVMMetadataKind::LLVMDILexicalBlockMetadataKind 153 | | LLVMMetadataKind::LLVMDILexicalBlockFileMetadataKind 154 | | LLVMMetadataKind::LLVMDINamespaceMetadataKind 155 | | LLVMMetadataKind::LLVMDIModuleMetadataKind 156 | | LLVMMetadataKind::LLVMDITemplateTypeParameterMetadataKind 157 | | LLVMMetadataKind::LLVMDITemplateValueParameterMetadataKind 158 | | LLVMMetadataKind::LLVMDILocalVariableMetadataKind 159 | | LLVMMetadataKind::LLVMDILabelMetadataKind 160 | | LLVMMetadataKind::LLVMDIObjCPropertyMetadataKind 161 | | LLVMMetadataKind::LLVMDIImportedEntityMetadataKind 162 | | LLVMMetadataKind::LLVMDIMacroMetadataKind 163 | | LLVMMetadataKind::LLVMDIMacroFileMetadataKind 164 | | LLVMMetadataKind::LLVMDIStringTypeMetadataKind 165 | | LLVMMetadataKind::LLVMDIGenericSubrangeMetadataKind 166 | | LLVMMetadataKind::LLVMDIArgListMetadataKind 167 | | LLVMMetadataKind::LLVMDIAssignIDMetadataKind => Metadata::Other(value), 168 | #[cfg(feature = "llvm-21")] 169 | LLVMMetadataKind::LLVMDISubrangeTypeMetadataKind 170 | | LLVMMetadataKind::LLVMDIFixedPointTypeMetadataKind => Metadata::Other(value), 171 | } 172 | } 173 | } 174 | } 175 | 176 | impl<'ctx> TryFrom> for Metadata<'ctx> { 177 | type Error = (); 178 | 179 | fn try_from(md_node: MDNode<'_>) -> Result { 180 | // FIXME: fail if md_node isn't a Metadata node 181 | Ok(unsafe { Self::from_value_ref(md_node.value_ref) }) 182 | } 183 | } 184 | 185 | /// Represents a metadata node. 186 | #[derive(Clone)] 187 | pub(crate) struct MDNode<'ctx> { 188 | pub(super) value_ref: LLVMValueRef, 189 | _marker: PhantomData<&'ctx ()>, 190 | } 191 | 192 | impl MDNode<'_> { 193 | /// Constructs a new [`MDNode`] from the given `metadata`. 194 | /// 195 | /// # Safety 196 | /// 197 | /// This method assumes that the given `metadata` corresponds to a valid 198 | /// instance of [LLVM `MDNode`](https://llvm.org/doxygen/classllvm_1_1MDNode.html). 199 | /// It's the caller's responsibility to ensure this invariant, as this 200 | /// method doesn't perform any validation checks. 201 | pub(crate) unsafe fn from_metadata_ref( 202 | context: LLVMContextRef, 203 | metadata: LLVMMetadataRef, 204 | ) -> Self { 205 | unsafe { MDNode::from_value_ref(LLVMMetadataAsValue(context, metadata)) } 206 | } 207 | 208 | /// Constructs a new [`MDNode`] from the given `value`. 209 | /// 210 | /// # Safety 211 | /// 212 | /// This method assumes that the provided `value` corresponds to a valid 213 | /// instance of [LLVM `MDNode`](https://llvm.org/doxygen/classllvm_1_1MDNode.html). 214 | /// It's the caller's responsibility to ensure this invariant, as this 215 | /// method doesn't perform any valiation checks. 216 | pub(crate) unsafe fn from_value_ref(value_ref: LLVMValueRef) -> Self { 217 | Self { 218 | value_ref, 219 | _marker: PhantomData, 220 | } 221 | } 222 | 223 | /// Constructs an empty metadata node. 224 | pub(crate) fn empty(context: LLVMContextRef) -> Self { 225 | let metadata = unsafe { LLVMMDNodeInContext2(context, core::ptr::null_mut(), 0) }; 226 | unsafe { Self::from_metadata_ref(context, metadata) } 227 | } 228 | 229 | /// Constructs a new metadata node from an array of [`DIType`] elements. 230 | /// 231 | /// This function is used to create composite metadata structures, such as 232 | /// arrays or tuples of different types or values, which can then be used 233 | /// to represent complex data structures within the metadata system. 234 | pub(crate) fn with_elements(context: LLVMContextRef, elements: &[DIType<'_>]) -> Self { 235 | let metadata = unsafe { 236 | let mut elements: Vec = elements 237 | .iter() 238 | .map(|di_type| LLVMValueAsMetadata(di_type.value_ref)) 239 | .collect(); 240 | LLVMMDNodeInContext2( 241 | context, 242 | elements.as_mut_slice().as_mut_ptr(), 243 | elements.len(), 244 | ) 245 | }; 246 | unsafe { Self::from_metadata_ref(context, metadata) } 247 | } 248 | } 249 | 250 | pub(crate) struct MetadataEntries { 251 | entries: *mut LLVMValueMetadataEntry, 252 | count: u32, 253 | } 254 | 255 | impl MetadataEntries { 256 | pub(crate) fn new(v: LLVMValueRef) -> Option { 257 | if unsafe { LLVMIsAGlobalObject(v).is_null() && LLVMIsAInstruction(v).is_null() } { 258 | return None; 259 | } 260 | 261 | let mut count = 0; 262 | let entries = unsafe { LLVMGlobalCopyAllMetadata(v, &mut count) }; 263 | if entries.is_null() { 264 | return None; 265 | } 266 | 267 | Some(Self { 268 | entries, 269 | count: count.try_into().unwrap(), 270 | }) 271 | } 272 | 273 | pub(crate) fn iter(&self) -> impl Iterator + '_ { 274 | let Self { entries, count } = self; 275 | (0..*count).map(|index| unsafe { 276 | ( 277 | LLVMValueMetadataEntriesGetMetadata(*entries, index), 278 | LLVMValueMetadataEntriesGetKind(*entries, index), 279 | ) 280 | }) 281 | } 282 | } 283 | 284 | impl Drop for MetadataEntries { 285 | fn drop(&mut self) { 286 | unsafe { 287 | LLVMDisposeValueMetadataEntries(self.entries); 288 | } 289 | } 290 | } 291 | 292 | /// Represents a metadata node. 293 | #[derive(Clone)] 294 | pub(crate) struct Function<'ctx> { 295 | pub value_ref: LLVMValueRef, 296 | _marker: PhantomData<&'ctx ()>, 297 | } 298 | 299 | impl<'ctx> Function<'ctx> { 300 | /// Constructs a new [`Function`] from the given `value`. 301 | /// 302 | /// # Safety 303 | /// 304 | /// This method assumes that the provided `value` corresponds to a valid 305 | /// instance of [LLVM `Function`](https://llvm.org/doxygen/classllvm_1_1Function.html). 306 | /// It's the caller's responsibility to ensure this invariant, as this 307 | /// method doesn't perform any valiation checks. 308 | pub(crate) unsafe fn from_value_ref(value_ref: LLVMValueRef) -> Self { 309 | Self { 310 | value_ref, 311 | _marker: PhantomData, 312 | } 313 | } 314 | 315 | pub(crate) fn name(&self) -> &[u8] { 316 | symbol_name(self.value_ref) 317 | } 318 | 319 | pub(crate) fn params(&self) -> impl Iterator { 320 | let params_count = unsafe { LLVMCountParams(self.value_ref) }; 321 | let value = self.value_ref; 322 | (0..params_count).map(move |i| unsafe { LLVMGetParam(value, i) }) 323 | } 324 | 325 | pub(crate) fn basic_blocks(&self) -> impl Iterator + '_ { 326 | self.value_ref.basic_blocks_iter() 327 | } 328 | 329 | pub(crate) fn subprogram(&self, context: LLVMContextRef) -> Option> { 330 | let subprogram = unsafe { LLVMGetSubprogram(self.value_ref) }; 331 | (!subprogram.is_null()).then(|| unsafe { 332 | DISubprogram::from_value_ref(LLVMMetadataAsValue(context, subprogram)) 333 | }) 334 | } 335 | 336 | pub(crate) fn set_subprogram(&mut self, subprogram: &DISubprogram<'_>) { 337 | unsafe { LLVMSetSubprogram(self.value_ref, LLVMValueAsMetadata(subprogram.value_ref)) }; 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | 6 | pull_request: 7 | 8 | schedule: 9 | - cron: 00 4 * * * 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | permissions: {} 15 | 16 | jobs: 17 | llvm: 18 | uses: ./.github/workflows/llvm.yml 19 | 20 | lint-stable: 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v6 25 | 26 | - uses: dtolnay/rust-toolchain@master 27 | with: 28 | toolchain: stable 29 | components: clippy, rust-src 30 | 31 | - name: Run clippy 32 | run: cargo clippy --features llvm-21,no-llvm-linking --all-targets --workspace -- --deny warnings 33 | 34 | lint-nightly: 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v6 39 | 40 | - uses: dtolnay/rust-toolchain@master 41 | with: 42 | toolchain: nightly 43 | components: rustfmt, rust-src 44 | 45 | - name: Check formatting 46 | run: cargo fmt --all -- --check 47 | 48 | build: 49 | runs-on: ${{ matrix.platform.os }} 50 | # We work with two git repositories in this workflow: 51 | # 52 | # - bpf-linker 53 | # - aya (for integration tests) 54 | # 55 | # Cargo searches for `.cargo` directories recursively in all parent 56 | # directories. Therefore, nesting different Rust projects with their own 57 | # `.cargo/config.toml` files in the same hierarchy (one nested in another) 58 | # causes cargo to read and merge both configurations. 59 | # 60 | # To avoid that, we clone both repositories in separate directories inside 61 | # the workspace. 62 | defaults: 63 | run: 64 | working-directory: bpf-linker 65 | strategy: 66 | fail-fast: false 67 | matrix: 68 | toolchain: 69 | - rust: 1.90.0 70 | llvm: 20 71 | exclude-features: default,llvm-19,llvm-21,rust-llvm-19,rust-llvm-21 72 | - rust: stable 73 | llvm: 21 74 | exclude-features: default,llvm-19,llvm-20,rust-llvm-19,rust-llvm-20 75 | - rust: beta 76 | llvm: 21 77 | exclude-features: default,llvm-20,rust-llvm-20 78 | - rust: nightly 79 | llvm: 21 80 | exclude-features: llvm-20,rust-llvm-20 81 | platform: 82 | # Rust CI ships only one flavor of libLLVM, dynamic or static, per 83 | # target. Linux GNU targets come with dynamic ones. Apple and Linux 84 | # musl targets come with static ones. 85 | - os: macos-latest 86 | static-target: aarch64-apple-darwin 87 | - os: macos-15-intel 88 | static-target: x86_64-apple-darwin 89 | # We don't use ubuntu-latest because we care about the apt packages available. 90 | - os: ubuntu-22.04 91 | dynamic-target: x86_64-unknown-linux-gnu 92 | static-target: x86_64-unknown-linux-musl 93 | - os: ubuntu-22.04-arm 94 | dynamic-target: aarch64-unknown-linux-gnu 95 | static-target: aarch64-unknown-linux-musl 96 | llvm-from: 97 | - packages 98 | - rust-ci 99 | include: 100 | # Currently we build LLVM from source only for Linux x86_64. 101 | - toolchain: 102 | rust: nightly 103 | llvm: 21 104 | exclude-features: llvm-20,rust-llvm-20 105 | platform: 106 | os: ubuntu-22.04 107 | llvm-from: source 108 | name: os=${{ matrix.platform.os }} rustc=${{ matrix.toolchain.rust }} llvm-version=${{ matrix.toolchain.llvm }} llvm-from=${{ matrix.llvm-from }} 109 | needs: llvm 110 | 111 | env: 112 | RUST_BACKTRACE: full 113 | # Features that have to be included for dynamic linking. 114 | LLVM_FEATURES_DYNAMIC: llvm-${{ matrix.toolchain.llvm }} 115 | # Features that have to be included for static linking. 116 | LLVM_FEATURES_STATIC: llvm-${{ matrix.toolchain.llvm }},llvm-link-static 117 | # Features that have to be excluded when running `cargo hack --feature-powerset` 118 | # and intending to link dynamically. 119 | LLVM_EXCLUDE_FEATURES_DYNAMIC: llvm-link-static,no-llvm-linking 120 | RUSTC_LLVM_INSTALL_DIR_DYNAMIC: /tmp/rustc-llvm-dynamic 121 | RUSTC_LLVM_INSTALL_DIR_STATIC: /tmp/rustc-llvm-static 122 | 123 | steps: 124 | - uses: actions/checkout@v6 125 | with: 126 | path: bpf-linker 127 | 128 | - name: Install Rust ${{ matrix.toolchain.rust }} 129 | uses: dtolnay/rust-toolchain@master 130 | with: 131 | toolchain: ${{ matrix.toolchain.rust }} 132 | components: rust-src 133 | 134 | - name: Check (default features, no system LLVM) 135 | run: cargo check 136 | 137 | - name: Build (default features, no system LLVM) 138 | run: cargo build 139 | 140 | - name: Install btfdump 141 | run: cargo install btfdump 142 | 143 | - name: Add clang to PATH 144 | if: runner.os == 'Linux' 145 | # ubuntu-22.04 comes with clang 13-15[0]; support for signed and 64bit 146 | # enum values was added in clang 15[1] which isn't in `$PATH`. 147 | # 148 | # [0] https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2204-Readme.md 149 | # 150 | # [1] https://github.com/llvm/llvm-project/commit/dc1c43d 151 | run: echo /usr/lib/llvm-15/bin >> $GITHUB_PATH 152 | 153 | - name: Install LLVM (Linux, packages) 154 | if: matrix.llvm-from == 'packages' && runner.os == 'Linux' 155 | run: | 156 | set -euxo pipefail 157 | wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc 158 | echo -e deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-${{ matrix.toolchain.llvm }} main | sudo tee /etc/apt/sources.list.d/llvm.list 159 | 160 | sudo apt update 161 | sudo apt -y install llvm-${{ matrix.toolchain.llvm }}-dev 162 | echo /usr/lib/llvm-${{ matrix.toolchain.llvm }}/bin >> $GITHUB_PATH 163 | 164 | - name: Install LLVM (macOS, packages) 165 | if: matrix.llvm-from == 'packages' && runner.os == 'macOS' 166 | run: | 167 | set -euxo pipefail 168 | brew install llvm@${{ matrix.toolchain.llvm }} 169 | echo $(brew --prefix llvm@${{ matrix.toolchain.llvm }})/bin >> $GITHUB_PATH 170 | # DYLD_LIBRARY_PATH is needed because we're going to link everything dynamically below. This 171 | # doesn't affect behavior, but greatly reduces disk usage. 172 | echo "DYLD_LIBRARY_PATH=$(brew --prefix llvm@${{ matrix.toolchain.llvm }})/lib" >> $GITHUB_ENV 173 | 174 | - name: Install LLVM from Rust CI 175 | if: matrix.llvm-from == 'rust-ci' 176 | run: | 177 | set -euxo pipefail 178 | mkdir -p $RUSTC_LLVM_INSTALL_DIR_DYNAMIC $RUSTC_LLVM_INSTALL_DIR_STATIC 179 | rustc_sha=$(cargo xtask rustc-llvm-commit --github-token "${{ secrets.GITHUB_TOKEN }}") 180 | download_llvm() { 181 | local target=$1 182 | local install_dir=$2 183 | wget -q -O - "https://ci-artifacts.rust-lang.org/rustc-builds/$rustc_sha/rust-dev-nightly-$target.tar.xz" | \ 184 | tar -xJ --strip-components 2 -C $install_dir 185 | } 186 | if [[ -n "${{ matrix.platform['dynamic-target'] }}" ]]; then 187 | download_llvm \ 188 | "${{ matrix.platform['dynamic-target'] }}" \ 189 | ${RUSTC_LLVM_INSTALL_DIR_DYNAMIC} 190 | # LD_LIBRARY_PATH is needed because we're going to link everything dynamically below. This 191 | # doesn't affect behavior, but greatly reduces disk usage. 192 | echo "LD_LIBRARY_PATH=${RUSTC_LLVM_INSTALL_DIR_DYNAMIC}/lib" >> $GITHUB_ENV 193 | # We start with steps that use dynamic linking. Add llvm-config 194 | # associated with dynamic target to `PATH`. 195 | echo "${RUSTC_LLVM_INSTALL_DIR_DYNAMIC}/bin" >> $GITHUB_PATH 196 | fi 197 | if [[ -n "${{ matrix.platform['static-target'] }}" ]]; then 198 | download_llvm \ 199 | "${{ matrix.platform['static-target'] }}" \ 200 | ${RUSTC_LLVM_INSTALL_DIR_STATIC} 201 | if [[ "${{ runner.os }}" == "Linux" ]]; then 202 | # `FileCheck` binary shipped in musl tarballs is linked dynamically 203 | # to musl, we can't execute it on Ubuntu. 204 | rm -f "${RUSTC_LLVM_INSTALL_DIR_STATIC}/bin/FileCheck" 205 | fi 206 | fi 207 | 208 | - name: Restore LLVM from GitHub Actions 209 | if: matrix.llvm-from == 'source' 210 | uses: actions/cache/restore@v4 211 | with: 212 | path: llvm-install 213 | key: ${{ needs.llvm.outputs.cache-key }} 214 | fail-on-cache-miss: true 215 | 216 | - name: Add LLVM to PATH && LD_LIBRARY_PATH 217 | if: matrix.llvm-from == 'source' 218 | run: | 219 | set -euxo pipefail 220 | echo "${{ github.workspace }}/llvm-install/bin" >> $GITHUB_PATH 221 | # LD_LIBRARY_PATH is needed because we're going to link everything dynamically below. This 222 | # doesn't affect behavior, but greatly reduces disk usage. 223 | echo "LD_LIBRARY_PATH=${{ github.workspace }}/llvm-install/lib" >> $GITHUB_ENV 224 | 225 | # llvm-sys discovers link flags at build script time; these are cached by cargo. The cached 226 | # flags may be incorrect when the cache is reused across LLVM versions. 227 | - name: Bust llvm-sys cache 228 | run: | 229 | set -euxo pipefail 230 | cargo clean -p llvm-sys 231 | cargo clean -p llvm-sys --release 232 | 233 | - uses: taiki-e/install-action@cargo-hack 234 | 235 | - name: Check (dynamic linking, feature powerset) 236 | if: matrix.platform.dynamic-target || matrix.llvm-from != 'rust-ci' 237 | run: | 238 | cargo hack check --feature-powerset --exclude-features \ 239 | ${{ env.LLVM_EXCLUDE_FEATURES_DYNAMIC }},${{ matrix.toolchain.exclude-features }} \ 240 | --features ${{ env.LLVM_FEATURES_DYNAMIC }} 241 | 242 | - name: Build (dynamic linking, feature powerset) 243 | if: matrix.platform.dynamic-target || matrix.llvm-from != 'rust-ci' 244 | run: | 245 | cargo hack build --feature-powerset --exclude-features \ 246 | ${{ env.LLVM_EXCLUDE_FEATURES_DYNAMIC }},${{ matrix.toolchain.exclude-features }} \ 247 | --features ${{ env.LLVM_FEATURES_DYNAMIC }} 248 | 249 | # Toolchains provided by rustup include standard library artifacts 250 | # only for Tier 1 targets, which do not include BPF targets. 251 | # 252 | # The default workaround is to use the `rustc-build-sysroot` crate to 253 | # build a custom sysroot with the required BPF standard library before 254 | # running compiler tests. 255 | # 256 | # `RUSTC_BOOTSTRAP` is needed to use `rustc-build-sysroot` on stable Rust. 257 | - name: Test (sysroot built on demand, dynamic linking) 258 | if: matrix.platform.dynamic-target || matrix.llvm-from != 'rust-ci' 259 | run: | 260 | RUSTC_BOOTSTRAP=1 cargo hack test --feature-powerset \ 261 | --exclude-features \ 262 | ${{ env.LLVM_EXCLUDE_FEATURES_DYNAMIC }},${{ matrix.toolchain.exclude-features }} \ 263 | --features ${{ env.LLVM_FEATURES_DYNAMIC }} 264 | 265 | # To make things easier for package maintainers, the step of building a 266 | # custom sysroot can be skipped by setting the `BPFEL_SYSROOT_DIR` 267 | # environment variable to the path of the prebuilt BPF sysroot. 268 | # 269 | # Test this configuration by prebuilding the BPF standard library 270 | # manually. 271 | # 272 | # `RUSTC_BOOTSTRAP` is needed to make `xtask build-std` work on stable 273 | # Rust. 274 | - name: Build BPF standard library 275 | run: | 276 | set -euxo pipefail 277 | 278 | RUSTC_SRC="$(rustc --print sysroot)/lib/rustlib/src/rust/library" 279 | BPFEL_SYSROOT_DIR="${{ github.workspace }}/bpf-sysroot" 280 | RUSTC_BOOTSTRAP=1 cargo xtask build-std \ 281 | --rustc-src "$RUSTC_SRC" \ 282 | --sysroot-dir "$BPFEL_SYSROOT_DIR" \ 283 | --target bpfel-unknown-none 284 | 285 | - name: Test (prebuilt BPF standard libary, dynamic linking) 286 | if: matrix.platform.dynamic-target || matrix.llvm-from != 'rust-ci' 287 | run: | 288 | BPFEL_SYSROOT_DIR="${{ github.workspace }}/bpf-sysroot" \ 289 | cargo hack test --feature-powerset --exclude-features \ 290 | ${{ env.LLVM_EXCLUDE_FEATURES_DYNAMIC }},${{ matrix.toolchain.exclude-features }} \ 291 | --features ${{ env.LLVM_FEATURES_DYNAMIC }} 292 | 293 | - uses: actions/checkout@v6 294 | if: runner.os == 'Linux' && matrix.toolchain.rust == 'nightly' 295 | with: 296 | repository: aya-rs/aya 297 | path: aya 298 | submodules: recursive 299 | 300 | - name: Install 301 | if: runner.os == 'Linux' && matrix.toolchain.rust == 'nightly' 302 | run: | 303 | cargo install --path . --no-default-features --features \ 304 | ${{ env.LLVM_FEATURES_DYNAMIC }} 305 | 306 | - name: Run aya integration tests 307 | if: runner.os == 'Linux' && matrix.toolchain.rust == 'nightly' 308 | working-directory: aya 309 | run: cargo xtask integration-test local 310 | 311 | - name: Prepare for static linking (LLVM from Rust CI) 312 | if: matrix.llvm-from == 'rust-ci' 313 | run: | 314 | echo "${RUSTC_LLVM_INSTALL_DIR_STATIC}/bin" >> $GITHUB_PATH 315 | 316 | - name: Install static libraries (macOS) 317 | if: runner.os == 'macOS' 318 | # macOS does not provide any static libraries. Homebrew does provide 319 | # them, but in custom paths that the system-wide clang is not aware of. 320 | # Point build.rs to them by setting environment variables. 321 | # 322 | # We install llvm package only for libc++. 323 | # 324 | # libLLVM from homebrew requires zstd. 325 | run: | 326 | set -euxo pipefail 327 | brew install llvm zlib 328 | echo "CXXSTDLIB_PATH=$(brew --prefix llvm)/lib/c++" >> $GITHUB_ENV 329 | echo "ZLIB_PATH=$(brew --prefix zlib)/lib" >> $GITHUB_ENV 330 | if [[ "${{ matrix.llvm-from }}" == "packages" ]]; then 331 | brew install zstd 332 | echo "LIBZSTD_PATH=$(brew --prefix zstd)/lib" >> $GITHUB_ENV 333 | fi 334 | 335 | - name: Check (static linking, single feature set) 336 | # Static linking in combination with `cargo hack --feature-powerset` 337 | # (multiple builds) increases the disk usage massively. Therefore we 338 | # perform all static builds with only one fixed feature set. 339 | run: | 340 | cargo check --no-default-features --features \ 341 | ${{ env.LLVM_FEATURES_STATIC }} 342 | 343 | - name: Build (static linking, single feature set) 344 | run: | 345 | cargo build --no-default-features --features \ 346 | ${{ env.LLVM_FEATURES_STATIC }} 347 | 348 | - name: Test (sysroot built on demand, static linking) 349 | run: | 350 | RUSTC_BOOTSTRAP=1 cargo test --no-default-features --features \ 351 | ${{ env.LLVM_FEATURES_STATIC }} 352 | 353 | - name: Test (prebuilt BPF standard library, static linking) 354 | run: | 355 | BPFEL_SYSROOT_DIR="${{ github.workspace }}/bpf-sysroot" \ 356 | cargo test --no-default-features --features \ 357 | ${{ env.LLVM_FEATURES_STATIC }} 358 | 359 | - name: Report disk usage 360 | if: ${{ always() }} 361 | uses: ./bpf-linker/.github/actions/report-disk-usage 362 | -------------------------------------------------------------------------------- /src/llvm/types/di.rs: -------------------------------------------------------------------------------- 1 | use std::{marker::PhantomData, slice}; 2 | 3 | use gimli::DwTag; 4 | use llvm_sys::{ 5 | core::{LLVMGetNumOperands, LLVMGetOperand, LLVMReplaceMDNodeOperandWith, LLVMValueAsMetadata}, 6 | debuginfo::{ 7 | LLVMDIFileGetFilename, LLVMDIFlags, LLVMDIScopeGetFile, LLVMDISubprogramGetLine, 8 | LLVMDITypeGetFlags, LLVMDITypeGetLine, LLVMDITypeGetName, LLVMDITypeGetOffsetInBits, 9 | LLVMGetDINodeTag, 10 | }, 11 | prelude::{LLVMContextRef, LLVMMetadataRef, LLVMValueRef}, 12 | }; 13 | 14 | use crate::llvm::{ 15 | LLVMGetMDString, 16 | types::ir::{MDNode, Metadata}, 17 | }; 18 | 19 | fn mdstring<'a>(mdstring: LLVMValueRef) -> &'a [u8] { 20 | let mut len = 0; 21 | let ptr = unsafe { LLVMGetMDString(mdstring, &mut len) }; 22 | unsafe { slice::from_raw_parts(ptr.cast(), len as usize) } 23 | } 24 | 25 | /// Returns a DWARF tag for the given debug info node. 26 | /// 27 | /// This function should be called in `tag` method of all LLVM debug info types 28 | /// inheriting from [`DINode`](https://llvm.org/doxygen/classllvm_1_1DINode.html). 29 | /// 30 | /// # Safety 31 | /// 32 | /// This function assumes that the given `metadata_ref` corresponds to a valid 33 | /// instance of [LLVM `DINode`](https://llvm.org/doxygen/classllvm_1_1DINode.html). 34 | /// It's the caller's responsibility to ensure this invariant, as this function 35 | /// doesn't perform any validation checks. 36 | unsafe fn di_node_tag(metadata_ref: LLVMMetadataRef) -> DwTag { 37 | DwTag(unsafe { LLVMGetDINodeTag(metadata_ref) }) 38 | } 39 | 40 | /// Represents a source code file in debug infomation. 41 | /// 42 | /// A `DIFile` debug info node, which represents a given file, is referenced by 43 | /// other debug info nodes which belong to the file. 44 | pub(crate) struct DIFile<'ctx> { 45 | pub(super) metadata_ref: LLVMMetadataRef, 46 | _marker: PhantomData<&'ctx ()>, 47 | } 48 | 49 | impl DIFile<'_> { 50 | /// Constructs a new [`DIFile`] from the given `metadata`. 51 | /// 52 | /// # Safety 53 | /// 54 | /// This method assumes that the given `metadata` corresponds to a valid 55 | /// instance of [LLVM `DIFile`](https://llvm.org/doxygen/classllvm_1_1DIFile.html). 56 | /// It's the caller's responsibility to ensure this invariant, as this 57 | /// method doesn't perform any validation checks. 58 | pub(crate) unsafe fn from_metadata_ref(metadata_ref: LLVMMetadataRef) -> Self { 59 | Self { 60 | metadata_ref, 61 | _marker: PhantomData, 62 | } 63 | } 64 | 65 | pub(crate) fn filename(&self) -> Option<&[u8]> { 66 | let mut len = 0; 67 | // `LLVMDIFileGetName` doesn't allocate any memory, it just returns 68 | // a pointer to the string which is already a part of `DIFile`: 69 | // https://github.com/llvm/llvm-project/blob/eee1f7cef856241ad7d66b715c584d29b1c89ca9/llvm/lib/IR/DebugInfo.cpp#L1175-L1179 70 | // 71 | // Therefore, we don't need to call `LLVMDisposeMessage`. The memory 72 | // gets freed when calling `LLVMDisposeDIBuilder`. 73 | let ptr = unsafe { LLVMDIFileGetFilename(self.metadata_ref, &mut len) }; 74 | (!ptr.is_null()).then(|| unsafe { slice::from_raw_parts(ptr.cast(), len as usize) }) 75 | } 76 | } 77 | 78 | /// Represents the operands for a [`DIType`]. The enum values correspond to the 79 | /// operand indices within metadata nodes. 80 | #[repr(u32)] 81 | enum DITypeOperand { 82 | /// Name of the type. 83 | /// [Reference in LLVM code](https://github.com/llvm/llvm-project/blob/llvmorg-17.0.3/llvm/include/llvm/IR/DebugInfoMetadata.h#L743). 84 | Name = 2, 85 | } 86 | 87 | /// Returns the name of the type. 88 | /// 89 | /// This function should be called in `name` method of `DIType` and all other 90 | /// LLVM debug info types inheriting from it. 91 | /// 92 | /// # Safety 93 | /// 94 | /// This function assumes that the given `metadata_ref` corresponds to a valid 95 | /// instance of [LLVM `DIType`](https://llvm.org/doxygen/classllvm_1_1DIType.html). 96 | /// It's the caller's responsibility to ensure this invariant, as this function 97 | /// doesn't perform any validation checks. 98 | unsafe fn di_type_name<'a>(metadata_ref: LLVMMetadataRef) -> Option<&'a [u8]> { 99 | let mut len = 0; 100 | // `LLVMDITypeGetName` doesn't allocate any memory, it just returns 101 | // a pointer to the string which is already a part of `DIType`: 102 | // https://github.com/llvm/llvm-project/blob/eee1f7cef856241ad7d66b715c584d29b1c89ca9/llvm/lib/IR/DebugInfo.cpp#L1489-L1493 103 | // 104 | // Therefore, we don't need to call `LLVMDisposeMessage`. The memory 105 | // gets freed when calling `LLVMDisposeDIBuilder`. Example: 106 | // https://github.com/llvm/llvm-project/blob/eee1f7cef856241ad7d66b715c584d29b1c89ca9/llvm/tools/llvm-c-test/debuginfo.c#L249-L255 107 | let ptr = unsafe { LLVMDITypeGetName(metadata_ref, &mut len) }; 108 | (!ptr.is_null()).then(|| unsafe { slice::from_raw_parts(ptr.cast(), len) }) 109 | } 110 | 111 | /// Represents the debug information for a primitive type in LLVM IR. 112 | pub(crate) struct DIType<'ctx> { 113 | pub(super) metadata_ref: LLVMMetadataRef, 114 | pub(super) value_ref: LLVMValueRef, 115 | _marker: PhantomData<&'ctx ()>, 116 | } 117 | 118 | impl DIType<'_> { 119 | /// Constructs a new [`DIType`] from the given `value`. 120 | /// 121 | /// # Safety 122 | /// 123 | /// This method assumes that the given `value` corresponds to a valid 124 | /// instance of [LLVM `DIType`](https://llvm.org/doxygen/classllvm_1_1DIType.html). 125 | /// It's the caller's responsibility to ensure this invariant, as this 126 | /// method doesn't perform any validation checks. 127 | pub(crate) unsafe fn from_value_ref(value_ref: LLVMValueRef) -> Self { 128 | let metadata_ref = unsafe { LLVMValueAsMetadata(value_ref) }; 129 | Self { 130 | metadata_ref, 131 | value_ref, 132 | _marker: PhantomData, 133 | } 134 | } 135 | 136 | /// Returns the offset of the type in bits. This offset is used in case the 137 | /// type is a member of a composite type. 138 | pub(crate) fn offset_in_bits(&self) -> u64 { 139 | unsafe { LLVMDITypeGetOffsetInBits(self.metadata_ref) } 140 | } 141 | } 142 | 143 | impl<'ctx> From> for DIType<'ctx> { 144 | fn from(di_derived_type: DIDerivedType<'_>) -> Self { 145 | unsafe { Self::from_value_ref(di_derived_type.value_ref) } 146 | } 147 | } 148 | 149 | /// Represents the operands for a [`DIDerivedType`]. The enum values correspond 150 | /// to the operand indices within metadata nodes. 151 | #[repr(u32)] 152 | enum DIDerivedTypeOperand { 153 | /// [`DIType`] representing a base type of the given derived type. 154 | /// Reference in [LLVM 20][llvm-20] and [LLVM 21][llvm-21]. 155 | /// 156 | /// [llvm-20]: https://github.com/llvm/llvm-project/blob/llvmorg-20.1.8/llvm/include/llvm/IR/DebugInfoMetadata.h#L1106 157 | /// [llvm-21]: https://github.com/llvm/llvm-project/blob/llvmorg-21.1.0-rc3/llvm/include/llvm/IR/DebugInfoMetadata.h#L1386 158 | /// 159 | #[cfg(feature = "llvm-20")] 160 | BaseType = 3, 161 | #[cfg(feature = "llvm-21")] 162 | BaseType = 5, 163 | } 164 | 165 | /// Represents the debug information for a derived type in LLVM IR. 166 | /// 167 | /// The types derived from other types usually add a level of indirection or an 168 | /// alternative name. The examples of derived types are pointers, references, 169 | /// typedefs, etc. 170 | pub(crate) struct DIDerivedType<'ctx> { 171 | metadata_ref: LLVMMetadataRef, 172 | value_ref: LLVMValueRef, 173 | _marker: PhantomData<&'ctx ()>, 174 | } 175 | 176 | impl DIDerivedType<'_> { 177 | /// Constructs a new [`DIDerivedType`] from the given `value`. 178 | /// 179 | /// # Safety 180 | /// 181 | /// This method assumes that the provided `value` corresponds to a valid 182 | /// instance of [LLVM `DIDerivedType`](https://llvm.org/doxygen/classllvm_1_1DIDerivedType.html). 183 | /// It's the caller's responsibility to ensure this invariant, as this 184 | /// method doesn't perform any validation checks. 185 | pub(crate) unsafe fn from_value_ref(value_ref: LLVMValueRef) -> Self { 186 | let metadata_ref = unsafe { LLVMValueAsMetadata(value_ref) }; 187 | Self { 188 | metadata_ref, 189 | value_ref, 190 | _marker: PhantomData, 191 | } 192 | } 193 | 194 | /// Returns the base type of this derived type. 195 | pub(crate) fn base_type(&self) -> Metadata<'_> { 196 | unsafe { 197 | let value = LLVMGetOperand(self.value_ref, DIDerivedTypeOperand::BaseType as u32); 198 | Metadata::from_value_ref(value) 199 | } 200 | } 201 | 202 | /// Replaces the name of the type with a new name. 203 | /// 204 | /// # Errors 205 | /// 206 | /// Returns a `NulError` if the new name contains a NUL byte, as it cannot 207 | /// be converted into a `CString`. 208 | pub(crate) fn replace_name(&mut self, context: LLVMContextRef, name: &[u8]) { 209 | super::ir::replace_name(self.value_ref, context, DITypeOperand::Name as u32, name) 210 | } 211 | 212 | /// Returns a DWARF tag of the given derived type. 213 | pub(crate) fn tag(&self) -> DwTag { 214 | unsafe { di_node_tag(self.metadata_ref) } 215 | } 216 | } 217 | 218 | /// Represents the operands for a [`DICompositeType`]. The enum values 219 | /// correspond to the operand indices within metadata nodes. 220 | #[repr(u32)] 221 | enum DICompositeTypeOperand { 222 | /// Elements of the composite type. Reference in [LLVM 20][llvm-20] and 223 | /// [LLVM 21][llvm-21]. 224 | /// 225 | /// [llvm-20]: https://github.com/llvm/llvm-project/blob/llvmorg-20.1.8/llvm/include/llvm/IR/DebugInfoMetadata.h#L1332 226 | /// [llvm-21]: https://github.com/llvm/llvm-project/blob/llvmorg-21.1.0-rc3/llvm/include/llvm/IR/DebugInfoMetadata.h#L1813 227 | #[cfg(feature = "llvm-20")] 228 | Elements = 4, 229 | #[cfg(feature = "llvm-21")] 230 | Elements = 6, 231 | } 232 | 233 | /// Represents the debug info for a composite type in LLVM IR. 234 | /// 235 | /// Composite type is a kind of type that can include other types, such as 236 | /// structures, enums, unions, etc. 237 | pub(crate) struct DICompositeType<'ctx> { 238 | metadata_ref: LLVMMetadataRef, 239 | value_ref: LLVMValueRef, 240 | _marker: PhantomData<&'ctx ()>, 241 | } 242 | 243 | impl DICompositeType<'_> { 244 | /// Constructs a new [`DICompositeType`] from the given `value`. 245 | /// 246 | /// # Safety 247 | /// 248 | /// This method assumes that the provided `value` corresponds to a valid 249 | /// instance of [LLVM `DICompositeType`](https://llvm.org/doxygen/classllvm_1_1DICompositeType.html). 250 | /// It's the caller's responsibility to ensure this invariant, as this 251 | /// method doesn't perform any validation checks. 252 | pub(crate) unsafe fn from_value_ref(value_ref: LLVMValueRef) -> Self { 253 | let metadata_ref = unsafe { LLVMValueAsMetadata(value_ref) }; 254 | Self { 255 | metadata_ref, 256 | value_ref, 257 | _marker: PhantomData, 258 | } 259 | } 260 | 261 | /// Returns an iterator over elements (struct fields, enum variants, etc.) 262 | /// of the composite type. 263 | pub(crate) fn elements(&self) -> impl Iterator> { 264 | let elements = 265 | unsafe { LLVMGetOperand(self.value_ref, DICompositeTypeOperand::Elements as u32) }; 266 | let operands = if elements.is_null() { 267 | 0 268 | } else { 269 | unsafe { LLVMGetNumOperands(elements) } 270 | }; 271 | 272 | (0..operands).map(move |i| unsafe { 273 | Metadata::from_value_ref(LLVMGetOperand(elements, i.cast_unsigned())) 274 | }) 275 | } 276 | 277 | /// Returns the name of the composite type. 278 | pub(crate) fn name(&self) -> Option<&[u8]> { 279 | unsafe { di_type_name(self.metadata_ref) } 280 | } 281 | 282 | /// Returns the file that the composite type belongs to. 283 | pub(crate) fn file(&self) -> DIFile<'_> { 284 | unsafe { 285 | let metadata = LLVMDIScopeGetFile(self.metadata_ref); 286 | DIFile::from_metadata_ref(metadata) 287 | } 288 | } 289 | 290 | /// Returns the flags associated with the composity type. 291 | pub(crate) fn flags(&self) -> LLVMDIFlags { 292 | unsafe { LLVMDITypeGetFlags(self.metadata_ref) } 293 | } 294 | 295 | /// Returns the line number in the source code where the type is defined. 296 | pub(crate) fn line(&self) -> u32 { 297 | unsafe { LLVMDITypeGetLine(self.metadata_ref) } 298 | } 299 | 300 | /// Replaces the elements of the composite type with a new metadata node. 301 | /// The provided metadata node should contain new composite type elements 302 | /// as operants. The metadata node can be empty if the intention is to 303 | /// remove all elements of the composite type. 304 | pub(crate) fn replace_elements(&mut self, mdnode: MDNode<'_>) { 305 | unsafe { 306 | LLVMReplaceMDNodeOperandWith( 307 | self.value_ref, 308 | DICompositeTypeOperand::Elements as u32, 309 | LLVMValueAsMetadata(mdnode.value_ref), 310 | ) 311 | } 312 | } 313 | 314 | /// Replaces the name of the type with a new name. 315 | /// 316 | /// # Errors 317 | /// 318 | /// Returns a `NulError` if the new name contains a NUL byte, as it cannot 319 | /// be converted into a `CString`. 320 | pub(crate) fn replace_name(&mut self, context: LLVMContextRef, name: &[u8]) { 321 | super::ir::replace_name(self.value_ref, context, DITypeOperand::Name as u32, name) 322 | } 323 | 324 | /// Returns a DWARF tag of the given composite type. 325 | pub(crate) fn tag(&self) -> DwTag { 326 | unsafe { di_node_tag(self.metadata_ref) } 327 | } 328 | } 329 | 330 | /// Represents the operands for a [`DISubprogram`]. The enum values correspond 331 | /// to the operand indices within metadata nodes. 332 | #[repr(u32)] 333 | enum DISubprogramOperand { 334 | Scope = 1, 335 | Name = 2, 336 | LinkageName = 3, 337 | Ty = 4, 338 | Unit = 5, 339 | RetainedNodes = 7, 340 | } 341 | 342 | /// Represents the debug information for a subprogram (function) in LLVM IR. 343 | pub(crate) struct DISubprogram<'ctx> { 344 | pub value_ref: LLVMValueRef, 345 | _marker: PhantomData<&'ctx ()>, 346 | } 347 | 348 | impl DISubprogram<'_> { 349 | /// Constructs a new [`DISubprogram`] from the given `value`. 350 | /// 351 | /// # Safety 352 | /// 353 | /// This method assumes that the provided `value` corresponds to a valid 354 | /// instance of [LLVM `DISubprogram`](https://llvm.org/doxygen/classllvm_1_1DISubprogram.html). 355 | /// It's the caller's responsibility to ensure this invariant, as this 356 | /// method doesn't perform any validation checks. 357 | pub(crate) unsafe fn from_value_ref(value_ref: LLVMValueRef) -> Self { 358 | DISubprogram { 359 | value_ref, 360 | _marker: PhantomData, 361 | } 362 | } 363 | 364 | /// Returns the name of the subprogram. 365 | pub(crate) fn name(&self) -> Option<&[u8]> { 366 | let operand = unsafe { LLVMGetOperand(self.value_ref, DISubprogramOperand::Name as u32) }; 367 | (!operand.is_null()).then(|| mdstring(operand)) 368 | } 369 | 370 | /// Returns the linkage name of the subprogram. 371 | pub(crate) fn linkage_name(&self) -> Option<&[u8]> { 372 | let operand = 373 | unsafe { LLVMGetOperand(self.value_ref, DISubprogramOperand::LinkageName as u32) }; 374 | (!operand.is_null()).then(|| mdstring(operand)) 375 | } 376 | 377 | pub(crate) fn ty(&self) -> LLVMMetadataRef { 378 | unsafe { 379 | LLVMValueAsMetadata(LLVMGetOperand( 380 | self.value_ref, 381 | DISubprogramOperand::Ty as u32, 382 | )) 383 | } 384 | } 385 | 386 | pub(crate) fn file(&self) -> LLVMMetadataRef { 387 | unsafe { LLVMDIScopeGetFile(LLVMValueAsMetadata(self.value_ref)) } 388 | } 389 | 390 | pub(crate) fn line(&self) -> u32 { 391 | unsafe { LLVMDISubprogramGetLine(LLVMValueAsMetadata(self.value_ref)) } 392 | } 393 | 394 | pub(crate) fn type_flags(&self) -> i32 { 395 | unsafe { LLVMDITypeGetFlags(LLVMValueAsMetadata(self.value_ref)) } 396 | } 397 | 398 | /// Replaces the name of the subprogram with a new name. 399 | /// 400 | /// # Errors 401 | /// 402 | /// Returns a `NulError` if the new name contains a NUL byte, as it cannot 403 | /// be converted into a `CString`. 404 | pub(crate) fn replace_name(&mut self, context: LLVMContextRef, name: &[u8]) { 405 | super::ir::replace_name( 406 | self.value_ref, 407 | context, 408 | DISubprogramOperand::Name as u32, 409 | name, 410 | ) 411 | } 412 | 413 | pub(crate) fn scope(&self) -> Option { 414 | unsafe { 415 | let operand = LLVMGetOperand(self.value_ref, DISubprogramOperand::Scope as u32); 416 | (!operand.is_null()).then(|| LLVMValueAsMetadata(operand)) 417 | } 418 | } 419 | 420 | pub(crate) fn unit(&self) -> Option { 421 | unsafe { 422 | let operand = LLVMGetOperand(self.value_ref, DISubprogramOperand::Unit as u32); 423 | (!operand.is_null()).then(|| LLVMValueAsMetadata(operand)) 424 | } 425 | } 426 | 427 | pub(crate) fn set_unit(&mut self, unit: LLVMMetadataRef) { 428 | unsafe { 429 | LLVMReplaceMDNodeOperandWith(self.value_ref, DISubprogramOperand::Unit as u32, unit) 430 | }; 431 | } 432 | 433 | pub(crate) fn retained_nodes(&self) -> Option { 434 | unsafe { 435 | let nodes = LLVMGetOperand(self.value_ref, DISubprogramOperand::RetainedNodes as u32); 436 | (!nodes.is_null()).then(|| LLVMValueAsMetadata(nodes)) 437 | } 438 | } 439 | 440 | pub(crate) fn set_retained_nodes(&mut self, nodes: LLVMMetadataRef) { 441 | unsafe { 442 | LLVMReplaceMDNodeOperandWith( 443 | self.value_ref, 444 | DISubprogramOperand::RetainedNodes as u32, 445 | nodes, 446 | ) 447 | }; 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /src/llvm/di.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | collections::{HashMap, HashSet, hash_map::DefaultHasher}, 4 | hash::Hasher as _, 5 | io::Write as _, 6 | marker::PhantomData, 7 | ptr, 8 | }; 9 | 10 | use gimli::{DW_TAG_pointer_type, DW_TAG_structure_type, DW_TAG_variant_part}; 11 | use llvm_sys::{core::*, debuginfo::*, prelude::*}; 12 | use tracing::{Level, span, trace, warn}; 13 | 14 | use super::types::{ 15 | di::DIType, 16 | ir::{Function, MDNode, Metadata, Value}, 17 | }; 18 | use crate::llvm::{LLVMContext, LLVMModule, iter::*, types::di::DISubprogram}; 19 | 20 | // KSYM_NAME_LEN from linux kernel intentionally set 21 | // to lower value found across kernel versions to ensure 22 | // backward compatibility 23 | const MAX_KSYM_NAME_LEN: usize = 128; 24 | 25 | pub(crate) struct DISanitizer<'ctx> { 26 | context: LLVMContextRef, 27 | module: LLVMModuleRef, 28 | builder: LLVMDIBuilderRef, 29 | visited_nodes: HashSet, 30 | replace_operands: HashMap, 31 | skipped_types_lossy: Vec, 32 | // TODO: use references of safe wrappers instead of PhantomData 33 | _marker: PhantomData>, 34 | } 35 | 36 | // Sanitize Rust type names to be valid C type names. 37 | fn sanitize_type_name(name: &[u8]) -> Vec { 38 | let mut sanitized = Vec::with_capacity(name.len()); 39 | for &byte in name { 40 | // Characters which are valid in C type names (alphanumeric and `_`). 41 | if matches!(byte, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'_') { 42 | sanitized.push(byte); 43 | } else { 44 | write!(&mut sanitized, "_{:X}_", byte).unwrap(); 45 | } 46 | } 47 | 48 | if sanitized.len() > MAX_KSYM_NAME_LEN { 49 | let mut hasher = DefaultHasher::new(); 50 | hasher.write(&sanitized); 51 | let hash = hasher.finish(); 52 | // leave space for underscore 53 | let trim = MAX_KSYM_NAME_LEN - 2 * size_of_val(&hash) - 1; 54 | sanitized.truncate(trim); 55 | write!(&mut sanitized, "_{:x}", hash).unwrap(); 56 | } 57 | 58 | sanitized 59 | } 60 | 61 | impl<'ctx> DISanitizer<'ctx> { 62 | pub(crate) fn new(context: &'ctx LLVMContext, module: &mut LLVMModule<'ctx>) -> Self { 63 | DISanitizer { 64 | context: context.as_mut_ptr(), 65 | module: module.as_mut_ptr(), 66 | builder: unsafe { LLVMCreateDIBuilder(module.as_mut_ptr()) }, 67 | visited_nodes: HashSet::new(), 68 | replace_operands: HashMap::new(), 69 | skipped_types_lossy: Vec::new(), 70 | _marker: PhantomData, 71 | } 72 | } 73 | 74 | fn visit_mdnode(&mut self, mdnode: MDNode<'_>) { 75 | match mdnode.try_into().expect("MDNode is not Metadata") { 76 | Metadata::DICompositeType(mut di_composite_type) => { 77 | #[expect(clippy::single_match)] 78 | #[expect(non_upper_case_globals)] 79 | match di_composite_type.tag() { 80 | DW_TAG_structure_type => { 81 | let names = di_composite_type 82 | .name() 83 | .map(|name| (name.to_owned(), sanitize_type_name(name))); 84 | 85 | // This is a forward declaration. We don't need to do 86 | // anything on the declaration, we're going to process 87 | // the actual definition. 88 | if di_composite_type.flags() == LLVMDIFlagFwdDecl { 89 | return; 90 | } 91 | 92 | let mut is_data_carrying_enum = false; 93 | let mut remove_name = false; 94 | let mut members: Vec> = Vec::new(); 95 | for element in di_composite_type.elements() { 96 | match element { 97 | Metadata::DICompositeType(di_composite_type_inner) => { 98 | // The presence of a composite type with `DW_TAG_variant_part` 99 | // as a member of another composite type means that we are 100 | // processing a data-carrying enum. Such types are not supported 101 | // by the Linux kernel. We need to remove the children, so BTF 102 | // doesn't contain data carried by the enum variant. 103 | match di_composite_type_inner.tag() { 104 | DW_TAG_variant_part => { 105 | if let Some((ref name, _)) = names { 106 | let file = di_composite_type.file(); 107 | let name = String::from_utf8_lossy(name.as_slice()) 108 | .to_string(); 109 | trace!( 110 | "found data carrying enum {name} ({filename}:{line}), not emitting the debug info for it", 111 | filename = file.filename().map_or( 112 | "".into(), 113 | String::from_utf8_lossy 114 | ), 115 | line = di_composite_type.line(), 116 | ); 117 | self.skipped_types_lossy.push(name); 118 | } 119 | 120 | is_data_carrying_enum = true; 121 | break; 122 | } 123 | _ => {} 124 | } 125 | } 126 | Metadata::DIDerivedType(di_derived_type) => { 127 | let base_type = di_derived_type.base_type(); 128 | 129 | match base_type { 130 | Metadata::DICompositeType(base_type_di_composite_type) => { 131 | if let Some(base_type_name) = 132 | base_type_di_composite_type.name() 133 | { 134 | // `AyaBtfMapMarker` is a type which is used in fields of BTF map 135 | // structs. We need to make such structs anonymous in order to get 136 | // BTF maps accepted by the Linux kernel. 137 | if base_type_name == b"AyaBtfMapMarker" { 138 | // Remove the name from the struct. 139 | remove_name = true; 140 | // And don't include the field in the sanitized DI. 141 | } else { 142 | members.push(di_derived_type.into()); 143 | } 144 | } else { 145 | members.push(di_derived_type.into()); 146 | } 147 | } 148 | _ => { 149 | members.push(di_derived_type.into()); 150 | } 151 | } 152 | } 153 | _ => {} 154 | } 155 | } 156 | if is_data_carrying_enum { 157 | di_composite_type.replace_elements(MDNode::empty(self.context)); 158 | } else if !members.is_empty() { 159 | members.sort_by_cached_key(|di_type| di_type.offset_in_bits()); 160 | let sorted_elements = 161 | MDNode::with_elements(self.context, members.as_mut_slice()); 162 | di_composite_type.replace_elements(sorted_elements); 163 | } 164 | if remove_name { 165 | // `AyaBtfMapMarker` is a type which is used in fields of BTF map 166 | // structs. We need to make such structs anonymous in order to get 167 | // BTF maps accepted by the Linux kernel. 168 | di_composite_type.replace_name(self.context, &[]) 169 | } else if let Some((_, sanitized_name)) = names { 170 | // Clear the name from characters incompatible with C. 171 | di_composite_type.replace_name(self.context, sanitized_name.as_slice()) 172 | } 173 | } 174 | _ => (), 175 | } 176 | } 177 | Metadata::DIDerivedType(mut di_derived_type) => { 178 | #[expect(clippy::single_match)] 179 | #[expect(non_upper_case_globals)] 180 | match di_derived_type.tag() { 181 | DW_TAG_pointer_type => { 182 | // remove rust names 183 | di_derived_type.replace_name(self.context, &[]) 184 | } 185 | _ => (), 186 | } 187 | } 188 | Metadata::DISubprogram(mut di_subprogram) => { 189 | // Sanitize function names 190 | if let Some(name) = di_subprogram.name() { 191 | let name = sanitize_type_name(name); 192 | di_subprogram.replace_name(self.context, name.as_slice()) 193 | } 194 | } 195 | _ => (), 196 | } 197 | } 198 | 199 | // navigate the tree of LLVMValueRefs (DFS-pre-order) 200 | fn visit_item(&mut self, mut item: Item) { 201 | let value_ref = item.value_ref(); 202 | let value_id = item.value_id(); 203 | 204 | let item_span = span!(Level::TRACE, "item", value_id); 205 | let _enter = item_span.enter(); 206 | trace!(?item, value = ?value_ref, "visiting item"); 207 | 208 | let value = match (value_ref, &item) { 209 | // An operand with no value is valid and means that the operand is 210 | // not set 211 | (v, Item::Operand { .. }) if v.is_null() => return, 212 | (v, _) if !v.is_null() => Value::new(v), 213 | // All other items should have values 214 | (_, item) => panic!("{item:?} has no value"), 215 | }; 216 | 217 | if let Item::Operand(operand) = &mut item { 218 | // When we have an operand to replace, we must do so regardless of whether we've already 219 | // seen its value or not, since the same value can appear as an operand in multiple 220 | // nodes in the tree. 221 | if let Some(new_metadata) = self.replace_operands.get(&value_id) { 222 | operand.replace(unsafe { LLVMMetadataAsValue(self.context, *new_metadata) }) 223 | } 224 | } 225 | 226 | let first_visit = self.visited_nodes.insert(value_id); 227 | if !first_visit { 228 | trace!("already visited"); 229 | return; 230 | } 231 | 232 | if let Value::MDNode(mdnode) = value.clone() { 233 | self.visit_mdnode(mdnode) 234 | } 235 | 236 | if let Some(operands) = value.operands() { 237 | for (index, operand) in operands.enumerate() { 238 | self.visit_item(Item::Operand(Operand { 239 | parent: value_ref, 240 | value: operand, 241 | index: index.try_into().unwrap(), 242 | })) 243 | } 244 | } 245 | 246 | if let Some(entries) = value.metadata_entries() { 247 | for (index, (metadata, kind)) in entries.iter().enumerate() { 248 | let metadata_value = unsafe { LLVMMetadataAsValue(self.context, metadata) }; 249 | self.visit_item(Item::MetadataEntry(metadata_value, kind, index)); 250 | } 251 | } 252 | 253 | // If an item has sub items that are not operands nor metadata entries, we need to visit 254 | // those too. 255 | if let Value::Function(fun) = value { 256 | for param in fun.params() { 257 | self.visit_item(Item::FunctionParam(param)); 258 | } 259 | 260 | for basic_block in fun.basic_blocks() { 261 | for instruction in basic_block.instructions_iter() { 262 | self.visit_item(Item::Instruction(instruction)); 263 | } 264 | } 265 | } 266 | } 267 | 268 | pub(crate) fn run(mut self, exported_symbols: &HashSet>) { 269 | let module = self.module; 270 | 271 | self.replace_operands = self.fix_subprogram_linkage(exported_symbols); 272 | 273 | for value in module.globals_iter() { 274 | self.visit_item(Item::GlobalVariable(value)); 275 | } 276 | for value in module.global_aliases_iter() { 277 | self.visit_item(Item::GlobalAlias(value)); 278 | } 279 | 280 | for function in module.functions_iter() { 281 | self.visit_item(Item::Function(function)); 282 | } 283 | 284 | if !self.skipped_types_lossy.is_empty() { 285 | warn!( 286 | "debug info was not emitted for the following types: {}", 287 | self.skipped_types_lossy.join(", ") 288 | ); 289 | } 290 | 291 | unsafe { LLVMDisposeDIBuilder(self.builder) }; 292 | } 293 | 294 | // Make it so that only exported symbols (programs marked as #[no_mangle]) get BTF 295 | // linkage=global. For all other functions we want linkage=static. This avoid issues like: 296 | // 297 | // Global function write() doesn't return scalar. Only those are supported. 298 | // verification time 18 usec 299 | // stack depth 0+0 300 | // ... 301 | // 302 | // This is an error we used to get compiling aya-log. Global functions are verified 303 | // independently from their callers, so the verifier has less context and as a result globals 304 | // are harder to verify successfully. 305 | // 306 | // See tests/btf/assembly/exported-symbols.rs . 307 | fn fix_subprogram_linkage( 308 | &mut self, 309 | export_symbols: &HashSet>, 310 | ) -> HashMap { 311 | let mut replace = HashMap::new(); 312 | 313 | for mut function in self 314 | .module 315 | .functions_iter() 316 | .map(|value| unsafe { Function::from_value_ref(value) }) 317 | { 318 | if export_symbols.contains(function.name()) { 319 | continue; 320 | } 321 | 322 | // Skip functions that don't have subprograms. 323 | let Some(mut subprogram) = function.subprogram(self.context) else { 324 | continue; 325 | }; 326 | 327 | let (name, name_len) = subprogram 328 | .name() 329 | .map_or((ptr::null(), 0), |s| (s.as_ptr(), s.len())); 330 | let (linkage_name, linkage_name_len) = subprogram 331 | .linkage_name() 332 | .map_or((ptr::null(), 0), |s| (s.as_ptr(), s.len())); 333 | let ty = subprogram.ty(); 334 | 335 | // Create a new subprogram that has DISPFlagLocalToUnit set, so the BTF backend emits it 336 | // with linkage=static 337 | let mut new_program = unsafe { 338 | let new_program = LLVMDIBuilderCreateFunction( 339 | self.builder, 340 | subprogram.scope().unwrap(), 341 | name.cast(), 342 | name_len, 343 | linkage_name.cast(), 344 | linkage_name_len, 345 | subprogram.file(), 346 | subprogram.line(), 347 | ty, 348 | 1, 349 | 1, 350 | subprogram.line(), 351 | subprogram.type_flags(), 352 | 1, 353 | ); 354 | // Technically this must be called as part of the builder API, but effectively does 355 | // nothing because we don't add any variables through the builder API, instead we 356 | // replace retained nodes manually below. 357 | LLVMDIBuilderFinalizeSubprogram(self.builder, new_program); 358 | 359 | DISubprogram::from_value_ref(LLVMMetadataAsValue(self.context, new_program)) 360 | }; 361 | 362 | // Point the function to the new subprogram. 363 | function.set_subprogram(&new_program); 364 | 365 | // There's no way to set the unit with LLVMDIBuilderCreateFunction 366 | // so we set it after creation. 367 | if let Some(unit) = subprogram.unit() { 368 | new_program.set_unit(unit); 369 | } 370 | 371 | // Add retained nodes from the old program. This is needed to preserve local debug 372 | // variables, including function arguments which otherwise become "anon". See 373 | // LLVMDIBuilderFinalizeSubprogram and DISubprogram::replaceRetainedNodes. 374 | if let Some(retained_nodes) = subprogram.retained_nodes() { 375 | new_program.set_retained_nodes(retained_nodes); 376 | } 377 | 378 | // Remove retained nodes from the old program or we'll hit a debug assertion since 379 | // its debug variables no longer point to the program. See the 380 | // NumAbstractSubprograms assertion in DwarfDebug::endFunctionImpl in LLVM. 381 | let empty_node = unsafe { LLVMMDNodeInContext2(self.context, ptr::null_mut(), 0) }; 382 | subprogram.set_retained_nodes(empty_node); 383 | 384 | let ret = replace.insert(subprogram.value_ref as u64, unsafe { 385 | LLVMValueAsMetadata(new_program.value_ref) 386 | }); 387 | assert!(ret.is_none()); 388 | } 389 | 390 | replace 391 | } 392 | } 393 | 394 | #[derive(Clone, Debug, Eq, PartialEq)] 395 | enum Item { 396 | GlobalVariable(LLVMValueRef), 397 | GlobalAlias(LLVMValueRef), 398 | Function(LLVMValueRef), 399 | FunctionParam(LLVMValueRef), 400 | Instruction(LLVMValueRef), 401 | Operand(Operand), 402 | MetadataEntry(LLVMValueRef, u32, usize), 403 | } 404 | 405 | #[derive(Clone, Debug, Eq, PartialEq)] 406 | struct Operand { 407 | parent: LLVMValueRef, 408 | value: LLVMValueRef, 409 | index: u32, 410 | } 411 | 412 | impl Operand { 413 | fn replace(&mut self, value: LLVMValueRef) { 414 | let Self { 415 | parent, 416 | value: _, 417 | index, 418 | } = self; 419 | unsafe { 420 | if !LLVMIsAMDNode(*parent).is_null() { 421 | let value = LLVMValueAsMetadata(value); 422 | LLVMReplaceMDNodeOperandWith(*parent, *index, value); 423 | } else if !LLVMIsAUser(*parent).is_null() { 424 | LLVMSetOperand(*parent, *index, value); 425 | } 426 | } 427 | } 428 | } 429 | 430 | impl Item { 431 | fn value_ref(&self) -> LLVMValueRef { 432 | match self { 433 | Self::GlobalVariable(value) 434 | | Self::GlobalAlias(value) 435 | | Self::Function(value) 436 | | Self::FunctionParam(value) 437 | | Self::Instruction(value) 438 | | Self::Operand(Operand { value, .. }) 439 | | Self::MetadataEntry(value, _, _) => *value, 440 | } 441 | } 442 | 443 | fn value_id(&self) -> u64 { 444 | self.value_ref() as u64 445 | } 446 | } 447 | 448 | #[cfg(test)] 449 | mod test { 450 | use super::*; 451 | 452 | #[test] 453 | fn test_strip_generics() { 454 | let name = "MyStruct"; 455 | assert_eq!( 456 | sanitize_type_name(name.as_bytes()), 457 | b"MyStruct_3C_u64_3E_".as_slice() 458 | ); 459 | 460 | let name = "MyStruct"; 461 | assert_eq!( 462 | sanitize_type_name(name.as_bytes()), 463 | b"MyStruct_3C_u64_2C__20_u64_3E_".as_slice() 464 | ); 465 | 466 | let name = "my_function"; 467 | assert_eq!( 468 | sanitize_type_name(name.as_bytes()), 469 | b"my_function_3C_aya_bpf_3A__3A_BpfContext_3E_".as_slice() 470 | ); 471 | 472 | let name = "my_function"; 473 | assert_eq!( 474 | sanitize_type_name(name.as_bytes()), 475 | b"my_function_3C_aya_bpf_3A__3A_BpfContext_2C__20_aya_log_ebpf_3A__3A_WriteToBuf_3E_" 476 | .as_slice() 477 | ); 478 | 479 | let name = "PerfEventArray<[u8; 32]>"; 480 | assert_eq!( 481 | sanitize_type_name(name.as_bytes()), 482 | b"PerfEventArray_3C__5B_u8_3B__20_32_5D__3E_".as_slice() 483 | ); 484 | 485 | let name = "my_function"; 486 | let san = sanitize_type_name(name.as_bytes()); 487 | 488 | assert_eq!(san.len(), 128); 489 | assert_eq!( 490 | san, 491 | b"my_function_3C_aya_bpf_3A__3A_this_3A__3A_is_3A__3A_a_3A__3A_very_3A__3A_long_3A__3A_namespace_3A__3A_BpfContex_94e4085604b3142f" 492 | .as_slice() 493 | ); 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /src/linker.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | collections::HashSet, 4 | ffi::{CStr, CString, OsStr}, 5 | fs, 6 | io::{self, Read as _}, 7 | ops::Deref, 8 | os::unix::ffi::OsStrExt as _, 9 | path::{Path, PathBuf}, 10 | str::{self, FromStr}, 11 | }; 12 | 13 | use ar::Archive; 14 | use llvm_sys::{ 15 | error_handling::{LLVMEnablePrettyStackTrace, LLVMInstallFatalErrorHandler}, 16 | target_machine::LLVMCodeGenFileType, 17 | }; 18 | use thiserror::Error; 19 | use tracing::{debug, error, info, warn}; 20 | 21 | use crate::llvm::{self, LLVMContext, LLVMModule, LLVMTargetMachine, MemoryBuffer}; 22 | 23 | /// Linker error 24 | #[derive(Debug, Error)] 25 | pub enum LinkerError { 26 | /// Invalid Cpu. 27 | #[error("invalid CPU {0}")] 28 | InvalidCpu(String), 29 | 30 | /// Invalid LLVM target. 31 | #[error("invalid LLVM target {0}")] 32 | InvalidTarget(String), 33 | 34 | /// An IO Error occurred while linking a module. 35 | #[error("`{0}`: {1}")] 36 | IoError(PathBuf, io::Error), 37 | 38 | /// The file is not bitcode, an object file containing bitcode or an archive file. 39 | #[error("invalid input file `{0}`")] 40 | InvalidInputType(PathBuf), 41 | 42 | /// Linking a module failed. 43 | #[error("failure linking module {0}")] 44 | LinkModuleError(PathBuf), 45 | 46 | /// Linking a module included in an archive failed. 47 | #[error("failure linking module {1} from {0}")] 48 | LinkArchiveModuleError(PathBuf, PathBuf), 49 | 50 | /// Optimizing the BPF code failed. 51 | #[error("LLVMRunPasses failed: {0}")] 52 | OptimizeError(String), 53 | 54 | /// Generating the BPF code failed. 55 | #[error("LLVMTargetMachineEmitToFile failed: {0}")] 56 | EmitCodeError(String), 57 | 58 | /// Writing the bitcode failed. 59 | #[error("LLVMWriteBitcodeToFile failed: {0}")] 60 | WriteBitcodeError(io::Error), 61 | 62 | /// Writing the LLVM IR failed. 63 | #[error("LLVMPrintModuleToFile failed: {0}")] 64 | WriteIRError(String), 65 | 66 | /// There was an error extracting the bitcode embedded in an object file. 67 | #[error("error reading embedded bitcode: {0}")] 68 | EmbeddedBitcodeError(String), 69 | 70 | /// The input object file does not have embedded bitcode. 71 | #[error("no bitcode section found in {0}")] 72 | MissingBitcodeSection(PathBuf), 73 | 74 | /// LLVM cannot create a module for linking. 75 | #[error("failed to create module")] 76 | CreateModuleError, 77 | } 78 | 79 | /// BPF Cpu type 80 | #[derive(Clone, Copy, Debug)] 81 | pub enum Cpu { 82 | Generic, 83 | Probe, 84 | V1, 85 | V2, 86 | V3, 87 | } 88 | 89 | impl Cpu { 90 | fn as_c_str(&self) -> &'static CStr { 91 | match self { 92 | Self::Generic => c"generic", 93 | Self::Probe => c"probe", 94 | Self::V1 => c"v1", 95 | Self::V2 => c"v2", 96 | Self::V3 => c"v3", 97 | } 98 | } 99 | } 100 | 101 | impl std::fmt::Display for Cpu { 102 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 103 | f.pad(match self { 104 | Self::Generic => "generic", 105 | Self::Probe => "probe", 106 | Self::V1 => "v1", 107 | Self::V2 => "v2", 108 | Self::V3 => "v3", 109 | }) 110 | } 111 | } 112 | 113 | impl FromStr for Cpu { 114 | type Err = LinkerError; 115 | 116 | fn from_str(s: &str) -> Result { 117 | Ok(match s { 118 | "generic" => Self::Generic, 119 | "probe" => Self::Probe, 120 | "v1" => Self::V1, 121 | "v2" => Self::V2, 122 | "v3" => Self::V3, 123 | _ => return Err(LinkerError::InvalidCpu(s.to_string())), 124 | }) 125 | } 126 | } 127 | 128 | /// Optimization level 129 | #[derive(Clone, Copy, Debug)] 130 | pub enum OptLevel { 131 | /// No optimizations. Equivalent to -O0. 132 | No, 133 | /// Less than the default optimizations. Equivalent to -O1. 134 | Less, 135 | /// Default level of optimizations. Equivalent to -O2. 136 | Default, 137 | /// Aggressive optimizations. Equivalent to -O3. 138 | Aggressive, 139 | /// Optimize for size. Equivalent to -Os. 140 | Size, 141 | /// Aggressively optimize for size. Equivalent to -Oz. 142 | SizeMin, 143 | } 144 | 145 | pub enum LinkerInput<'a> { 146 | File { path: &'a Path }, 147 | Buffer { name: &'a str, bytes: &'a [u8] }, 148 | } 149 | 150 | impl<'a> LinkerInput<'a> { 151 | pub fn new_from_file(path: &'a Path) -> Self { 152 | LinkerInput::File { path } 153 | } 154 | 155 | pub fn new_from_buffer(name: &'a str, bytes: &'a [u8]) -> Self { 156 | LinkerInput::Buffer { name, bytes } 157 | } 158 | } 159 | 160 | /// Linker input type 161 | #[derive(Clone, Copy, Debug, PartialEq)] 162 | enum InputType { 163 | /// LLVM bitcode. 164 | Bitcode, 165 | /// ELF object file. 166 | Elf, 167 | /// Mach-O object file. 168 | MachO, 169 | /// Archive file. (.a) 170 | Archive, 171 | } 172 | 173 | impl std::fmt::Display for InputType { 174 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 175 | write!( 176 | f, 177 | "{}", 178 | match self { 179 | Self::Bitcode => "bitcode", 180 | Self::Elf => "elf", 181 | Self::MachO => "Mach-O", 182 | Self::Archive => "archive", 183 | } 184 | ) 185 | } 186 | } 187 | 188 | /// Output type 189 | #[derive(Clone, Copy, Debug)] 190 | pub enum OutputType { 191 | /// LLVM bitcode. 192 | Bitcode, 193 | /// Assembly. 194 | Assembly, 195 | /// LLVM IR. 196 | LlvmAssembly, 197 | /// ELF object file. 198 | Object, 199 | } 200 | 201 | /// Options to configure the linker 202 | #[derive(Debug)] 203 | pub struct LinkerOptions { 204 | /// The LLVM target to generate code for. If None, the target will be inferred from the input 205 | /// modules. 206 | pub target: Option, 207 | /// Cpu type. 208 | pub cpu: Cpu, 209 | /// Cpu features. 210 | pub cpu_features: CString, 211 | /// Optimization level. 212 | pub optimize: OptLevel, 213 | /// Whether to aggressively unroll loops. Useful for older kernels that don't support loops. 214 | pub unroll_loops: bool, 215 | /// Remove `noinline` attributes from functions. Useful for kernels before 5.8 that don't 216 | /// support function calls. 217 | pub ignore_inline_never: bool, 218 | /// Extra command line args to pass to LLVM. 219 | pub llvm_args: Vec, 220 | /// Disable passing --bpf-expand-memcpy-in-order to LLVM. 221 | pub disable_expand_memcpy_in_order: bool, 222 | /// Disable exporting memcpy, memmove, memset, memcmp and bcmp. Exporting 223 | /// those is commonly needed when LLVM does not manage to expand memory 224 | /// intrinsics to a sequence of loads and stores. 225 | pub disable_memory_builtins: bool, 226 | /// Emit BTF information 227 | pub btf: bool, 228 | /// Permit automatic insertion of __bpf_trap calls. 229 | /// See: https://github.com/llvm/llvm-project/commit/ab391beb11f733b526b86f9df23734a34657d876 230 | pub allow_bpf_trap: bool, 231 | } 232 | 233 | /// BPF Linker 234 | pub struct Linker { 235 | options: LinkerOptions, 236 | context: LLVMContext, 237 | diagnostic_handler: llvm::InstalledDiagnosticHandler, 238 | dump_module: Option, 239 | } 240 | 241 | impl Linker { 242 | /// Create a new linker instance with the given options. 243 | pub fn new(options: LinkerOptions) -> Self { 244 | let (context, diagnostic_handler) = llvm_init(&options); 245 | 246 | Self { 247 | options, 248 | context, 249 | diagnostic_handler, 250 | dump_module: None, 251 | } 252 | } 253 | 254 | /// Set the directory where the linker will dump the linked LLVM IR before and after 255 | /// optimization, for debugging and inspection purposes. 256 | /// 257 | /// When set: 258 | /// - The directory is created if it does not already exist. 259 | /// - A "pre-opt.ll" file is written with the IR before optimization. 260 | /// - A "post-opt.ll" file is written with the IR after optimization. 261 | pub fn set_dump_module_path(&mut self, path: impl AsRef) { 262 | self.dump_module = Some(path.as_ref().to_path_buf()) 263 | } 264 | 265 | /// Link and generate the output code to file. 266 | /// 267 | /// # Example 268 | /// 269 | /// ```rust,no_run 270 | /// # use std::{collections::HashSet, path::Path, borrow::Cow, ffi::CString}; 271 | /// # use bpf_linker::{Cpu, Linker, LinkerInput, LinkerOptions, OptLevel, OutputType}; 272 | /// # fn main() -> Result<(), Box> { 273 | /// let path = Path::new("/path/to/object-or-bitcode"); 274 | /// let bytes: &[u8] = &[]; // An in memory object/bitcode 275 | /// # let options = LinkerOptions { 276 | /// # target: None, 277 | /// # cpu: Cpu::Generic, 278 | /// # cpu_features: CString::default(), 279 | /// # optimize: OptLevel::Default, 280 | /// # unroll_loops: false, 281 | /// # ignore_inline_never: false, 282 | /// # llvm_args: vec![], 283 | /// # disable_expand_memcpy_in_order: false, 284 | /// # disable_memory_builtins: false, 285 | /// # allow_bpf_trap: false, 286 | /// # btf: false, 287 | /// # }; 288 | /// # let linker = Linker::new(options); 289 | /// 290 | /// let export_symbols = ["my_sym_1", "my_sym_2"]; 291 | /// 292 | /// linker.link_to_file( 293 | /// [ 294 | /// LinkerInput::new_from_file(path), 295 | /// LinkerInput::new_from_buffer("my buffer", bytes), // In memory buffer needs a name 296 | /// ], 297 | /// "/path/to/output", 298 | /// OutputType::Object, 299 | /// export_symbols, 300 | /// )?; 301 | /// # Ok(()) 302 | /// # } 303 | /// ``` 304 | pub fn link_to_file<'i, 'a, I, P, E>( 305 | &self, 306 | inputs: I, 307 | output: P, 308 | output_type: OutputType, 309 | export_symbols: E, 310 | ) -> Result<(), LinkerError> 311 | where 312 | I: IntoIterator>, 313 | E: IntoIterator, 314 | P: AsRef, 315 | { 316 | let (linked_module, target_machine) = self.link(inputs, export_symbols)?; 317 | codegen_to_file( 318 | &linked_module, 319 | &target_machine, 320 | output.as_ref(), 321 | output_type, 322 | )?; 323 | Ok(()) 324 | } 325 | 326 | /// Link and generate the output code to an in-memory buffer. 327 | /// 328 | /// # Example 329 | /// 330 | /// ```rust,no_run 331 | /// # use std::{collections::HashSet, path::Path, borrow::Cow, ffi::CString}; 332 | /// # use bpf_linker::{Cpu, Linker, LinkerInput, LinkerOptions, OptLevel, OutputType}; 333 | /// # fn main() -> Result<(), Box> { 334 | /// let path = Path::new("/path/to/object-or-bitcode"); 335 | /// let bytes: &[u8] = &[]; // An in memory object/bitcode 336 | /// # let options = LinkerOptions { 337 | /// # target: None, 338 | /// # cpu: Cpu::Generic, 339 | /// # cpu_features: CString::default(), 340 | /// # optimize: OptLevel::Default, 341 | /// # unroll_loops: false, 342 | /// # ignore_inline_never: false, 343 | /// # llvm_args: vec![], 344 | /// # disable_expand_memcpy_in_order: false, 345 | /// # disable_memory_builtins: false, 346 | /// # allow_bpf_trap: false, 347 | /// # btf: false, 348 | /// # }; 349 | /// # let linker = Linker::new(options); 350 | /// 351 | /// let export_symbols = ["my_sym_1", "my_sym_2"]; 352 | /// 353 | /// let out_buf = linker.link_to_buffer( 354 | /// [ 355 | /// LinkerInput::new_from_file(path), 356 | /// LinkerInput::new_from_buffer("my buffer", bytes), // In memory buffer needs a name 357 | /// ], 358 | /// OutputType::Bitcode, 359 | /// export_symbols, 360 | /// )?; 361 | /// 362 | /// // Use the buffer as slice of u8 363 | /// let bytes = out_buf.as_slice(); 364 | /// println!("Linked {} bytes into memory)", bytes.len()); 365 | /// 366 | /// # Ok(()) 367 | /// # } 368 | /// ``` 369 | pub fn link_to_buffer<'i, 'a, I, E>( 370 | &self, 371 | inputs: I, 372 | output_type: OutputType, 373 | export_symbols: E, 374 | ) -> Result 375 | where 376 | I: IntoIterator>, 377 | E: IntoIterator, 378 | { 379 | let (linked_module, target_machine) = self.link(inputs, export_symbols)?; 380 | codegen_to_buffer(&linked_module, &target_machine, output_type) 381 | } 382 | 383 | /// Link and generate the output code. 384 | fn link<'ctx, 'i, 'a, I, E>( 385 | &'ctx self, 386 | inputs: I, 387 | export_symbols: E, 388 | ) -> Result<(LLVMModule<'ctx>, LLVMTargetMachine), LinkerError> 389 | where 390 | I: IntoIterator>, 391 | E: IntoIterator, 392 | { 393 | let Self { 394 | options, 395 | context, 396 | dump_module, 397 | .. 398 | } = self; 399 | 400 | let mut module = link_modules(context, inputs)?; 401 | 402 | let target_machine = create_target_machine(options, &module)?; 403 | 404 | if let Some(path) = dump_module { 405 | fs::create_dir_all(path).map_err(|err| LinkerError::IoError(path.to_owned(), err))?; 406 | } 407 | if let Some(path) = dump_module { 408 | // dump IR before optimization 409 | let path = path.join("pre-opt.ll"); 410 | let path = CString::new(path.as_os_str().as_encoded_bytes()).unwrap(); 411 | module 412 | .write_ir_to_path(&path) 413 | .map_err(LinkerError::WriteIRError)?; 414 | }; 415 | optimize( 416 | options, 417 | context, 418 | &target_machine, 419 | &mut module, 420 | export_symbols, 421 | )?; 422 | if let Some(path) = dump_module { 423 | // dump IR before optimization 424 | let path = path.join("post-opt.ll"); 425 | let path = CString::new(path.as_os_str().as_encoded_bytes()).unwrap(); 426 | module 427 | .write_ir_to_path(&path) 428 | .map_err(LinkerError::WriteIRError)?; 429 | }; 430 | 431 | Ok((module, target_machine)) 432 | } 433 | 434 | pub fn has_errors(&self) -> bool { 435 | self.diagnostic_handler.with_view(|h| h.has_errors) 436 | } 437 | } 438 | 439 | fn link_modules<'ctx, 'i, I>( 440 | context: &'ctx LLVMContext, 441 | inputs: I, 442 | ) -> Result, LinkerError> 443 | where 444 | I: IntoIterator>, 445 | { 446 | let mut module = context 447 | .create_module(c"linked_module") 448 | .ok_or(LinkerError::CreateModuleError)?; 449 | 450 | let mut buf = Vec::new(); 451 | for input in inputs { 452 | let data: Vec; 453 | let (path, input) = match input { 454 | LinkerInput::File { path } => { 455 | data = fs::read(path).map_err(|e| LinkerError::IoError(path.to_owned(), e))?; 456 | (path.to_owned(), data.as_ref()) 457 | } 458 | LinkerInput::Buffer { name, bytes } => { 459 | (PathBuf::from(format!("in_memory::{}", name)), bytes) 460 | } 461 | }; 462 | 463 | // determine whether the input is bitcode, ELF with embedded bitcode, an archive file 464 | // or an invalid file 465 | let in_type = 466 | detect_input_type(input).ok_or_else(|| LinkerError::InvalidInputType(path.clone()))?; 467 | 468 | match in_type { 469 | InputType::Archive => { 470 | info!("linking archive {}", path.display()); 471 | 472 | // Extract the archive and call link_reader() for each item. 473 | let mut archive = Archive::new(input); 474 | while let Some(item) = archive.next_entry() { 475 | let mut item = item.map_err(|e| LinkerError::IoError(path.clone(), e))?; 476 | let name = PathBuf::from(OsStr::from_bytes(item.header().identifier())); 477 | info!("linking archive item {}", name.display()); 478 | 479 | buf.clear(); 480 | let _: usize = item 481 | .read_to_end(&mut buf) 482 | .map_err(|e| LinkerError::IoError(name.to_owned(), e))?; 483 | let in_type = match detect_input_type(&buf) { 484 | Some(in_type) => in_type, 485 | None => { 486 | info!("ignoring archive item {}: invalid type", name.display()); 487 | continue; 488 | } 489 | }; 490 | 491 | match link_data(context, &mut module, &name, &buf, in_type) { 492 | Ok(()) => continue, 493 | Err(LinkerError::InvalidInputType(name)) => { 494 | info!("ignoring archive item {}: invalid type", name.display()); 495 | continue; 496 | } 497 | Err(LinkerError::MissingBitcodeSection(name)) => { 498 | warn!( 499 | "ignoring archive item {}: no embedded bitcode", 500 | name.display() 501 | ); 502 | continue; 503 | } 504 | // TODO: this discards the underlying error. 505 | Err(_) => { 506 | return Err(LinkerError::LinkArchiveModuleError( 507 | path.to_owned(), 508 | name.to_owned(), 509 | )); 510 | } 511 | }; 512 | } 513 | } 514 | ty => { 515 | info!("linking file {} type {}", path.display(), ty); 516 | match link_data(context, &mut module, &path, input, ty) { 517 | Ok(()) => {} 518 | Err(LinkerError::InvalidInputType(path)) => { 519 | info!("ignoring file {}: invalid type", path.display()); 520 | continue; 521 | } 522 | Err(LinkerError::MissingBitcodeSection(path)) => { 523 | warn!("ignoring file {}: no embedded bitcode", path.display()); 524 | } 525 | Err(err) => return Err(err), 526 | } 527 | } 528 | } 529 | } 530 | 531 | Ok(module) 532 | } 533 | 534 | fn link_data<'ctx>( 535 | context: &'ctx LLVMContext, 536 | module: &mut LLVMModule<'ctx>, 537 | path: &Path, 538 | data: &[u8], 539 | in_type: InputType, 540 | ) -> Result<(), LinkerError> { 541 | let bitcode = match in_type { 542 | InputType::Bitcode => Cow::Borrowed(data), 543 | InputType::Elf => match llvm::find_embedded_bitcode(context, data) { 544 | Ok(Some(bitcode)) => Cow::Owned(bitcode), 545 | Ok(None) => return Err(LinkerError::MissingBitcodeSection(path.to_owned())), 546 | Err(e) => return Err(LinkerError::EmbeddedBitcodeError(e)), 547 | }, 548 | // we need to handle this here since archive files could contain 549 | // mach-o files, eg somecrate.rlib containing lib.rmeta which is 550 | // mach-o on macos 551 | InputType::MachO => return Err(LinkerError::InvalidInputType(path.to_owned())), 552 | // this can't really happen 553 | InputType::Archive => panic!("nested archives not supported duh"), 554 | }; 555 | 556 | if !llvm::link_bitcode_buffer(context, module, &bitcode) { 557 | return Err(LinkerError::LinkModuleError(path.to_owned())); 558 | } 559 | 560 | Ok(()) 561 | } 562 | 563 | fn create_target_machine( 564 | options: &LinkerOptions, 565 | module: &LLVMModule<'_>, 566 | ) -> Result { 567 | let LinkerOptions { 568 | target, 569 | cpu, 570 | cpu_features, 571 | .. 572 | } = options; 573 | // Here's how the output target is selected: 574 | // 575 | // 1) rustc with builtin BPF support: cargo build --target=bpf[el|eb]-unknown-none 576 | // the input modules are already configured for the correct output target 577 | // 578 | // 2) rustc with no BPF support: cargo rustc -- -C linker-flavor=bpf-linker -C linker=bpf-linker -C link-arg=--target=bpf[el|eb] 579 | // the input modules are configured for the *host* target, and the output target 580 | // is configured with the `--target` linker argument 581 | // 582 | // 3) rustc with no BPF support: cargo rustc -- -C linker-flavor=bpf-linker -C linker=bpf-linker 583 | // the input modules are configured for the *host* target, the output target isn't 584 | // set via `--target`, so default to `bpf` (bpfel or bpfeb depending on the host 585 | // endianness) 586 | let (triple, target) = match target { 587 | // case 1 588 | Some(c_triple) => (c_triple.as_c_str(), llvm::target_from_triple(c_triple)), 589 | None => { 590 | let c_triple = module.get_target(); 591 | let c_triple = unsafe { CStr::from_ptr(c_triple) }; 592 | if c_triple.to_bytes().starts_with(b"bpf") { 593 | // case 2 594 | (c_triple, llvm::target_from_module(module)) 595 | } else { 596 | // case 3. 597 | info!( 598 | "detected non-bpf input target {} and no explicit output --target specified, selecting `bpf'", 599 | OsStr::from_bytes(c_triple.to_bytes()).display() 600 | ); 601 | let c_triple = c"bpf"; 602 | (c_triple, llvm::target_from_triple(c_triple)) 603 | } 604 | } 605 | }; 606 | let target = 607 | target.map_err(|_msg| LinkerError::InvalidTarget(triple.to_string_lossy().to_string()))?; 608 | 609 | debug!( 610 | "creating target machine: triple: {} cpu: {} features: {}", 611 | triple.to_string_lossy(), 612 | cpu, 613 | cpu_features.to_string_lossy(), 614 | ); 615 | 616 | let target_machine = LLVMTargetMachine::new(target, triple, cpu.as_c_str(), cpu_features) 617 | .ok_or_else(|| LinkerError::InvalidTarget(triple.to_string_lossy().to_string()))?; 618 | 619 | Ok(target_machine) 620 | } 621 | 622 | fn optimize<'ctx, 'a, E>( 623 | options: &LinkerOptions, 624 | context: &'ctx LLVMContext, 625 | target_machine: &LLVMTargetMachine, 626 | module: &mut LLVMModule<'ctx>, 627 | export_symbols: E, 628 | ) -> Result<(), LinkerError> 629 | where 630 | E: IntoIterator, 631 | { 632 | let LinkerOptions { 633 | disable_memory_builtins, 634 | optimize, 635 | btf, 636 | ignore_inline_never, 637 | .. 638 | } = options; 639 | 640 | let mut export_symbols: HashSet> = export_symbols 641 | .into_iter() 642 | .map(|s| Cow::Borrowed(s.as_bytes())) 643 | .collect(); 644 | 645 | if !disable_memory_builtins { 646 | export_symbols.extend( 647 | ["memcpy", "memmove", "memset", "memcmp", "bcmp"] 648 | .into_iter() 649 | .map(|s| s.as_bytes().into()), 650 | ); 651 | }; 652 | debug!( 653 | "linking exporting symbols {:?}, opt level {:?}", 654 | export_symbols, optimize 655 | ); 656 | // run optimizations. Will optionally remove noinline attributes, intern all non exported 657 | // programs and maps and remove dead code. 658 | 659 | if *btf { 660 | // if we want to emit BTF, we need to sanitize the debug information 661 | llvm::DISanitizer::new(context, module).run(&export_symbols); 662 | } else { 663 | // if we don't need BTF emission, we can strip DI 664 | let ok = module.strip_debug_info(); 665 | debug!("Stripping DI, changed={}", ok); 666 | } 667 | 668 | llvm::optimize( 669 | target_machine, 670 | module, 671 | options.optimize, 672 | *ignore_inline_never, 673 | &export_symbols, 674 | ) 675 | .map_err(LinkerError::OptimizeError)?; 676 | 677 | Ok(()) 678 | } 679 | 680 | fn codegen_to_file( 681 | module: &LLVMModule<'_>, 682 | target_machine: &LLVMTargetMachine, 683 | output: &Path, 684 | output_type: OutputType, 685 | ) -> Result<(), LinkerError> { 686 | info!("writing {:?} to {:?}", output_type, output); 687 | let output = CString::new(output.as_os_str().as_encoded_bytes()).unwrap(); 688 | match output_type { 689 | OutputType::Bitcode => module 690 | .write_bitcode_to_path(&output) 691 | .map_err(LinkerError::WriteBitcodeError), 692 | OutputType::LlvmAssembly => module 693 | .write_ir_to_path(&output) 694 | .map_err(LinkerError::WriteIRError), 695 | OutputType::Assembly => target_machine 696 | .emit_to_file(module, &output, LLVMCodeGenFileType::LLVMAssemblyFile) 697 | .map_err(LinkerError::EmitCodeError), 698 | OutputType::Object => target_machine 699 | .emit_to_file(module, &output, LLVMCodeGenFileType::LLVMObjectFile) 700 | .map_err(LinkerError::EmitCodeError), 701 | } 702 | } 703 | 704 | fn codegen_to_buffer( 705 | module: &LLVMModule<'_>, 706 | target_machine: &LLVMTargetMachine, 707 | output_type: OutputType, 708 | ) -> Result { 709 | let memory_buffer = match output_type { 710 | OutputType::Bitcode => module.write_bitcode_to_memory(), 711 | OutputType::LlvmAssembly => module.write_ir_to_memory(), 712 | OutputType::Assembly => target_machine 713 | .emit_to_memory_buffer(module, LLVMCodeGenFileType::LLVMAssemblyFile) 714 | .map_err(LinkerError::EmitCodeError)?, 715 | OutputType::Object => target_machine 716 | .emit_to_memory_buffer(module, LLVMCodeGenFileType::LLVMObjectFile) 717 | .map_err(LinkerError::EmitCodeError)?, 718 | }; 719 | 720 | Ok(LinkerOutput { 721 | inner: memory_buffer, 722 | }) 723 | } 724 | 725 | fn llvm_init( 726 | options: &LinkerOptions, 727 | ) -> ( 728 | LLVMContext, 729 | llvm::InstalledDiagnosticHandler, 730 | ) { 731 | let mut args = Vec::>::new(); 732 | args.push(c"bpf-linker".into()); 733 | // Disable cold call site detection. Many accessors in aya-ebpf return Result 734 | // where the layout is larger than 64 bits, but the LLVM BPF target only supports 735 | // up to 64 bits return values. Since the accessors are tiny in terms of code, we 736 | // avoid the issue by annotating them with #[inline(always)]. If they are classified 737 | // as cold though - and they often are starting from LLVM17 - #[inline(always)] 738 | // is ignored and the BPF target fails codegen. 739 | args.push(c"--cold-callsite-rel-freq=0".into()); 740 | if options.unroll_loops { 741 | // setting cmdline arguments is the only way to customize the unroll pass with the 742 | // C API. 743 | args.extend([ 744 | c"--unroll-runtime".into(), 745 | c"--unroll-runtime-multi-exit".into(), 746 | CString::new(format!("--unroll-max-upperbound={}", u32::MAX)) 747 | .unwrap() 748 | .into(), 749 | CString::new(format!("--unroll-threshold={}", u32::MAX)) 750 | .unwrap() 751 | .into(), 752 | ]); 753 | } 754 | if !options.disable_expand_memcpy_in_order { 755 | args.push(c"--bpf-expand-memcpy-in-order".into()); 756 | } 757 | if !options.allow_bpf_trap { 758 | // TODO: Remove this once ksyms support is guaranteed. 759 | // LLVM introduces __bpf_trap calls at points where __builtin_trap would normally be 760 | // emitted. This is currently not supported by aya because __bpf_trap requires a .ksyms 761 | // section, but this is not trivial to support. In the meantime, using this flag 762 | // returns LLVM to the old behaviour, which did not introduce these calls and therefore 763 | // does not require the .ksyms section. 764 | args.push(c"--bpf-disable-trap-unreachable".into()); 765 | } 766 | args.extend(options.llvm_args.iter().map(Into::into)); 767 | info!("LLVM command line: {:?}", args); 768 | llvm::init(args.as_slice(), c"BPF linker"); 769 | 770 | let mut context = LLVMContext::new(); 771 | 772 | let diagnostic_handler = context.set_diagnostic_handler(DiagnosticHandler::default()); 773 | 774 | unsafe { 775 | LLVMInstallFatalErrorHandler(Some(llvm::fatal_error)); 776 | LLVMEnablePrettyStackTrace(); 777 | } 778 | 779 | (context, diagnostic_handler) 780 | } 781 | 782 | #[derive(Default)] 783 | pub(crate) struct DiagnosticHandler { 784 | pub(crate) has_errors: bool, 785 | // The handler is passed to LLVM as a raw pointer so it must not be moved. 786 | _marker: std::marker::PhantomPinned, 787 | } 788 | 789 | impl llvm::LLVMDiagnosticHandler for DiagnosticHandler { 790 | fn handle_diagnostic( 791 | &mut self, 792 | severity: llvm_sys::LLVMDiagnosticSeverity, 793 | message: Cow<'_, str>, 794 | ) { 795 | // TODO(https://reviews.llvm.org/D155894): Remove this when LLVM no longer emits these 796 | // errors. 797 | // 798 | // See https://github.com/rust-lang/compiler-builtins/blob/a61823f/src/mem/mod.rs#L22-L68. 799 | const MATCHERS: &[&str] = &[ 800 | "A call to built-in function 'memcpy' is not supported.\n", 801 | "A call to built-in function 'memmove' is not supported.\n", 802 | "A call to built-in function 'memset' is not supported.\n", 803 | "A call to built-in function 'memcmp' is not supported.\n", 804 | "A call to built-in function 'bcmp' is not supported.\n", 805 | "A call to built-in function 'strlen' is not supported.\n", 806 | ]; 807 | 808 | match severity { 809 | llvm_sys::LLVMDiagnosticSeverity::LLVMDSError => { 810 | if MATCHERS.iter().any(|matcher| message.ends_with(matcher)) { 811 | return; 812 | } 813 | self.has_errors = true; 814 | 815 | error!("llvm: {}", message) 816 | } 817 | llvm_sys::LLVMDiagnosticSeverity::LLVMDSWarning => warn!("llvm: {}", message), 818 | llvm_sys::LLVMDiagnosticSeverity::LLVMDSRemark => debug!("remark: {}", message), 819 | llvm_sys::LLVMDiagnosticSeverity::LLVMDSNote => debug!("note: {}", message), 820 | } 821 | } 822 | } 823 | 824 | fn detect_input_type(data: &[u8]) -> Option { 825 | if data.len() < 8 { 826 | return None; 827 | } 828 | 829 | match &data[..4] { 830 | b"\x42\x43\xC0\xDE" | b"\xDE\xC0\x17\x0b" => Some(InputType::Bitcode), 831 | b"\x7FELF" => Some(InputType::Elf), 832 | b"\xcf\xfa\xed\xfe" => Some(InputType::MachO), 833 | _ => { 834 | if &data[..8] == b"!\x0A" { 835 | Some(InputType::Archive) 836 | } else { 837 | None 838 | } 839 | } 840 | } 841 | } 842 | 843 | pub struct LinkerOutput { 844 | inner: MemoryBuffer, 845 | } 846 | 847 | impl LinkerOutput { 848 | pub fn as_slice(&self) -> &[u8] { 849 | self.inner.as_slice() 850 | } 851 | } 852 | 853 | impl AsRef<[u8]> for LinkerOutput { 854 | fn as_ref(&self) -> &[u8] { 855 | self.as_slice() 856 | } 857 | } 858 | 859 | impl Deref for LinkerOutput { 860 | type Target = [u8]; 861 | 862 | fn deref(&self) -> &Self::Target { 863 | self.as_slice() 864 | } 865 | } 866 | --------------------------------------------------------------------------------