diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e668e96..061502bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,29 +53,33 @@ jobs: - rust: 1.86.0 llvm-version: 19 llvm-from: apt + features: di-sanitizer,llvm-19,llvm-sys-19/force-dynamic exclude-features: default,llvm-20,llvm-21,rust-llvm-20,rust-llvm-21 - rust: 1.89.0 llvm-version: 20 llvm-from: apt + features: di-sanitizer,llvm-20,llvm-sys-20/force-dynamic exclude-features: default,llvm-19,llvm-21,rust-llvm-19,rust-llvm-21 - rust: beta llvm-version: 21 llvm-from: apt + features: di-sanitizer,llvm-21,llvm-sys-21/force-dynamic exclude-features: default,llvm-19,llvm-20,rust-llvm-19,rust-llvm-20 - rust: nightly llvm-version: 21 llvm-from: apt + features: di-sanitizer,llvm-21,llvm-sys-21/force-dynamic exclude-features: llvm-19,llvm-20,rust-llvm-19,rust-llvm-20 - rust: nightly llvm-version: 21 llvm-from: source - exclude-features: llvm-19,llvm-20,rust-llvm-19,rust-llvm-20 + features: llvm-21,llvm-sys-21/force-dynamic + exclude-features: di-sanitizer,llvm-19,llvm-20,rust-llvm-19,rust-llvm-20 name: rustc=${{ matrix.rust }} llvm-version=${{ matrix.llvm-version }} llvm-from=${{ matrix.llvm-from }} needs: llvm env: RUST_BACKTRACE: full - LLVM_FEATURES: llvm-${{ matrix.llvm-version }},llvm-sys-${{ matrix.llvm-version }}/force-dynamic steps: - uses: actions/checkout@v5 @@ -165,13 +169,13 @@ jobs: run: | cargo hack check --feature-powerset \ --exclude-features ${{ matrix.exclude-features }} \ - --features ${{ env.LLVM_FEATURES }} + --features ${{ matrix.features }} - name: Build run: | cargo hack build --feature-powerset \ --exclude-features ${{ matrix.exclude-features }} \ - --features ${{ env.LLVM_FEATURES }} + --features ${{ matrix.features }} # Toolchains provided by rustup include standard library artifacts # only for Tier 1 targets, which do not include BPF targets. @@ -185,7 +189,7 @@ jobs: run: | RUSTC_BOOTSTRAP=1 cargo hack test --feature-powerset \ --exclude-features ${{ matrix.exclude-features }} \ - --features ${{ env.LLVM_FEATURES }} + --features ${{ matrix.features }} # To make things easier for package maintainers, the step of building a # custom sysroot can be skipped by setting the `BPFEL_SYSROOT_DIR` @@ -209,18 +213,21 @@ jobs: BPFEL_SYSROOT_DIR="$BPFEL_SYSROOT_DIR" cargo hack test --feature-powerset \ --exclude-features ${{ matrix.exclude-features }} \ - --features ${{ env.LLVM_FEATURES }} + --features ${{ matrix.features }} - uses: actions/checkout@v5 if: matrix.rust == 'nightly' with: - repository: aya-rs/aya + # FIXME: Switch to upstream after https://github.com/aya-rs/aya/pull/1382 + # is merged. + repository: vadorovsky/aya + ref: btf-sanitize-name path: aya submodules: recursive - name: Install if: matrix.rust == 'nightly' - run: cargo install --path . --no-default-features --features ${{ env.LLVM_FEATURES }} + run: cargo install --path . --no-default-features --features ${{ matrix.features }} - name: Run aya integration tests if: matrix.rust == 'nightly' diff --git a/Cargo.toml b/Cargo.toml index 1a506ce9..0c92b612 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,8 +48,9 @@ workspace = true name = "bpf-linker" [features] -llvm-19 = ["dep:llvm-sys-19"] -llvm-20 = ["dep:llvm-sys-20"] +di-sanitizer = [] +llvm-19 = ["dep:llvm-sys-19", "di-sanitizer"] +llvm-20 = ["dep:llvm-sys-20", "di-sanitizer"] llvm-21 = ["dep:llvm-sys-21"] rust-llvm-19 = [ "dep:aya-rustc-llvm-proxy", @@ -69,6 +70,11 @@ rust-llvm-21 = [ default = [ "llvm-21", "rust-llvm-21", + # Remove once Rust nightly and Linux distributions update to LLVM 21.1.5, + # that contains fixes making the sanitizer obsolete[0]. + # + # [0] https://github.com/llvm/llvm-project/pull/165154 + "di-sanitizer" ] [workspace] diff --git a/src/linker.rs b/src/linker.rs index faea046b..eb17848b 100644 --- a/src/linker.rs +++ b/src/linker.rs @@ -670,6 +670,10 @@ fn create_target_machine( fn optimize<'ctx, 'a, E>( options: &LinkerOptions, + #[cfg_attr( + not(feature = "di-sanitizer"), + expect(unused, reason = "used when `di-sanitizer` feature is enabled") + )] context: &'ctx LLVMContext, target_machine: &LLVMTargetMachine, module: &mut LLVMModule<'ctx>, @@ -707,6 +711,7 @@ where if *btf { // if we want to emit BTF, we need to sanitize the debug information + #[cfg(feature = "di-sanitizer")] llvm::DISanitizer::new(context, module).run(&export_symbols); } else { // if we don't need BTF emission, we can strip DI diff --git a/src/llvm/di.rs b/src/llvm/di.rs index dbaa0f29..6213afbd 100644 --- a/src/llvm/di.rs +++ b/src/llvm/di.rs @@ -11,10 +11,7 @@ use gimli::{DW_TAG_pointer_type, DW_TAG_structure_type, DW_TAG_variant_part}; use llvm_sys::{core::*, debuginfo::*, prelude::*}; use tracing::{Level, span, trace, warn}; -use super::types::{ - di::DIType, - ir::{Function, MDNode, Metadata, Value}, -}; +use super::types::ir::{Function, MDNode, Metadata, Value}; use crate::llvm::{LLVMContext, LLVMModule, iter::*, types::di::DISubprogram}; // KSYM_NAME_LEN from linux kernel intentionally set @@ -90,8 +87,6 @@ impl<'ctx> DISanitizer<'ctx> { } let mut is_data_carrying_enum = false; - let mut remove_name = false; - let mut members: Vec> = Vec::new(); for element in di_composite_type.elements() { match element { Metadata::DICompositeType(di_composite_type_inner) => { @@ -123,50 +118,13 @@ impl<'ctx> DISanitizer<'ctx> { _ => {} } } - Metadata::DIDerivedType(di_derived_type) => { - let base_type = di_derived_type.base_type(); - - match base_type { - Metadata::DICompositeType(base_type_di_composite_type) => { - if let Some(base_type_name) = - base_type_di_composite_type.name() - { - // `AyaBtfMapMarker` is a type which is used in fields of BTF map - // structs. We need to make such structs anonymous in order to get - // BTF maps accepted by the Linux kernel. - if base_type_name == b"AyaBtfMapMarker" { - // Remove the name from the struct. - remove_name = true; - // And don't include the field in the sanitized DI. - } else { - members.push(di_derived_type.into()); - } - } else { - members.push(di_derived_type.into()); - } - } - _ => { - members.push(di_derived_type.into()); - } - } - } _ => {} } } if is_data_carrying_enum { di_composite_type.replace_elements(MDNode::empty(self.context)); - } else if !members.is_empty() { - members.sort_by_cached_key(|di_type| di_type.offset_in_bits()); - let sorted_elements = - MDNode::with_elements(self.context, members.as_mut_slice()); - di_composite_type.replace_elements(sorted_elements); } - if remove_name { - // `AyaBtfMapMarker` is a type which is used in fields of BTF map - // structs. We need to make such structs anonymous in order to get - // BTF maps accepted by the Linux kernel. - di_composite_type.replace_name(self.context, &[]) - } else if let Some((_, sanitized_name)) = names { + if let Some((_, sanitized_name)) = names { // Clear the name from characters incompatible with C. di_composite_type.replace_name(self.context, sanitized_name.as_slice()) } diff --git a/src/llvm/iter.rs b/src/llvm/iter.rs index 4f2d957b..82b2b376 100644 --- a/src/llvm/iter.rs +++ b/src/llvm/iter.rs @@ -1,13 +1,20 @@ use std::marker::PhantomData; +#[cfg(feature = "di-sanitizer")] use llvm_sys::{ core::{ - LLVMGetFirstBasicBlock, LLVMGetFirstFunction, LLVMGetFirstGlobal, LLVMGetFirstGlobalAlias, - LLVMGetFirstInstruction, LLVMGetLastBasicBlock, LLVMGetLastFunction, LLVMGetLastGlobal, - LLVMGetLastGlobalAlias, LLVMGetLastInstruction, LLVMGetNextBasicBlock, LLVMGetNextFunction, - LLVMGetNextGlobal, LLVMGetNextGlobalAlias, LLVMGetNextInstruction, + LLVMGetFirstBasicBlock, LLVMGetFirstInstruction, LLVMGetLastBasicBlock, + LLVMGetLastInstruction, LLVMGetNextBasicBlock, LLVMGetNextInstruction, }, - prelude::{LLVMBasicBlockRef, LLVMModuleRef, LLVMValueRef}, + prelude::LLVMBasicBlockRef, +}; +use llvm_sys::{ + core::{ + LLVMGetFirstFunction, LLVMGetFirstGlobal, LLVMGetFirstGlobalAlias, LLVMGetLastFunction, + LLVMGetLastGlobal, LLVMGetLastGlobalAlias, LLVMGetNextFunction, LLVMGetNextGlobal, + LLVMGetNextGlobalAlias, + }, + prelude::{LLVMModuleRef, LLVMValueRef}, }; macro_rules! llvm_iterator { @@ -90,6 +97,7 @@ llvm_iterator! { LLVMGetNextFunction, } +#[cfg(feature = "di-sanitizer")] llvm_iterator!( IterBasicBlocks, BasicBlockIter, @@ -101,6 +109,7 @@ llvm_iterator!( LLVMGetNextBasicBlock ); +#[cfg(feature = "di-sanitizer")] llvm_iterator!( IterInstructions, InstructionsIter, diff --git a/src/llvm/mod.rs b/src/llvm/mod.rs index 5e644084..14d3b0f7 100644 --- a/src/llvm/mod.rs +++ b/src/llvm/mod.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "di-sanitizer")] mod di; mod iter; mod types; @@ -10,16 +11,18 @@ use std::{ ptr, slice, str, }; +#[cfg(feature = "di-sanitizer")] pub(crate) use di::DISanitizer; use iter::{IterModuleFunctions as _, IterModuleGlobalAliases as _, IterModuleGlobals as _}; +#[cfg(feature = "di-sanitizer")] +use llvm_sys::core::LLVMGetMDString; use llvm_sys::{ LLVMAttributeFunctionIndex, LLVMLinkage, LLVMVisibility, bit_reader::LLVMParseBitcodeInContext2, core::{ LLVMCreateMemoryBufferWithMemoryRange, LLVMDisposeMemoryBuffer, LLVMDisposeMessage, - LLVMGetEnumAttributeKindForName, LLVMGetMDString, LLVMGetModuleInlineAsm, LLVMGetTarget, - LLVMGetValueName2, LLVMRemoveEnumAttributeAtIndex, LLVMSetLinkage, LLVMSetModuleInlineAsm2, - LLVMSetVisibility, + LLVMGetEnumAttributeKindForName, LLVMGetModuleInlineAsm, LLVMGetTarget, LLVMGetValueName2, + LLVMRemoveEnumAttributeAtIndex, LLVMSetLinkage, LLVMSetModuleInlineAsm2, LLVMSetVisibility, }, error::{ LLVMDisposeErrorMessage, LLVMGetErrorMessage, LLVMGetErrorTypeId, LLVMGetStringErrorTypeId, diff --git a/src/llvm/types/di.rs b/src/llvm/types/di.rs index ce0211b9..ca8967ba 100644 --- a/src/llvm/types/di.rs +++ b/src/llvm/types/di.rs @@ -5,8 +5,7 @@ use llvm_sys::{ core::{LLVMGetNumOperands, LLVMGetOperand, LLVMReplaceMDNodeOperandWith, LLVMValueAsMetadata}, debuginfo::{ LLVMDIFileGetFilename, LLVMDIFlags, LLVMDIScopeGetFile, LLVMDISubprogramGetLine, - LLVMDITypeGetFlags, LLVMDITypeGetLine, LLVMDITypeGetName, LLVMDITypeGetOffsetInBits, - LLVMGetDINodeTag, + LLVMDITypeGetFlags, LLVMDITypeGetLine, LLVMDITypeGetName, LLVMGetDINodeTag, }, prelude::{LLVMContextRef, LLVMMetadataRef, LLVMValueRef}, }; @@ -108,60 +107,6 @@ unsafe fn di_type_name<'a>(metadata_ref: LLVMMetadataRef) -> Option<&'a [u8]> { (!ptr.is_null()).then(|| unsafe { slice::from_raw_parts(ptr.cast(), len) }) } -/// Represents the debug information for a primitive type in LLVM IR. -pub(crate) struct DIType<'ctx> { - pub(super) metadata_ref: LLVMMetadataRef, - pub(super) value_ref: LLVMValueRef, - _marker: PhantomData<&'ctx ()>, -} - -impl DIType<'_> { - /// Constructs a new [`DIType`] from the given `value`. - /// - /// # Safety - /// - /// This method assumes that the given `value` corresponds to a valid - /// instance of [LLVM `DIType`](https://llvm.org/doxygen/classllvm_1_1DIType.html). - /// It's the caller's responsibility to ensure this invariant, as this - /// method doesn't perform any validation checks. - pub(crate) unsafe fn from_value_ref(value_ref: LLVMValueRef) -> Self { - let metadata_ref = unsafe { LLVMValueAsMetadata(value_ref) }; - Self { - metadata_ref, - value_ref, - _marker: PhantomData, - } - } - - /// Returns the offset of the type in bits. This offset is used in case the - /// type is a member of a composite type. - pub(crate) fn offset_in_bits(&self) -> u64 { - unsafe { LLVMDITypeGetOffsetInBits(self.metadata_ref) } - } -} - -impl<'ctx> From> for DIType<'ctx> { - fn from(di_derived_type: DIDerivedType<'_>) -> Self { - unsafe { Self::from_value_ref(di_derived_type.value_ref) } - } -} - -/// Represents the operands for a [`DIDerivedType`]. The enum values correspond -/// to the operand indices within metadata nodes. -#[repr(u32)] -enum DIDerivedTypeOperand { - /// [`DIType`] representing a base type of the given derived type. - /// Reference in [LLVM 19-20][llvm-19] and [LLVM 21][llvm-21]. - /// - /// [llvm-19]: https://github.com/llvm/llvm-project/blob/llvmorg-19.1.7/llvm/include/llvm/IR/DebugInfoMetadata.h#L1084 - /// [llvm-21]: https://github.com/llvm/llvm-project/blob/llvmorg-21.1.0-rc3/llvm/include/llvm/IR/DebugInfoMetadata.h#L1386 - /// - #[cfg(any(feature = "llvm-19", feature = "llvm-20"))] - BaseType = 3, - #[cfg(feature = "llvm-21")] - BaseType = 5, -} - /// Represents the debug information for a derived type in LLVM IR. /// /// The types derived from other types usually add a level of indirection or an @@ -191,14 +136,6 @@ impl DIDerivedType<'_> { } } - /// Returns the base type of this derived type. - pub(crate) fn base_type(&self) -> Metadata<'_> { - unsafe { - let value = LLVMGetOperand(self.value_ref, DIDerivedTypeOperand::BaseType as u32); - Metadata::from_value_ref(value) - } - } - /// Replaces the name of the type with a new name. /// /// # Errors diff --git a/src/llvm/types/ir.rs b/src/llvm/types/ir.rs index b790f72e..719b658b 100644 --- a/src/llvm/types/ir.rs +++ b/src/llvm/types/ir.rs @@ -19,7 +19,7 @@ use crate::llvm::{ Message, iter::IterBasicBlocks as _, symbol_name, - types::di::{DICompositeType, DIDerivedType, DISubprogram, DIType}, + types::di::{DICompositeType, DIDerivedType, DISubprogram}, }; pub(crate) fn replace_name( @@ -229,26 +229,6 @@ impl MDNode<'_> { let metadata = unsafe { LLVMMDNodeInContext2(context, core::ptr::null_mut(), 0) }; unsafe { Self::from_metadata_ref(context, metadata) } } - - /// Constructs a new metadata node from an array of [`DIType`] elements. - /// - /// This function is used to create composite metadata structures, such as - /// arrays or tuples of different types or values, which can then be used - /// to represent complex data structures within the metadata system. - pub(crate) fn with_elements(context: LLVMContextRef, elements: &[DIType<'_>]) -> Self { - let metadata = unsafe { - let mut elements: Vec = elements - .iter() - .map(|di_type| LLVMValueAsMetadata(di_type.value_ref)) - .collect(); - LLVMMDNodeInContext2( - context, - elements.as_mut_slice().as_mut_ptr(), - elements.len(), - ) - }; - unsafe { Self::from_metadata_ref(context, metadata) } - } } pub(crate) struct MetadataEntries { diff --git a/src/llvm/types/mod.rs b/src/llvm/types/mod.rs index f585bb04..9d230282 100644 --- a/src/llvm/types/mod.rs +++ b/src/llvm/types/mod.rs @@ -1,5 +1,7 @@ pub(super) mod context; +#[cfg(feature = "di-sanitizer")] pub(super) mod di; +#[cfg(feature = "di-sanitizer")] pub(super) mod ir; pub(super) mod memory_buffer; pub(super) mod module; diff --git a/tests/btf/assembly/anon_rust.rs b/tests/btf/assembly/anon_rust.rs deleted file mode 100644 index 945dfe42..00000000 --- a/tests/btf/assembly/anon_rust.rs +++ /dev/null @@ -1,34 +0,0 @@ -// assembly-output: bpf-linker -// compile-flags: --crate-type cdylib -C link-arg=--emit=obj -C link-arg=--btf -C debuginfo=2 - -#![no_std] - -use core::marker::PhantomData; - -#[repr(transparent)] -pub struct AyaBtfMapMarker(PhantomData<()>); - -pub struct Foo { - // Anonymize the stuct. - _anon: AyaBtfMapMarker, - - pub ayy: u32, - pub lmao: u32, -} - -#[no_mangle] -static FOO: Foo = Foo { - _anon: AyaBtfMapMarker(PhantomData), - - ayy: 0, - lmao: 0, -}; - -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - loop {} -} - -// CHECK: '' sz:8 n:2 -// CHECK-NEXT: 'ayy' off:0 --> [{{[0-9]+}}] -// CHECK-NEXT: 'lmao' off:32 --> [{{[0-9]+}}] diff --git a/tests/btf/assembly/auxiliary/loop-panic-handler.rs b/tests/btf/assembly/auxiliary/loop-panic-handler.rs deleted file mode 100644 index 76953359..00000000 --- a/tests/btf/assembly/auxiliary/loop-panic-handler.rs +++ /dev/null @@ -1,8 +0,0 @@ -// no-prefer-dynamic -// compile-flags: --crate-type rlib -#![no_std] - -#[panic_handler] -fn panic_impl(_: &core::panic::PanicInfo) -> ! { - loop {} -} diff --git a/tests/btf/assembly/auxiliary/loop-panic-handler.rs b/tests/btf/assembly/auxiliary/loop-panic-handler.rs new file mode 120000 index 00000000..add73340 --- /dev/null +++ b/tests/btf/assembly/auxiliary/loop-panic-handler.rs @@ -0,0 +1 @@ +../../../assembly/auxiliary/loop-panic-handler.rs \ No newline at end of file diff --git a/tests/sanitizer/assembly/auxiliary/dep-exports.rs b/tests/sanitizer/assembly/auxiliary/dep-exports.rs new file mode 120000 index 00000000..c082dcdb --- /dev/null +++ b/tests/sanitizer/assembly/auxiliary/dep-exports.rs @@ -0,0 +1 @@ +../../../assembly/auxiliary/dep-exports.rs \ No newline at end of file diff --git a/tests/sanitizer/assembly/auxiliary/loop-panic-handler.rs b/tests/sanitizer/assembly/auxiliary/loop-panic-handler.rs new file mode 120000 index 00000000..add73340 --- /dev/null +++ b/tests/sanitizer/assembly/auxiliary/loop-panic-handler.rs @@ -0,0 +1 @@ +../../../assembly/auxiliary/loop-panic-handler.rs \ No newline at end of file diff --git a/tests/assembly/di_generics.rs b/tests/sanitizer/assembly/di_generics.rs similarity index 100% rename from tests/assembly/di_generics.rs rename to tests/sanitizer/assembly/di_generics.rs diff --git a/tests/sanitizer/btf/assembly/auxiliary/dep-exports.rs b/tests/sanitizer/btf/assembly/auxiliary/dep-exports.rs new file mode 120000 index 00000000..38945ce4 --- /dev/null +++ b/tests/sanitizer/btf/assembly/auxiliary/dep-exports.rs @@ -0,0 +1 @@ +../../../../btf/assembly/auxiliary/dep-exports.rs \ No newline at end of file diff --git a/tests/sanitizer/btf/assembly/auxiliary/loop-panic-handler.rs b/tests/sanitizer/btf/assembly/auxiliary/loop-panic-handler.rs new file mode 120000 index 00000000..621d4bcf --- /dev/null +++ b/tests/sanitizer/btf/assembly/auxiliary/loop-panic-handler.rs @@ -0,0 +1 @@ +../../../../assembly/auxiliary/loop-panic-handler.rs \ No newline at end of file diff --git a/tests/btf/assembly/data-carrying-enum.rs b/tests/sanitizer/btf/assembly/data-carrying-enum.rs similarity index 100% rename from tests/btf/assembly/data-carrying-enum.rs rename to tests/sanitizer/btf/assembly/data-carrying-enum.rs diff --git a/tests/btf/assembly/exported-symbols.rs b/tests/sanitizer/btf/assembly/exported-symbols.rs similarity index 100% rename from tests/btf/assembly/exported-symbols.rs rename to tests/sanitizer/btf/assembly/exported-symbols.rs diff --git a/tests/tests.rs b/tests/tests.rs index 45fa4e08..c9687a08 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -167,6 +167,26 @@ fn compile_test() { cfg.llvm_filecheck_preprocess = Some(btf_dump); }), ); + #[cfg(feature = "di-sanitizer")] + { + run_mode( + target, + "assembly", + &bpf_sysroot, + Some(|cfg: &mut compiletest_rs::Config| { + cfg.src_base = PathBuf::from("tests/sanitizer/assembly"); + }), + ); + run_mode( + target, + "assembly", + &bpf_sysroot, + Some(|cfg: &mut compiletest_rs::Config| { + cfg.src_base = PathBuf::from("tests/sanitizer/btf"); + cfg.llvm_filecheck_preprocess = Some(btf_dump); + }), + ); + } // The `tests/nightly` directory contains tests which require unstable compiler // features through the `-Z` argument in `compile-flags`. if is_nightly() {