2 # Copyright (c) 2012 The Native Client Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 from __future__ import print_function
14 import pynacl.platform
16 # Target architecture for PNaCl can be set through the ``-arch``
17 # command-line argument, and when its value is ``env`` the following
18 # program environment variable is queried to figure out which
19 # architecture to target.
20 ARCH_ENV_VAR_NAME = 'PNACL_RUN_ARCH'
26 def SetupEnvironment():
27 # native_client/ directory
28 env.nacl_root = FindBaseDir()
30 env.toolchain_base = os.path.join(env.nacl_root,
32 '%s_x86' % pynacl.platform.GetOS())
34 # Path to PNaCl toolchain
35 env.pnacl_base = os.path.join(env.toolchain_base, 'pnacl_newlib')
38 env.arm_root = os.path.join(env.toolchain_base, 'arm_trusted')
39 env.qemu_arm = os.path.join(env.arm_root, 'run_under_qemu_arm')
41 env.mips32_root = os.path.join(env.toolchain_base, 'mips_trusted')
42 env.qemu_mips32 = os.path.join(env.mips32_root, 'run_under_qemu_mips32')
45 env.readelf = FindReadElf()
48 env.scons = os.path.join(env.nacl_root, 'scons')
50 # Library path for dynamic linker.
56 # Only print commands, don't run them
59 # Force a specific sel_ldr
60 env.force_sel_ldr = None
62 # Force a specific IRT
65 # Don't print anything
68 # Arch (x86-32, x86-64, arm, mips32)
74 # Debug the nexe using the debug stub
77 # PNaCl (as opposed to NaCl).
80 def PrintBanner(output):
82 lines = output.split('\n')
85 padding = ' ' * max(0, (80 - len(line)) / 2)
86 print(padding + output + padding)
97 def SetupArch(arch, allow_build=True):
98 '''Setup environment variables that require knowing the
99 architecture. We can only do this after we've seen the
100 nexe or once we've read -arch off the command-line.
103 # Path to Native NaCl toolchain (glibc)
104 # MIPS does not have glibc support.
106 toolchain_arch, tooldir_arch, libdir = {
107 'x86-32': ('x86', 'x86_64', 'lib32'),
108 'x86-64': ('x86', 'x86_64', 'lib'),
109 'arm': ('arm', 'arm', 'lib'),
111 env.nnacl_tooldir = os.path.join(env.toolchain_base,
112 'nacl_%s_glibc' % toolchain_arch,
113 '%s-nacl' % tooldir_arch)
114 env.nnacl_libdir = os.path.join(env.nnacl_tooldir, libdir)
115 env.sel_ldr = FindOrBuildSelLdr(allow_build=allow_build)
116 env.irt = FindOrBuildIRT(allow_build=allow_build)
118 env.elf_loader = FindOrBuildElfLoader(allow_build=allow_build)
121 def SetupLibC(arch, is_dynamic):
124 libdir = os.path.join(env.pnacl_base, 'lib-' + arch)
126 libdir = env.nnacl_libdir
127 env.runnable_ld = os.path.join(libdir, 'runnable-ld.so')
128 env.library_path.append(libdir)
136 # sel_ldr's "quiet" options need to come early in the command line
137 # to suppress noisy output from processing other options, like -Q.
138 sel_ldr_quiet_options = []
139 nexe, nexe_params = ArgSplit(argv[1:])
143 nexe = Translate(env.arch, nexe)
145 # Read the ELF file info
146 if env.is_pnacl and env.dry_run:
147 # In a dry run, we don't actually run pnacl-translate, so there is
148 # no nexe for readelf. Fill in the information manually.
151 is_glibc_static = False
153 arch, is_dynamic, is_glibc_static = ReadELFInfo(nexe)
155 # Add default sel_ldr options
157 # Enable mmap() for loading the nexe, so that 'perf' gives usable output.
158 # Note that this makes the sandbox unsafe if the mmap()'d nexe gets
159 # modified while sel_ldr is running.
160 os.environ['NACL_FAULT_INJECTION'] = \
161 'ELF_LOAD_BYPASS_DESCRIPTOR_SAFETY_CHECK=GF1/999'
162 sel_ldr_options += ['-a']
163 # -S signal handling is not supported on windows, but otherwise
164 # it is useful getting the address of crashes.
165 if not pynacl.platform.IsWindows():
166 sel_ldr_options += ['-S']
167 # X86-64 glibc static has validation problems without stub out (-s)
168 if arch == 'x86-64' and is_glibc_static:
169 sel_ldr_options += ['-s']
171 # Don't print sel_ldr logs
172 # These need to be at the start of the arglist for full effectiveness.
173 # -q means quiet most stderr warnings.
174 # -l /dev/null means log to /dev/null.
175 sel_ldr_quiet_options = ['-q', '-l', '/dev/null']
177 # Disabling validation (-c) is used by the debug stub test.
178 # TODO(dschuff): remove if/when it's no longer necessary
179 sel_ldr_options += ['-c', '-c', '-g']
186 PrintBanner('%s is %s %s' % (os.path.basename(nexe),
187 arch.upper(), extra))
189 # Setup architecture-specific environment variables
192 # Setup LibC-specific environment variables
193 SetupLibC(arch, is_dynamic)
195 # Add IRT to sel_ldr options.
197 sel_ldr_options += ['-B', env.irt]
199 # The NaCl dynamic loader prefers posixy paths.
200 nexe_path = os.path.abspath(nexe)
201 nexe_path = nexe_path.replace('\\', '/')
202 sel_ldr_nexe_args = [nexe_path] + nexe_params
205 ld_library_path = ':'.join(env.library_path)
207 sel_ldr_nexe_args = [env.elf_loader, '--interp-prefix',
208 env.nnacl_tooldir] + sel_ldr_nexe_args
209 sel_ldr_options += ['-E', 'LD_LIBRARY_PATH=' + ld_library_path]
211 sel_ldr_nexe_args = [env.runnable_ld, '--library-path',
212 ld_library_path] + sel_ldr_nexe_args
214 # Setup sel_ldr arguments.
215 sel_ldr_args = sel_ldr_options + ['--'] + sel_ldr_nexe_args
220 if hasattr(env, 'retries'):
221 retries = int(env.retries)
224 collate = env.collate or retries > 0
225 input = sys.stdin.read() if collate else None
226 for iter in range(1 + max(retries, 0)):
227 output = RunSelLdr(sel_ldr_args, quiet_args=sel_ldr_quiet_options,
228 collate=collate, stdin_string=input)
229 if env.last_return_code < 128:
230 # If the application crashes, we expect a 128+ return code.
232 sys.stdout.write(output or '')
233 return_code = env.last_return_code
236 # Clean up the .nexe that was created.
245 def RunSelLdr(args, quiet_args=[], collate=False, stdin_string=None):
246 """Run the sel_ldr command and optionally capture its output.
249 args: A string list containing the command and arguments.
250 collate: Whether to capture stdout+stderr (rather than passing
251 them through to the terminal).
252 stdin_string: Text to send to the command via stdin. If None, stdin is
253 inherited from the caller.
256 A string containing the concatenation of any captured stdout plus
260 # The bootstrap loader args (--r_debug, --reserved_at_zero) need to
261 # come before quiet_args.
262 bootstrap_loader_args = []
263 arch = pynacl.platform.GetArch3264()
264 if arch != pynacl.platform.ARCH3264_ARM and env.arch == 'arm':
265 prefix = [ env.qemu_arm, '-cpu', 'cortex-a9']
267 prefix += ['-d', 'in_asm,op,exec,cpu']
270 if arch != pynacl.platform.ARCH3264_MIPS32 and env.arch == 'mips32':
271 prefix = [env.qemu_mips32]
273 prefix += ['-d', 'in_asm,op,exec,cpu']
276 # Use the bootstrap loader on linux.
277 if pynacl.platform.IsLinux():
278 bootstrap = os.path.join(os.path.dirname(env.sel_ldr),
279 'nacl_helper_bootstrap')
280 loader = [bootstrap, env.sel_ldr]
281 template_digits = 'X' * 16
282 bootstrap_loader_args = ['--r_debug=0x' + template_digits,
283 '--reserved_at_zero=0x' + template_digits]
285 loader = [env.sel_ldr]
286 return Run(prefix + loader + bootstrap_loader_args + quiet_args + args,
287 exit_on_failure=(not collate),
288 capture_stdout=collate, capture_stderr=collate,
289 stdin_string=stdin_string)
292 def FindOrBuildIRT(allow_build = True):
294 if env.force_irt == 'none':
296 elif env.force_irt == 'core':
297 flavors = ['irt_core']
300 if not os.path.exists(irt):
301 Fatal('IRT not found: %s' % irt)
304 flavors = ['irt_core']
307 for flavor in flavors:
308 path = os.path.join(env.nacl_root, 'scons-out',
309 'nacl_irt-%s/staging/%s.nexe' % (env.arch, flavor))
310 irt_paths.append(path)
312 for path in irt_paths:
313 if os.path.exists(path):
317 PrintBanner('irt not found. Building it with scons.')
320 assert(env.dry_run or os.path.exists(irt))
325 def BuildIRT(flavor):
326 args = ('platform=%s naclsdk_validate=0 ' +
327 'sysinfo=0 -j8 %s') % (env.arch, flavor)
329 Run([env.scons] + args, cwd=env.nacl_root)
331 def FindOrBuildElfLoader(allow_build=True):
332 if env.force_elf_loader:
333 if env.force_elf_loader == 'none':
335 if not os.path.exists(env.force_elf_loader):
336 Fatal('elf_loader.nexe not found: %s' % env.force_elf_loader)
337 return env.force_elf_loader
339 path = os.path.join(env.nacl_root, 'scons-out',
341 'staging', 'elf_loader.nexe')
342 if os.path.exists(path):
346 PrintBanner('elf_loader not found. Building it with scons.')
347 Run([env.scons, 'platform=' + env.arch,
348 'naclsdk_validate=0', 'sysinfo=0',
349 '-j8', 'elf_loader'],
351 assert(env.dry_run or os.path.exists(path))
356 def FindOrBuildSelLdr(allow_build=True):
357 if env.force_sel_ldr:
358 if env.force_sel_ldr in ('dbg','opt'):
359 modes = [ env.force_sel_ldr ]
361 sel_ldr = env.force_sel_ldr
362 if not os.path.exists(sel_ldr):
363 Fatal('sel_ldr not found: %s' % sel_ldr)
366 modes = ['opt','dbg']
370 sel_ldr = os.path.join(
371 env.nacl_root, 'scons-out',
372 '%s-%s-%s' % (mode, pynacl.platform.GetOS(), env.arch),
373 'staging', 'sel_ldr')
374 if pynacl.platform.IsWindows():
376 loaders.append(sel_ldr)
378 # If one exists, use it.
379 for sel_ldr in loaders:
380 if os.path.exists(sel_ldr):
385 PrintBanner('sel_ldr not found. Building it with scons.')
387 BuildSelLdr(modes[0])
388 assert(env.dry_run or os.path.exists(sel_ldr))
393 def BuildSelLdr(mode):
394 args = ('platform=%s MODE=%s-host naclsdk_validate=0 ' +
395 'sysinfo=0 -j8 sel_ldr') % (env.arch, mode)
397 Run([env.scons] + args, cwd=env.nacl_root)
399 def Translate(arch, pexe):
400 output_file = os.path.splitext(pexe)[0] + '.' + arch + '.nexe'
401 pnacl_translate = os.path.join(env.pnacl_base, 'bin', 'pnacl-translate')
402 args = [ pnacl_translate, '-arch', arch, pexe, '-o', output_file,
403 '--allow-llvm-bitcode-input' ]
416 def PrepareStdin(stdin_string):
417 """Prepare a stdin stream for a subprocess based on contents of a string.
418 This has to be in the form of an actual file, rather than directly piping
419 the string, since the child may (inappropriately) try to fseek() on stdin.
422 stdin_string: The characters to pipe to the subprocess.
425 An open temporary file object ready to be read from.
427 f = tempfile.TemporaryFile()
428 f.write(stdin_string)
432 def Run(args, cwd=None, verbose=True, exit_on_failure=False,
433 capture_stdout=False, capture_stderr=False, stdin_string=None):
434 """Run a command and optionally capture its output.
437 args: A string list containing the command and arguments.
438 cwd: Change to this directory before running.
439 verbose: Print the command before running it.
440 exit_on_failure: Exit immediately if the command returns nonzero.
441 capture_stdout: Capture the stdout as a string (rather than passing it
442 through to the terminal).
443 capture_stderr: Capture the stderr as a string (rather than passing it
444 through to the terminal).
445 stdin_string: Text to send to the command via stdin. If None, stdin is
446 inherited from the caller.
449 A string containing the concatenation of any captured stdout plus
453 PrintCommand(Stringify(args))
461 stdout_redir = subprocess.PIPE
463 stderr_redir = subprocess.PIPE
465 stdin_redir = PrepareStdin(stdin_string)
469 # PNaCl toolchain executables (pnacl-translate, readelf) are scripts
470 # not binaries, so it doesn't want to run on Windows without a shell.
471 use_shell = True if pynacl.platform.IsWindows() else False
472 p = subprocess.Popen(args, stdin=stdin_redir, stdout=stdout_redir,
473 stderr=stderr_redir, cwd=cwd, shell=use_shell)
474 (stdout_contents, stderr_contents) = p.communicate()
475 except KeyboardInterrupt, e:
479 except BaseException, e:
484 env.last_return_code = p.returncode
486 if p.returncode != 0 and exit_on_failure:
487 if capture_stdout or capture_stderr:
488 # Print an extra message if any of the program's output wasn't
489 # going to the screen.
490 Fatal('Failed to run: %s' % Stringify(args))
491 sys.exit(p.returncode)
493 return (stdout_contents or '') + (stderr_contents or '')
497 """Parse command-line arguments.
500 Tuple (nexe, nexe_args) where nexe is the name of the nexe or pexe
501 to execute, and nexe_args are its runtime arguments.
503 desc = ('Run a command-line nexe (or pexe). Automatically handles\n' +
504 'translation, building sel_ldr, and building the IRT.')
505 parser = argparse.ArgumentParser(description=desc)
506 parser.add_argument('-L', action='append', dest='library_path', default=[],
507 help='Additional library path for dynamic linker.')
508 parser.add_argument('--paranoid', action='store_true', default=False,
509 help='Remove -S (signals) and -a (file access) ' +
510 'from the default sel_ldr options, and disallow mmap() ' +
511 'for loading the nexe.')
512 parser.add_argument('--loader', dest='force_sel_ldr', metavar='SEL_LDR',
513 help='Path to sel_ldr. "dbg" or "opt" means use ' +
514 'dbg or opt version of sel_ldr. ' +
515 'By default, use whichever sel_ldr already exists; ' +
516 'otherwise, build opt version.')
517 parser.add_argument('--irt', dest='force_irt', metavar='IRT',
518 help='Path to IRT nexe. "core" or "none" means use ' +
519 'Core IRT or no IRT. By default, use whichever IRT ' +
520 'already exists; otherwise, build irt_core.')
521 parser.add_argument('--elf_loader', dest='force_elf_loader',
522 metavar='ELF_LOADER',
523 help='Path to elf_loader nexe. ' +
524 'By default find it in scons-out, or build it.')
525 parser.add_argument('--dry-run', '-n', action='store_true', default=False,
526 help="Just print commands, don't execute them.")
527 parser.add_argument('--quiet', '-q', action='store_true', default=False,
528 help="Don't print anything.")
529 parser.add_argument('--retries', default='0', metavar='N',
530 help='Retry sel_ldr command up to N times (if ' +
531 'flakiness is expected). This argument implies ' +
533 parser.add_argument('--collate', action='store_true', default=False,
534 help="Combine/collate sel_ldr's stdout and stderr, and " +
536 parser.add_argument('--trace', '-t', action='store_true', default=False,
537 help='Trace qemu execution.')
538 parser.add_argument('--debug', '-g', action='store_true', default=False,
539 help='Run sel_ldr with debugging enabled.')
540 parser.add_argument('-arch', '-m', dest='arch', action='store',
542 pynacl.platform.ARCH3264_LIST + ['env']),
543 help=('Specify architecture for PNaCl translation. ' +
544 '"env" is a special value which obtains the ' +
545 'architecture from the environment ' +
546 'variable "%s".') % ARCH_ENV_VAR_NAME)
547 parser.add_argument('remainder', nargs=argparse.REMAINDER,
548 metavar='nexe/pexe + args')
549 (options, args) = parser.parse_known_args(argv)
551 # Copy the options into env.
552 for (key, value) in vars(options).iteritems():
553 setattr(env, key, value)
555 args += options.remainder
556 nexe = args[0] if len(args) else ''
557 env.is_pnacl = nexe.endswith('.pexe')
559 if env.arch == 'env':
560 # Get the architecture from the environment.
562 env.arch = os.environ[ARCH_ENV_VAR_NAME]
563 except Exception as e:
564 Fatal(('Option "-arch env" specified, but environment variable ' +
565 '"%s" not specified: %s') % (ARCH_ENV_VAR_NAME, e))
566 if not env.arch and env.is_pnacl:
567 # For NaCl we'll figure out the architecture from the nexe's
568 # architecture, but for PNaCl we first need to translate and the
569 # user didn't tell us which architecture to translate to. Be nice
570 # and just translate to the current machine's architecture.
571 env.arch = pynacl.platform.GetArch3264()
572 # Canonicalize env.arch.
573 env.arch = pynacl.platform.GetArch3264(env.arch)
574 return nexe, args[1:]
577 def Fatal(msg, *args):
585 '''Returns the path of "readelf" binary.'''
588 # Use PNaCl's if it available.
590 os.path.join(env.pnacl_base, 'bin', 'pnacl-readelf'))
592 # Otherwise, look for the system readelf
593 for path in os.environ['PATH'].split(os.pathsep):
594 candidates.append(os.path.join(path, 'readelf'))
596 for readelf in candidates:
597 if os.path.exists(readelf):
600 Fatal('Cannot find readelf!')
604 ''' Returns: (arch, is_dynamic, is_glibc_static) '''
606 readelf = env.readelf
607 readelf_out = Run([readelf, '-lh', f], capture_stdout=True, verbose=False)
611 is_glibc_static = False
612 for line in readelf_out.split('\n'):
614 if line.startswith('Machine:'):
616 if line.startswith('DYNAMIC'):
618 if '__libc_atexit' in line:
619 is_glibc_static = True
622 Fatal('Script error: readelf output did not make sense!')
624 if 'Intel 80386' in machine_line:
626 elif 'X86-64' in machine_line:
628 elif 'ARM' in machine_line:
630 elif 'MIPS' in machine_line:
633 Fatal('%s: Unknown machine type', f)
635 return (arch, is_dynamic, is_glibc_static)
639 '''Crawl backwards, starting from the directory containing this script,
640 until we find the native_client/ directory.
643 curdir = os.path.abspath(sys.argv[0])
644 while os.path.basename(curdir) != 'native_client':
645 curdir,subdir = os.path.split(curdir)
647 # We've hit the file system root
650 if os.path.basename(curdir) != 'native_client':
651 Fatal('Unable to find native_client directory!')
655 if __name__ == '__main__':
656 sys.exit(main(sys.argv))