blob: a75db22c8b4ca28096d88cebb727b6f438dd39f5 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import print_function
import argparse
import os
import subprocess
import sys
import tempfile
import pynacl.platform
# Target architecture for PNaCl can be set through the ``-arch``
# command-line argument, and when its value is ``env`` the following
# program environment variable is queried to figure out which
# architecture to target.
ARCH_ENV_VAR_NAME = 'PNACL_RUN_ARCH'
class Environment:
pass
env = Environment()
def SetupEnvironment():
# native_client/ directory
env.nacl_root = FindBaseDir()
env.toolchain_base = os.path.join(env.nacl_root,
'toolchain',
'%s_x86' % pynacl.platform.GetOS())
# Path to PNaCl toolchain
env.pnacl_base = os.path.join(env.toolchain_base, 'pnacl_newlib')
# QEMU
env.arm_root = os.path.join(env.toolchain_base, 'arm_trusted')
env.qemu_arm = os.path.join(env.arm_root, 'run_under_qemu_arm')
env.mips32_root = os.path.join(env.toolchain_base, 'mips_trusted')
env.qemu_mips32 = os.path.join(env.mips32_root, 'run_under_qemu_mips32')
# Path to 'readelf'
env.readelf = FindReadElf()
# Path to 'scons'
env.scons = os.path.join(env.nacl_root, 'scons')
# Library path for dynamic linker.
env.library_path = []
# Suppress -S -a
env.paranoid = False
# Only print commands, don't run them
env.dry_run = False
# Force a specific sel_ldr
env.force_sel_ldr = None
# Force a specific IRT
env.force_irt = None
# Don't print anything
env.quiet = False
# Arch (x86-32, x86-64, arm, mips32)
env.arch = None
# Trace in QEMU
env.trace = False
# Debug the nexe using the debug stub
env.debug = False
# PNaCl (as opposed to NaCl).
env.is_pnacl = False
def PrintBanner(output):
if not env.quiet:
lines = output.split('\n')
print('*' * 80)
for line in lines:
padding = ' ' * max(0, (80 - len(line)) // 2)
print(padding + output + padding)
print('*' * 80)
def PrintCommand(s):
if not env.quiet:
print()
print(s)
print()
def SetupArch(arch, allow_build=True):
'''Setup environment variables that require knowing the
architecture. We can only do this after we've seen the
nexe or once we've read -arch off the command-line.
'''
env.arch = arch
# Path to Native NaCl toolchain (glibc)
# MIPS does not have glibc support.
if arch != 'mips32':
toolchain_arch, tooldir_arch, libdir = {
'x86-32': ('x86', 'x86_64', 'lib32'),
'x86-64': ('x86', 'x86_64', 'lib'),
'arm': ('arm', 'arm', 'lib'),
}[arch]
env.nnacl_tooldir = os.path.join(env.toolchain_base,
'nacl_%s_glibc' % toolchain_arch,
'%s-nacl' % tooldir_arch)
env.nnacl_libdir = os.path.join(env.nnacl_tooldir, libdir)
env.sel_ldr = FindOrBuildSelLdr(allow_build=allow_build)
env.irt = FindOrBuildIRT(allow_build=allow_build)
if arch == 'arm':
env.elf_loader = FindOrBuildElfLoader(allow_build=allow_build)
def SetupLibC(arch, is_dynamic):
if is_dynamic:
if env.is_pnacl:
libdir = os.path.join(env.pnacl_base, 'lib-' + arch)
else:
libdir = env.nnacl_libdir
env.runnable_ld = os.path.join(libdir, 'runnable-ld.so')
env.library_path.append(libdir)
def main(argv):
SetupEnvironment()
return_code = 0
sel_ldr_options = []
# sel_ldr's "quiet" options need to come early in the command line
# to suppress noisy output from processing other options, like -Q.
sel_ldr_quiet_options = []
nexe, nexe_params = ArgSplit(argv[1:])
try:
if env.is_pnacl:
nexe = Translate(env.arch, nexe)
# Read the ELF file info
if env.is_pnacl and env.dry_run:
# In a dry run, we don't actually run pnacl-translate, so there is
# no nexe for readelf. Fill in the information manually.
arch = env.arch
is_dynamic = False
is_glibc_static = False
else:
arch, is_dynamic, is_glibc_static = ReadELFInfo(nexe)
# Add default sel_ldr options
if not env.paranoid:
# Enable mmap() for loading the nexe, so that 'perf' gives usable output.
# Note that this makes the sandbox unsafe if the mmap()'d nexe gets
# modified while sel_ldr is running.
os.environ['NACL_FAULT_INJECTION'] = \
'ELF_LOAD_BYPASS_DESCRIPTOR_SAFETY_CHECK=GF1/999'
sel_ldr_options += ['-a']
# -S signal handling is not supported on windows, but otherwise
# it is useful getting the address of crashes.
if not pynacl.platform.IsWindows():
sel_ldr_options += ['-S']
# X86-64 glibc static has validation problems without stub out (-s)
if arch == 'x86-64' and is_glibc_static:
sel_ldr_options += ['-s']
if env.quiet:
# Don't print sel_ldr logs
# These need to be at the start of the arglist for full effectiveness.
# -q means quiet most stderr warnings.
# -l /dev/null means log to /dev/null.
sel_ldr_quiet_options = ['-q', '-l', '/dev/null']
if env.debug:
# Disabling validation (-c) is used by the debug stub test.
# TODO(dschuff): remove if/when it's no longer necessary
sel_ldr_options += ['-c', '-c', '-g']
# Tell the user
if is_dynamic:
extra = 'DYNAMIC'
else:
extra = 'STATIC'
PrintBanner('%s is %s %s' % (os.path.basename(nexe),
arch.upper(), extra))
# Setup architecture-specific environment variables
SetupArch(arch)
# Setup LibC-specific environment variables
SetupLibC(arch, is_dynamic)
# Add IRT to sel_ldr options.
if env.irt:
sel_ldr_options += ['-B', env.irt]
# The NaCl dynamic loader prefers posixy paths.
nexe_path = os.path.abspath(nexe)
nexe_path = nexe_path.replace('\\', '/')
sel_ldr_nexe_args = [nexe_path] + nexe_params
if is_dynamic:
ld_library_path = ':'.join(env.library_path)
if arch == 'arm':
sel_ldr_nexe_args = [env.elf_loader, '--interp-prefix',
env.nnacl_tooldir] + sel_ldr_nexe_args
sel_ldr_options += ['-E', 'LD_LIBRARY_PATH=' + ld_library_path]
else:
sel_ldr_nexe_args = [env.runnable_ld, '--library-path',
ld_library_path] + sel_ldr_nexe_args
# Setup sel_ldr arguments.
sel_ldr_args = sel_ldr_options + ['--'] + sel_ldr_nexe_args
# Run sel_ldr!
retries = 0
try:
if hasattr(env, 'retries'):
retries = int(env.retries)
except ValueError:
pass
collate = env.collate or retries > 0
input = sys.stdin.read() if collate else None
for iter in range(1 + max(retries, 0)):
output = RunSelLdr(sel_ldr_args, quiet_args=sel_ldr_quiet_options,
collate=collate, stdin_string=input)
if env.last_return_code < 128:
# If the application crashes, we expect a 128+ return code.
break
sys.stdout.write(output or '')
return_code = env.last_return_code
finally:
if env.is_pnacl:
# Clean up the .nexe that was created.
try:
os.remove(nexe)
except:
pass
return return_code
def RunSelLdr(args, quiet_args=[], collate=False, stdin_string=None):
"""Run the sel_ldr command and optionally capture its output.
Args:
args: A string list containing the command and arguments.
collate: Whether to capture stdout+stderr (rather than passing
them through to the terminal).
stdin_string: Text to send to the command via stdin. If None, stdin is
inherited from the caller.
Returns:
A string containing the concatenation of any captured stdout plus
any captured stderr.
"""
prefix = []
# The bootstrap loader args (--r_debug, --reserved_at_zero) need to
# come before quiet_args.
bootstrap_loader_args = []
arch = pynacl.platform.GetArch3264()
if arch != pynacl.platform.ARCH3264_ARM and env.arch == 'arm':
prefix = [ env.qemu_arm, '-cpu', 'cortex-a9']
if env.trace:
prefix += ['-d', 'in_asm,op,exec,cpu']
args = ['-Q'] + args
if arch != pynacl.platform.ARCH3264_MIPS32 and env.arch == 'mips32':
prefix = [env.qemu_mips32]
if env.trace:
prefix += ['-d', 'in_asm,op,exec,cpu']
args = ['-Q'] + args
# Use the bootstrap loader on linux.
if pynacl.platform.IsLinux():
bootstrap = os.path.join(os.path.dirname(env.sel_ldr),
'nacl_helper_bootstrap')
loader = [bootstrap, env.sel_ldr]
template_digits = 'X' * 16
bootstrap_loader_args = ['--r_debug=0x' + template_digits,
'--reserved_at_zero=0x' + template_digits]
else:
loader = [env.sel_ldr]
return Run(prefix + loader + bootstrap_loader_args + quiet_args + args,
exit_on_failure=(not collate),
capture_stdout=collate, capture_stderr=collate,
stdin_string=stdin_string)
def FindOrBuildIRT(allow_build = True):
if env.force_irt:
if env.force_irt == 'none':
return None
elif env.force_irt == 'core':
flavors = ['irt_core']
else:
irt = env.force_irt
if not os.path.exists(irt):
Fatal('IRT not found: %s' % irt)
return irt
else:
flavors = ['irt_core']
irt_paths = []
for flavor in flavors:
path = os.path.join(env.nacl_root, 'scons-out',
'nacl_irt-%s/staging/%s.nexe' % (env.arch, flavor))
irt_paths.append(path)
for path in irt_paths:
if os.path.exists(path):
return path
if allow_build:
PrintBanner('irt not found. Building it with scons.')
irt = irt_paths[0]
BuildIRT(flavors[0])
assert(env.dry_run or os.path.exists(irt))
return irt
return None
def BuildIRT(flavor):
args = ('platform=%s naclsdk_validate=0 ' +
'sysinfo=0 -j8 %s') % (env.arch, flavor)
args = args.split()
Run([env.scons] + args, cwd=env.nacl_root)
def FindOrBuildElfLoader(allow_build=True):
if env.force_elf_loader:
if env.force_elf_loader == 'none':
return None
if not os.path.exists(env.force_elf_loader):
Fatal('elf_loader.nexe not found: %s' % env.force_elf_loader)
return env.force_elf_loader
path = os.path.join(env.nacl_root, 'scons-out',
'nacl-' + env.arch,
'staging', 'elf_loader.nexe')
if os.path.exists(path):
return path
if allow_build:
PrintBanner('elf_loader not found. Building it with scons.')
Run([env.scons, 'platform=' + env.arch,
'naclsdk_validate=0', 'sysinfo=0',
'-j8', 'elf_loader'],
cwd=env.nacl_root)
assert(env.dry_run or os.path.exists(path))
return path
return None
def FindOrBuildSelLdr(allow_build=True):
if env.force_sel_ldr:
if env.force_sel_ldr in ('dbg','opt'):
modes = [ env.force_sel_ldr ]
else:
sel_ldr = env.force_sel_ldr
if not os.path.exists(sel_ldr):
Fatal('sel_ldr not found: %s' % sel_ldr)
return sel_ldr
else:
modes = ['opt','dbg']
loaders = []
for mode in modes:
sel_ldr = os.path.join(
env.nacl_root, 'scons-out',
'%s-%s-%s' % (mode, pynacl.platform.GetOS(), env.arch),
'staging', 'sel_ldr')
if pynacl.platform.IsWindows():
sel_ldr += '.exe'
loaders.append(sel_ldr)
# If one exists, use it.
for sel_ldr in loaders:
if os.path.exists(sel_ldr):
return sel_ldr
# Build it
if allow_build:
PrintBanner('sel_ldr not found. Building it with scons.')
sel_ldr = loaders[0]
BuildSelLdr(modes[0])
assert(env.dry_run or os.path.exists(sel_ldr))
return sel_ldr
return None
def BuildSelLdr(mode):
args = ('platform=%s MODE=%s-host naclsdk_validate=0 ' +
'sysinfo=0 -j8 sel_ldr') % (env.arch, mode)
args = args.split()
Run([env.scons] + args, cwd=env.nacl_root)
def Translate(arch, pexe):
output_file = os.path.splitext(pexe)[0] + '.' + arch + '.nexe'
pnacl_translate = os.path.join(env.pnacl_base, 'bin', 'pnacl-translate')
args = [ pnacl_translate, '-arch', arch, pexe, '-o', output_file,
'--allow-llvm-bitcode-input' ]
Run(args)
return output_file
def Stringify(args):
ret = ''
for arg in args:
if ' ' in arg:
ret += ' "%s"' % arg
else:
ret += ' %s' % arg
return ret.strip()
def PrepareStdin(stdin_string):
"""Prepare a stdin stream for a subprocess based on contents of a string.
This has to be in the form of an actual file, rather than directly piping
the string, since the child may (inappropriately) try to fseek() on stdin.
Args:
stdin_string: The characters to pipe to the subprocess.
Returns:
An open temporary file object ready to be read from.
"""
f = tempfile.TemporaryFile()
f.write(stdin_string)
f.seek(0)
return f
def Run(args, cwd=None, verbose=True, exit_on_failure=False,
capture_stdout=False, capture_stderr=False, stdin_string=None):
"""Run a command and optionally capture its output.
Args:
args: A string list containing the command and arguments.
cwd: Change to this directory before running.
verbose: Print the command before running it.
exit_on_failure: Exit immediately if the command returns nonzero.
capture_stdout: Capture the stdout as a string (rather than passing it
through to the terminal).
capture_stderr: Capture the stderr as a string (rather than passing it
through to the terminal).
stdin_string: Text to send to the command via stdin. If None, stdin is
inherited from the caller.
Returns:
A string containing the concatenation of any captured stdout plus
any captured stderr.
"""
if verbose:
PrintCommand(Stringify(args))
if env.dry_run:
return
stdout_redir = None
stderr_redir = None
stdin_redir = None
if capture_stdout:
stdout_redir = subprocess.PIPE
if capture_stderr:
stderr_redir = subprocess.PIPE
if stdin_string:
stdin_redir = PrepareStdin(stdin_string)
p = None
try:
# PNaCl toolchain executables (pnacl-translate, readelf) are scripts
# not binaries, so it doesn't want to run on Windows without a shell.
use_shell = True if pynacl.platform.IsWindows() else False
p = subprocess.Popen(args, stdin=stdin_redir, stdout=stdout_redir,
stderr=stderr_redir, cwd=cwd, shell=use_shell)
(stdout_contents, stderr_contents) = p.communicate()
except KeyboardInterrupt as e:
if p:
p.kill()
raise e
except BaseException as e:
if p:
p.kill()
raise e
env.last_return_code = p.returncode
if p.returncode != 0 and exit_on_failure:
if capture_stdout or capture_stderr:
# Print an extra message if any of the program's output wasn't
# going to the screen.
Fatal('Failed to run: %s' % Stringify(args))
sys.exit(p.returncode)
return (stdout_contents or '') + (stderr_contents or '')
def ArgSplit(argv):
"""Parse command-line arguments.
Returns:
Tuple (nexe, nexe_args) where nexe is the name of the nexe or pexe
to execute, and nexe_args are its runtime arguments.
"""
desc = ('Run a command-line nexe (or pexe). Automatically handles\n' +
'translation, building sel_ldr, and building the IRT.')
parser = argparse.ArgumentParser(description=desc)
parser.add_argument('-L', action='append', dest='library_path', default=[],
help='Additional library path for dynamic linker.')
parser.add_argument('--paranoid', action='store_true', default=False,
help='Remove -S (signals) and -a (file access) ' +
'from the default sel_ldr options, and disallow mmap() ' +
'for loading the nexe.')
parser.add_argument('--loader', dest='force_sel_ldr', metavar='SEL_LDR',
help='Path to sel_ldr. "dbg" or "opt" means use ' +
'dbg or opt version of sel_ldr. ' +
'By default, use whichever sel_ldr already exists; ' +
'otherwise, build opt version.')
parser.add_argument('--irt', dest='force_irt', metavar='IRT',
help='Path to IRT nexe. "core" or "none" means use ' +
'Core IRT or no IRT. By default, use whichever IRT ' +
'already exists; otherwise, build irt_core.')
parser.add_argument('--elf_loader', dest='force_elf_loader',
metavar='ELF_LOADER',
help='Path to elf_loader nexe. ' +
'By default find it in scons-out, or build it.')
parser.add_argument('--dry-run', '-n', action='store_true', default=False,
help="Just print commands, don't execute them.")
parser.add_argument('--quiet', '-q', action='store_true', default=False,
help="Don't print anything.")
parser.add_argument('--retries', default='0', metavar='N',
help='Retry sel_ldr command up to N times (if ' +
'flakiness is expected). This argument implies ' +
'--collate.')
parser.add_argument('--collate', action='store_true', default=False,
help="Combine/collate sel_ldr's stdout and stderr, and " +
"print to stdout.")
parser.add_argument('--trace', '-t', action='store_true', default=False,
help='Trace qemu execution.')
parser.add_argument('--debug', '-g', action='store_true', default=False,
help='Run sel_ldr with debugging enabled.')
parser.add_argument('-arch', '-m', dest='arch', action='store',
choices=sorted(
pynacl.platform.ARCH3264_LIST + ['env']),
help=('Specify architecture for PNaCl translation. ' +
'"env" is a special value which obtains the ' +
'architecture from the environment ' +
'variable "%s".') % ARCH_ENV_VAR_NAME)
parser.add_argument('remainder', nargs=argparse.REMAINDER,
metavar='nexe/pexe + args')
(options, args) = parser.parse_known_args(argv)
# Copy the options into env.
for (key, value) in vars(options).items():
setattr(env, key, value)
args += options.remainder
nexe = args[0] if len(args) else ''
env.is_pnacl = nexe.endswith('.pexe')
if env.arch == 'env':
# Get the architecture from the environment.
try:
env.arch = os.environ[ARCH_ENV_VAR_NAME]
except Exception as e:
Fatal(('Option "-arch env" specified, but environment variable ' +
'"%s" not specified: %s') % (ARCH_ENV_VAR_NAME, e))
if not env.arch and env.is_pnacl:
# For NaCl we'll figure out the architecture from the nexe's
# architecture, but for PNaCl we first need to translate and the
# user didn't tell us which architecture to translate to. Be nice
# and just translate to the current machine's architecture.
env.arch = pynacl.platform.GetArch3264()
# Canonicalize env.arch.
env.arch = pynacl.platform.GetArch3264(env.arch)
return nexe, args[1:]
def Fatal(msg, *args):
if len(args) > 0:
msg = msg % args
print(msg)
sys.exit(1)
def FindReadElf():
'''Returns the path of "readelf" binary.'''
candidates = []
# Use PNaCl's if it available.
candidates.append(
os.path.join(env.pnacl_base, 'bin', 'pnacl-readelf'))
# Otherwise, look for the system readelf
for path in os.environ['PATH'].split(os.pathsep):
candidates.append(os.path.join(path, 'readelf'))
for readelf in candidates:
if os.path.exists(readelf):
return readelf
Fatal('Cannot find readelf!')
def ReadELFInfo(f):
''' Returns: (arch, is_dynamic, is_glibc_static) '''
readelf = env.readelf
readelf_out = Run([readelf, '-lh', f], capture_stdout=True, verbose=False)
machine_line = None
is_dynamic = False
is_glibc_static = False
for line in readelf_out.split('\n'):
line = line.strip()
if line.startswith('Machine:'):
machine_line = line
if line.startswith('DYNAMIC'):
is_dynamic = True
if '__libc_atexit' in line:
is_glibc_static = True
if not machine_line:
Fatal('Script error: readelf output did not make sense!')
if 'Intel 80386' in machine_line:
arch = 'x86-32'
elif 'X86-64' in machine_line:
arch = 'x86-64'
elif 'ARM' in machine_line:
arch = 'arm'
elif 'MIPS' in machine_line:
arch = 'mips32'
else:
Fatal('%s: Unknown machine type', f)
return (arch, is_dynamic, is_glibc_static)
def FindBaseDir():
'''Crawl backwards, starting from the directory containing this script,
until we find the native_client/ directory.
'''
curdir = os.path.abspath(sys.argv[0])
while os.path.basename(curdir) != 'native_client':
curdir,subdir = os.path.split(curdir)
if subdir == '':
# We've hit the file system root
break
if os.path.basename(curdir) != 'native_client':
Fatal('Unable to find native_client directory!')
return curdir
if __name__ == '__main__':
sys.exit(main(sys.argv))