1 #-------------------------------------------------------------------------------
2 # elftools example: dwarf_lineprogram_filenames.py
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.
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
16 # If pyelftools is not installed, the example can also run from the root or
17 # examples/ dir of the source distribution.
18 sys
.path
[0:0] = ['.', '..']
20 from elftools
.elf
.elffile
import ELFFile
23 def process_file(filename
):
24 print('Processing file:', filename
)
25 with
open(filename
, 'rb') as f
:
28 if not elffile
.has_dwarf_info():
29 print(' file has no DWARF info')
32 dwarfinfo
= elffile
.get_dwarf_info()
33 for CU
in dwarfinfo
.iter_CUs():
34 print(' Found a compile unit at offset %s, length %s' % (
35 CU
.cu_offset
, CU
['unit_length']))
37 # Every compilation unit in the DWARF information may or may not
38 # have a corresponding line program in .debug_line.
39 line_program
= dwarfinfo
.line_program_for_CU(CU
)
40 if line_program
is None:
41 print(' DWARF info is missing a line program for this CU')
44 # Print a reverse mapping of filename -> #entries
45 line_entry_mapping(line_program
)
48 def line_entry_mapping(line_program
):
49 filename_map
= defaultdict(int)
51 # The line program, when decoded, returns a list of line program
52 # entries. Each entry contains a state, which we'll use to build
53 # a reverse mapping of filename -> #entries.
54 lp_entries
= line_program
.get_entries()
55 for lpe
in lp_entries
:
56 # We skip LPEs that don't have an associated file.
57 # This can happen if instructions in the compiled binary
58 # don't correspond directly to any original source file.
59 if not lpe
.state
or lpe
.state
.file == 0:
61 filename
= lpe_filename(line_program
, lpe
.state
.file)
62 filename_map
[filename
] += 1
64 for filename
, lpe_count
in filename_map
.items():
65 print(" filename=%s -> %d entries" % (filename
, lpe_count
))
68 def lpe_filename(line_program
, file_index
):
69 # Retrieving the filename associated with a line program entry
70 # involves two levels of indirection: we take the file index from
71 # the LPE to grab the file_entry from the line program header,
72 # then take the directory index from the file_entry to grab the
73 # directory name from the line program header. Finally, we
74 # join the (base) filename from the file_entry to the directory
75 # name to get the absolute filename.
76 lp_header
= line_program
.header
77 file_entries
= lp_header
["file_entry"]
79 # File and directory indices are 1-indexed.
80 file_entry
= file_entries
[file_index
- 1]
81 dir_index
= file_entry
["dir_index"]
83 # A dir_index of 0 indicates that no absolute directory was recorded during
84 # compilation; return just the basename.
86 return file_entry
.name
.decode()
88 directory
= lp_header
["include_directory"][dir_index
- 1]
89 return os
.path
.join(directory
, file_entry
.name
).decode()
92 if __name__
== '__main__':
93 if sys
.argv
[1] == '--test':
94 for filename
in sys
.argv
[2:]:
95 process_file(filename
)