- Contains the host code with a protocol implementation, data analyser and web-based visualiser
206 lines
6.5 KiB
Python
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()
|