Fix for mixed version loclists, tests (#521)
[pyelftools.git] / examples / dwarf_lineprogram_filenames.py
1 #-------------------------------------------------------------------------------
2 # elftools example: dwarf_lineprogram_filenames.py
3 #
4 # In the .debug_line section, the Dwarf line program generates a matrix
5 # of address-source references. This example demonstrates accessing the state
6 # of each line program entry to retrieve the underlying filenames.
7 #
8 # William Woodruff (william@yossarian.net)
9 # This code is in the public domain
10 #-------------------------------------------------------------------------------
11 from __future__ import print_function
12 from collections import defaultdict
13 import os
14 import sys
15 import posixpath
16
17 # If pyelftools is not installed, the example can also run from the root or
18 # examples/ dir of the source distribution.
19 sys.path[0:0] = ['.', '..']
20
21 from elftools.elf.elffile import ELFFile
22
23
24 def process_file(filename):
25 print('Processing file:', filename)
26 with open(filename, 'rb') as f:
27 elffile = ELFFile(f)
28
29 if not elffile.has_dwarf_info():
30 print(' file has no DWARF info')
31 return
32
33 dwarfinfo = elffile.get_dwarf_info()
34 for CU in dwarfinfo.iter_CUs():
35 print(' Found a compile unit at offset %s, length %s' % (
36 CU.cu_offset, CU['unit_length']))
37
38 # Every compilation unit in the DWARF information may or may not
39 # have a corresponding line program in .debug_line.
40 line_program = dwarfinfo.line_program_for_CU(CU)
41 if line_program is None:
42 print(' DWARF info is missing a line program for this CU')
43 continue
44
45 # Print a reverse mapping of filename -> #entries
46 line_entry_mapping(line_program)
47
48
49 def line_entry_mapping(line_program):
50 filename_map = defaultdict(int)
51
52 # The line program, when decoded, returns a list of line program
53 # entries. Each entry contains a state, which we'll use to build
54 # a reverse mapping of filename -> #entries.
55 lp_entries = line_program.get_entries()
56 for lpe in lp_entries:
57 # We skip LPEs that don't have an associated file.
58 # This can happen if instructions in the compiled binary
59 # don't correspond directly to any original source file.
60 if not lpe.state or lpe.state.file == 0:
61 continue
62 filename = lpe_filename(line_program, lpe.state.file)
63 filename_map[filename] += 1
64
65 for filename, lpe_count in filename_map.items():
66 print(" filename=%s -> %d entries" % (filename, lpe_count))
67
68
69 def lpe_filename(line_program, file_index):
70 # Retrieving the filename associated with a line program entry
71 # involves two levels of indirection: we take the file index from
72 # the LPE to grab the file_entry from the line program header,
73 # then take the directory index from the file_entry to grab the
74 # directory name from the line program header. Finally, we
75 # join the (base) filename from the file_entry to the directory
76 # name to get the absolute filename.
77 lp_header = line_program.header
78 file_entries = lp_header["file_entry"]
79
80 # File and directory indices are 1-indexed.
81 file_entry = file_entries[file_index - 1]
82 dir_index = file_entry["dir_index"]
83
84 # A dir_index of 0 indicates that no absolute directory was recorded during
85 # compilation; return just the basename.
86 if dir_index == 0:
87 return file_entry.name.decode()
88
89 directory = lp_header["include_directory"][dir_index - 1]
90 return posixpath.join(directory, file_entry.name).decode()
91
92
93 if __name__ == '__main__':
94 if sys.argv[1] == '--test':
95 for filename in sys.argv[2:]:
96 process_file(filename)