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.
12 import pynacl.platform
14 # Target architecture for PNaCl can be set through the ``-arch``
15 # command-line argument, and when its value is ``env`` the following
16 # program environment variable is queried to figure out which
17 # architecture to target.
18 ARCH_ENV_VAR_NAME = 'PNACL_RUN_ARCH'
24 def SetupEnvironment():
25 # native_client/ directory
26 env.nacl_root = FindBaseDir()
28 toolchain_base = os.path.join(env.nacl_root,
30 '%s_x86' % pynacl.platform.GetOS())
32 # Path to Native NaCl toolchain (glibc)
33 env.nnacl_root = os.path.join(toolchain_base, 'nacl_x86_glibc')
35 # Path to PNaCl toolchain
36 env.pnacl_base = os.path.join(toolchain_base, 'pnacl_newlib')
39 env.arm_root = os.path.join(toolchain_base, 'arm_trusted')
40 env.qemu_arm = os.path.join(env.arm_root, 'run_under_qemu_arm')
42 env.mips32_root = os.path.join(toolchain_base, 'mips_trusted')
43 env.qemu_mips32 = os.path.join(env.mips32_root, 'run_under_qemu_mips32')
46 env.readelf = FindReadElf()
49 env.scons = os.path.join(env.nacl_root, 'scons')
51 # Library path for runnable-ld.so
57 # Only print commands, don't run them
60 # Force a specific sel_ldr
61 env.force_sel_ldr = None
63 # Force a specific IRT
66 # Don't print anything
69 # Arch (x86-32, x86-64, arm, mips32)
75 # Debug the nexe using the debug stub
78 # PNaCl (as opposed to NaCl).
81 def PrintBanner(output):
83 lines = output.split('\n')
86 padding = ' ' * max(0, (80 - len(line)) / 2)
87 print padding + output + padding
96 def GetMultiDir(arch):
99 elif arch == 'x86-64':
102 Fatal('nacl-gcc does not support %s' % arch)
104 def SetupArch(arch, allow_build=True):
105 '''Setup environment variables that require knowing the
106 architecture. We can only do this after we've seen the
107 nexe or once we've read -arch off the command-line.
110 env.sel_ldr = FindOrBuildSelLdr(allow_build=allow_build)
111 env.irt = FindOrBuildIRT(allow_build=allow_build)
114 def SetupLibC(arch, is_dynamic):
117 libdir = os.path.join(env.pnacl_base, 'lib-' + arch)
119 libdir = os.path.join(env.nnacl_root, 'x86_64-nacl', GetMultiDir(arch))
120 env.runnable_ld = os.path.join(libdir, 'runnable-ld.so')
121 env.library_path.append(libdir)
129 # sel_ldr's "quiet" options need to come early in the command line
130 # to suppress noisy output from processing other options, like -Q.
131 sel_ldr_quiet_options = []
132 nexe, nexe_params = ArgSplit(argv[1:])
136 nexe = Translate(env.arch, nexe)
138 # Read the ELF file info
139 if env.is_pnacl and env.dry_run:
140 # In a dry run, we don't actually run pnacl-translate, so there is
141 # no nexe for readelf. Fill in the information manually.
144 is_glibc_static = False
146 arch, is_dynamic, is_glibc_static = ReadELFInfo(nexe)
148 # Add default sel_ldr options
150 sel_ldr_options += ['-a']
151 # -S signal handling is not supported on windows, but otherwise
152 # it is useful getting the address of crashes.
153 if not pynacl.platform.IsWindows():
154 sel_ldr_options += ['-S']
155 # X86-64 glibc static has validation problems without stub out (-s)
156 if arch == 'x86-64' and is_glibc_static:
157 sel_ldr_options += ['-s']
159 # Don't print sel_ldr logs
160 # These need to be at the start of the arglist for full effectiveness.
161 # -q means quiet most stderr warnings.
162 # -l /dev/null means log to /dev/null.
163 sel_ldr_quiet_options = ['-q', '-l', '/dev/null']
165 # Disabling validation (-c) is used by the debug stub test.
166 # TODO(dschuff): remove if/when it's no longer necessary
167 sel_ldr_options += ['-c', '-c', '-g']
174 PrintBanner('%s is %s %s' % (os.path.basename(nexe),
175 arch.upper(), extra))
177 # Setup architecture-specific environment variables
180 # Setup LibC-specific environment variables
181 SetupLibC(arch, is_dynamic)
185 # Add irt to sel_ldr arguments
187 sel_ldr_args += ['-B', env.irt]
189 # Setup sel_ldr arguments
190 sel_ldr_args += sel_ldr_options + ['--']
193 sel_ldr_args += [env.runnable_ld,
194 '--library-path', ':'.join(env.library_path)]
196 # The NaCl dynamic loader prefers posixy paths.
197 nexe_path = os.path.abspath(nexe)
198 nexe_path = nexe_path.replace('\\', '/')
199 sel_ldr_args += [nexe_path] + nexe_params
204 if hasattr(env, 'retries'):
205 retries = int(env.retries)
208 collate = env.collate or retries > 0
209 input = sys.stdin.read() if collate else None
210 for iter in range(1 + max(retries, 0)):
211 output = RunSelLdr(sel_ldr_args, quiet_args=sel_ldr_quiet_options,
212 collate=collate, stdin_string=input)
213 if env.last_return_code < 128:
214 # If the application crashes, we expect a 128+ return code.
216 sys.stdout.write(output or '')
217 return_code = env.last_return_code
220 # Clean up the .nexe that was created.
229 def RunSelLdr(args, quiet_args=[], collate=False, stdin_string=None):
230 """Run the sel_ldr command and optionally capture its output.
233 args: A string list containing the command and arguments.
234 collate: Whether to capture stdout+stderr (rather than passing
235 them through to the terminal).
236 stdin_string: Text to send to the command via stdin. If None, stdin is
237 inherited from the caller.
240 A string containing the concatenation of any captured stdout plus
244 # The bootstrap loader args (--r_debug, --reserved_at_zero) need to
245 # come before quiet_args.
246 bootstrap_loader_args = []
247 arch = pynacl.platform.GetArch3264()
248 if arch != pynacl.platform.ARCH3264_ARM and env.arch == 'arm':
249 prefix = [ env.qemu_arm, '-cpu', 'cortex-a9']
251 prefix += ['-d', 'in_asm,op,exec,cpu']
254 if arch != pynacl.platform.ARCH3264_MIPS32 and env.arch == 'mips32':
255 prefix = [env.qemu_mips32]
257 prefix += ['-d', 'in_asm,op,exec,cpu']
260 # Use the bootstrap loader on linux.
261 if pynacl.platform.IsLinux():
262 bootstrap = os.path.join(os.path.dirname(env.sel_ldr),
263 'nacl_helper_bootstrap')
264 loader = [bootstrap, env.sel_ldr]
265 template_digits = 'X' * 16
266 bootstrap_loader_args = ['--r_debug=0x' + template_digits,
267 '--reserved_at_zero=0x' + template_digits]
269 loader = [env.sel_ldr]
270 return Run(prefix + loader + bootstrap_loader_args + quiet_args + args,
271 exit_on_failure=(not collate),
272 capture_stdout=collate, capture_stderr=collate,
273 stdin_string=stdin_string)
276 def FindOrBuildIRT(allow_build = True):
278 if env.force_irt == 'none':
280 elif env.force_irt == 'core':
281 flavors = ['irt_core']
284 if not os.path.exists(irt):
285 Fatal('IRT not found: %s' % irt)
288 flavors = ['irt_core']
291 for flavor in flavors:
292 path = os.path.join(env.nacl_root, 'scons-out',
293 'nacl_irt-%s/staging/%s.nexe' % (env.arch, flavor))
294 irt_paths.append(path)
296 for path in irt_paths:
297 if os.path.exists(path):
301 PrintBanner('irt not found. Building it with scons.')
304 assert(env.dry_run or os.path.exists(irt))
309 def BuildIRT(flavor):
310 args = ('platform=%s naclsdk_validate=0 ' +
311 'sysinfo=0 -j8 %s') % (env.arch, flavor)
313 Run([env.scons] + args, cwd=env.nacl_root)
315 def FindOrBuildSelLdr(allow_build=True):
316 if env.force_sel_ldr:
317 if env.force_sel_ldr in ('dbg','opt'):
318 modes = [ env.force_sel_ldr ]
320 sel_ldr = env.force_sel_ldr
321 if not os.path.exists(sel_ldr):
322 Fatal('sel_ldr not found: %s' % sel_ldr)
325 modes = ['opt','dbg']
329 sel_ldr = os.path.join(
330 env.nacl_root, 'scons-out',
331 '%s-%s-%s' % (mode, pynacl.platform.GetOS(), env.arch),
332 'staging', 'sel_ldr')
333 if pynacl.platform.IsWindows():
335 loaders.append(sel_ldr)
337 # If one exists, use it.
338 for sel_ldr in loaders:
339 if os.path.exists(sel_ldr):
344 PrintBanner('sel_ldr not found. Building it with scons.')
346 BuildSelLdr(modes[0])
347 assert(env.dry_run or os.path.exists(sel_ldr))
352 def BuildSelLdr(mode):
353 args = ('platform=%s MODE=%s-host naclsdk_validate=0 ' +
354 'sysinfo=0 -j8 sel_ldr') % (env.arch, mode)
356 Run([env.scons] + args, cwd=env.nacl_root)
358 def Translate(arch, pexe):
359 output_file = os.path.splitext(pexe)[0] + '.' + arch + '.nexe'
360 pnacl_translate = os.path.join(env.pnacl_base, 'bin', 'pnacl-translate')
361 args = [ pnacl_translate, '-arch', arch, pexe, '-o', output_file,
362 '--allow-llvm-bitcode-input' ]
364 args.append('--pnacl-allow-zerocost-eh')
377 def PrepareStdin(stdin_string):
378 """Prepare a stdin stream for a subprocess based on contents of a string.
379 This has to be in the form of an actual file, rather than directly piping
380 the string, since the child may (inappropriately) try to fseek() on stdin.
383 stdin_string: The characters to pipe to the subprocess.
386 An open temporary file object ready to be read from.
388 f = tempfile.TemporaryFile()
389 f.write(stdin_string)
393 def Run(args, cwd=None, verbose=True, exit_on_failure=False,
394 capture_stdout=False, capture_stderr=False, stdin_string=None):
395 """Run a command and optionally capture its output.
398 args: A string list containing the command and arguments.
399 cwd: Change to this directory before running.
400 verbose: Print the command before running it.
401 exit_on_failure: Exit immediately if the command returns nonzero.
402 capture_stdout: Capture the stdout as a string (rather than passing it
403 through to the terminal).
404 capture_stderr: Capture the stderr as a string (rather than passing it
405 through to the terminal).
406 stdin_string: Text to send to the command via stdin. If None, stdin is
407 inherited from the caller.
410 A string containing the concatenation of any captured stdout plus
414 PrintCommand(Stringify(args))
422 stdout_redir = subprocess.PIPE
424 stderr_redir = subprocess.PIPE
426 stdin_redir = PrepareStdin(stdin_string)
430 # PNaCl toolchain executables (pnacl-translate, readelf) are scripts
431 # not binaries, so it doesn't want to run on Windows without a shell.
432 use_shell = True if pynacl.platform.IsWindows() else False
433 p = subprocess.Popen(args, stdin=stdin_redir, stdout=stdout_redir,
434 stderr=stderr_redir, cwd=cwd, shell=use_shell)
435 (stdout_contents, stderr_contents) = p.communicate()
436 except KeyboardInterrupt, e:
440 except BaseException, e:
445 env.last_return_code = p.returncode
447 if p.returncode != 0 and exit_on_failure:
448 if capture_stdout or capture_stderr:
449 # Print an extra message if any of the program's output wasn't
450 # going to the screen.
451 Fatal('Failed to run: %s' % Stringify(args))
452 sys.exit(p.returncode)
454 return (stdout_contents or '') + (stderr_contents or '')
458 """Parse command-line arguments.
461 Tuple (nexe, nexe_args) where nexe is the name of the nexe or pexe
462 to execute, and nexe_args are its runtime arguments.
464 desc = ('Run a command-line nexe (or pexe). Automatically handles\n' +
465 'translation, building sel_ldr, and building the IRT.')
466 parser = argparse.ArgumentParser(description=desc)
467 parser.add_argument('-L', action='append', dest='library_path', default=[],
468 help='Additional library path for runnable-ld.so.')
469 parser.add_argument('--paranoid', action='store_true', default=False,
470 help='Remove -S (signals) and -a (file access) ' +
471 'from the default sel_ldr options.')
472 parser.add_argument('--loader', dest='force_sel_ldr', metavar='SEL_LDR',
473 help='Path to sel_ldr. "dbg" or "opt" means use ' +
474 'dbg or opt version of sel_ldr. ' +
475 'By default, use whichever sel_ldr already exists; ' +
476 'otherwise, build opt version.')
477 parser.add_argument('--irt', dest='force_irt', metavar='IRT',
478 help='Path to IRT nexe. "core" or "none" means use ' +
479 'Core IRT or no IRT. By default, use whichever IRT ' +
480 'already exists; otherwise, build irt_core.')
481 parser.add_argument('--dry-run', '-n', action='store_true', default=False,
482 help="Just print commands, don't execute them.")
483 parser.add_argument('--quiet', '-q', action='store_true', default=False,
484 help="Don't print anything.")
485 parser.add_argument('--retries', default='0', metavar='N',
486 help='Retry sel_ldr command up to N times (if ' +
487 'flakiness is expected). This argument implies ' +
489 parser.add_argument('--collate', action='store_true', default=False,
490 help="Combine/collate sel_ldr's stdout and stderr, and " +
492 parser.add_argument('--trace', '-t', action='store_true', default=False,
493 help='Trace qemu execution.')
494 parser.add_argument('--debug', '-g', action='store_true', default=False,
495 help='Run sel_ldr with debugging enabled.')
496 parser.add_argument('-arch', '-m', dest='arch', action='store',
498 pynacl.platform.ARCH3264_LIST + ['env']),
499 help=('Specify architecture for PNaCl translation. ' +
500 '"env" is a special value which obtains the ' +
501 'architecture from the environment ' +
502 'variable "%s".') % ARCH_ENV_VAR_NAME)
503 parser.add_argument('remainder', nargs=argparse.REMAINDER,
504 metavar='nexe/pexe + args')
505 parser.add_argument('--pnacl-allow-zerocost-eh', action='store_true',
506 default=False, dest='zerocost_eh',
507 help='Allow non-stable zero-cost exception handling.')
508 (options, args) = parser.parse_known_args(argv)
510 # Copy the options into env.
511 for (key, value) in vars(options).iteritems():
512 setattr(env, key, value)
514 args += options.remainder
515 nexe = args[0] if len(args) else ''
516 env.is_pnacl = nexe.endswith('.pexe')
518 if env.arch == 'env':
519 # Get the architecture from the environment.
521 env.arch = os.environ[ARCH_ENV_VAR_NAME]
522 except Exception as e:
523 Fatal(('Option "-arch env" specified, but environment variable ' +
524 '"%s" not specified: %s') % (ARCH_ENV_VAR_NAME, e))
525 if not env.arch and env.is_pnacl:
526 # For NaCl we'll figure out the architecture from the nexe's
527 # architecture, but for PNaCl we first need to translate and the
528 # user didn't tell us which architecture to translate to. Be nice
529 # and just translate to the current machine's architecture.
530 env.arch = pynacl.platform.GetArch3264()
531 # Canonicalize env.arch.
532 env.arch = pynacl.platform.GetArch3264(env.arch)
533 return nexe, args[1:]
536 def Fatal(msg, *args):
544 '''Returns the path of "readelf" binary.'''
547 # Use PNaCl's if it available.
549 os.path.join(env.pnacl_base, 'bin', 'pnacl-readelf'))
551 # Otherwise, look for the system readelf
552 for path in os.environ['PATH'].split(os.pathsep):
553 candidates.append(os.path.join(path, 'readelf'))
555 for readelf in candidates:
556 if os.path.exists(readelf):
559 Fatal('Cannot find readelf!')
563 ''' Returns: (arch, is_dynamic, is_glibc_static) '''
565 readelf = env.readelf
566 readelf_out = Run([readelf, '-lh', f], capture_stdout=True, verbose=False)
570 is_glibc_static = False
571 for line in readelf_out.split('\n'):
573 if line.startswith('Machine:'):
575 if line.startswith('DYNAMIC'):
577 if '__libc_atexit' in line:
578 is_glibc_static = True
581 Fatal('Script error: readelf output did not make sense!')
583 if 'Intel 80386' in machine_line:
585 elif 'X86-64' in machine_line:
587 elif 'ARM' in machine_line:
589 elif 'MIPS' in machine_line:
592 Fatal('%s: Unknown machine type', f)
594 return (arch, is_dynamic, is_glibc_static)
598 '''Crawl backwards, starting from the directory containing this script,
599 until we find the native_client/ directory.
602 curdir = os.path.abspath(sys.argv[0])
603 while os.path.basename(curdir) != 'native_client':
604 curdir,subdir = os.path.split(curdir)
606 # We've hit the file system root
609 if os.path.basename(curdir) != 'native_client':
610 Fatal('Unable to find native_client directory!')
614 if __name__ == '__main__':
615 sys.exit(main(sys.argv))