30
30
from ..ehabi .ehabiinfo import EHABIInfo
31
31
from .hash import ELFHashSection , GNUHashSection
32
32
from .constants import SHN_INDICES
33
+ from ..dwarf .dwarf_util import _file_crc32
33
34
34
35
class ELFFile (object ):
35
36
""" Creation: the constructor accepts a stream (file-like object) with the
@@ -94,15 +95,24 @@ def load_from_path(cls, path):
94
95
ELFFile from it, setting up a correct stream_loader relative to the
95
96
original file.
96
97
"""
97
- base_directory = os .path .dirname (path )
98
- def loader (elf_path ):
99
- # FIXME: use actual path instead of str/bytes
100
- if not os .path .isabs (elf_path ):
101
- elf_path = os .path .join (base_directory ,
102
- elf_path )
103
- return open (elf_path , 'rb' )
104
98
stream = open (path , 'rb' )
105
- return ELFFile (stream , loader )
99
+ return ELFFile (stream , ELFFile .make_relative_loader (path ))
100
+
101
+ @staticmethod
102
+ def make_relative_loader (base_path ):
103
+ """ Return a function that takes a potentially relative path,
104
+ resolves it against base_path (bytes or str), and opens a file at that.
105
+
106
+ ELFFile uses functions like that for resolving DWARF links.
107
+ """
108
+ if isinstance (base_path , str ):
109
+ base_path = base_path .encode ('UTF-8' ) # resolver takes a bytes path
110
+ base_directory = os .path .dirname (base_path )
111
+ def loader (rel_path ):
112
+ if not os .path .isabs (rel_path ):
113
+ rel_path = os .path .join (base_directory , rel_path )
114
+ return open (rel_path , 'rb' )
115
+ return loader
106
116
107
117
def num_sections (self ):
108
118
""" Number of sections in the file
@@ -172,6 +182,13 @@ def get_section_index(self, section_name):
172
182
if self ._section_name_map is None :
173
183
self ._make_section_name_map ()
174
184
return self ._section_name_map .get (section_name , None )
185
+
186
+ def has_section (self , section_name ):
187
+ """ Section existence check by name, without the overhead of parsing if found.
188
+ """
189
+ if self ._section_name_map is None :
190
+ self ._make_section_name_map ()
191
+ return section_name in self ._section_name_map
175
192
176
193
def iter_sections (self , type = None ):
177
194
""" Yield all the sections in the file. If the optional |type|
@@ -231,14 +248,18 @@ def address_offsets(self, start, size=1):
231
248
end <= seg ['p_vaddr' ] + seg ['p_filesz' ]):
232
249
yield start - seg ['p_vaddr' ] + seg ['p_offset' ]
233
250
234
- def has_dwarf_info (self ):
251
+ def has_dwarf_info (self , strict = False ):
235
252
""" Check whether this file appears to have debugging information.
236
253
We assume that if it has the .debug_info or .zdebug_info section, it
237
254
has all the other required sections as well.
255
+
256
+ Unless you pass strict=True, the presence of .eh_frame section,
257
+ which is DWARF adjacent but hardly DWARF proper, will count as debug info.
258
+ Stripped files contain .eh_frame but none of the .[z]debug_xxx sections.
238
259
"""
239
- return bool (self .get_section_by_name ('.debug_info' ) or
240
- self .get_section_by_name ('.zdebug_info' ) or
241
- self .get_section_by_name ('.eh_frame' ))
260
+ return (self .has_section ('.debug_info' ) or
261
+ self .has_section ('.zdebug_info' ) or
262
+ ( not strict and self .has_section ('.eh_frame' ) ))
242
263
243
264
def get_dwarf_info (self , relocate_dwarf_sections = True , follow_links = True ):
244
265
""" Return a DWARFInfo object representing the debugging information in
@@ -247,23 +268,39 @@ def get_dwarf_info(self, relocate_dwarf_sections=True, follow_links=True):
247
268
If relocate_dwarf_sections is True, relocations for DWARF sections
248
269
are looked up and applied.
249
270
250
- If follow_links is True, we will try to load the supplementary
271
+ If follow_links is True, we will try to load the external and/or supplementary
251
272
object file (if any), and use it to resolve references and imports.
252
273
"""
253
- # Expect that has_dwarf_info was called, so at least .debug_info is
274
+ # Expect that has_dwarf_info() was called, so at least .debug_info is
254
275
# present.
255
276
# Sections that aren't found will be passed as None to DWARFInfo.
256
277
278
+ # TODO: support linking by build ID
279
+ # https://sourceware.org/gdb/current/onlinedocs/gdb.html/Separate-Debug-Files.html
280
+
281
+ # A file may contain a debug link but not be stripped, so check for debug_info just in case
282
+ debuglink_section = self .get_section_by_name ('.gnu_debuglink' )
283
+ if debuglink_section and not self .has_dwarf_info (True ) and follow_links and self .stream_loader :
284
+ debuglink = struct_parse (self .structs .Gnu_debuglink , debuglink_section .stream , debuglink_section .header .sh_offset )
285
+ with self .stream_loader (debuglink .filename ) as ext_file :
286
+ # Validate checksum...
287
+ if _file_crc32 (ext_file ) != debuglink .checksum :
288
+ raise ELFError ('The linked DWARF file does not match the checksum in the link.' )
289
+ ext_file .seek (0 , os .SEEK_SET )
290
+ ext_elffile = ELFFile (ext_file , self .stream_loader )
291
+ # Inheriting the stream loader like that might be wrong if the supplementary DWARF link in the other file
292
+ # is relative to the other file's directory as opposed to this file's directory.
293
+ return ext_elffile .get_dwarf_info (relocate_dwarf_sections = relocate_dwarf_sections , follow_links = True )
294
+
257
295
section_names = ('.debug_info' , '.debug_aranges' , '.debug_abbrev' ,
258
296
'.debug_str' , '.debug_line' , '.debug_frame' ,
259
297
'.debug_loc' , '.debug_ranges' , '.debug_pubtypes' ,
260
298
'.debug_pubnames' , '.debug_addr' ,
261
299
'.debug_str_offsets' , '.debug_line_str' ,
262
300
'.debug_loclists' , '.debug_rnglists' ,
263
- '.debug_sup' , '.gnu_debugaltlink' , '.gnu_debuglink' ,
264
- '.debug_types' )
301
+ '.debug_sup' , '.gnu_debugaltlink' , '.debug_types' )
265
302
266
- compressed = bool ( self .get_section_by_name ('.zdebug_info' ) )
303
+ compressed = self .has_section ('.zdebug_info' )
267
304
if compressed :
268
305
section_names = tuple (map (lambda x : '.z' + x [1 :], section_names ))
269
306
@@ -275,7 +312,7 @@ def get_dwarf_info(self, relocate_dwarf_sections=True, follow_links=True):
275
312
debug_loc_sec_name , debug_ranges_sec_name , debug_pubtypes_name ,
276
313
debug_pubnames_name , debug_addr_name , debug_str_offsets_name ,
277
314
debug_line_str_name , debug_loclists_sec_name , debug_rnglists_sec_name ,
278
- debug_sup_name , gnu_debugaltlink_name , gnu_debuglink , debug_types_sec_name ,
315
+ debug_sup_name , gnu_debugaltlink_name , debug_types_sec_name ,
279
316
eh_frame_sec_name ) = section_names
280
317
281
318
debug_sections = {}
@@ -318,13 +355,23 @@ def get_dwarf_info(self, relocate_dwarf_sections=True, follow_links=True):
318
355
debug_rnglists_sec = debug_sections [debug_rnglists_sec_name ],
319
356
debug_sup_sec = debug_sections [debug_sup_name ],
320
357
gnu_debugaltlink_sec = debug_sections [gnu_debugaltlink_name ],
321
- gnu_debuglink_sec = debug_sections [gnu_debuglink ],
322
358
debug_types_sec = debug_sections [debug_types_sec_name ]
323
359
)
324
360
if follow_links :
325
361
dwarfinfo .supplementary_dwarfinfo = self .get_supplementary_dwarfinfo (dwarfinfo )
326
362
return dwarfinfo
327
-
363
+
364
+ def has_dwarf_link (self ):
365
+ """ Whether the binary's debug info is in an
366
+ external file. Use get_dwarf_link to retrieve the path to it.
367
+ """
368
+ return self .has_section ('.gnu_debuglink' )
369
+
370
+ def get_dwarf_link (self ):
371
+ """ Read the .gnu_debuglink section, return an object with filename (as bytes) and checksum (as number) in it.
372
+ """
373
+ section = self .get_section_by_name ('.gnu_debuglink' )
374
+ return struct_parse (self .structs .Gnu_debuglink , section .stream , section .header .sh_offset ) if section else None
328
375
329
376
def get_supplementary_dwarfinfo (self , dwarfinfo ):
330
377
"""
0 commit comments