blob: 7c2b74c56f124d78245eb53d2cd649b19203bd8e [file] [log] [blame]
#-------------------------------------------------------------------------------
# elftools: elf/relocation.py
#
# ELF relocations
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from collections import namedtuple
from ..common.exceptions import ELFRelocationError
from ..common.utils import elf_assert, struct_parse
from .sections import Section
from .enums import ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64
class Relocation(object):
""" Relocation object - representing a single relocation entry. Allows
dictionary-like access to the entry's fields.
Can be either a REL or RELA relocation.
"""
def __init__(self, entry, elffile):
self.entry = entry
self.elffile = elffile
def is_RELA(self):
""" Is this a RELA relocation? If not, it's REL.
"""
return 'r_addend' in self.entry
def __getitem__(self, name):
""" Dict-like access to entries
"""
return self.entry[name]
def __repr__(self):
return '<Relocation (%s): %s>' % (
'RELA' if self.is_RELA() else 'REL',
self.entry)
def __str__(self):
return self.__repr__()
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
if self.header['sh_type'] == 'SHT_REL':
expected_size = self.elfstructs.Elf_Rel.sizeof()
self.entry_struct = self.elfstructs.Elf_Rel
elif self.header['sh_type'] == 'SHT_RELA':
expected_size = self.elfstructs.Elf_Rela.sizeof()
self.entry_struct = self.elfstructs.Elf_Rela
else:
elf_assert(False, 'Unknown relocation type section')
elf_assert(
self.header['sh_entsize'] == expected_size,
'Expected sh_entsize of SHT_REL section to be %s' % expected_size)
def is_RELA(self):
""" Is this a RELA relocation section? If not, it's REL.
"""
return self.header['sh_type'] == 'SHT_RELA'
def num_relocations(self):
""" Number of relocations in the section
"""
return self['sh_size'] // self['sh_entsize']
def get_relocation(self, n):
""" Get the relocation at index #n from the section (Relocation object)
"""
entry_offset = self['sh_offset'] + n * self['sh_entsize']
entry = struct_parse(
self.entry_struct,
self.stream,
stream_pos=entry_offset)
return Relocation(entry, self.elffile)
def iter_relocations(self):
""" Yield all the relocations in the section
"""
for i in range(self.num_relocations()):
yield self.get_relocation(i)
class RelocationHandler(object):
""" Handles the logic of relocations in ELF files.
"""
def __init__(self, elffile):
self.elffile = elffile
def find_relocations_for_section(self, section):
""" Given a section, find the relocation section for it in the ELF
file. Return a RelocationSection object, or None if none was
found.
"""
reloc_section_names = (
b'.rel' + section.name,
b'.rela' + section.name)
# Find the relocation section aimed at this one. Currently assume
# that either .rel or .rela section exists for this section, but
# not both.
for relsection in self.elffile.iter_sections():
if ( isinstance(relsection, RelocationSection) and
relsection.name in reloc_section_names):
return relsection
return None
def apply_section_relocations(self, stream, reloc_section):
""" Apply all relocations in reloc_section (a RelocationSection object)
to the given stream, that contains the data of the section that is
being relocated. The stream is modified as a result.
"""
# The symbol table associated with this relocation section
symtab = self.elffile.get_section(reloc_section['sh_link'])
for reloc in reloc_section.iter_relocations():
self._do_apply_relocation(stream, reloc, symtab)
def _do_apply_relocation(self, stream, reloc, symtab):
# Preparations for performing the relocation: obtain the value of
# the symbol mentioned in the relocation, as well as the relocation
# recipe which tells us how to actually perform it.
# All peppered with some sanity checking.
if reloc['r_info_sym'] >= symtab.num_symbols():
raise ELFRelocationError(
'Invalid symbol reference in relocation: index %s' % (
reloc['r_info_sym']))
sym_value = symtab.get_symbol(reloc['r_info_sym'])['st_value']
reloc_type = reloc['r_info_type']
recipe = None
if self.elffile.get_machine_arch() == 'x86':
if reloc.is_RELA():
raise ELFRelocationError(
'Unexpected RELA relocation for x86: %s' % reloc)
recipe = self._RELOCATION_RECIPES_X86.get(reloc_type, None)
elif self.elffile.get_machine_arch() == 'x64':
if not reloc.is_RELA():
raise ELFRelocationError(
'Unexpected REL relocation for x64: %s' % reloc)
recipe = self._RELOCATION_RECIPES_X64.get(reloc_type, None)
if recipe is None:
raise ELFRelocationError(
'Unsupported relocation type: %s' % reloc_type)
# So now we have everything we need to actually perform the relocation.
# Let's get to it:
# 0. Find out which struct we're going to be using to read this value
# from the stream and write it back.
if recipe.bytesize == 4:
value_struct = self.elffile.structs.Elf_word('')
elif recipe.bytesize == 8:
value_struct = self.elffile.structs.Elf_word64('')
else:
raise ELFRelocationError('Invalid bytesize %s for relocation' %
recipe_bytesize)
# 1. Read the value from the stream (with correct size and endianness)
original_value = struct_parse(
value_struct,
stream,
stream_pos=reloc['r_offset'])
# 2. Apply the relocation to the value, acting according to the recipe
relocated_value = recipe.calc_func(
value=original_value,
sym_value=sym_value,
offset=reloc['r_offset'],
addend=reloc['r_addend'] if recipe.has_addend else 0)
# 3. Write the relocated value back into the stream
stream.seek(reloc['r_offset'])
value_struct.build_stream(relocated_value, stream)
# Relocations are represented by "recipes". Each recipe specifies:
# bytesize: The number of bytes to read (and write back) to the section.
# This is the unit of data on which relocation is performed.
# has_addend: Does this relocation have an extra addend?
# calc_func: A function that performs the relocation on an extracted
# value, and returns the updated value.
#
_RELOCATION_RECIPE_TYPE = namedtuple('_RELOCATION_RECIPE_TYPE',
'bytesize has_addend calc_func')
def _reloc_calc_identity(value, sym_value, offset, addend=0):
return value
def _reloc_calc_sym_plus_value(value, sym_value, offset, addend=0):
return sym_value + value
def _reloc_calc_sym_plus_value_pcrel(value, sym_value, offset, addend=0):
return sym_value + value - offset
def _reloc_calc_sym_plus_addend(value, sym_value, offset, addend=0):
return sym_value + addend
_RELOCATION_RECIPES_X86 = {
ENUM_RELOC_TYPE_i386['R_386_NONE']: _RELOCATION_RECIPE_TYPE(
bytesize=4, has_addend=False, calc_func=_reloc_calc_identity),
ENUM_RELOC_TYPE_i386['R_386_32']: _RELOCATION_RECIPE_TYPE(
bytesize=4, has_addend=False,
calc_func=_reloc_calc_sym_plus_value),
ENUM_RELOC_TYPE_i386['R_386_PC32']: _RELOCATION_RECIPE_TYPE(
bytesize=4, has_addend=False,
calc_func=_reloc_calc_sym_plus_value_pcrel),
}
_RELOCATION_RECIPES_X64 = {
ENUM_RELOC_TYPE_x64['R_X86_64_NONE']: _RELOCATION_RECIPE_TYPE(
bytesize=8, has_addend=True, calc_func=_reloc_calc_identity),
ENUM_RELOC_TYPE_x64['R_X86_64_64']: _RELOCATION_RECIPE_TYPE(
bytesize=8, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
ENUM_RELOC_TYPE_x64['R_X86_64_32']: _RELOCATION_RECIPE_TYPE(
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
ENUM_RELOC_TYPE_x64['R_X86_64_32S']: _RELOCATION_RECIPE_TYPE(
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
}