Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 106 additions & 9 deletions src/llvm/di.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ use std::{
ptr,
};

use gimli::{DW_TAG_pointer_type, DW_TAG_structure_type, DW_TAG_variant_part};
use gimli::{
DW_TAG_enumeration_type, DW_TAG_pointer_type, DW_TAG_structure_type, DW_TAG_variant_part,
};
use llvm_sys::{core::*, debuginfo::*, prelude::*};
use tracing::{span, trace, warn, Level};

use super::types::{
di::DIType,
di::{DIBasicType, DIBasicTypeKind, DICompileUnit, DIType},
ir::{Function, MDNode, Metadata, Value},
};
use crate::llvm::{iter::*, types::di::DISubprogram};
Expand All @@ -21,13 +23,14 @@ use crate::llvm::{iter::*, types::di::DISubprogram};
// backward compatibility
const MAX_KSYM_NAME_LEN: usize = 128;

pub struct DISanitizer {
pub struct DISanitizer<'ctx> {
context: LLVMContextRef,
module: LLVMModuleRef,
builder: LLVMDIBuilderRef,
visited_nodes: HashSet<u64>,
replace_operands: HashMap<u64, LLVMMetadataRef>,
skipped_types: Vec<String>,
basic_types: HashMap<DIBasicTypeKind, DIBasicType<'ctx>>,
}

// Sanitize Rust type names to be valid C type names.
Expand Down Expand Up @@ -58,24 +61,56 @@ fn sanitize_type_name<T: AsRef<str>>(name: T) -> String {
n
}

impl DISanitizer {
pub fn new(context: LLVMContextRef, module: LLVMModuleRef) -> DISanitizer {
/// [`LLVMGetMetadataKind`] wrapper
fn kind(m: LLVMMetadataRef) -> LLVMMetadataKind {
unsafe { LLVMGetMetadataKind(m) }
}

impl<'ctx> DISanitizer<'_> {
pub fn new(context: LLVMContextRef, module: LLVMModuleRef) -> DISanitizer<'ctx> {
DISanitizer {
context,
module,
builder: unsafe { LLVMCreateDIBuilder(module) },
visited_nodes: HashSet::new(),
replace_operands: HashMap::new(),
skipped_types: Vec::new(),
basic_types: HashMap::new(),
}
}

fn visit_mdnode(&mut self, mdnode: MDNode) {
/// Returns a [`DIBasicType`] given a [`DIBasicTypeKind`].
fn di_basic_type(&'ctx mut self, di_bt: DIBasicTypeKind) -> &'ctx DIBasicType<'ctx> {
self.basic_types
.entry(di_bt)
.or_insert_with(|| DIBasicType::llvm_create(self.context, self.builder, di_bt))
}

fn visit_mdnode_item(&mut self, item: &mut Item) {
let Some(mdnode) = item.as_mdnode() else {
return;
};

match mdnode.try_into().expect("MDNode is not Metadata") {
Metadata::DICompositeType(mut di_composite_type) => {
#[allow(clippy::single_match)]
#[allow(non_upper_case_globals)]
match di_composite_type.tag() {
DW_TAG_enumeration_type => {
if let Some(name) = di_composite_type.name() {
// we found the c_void enum
if name == c"c_void" && di_composite_type.size_in_bits() == 8 {
if let Item::Operand(ref mut op) = item {
// get i8 DIBasicType
let i8_bt = self.di_basic_type(DIBasicTypeKind::I8);
op.replace(i8_bt.value_ref);
} else {
// c_void enum is not an Item::Operand so we cannot replace it
warn!("failed at replacing c_void enum, it might result in BTF parsing errors in kernels < 5.4")
}
}
}
}
DW_TAG_structure_type => {
let names = match di_composite_type.name() {
Some(name) => {
Expand Down Expand Up @@ -247,9 +282,7 @@ impl DISanitizer {
return;
}

if let Value::MDNode(mdnode) = value.clone() {
self.visit_mdnode(mdnode)
}
self.visit_mdnode_item(&mut item);

if let Some(operands) = value.operands() {
for (index, operand) in operands.enumerate() {
Expand Down Expand Up @@ -306,6 +339,8 @@ impl DISanitizer {
);
}

self.fix_di_compile_units();

unsafe { LLVMDisposeDIBuilder(self.builder) };
}

Expand Down Expand Up @@ -404,6 +439,58 @@ impl DISanitizer {

replace
}

fn di_compile_units(&self) -> Vec<DICompileUnit> {
let compile_unit_name = c"llvm.dbg.cu";

// Get the number of DICompileUnit.
let num_di_cu =
unsafe { LLVMGetNamedMetadataNumOperands(self.module, compile_unit_name.as_ptr()) };

// Create a vector to hold them all.
let mut di_cus: Vec<LLVMValueRef> = vec![core::ptr::null_mut(); num_di_cu as usize];

unsafe {
LLVMGetNamedMetadataOperands(
self.module,
compile_unit_name.as_ptr(),
di_cus.as_mut_ptr(),
)
};

di_cus
.into_iter()
.map(|v| unsafe { DICompileUnit::from_value_ref(v) })
.collect()
}

/// Removes replaced `c_void` Rust enum from all [`DICompileUnit`]. After
/// replacing `c_void` enum with a [`DIBasicType`] the [`DICompileUnit`] still
/// hold a reference to the enum and still believes it is a [`DICompositeType`]
/// which triggers a casting assertion in LLVM.
fn fix_di_compile_units(&mut self) {
for mut di_cu in self.di_compile_units() {
let tmp_cu = di_cu.clone();
let need_replace = tmp_cu.enum_types().any(|ct| {
matches!(
kind(ct.metadata_ref),
LLVMMetadataKind::LLVMDIBasicTypeMetadataKind
)
});

if need_replace {
di_cu.replace_enum_types(
self.builder,
tmp_cu.enum_types().filter(|ct| {
!matches!(
kind(ct.metadata_ref),
LLVMMetadataKind::LLVMDIBasicTypeMetadataKind
)
}),
);
}
}
}
}

#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -438,6 +525,16 @@ impl Operand {
}

impl Item {
/// Returns the [`Item`] as [`MDNode`] only if [`Item::is_mdnode`] is `true` else `None`.
fn as_mdnode(&self) -> Option<MDNode<'_>> {
let is_mdnode = unsafe { !LLVMIsAMDNode(self.value_ref()).is_null() };
if is_mdnode {
Some(unsafe { MDNode::from_value_ref(self.value_ref()) })
} else {
None
}
}

fn value_ref(&self) -> LLVMValueRef {
match self {
Item::GlobalVariable(value)
Expand Down
157 changes: 151 additions & 6 deletions src/llvm/types/di.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ use std::{

use gimli::DwTag;
use llvm_sys::{
core::{LLVMGetNumOperands, LLVMGetOperand, LLVMReplaceMDNodeOperandWith, LLVMValueAsMetadata},
core::{
LLVMGetNumOperands, LLVMGetOperand, LLVMMetadataAsValue, LLVMReplaceMDNodeOperandWith,
LLVMValueAsMetadata,
},
debuginfo::{
LLVMDIFileGetFilename, LLVMDIFlags, LLVMDIScopeGetFile, LLVMDISubprogramGetLine,
LLVMDITypeGetFlags, LLVMDITypeGetLine, LLVMDITypeGetName, LLVMDITypeGetOffsetInBits,
LLVMGetDINodeTag,
LLVMDIBuilderCreateBasicType, LLVMDIBuilderGetOrCreateTypeArray, LLVMDIFileGetFilename,
LLVMDIFlags, LLVMDIScopeGetFile, LLVMDISubprogramGetLine, LLVMDITypeGetFlags,
LLVMDITypeGetLine, LLVMDITypeGetName, LLVMDITypeGetOffsetInBits, LLVMDITypeGetSizeInBits,
LLVMDWARFTypeEncoding, LLVMGetDINodeTag,
},
prelude::{LLVMContextRef, LLVMMetadataRef, LLVMValueRef},
prelude::{LLVMContextRef, LLVMDIBuilderRef, LLVMMetadataRef, LLVMValueRef},
};

use crate::llvm::{
Expand Down Expand Up @@ -221,7 +225,7 @@ enum DICompositeTypeOperand {
/// Composite type is a kind of type that can include other types, such as
/// structures, enums, unions, etc.
pub struct DICompositeType<'ctx> {
metadata_ref: LLVMMetadataRef,
pub(crate) metadata_ref: LLVMMetadataRef,
value_ref: LLVMValueRef,
_marker: PhantomData<&'ctx ()>,
}
Expand Down Expand Up @@ -308,6 +312,11 @@ impl DICompositeType<'_> {
pub fn tag(&self) -> DwTag {
unsafe { di_node_tag(self.metadata_ref) }
}

/// Returns the size in bits of the composite type.
pub fn size_in_bits(&self) -> u64 {
unsafe { LLVMDITypeGetSizeInBits(LLVMValueAsMetadata(self.value_ref)) }
}
}

/// Represents the operands for a [`DISubprogram`]. The enum values correspond
Expand Down Expand Up @@ -430,3 +439,139 @@ impl DISubprogram<'_> {
};
}
}

/// Represents the operands for a [`DICompileUnit`]. The enum values correspond
/// to the operand indices within metadata nodes.
#[repr(u32)]
enum DICompileUnitOperand {
EnumTypes = 4,
}

/// Represents the debug information for a compile unit in LLVM IR.
#[derive(Clone)]
pub struct DICompileUnit<'ctx> {
value_ref: LLVMValueRef,
_marker: PhantomData<&'ctx ()>,
}

impl<'ctx> DICompileUnit<'ctx> {
/// Constructs a new [`DICompileUnit`] from the given `value_ref`.
///
/// # Safety
///
/// This method assumes that the provided `value_ref` corresponds to a valid
/// instance of [LLVM `DICompileUnit`](https://llvm.org/doxygen/classllvm_1_1DICompileUnit.html).
/// It's the caller's responsibility to ensure this invariant, as this
/// method doesn't perform any valiation checks.
pub(crate) unsafe fn from_value_ref(value_ref: LLVMValueRef) -> Self {
Self {
value_ref,
_marker: PhantomData,
}
}

pub fn enum_types(&self) -> impl Iterator<Item = DICompositeType> {
let llvm_enum_types =
unsafe { LLVMGetOperand(self.value_ref, DICompileUnitOperand::EnumTypes as u32) };

let llvm_enum_types_len = if llvm_enum_types.is_null() {
0
} else {
unsafe { LLVMGetNumOperands(llvm_enum_types) }
};

(0..llvm_enum_types_len).map(move |i| unsafe {
let enum_type = LLVMGetOperand(llvm_enum_types, i as u32);
DICompositeType::from_value_ref(enum_type)
})
}

pub fn replace_enum_types<I>(&mut self, builder: LLVMDIBuilderRef, rep: I)
where
I: IntoIterator<Item = DICompositeType<'ctx>>,
{
let mut rep: Vec<_> = rep.into_iter().map(|dct| dct.metadata_ref).collect();

unsafe {
let enum_array =
LLVMDIBuilderGetOrCreateTypeArray(builder, rep.as_mut_ptr(), rep.len());
LLVMReplaceMDNodeOperandWith(
self.value_ref,
DICompileUnitOperand::EnumTypes as u32,
enum_array,
);
}
}
}

/// Represents [`DIBasicType`] kinds.
#[derive(Hash, PartialEq, Eq, Clone, Copy)]
pub enum DIBasicTypeKind {
I8,
}

impl DIBasicTypeKind {
fn name(&self) -> &'static str {
match self {
Self::I8 => "i8",
}
}

fn size_in_bits(&self) -> u64 {
match self {
Self::I8 => 8,
}
}

// DWARF encoding https://llvm.org/docs/LangRef.html#dibasictype
fn dwarf_type_encoding(&self) -> LLVMDWARFTypeEncoding {
match self {
// DW_ATE_signed
Self::I8 => gimli::DW_ATE_signed.0.into(),
}
}
}

/// Represents the debug information for a basic type in LLVM IR.
pub struct DIBasicType<'ctx> {
pub(crate) value_ref: LLVMValueRef,
_marker: PhantomData<&'ctx ()>,
}

impl DIBasicType<'_> {
/// Constructs a new [`DIBasicType`] from the given `value_ref`.
///
/// # Safety
///
/// This method assumes that the provided `value_ref` corresponds to a valid
/// instance of [LLVM `DIBasicType`](https://llvm.org/doxygen/classllvm_1_1DIBasicType.html).
/// It's the caller's responsibility to ensure this invariant, as this
/// method doesn't perform any valiation checks.
pub(crate) unsafe fn from_value_ref(value_ref: LLVMValueRef) -> Self {
Self {
value_ref,
_marker: PhantomData,
}
}

/// Creates a new [`DIBasicType`] of `kind` given a `context` and a `builder`.
pub fn llvm_create(
ctx: LLVMContextRef,
builder: LLVMDIBuilderRef,
kind: DIBasicTypeKind,
) -> Self {
let name = kind.name();
let metadata_ref = unsafe {
LLVMDIBuilderCreateBasicType(
builder,
name.as_ptr() as *const _,
name.len(),
kind.size_in_bits(),
kind.dwarf_type_encoding(),
0,
)
};

unsafe { Self::from_value_ref(LLVMMetadataAsValue(ctx, metadata_ref)) }
}
}
Loading