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
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] = ['.', '..']
21 from elftools
.elf
.elffile
import ELFFile
24 def process_file(filename
):
25 print('Processing file:', filename
)
26 with
open(filename
, 'rb') as f
:
29 if not elffile
.has_dwarf_info():
30 print(' file has no DWARF info')
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']))
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')
45 # Print a reverse mapping of filename -> #entries
46 line_entry_mapping(line_program
)
49 def line_entry_mapping(line_program
):
50 filename_map
= defaultdict(int)
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:
62 filename
= lpe_filename(line_program
, lpe
.state
.file)
63 filename_map
[filename
] += 1
65 for filename
, lpe_count
in filename_map
.items():
66 print(" filename=%s -> %d entries" % (filename
, lpe_count
))
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"]
80 # File and directory indices are 1-indexed.
81 file_entry
= file_entries
[file_index
- 1]
82 dir_index
= file_entry
["dir_index"]
84 # A dir_index of 0 indicates that no absolute directory was recorded during
85 # compilation; return just the basename.
87 return file_entry
.name
.decode()
89 directory
= lp_header
["include_directory"][dir_index
- 1]
90 return posixpath
.join(directory
, file_entry
.name
).decode()
93 if __name__
== '__main__':
94 if sys
.argv
[1] == '--test':
95 for filename
in sys
.argv
[2:]:
96 process_file(filename
)