Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / native_client / run.py
1 #!/usr/bin/python
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.
5
6 import argparse
7 import os
8 import subprocess
9 import sys
10 import tempfile
11
12 import pynacl.platform
13
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'
19
20 class Environment:
21   pass
22 env = Environment()
23
24 def SetupEnvironment():
25   # native_client/ directory
26   env.nacl_root = FindBaseDir()
27
28   toolchain_base = os.path.join(env.nacl_root,
29                                 'toolchain',
30                                 '%s_x86' % pynacl.platform.GetOS())
31
32   # Path to Native NaCl toolchain (glibc)
33   env.nnacl_root = os.path.join(toolchain_base, 'nacl_x86_glibc')
34
35   # Path to PNaCl toolchain
36   env.pnacl_base = os.path.join(toolchain_base, 'pnacl_newlib')
37
38   # QEMU
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')
41
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')
44
45   # Path to 'readelf'
46   env.readelf = FindReadElf()
47
48   # Path to 'scons'
49   env.scons = os.path.join(env.nacl_root, 'scons')
50
51   # Library path for runnable-ld.so
52   env.library_path = []
53
54   # Suppress -S -a
55   env.paranoid = False
56
57   # Only print commands, don't run them
58   env.dry_run = False
59
60   # Force a specific sel_ldr
61   env.force_sel_ldr = None
62
63   # Force a specific IRT
64   env.force_irt = None
65
66   # Don't print anything
67   env.quiet = False
68
69   # Arch (x86-32, x86-64, arm, mips32)
70   env.arch = None
71
72   # Trace in QEMU
73   env.trace = False
74
75   # Debug the nexe using the debug stub
76   env.debug = False
77
78   # PNaCl (as opposed to NaCl).
79   env.is_pnacl = False
80
81 def PrintBanner(output):
82   if not env.quiet:
83     lines = output.split('\n')
84     print '*' * 80
85     for line in lines:
86       padding = ' ' * max(0, (80 - len(line)) / 2)
87       print padding + output + padding
88     print '*' * 80
89
90 def PrintCommand(s):
91   if not env.quiet:
92     print
93     print s
94     print
95
96 def GetMultiDir(arch):
97   if arch == 'x86-32':
98     return 'lib32'
99   elif arch == 'x86-64':
100     return 'lib'
101   else:
102     Fatal('nacl-gcc does not support %s' % arch)
103
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.
108   '''
109   env.arch = arch
110   env.sel_ldr = FindOrBuildSelLdr(allow_build=allow_build)
111   env.irt = FindOrBuildIRT(allow_build=allow_build)
112
113
114 def SetupLibC(arch, is_dynamic):
115   if is_dynamic:
116     if env.is_pnacl:
117       libdir = os.path.join(env.pnacl_base, 'lib-' + arch)
118     else:
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)
122
123
124 def main(argv):
125   SetupEnvironment()
126   return_code = 0
127
128   sel_ldr_options = []
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:])
133
134   try:
135     if env.is_pnacl:
136       nexe = Translate(env.arch, nexe)
137
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.
142       arch = env.arch
143       is_dynamic = False
144       is_glibc_static = False
145     else:
146       arch, is_dynamic, is_glibc_static = ReadELFInfo(nexe)
147
148     # Add default sel_ldr options
149     if not env.paranoid:
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']
158     if env.quiet:
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']
164     if env.debug:
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']
168
169     # Tell the user
170     if is_dynamic:
171       extra = 'DYNAMIC'
172     else:
173       extra = 'STATIC'
174     PrintBanner('%s is %s %s' % (os.path.basename(nexe),
175                                  arch.upper(), extra))
176
177     # Setup architecture-specific environment variables
178     SetupArch(arch)
179
180     # Setup LibC-specific environment variables
181     SetupLibC(arch, is_dynamic)
182
183     sel_ldr_args = []
184
185     # Add irt to sel_ldr arguments
186     if env.irt:
187       sel_ldr_args += ['-B', env.irt]
188
189     # Setup sel_ldr arguments
190     sel_ldr_args += sel_ldr_options + ['--']
191
192     if is_dynamic:
193       sel_ldr_args += [env.runnable_ld,
194                        '--library-path', ':'.join(env.library_path)]
195
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
200
201     # Run sel_ldr!
202     retries = 0
203     try:
204       if hasattr(env, 'retries'):
205         retries = int(env.retries)
206     except ValueError:
207       pass
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.
215         break
216     sys.stdout.write(output or '')
217     return_code = env.last_return_code
218   finally:
219     if env.is_pnacl:
220       # Clean up the .nexe that was created.
221       try:
222         os.remove(nexe)
223       except:
224         pass
225
226   return return_code
227
228
229 def RunSelLdr(args, quiet_args=[], collate=False, stdin_string=None):
230   """Run the sel_ldr command and optionally capture its output.
231
232   Args:
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.
238
239   Returns:
240     A string containing the concatenation of any captured stdout plus
241     any captured stderr.
242   """
243   prefix = []
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']
250     if env.trace:
251       prefix += ['-d', 'in_asm,op,exec,cpu']
252     args = ['-Q'] + args
253
254   if arch != pynacl.platform.ARCH3264_MIPS32 and env.arch == 'mips32':
255     prefix = [env.qemu_mips32]
256     if env.trace:
257       prefix += ['-d', 'in_asm,op,exec,cpu']
258     args = ['-Q'] + args
259
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]
268   else:
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)
274
275
276 def FindOrBuildIRT(allow_build = True):
277   if env.force_irt:
278     if env.force_irt == 'none':
279       return None
280     elif env.force_irt == 'core':
281       flavors = ['irt_core']
282     else:
283       irt = env.force_irt
284       if not os.path.exists(irt):
285         Fatal('IRT not found: %s' % irt)
286       return irt
287   else:
288     flavors = ['irt_core']
289
290   irt_paths = []
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)
295
296   for path in irt_paths:
297     if os.path.exists(path):
298       return path
299
300   if allow_build:
301     PrintBanner('irt not found. Building it with scons.')
302     irt = irt_paths[0]
303     BuildIRT(flavors[0])
304     assert(env.dry_run or os.path.exists(irt))
305     return irt
306
307   return None
308
309 def BuildIRT(flavor):
310   args = ('platform=%s naclsdk_validate=0 ' +
311           'sysinfo=0 -j8 %s') % (env.arch, flavor)
312   args = args.split()
313   Run([env.scons] + args, cwd=env.nacl_root)
314
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 ]
319     else:
320       sel_ldr = env.force_sel_ldr
321       if not os.path.exists(sel_ldr):
322         Fatal('sel_ldr not found: %s' % sel_ldr)
323       return sel_ldr
324   else:
325     modes = ['opt','dbg']
326
327   loaders = []
328   for mode in modes:
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():
334       sel_ldr += '.exe'
335     loaders.append(sel_ldr)
336
337   # If one exists, use it.
338   for sel_ldr in loaders:
339     if os.path.exists(sel_ldr):
340       return sel_ldr
341
342   # Build it
343   if allow_build:
344     PrintBanner('sel_ldr not found. Building it with scons.')
345     sel_ldr = loaders[0]
346     BuildSelLdr(modes[0])
347     assert(env.dry_run or os.path.exists(sel_ldr))
348     return sel_ldr
349
350   return None
351
352 def BuildSelLdr(mode):
353   args = ('platform=%s MODE=%s-host naclsdk_validate=0 ' +
354           'sysinfo=0 -j8 sel_ldr') % (env.arch, mode)
355   args = args.split()
356   Run([env.scons] + args, cwd=env.nacl_root)
357
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' ]
363   if env.zerocost_eh:
364     args.append('--pnacl-allow-zerocost-eh')
365   Run(args)
366   return output_file
367
368 def Stringify(args):
369   ret = ''
370   for arg in args:
371     if ' ' in arg:
372       ret += ' "%s"' % arg
373     else:
374       ret += ' %s' % arg
375   return ret.strip()
376
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.
381
382   Args:
383     stdin_string: The characters to pipe to the subprocess.
384
385   Returns:
386     An open temporary file object ready to be read from.
387   """
388   f = tempfile.TemporaryFile()
389   f.write(stdin_string)
390   f.seek(0)
391   return f
392
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.
396
397   Args:
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.
408
409   Returns:
410     A string containing the concatenation of any captured stdout plus
411     any captured stderr.
412   """
413   if verbose:
414     PrintCommand(Stringify(args))
415     if env.dry_run:
416       return
417
418   stdout_redir = None
419   stderr_redir = None
420   stdin_redir = None
421   if capture_stdout:
422     stdout_redir = subprocess.PIPE
423   if capture_stderr:
424     stderr_redir = subprocess.PIPE
425   if stdin_string:
426     stdin_redir = PrepareStdin(stdin_string)
427
428   p = None
429   try:
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:
437     if p:
438       p.kill()
439     raise e
440   except BaseException, e:
441     if p:
442       p.kill()
443     raise e
444
445   env.last_return_code = p.returncode
446
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)
453
454   return (stdout_contents or '') + (stderr_contents or '')
455
456
457 def ArgSplit(argv):
458   """Parse command-line arguments.
459
460   Returns:
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.
463   """
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 ' +
488                       '--collate.')
489   parser.add_argument('--collate', action='store_true', default=False,
490                       help="Combine/collate sel_ldr's stdout and stderr, and " +
491                       "print to stdout.")
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',
497                       choices=sorted(
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)
509
510   # Copy the options into env.
511   for (key, value) in vars(options).iteritems():
512     setattr(env, key, value)
513
514   args += options.remainder
515   nexe = args[0] if len(args) else ''
516   env.is_pnacl = nexe.endswith('.pexe')
517
518   if env.arch == 'env':
519     # Get the architecture from the environment.
520     try:
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:]
534
535
536 def Fatal(msg, *args):
537   if len(args) > 0:
538     msg = msg % args
539   print msg
540   sys.exit(1)
541
542
543 def FindReadElf():
544   '''Returns the path of "readelf" binary.'''
545
546   candidates = []
547   # Use PNaCl's if it available.
548   candidates.append(
549     os.path.join(env.pnacl_base, 'bin', 'pnacl-readelf'))
550
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'))
554
555   for readelf in candidates:
556     if os.path.exists(readelf):
557       return readelf
558
559   Fatal('Cannot find readelf!')
560
561
562 def ReadELFInfo(f):
563   ''' Returns: (arch, is_dynamic, is_glibc_static) '''
564
565   readelf = env.readelf
566   readelf_out = Run([readelf, '-lh', f], capture_stdout=True, verbose=False)
567
568   machine_line = None
569   is_dynamic = False
570   is_glibc_static = False
571   for line in readelf_out.split('\n'):
572     line = line.strip()
573     if line.startswith('Machine:'):
574       machine_line = line
575     if line.startswith('DYNAMIC'):
576       is_dynamic = True
577     if '__libc_atexit' in line:
578       is_glibc_static = True
579
580   if not machine_line:
581     Fatal('Script error: readelf output did not make sense!')
582
583   if 'Intel 80386' in machine_line:
584     arch = 'x86-32'
585   elif 'X86-64' in machine_line:
586     arch = 'x86-64'
587   elif 'ARM' in machine_line:
588     arch = 'arm'
589   elif 'MIPS' in machine_line:
590     arch = 'mips32'
591   else:
592     Fatal('%s: Unknown machine type', f)
593
594   return (arch, is_dynamic, is_glibc_static)
595
596
597 def FindBaseDir():
598   '''Crawl backwards, starting from the directory containing this script,
599      until we find the native_client/ directory.
600   '''
601
602   curdir = os.path.abspath(sys.argv[0])
603   while os.path.basename(curdir) != 'native_client':
604     curdir,subdir = os.path.split(curdir)
605     if subdir == '':
606       # We've hit the file system root
607       break
608
609   if os.path.basename(curdir) != 'native_client':
610     Fatal('Unable to find native_client directory!')
611   return curdir
612
613
614 if __name__ == '__main__':
615   sys.exit(main(sys.argv))