blob: 7b7d3e0186c09314652c7aed33db6aa7559d2f48 [file] [log] [blame]
#-------------------------------------------------------------------------------
# elftools example: dwarf_decode_address.py
#
# Decode an address in an ELF file to find out which function it belongs to
# and from which filename/line it comes in the original source file.
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import print_function
import sys
# If pyelftools is not installed, the example can also run from the root or
# examples/ dir of the source distribution.
sys.path[0:0] = ['.', '..']
from elftools.common.py3compat import maxint, bytes2str
from elftools.dwarf.descriptions import describe_form_class
from elftools.elf.elffile import ELFFile
def process_file(filename, address):
print('Processing file:', filename)
with open(filename, 'rb') as f:
elffile = ELFFile(f)
if not elffile.has_dwarf_info():
print(' file has no DWARF info')
return
# get_dwarf_info returns a DWARFInfo context object, which is the
# starting point for all DWARF-based processing in pyelftools.
dwarfinfo = elffile.get_dwarf_info()
funcname = decode_funcname(dwarfinfo, address)
file, line = decode_file_line(dwarfinfo, address)
print('Function:', bytes2str(funcname))
print('File:', bytes2str(file))
print('Line:', line)
def decode_funcname(dwarfinfo, address):
# Go over all DIEs in the DWARF information, looking for a subprogram
# entry with an address range that includes the given address. Note that
# this simplifies things by disregarding subprograms that may have
# split address ranges.
for CU in dwarfinfo.iter_CUs():
for DIE in CU.iter_DIEs():
try:
if DIE.tag == 'DW_TAG_subprogram':
lowpc = DIE.attributes['DW_AT_low_pc'].value
# DWARF v4 in section 2.17 describes how to interpret the
# DW_AT_high_pc attribute based on the class of its form.
# For class 'address' it's taken as an absolute address
# (similarly to DW_AT_low_pc); for class 'constant', it's
# an offset from DW_AT_low_pc.
highpc_attr = DIE.attributes['DW_AT_high_pc']
highpc_attr_class = describe_form_class(highpc_attr.form)
if highpc_attr_class == 'address':
highpc = highpc_attr.value
elif highpc_attr_class == 'constant':
highpc = lowpc + highpc_attr.value
else:
print('Error: invalid DW_AT_high_pc class:',
highpc_attr_class)
continue
if lowpc <= address <= highpc:
return DIE.attributes['DW_AT_name'].value
except KeyError:
continue
return None
def decode_file_line(dwarfinfo, address):
# Go over all the line programs in the DWARF information, looking for
# one that describes the given address.
for CU in dwarfinfo.iter_CUs():
# First, look at line programs to find the file/line for the address
lineprog = dwarfinfo.line_program_for_CU(CU)
prevstate = None
for entry in lineprog.get_entries():
# We're interested in those entries where a new state is assigned
if entry.state is None or entry.state.end_sequence:
continue
# Looking for a range of addresses in two consecutive states that
# contain the required address.
if prevstate and prevstate.address <= address < entry.state.address:
filename = lineprog['file_entry'][prevstate.file - 1].name
line = prevstate.line
return filename, line
prevstate = entry.state
return None, None
if __name__ == '__main__':
if sys.argv[1] == '--test':
process_file(sys.argv[2], 0x400503)
sys.exit(0)
if len(sys.argv) < 3:
print('Expected usage: {0} <address> <executable>'.format(sys.argv[0]))
sys.exit(1)
addr = int(sys.argv[1], 0)
process_file(sys.argv[2], addr)