mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
d696197597
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.
206 lines
6.3 KiB
Python
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)
|
|
|