Files
revng-revng/python/revng/cli/merge_dynamic/parsed_elf.py
2022-04-20 09:57:08 +02:00

193 lines
6.4 KiB
Python

#
# This file is distributed under the MIT License. See LICENSE.md for details.
#
import struct
from io import BytesIO
from elftools.elf.elffile import ELFFile
from .util import chunks, first_or_none, only, parse
class ParsedElf:
def __init__(self, file):
self.file = file
self.elf = ELFFile(file)
self.is_dynamic = len(self.segment_by_type("PT_DYNAMIC")) != 0
if not self.is_dynamic:
return
self.segments = list(self.elf.iter_segments())
self.sections = list(self.elf.iter_sections())
self.dynamic = only(self.segment_by_type("PT_DYNAMIC"))
self.is_rela = (self.has_tag("DT_PLTREL") and self.tag("DT_PLTREL") == 7) or self.has_tag(
"DT_RELA"
)
self.dynstr = self.read_section("DT_STRTAB", "DT_STRSZ")
if self.is_rela:
self.reldyn = self.read_section("DT_RELA", "DT_RELASZ")
self.relstruct = self.elf.structs.Elf_Rela
else:
self.reldyn = self.read_section("DT_REL", "DT_RELSZ")
self.relstruct = self.elf.structs.Elf_Rel
self.reldyn_relocations = parse(self.reldyn, self.relstruct)
self.relplt = self.read_section("DT_JMPREL", "DT_PLTRELSZ")
self.relplt_relocations = parse(self.relplt, self.relstruct)
# New symbol counting code
reloc_info_sym = [
relocation.r_info_sym
for relocation in (self.reldyn_relocations + self.relplt_relocations)
]
if len(reloc_info_sym) > 0:
self.symbols_count = max(reloc_info_sym) + 1
else:
self.symbols_count = 0
self.dynsym = self.read_section("DT_SYMTAB", "DT_SYMENT", self.symbols_count)
self.symbols = parse(self.dynsym, self.elf.structs.Elf_Sym)
self.gnuversion = self.read_section("DT_VERSYM", scale=self.symbols_count * 2)
self.gnuversion_indices = self.parse_ints(self.gnuversion, 2)
self.verneeds = []
if self.has_tag("DT_VERNEED"):
verneed_count = self.tag("DT_VERNEEDNUM")
self.seek_address(self.tag("DT_VERNEED"))
verneed_position = self.current_position()
verneed_struct = self.elf.structs.Elf_Verneed
vernaux_struct = self.elf.structs.Elf_Vernaux
for _ in range(verneed_count):
verneed = self.read_struct(verneed_struct)
vernauxs = []
vernaux_position = verneed_position + verneed.vn_aux
self.file.seek(vernaux_position)
for _ in range(verneed.vn_cnt):
vernaux = self.read_struct(vernaux_struct)
vernauxs.append(vernaux)
vernaux_position += vernaux.vna_next
self.file.seek(vernaux_position)
self.verneeds.append((verneed, vernauxs))
verneed_position += verneed.vn_next
self.file.seek(verneed_position)
def serialize_verneeds(self, verneeds):
stream = BytesIO()
verneed_struct = self.elf.structs.Elf_Verneed
vernaux_struct = self.elf.structs.Elf_Vernaux
verneed_position = 0
for verneed in verneeds:
stream.write(verneed_struct.build(verneed[0]))
vernaux_position = verneed_position + verneed[0].vn_aux
for vernaux in verneed[1]:
stream.seek(vernaux_position)
stream.write(vernaux_struct.build(vernaux))
vernaux_position += vernaux.vna_next
verneed_position += verneed[0].vn_next
stream.seek(verneed_position)
return stream.getvalue()
def parse_ints(self, buffer, size):
assert len(buffer) % size == 0
size_map = {1: "B", 2: "H", 4: "I", 8: "Q"}
if self.elf.little_endian:
format_str = "<" + size_map[size]
else:
format_str = ">" + size_map[size]
return [struct.unpack(format_str, chunk)[0] for chunk in chunks(buffer, size)]
def serialize_ints(self, ints, size):
size_map = {1: "B", 2: "H", 4: "I", 8: "Q"}
if self.elf.little_endian:
format_str = "<" + size_map[size]
else:
format_str = ">" + size_map[size]
return b"".join([struct.pack(format_str, number) for number in ints])
def current_position(self):
return self.file.tell()
def read_struct(self, struct_def):
buffer = self.file.read(struct_def.sizeof())
assert len(buffer) == struct_def.sizeof()
return only(parse(buffer, struct_def))
def read_section(self, address_tag, size_tag=None, scale=1):
if size_tag is not None:
if not self.has_tag(size_tag):
return bytes()
size = self.tag(size_tag) * scale
else:
size = scale
if self.has_tag(address_tag):
return self.read_address(self.tag(address_tag), size)
return bytes()
def seek_address(self, address):
self.file.seek(only(self.elf.address_offsets(address)))
def read_address(self, address, size):
if size == 0:
return b""
self.seek_address(address)
result = self.file.read(size)
assert len(result) == size
return result
def segment_by_type(self, stype):
return [segment for segment in self.elf.iter_segments() if segment.header.p_type == stype]
def dt_by_tag(self, search):
return [tag for tag in self.dynamic.iter_tags() if tag.entry.d_tag == search]
def tag(self, tag):
return only(self.dt_by_tag(tag)).entry.d_val
def has_tag(self, tag):
return len(self.dt_by_tag(tag)) != 0
def segment_by_range(self, address, size):
return first_or_none(
[
segment
for segment in self.segment_by_type("PT_LOAD")
if overlaps(
address,
size,
segment.header.p_vaddr,
segment.header.p_memsz,
)
]
)
def dynamic_size(self):
return (
len(self.dynsym)
+ len(self.dynstr)
+ len(self.gnuversion)
+ len(self.reldyn)
+ self.dynamic.header.p_memsz
+ len(self.segments) * self.elf.structs.Elf_Phdr.sizeof()
+ len(self.sections) * self.elf.structs.Elf_Shdr.sizeof()
)
def overlaps(start1, size1, start2, size2):
return (start1 + size1) >= start2 and (start2 + size2) >= start1