Compressed sections (#152)
authorPierre-Marie de Rodat <pmderodat@kawie.fr>
Thu, 17 Aug 2017 03:52:45 +0000 (05:52 +0200)
committerEli Bendersky <eliben@users.noreply.github.com>
Thu, 17 Aug 2017 03:52:45 +0000 (20:52 -0700)
* Add constants, enums and structs for compressed section

* ELFStructs: update comments for new section numbers in Oracle docs

* Make the ELFFile's stream/structs available to all Section instances

This harmonizes the signature of Section constructors. Besides, structs
will be required to read compressed sections.

* ELFFile._read_dwarf_section: use Section.data to read the section

An upcoming change will add compressed sections handling. In this
context, the DWARF must be parsed from the decompressed data, so reading
the ELFFile stream will be wrong.

* Add ELF compressed debug sections handling

15 files changed:
elftools/common/exceptions.py
elftools/elf/constants.py
elftools/elf/dynamic.py
elftools/elf/elffile.py
elftools/elf/enums.py
elftools/elf/gnuversions.py
elftools/elf/relocation.py
elftools/elf/sections.py
elftools/elf/structs.py
test/test_compressed_support.py [new file with mode: 0644]
test/testfiles_for_unittests/compressed.c [new file with mode: 0644]
test/testfiles_for_unittests/compressed_32.o [new file with mode: 0644]
test/testfiles_for_unittests/compressed_64.o [new file with mode: 0644]
test/testfiles_for_unittests/compressed_bad_size.o [new file with mode: 0644]
test/testfiles_for_unittests/compressed_unknown_type.o [new file with mode: 0644]

index 26f1ba09f3e460b70f7b99c9ff1e8e4c0cbbf64f..5e409cf1f5262772336872aca9132fe6c15c126e 100644 (file)
@@ -15,6 +15,8 @@ class ELFRelocationError(ELFError):
 class ELFParseError(ELFError):
     pass
 
-class DWARFError(Exception):
+class ELFCompressionError(ELFError):
     pass
 
+class DWARFError(Exception):
+    pass
index df6e0af50e9beb36ff92b795ead253448e7e5429..3ea32958aec0efe2236dd1d54c31a28aa869de5e 100644 (file)
@@ -70,6 +70,7 @@ class SH_FLAGS(object):
     SHF_OS_NONCONFORMING=0x100
     SHF_GROUP=0x200
     SHF_TLS=0x400
+    SHF_COMPRESSED=0x800
     SHF_MASKOS=0x0ff00000
     SHF_EXCLUDE=0x80000000
     SHF_MASKPROC=0xf0000000
index 82887ed692a355c88e53cb6ca75f5000bf322e76..9282284437371c522798dd9955bf68f1ef1eff54 100644 (file)
@@ -164,10 +164,11 @@ class Dynamic(object):
 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):
index 6fd2a7300850278ac7304405994ec67d5d8cf045..fedd14de2ac6e729c41b9f952eb424c6ea351bc1 100644 (file)
@@ -297,9 +297,9 @@ class ELFFile(object):
         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':
@@ -311,16 +311,15 @@ class ELFFile(object):
         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
@@ -328,7 +327,7 @@ class ELFFile(object):
         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)
 
@@ -338,7 +337,7 @@ class ELFFile(object):
         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)
 
@@ -348,7 +347,7 @@ class ELFFile(object):
         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)
 
@@ -358,7 +357,7 @@ class ELFFile(object):
         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)
 
@@ -368,7 +367,7 @@ class ELFFile(object):
         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)
 
@@ -387,7 +386,7 @@ class ELFFile(object):
         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
@@ -399,10 +398,9 @@ class ELFFile(object):
         """ 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)
index 904a3ba2a1031f4a57e545dbea0c6882b7a80eb0..13ef68f71c53721e37b97a80d77a3ebd3129304d 100644 (file)
@@ -341,6 +341,15 @@ ENUM_SH_TYPE = dict(
     _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(
index 4a4473f3faef5941fce1c0c098ecfac8673bce51..3ad8dd169baaa66e38b095be7ec61978ef00526f 100644 (file)
@@ -56,10 +56,9 @@ class GNUVersionSection(Section):
         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
@@ -130,9 +129,9 @@ class GNUVerNeedSection(GNUVersionSection):
     """ 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
 
@@ -173,9 +172,9 @@ class GNUVerDefSection(GNUVersionSection):
     """ 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):
@@ -195,10 +194,8 @@ class GNUVerSymSection(Section):
     """ 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):
@@ -214,7 +211,7 @@ class GNUVerSymSection(Section):
         # 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
index 690309f0ad17377e4d374195245ceab3dff8b2c8..c202bf9b1aa49ade97190efee8c72918f8629b92 100644 (file)
@@ -47,16 +47,14 @@ class Relocation(object):
 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')
 
index c7729586045f067646cf082f36bcd0b8e1b4479d..eb4d40a3a587b3b879bbc2b32bd11f11b6de8f56 100644 (file)
@@ -6,9 +6,12 @@
 # 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):
@@ -19,16 +22,84 @@ 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?
@@ -53,9 +124,6 @@ class Section(object):
 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
 
@@ -63,9 +131,6 @@ class NullSection(Section):
 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.
         """
@@ -78,10 +143,8 @@ class SymbolTableSection(Section):
     """ 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)
@@ -100,7 +163,7 @@ class SymbolTableSection(Section):
         # 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
@@ -149,10 +212,8 @@ class SUNWSyminfoTableSection(Section):
     """ 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):
@@ -168,7 +229,7 @@ class SUNWSyminfoTableSection(Section):
         # 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
@@ -185,10 +246,6 @@ class SUNWSyminfoTableSection(Section):
 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
@@ -199,24 +256,19 @@ class NoteSection(Section):
 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
 
index 7c2c5d16f61b1b22dc3cc0d0cdac08c977b6a215..8cc87d2d07d6954b7ecd45dfe462057e2a406f8a 100644 (file)
@@ -76,6 +76,7 @@ class ELFStructs(object):
         """
         self._create_phdr()
         self._create_shdr()
+        self._create_chdr()
         self._create_sym()
         self._create_rel()
         self._create_dyn()
@@ -153,6 +154,20 @@ class ELFStructs(object):
             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.
@@ -226,7 +241,7 @@ class ELFStructs(object):
 
     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'),
@@ -243,8 +258,8 @@ class ELFStructs(object):
         )
 
     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'),
@@ -260,8 +275,8 @@ class ELFStructs(object):
         )
 
     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),
         )
diff --git a/test/test_compressed_support.py b/test/test_compressed_support.py
new file mode 100644 (file)
index 0000000..9672342
--- /dev/null
@@ -0,0 +1,93 @@
+#-------------------------------------------------------------------------------
+# 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
diff --git a/test/testfiles_for_unittests/compressed.c b/test/testfiles_for_unittests/compressed.c
new file mode 100644 (file)
index 0000000..8387362
--- /dev/null
@@ -0,0 +1,20 @@
+/* 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;
+}
diff --git a/test/testfiles_for_unittests/compressed_32.o b/test/testfiles_for_unittests/compressed_32.o
new file mode 100644 (file)
index 0000000..da39bd9
Binary files /dev/null and b/test/testfiles_for_unittests/compressed_32.o differ
diff --git a/test/testfiles_for_unittests/compressed_64.o b/test/testfiles_for_unittests/compressed_64.o
new file mode 100644 (file)
index 0000000..dc420bd
Binary files /dev/null and b/test/testfiles_for_unittests/compressed_64.o differ
diff --git a/test/testfiles_for_unittests/compressed_bad_size.o b/test/testfiles_for_unittests/compressed_bad_size.o
new file mode 100644 (file)
index 0000000..602e17c
Binary files /dev/null and b/test/testfiles_for_unittests/compressed_bad_size.o differ
diff --git a/test/testfiles_for_unittests/compressed_unknown_type.o b/test/testfiles_for_unittests/compressed_unknown_type.o
new file mode 100644 (file)
index 0000000..67970e5
Binary files /dev/null and b/test/testfiles_for_unittests/compressed_unknown_type.o differ