Files
revng-revng/scripts/merge-dynamic.py
T
Alessandro Di Federico 076aeac7f3 Move and rename all files
This commit moves around most files. The new directory structure is as
follows:

* `lib/$LIBRARY/`: contains a library, i.e., a set of `.cpp` files used
  by multiple libraries/tools.
* `include/revng/$LIBRARY/`: contains the public headers associated to
  the library in `lib/$LIBRARY/`.
* `tools/$TOOL/`: directory where all the `.cpp` files (and private
  headers) for a tool reside. Currently we have two tools: `revamb` and
  `revamb-dump`.

On top of this, all file names are now in camel case.
2018-10-03 23:11:12 +02:00

644 lines
23 KiB
Python
Executable File

#!/usr/bin/env python
# * DR_{INIT,FINI}_ARRAY?
import argparse
import os
import shutil
import stat
import struct
import sys
from io import BytesIO
from pprint import pprint
from elftools.elf.elffile import ELFFile
from elftools.elf.constants import P_FLAGS
try:
from elftools.elf.enums import ENUM_P_TYPE
except ImportError:
from elftools.elf.enums import ENUM_P_TYPE_BASE as ENUM_P_TYPE
from elftools.elf.enums import ENUM_RELOC_TYPE_MIPS
from elftools.elf.enums import ENUM_RELOC_TYPE_i386
from elftools.elf.enums import ENUM_RELOC_TYPE_x64
from elftools.elf.enums import ENUM_RELOC_TYPE_ARM
def log(message):
global verbose
if verbose:
sys.stderr.write(message + "\n")
def set_executable(path):
st = os.stat(path)
os.chmod(path, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
def rebuild_r_info(relocation, is64):
if is64:
relocation.r_info = (relocation.r_info_sym << 32) | relocation.r_info_type
else:
relocation.r_info = (relocation.r_info_sym << 8) | relocation.r_info_type
def file_size(file):
file.seek(0, 2)
return file.tell()
def only(iterable):
iterable = list(iterable)
assert len(iterable) == 1
return iterable[0]
def first_or_none(iterable):
iterable = list(iterable)
return iterable[0] if len(iterable) != 0 else None
def read_at_offset(file, start, size):
file.seek(start)
return file.read(size)
def chunks(list, size):
for i in range(0, len(list), size):
yield list[i:i + size]
def parse(buffer, struct):
struct_size = struct.sizeof()
assert len(buffer) % struct_size == 0
return list(map(struct.parse, chunks(buffer, struct_size)))
def serialize(list, struct):
return b"".join(map(struct.build, list))
def overlaps(start1, size1, start2, size2):
return (start1 + size1) >= start2 and (start2 + size2) >= start1
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.tag("DT_PLTREL") == 7
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)
self.symbols_count = max([relocation.r_info_sym
for relocation
in self.reldyn_relocations + self.relplt_relocations]) + 1
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)
if self.has_tag("DT_VERNEED"):
self.verneeds = []
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, list):
stream = BytesIO()
verneed_struct = self.elf.structs.Elf_Verneed
vernaux_struct = self.elf.structs.Elf_Vernaux
verneed_position = 0
for verneed in list:
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 = "<" + size_map[size]
else:
format = ">" + size_map[size]
return [struct.unpack(format, chunk)[0] for chunk in chunks(buffer, size)]
def serialize_ints(self, list, size):
size_map = {1: "B", 2: "H", 4: "I", 8: "Q"}
if self.elf.little_endian:
format = "<" + size_map[size]
else:
format = ">" + size_map[size]
return b"".join([struct.pack(format, number) for number in list])
def current_position(self):
return self.file.tell()
def read_struct(self, struct):
buffer = self.file.read(struct.sizeof())
assert len(buffer) == struct.sizeof()
return only(parse(buffer, struct))
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();
else:
size = self.tag(size_tag) * scale
else:
size = scale
if self.has_tag(address_tag):
return self.read_address(self.tag(address_tag), size)
else:
return bytes()
def seek_address(self, address):
self.file.seek(only(self.elf.address_offsets(address)))
def read_address(self, address, size):
self.seek_address(address)
result = self.file.read(size)
assert len(result) == size
return result
def segment_by_type(self, type):
return [segment
for segment
in self.elf.iter_segments()
if segment.header.p_type == type]
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 align(start, alignment):
return ((start + alignment - 1) / alignment) * alignment
def main():
parser = argparse.ArgumentParser(description=("Merge the dynamic portions of "
+ "the translate ELF with the "
+ "one from the host ELF."))
parser.add_argument("to_extend",
metavar="TO_EXTEND",
help="The ELF extend.")
parser.add_argument("source",
metavar="SOURCE",
help="The original ELF.")
parser.add_argument("output",
metavar="OUTPUT",
nargs="?",
default="-",
help="The output ELF.")
parser.add_argument("--verbose",
action="store_true",
help="Print debug information and warnings.")
parser.add_argument("--base",
metavar="ADDRESS",
default="0x50000000",
help=("The base address where dynamic object have been" +
" loaded."))
args = parser.parse_args()
global verbose
verbose = args.verbose
with (sys.stdout
if args.output == "-"
else open(args.output, "wb")) as output_file, \
open(args.source, "rb") as source_file, \
open(args.to_extend, "rb") as to_extend_file:
to_extend_elf = ParsedElf(to_extend_file)
source_elf = ParsedElf(source_file)
# If the original ELF was not dynamic, we don't have to do anything
if not source_elf.is_dynamic:
to_extend_elf.file.seek(0)
shutil.copyfileobj(to_extend_elf.file, output_file)
if args.output != "-":
set_executable(args.output)
return 0
assert to_extend_elf.is_dynamic
assert to_extend_elf.elf.header.e_machine == source_elf.elf.header.e_machine
if to_extend_elf.elf.little_endian:
parse32 = "<I"
parse64 = "<Q"
else:
parse32 = ">I"
parse64 = ">Q"
if to_extend_elf.elf.elfclass == 64:
parse = parse64
else:
parse = parse32
relocation_offset = 0
if source_elf.elf.header.e_type == "ET_DYN":
relocation_offset = int(args.base, base=0)
machine = to_extend_elf.elf.header.e_machine
if machine == "EM_X86_64":
relative_relocation = ENUM_RELOC_TYPE_x64["R_X86_64_RELATIVE"]
elif machine == "EM_ARM":
relative_relocation = ENUM_RELOC_TYPE_ARM["R_ARM_RELATIVE"]
elif machine == "EM_386":
relative_relocation = ENUM_RELOC_TYPE_i386["R_386_RELATIVE"]
elif machine == "EM_MIPS":
# TODO: check
relative_relocation = 0xffffffff
elif machine == "EM_S390":
relative_relocation = 12 # R_390_RELATIVE
else:
assert False
# Prepare new .dynstr
new_dynstr = to_extend_elf.dynstr
dynstr_offset = len(new_dynstr)
assert new_dynstr[-1] == b"\x00"
new_dynstr += source_elf.dynstr
to_extend_size = file_size(to_extend_file)
base_address = min([segment.header.p_vaddr
for segment
in to_extend_elf.segment_by_type("PT_LOAD")])
alignment = 0x1000
estimated_size = align(to_extend_elf.dynamic_size()
+ source_elf.dynamic_size(), alignment)
start_address = align(base_address + to_extend_size, alignment)
matching_segment = (to_extend_elf.segment_by_range(start_address,
estimated_size)
or source_elf.segment_by_range(start_address,
estimated_size))
while matching_segment is not None:
log("Discarding {} since overlaps the following segment:\n"
+ " {}".format(hex(start_address),
matching_segment.header))
start_address = align(matching_segment.header.p_vaddr
+ matching_segment.header.p_memsz,
alignment)
matching_segment = (to_extend_elf.segment_by_range(start_address,
estimated_size)
or source_elf.segment_by_range(start_address,
estimated_size))
padding = start_address - base_address - to_extend_size
log("Padding size: {}".format(padding))
new_dynstr_offset = to_extend_size + padding
# Prepare new .dynsym
new_dynsym = to_extend_elf.dynsym
defined_symbols = []
for index, symbol in enumerate(to_extend_elf.symbols):
if symbol.st_shndx != "SHN_UNDEF":
defined_symbols.append(index)
dynsym_offset = len(to_extend_elf.symbols) - 1
new_symbols = list(source_elf.symbols)[1:]
for index, symbol in enumerate(new_symbols):
symbol.st_name += dynstr_offset
if symbol.st_value != 0:
symbol.st_value += relocation_offset
if symbol.st_shndx != "SHN_UNDEF":
defined_symbols.append(index + dynsym_offset + 1)
new_dynsym += serialize(new_symbols, source_elf.elf.structs.Elf_Sym)
new_dynsym_offset = new_dynstr_offset + len(new_dynstr)
# Prepare new .dynrel
new_reldyn = to_extend_elf.reldyn
new_relocations = (source_elf.relplt_relocations
+ source_elf.reldyn_relocations)
for relocation in new_relocations:
if relocation.r_info_sym != 0:
relocation.r_info_sym += dynsym_offset
if relocation.r_info_type == relative_relocation:
relocation.r_addend += relocation_offset
relocation.r_offset += relocation_offset
rebuild_r_info(relocation, to_extend_elf.elf.elfclass == 64)
new_reldyn += serialize(new_relocations, source_elf.relstruct)
new_reldyn_offset = (new_dynsym_offset + len(new_dynsym))
# 1. Find the highest version index in to_extend_elf
version_index_offset = 0
for verneed in to_extend_elf.verneeds:
for vernaux in verneed[1]:
version_index_offset = max(version_index_offset, vernaux.vna_other)
version_index_offset -= 1
# 2. Go though all the version indexes of source_elf and, unless they
# are 0 or 1, increase them by the previous value
# 3. Concat .gnu.version
new_gnuversion_offset = new_reldyn_offset + len(new_reldyn)
new_gnuversion = to_extend_elf.gnuversion
new_gnuversion_indices = source_elf.gnuversion_indices[1:]
for index, value in enumerate(new_gnuversion_indices):
if value != 0 and value != 1:
new_gnuversion_indices[index] += version_index_offset
new_gnuversion += source_elf.serialize_ints(new_gnuversion_indices, 2)
# 4. Go through .gnu.version_r and, for each verneed add the string
# table offset to the library name.
# 5. Go through each Vernaux and increment vna_name
new_verneeds = to_extend_elf.verneeds
# Find the start position of the last verneed
position = 0
for verneed in new_verneeds:
position += verneed[0].vn_next
# Update the pointer to the next element of the last verneed to the end
# of the buffer
new_verneeds_size = len(to_extend_elf.serialize_verneeds(new_verneeds))
new_verneeds[-1][0].vn_next = new_verneeds_size - position
new_gnuversion_r_offset = new_gnuversion_offset + len(new_gnuversion)
# Fix verneeds and vernaux in source_elf
for verneed in source_elf.verneeds:
verneed[0].vn_file += dynstr_offset
for vernaux in verneed[1]:
vernaux.vna_name += dynstr_offset
vernaux.vna_other += version_index_offset
new_verneeds += source_elf.verneeds
new_gnuversion_r = source_elf.serialize_verneeds(new_verneeds)
# We now build a fake old-style (non-GNU) hash table. Basically we have a
# single bucketup pointing to the first defined symbol, which in turn will
# point to the second one and so on, up to the last which has identifier 0
# and stops the search for a symbol. Basically, we transformed a hash
# lookup in a linear search.
# TODO: implement an actual hash table, possibly GNU
# nbucket + nchain + [buckets] + [chains]
symbols_count = dynsym_offset + len(new_symbols) + 1
chain = [0 for i in range(symbols_count)]
for i in range(len(defined_symbols) - 1):
chain[defined_symbols[i]] = defined_symbols[i + 1]
chain = [struct.pack(parse32, chain[i]) for i in range(len(chain))]
chain = "".join(chain)
new_hash = (struct.pack(parse32, 1)
+ struct.pack(parse32, symbols_count)
+ struct.pack(parse32, defined_symbols[0])
+ chain)
new_hash_offset = new_gnuversion_r_offset + len(new_gnuversion_r)
# Prepare new .dynamic
new_dynamic_tags = [dt
for dt
in to_extend_elf.dynamic.iter_tags()]
last_dt_needed = -1
libraries = set()
to_address = lambda offset: start_address + offset - new_dynstr_offset
for index, dynamic_tag in enumerate(new_dynamic_tags):
if dynamic_tag.entry.d_tag == "DT_STRTAB":
dynamic_tag.entry.d_val = to_address(new_dynstr_offset)
elif dynamic_tag.entry.d_tag == "DT_STRSZ":
dynamic_tag.entry.d_val = len(new_dynstr)
elif dynamic_tag.entry.d_tag in ["DT_REL", "DT_RELA"]:
dynamic_tag.entry.d_val = to_address(new_reldyn_offset)
elif dynamic_tag.entry.d_tag in ["DT_RELSZ", "DT_RELASZ"]:
dynamic_tag.entry.d_val = len(new_reldyn)
elif dynamic_tag.entry.d_tag == "DT_SYMTAB":
dynamic_tag.entry.d_val = to_address(new_dynsym_offset)
elif dynamic_tag.entry.d_tag == "DT_NEEDED":
last_dt_needed = index
libraries.add(dynamic_tag.needed)
elif dynamic_tag.entry.d_tag == "DT_VERNEED":
dynamic_tag.entry.d_val = to_address(new_gnuversion_r_offset)
elif dynamic_tag.entry.d_tag == "DT_VERNEEDNUM":
dynamic_tag.entry.d_val = len(new_verneeds)
elif dynamic_tag.entry.d_tag == "DT_VERSYM":
dynamic_tag.entry.d_val = to_address(new_gnuversion_offset)
elif dynamic_tag.entry.d_tag == "DT_GNU_HASH":
dynamic_tag.entry.d_tag = "DT_HASH"
dynamic_tag.entry.d_val = to_address(new_hash_offset)
# This is done a link-time
# for dt_needed in reversed(source_elf.dt_by_tag("DT_NEEDED")):
# if not dt_needed.needed in libraries:
# dt_needed.entry.d_val += dynstr_offset
# new_dynamic_tags.insert(last_dt_needed + 1, dt_needed)
new_dynamic_tags = [dt.entry for dt in new_dynamic_tags]
new_dynamic = serialize(new_dynamic_tags, source_elf.elf.structs.Elf_Dyn)
new_dynamic_offset = new_hash_offset + len(new_hash)
new_section_headers_offset = new_dynamic_offset + len(new_dynamic)
new_sections = to_extend_elf.sections
for section in new_sections:
if section.name == ".dynstr":
section.header.sh_addr = to_address((new_dynstr_offset))
section.header.sh_offset = new_dynstr_offset
section.header.sh_size = len(new_dynstr)
elif section.name == ".dynsym":
section.header.sh_addr = to_address((new_dynsym_offset))
section.header.sh_offset = new_dynsym_offset
section.header.sh_size = len(new_dynsym)
elif section.name in [".rela.dyn", ".rel.dyn"]:
section.header.sh_addr = to_address((new_reldyn_offset))
section.header.sh_offset = new_reldyn_offset
section.header.sh_size = len(new_reldyn)
elif section.name == ".dynamic":
section.header.sh_addr = to_address((new_dynamic_offset))
section.header.sh_offset = new_dynamic_offset
section.header.sh_size = len(new_dynamic)
elif section.name == ".gnu.version":
section.header.sh_addr = to_address((new_gnuversion_offset))
section.header.sh_offset = new_gnuversion_offset
section.header.sh_size = len(new_gnuversion)
elif section.name == ".gnu.version_r":
section.header.sh_addr = to_address((new_gnuversion_r_offset))
section.header.sh_offset = new_gnuversion_r_offset
section.header.sh_size = len(new_gnuversion_r)
section.header.sh_info = len(new_verneeds)
new_section_headers = serialize([section.header
for section
in new_sections],
source_elf.elf.structs.Elf_Shdr)
# Prepare new program headers
new_program_headers_offset = (new_section_headers_offset
+ len(new_section_headers))
segment_header_size = source_elf.elf.structs.Elf_Phdr.sizeof()
new_segments = [segment.header for segment in to_extend_elf.segments]
new_program_headers_size =(len(new_segments) + 1) * segment_header_size
for segment in new_segments:
if segment.p_type == "PT_DYNAMIC":
segment.p_filesz = len(new_dynamic)
segment.p_memsz = len(new_dynamic)
segment.p_paddr = to_address((new_dynamic_offset))
segment.p_vaddr = to_address((new_dynamic_offset))
segment.p_offset = new_dynamic_offset
elif segment.p_type == "PT_PHDR":
segment.p_filesz = new_program_headers_size
segment.p_memsz = new_program_headers_size
segment.p_paddr = to_address((new_program_headers_offset))
segment.p_vaddr = to_address((new_program_headers_offset))
segment.p_offset = new_program_headers_offset
new_segment_size = (new_program_headers_offset
+ new_program_headers_size
- new_dynstr_offset)
if new_segment_size > estimated_size:
log("Warning: the new segment for dynamic sections is larger than"
+ " expected:\n Expected: {}\n Actual: {}".format(estimated_size,
new_segment_size))
zeros = "\x00" * segment_header_size
new_segment = source_elf.elf.structs.Elf_Phdr.parse(zeros)
new_segment.p_type = ENUM_P_TYPE["PT_LOAD"]
new_segment.p_memsz = new_segment_size
new_segment.p_flags = P_FLAGS.PF_R | P_FLAGS.PF_W
new_segment.p_offset = new_dynstr_offset
new_segment.p_vaddr = start_address
new_segment.p_align = alignment
new_segment.p_filesz = new_segment_size
new_segment.p_paddr = start_address
new_segments += [new_segment]
new_program_headers = serialize(new_segments,
source_elf.elf.structs.Elf_Phdr)
# Prepare new ELF header
new_elf_header = to_extend_elf.elf.header
new_elf_header.e_phnum = len(new_segments)
new_elf_header.e_phoff = new_program_headers_offset
new_elf_header.e_shnum = len(new_sections)
new_elf_header.e_shoff = new_section_headers_offset
new_elf_header = to_extend_elf.elf.structs.Elf_Ehdr.build(new_elf_header)
# Write new ELF header
output_file.write(new_elf_header)
# Write rest of the to_extend file
to_extend_elf.file.seek(len(new_elf_header))
shutil.copyfileobj(to_extend_elf.file, output_file)
# Align to page
output_file.write(b"\x00" * padding)
# Write new .dynstr
assert output_file.tell() == new_dynstr_offset
output_file.write(new_dynstr)
# Write new .dynsym
assert output_file.tell() == new_dynsym_offset
output_file.write(new_dynsym)
# Write new .rel.dyn
assert output_file.tell() == new_reldyn_offset
output_file.write(new_reldyn)
# Write new .rel.dyn
assert output_file.tell() == new_gnuversion_offset
output_file.write(new_gnuversion)
# Write new .rel.dyn
assert output_file.tell() == new_gnuversion_r_offset
output_file.write(new_gnuversion_r)
# Write new .hash
assert output_file.tell() == new_hash_offset
output_file.write(new_hash)
# Write new .dynamic
assert output_file.tell() == new_dynamic_offset
output_file.write(new_dynamic)
# Write new section headers
assert output_file.tell() == new_section_headers_offset
output_file.write(new_section_headers)
# Write new program headers
assert output_file.tell() == new_program_headers_offset
output_file.write(new_program_headers)
if args.output != "-":
set_executable(args.output)
return 0
if __name__ == "__main__":
sys.exit(main())