Mixing v4 and v5 loclists and rangelists sections (#429)
[pyelftools.git] / elftools / dwarf / ranges.py
1 #-------------------------------------------------------------------------------
2 # elftools: dwarf/ranges.py
3 #
4 # DWARF ranges section decoding (.debug_ranges)
5 #
6 # Eli Bendersky (eliben@gmail.com)
7 # This code is in the public domain
8 #-------------------------------------------------------------------------------
9 import os
10 from collections import namedtuple
11
12 from ..common.utils import struct_parse
13 from ..common.exceptions import DWARFError
14 from .dwarf_util import _iter_CUs_in_section
15
16
17 RangeEntry = namedtuple('RangeEntry', 'entry_offset entry_length begin_offset end_offset is_absolute')
18 BaseAddressEntry = namedtuple('BaseAddressEntry', 'entry_offset base_address')
19
20 def not_implemented(e):
21 raise NotImplementedError("Range list entry %s is not supported yet" % (e.entry_type,))
22
23 # Maps parsed entry types to RangeEntry/BaseAddressEntry objects
24 entry_translate = {
25 'DW_RLE_base_address' : lambda e: BaseAddressEntry(e.entry_offset, e.address),
26 'DW_RLE_offset_pair' : lambda e: RangeEntry(e.entry_offset, e.entry_length, e.start_offset, e.end_offset, False),
27 'DW_RLE_start_end' : lambda e: RangeEntry(e.entry_offset, e.entry_length, e.start_address, e.end_address, True),
28 'DW_RLE_start_length' : lambda e: RangeEntry(e.entry_offset, e.entry_length, e.start_address, e.start_address + e.length, True),
29 'DW_RLE_base_addressx': not_implemented,
30 'DW_RLE_startx_endx' : not_implemented,
31 'DW_RLE_startx_length': not_implemented
32 }
33
34 class RangeListsPair(object):
35 """For those binaries that contain both a debug_ranges and a debug_rnglists section,
36 it holds a RangeLists object for both and forwards API calls to the right one based
37 on the CU version.
38 """
39 def __init__(self, streamv4, streamv5, structs, dwarfinfo=None):
40 self._ranges = RangeLists(streamv4, structs, 4, dwarfinfo)
41 self._rnglists = RangeLists(streamv5, structs, 5, dwarfinfo)
42
43 def get_range_list_at_offset(self, offset, cu=None):
44 """Forwards the call to either v4 section or v5 one,
45 depending on DWARF version in the CU.
46 """
47 if cu is None:
48 raise DWARFError("For this binary, \"cu\" needs to be provided")
49 section = self._rnglists if cu.header.version >= 5 else self._ranges
50 return section.get_range_list_at_offset(offset, cu)
51
52 def get_range_list_at_offset_ex(self, offset):
53 """Gets an untranslated v5 rangelist from the v5 section.
54 """
55 return self._rnglists.get_range_list_at_offset_ex(offset)
56
57 def iter_range_lists(self):
58 """Tricky proposition, since the structure of ranges and rnglists
59 is not identical. A realistic readelf implementation needs to be aware of both.
60 """
61 raise DWARFError("Iterating through two sections is not supported")
62
63 def iter_CUs(self):
64 """See RangeLists.iter_CUs()
65
66 CU structure is only present in DWARFv5 rnglists sections. A well written
67 section dumper should check if one is present.
68 """
69 return self._rnglists.iter_CUs()
70
71 def iter_CU_range_lists_ex(self, cu):
72 """See RangeLists.iter_CU_range_lists_ex()
73
74 CU structure is only present in DWARFv5 rnglists sections. A well written
75 section dumper should check if one is present.
76 """
77 return self._rnglists.iter_CU_range_lists_ex(cu)
78
79 class RangeLists(object):
80 """ A single range list is a Python list consisting of RangeEntry or
81 BaseAddressEntry objects.
82
83 Since v0.29, two new parameters - version and dwarfinfo
84
85 version is used to distinguish DWARFv5 rnglists section from
86 the DWARF<=4 ranges section. Only the 4/5 distinction matters.
87
88 The dwarfinfo is needed for enumeration, because enumeration
89 requires scanning the DIEs, because ranges may overlap, even on DWARF<=4
90 """
91 def __init__(self, stream, structs, version, dwarfinfo):
92 self.stream = stream
93 self.structs = structs
94 self._max_addr = 2 ** (self.structs.address_size * 8) - 1
95 self.version = version
96 self._dwarfinfo = dwarfinfo
97
98 def get_range_list_at_offset(self, offset, cu=None):
99 """ Get a range list at the given offset in the section.
100 """
101 self.stream.seek(offset, os.SEEK_SET)
102 return self._parse_range_list_from_stream()
103
104 def get_range_list_at_offset_ex(self, offset):
105 """Get a DWARF v5 range list, addresses and offsets unresolved,
106 at the given offset in the section
107 """
108 return struct_parse(self.structs.Dwarf_rnglists_entries, self.stream, offset)
109
110 def iter_range_lists(self):
111 """ Yield all range lists found in the section according to readelf rules.
112 Scans the DIEs for rangelist offsets, then pulls those.
113 """
114 # Calling parse until the stream ends is wrong, because ranges can overlap.
115 # Need to scan the DIEs to know all range locations
116 ver5 = self.version >= 5
117 all_offsets = list(set(die.attributes['DW_AT_ranges'].value
118 for cu in self._dwarfinfo.iter_CUs()
119 for die in cu.iter_DIEs()
120 if 'DW_AT_ranges' in die.attributes and (cu.header.version >= 5) == ver5))
121 all_offsets.sort()
122
123 for offset in all_offsets:
124 yield self.get_range_list_at_offset(offset)
125
126 def iter_CUs(self):
127 """For DWARF5 returns an array of objects, where each one has an array of offsets
128 """
129 if self.version < 5:
130 raise DWARFError("CU iteration in rnglists is not supported with DWARF<5")
131
132 structs = next(self._dwarfinfo.iter_CUs()).structs # Just pick one
133 return _iter_CUs_in_section(self.stream, structs, structs.Dwarf_rnglists_CU_header)
134
135 def iter_CU_range_lists_ex(self, cu):
136 """For DWARF5, returns untranslated rangelists in the CU, where CU comes from iter_CUs above
137 """
138 stream = self.stream
139 stream.seek(cu.offset_table_offset + (64 if cu.is64 else 32) * cu.offset_count)
140 while stream.tell() < cu.offset_after_length + cu.unit_length:
141 yield struct_parse(self.structs.Dwarf_rnglists_entries, stream);
142
143
144 #------ PRIVATE ------#
145
146 def _parse_range_list_from_stream(self):
147 if self.version >= 5:
148 return list(entry_translate[entry.entry_type](entry)
149 for entry
150 in struct_parse(self.structs.Dwarf_rnglists_entries, self.stream))
151 else:
152 lst = []
153 while True:
154 entry_offset = self.stream.tell()
155 begin_offset = struct_parse(
156 self.structs.Dwarf_target_addr(''), self.stream)
157 end_offset = struct_parse(
158 self.structs.Dwarf_target_addr(''), self.stream)
159 if begin_offset == 0 and end_offset == 0:
160 # End of list - we're done.
161 break
162 elif begin_offset == self._max_addr:
163 # Base address selection entry
164 lst.append(BaseAddressEntry(entry_offset=entry_offset, base_address=end_offset))
165 else:
166 # Range entry
167 lst.append(RangeEntry(
168 entry_offset=entry_offset,
169 entry_length=self.stream.tell() - entry_offset,
170 begin_offset=begin_offset,
171 end_offset=end_offset,
172 is_absolute=False))
173 return lst