Files
MiniProfiler/host/miniprofiler/symbolizer.py
Atharva Sawant 852957a7de Initialized MiniProfiler project
- Contains the host code with a protocol implementation, data analyser and web-based visualiser
2025-11-27 20:34:41 +05:30

206 lines
6.5 KiB
Python

"""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()