use llvm_native_core::debug_info::{
DW_AT_location, DW_AT_name, DW_AT_type, DW_LANG_C_plus_plus, DW_TAG_compile_unit,
DW_TAG_lexical_block, DebugInfoBuilder, ExpressionBuilder, DW_LANG_C,
};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone)]
pub struct DILocation {
pub line: u32,
pub column: u32,
pub scope: Option<Box<DIScope>>,
pub inlined_at: Option<Box<DILocation>>,
pub is_implicit_code: bool,
}
impl DILocation {
pub fn new(line: u32, column: u32, scope: DIScope) -> Self {
DILocation {
line,
column,
scope: Some(Box::new(scope)),
inlined_at: None,
is_implicit_code: false,
}
}
pub fn with_inlined_at(mut self, loc: DILocation) -> Self {
self.inlined_at = Some(Box::new(loc));
self
}
pub fn get_scope(&self) -> Option<&DIScope> {
self.scope.as_ref().map(|s| s.as_ref())
}
pub fn get_inlined_at(&self) -> Option<&DILocation> {
self.inlined_at.as_ref().map(|l| l.as_ref())
}
pub fn get_line(&self) -> u32 {
self.line
}
pub fn get_column(&self) -> u32 {
self.column
}
}
impl fmt::Display for DILocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "line {} col {}", self.line, self.column)?;
if let Some(ref inlined) = self.inlined_at {
write!(f, " inlined from {}", inlined)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub enum DIScope {
CompileUnit(DICompileUnit),
File(DIFile),
Subprogram(DISubprogram),
LexicalBlock(DILexicalBlock),
LexicalBlockFile(DILexicalBlockFile),
Namespace(DINamespace),
Module(DIModule),
Type(DIType),
}
impl DIScope {
pub fn get_name(&self) -> Option<&str> {
match self {
DIScope::CompileUnit(cu) => cu.file.as_ref().map(|f| f.name.as_str()),
DIScope::File(f) => Some(&f.name),
DIScope::Subprogram(sp) => Some(&sp.name),
DIScope::LexicalBlock(lb) => lb.name.as_deref(),
DIScope::Namespace(ns) => Some(&ns.name),
DIScope::Module(m) => Some(&m.name),
_ => None,
}
}
pub fn is_subprogram(&self) -> bool {
matches!(self, DIScope::Subprogram(_))
}
pub fn is_compile_unit(&self) -> bool {
matches!(self, DIScope::CompileUnit(_))
}
pub fn is_lexical_block(&self) -> bool {
matches!(self, DIScope::LexicalBlock(_))
}
}
#[derive(Debug, Clone)]
pub struct DICompileUnit {
pub id: u64,
pub language: u16,
pub file: Option<DIFile>,
pub producer: String,
pub is_optimized: bool,
pub flags: String,
pub runtime_version: u32,
pub enum_types: Vec<u64>,
pub retained_types: Vec<u64>,
pub global_variables: Vec<u64>,
pub imported_entities: Vec<u64>,
pub macros: Vec<u64>,
pub split_debug_inlining: bool,
pub debug_info_for_profiling: bool,
pub name_table_kind: NameTableKind,
pub sysroot: String,
pub sdk: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NameTableKind {
None,
GNU,
Apple,
AppleAccel,
}
impl Default for DICompileUnit {
fn default() -> Self {
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
DICompileUnit {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
language: 0,
file: None,
producer: String::new(),
is_optimized: false,
flags: String::new(),
runtime_version: 0,
enum_types: Vec::new(),
retained_types: Vec::new(),
global_variables: Vec::new(),
imported_entities: Vec::new(),
macros: Vec::new(),
split_debug_inlining: false,
debug_info_for_profiling: false,
name_table_kind: NameTableKind::None,
sysroot: String::new(),
sdk: String::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct DIFile {
pub name: String,
pub directory: String,
pub checksum: Option<DIFileChecksum>,
pub source: Option<String>,
pub source_kind: SourceKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceKind {
Text,
Preprocessed,
Assembly,
Other,
}
#[derive(Debug, Clone)]
pub struct DIFileChecksum {
pub kind: ChecksumKind,
pub value: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChecksumKind {
MD5,
SHA1,
SHA256,
}
impl DIFile {
pub fn new(name: String, directory: String) -> Self {
DIFile {
name,
directory,
checksum: None,
source: None,
source_kind: SourceKind::Text,
}
}
pub fn get_filename(&self) -> String {
if self.directory.is_empty() {
self.name.clone()
} else {
format!("{}/{}", self.directory, self.name)
}
}
}
#[derive(Debug, Clone)]
pub struct DISubprogram {
pub id: u64,
pub name: String,
pub linkage_name: String,
pub file: Option<DIFile>,
pub line: u32,
pub ty: Option<DIType>,
pub scope: Option<Box<DIScope>>,
pub containing_type: Option<DIType>,
pub is_local: bool,
pub is_definition: bool,
pub virtual_index: u32,
pub this_adjustment: i32,
pub flags: DIFlags,
pub is_optimized: bool,
pub unit: Option<u64>,
pub template_params: Vec<u64>,
pub declaration: Option<u64>,
pub retained_nodes: Vec<u64>,
pub thrown_types: Vec<u64>,
}
impl Default for DISubprogram {
fn default() -> Self {
static NEXT_ID: AtomicU64 = AtomicU64::new(1000);
DISubprogram {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
name: String::new(),
linkage_name: String::new(),
file: None,
line: 0,
ty: None,
scope: None,
containing_type: None,
is_local: false,
is_definition: false,
virtual_index: 0,
this_adjustment: 0,
flags: DIFlags::default(),
is_optimized: false,
unit: None,
template_params: Vec::new(),
declaration: None,
retained_nodes: Vec::new(),
thrown_types: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct DILexicalBlock {
pub scope: Option<Box<DIScope>>,
pub file: Option<DIFile>,
pub line: u32,
pub column: u32,
pub name: Option<String>,
pub discriminator: u32,
}
#[derive(Debug, Clone)]
pub struct DILexicalBlockFile {
pub scope: Option<Box<DIScope>>,
pub file: DIFile,
pub discriminator: u32,
}
#[derive(Debug, Clone)]
pub struct DINamespace {
pub name: String,
pub scope: Option<Box<DIScope>>,
pub export_symbols: bool,
}
#[derive(Debug, Clone)]
pub struct DIModule {
pub name: String,
pub scope: Option<Box<DIScope>>,
pub configuration_macros: String,
pub include_path: String,
pub api_notes_file: String,
}
#[derive(Debug, Clone)]
pub enum DIType {
Basic(DIBasicType),
Derived(DIDerivedType),
Composite(DICompositeType),
Subroutine(DISubroutineType),
ForwardDeclaration(DIForwardDecl),
Unspecified,
}
#[derive(Debug, Clone)]
pub struct DIBasicType {
pub name: String,
pub size_in_bits: u64,
pub encoding: u32,
pub flags: DIFlags,
}
#[derive(Debug, Clone)]
pub struct DIDerivedType {
pub tag: u16,
pub name: String,
pub base_type: Option<Box<DIType>>,
pub size_in_bits: u64,
pub align_in_bits: u64,
pub offset_in_bits: u64,
pub flags: DIFlags,
pub extra_data: Option<u64>,
pub dwarf_address_space: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct DICompositeType {
pub tag: u16,
pub name: String,
pub file: Option<DIFile>,
pub line: u32,
pub scope: Option<Box<DIScope>>,
pub base_type: Option<Box<DIType>>,
pub size_in_bits: u64,
pub align_in_bits: u64,
pub offset_in_bits: u64,
pub flags: DIFlags,
pub elements: Vec<u64>,
pub runtime_lang: u32,
pub vtable_holder: Option<Box<DIType>>,
pub template_params: Vec<u64>,
pub identifier: String,
pub discriminator: Option<u64>,
pub data_location: Option<Box<DIExpression>>,
pub associated: Option<Box<DIExpression>>,
pub allocated: Option<Box<DIExpression>>,
pub rank: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct DISubroutineType {
pub flags: DIFlags,
pub cc: u32,
pub types: Vec<DIType>,
}
#[derive(Debug, Clone)]
pub struct DIForwardDecl {
pub tag: u16,
pub name: String,
pub file: Option<DIFile>,
pub line: u32,
pub scope: Option<Box<DIScope>>,
pub identifier: String,
pub flags: DIFlags,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DIFlags {
pub is_private: bool,
pub is_protected: bool,
pub is_public: bool,
pub is_explicit: bool,
pub is_prototyped: bool,
pub is_artificial: bool,
pub is_virtual: bool,
pub is_pure_virtual: bool,
pub is_optimized: bool,
pub is_main_subprogram: bool,
pub is_definition: bool,
pub is_export_symbols: bool,
pub is_imported: bool,
pub is_rvalue_reference: bool,
pub is_big_endian: bool,
pub is_little_endian: bool,
pub is_trivial: bool,
pub is_pass_by_value: bool,
pub is_noderef: bool,
pub is_object_pointer: bool,
pub is_static_data_member: bool,
pub is_indirect_virtual_base: bool,
pub is_fixed_enum: bool,
}
impl DIFlags {
pub fn apply(&self, _tag: u16) {}
}
#[derive(Debug, Clone)]
pub struct DILocalVariable {
pub name: String,
pub scope: Option<Box<DIScope>>,
pub file: Option<DIFile>,
pub line: u32,
pub ty: Option<DIType>,
pub arg: u32, pub flags: DIFlags,
pub align_in_bits: u32,
}
impl DILocalVariable {
pub fn new(name: String, ty: DIType, arg: u32) -> Self {
DILocalVariable {
name,
scope: None,
file: None,
line: 0,
ty: Some(ty),
arg,
flags: DIFlags::default(),
align_in_bits: 0,
}
}
pub fn with_scope(mut self, scope: DIScope) -> Self {
self.scope = Some(Box::new(scope));
self
}
pub fn with_file(mut self, file: DIFile) -> Self {
self.file = Some(file);
self
}
pub fn with_line(mut self, line: u32) -> Self {
self.line = line;
self
}
pub fn is_parameter(&self) -> bool {
self.arg > 0
}
}
#[derive(Debug, Clone)]
pub struct DIGlobalVariable {
pub name: String,
pub linkage_name: String,
pub scope: Option<Box<DIScope>>,
pub file: Option<DIFile>,
pub line: u32,
pub ty: Option<DIType>,
pub is_local: bool,
pub is_definition: bool,
pub declaration: Option<u64>,
pub align_in_bits: u32,
}
#[derive(Debug, Clone)]
pub struct DIGlobalVariableExpression {
pub variable: u64,
pub expression: Option<Box<DIExpression>>,
}
#[derive(Debug, Clone)]
pub struct DIExpression {
pub operations: Vec<DIExpressionOp>,
pub num_elements: usize,
}
#[derive(Debug, Clone)]
pub enum DIExpressionOp {
Simple(u8),
UnsignedArg(u8, u64),
SignedArg(u8, i64),
Fragment(u64, u64),
}
impl DIExpression {
pub fn new() -> Self {
DIExpression {
operations: Vec::new(),
num_elements: 0,
}
}
pub fn from_opcodes(ops: &[u8]) -> Self {
let mut expr = DIExpression::new();
for &op in ops {
expr.operations.push(DIExpressionOp::Simple(op));
expr.num_elements += 1;
}
expr
}
pub fn append_op(&mut self, op: DIExpressionOp) {
self.num_elements += 1;
self.operations.push(op);
}
pub fn append_fragment(&mut self, offset_in_bits: u64, size_in_bits: u64) {
self.operations
.push(DIExpressionOp::Fragment(offset_in_bits, size_in_bits));
self.num_elements += 1;
}
pub fn is_empty(&self) -> bool {
self.operations.is_empty()
}
pub fn is_complex(&self) -> bool {
self.operations.len() > 1
}
pub fn get_fragment_info(&self) -> Option<(u64, u64)> {
for op in &self.operations {
if let DIExpressionOp::Fragment(off, size) = op {
return Some((*off, *size));
}
}
None
}
pub fn create_fragment(offset_in_bits: u64, size_in_bits: u64) -> Self {
let mut expr = DIExpression::new();
expr.append_fragment(offset_in_bits, size_in_bits);
expr
}
pub fn prepend(&mut self, other: &DIExpression) {
let mut new_ops = other.operations.clone();
new_ops.extend(self.operations.clone());
self.operations = new_ops;
self.num_elements = self.operations.len();
}
pub fn append(&mut self, other: &DIExpression) {
self.operations.extend(other.operations.clone());
self.num_elements = self.operations.len();
}
}
impl Default for DIExpression {
fn default() -> Self {
DIExpression::new()
}
}
#[derive(Debug, Clone)]
pub struct DIMacro {
pub ty: MacroType,
pub line: u32,
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MacroType {
Define = 0x01,
Undef = 0x02,
StartFile = 0x03,
EndFile = 0x04,
DefineStmt = 0x05,
UndefStmt = 0x06,
DefineIndirect = 0x07,
UndefIndirect = 0x08,
TransparentInclude = 0x09,
}
#[derive(Debug, Clone)]
pub struct DIMacroFile {
pub file: Option<DIFile>,
pub line: u32,
pub macros: Vec<u64>,
}
#[derive(Debug, Clone)]
pub struct DIImportedEntity {
pub tag: u16,
pub scope: Option<Box<DIScope>>,
pub entity: Option<u64>,
pub file: Option<DIFile>,
pub line: u32,
pub name: String,
}
#[derive(Debug, Clone)]
pub struct DITemplateTypeParameter {
pub name: String,
pub ty: Option<DIType>,
}
#[derive(Debug, Clone)]
pub struct DITemplateValueParameter {
pub name: String,
pub ty: Option<DIType>,
pub value: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DebugInfoKind {
None,
LineTablesOnly,
Full,
Limited,
ConstructorHoming,
}
pub struct DebugInfoStripper {
pub keep_kind: DebugInfoKind,
pub keep_functions: HashSet<String>,
}
impl DebugInfoStripper {
pub fn new(kind: DebugInfoKind) -> Self {
DebugInfoStripper {
keep_kind: kind,
keep_functions: HashSet::new(),
}
}
pub fn keep_for_function(&self, name: &str, _subprogram: &DISubprogram) -> DebugInfoKind {
if self.keep_functions.contains(name) {
return DebugInfoKind::Full;
}
self.keep_kind
}
pub fn strip_subprogram(&self, sub: &mut DISubprogram, kind: DebugInfoKind) {
match kind {
DebugInfoKind::None => {
sub.name.clear();
sub.linkage_name.clear();
sub.file = None;
}
DebugInfoKind::LineTablesOnly => {
sub.linkage_name.clear();
sub.ty = None;
}
DebugInfoKind::Limited => {
sub.linkage_name.clear();
}
DebugInfoKind::Full | DebugInfoKind::ConstructorHoming => {
}
}
}
}
#[derive(Debug, Clone)]
pub struct DebugValue {
pub variable: DILocalVariable,
pub expression: DIExpression,
pub location: DILocation,
pub value: DebugValueKind,
}
#[derive(Debug, Clone)]
pub enum DebugValueKind {
Register(u32),
Memory(u64),
Constant(i64),
Undef,
Unknown,
}
pub struct DebugValueTracker {
pub values: HashMap<String, Vec<DebugValue>>,
}
impl DebugValueTracker {
pub fn new() -> Self {
DebugValueTracker {
values: HashMap::new(),
}
}
pub fn record_declare(
&mut self,
variable: DILocalVariable,
expression: DIExpression,
location: DILocation,
reg: u32,
) {
let key = variable.name.clone();
self.values.entry(key).or_default().push(DebugValue {
variable,
expression,
location,
value: DebugValueKind::Register(reg),
});
}
pub fn record_value(
&mut self,
variable: DILocalVariable,
expression: DIExpression,
location: DILocation,
value: DebugValueKind,
) {
let key = variable.name.clone();
self.values.entry(key).or_default().push(DebugValue {
variable,
expression,
location,
value,
});
}
pub fn get_values(&self, var_name: &str) -> Option<&Vec<DebugValue>> {
self.values.get(var_name)
}
pub fn clear(&mut self) {
self.values.clear();
}
}
impl Default for DebugValueTracker {
fn default() -> Self {
DebugValueTracker::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_di_location() {
let file = DIFile::new("test.c".to_string(), "/src".to_string());
let scope = DIScope::File(file);
let loc = DILocation::new(10, 5, scope);
assert_eq!(loc.get_line(), 10);
assert_eq!(loc.get_column(), 5);
}
#[test]
fn test_di_compile_unit() {
let mut cu = DICompileUnit::default();
cu.language = 12; cu.producer = "llvm-native 1.0".to_string();
assert_eq!(cu.language, 12);
}
#[test]
fn test_di_expression_fragment() {
let expr = DIExpression::create_fragment(0, 32);
assert!(expr.is_complex());
let (off, size) = expr.get_fragment_info().unwrap();
assert_eq!(off, 0);
assert_eq!(size, 32);
}
#[test]
fn test_di_expression_prepend() {
let mut expr = DIExpression::create_fragment(32, 32);
let prefix = DIExpression::from_opcodes(&[0x70]); expr.prepend(&prefix);
assert!(expr.is_complex());
}
#[test]
fn test_difile_get_filename() {
let file = DIFile::new("main.c".to_string(), "/home/user".to_string());
assert_eq!(file.get_filename(), "/home/user/main.c");
}
#[test]
fn test_di_local_variable() {
let basic = DIType::Basic(DIBasicType {
name: "int".to_string(),
size_in_bits: 32,
encoding: 5, flags: DIFlags::default(),
});
let var = DILocalVariable::new("x".to_string(), basic, 1);
assert!(var.is_parameter());
assert_eq!(var.name, "x");
}
#[test]
fn test_debug_value_tracker() {
let mut tracker = DebugValueTracker::new();
let var = DILocalVariable::new("count".to_string(), DIType::Unspecified, 0);
let expr = DIExpression::new();
let loc = DILocation::new(
42,
1,
DIScope::File(DIFile::new("f.c".to_string(), ".".to_string())),
);
tracker.record_declare(var, expr, loc, 5);
assert!(tracker.get_values("count").is_some());
}
#[test]
fn test_debug_info_stripper() {
let mut stripper = DebugInfoStripper::new(DebugInfoKind::LineTablesOnly);
stripper.keep_functions.insert("important_fn".to_string());
assert_eq!(
stripper.keep_for_function("important_fn", &DISubprogram::default()),
DebugInfoKind::Full
);
assert_eq!(
stripper.keep_for_function("boring_fn", &DISubprogram::default()),
DebugInfoKind::LineTablesOnly
);
}
#[test]
fn test_di_scope_name() {
let ns = DIScope::Namespace(DINamespace {
name: "std".to_string(),
scope: None,
export_symbols: true,
});
assert_eq!(ns.get_name(), Some("std"));
}
#[test]
fn test_di_flags() {
let mut flags = DIFlags::default();
flags.is_private = true;
flags.is_virtual = true;
assert!(flags.is_private);
assert!(flags.is_virtual);
assert!(!flags.is_protected);
}
}