Initialized MiniProfiler project
- Contains the host code with a protocol implementation, data analyser and web-based visualiser
This commit is contained in:
205
host/miniprofiler/symbolizer.py
Normal file
205
host/miniprofiler/symbolizer.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user