class ELFParseError(ELFError):
pass
-class DWARFError(Exception):
+class ELFCompressionError(ELFError):
pass
+class DWARFError(Exception):
+ pass
SHF_OS_NONCONFORMING=0x100
SHF_GROUP=0x200
SHF_TLS=0x400
+ SHF_COMPRESSED=0x800
SHF_MASKOS=0x0ff00000
SHF_EXCLUDE=0x80000000
SHF_MASKPROC=0xf0000000
class DynamicSection(Section, Dynamic):
""" ELF dynamic table section. Knows how to process the list of tags.
"""
- def __init__(self, header, name, stream, elffile):
- Section.__init__(self, header, name, stream)
+ def __init__(self, header, name, elffile):
+ Section.__init__(self, header, name, elffile)
stringtable = elffile.get_section(header['sh_link'])
- Dynamic.__init__(self, stream, elffile, stringtable, self['sh_offset'])
+ Dynamic.__init__(self, self.stream, self.elffile, stringtable,
+ self['sh_offset'])
class DynamicSegment(Segment, Dynamic):
sectype = section_header['sh_type']
if sectype == 'SHT_STRTAB':
- return StringTableSection(section_header, name, self.stream)
+ return StringTableSection(section_header, name, self)
elif sectype == 'SHT_NULL':
- return NullSection(section_header, name, self.stream)
+ return NullSection(section_header, name, self)
elif sectype in ('SHT_SYMTAB', 'SHT_DYNSYM', 'SHT_SUNW_LDYNSYM'):
return self._make_symbol_table_section(section_header, name)
elif sectype == 'SHT_SUNW_syminfo':
elif sectype == 'SHT_GNU_versym':
return self._make_gnu_versym_section(section_header, name)
elif sectype in ('SHT_REL', 'SHT_RELA'):
- return RelocationSection(
- section_header, name, self.stream, self)
+ return RelocationSection(section_header, name, self)
elif sectype == 'SHT_DYNAMIC':
- return DynamicSection(section_header, name, self.stream, self)
+ return DynamicSection(section_header, name, self)
elif sectype == 'SHT_NOTE':
- return NoteSection(section_header, name, self.stream, self)
+ return NoteSection(section_header, name, self)
elif sectype == 'SHT_PROGBITS' and name == '.stab':
- return StabSection(section_header, name, self.stream, self)
+ return StabSection(section_header, name, self)
else:
- return Section(section_header, name, self.stream)
+ return Section(section_header, name, self)
def _make_symbol_table_section(self, section_header, name):
""" Create a SymbolTableSection
linked_strtab_index = section_header['sh_link']
strtab_section = self.get_section(linked_strtab_index)
return SymbolTableSection(
- section_header, name, self.stream,
+ section_header, name,
elffile=self,
stringtable=strtab_section)
linked_strtab_index = section_header['sh_link']
strtab_section = self.get_section(linked_strtab_index)
return SUNWSyminfoTableSection(
- section_header, name, self.stream,
+ section_header, name,
elffile=self,
symboltable=strtab_section)
linked_strtab_index = section_header['sh_link']
strtab_section = self.get_section(linked_strtab_index)
return GNUVerNeedSection(
- section_header, name, self.stream,
+ section_header, name,
elffile=self,
stringtable=strtab_section)
linked_strtab_index = section_header['sh_link']
strtab_section = self.get_section(linked_strtab_index)
return GNUVerDefSection(
- section_header, name, self.stream,
+ section_header, name,
elffile=self,
stringtable=strtab_section)
linked_strtab_index = section_header['sh_link']
strtab_section = self.get_section(linked_strtab_index)
return GNUVerSymSection(
- section_header, name, self.stream,
+ section_header, name,
elffile=self,
symboltable=strtab_section)
return StringTableSection(
header=self._get_section_header(stringtable_section_num),
name='',
- stream=self.stream)
+ elffile=self)
def _parse_elf_header(self):
""" Parses the ELF file header and assigns the result to attributes
""" Read the contents of a DWARF section from the stream and return a
DebugSectionDescriptor. Apply relocations if asked to.
"""
- self.stream.seek(section['sh_offset'])
# The section data is read into a new stream, for processing
section_stream = BytesIO()
- section_stream.write(self.stream.read(section['sh_size']))
+ section_stream.write(section.data())
if relocate_dwarf_sections:
reloc_handler = RelocationHandler(self)
_default_=Pass,
)
+ENUM_ELFCOMPRESS_TYPE = dict(
+ ELFCOMPRESS_ZLIB=1,
+ ELFCOMPRESS_LOOS=0x60000000,
+ ELFCOMPRESS_HIOS=0x6fffffff,
+ ELFCOMPRESS_LOPROC=0x70000000,
+ ELFCOMPRESS_HIPROC=0x7fffffff,
+ _default_=Pass,
+)
+
# p_type in the program header
# some values scavenged from the ELF headers in binutils-2.21
ENUM_P_TYPE = dict(
sections class which contains shareable code
"""
- def __init__(self, header, name, stream, elffile, stringtable,
+ def __init__(self, header, name, elffile, stringtable,
field_prefix, version_struct, version_auxiliaries_struct):
- super(GNUVersionSection, self).__init__(header, name, stream)
- self.elffile = elffile
+ super(GNUVersionSection, self).__init__(header, name, elffile)
self.stringtable = stringtable
self.field_prefix = field_prefix
self.version_struct = version_struct
""" ELF SUNW or GNU Version Needed table section.
Has an associated StringTableSection that's passed in the constructor.
"""
- def __init__(self, header, name, stream, elffile, stringtable):
+ def __init__(self, header, name, elffile, stringtable):
super(GNUVerNeedSection, self).__init__(
- header, name, stream, elffile, stringtable, 'vn',
+ header, name, elffile, stringtable, 'vn',
elffile.structs.Elf_Verneed, elffile.structs.Elf_Vernaux)
self._has_indexes = None
""" ELF SUNW or GNU Version Definition table section.
Has an associated StringTableSection that's passed in the constructor.
"""
- def __init__(self, header, name, stream, elffile, stringtable):
+ def __init__(self, header, name, elffile, stringtable):
super(GNUVerDefSection, self).__init__(
- header, name, stream, elffile, stringtable, 'vd',
+ header, name, elffile, stringtable, 'vd',
elffile.structs.Elf_Verdef, elffile.structs.Elf_Verdaux)
def get_version(self, index):
""" ELF SUNW or GNU Versym table section.
Has an associated SymbolTableSection that's passed in the constructor.
"""
- def __init__(self, header, name, stream, elffile, symboltable):
- super(GNUVerSymSection, self).__init__(header, name, stream)
- self.elffile = elffile
- self.elfstructs = self.elffile.structs
+ def __init__(self, header, name, elffile, symboltable):
+ super(GNUVerSymSection, self).__init__(header, name, elffile)
self.symboltable = symboltable
def num_symbols(self):
# Grab the symbol's entry from the stream
entry_offset = self['sh_offset'] + n * self['sh_entsize']
entry = struct_parse(
- self.elfstructs.Elf_Versym,
+ self.structs.Elf_Versym,
self.stream,
stream_pos=entry_offset)
# Find the symbol name in the associated symbol table
class RelocationSection(Section):
""" ELF relocation section. Serves as a collection of Relocation entries.
"""
- def __init__(self, header, name, stream, elffile):
- super(RelocationSection, self).__init__(header, name, stream)
- self.elffile = elffile
- self.elfstructs = self.elffile.structs
+ def __init__(self, header, name, elffile):
+ super(RelocationSection, self).__init__(header, name, elffile)
if self.header['sh_type'] == 'SHT_REL':
- expected_size = self.elfstructs.Elf_Rel.sizeof()
- self.entry_struct = self.elfstructs.Elf_Rel
+ expected_size = self.structs.Elf_Rel.sizeof()
+ self.entry_struct = self.structs.Elf_Rel
elif self.header['sh_type'] == 'SHT_RELA':
- expected_size = self.elfstructs.Elf_Rela.sizeof()
- self.entry_struct = self.elfstructs.Elf_Rela
+ expected_size = self.structs.Elf_Rela.sizeof()
+ self.entry_struct = self.structs.Elf_Rela
else:
elf_assert(False, 'Unknown relocation type section')
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
+from ..common.exceptions import ELFCompressionError
from ..common.utils import struct_parse, elf_assert, parse_cstring_from_stream
from collections import defaultdict
+from .constants import SH_FLAGS
from .notes import iter_notes
+import zlib
class Section(object):
> sec = Section(...)
> sec['sh_type'] # section type
"""
- def __init__(self, header, name, stream):
+ def __init__(self, header, name, elffile):
self.header = header
self.name = name
- self.stream = stream
+ self.elffile = elffile
+ self.stream = self.elffile.stream
+ self.structs = self.elffile.structs
+ self._compressed = header['sh_flags'] & SH_FLAGS.SHF_COMPRESSED
+
+ if self.compressed:
+ # Read the compression header now to know about the size/alignment
+ # of the decompressed data.
+ header = struct_parse(self.structs.Elf_Chdr,
+ self.stream,
+ stream_pos=self['sh_offset'])
+ self._compression_type = header['ch_type']
+ self._decompressed_size = header['ch_size']
+ self._decompressed_align = header['ch_addralign']
+ else:
+ self._decompressed_size = header['sh_size']
+ self._decompressed_align = header['sh_addralign']
+
+ @property
+ def compressed(self):
+ """ Is this section compressed?
+ """
+ return self._compressed
+
+ @property
+ def data_size(self):
+ """ Return the logical size for this section's data.
+
+ This can be different from the .sh_size header field when the section
+ is compressed.
+ """
+ return self._decompressed_size
+
+ @property
+ def data_alignment(self):
+ """ Return the logical alignment for this section's data.
+
+ This can be different from the .sh_addralign header field when the
+ section is compressed.
+ """
+ return self._decompressed_align
def data(self):
""" The section data from the file.
+
+ Note that data is decompressed if the stored section data is
+ compressed.
"""
- self.stream.seek(self['sh_offset'])
- return self.stream.read(self['sh_size'])
+ # If this section is compressed, deflate it
+ if self.compressed:
+ c_type = self._compression_type
+ if c_type == 'ELFCOMPRESS_ZLIB':
+ # Read the data to decompress starting right after the
+ # compression header until the end of the section.
+ hdr_size = self.structs.Elf_Chdr.sizeof()
+ self.stream.seek(self['sh_offset'] + hdr_size)
+ compressed = self.stream.read(self['sh_size'] - hdr_size)
+
+ decomp = zlib.decompressobj()
+ result = decomp.decompress(compressed, self.data_size)
+ else:
+ raise ELFCompressionError(
+ 'Unknown compression type: {:#0x}'.format(c_type)
+ )
+
+ if len(result) != self._decompressed_size:
+ raise ELFCompressionError(
+ 'Decompressed data is {} bytes long, should be {} bytes'
+ ' long'.format(len(result), self._decompressed_size)
+ )
+ else:
+ self.stream.seek(self['sh_offset'])
+ result = self.stream.read(self._decompressed_size)
+
+ return result
def is_null(self):
""" Is this a null section?
class NullSection(Section):
""" ELF NULL section
"""
- def __init__(self, header, name, stream):
- super(NullSection, self).__init__(header, name, stream)
-
def is_null(self):
return True
class StringTableSection(Section):
""" ELF string table section.
"""
- def __init__(self, header, name, stream):
- super(StringTableSection, self).__init__(header, name, stream)
-
def get_string(self, offset):
""" Get the string stored at the given offset in this string table.
"""
""" ELF symbol table section. Has an associated StringTableSection that's
passed in the constructor.
"""
- def __init__(self, header, name, stream, elffile, stringtable):
- super(SymbolTableSection, self).__init__(header, name, stream)
- self.elffile = elffile
- self.elfstructs = self.elffile.structs
+ def __init__(self, header, name, elffile, stringtable):
+ super(SymbolTableSection, self).__init__(header, name, elffile)
self.stringtable = stringtable
elf_assert(self['sh_entsize'] > 0,
'Expected entry size of section %r to be > 0' % name)
# Grab the symbol's entry from the stream
entry_offset = self['sh_offset'] + n * self['sh_entsize']
entry = struct_parse(
- self.elfstructs.Elf_Sym,
+ self.structs.Elf_Sym,
self.stream,
stream_pos=entry_offset)
# Find the symbol name in the associated string table
""" ELF .SUNW Syminfo table section.
Has an associated SymbolTableSection that's passed in the constructor.
"""
- def __init__(self, header, name, stream, elffile, symboltable):
- super(SUNWSyminfoTableSection, self).__init__(header, name, stream)
- self.elffile = elffile
- self.elfstructs = self.elffile.structs
+ def __init__(self, header, name, elffile, symboltable):
+ super(SUNWSyminfoTableSection, self).__init__(header, name, elffile)
self.symboltable = symboltable
def num_symbols(self):
# Grab the symbol's entry from the stream
entry_offset = self['sh_offset'] + n * self['sh_entsize']
entry = struct_parse(
- self.elfstructs.Elf_Sunw_Syminfo,
+ self.structs.Elf_Sunw_Syminfo,
self.stream,
stream_pos=entry_offset)
# Find the symbol name in the associated symbol table
class NoteSection(Section):
""" ELF NOTE section. Knows how to parse notes.
"""
- def __init__(self, header, name, stream, elffile):
- super(NoteSection, self).__init__(header, name, stream)
- self.elffile = elffile
-
def iter_notes(self):
""" Yield all the notes in the section. Each result is a dictionary-
like object with "n_name", "n_type", and "n_desc" fields, amongst
class StabSection(Section):
""" ELF stab section.
"""
- def __init__(self, header, name, stream, elffile):
- super(StabSection, self).__init__(header, name, stream)
- self.elffile = elffile
-
def iter_stabs(self):
""" Yield all stab entries. Result type is ELFStructs.Elf_Stabs.
"""
- elffile = self.elffile
offset = self['sh_offset']
size = self['sh_size']
end = offset + size
while offset < end:
stabs = struct_parse(
- elffile.structs.Elf_Stabs,
- elffile.stream,
+ self.structs.Elf_Stabs,
+ self.elffile.stream,
stream_pos=offset)
stabs['n_offset'] = offset
- offset += elffile.structs.Elf_Stabs.sizeof()
- elffile.stream.seek(offset)
+ offset += self.structs.Elf_Stabs.sizeof()
+ self.stream.seek(offset)
yield stabs
"""
self._create_phdr()
self._create_shdr()
+ self._create_chdr()
self._create_sym()
self._create_rel()
self._create_dyn()
self.Elf_xword('sh_entsize'),
)
+ def _create_chdr(self):
+ # Structure of compressed sections header. It is documented in Oracle
+ # "Linker and Libraries Guide", Part IV ELF Application Binary
+ # Interface, Chapter 13 Object File Format, Section Compression:
+ # https://docs.oracle.com/cd/E53394_01/html/E54813/section_compression.html
+ fields = [
+ Enum(self.Elf_word('ch_type'), **ENUM_ELFCOMPRESS_TYPE),
+ self.Elf_xword('ch_size'),
+ self.Elf_xword('ch_addralign'),
+ ]
+ if self.elfclass == 64:
+ fields.insert(1, self.Elf_word('ch_reserved'))
+ self.Elf_Chdr = Struct('Elf_Chdr', *fields)
+
def _create_rel(self):
# r_info is also taken apart into r_info_sym and r_info_type.
# This is done in Value to avoid endianity issues while parsing.
def _create_gnu_verneed(self):
# Structure of "version needed" entries is documented in
- # Oracle "Linker and Libraries Guide", Chapter 7 Object File Format
+ # Oracle "Linker and Libraries Guide", Chapter 13 Object File Format
self.Elf_Verneed = Struct('Elf_Verneed',
self.Elf_half('vn_version'),
self.Elf_half('vn_cnt'),
)
def _create_gnu_verdef(self):
- # Structure off "version definition" entries are documented in
- # Oracle "Linker and Libraries Guide", Chapter 7 Object File Format
+ # Structure of "version definition" entries are documented in
+ # Oracle "Linker and Libraries Guide", Chapter 13 Object File Format
self.Elf_Verdef = Struct('Elf_Verdef',
self.Elf_half('vd_version'),
self.Elf_half('vd_flags'),
)
def _create_gnu_versym(self):
- # Structure off "version symbol" entries are documented in
- # Oracle "Linker and Libraries Guide", Chapter 7 Object File Format
+ # Structure of "version symbol" entries are documented in
+ # Oracle "Linker and Libraries Guide", Chapter 13 Object File Format
self.Elf_Versym = Struct('Elf_Versym',
Enum(self.Elf_half('ndx'), **ENUM_VERSYM),
)
--- /dev/null
+#-------------------------------------------------------------------------------
+# Test handling for compressed debug sections
+#
+# Pierre-Marie de Rodat (pmderodat@kawie.fr)
+# This code is in the public domain
+#-------------------------------------------------------------------------------
+
+from contextlib import contextmanager
+import os
+import unittest
+
+from elftools.elf.elffile import ELFFile
+from elftools.common.exceptions import ELFCompressionError
+
+
+class TestCompressedSupport(unittest.TestCase):
+
+ def test_compressed_32(self):
+ with self.elffile('32') as elf:
+ section = elf.get_section_by_name('.debug_info')
+ self.assertTrue(section.compressed)
+ self.assertEqual(section.data_size, 0x330)
+ self.assertEqual(section.data_alignment, 1)
+
+ self.assertEqual(self.get_cus_info(elf), ['CU 0x0: 0xb-0x322'])
+
+ def test_compressed_64(self):
+ with self.elffile('64') as elf:
+ section = elf.get_section_by_name('.debug_info')
+ self.assertTrue(section.compressed)
+ self.assertEqual(section.data_size, 0x327)
+ self.assertEqual(section.data_alignment, 1)
+ self.assertEqual(self.get_cus_info(elf), ['CU 0x0: 0xb-0x319'])
+
+ def test_compressed_unknown_type(self):
+ with self.elffile('unknown_type') as elf:
+ section = elf.get_section_by_name('.debug_info')
+ try:
+ section.data()
+ except ELFCompressionError as exc:
+ self.assertEqual(
+ str(exc), 'Unknown compression type: 0x7ffffffe'
+ )
+ else:
+ self.fail('An exception was exected')
+
+ def test_compressed_bad_size(self):
+ with self.elffile('bad_size') as elf:
+ section = elf.get_section_by_name('.debug_info')
+ try:
+ section.data()
+ except ELFCompressionError as exc:
+ self.assertEqual(
+ str(exc),
+ 'Decompressed data is 807 bytes long, should be 808 bytes'
+ ' long'
+ )
+ else:
+ self.fail('An exception was exected')
+
+ # Test helpers
+
+ @contextmanager
+ def elffile(self, name):
+ """ Context manager to open and parse an ELF file.
+ """
+ with open(os.path.join('test', 'testfiles_for_unittests',
+ 'compressed_{}.o'.format(name)), 'rb') as f:
+ yield ELFFile(f)
+
+ def get_cus_info(self, elffile):
+ """ Return basic info about the compile units in `elffile`.
+
+ This is used as a basic sanity check for decompressed DWARF data.
+ """
+ result = []
+
+ dwarf = elffile.get_dwarf_info()
+ for cu in dwarf.iter_CUs():
+ dies = []
+
+ def traverse(die):
+ dies.append(die.offset)
+ for child in die.iter_children():
+ traverse(child)
+
+ traverse(cu.get_top_DIE())
+ result.append('CU {:#0x}: {:#0x}-{:#0x}'.format(
+ cu.cu_offset,
+ dies[0], dies[-1]
+ ))
+
+ return result
--- /dev/null
+/* Generated by compiling with any GCC version and with a binutils
+** distribution that includes support for compressed sections. GNU binutils
+** 2.28 is fine.
+**
+** gcc -c -m32 -O0 -g compressed.c -o compressed_32.o
+** gcc -c -m64 -O0 -g compressed.c -o compressed_64.o
+**
+** compressed_unknown_type.o is a copy of compressed_64.o that is hand
+** hex-edited to replace the ch_field with 0x7ffffffe.
+**
+** compressed_bad_size.o is a copy of compressed_64.o that is hand
+** hex-edited to replace the ch_size with 0x328 (instead of 0x327).
+*/
+
+#include <stdio.h>
+
+int foo(int i) {
+ printf ("i = %i\n", i);
+ return 0;
+}