| #------------------------------------------------------------------------------- |
| # 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 elftools is not installed, maybe we're running from the root or examples |
| # dir of the source distribution |
| try: |
| import elftools |
| except ImportError: |
| sys.path.extend(['.', '..']) |
| |
| from elftools.common.py3compat import maxint, bytes2str |
| 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 |
| highpc = DIE.attributes['DW_AT_high_pc'].value |
| 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) |
| prevaddr = maxint |
| for entry in lineprog.get_entries(): |
| # We're interested in those entries where a new state is assigned |
| state = entry.state |
| if state is not None and not state.end_sequence: |
| if prevaddr <= address <= state.address: |
| filename = lineprog['file_entry'][state.file - 1].name |
| line = state.line |
| return filename, line |
| prevaddr = state.address |
| return None, None |
| |
| |
| if __name__ == '__main__': |
| for filename in sys.argv[1:]: |
| # For testing we use a hardcoded address. |
| process_file(filename, 0x400503) |
| |