Files
revng-revng/scripts/revng_support/support.py
Massimo Fioravanti d696197597 scripts/revng: adopt revng-pipeline
The revng-translate tool is now a driver for revng-pipeline to perform
the translation.

This commit also drops computing the `-load` "roots". It shouldn't
really be necessary.
2022-02-08 00:05:03 +01:00

206 lines
6.3 KiB
Python

import sys
import os
import glob
import re
import signal
import subprocess
import shlex
from itertools import chain
from elftools.elf.dynamic import DynamicSegment
from elftools.elf.elffile import ELFFile
try:
from shutil import which
except ImportError:
from backports.shutil_which import which
log_commands = False
def shlex_join(split_command):
return ' '.join(shlex.quote(arg) for arg in split_command)
def log_error(msg):
sys.stderr.write(msg + "\n")
def relative(path):
return os.path.relpath(path, ".")
def wrap(args, command_prefix):
return command_prefix + args
def set_verbose():
global log_commands
log_commands = True
def run(command, command_prefix, override={}):
if is_executable(command[0]):
command = wrap(command, command_prefix)
global log_commands
if log_commands:
cwd = os.getcwd().rstrip("/")
if command[0].startswith(cwd):
program_path = "." + command[0][len(cwd):]
else:
program_path = command[0]
sys.stderr.write("{}\n\n".format(
" \\\n ".join([program_path] + command[1:])))
signal.signal(signal.SIGINT, signal.SIG_IGN)
environment = dict(os.environ)
environment.update(override)
p = subprocess.Popen(command,
preexec_fn=lambda: signal.signal(signal.SIGINT,
signal.SIG_DFL),
env=environment)
if p.wait() != 0:
log_error("The following command exited with {}:\n{}".format(
p.returncode, shlex_join(command)))
sys.exit(p.returncode)
def is_executable(path):
with open(path, "rb") as program:
return program.read(4) == b"\x7fELF"
def is_dynamic(path):
with open(path, "rb") as input_file:
return len([segment
for segment
in ELFFile(input_file).iter_segments()
if segment.header.p_type == "PT_DYNAMIC"]) != 0
def get_command(command, search_path):
path = which(command, path=search_path)
if not path:
log_error("""Couldn't find "{}" in "{}".""".format(
command, search_path))
assert False
return os.path.abspath(path)
def interleave(base, repeat):
return list(sum(zip([repeat] * len(base), base), ()))
# Use in case different version of pyelftools might give str or bytes
def to_string(obj):
if type(obj) is str:
return obj
elif type(obj) is bytes:
return obj.decode("utf-8")
def get_elf_needed(path):
with open(path, "rb") as elf_file:
segments = [segment
for segment
in ELFFile(elf_file).iter_segments()
if type(segment) is DynamicSegment]
if len(segments) == 1:
needed = [to_string(tag.needed)
for tag
in segments[0].iter_tags()
if tag.entry.d_tag == "DT_NEEDED"]
runpath = [tag.runpath
for tag
in segments[0].iter_tags()
if tag.entry.d_tag == "DT_RUNPATH"]
assert len(runpath) < 2
if not runpath:
return needed
else:
runpaths = [runpath.replace("$ORIGIN",
os.path.dirname(path))
for runpath
in runpath[0].split(":")]
absolute_needed = []
for lib in needed:
found = False
for runpath in runpaths:
full_path = os.path.join(runpath, lib)
if os.path.isfile(full_path):
absolute_needed.append(full_path)
found = True
break
if not found:
absolute_needed.append(lib)
return absolute_needed
else:
return []
def collect_libraries(search_prefixes):
to_load = {}
for prefix in search_prefixes:
analyses_path = os.path.join(prefix, "lib", "revng", "analyses")
if not os.path.isdir(analyses_path):
continue
# Enumerate all the libraries containing analyses
for library in glob.glob(os.path.join(analyses_path, "*.so")):
basename = os.path.basename(library)
if basename not in to_load:
to_load[basename] = library
# Identify all the libraries that are dependencies of other libraries, i.e.,
# non-roots in the dependencies tree. Note that circular dependencies are
# not allowed.
dependencies = set(chain.from_iterable([get_elf_needed(path)
for path
in to_load.values()]))
return (list(to_load.values()), dependencies)
def handle_asan(dependencies, search_path):
libasan = [name
for name
in dependencies
if ("libasan." in name
or "libclang_rt.asan" in name)]
if len(libasan) != 1:
return []
libasan_path = relative(libasan[0])
original_asan_options = os.environ.get("ASAN_OPTIONS", "")
if original_asan_options:
asan_options = dict([option.split("=")
for option
in original_asan_options.split(":")])
else:
asan_options = dict()
asan_options["abort_on_error"] = "1"
new_asan_options = ":".join(["=".join(option)
for option
in asan_options.items()])
# Use `sh` instead of `env` since `env` sometimes is not a real executable
# but a shebang script spawning /usr/bin/coreutils, which makes gdb unhappy
return [get_command("sh", search_path),
"-c",
'LD_PRELOAD={} ASAN_OPTIONS={} '
'exec "$0" "$@"'.format(libasan_path, new_asan_options)]
def build_command_with_loads(command, args, search_path, search_prefixes):
(to_load, dependencies) = collect_libraries(search_prefixes)
prefix = handle_asan(dependencies, search_path)
return (prefix + [relative(get_command(command, search_path))]
+ interleave(to_load, "-load")
+ args)