blob: 6ee568165faccf3bbd215d1116d0f1d3ef63a7d8 [file] [log] [blame]
#!/usr/bin/python3
# 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(b'\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 + '/'