text: #!/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.
  #
  #
  # This is a thin wrapper for native LD. This is not meant to be
  # used by the user, but is called from pnacl-translate.
  # This implements the native linking part of translation.
  #
  # All inputs must be native objects or linker scripts.
  #
  # --pnacl-sb will cause the sandboxed LD to be used.
  # The bulk of this file is logic to invoke the sandboxed translator.
  
  import os
  import subprocess
  
  from driver_tools import CheckTranslatorPrerequisites, GetArch, ParseArgs, \
      Run, UnrecognizedOption
  from driver_env import env
  from driver_log import Log
  import driver_tools
  import elftools
  import ldtools
  import pathtools
  
  
  EXTRA_ENV = {
    'INPUTS'   : '',
    'OUTPUT'   : '',
  
    # The INPUTS file coming from the llc translation step
    'LLC_TRANSLATED_FILE' : '',
    # Number of separate modules used for multi-threaded translation. This should
    # have been set by pnacl-translate, but default to 0 for checking.
    'SPLIT_MODULE' : '0',
    'USE_STDLIB': '1',
  
    # Upstream gold has the segment gap built in, but the gap can be modified
    # when not using the IRT. The gap does need to be at least one bundle so the
    # halt sled can be added for the TCB in case the segment ends up being a
    # multiple of 64k.
    # --eh-frame-hdr asks the linker to generate an .eh_frame_hdr section,
    # which is a presorted list of registered frames. This section is
    # used by libgcc_eh/libgcc_s to avoid doing the sort during runtime.
    # http://www.airs.com/blog/archives/462
    #
    # BE CAREFUL: anything added to LD_FLAGS should be synchronized with
    # flags used by the in-browser translator.
    # See: binutils/gold/nacl_file.cc
    'LD_FLAGS'    : '-nostdlib ' +
                    # Only relevant for ARM where it suppresses a warning.
                    # Ignored for other archs.
                    '--no-fix-cortex-a8 ' +
                    '--eh-frame-hdr ' +
                    # Give an error if any TEXTRELs occur.
                    '-z text ' +
                    # Ensure we don't accidentally get READ_IMPLIES_EXEC
                    # behaviour when building Linux Non-SFI executables.
                    '-z noexecstack ' +
                    '--build-id ',
  
    'SEARCH_DIRS'        : '${SEARCH_DIRS_USER} ${SEARCH_DIRS_BUILTIN}',
    'SEARCH_DIRS_USER'   : '',
    'SEARCH_DIRS_BUILTIN': '${USE_STDLIB ? ${LIBS_NATIVE_ARCH}/}',
  
    # Note: this is only used in the unsandboxed case
    'RUN_LD' : '${LD} ${LD_FLAGS} ${inputs} -o ${output}'
  }
  
  def PassThrough(*args):
    env.append('LD_FLAGS', *args)
  
  LDPatterns = [
    ( '-o(.+)',          "env.set('OUTPUT', pathtools.normalize($0))"),
    ( ('-o', '(.+)'),    "env.set('OUTPUT', pathtools.normalize($0))"),
  
    ( '-nostdlib',       "env.set('USE_STDLIB', '0')"),
  
    ( '-L(.+)',
      "env.append('SEARCH_DIRS_USER', pathtools.normalize($0))"),
    ( ('-L', '(.*)'),
      "env.append('SEARCH_DIRS_USER', pathtools.normalize($0))"),
  
    # Note: we do not yet support all the combinations of flags which affect
    # layout of the various sections and segments because the corner cases in gold
    # may not all be worked out yet. They can be added (and tested!) as needed.
    ( '(-static)',                  PassThrough),
    ( '(-pie)',                     PassThrough),
  
    ( ('(-Ttext=.*)'),              PassThrough),
    ( ('(-Trodata=.*)'),            PassThrough),
    ( ('(-Ttext-segment=.*)'),      PassThrough),
    ( ('(-Trodata-segment=.*)'),    PassThrough),
    ( ('(--rosegment-gap=.*)'),     PassThrough),
    ( ('(--section-start)', '(.+)'),PassThrough),
    ( ('(--section-start=.*)'),     PassThrough),
    ( ('(-e)','(.*)'),              PassThrough),
    ( '(--entry=.*)',               PassThrough),
    ( '(-M)',                       PassThrough),
    ( '(-t)',                       PassThrough),
    ( ('-y','(.*)'),                PassThrough),
    ( ('(-defsym)','(.*)'),         PassThrough),
    ( '(-defsym=.*)',               PassThrough),
    ( '-export-dynamic',            PassThrough),
  
    ( '(--print-gc-sections)',      PassThrough),
    ( '(--gc-sections)',            PassThrough),
    ( '(--unresolved-symbols=.*)',  PassThrough),
    ( '(--dynamic-linker=.*)',      PassThrough),
    ( '(-g)',                       PassThrough),
    ( '(--build-id)',               PassThrough),
  
    ( '-melf_nacl',            "env.set('ARCH', 'X8632')"),
    ( ('-m','elf_nacl'),       "env.set('ARCH', 'X8632')"),
    ( '-melf64_nacl',          "env.set('ARCH', 'X8664')"),
    ( ('-m','elf64_nacl'),     "env.set('ARCH', 'X8664')"),
    ( '-marmelf_nacl',         "env.set('ARCH', 'ARM')"),
    ( ('-m','armelf_nacl'),    "env.set('ARCH', 'ARM')"),
    ( '-mmipselelf_nacl',      "env.set('ARCH', 'MIPS32')"),
    ( ('-m','mipselelf_nacl'), "env.set('ARCH', 'MIPS32')"),
  
    # Inputs and options that need to be kept in order
    ( '(--no-as-needed)',    "env.append('INPUTS', $0)"),
    ( '(--as-needed)',       "env.append('INPUTS', $0)"),
    ( '(--start-group)',     "env.append('INPUTS', $0)"),
    ( '(--end-group)',       "env.append('INPUTS', $0)"),
    ( '(-Bstatic)',          "env.append('INPUTS', $0)"),
    ( '(-Bdynamic)',         "env.append('INPUTS', $0)"),
    # This is the file passed from llc during translation (used to be via shmem)
    ( ('--llc-translated-file=(.*)'), "env.append('INPUTS', $0)\n"
                                      "env.set('LLC_TRANSLATED_FILE', $0)"),
    ( '-split-module=([0-9]+)', "env.set('SPLIT_MODULE', $0)"),
    ( '(--(no-)?whole-archive)', "env.append('INPUTS', $0)"),
  
    ( '(-l.*)',              "env.append('INPUTS', $0)"),
    ( '(--undefined=.*)',    "env.append('INPUTS', $0)"),
  
    ( '(-.*)',               UnrecognizedOption),
    ( '(.*)',                "env.append('INPUTS', pathtools.normalize($0))"),
  ]
  
  def RemoveInterpProgramHeader(filename):
    headers = elftools.GetELFAndProgramHeaders(filename)
    assert headers
    ehdr, phdrs = headers
    for i, phdr in enumerate(phdrs):
      if phdr.type == elftools.ProgramHeader.PT_INTERP:
        fp = open(filename, 'rb+')
        fp.seek(ehdr.phoff + ehdr.phentsize * i)
        # Zero this program header. Note PT_NULL is 0.
        fp.write('\0' * ehdr.phentsize)
        fp.close()
  
  def main(argv):
    env.update(EXTRA_ENV)
  
    ParseArgs(argv, LDPatterns)
  
    GetArch(required=True)
    inputs = env.get('INPUTS')
    output = env.getone('OUTPUT')
  
    if output == '':
      output = pathtools.normalize('a.out')
  
    # As we will modify the output file in-place for non-SFI, we output
    # the file to a temporary file first and then rename it. Otherwise,
    # build systems such as make assume the output file is ready even
    # if the last build failed during the in-place update.
    tmp_output = output + '.tmp'
  
    # Expand all parameters
    # This resolves -lfoo into actual filenames,
    # and expands linker scripts into command-line arguments.
    inputs = ldtools.ExpandInputs(inputs,
                                  env.get('SEARCH_DIRS'),
                                  True,
                                  ldtools.LibraryTypes.NATIVE)
  
    env.push()
    env.set('inputs', *inputs)
    env.set('output', tmp_output)
  
    if env.getbool('SANDBOXED'):
      RunLDSandboxed()
    else:
      Run('${RUN_LD}')
  
    if env.getbool('NONSFI_NACL'):
      # Remove PT_INTERP in non-SFI binaries as we never use host's
      # dynamic linker/loader.
      #
      # This is necessary otherwise we get a statically linked
      # executable that is not directly runnable by Linux, because Linux
      # tries to load the non-existent file that PT_INTERP points to.
      #
      # This is fairly hacky.  It would be better if the linker provided
      # an option for omitting PT_INTERP (e.g. "--dynamic-linker ''").
      RemoveInterpProgramHeader(tmp_output)
    if driver_tools.IsWindowsPython() and os.path.exists(output):
      # On Windows (but not on Unix), the os.rename() call would fail if the
      # output file already exists.
      os.remove(output)
    os.rename(tmp_output, output)
    env.pop()
    # only reached in case of no errors
    return 0
  
  def IsFlag(arg):
    return arg.startswith('-')
  
  def RunLDSandboxed():
    if not env.getbool('USE_STDLIB'):
      Log.Fatal('-nostdlib is not supported by the sandboxed translator')
    CheckTranslatorPrerequisites()
    # The "main" input file is the application's combined object file.
    all_inputs = env.get('inputs')
  
    main_input = env.getone('LLC_TRANSLATED_FILE')
    if not main_input:
      Log.Fatal("Sandboxed LD requires one shm input file")
  
    outfile = env.getone('output')
  
    modules = int(env.getone('SPLIT_MODULE'))
    assert modules >= 1
    first_mainfile = all_inputs.index(main_input)
    first_extra = all_inputs.index(main_input) + modules
    # Have a list of just the split module files.
    llc_outputs = all_inputs[first_mainfile:first_extra]
    # Have a list of everything else.
    other_inputs = all_inputs[:first_mainfile] + all_inputs[first_extra:]
  
    native_libs_dirname = pathtools.tosys(GetNativeLibsDirname(other_inputs))
    command = [driver_tools.SelLdrCommand(),
               '-a'] # Allow file access
    driver_tools.AddListToEnv(command, 'NACL_IRT_PNACL_TRANSLATOR_LINK_INPUT',
                              llc_outputs)
    command.extend([
        '-E', 'NACL_IRT_PNACL_TRANSLATOR_LINK_OUTPUT=%s ' % outfile,
        '-E', 'NACL_IRT_OPEN_RESOURCE_BASE=%s' % native_libs_dirname,
        '-E', 'NACL_IRT_OPEN_RESOURCE_REMAP=%s'
        % 'libpnacl_irt_shim.a:libpnacl_irt_shim_dummy.a',
        '--', '${LD_SB}'])
    Run(' '.join(command),
        # stdout/stderr will be automatically dumped
        # upon failure
        redirect_stderr=subprocess.PIPE,
        redirect_stdout=subprocess.PIPE)
  
  
  def GetNativeLibsDirname(other_inputs):
    """Check that native libs have a common directory and return the directory."""
    dirname = None
    for f in other_inputs:
      if IsFlag(f):
        continue
      else:
        if not pathtools.exists(f):
          Log.Fatal("Unable to open '%s'", pathtools.touser(f))
        if dirname is None:
          dirname = pathtools.dirname(f)
        else:
          if dirname != pathtools.dirname(f):
            Log.Fatal('Need a common directory for native libs: %s != %s',
                      dirname, pathtools.dirname(f))
    if not dirname:
      Log.Fatal('No native libraries found')
    return dirname + '/'
  