"""Symbol resolution using ELF/DWARF debug information. This module resolves function addresses to human-readable names, file locations, and line numbers using the ELF symbol table and DWARF debugging information. """ import logging from typing import Dict, Optional, Tuple from pathlib import Path from elftools.elf.elffile import ELFFile from elftools.dwarf.descriptions import describe_form_class from elftools.dwarf.die import DIE logger = logging.getLogger(__name__) class SymbolInfo: """Information about a symbol.""" def __init__(self, name: str, file: str = "", line: int = 0, size: int = 0): """Initialize symbol information. Args: name: Function or symbol name file: Source file path line: Line number in source file size: Symbol size in bytes """ self.name = name self.file = file self.line = line self.size = size def __repr__(self) -> str: if self.file and self.line: return f"{self.name} ({self.file}:{self.line})" return self.name class Symbolizer: """Resolves addresses to symbol names using ELF/DWARF information.""" def __init__(self, elf_path: str): """Initialize symbolizer with an ELF file. Args: elf_path: Path to the ELF file with debug symbols """ self.elf_path = Path(elf_path) self.symbols: Dict[int, SymbolInfo] = {} self.loaded = False if self.elf_path.exists(): self._load_symbols() else: logger.warning(f"ELF file not found: {elf_path}") def _load_symbols(self): """Load symbols from the ELF file.""" try: with open(self.elf_path, 'rb') as f: elffile = ELFFile(f) # Load symbol table self._load_symbol_table(elffile) # Load DWARF debug info for line numbers if elffile.has_dwarf_info(): self._load_dwarf_info(elffile) self.loaded = True logger.info(f"Loaded {len(self.symbols)} symbols from {self.elf_path}") except Exception as e: logger.error(f"Failed to load symbols from {self.elf_path}: {e}") self.loaded = False def _load_symbol_table(self, elffile: ELFFile): """Load function symbols from the symbol table. Args: elffile: Parsed ELF file object """ symtab = elffile.get_section_by_name('.symtab') if not symtab: logger.warning("No symbol table found in ELF file") return for symbol in symtab.iter_symbols(): # Only interested in function symbols if symbol['st_info']['type'] == 'STT_FUNC': addr = symbol['st_value'] name = symbol.name size = symbol['st_size'] if addr and name: self.symbols[addr] = SymbolInfo(name, size=size) logger.debug(f"Loaded {len(self.symbols)} function symbols from symbol table") def _load_dwarf_info(self, elffile: ELFFile): """Load DWARF debug information for file/line mappings. Args: elffile: Parsed ELF file object """ dwarfinfo = elffile.get_dwarf_info() # Process line number information for CU in dwarfinfo.iter_CUs(): lineprog = dwarfinfo.line_program_for_CU(CU) if not lineprog: continue # Get the file table file_entries = lineprog.header['file_entry'] # Process line program entries prevstate = None for entry in lineprog.get_entries(): if entry.state is None: continue # Look for end of sequence or new addresses state = entry.state if prevstate and state.address != prevstate.address: addr = prevstate.address file_index = prevstate.file - 1 if 0 <= file_index < len(file_entries): file_entry = file_entries[file_index] filename = file_entry.name.decode('utf-8') if isinstance( file_entry.name, bytes) else file_entry.name # Update existing symbol or create new one if addr in self.symbols: self.symbols[addr].file = filename self.symbols[addr].line = prevstate.line else: # Create placeholder symbol for addresses without symbol table entry self.symbols[addr] = SymbolInfo( f"func_0x{addr:08x}", file=filename, line=prevstate.line ) prevstate = state logger.debug("Loaded DWARF debug information") def resolve(self, addr: int) -> SymbolInfo: """Resolve an address to symbol information. Args: addr: Function address to resolve Returns: SymbolInfo object (may contain placeholder name if not found) """ # Exact match if addr in self.symbols: return self.symbols[addr] # Try to find the containing function (address within function range) for sym_addr, sym_info in self.symbols.items(): if sym_addr <= addr < sym_addr + sym_info.size: return sym_info # Not found - return placeholder return SymbolInfo(f"unknown_0x{addr:08x}") def resolve_name(self, addr: int) -> str: """Resolve an address to a function name. Args: addr: Function address Returns: Function name string """ return self.resolve(addr).name def resolve_location(self, addr: int) -> str: """Resolve an address to a file:line location string. Args: addr: Function address Returns: Location string in format "file:line" or empty string """ info = self.resolve(addr) if info.file and info.line: return f"{info.file}:{info.line}" return "" def get_all_symbols(self) -> Dict[int, SymbolInfo]: """Get all loaded symbols. Returns: Dictionary mapping addresses to SymbolInfo objects """ return self.symbols.copy()