1 #-------------------------------------------------------------------------------
2 # elftools: dwarf/ranges.py
4 # DWARF ranges section decoding (.debug_ranges)
6 # Eli Bendersky (eliben@gmail.com)
7 # This code is in the public domain
8 #-------------------------------------------------------------------------------
10 from collections
import namedtuple
12 from ..common
.utils
import struct_parse
13 from ..common
.exceptions
import DWARFError
14 from .dwarf_util
import _iter_CUs_in_section
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.
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)
25 # Maps parsed entry types to RangeEntry/BaseAddressEntry objects
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
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
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
)
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.
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
)
54 def get_range_list_at_offset_ex(self
, offset
):
55 """Gets an untranslated v5 rangelist from the v5 section.
57 return self
._rnglists
.get_range_list_at_offset_ex(offset
)
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.
63 raise DWARFError("Iterating through two sections is not supported")
66 """See RangeLists.iter_CUs()
68 CU structure is only present in DWARFv5 rnglists sections. A well written
69 section dumper should check if one is present.
71 return self
._rnglists
.iter_CUs()
73 def iter_CU_range_lists_ex(self
, cu
):
74 """See RangeLists.iter_CU_range_lists_ex()
76 CU structure is only present in DWARFv5 rnglists sections. A well written
77 section dumper should check if one is present.
79 return self
._rnglists
.iter_CU_range_lists_ex(cu
)
81 def translate_v5_entry(self
, entry
, cu
):
82 """Forwards a V5 entry translation request to the V5 section
84 return self
._rnglists
.translate_v5_entry(entry
, cu
)
86 class RangeLists(object):
87 """ A single range list is a Python list consisting of RangeEntry or
88 BaseAddressEntry objects.
90 Since v0.29, two new parameters - version and dwarfinfo
92 version is used to distinguish DWARFv5 rnglists section from
93 the DWARF<=4 ranges section. Only the 4/5 distinction matters.
95 The dwarfinfo is needed for enumeration, because enumeration
96 requires scanning the DIEs, because ranges may overlap, even on DWARF<=4
98 def __init__(self
, stream
, structs
, version
, dwarfinfo
):
100 self
.structs
= structs
101 self
._max
_addr
= 2 ** (self
.structs
.address_size
* 8) - 1
102 self
.version
= version
103 self
._dwarfinfo
= dwarfinfo
105 def get_range_list_at_offset(self
, offset
, cu
=None):
106 """ Get a range list at the given offset in the section.
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
112 self
.stream
.seek(offset
, os
.SEEK_SET
)
113 return self
._parse
_range
_list
_from
_stream
(cu
)
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
119 return struct_parse(self
.structs
.Dwarf_rnglists_entries
, self
.stream
, offset
)
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.
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.
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())
144 for offset
in all_offsets
:
145 yield self
.get_range_list_at_offset(offset
, cu_map
[offset
])
148 """For DWARF5 returns an array of objects, where each one has an array of offsets
151 raise DWARFError("CU iteration in rnglists is not supported with DWARF<5")
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
)
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
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
)
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
168 return entry_translate
[entry
.entry_type
](entry
, cu
)
170 #------ PRIVATE ------#
172 def _parse_range_list_from_stream(self
, cu
):
173 if self
.version
>= 5:
174 return list(entry_translate
[entry
.entry_type
](entry
, cu
)
176 in struct_parse(self
.structs
.Dwarf_rnglists_entries
, self
.stream
))
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.
188 elif begin_offset
== self
._max
_addr
:
189 # Base address selection entry
190 lst
.append(BaseAddressEntry(entry_offset
=entry_offset
, base_address
=end_offset
))
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
,