Fix ranges autotest take 2 (#505)
[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 # If we ever see a list with a base entry at the end, there will be an error that entry_length is not a field.
20
21 def _translate_startx_length(e, cu):
22 start_offset = cu.dwarfinfo.get_addr(cu, e.start_index)
23 return RangeEntry(e.entry_offset, e.entry_length, start_offset, start_offset + e.length, True)
24
25 # Maps parsed entry types to RangeEntry/BaseAddressEntry objects
26 entry_translate = {
27 'DW_RLE_base_address' : lambda e, cu: BaseAddressEntry(e.entry_offset, e.address),
28 'DW_RLE_offset_pair' : lambda e, cu: RangeEntry(e.entry_offset, e.entry_length, e.start_offset, e.end_offset, False),
29 'DW_RLE_start_end' : lambda e, cu: RangeEntry(e.entry_offset, e.entry_length, e.start_address, e.end_address, True),
30 'DW_RLE_start_length' : lambda e, cu: RangeEntry(e.entry_offset, e.entry_length, e.start_address, e.start_address + e.length, True),
31 'DW_RLE_base_addressx': lambda e, cu: BaseAddressEntry(e.entry_offset, cu.dwarfinfo.get_addr(cu, e.index)),
32 'DW_RLE_startx_endx' : lambda e, cu: RangeEntry(e.entry_offset, e.entry_length, cu.dwarfinfo.get_addr(cu, e.start_index), cu.dwarfinfo.get_addr(cu, e.end_index), True),
33 'DW_RLE_startx_length': _translate_startx_length
34 }
35
36 class RangeListsPair(object):
37 """For those binaries that contain both a debug_ranges and a debug_rnglists section,
38 it holds a RangeLists object for both and forwards API calls to the right one based
39 on the CU version.
40 """
41 def __init__(self, streamv4, streamv5, structs, dwarfinfo=None):
42 self._ranges = RangeLists(streamv4, structs, 4, dwarfinfo)
43 self._rnglists = RangeLists(streamv5, structs, 5, dwarfinfo)
44
45 def get_range_list_at_offset(self, offset, cu=None):
46 """Forwards the call to either v4 section or v5 one,
47 depending on DWARF version in the CU.
48 """
49 if cu is None:
50 raise DWARFError("For this binary, \"cu\" needs to be provided")
51 section = self._rnglists if cu.header.version >= 5 else self._ranges
52 return section.get_range_list_at_offset(offset, cu)
53
54 def get_range_list_at_offset_ex(self, offset):
55 """Gets an untranslated v5 rangelist from the v5 section.
56 """
57 return self._rnglists.get_range_list_at_offset_ex(offset)
58
59 def iter_range_lists(self):
60 """Tricky proposition, since the structure of ranges and rnglists
61 is not identical. A realistic readelf implementation needs to be aware of both.
62 """
63 raise DWARFError("Iterating through two sections is not supported")
64
65 def iter_CUs(self):
66 """See RangeLists.iter_CUs()
67
68 CU structure is only present in DWARFv5 rnglists sections. A well written
69 section dumper should check if one is present.
70 """
71 return self._rnglists.iter_CUs()
72
73 def iter_CU_range_lists_ex(self, cu):
74 """See RangeLists.iter_CU_range_lists_ex()
75
76 CU structure is only present in DWARFv5 rnglists sections. A well written
77 section dumper should check if one is present.
78 """
79 return self._rnglists.iter_CU_range_lists_ex(cu)
80
81 def translate_v5_entry(self, entry, cu):
82 """Forwards a V5 entry translation request to the V5 section
83 """
84 return self._rnglists.translate_v5_entry(entry, cu)
85
86 class RangeLists(object):
87 """ A single range list is a Python list consisting of RangeEntry or
88 BaseAddressEntry objects.
89
90 Since v0.29, two new parameters - version and dwarfinfo
91
92 version is used to distinguish DWARFv5 rnglists section from
93 the DWARF<=4 ranges section. Only the 4/5 distinction matters.
94
95 The dwarfinfo is needed for enumeration, because enumeration
96 requires scanning the DIEs, because ranges may overlap, even on DWARF<=4
97 """
98 def __init__(self, stream, structs, version, dwarfinfo):
99 self.stream = stream
100 self.structs = structs
101 self._max_addr = 2 ** (self.structs.address_size * 8) - 1
102 self.version = version
103 self._dwarfinfo = dwarfinfo
104
105 def get_range_list_at_offset(self, offset, cu=None):
106 """ Get a range list at the given offset in the section.
107
108 The cu argument is necessary if the ranges section is a
109 DWARFv5 debug_rnglists one, and the target rangelist
110 contains indirect encodings
111 """
112 self.stream.seek(offset, os.SEEK_SET)
113 return self._parse_range_list_from_stream(cu)
114
115 def get_range_list_at_offset_ex(self, offset):
116 """Get a DWARF v5 range list, addresses and offsets unresolved,
117 at the given offset in the section
118 """
119 return struct_parse(self.structs.Dwarf_rnglists_entries, self.stream, offset)
120
121 def iter_range_lists(self):
122 """ Yields all range lists found in the section according to readelf rules.
123 Scans the DIEs for rangelist offsets, then pulls those.
124 Returned rangelists are always translated into lists of BaseAddressEntry/RangeEntry objects.
125 """
126 # Rangelists can overlap. That is, one DIE points at the rangelist beginning, and another
127 # points at the middle of the same. Therefore, enumerating them is not a well defined
128 # operation - do you count those as two different (but overlapping) ones, or as a single one?
129 # For debugging utility, you want two. That's what readelf does. For faithfully
130 # representing the section contents, you want one.
131 # That was the behaviour of pyelftools 0.28 and below - calling
132 # parse until the stream end. Leaving aside the question of correctless,
133 # that's uncompatible with readelf.
134
135 ver5 = self.version >= 5
136 # This maps list offset to CU
137 cu_map = {die.attributes['DW_AT_ranges'].value : cu
138 for cu in self._dwarfinfo.iter_CUs()
139 for die in cu.iter_DIEs()
140 if 'DW_AT_ranges' in die.attributes and (cu['version'] >= 5) == ver5}
141 all_offsets = list(cu_map.keys())
142 all_offsets.sort()
143
144 for offset in all_offsets:
145 yield self.get_range_list_at_offset(offset, cu_map[offset])
146
147 def iter_CUs(self):
148 """For DWARF5 returns an array of objects, where each one has an array of offsets
149 """
150 if self.version < 5:
151 raise DWARFError("CU iteration in rnglists is not supported with DWARF<5")
152
153 structs = next(self._dwarfinfo.iter_CUs()).structs # Just pick one
154 return _iter_CUs_in_section(self.stream, structs, structs.Dwarf_rnglists_CU_header)
155
156 def iter_CU_range_lists_ex(self, cu):
157 """For DWARF5, returns untranslated rangelists in the CU, where CU comes from iter_CUs above
158 """
159 stream = self.stream
160 stream.seek(cu.offset_table_offset + (64 if cu.is64 else 32) * cu.offset_count)
161 while stream.tell() < cu.offset_after_length + cu.unit_length:
162 yield struct_parse(self.structs.Dwarf_rnglists_entries, stream)
163
164 def translate_v5_entry(self, entry, cu):
165 """Translates entries in a DWARFv5 rangelist from raw parsed format to
166 a list of BaseAddressEntry/RangeEntry, using the CU
167 """
168 return entry_translate[entry.entry_type](entry, cu)
169
170 #------ PRIVATE ------#
171
172 def _parse_range_list_from_stream(self, cu):
173 if self.version >= 5:
174 return list(entry_translate[entry.entry_type](entry, cu)
175 for entry
176 in struct_parse(self.structs.Dwarf_rnglists_entries, self.stream))
177 else:
178 lst = []
179 while True:
180 entry_offset = self.stream.tell()
181 begin_offset = struct_parse(
182 self.structs.Dwarf_target_addr(''), self.stream)
183 end_offset = struct_parse(
184 self.structs.Dwarf_target_addr(''), self.stream)
185 if begin_offset == 0 and end_offset == 0:
186 # End of list - we're done.
187 break
188 elif begin_offset == self._max_addr:
189 # Base address selection entry
190 lst.append(BaseAddressEntry(entry_offset=entry_offset, base_address=end_offset))
191 else:
192 # Range entry
193 lst.append(RangeEntry(
194 entry_offset=entry_offset,
195 entry_length=self.stream.tell() - entry_offset,
196 begin_offset=begin_offset,
197 end_offset=end_offset,
198 is_absolute=False))
199 return lst