blob: 20e90562e60e6e20a7ab1e27df17df01799ac2bb [file] [log] [blame]
#-------------------------------------------------------------------------------
# elftools: elf/sections.py
#
# ELF sections
#
# 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):
""" Base class for ELF sections. Also used for all sections types that have
no special functionality.
Allows dictionary-like access to the section header. For example:
> sec = Section(...)
> sec['sh_type'] # section type
"""
def __init__(self, header, name, elffile):
self.header = header
self.name = name
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.
"""
# 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?
"""
return False
def __getitem__(self, name):
""" Implement dict-like access to header entries
"""
return self.header[name]
def __eq__(self, other):
try:
return self.header == other.header
except AttributeError:
return False
def __hash__(self):
return hash(self.header)
class NullSection(Section):
""" ELF NULL section
"""
def is_null(self):
return True
class StringTableSection(Section):
""" ELF string table section.
"""
def get_string(self, offset):
""" Get the string stored at the given offset in this string table.
"""
table_offset = self['sh_offset']
s = parse_cstring_from_stream(self.stream, table_offset + offset)
return s.decode('utf-8') if s else ''
class SymbolTableSection(Section):
""" ELF symbol table section. Has an associated StringTableSection that's
passed in the constructor.
"""
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)
elf_assert(self['sh_size'] % self['sh_entsize'] == 0,
'Expected section size to be a multiple of entry size in section %r' % name)
self._symbol_name_map = None
def num_symbols(self):
""" Number of symbols in the table
"""
return self['sh_size'] // self['sh_entsize']
def get_symbol(self, n):
""" Get the symbol at index #n from the table (Symbol object)
"""
# Grab the symbol's entry from the stream
entry_offset = self['sh_offset'] + n * self['sh_entsize']
entry = struct_parse(
self.structs.Elf_Sym,
self.stream,
stream_pos=entry_offset)
# Find the symbol name in the associated string table
name = self.stringtable.get_string(entry['st_name'])
return Symbol(entry, name)
def get_symbol_by_name(self, name):
""" Get a symbol(s) by name. Return None if no symbol by the given name
exists.
"""
# The first time this method is called, construct a name to number
# mapping
#
if self._symbol_name_map is None:
self._symbol_name_map = defaultdict(list)
for i, sym in enumerate(self.iter_symbols()):
self._symbol_name_map[sym.name].append(i)
symnums = self._symbol_name_map.get(name)
return [self.get_symbol(i) for i in symnums] if symnums else None
def iter_symbols(self):
""" Yield all the symbols in the table
"""
for i in range(self.num_symbols()):
yield self.get_symbol(i)
class Symbol(object):
""" Symbol object - representing a single symbol entry from a symbol table
section.
Similarly to Section objects, allows dictionary-like access to the
symbol entry.
"""
def __init__(self, entry, name):
self.entry = entry
self.name = name
def __getitem__(self, name):
""" Implement dict-like access to entries
"""
return self.entry[name]
class SUNWSyminfoTableSection(Section):
""" ELF .SUNW Syminfo table section.
Has an associated SymbolTableSection that's passed in the constructor.
"""
def __init__(self, header, name, elffile, symboltable):
super(SUNWSyminfoTableSection, self).__init__(header, name, elffile)
self.symboltable = symboltable
def num_symbols(self):
""" Number of symbols in the table
"""
return self['sh_size'] // self['sh_entsize'] - 1
def get_symbol(self, n):
""" Get the symbol at index #n from the table (Symbol object).
It begins at 1 and not 0 since the first entry is used to
store the current version of the syminfo table.
"""
# Grab the symbol's entry from the stream
entry_offset = self['sh_offset'] + n * self['sh_entsize']
entry = struct_parse(
self.structs.Elf_Sunw_Syminfo,
self.stream,
stream_pos=entry_offset)
# Find the symbol name in the associated symbol table
name = self.symboltable.get_symbol(n).name
return Symbol(entry, name)
def iter_symbols(self):
""" Yield all the symbols in the table
"""
for i in range(1, self.num_symbols() + 1):
yield self.get_symbol(i)
class NoteSection(Section):
""" ELF NOTE section. Knows how to parse notes.
"""
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
others.
"""
return iter_notes(self.elffile, self['sh_offset'], self['sh_size'])
class StabSection(Section):
""" ELF stab section.
"""
def iter_stabs(self):
""" Yield all stab entries. Result type is ELFStructs.Elf_Stabs.
"""
offset = self['sh_offset']
size = self['sh_size']
end = offset + size
while offset < end:
stabs = struct_parse(
self.structs.Elf_Stabs,
self.elffile.stream,
stream_pos=offset)
stabs['n_offset'] = offset
offset += self.structs.Elf_Stabs.sizeof()
self.stream.seek(offset)
yield stabs
class ARMAttribute(object):
""" ARM attribute object - representing a build attribute of ARM ELF files.
"""
def __init__(self, structs, stream):
self._tag = struct_parse(structs.Elf_Attribute_Tag, stream)
self.extra = None
if self.tag in ('TAG_FILE', 'TAG_SECTION', 'TAG_SYMBOL'):
self.value = struct_parse(structs.Elf_word('value'), stream)
if self.tag != 'TAG_FILE':
self.extra = []
s_number = struct_parse(self.structs.Elf_uleb128('s_number'),
self.stream
)
while s_number != 0:
self.extra.append(s_number)
s_number = struct_parse(self.structs.Elf_uleb128('s_number'),
self.stream
)
elif self.tag in ('TAG_CPU_RAW_NAME', 'TAG_CPU_NAME', 'TAG_CONFORMANCE'):
self.value = struct_parse(structs.Elf_ntbs('value',
encoding='utf-8'),
stream)
elif self.tag == 'TAG_COMPATIBILITY':
self.value = struct_parse(structs.Elf_uleb128('value'), stream)
self.extra = struct_parse(structs.Elf_ntbs('vendor_name',
encoding='utf-8'),
stream)
elif self.tag == 'TAG_ALSO_COMPATIBLE_WITH':
self.value = ARMAttribute(structs, stream)
if type(self.value.value) is not str:
nul = struct_parse(structs.Elf_byte('nul'), stream)
elf_assert(null_byte == 0,
"Invalid terminating byte %r, expecting NUL." % nul)
else:
self.value = struct_parse(structs.Elf_uleb128('value'), stream)
@property
def tag(self):
return self._tag['tag']
def __repr__(self):
s = '<ARMAttribute (%s): %r>' % (self.tag, self.value)
s += ' %s' % self.extra if self.extra is not None else ''
return s
class ARMAttributesSubsubsection(object):
""" Subsubsection of an ELF .ARM.attributes section's subsection.
"""
def __init__(self, stream, structs, offset):
self.stream = stream
self.offset = offset
self.structs = structs
self.header = ARMAttribute(self.structs, self.stream)
self.attr_start = self.stream.tell()
def iter_attributes(self, tag=None):
""" Yield all attributes (limit to |tag| if specified).
"""
for attribute in self._make_attributes():
if tag is None or attribute.tag == tag:
yield attribute
@property
def num_attributes(self):
""" Number of attributes in the subsubsection.
"""
return sum(1 for _ in self.iter_attributes()) + 1
@property
def attributes(self):
""" List of all attributes in the subsubsection.
"""
return [self.header] + list(self.iter_attributes())
def _make_attributes(self):
""" Create all attributes for this subsubsection except the first one
which is the header.
"""
end = self.offset + self.header.value
self.stream.seek(self.attr_start)
while self.stream.tell() != end:
yield ARMAttribute(self.structs, self.stream)
def __repr__(self):
s = "<ARMAttributesSubsubsection (%s): %d bytes>"
return s % (self.header.tag[4:], self.header.value)
class ARMAttributesSubsection(object):
""" Subsection of an ELF .ARM.attributes section.
"""
def __init__(self, stream, structs, offset):
self.stream = stream
self.offset = offset
self.structs = structs
self.header = struct_parse(self.structs.Elf_Attr_Subsection_Header,
self.stream,
self.offset
)
self.subsubsec_start = self.stream.tell()
def iter_subsubsections(self, scope=None):
""" Yield all subsubsections (limit to |scope| if specified).
"""
for subsubsec in self._make_subsubsections():
if scope is None or subsubsec.header.tag == scope:
yield subsubsec
@property
def num_subsubsections(self):
""" Number of subsubsections in the subsection.
"""
return sum(1 for _ in self.iter_subsubsections())
@property
def subsubsections(self):
""" List of all subsubsections in the subsection.
"""
return list(self.iter_subsubsections())
def _make_subsubsections(self):
""" Create all subsubsections for this subsection.
"""
end = self.offset + self['length']
self.stream.seek(self.subsubsec_start)
while self.stream.tell() != end:
subsubsec = ARMAttributesSubsubsection(self.stream,
self.structs,
self.stream.tell())
self.stream.seek(self.subsubsec_start + subsubsec.header.value)
yield subsubsec
def __getitem__(self, name):
""" Implement dict-like access to header entries.
"""
return self.header[name]
def __repr__(self):
s = "<ARMAttributesSubsection (%s): %d bytes>"
return s % (self.header['vendor_name'], self.header['length'])
class ARMAttributesSection(Section):
""" ELF .ARM.attributes section.
"""
def __init__(self, header, name, elffile):
super(ARMAttributesSection, self).__init__(header, name, elffile)
fv = struct_parse(self.structs.Elf_byte('format_version'),
self.stream,
self['sh_offset']
)
elf_assert(chr(fv) == 'A',
"Unknown attributes version %s, expecting 'A'." % chr(fv)
)
self.subsec_start = self.stream.tell()
def iter_subsections(self, vendor_name=None):
""" Yield all subsections (limit to |vendor_name| if specified).
"""
for subsec in self._make_subsections():
if vendor_name is None or subsec['vendor_name'] == vendor_name:
yield subsec
@property
def num_subsections(self):
""" Number of subsections in the section.
"""
return sum(1 for _ in self.iter_subsections())
@property
def subsections(self):
""" List of all subsections in the section.
"""
return list(self.iter_subsections())
def _make_subsections(self):
""" Create all subsections for this section.
"""
end = self['sh_offset'] + self.data_size
self.stream.seek(self.subsec_start)
while self.stream.tell() != end:
subsec = ARMAttributesSubsection(self.stream,
self.structs,
self.stream.tell())
self.stream.seek(self.subsec_start + subsec['length'])
yield subsec