[Windows] Autolink with basenames and add libdir to libpath

Prior to this change, for a few compiler-rt libraries such as ubsan and
the profile library, Clang would embed "-defaultlib:path/to/rt-arch.lib"
into the .drective section of every object compiled with
-finstr-profile-generate or -fsanitize=ubsan as appropriate.

These paths assume that the link step will run from the same working
directory as the compile step. There is also evidence that sometimes the
paths become absolute, such as when clang is run from a different drive
letter from the current working directory. This is fragile, and I'd like
to get away from having paths embedded in the object if possible. Long
ago it was suggested that we use this for ASan, and apparently I felt
the same way back then:
https://reviews.llvm.org/D4428#56536

This is also consistent with how all other autolinking usage works for
PS4, Mac, and Windows: they all use basenames, not paths.

To keep things working for people using the standard GCC driver
workflow, the driver now adds the resource directory to the linker
library search path when it calls the linker. This is enough to make
check-ubsan pass, and seems like a generally good thing.

Users that invoke the linker directly (most clang-cl users) will have to
add clang's resource library directory to their linker search path in
their build system. I'm not sure where I can document this. Ideally I'd
also do it in the MSBuild files, but I can't figure out where they go.
I'd like to start with this for now.

Reviewed By: hans

Differential Revision: https://reviews.llvm.org/D65543
This commit is contained in:
Reid Kleckner 2019-07-31 14:52:00 -07:00
parent 59b9e6fe76
commit b8000c0ce8
10 changed files with 142 additions and 25 deletions

View File

@ -64,12 +64,22 @@ Non-comprehensive list of changes in this release
- For the ARM target, C-language intrinsics ``<arm_cde.h>`` for the CDE
instruction set are now provided.
* clang adds support for a set of extended integer types (``_ExtInt(N)``) that
- clang adds support for a set of extended integer types (``_ExtInt(N)``) that
permit non-power of 2 integers, exposing the LLVM integer types. Since a major
motivating use case for these types is to limit 'bit' usage, these types don't
automatically promote to 'int' when operations are done between two ``ExtInt(N)``
types, instead math occurs at the size of the largest ``ExtInt(N)`` type.
- Users of UBSan, PGO, and coverage on Windows will now need to add clang's
library resource directory to their library search path. These features all
use runtime libraries, and Clang provides these libraries in its resource
directory. For example, if LLVM is installed in ``C:\Program Files\LLVM``,
then the profile runtime library will appear at
``C:\Program Files\LLVM\lib\clang\11.0.0\lib\windows\clang_rt.profile-x86_64.lib``.
To ensure that the linker can find the appropriate library, users should pass
``/LIBPATH:C:\Program Files\LLVM\lib\clang\11.0.0\lib\windows`` to the
linker. If the user links the program with the ``clang`` or ``clang-cl``
drivers, the driver will pass this flag for them.
New Compiler Flags

View File

@ -3668,3 +3668,56 @@ This option is intended to be used as a temporary means to build projects where
clang-cl cannot successfully compile all the files. clang-cl may fail to compile
a file either because it cannot generate code for some C++ feature, or because
it cannot parse some Microsoft language extension.
Finding Clang runtime libraries
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
clang-cl supports several features that require runtime library support:
- Address Sanitizer (ASan): ``-fsanitize=address``
- Undefined Behavior Sanitizer (UBSan): ``-fsanitize=undefined``
- Code coverage: ``-fprofile-instr-generate -fcoverage-mapping``
- Profile Guided Optimization (PGO): ``-fprofile-instr-generate``
- Certain math operations (int128 division) require the builtins library
In order to use these features, the user must link the right runtime libraries
into their program. These libraries are distributed alongside Clang in the
library resource directory. Clang searches for the resource directory by
searching relative to the Clang executable. For example, if LLVM is installed
in ``C:\Program Files\LLVM``, then the profile runtime library will be located
at the path
``C:\Program Files\LLVM\lib\clang\11.0.0\lib\windows\clang_rt.profile-x86_64.lib``.
For UBSan, PGO, and coverage, Clang will emit object files that auto-link the
appropriate runtime library, but the user generally needs to help the linker
(whether it is ``lld-link.exe`` or MSVC ``link.exe``) find the library resource
directory. Using the example installation above, this would mean passing
``/LIBPATH:C:\Program Files\LLVM\lib\clang\11.0.0\lib\windows`` to the linker.
If the user links the program with the ``clang`` or ``clang-cl`` drivers, the
driver will pass this flag for them.
If the linker cannot find the appropriate library, it will emit an error like
this::
$ clang-cl -c -fsanitize=undefined t.cpp
$ lld-link t.obj -dll
lld-link: error: could not open 'clang_rt.ubsan_standalone-x86_64.lib': no such file or directory
lld-link: error: could not open 'clang_rt.ubsan_standalone_cxx-x86_64.lib': no such file or directory
$ link t.obj -dll -nologo
LINK : fatal error LNK1104: cannot open file 'clang_rt.ubsan_standalone-x86_64.lib'
To fix the error, add the appropriate ``/libpath:`` flag to the link line.
For ASan, as of this writing, the user is also responsible for linking against
the correct ASan libraries.
If the user is using the dynamic CRT (``/MD``), then they should add
``clang_rt.asan_dynamic-x86_64.lib`` to the link line as a regular input. For
other architectures, replace x86_64 with the appropriate name here and below.
If the user is using the static CRT (``/MT``), then different runtimes are used
to produce DLLs and EXEs. To link a DLL, pass
``clang_rt.asan_dll_thunk-x86_64.lib``. To link an EXE, pass
``-wholearchive:clang_rt.asan-x86_64.lib``.

View File

@ -413,6 +413,11 @@ public:
getCompilerRTArgString(const llvm::opt::ArgList &Args, StringRef Component,
FileType Type = ToolChain::FT_Static) const;
std::string getCompilerRTBasename(const llvm::opt::ArgList &Args,
StringRef Component,
FileType Type = ToolChain::FT_Static,
bool AddArch = true) const;
// Returns target specific runtime path if it exists.
virtual Optional<std::string> getRuntimePath() const;

View File

@ -955,22 +955,24 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
if (TC.getTriple().isOSWindows() && needsUbsanRt()) {
// Instruct the code generator to embed linker directives in the object file
// that cause the required runtime libraries to be linked.
CmdArgs.push_back(Args.MakeArgString(
"--dependent-lib=" + TC.getCompilerRT(Args, "ubsan_standalone")));
CmdArgs.push_back(
Args.MakeArgString("--dependent-lib=" +
TC.getCompilerRTBasename(Args, "ubsan_standalone")));
if (types::isCXX(InputType))
CmdArgs.push_back(Args.MakeArgString(
"--dependent-lib=" + TC.getCompilerRT(Args, "ubsan_standalone_cxx")));
"--dependent-lib=" +
TC.getCompilerRTBasename(Args, "ubsan_standalone_cxx")));
}
if (TC.getTriple().isOSWindows() && needsStatsRt()) {
CmdArgs.push_back(Args.MakeArgString("--dependent-lib=" +
TC.getCompilerRT(Args, "stats_client")));
CmdArgs.push_back(Args.MakeArgString(
"--dependent-lib=" + TC.getCompilerRTBasename(Args, "stats_client")));
// The main executable must export the stats runtime.
// FIXME: Only exporting from the main executable (e.g. based on whether the
// translation unit defines main()) would save a little space, but having
// multiple copies of the runtime shouldn't hurt.
CmdArgs.push_back(Args.MakeArgString("--dependent-lib=" +
TC.getCompilerRT(Args, "stats")));
CmdArgs.push_back(Args.MakeArgString(
"--dependent-lib=" + TC.getCompilerRTBasename(Args, "stats")));
addIncludeLinkerOption(TC, Args, CmdArgs, "__sanitizer_stats_register");
}

View File

@ -390,8 +390,9 @@ std::string ToolChain::getCompilerRTPath() const {
return std::string(Path.str());
}
std::string ToolChain::getCompilerRT(const ArgList &Args, StringRef Component,
FileType Type) const {
std::string ToolChain::getCompilerRTBasename(const ArgList &Args,
StringRef Component, FileType Type,
bool AddArch) const {
const llvm::Triple &TT = getTriple();
bool IsITANMSVCWindows =
TT.isWindowsMSVCEnvironment() || TT.isWindowsItaniumEnvironment();
@ -413,18 +414,32 @@ std::string ToolChain::getCompilerRT(const ArgList &Args, StringRef Component,
break;
}
std::string ArchAndEnv;
if (AddArch) {
StringRef Arch = getArchNameForCompilerRTLib(*this, Args);
const char *Env = TT.isAndroid() ? "-android" : "";
ArchAndEnv = ("-" + Arch + Env).str();
}
return (Prefix + Twine("clang_rt.") + Component + ArchAndEnv + Suffix).str();
}
std::string ToolChain::getCompilerRT(const ArgList &Args, StringRef Component,
FileType Type) const {
// Check for runtime files in the new layout without the architecture first.
std::string CRTBasename =
getCompilerRTBasename(Args, Component, Type, /*AddArch=*/false);
for (const auto &LibPath : getLibraryPaths()) {
SmallString<128> P(LibPath);
llvm::sys::path::append(P, Prefix + Twine("clang_rt.") + Component + Suffix);
llvm::sys::path::append(P, CRTBasename);
if (getVFS().exists(P))
return std::string(P.str());
}
StringRef Arch = getArchNameForCompilerRTLib(*this, Args);
const char *Env = TT.isAndroid() ? "-android" : "";
// Fall back to the old expected compiler-rt name if the new one does not
// exist.
CRTBasename = getCompilerRTBasename(Args, Component, Type, /*AddArch=*/true);
SmallString<128> Path(getCompilerRTPath());
llvm::sys::path::append(Path, Prefix + Twine("clang_rt.") + Component + "-" +
Arch + Env + Suffix);
llvm::sys::path::append(Path, CRTBasename);
return std::string(Path.str());
}

View File

@ -812,8 +812,8 @@ static void addPGOAndCoverageFlags(const ToolChain &TC, Compilation &C,
CmdArgs.push_back("-fprofile-instrument=clang");
if (TC.getTriple().isWindowsMSVCEnvironment()) {
// Add dependent lib for clang_rt.profile
CmdArgs.push_back(Args.MakeArgString("--dependent-lib=" +
TC.getCompilerRT(Args, "profile")));
CmdArgs.push_back(Args.MakeArgString(
"--dependent-lib=" + TC.getCompilerRTBasename(Args, "profile")));
}
}
@ -830,8 +830,9 @@ static void addPGOAndCoverageFlags(const ToolChain &TC, Compilation &C,
}
if (PGOGenArg) {
if (TC.getTriple().isWindowsMSVCEnvironment()) {
CmdArgs.push_back(Args.MakeArgString("--dependent-lib=" +
TC.getCompilerRT(Args, "profile")));
// Add dependent lib for clang_rt.profile
CmdArgs.push_back(Args.MakeArgString(
"--dependent-lib=" + TC.getCompilerRTBasename(Args, "profile")));
}
if (PGOGenArg->getOption().matches(
PGOGenerateArg ? options::OPT_fprofile_generate_EQ

View File

@ -350,6 +350,16 @@ void visualstudio::Linker::ConstructJob(Compilation &C, const JobAction &JA,
Args.MakeArgString(std::string("-libpath:") + WindowsSdkLibPath));
}
// Add the compiler-rt library directories to libpath if they exist to help
// the linker find the various sanitizer, builtin, and profiling runtimes.
for (const auto &LibPath : TC.getLibraryPaths()) {
if (TC.getVFS().exists(LibPath))
CmdArgs.push_back(Args.MakeArgString("-libpath:" + LibPath));
}
auto CRTPath = TC.getCompilerRTPath();
if (TC.getVFS().exists(CRTPath))
CmdArgs.push_back(Args.MakeArgString("-libpath:" + CRTPath));
if (!C.getDriver().IsCLMode() && Args.hasArg(options::OPT_L))
for (const auto &LibPath : Args.getAllArgValues(options::OPT_L))
CmdArgs.push_back(Args.MakeArgString("-libpath:" + LibPath));

View File

@ -67,11 +67,11 @@
// RUN: %clang_cl -### /FA -fprofile-instr-generate -- %s 2>&1 | FileCheck -check-prefix=CHECK-PROFILE-INSTR-GENERATE %s
// RUN: %clang_cl -### /FA -fprofile-instr-generate=/tmp/somefile.profraw -- %s 2>&1 | FileCheck -check-prefix=CHECK-PROFILE-INSTR-GENERATE-FILE %s
// CHECK-PROFILE-INSTR-GENERATE: "-fprofile-instrument=clang" "--dependent-lib={{[^"]*}}clang_rt.profile-{{[^"]*}}.lib"
// CHECK-PROFILE-INSTR-GENERATE: "-fprofile-instrument=clang" "--dependent-lib=clang_rt.profile-{{[^"]*}}.lib"
// CHECK-PROFILE-INSTR-GENERATE-FILE: "-fprofile-instrument-path=/tmp/somefile.profraw"
// RUN: %clang_cl -### /FA -fprofile-generate -- %s 2>&1 | FileCheck -check-prefix=CHECK-PROFILE-GENERATE %s
// CHECK-PROFILE-GENERATE: "-fprofile-instrument=llvm" "--dependent-lib={{[^"]*}}clang_rt.profile-{{[^"]*}}.lib"
// CHECK-PROFILE-GENERATE: "-fprofile-instrument=llvm" "--dependent-lib=clang_rt.profile-{{[^"]*}}.lib"
// RUN: %clang_cl -### /FA -fprofile-instr-generate -fprofile-instr-use -- %s 2>&1 | FileCheck -check-prefix=CHECK-NO-MIX-GEN-USE %s
// RUN: %clang_cl -### /FA -fprofile-instr-generate -fprofile-instr-use=file -- %s 2>&1 | FileCheck -check-prefix=CHECK-NO-MIX-GEN-USE %s

View File

@ -656,16 +656,16 @@
// RUN: -target x86_64-pc-windows \
// RUN: --sysroot=%S/Inputs/basic_linux_tree \
// RUN: | FileCheck --check-prefix=CHECK-CFI-STATS-WIN64 %s
// CHECK-CFI-STATS-WIN64: "--dependent-lib={{[^"]*}}clang_rt.stats_client-x86_64.lib"
// CHECK-CFI-STATS-WIN64: "--dependent-lib={{[^"]*}}clang_rt.stats-x86_64.lib"
// CHECK-CFI-STATS-WIN64: "--dependent-lib=clang_rt.stats_client-x86_64.lib"
// CHECK-CFI-STATS-WIN64: "--dependent-lib=clang_rt.stats-x86_64.lib"
// CHECK-CFI-STATS-WIN64: "--linker-option=/include:__sanitizer_stats_register"
// RUN: %clang -fsanitize=cfi -fsanitize-stats %s -### -o %t.o 2>&1 \
// RUN: -target i686-pc-windows \
// RUN: --sysroot=%S/Inputs/basic_linux_tree \
// RUN: | FileCheck --check-prefix=CHECK-CFI-STATS-WIN32 %s
// CHECK-CFI-STATS-WIN32: "--dependent-lib={{[^"]*}}clang_rt.stats_client-i386.lib"
// CHECK-CFI-STATS-WIN32: "--dependent-lib={{[^"]*}}clang_rt.stats-i386.lib"
// CHECK-CFI-STATS-WIN32: "--dependent-lib=clang_rt.stats_client-i386.lib"
// CHECK-CFI-STATS-WIN32: "--dependent-lib=clang_rt.stats-i386.lib"
// CHECK-CFI-STATS-WIN32: "--linker-option=/include:___sanitizer_stats_register"
// RUN: %clang -no-canonical-prefixes %s -### -o %t.o 2>&1 \

View File

@ -886,6 +886,27 @@ if (LLVM_BUILD_INSTRUMENTED)
endif()
endif()
# When using clang-cl with an instrumentation-based tool, add clang's library
# resource directory to the library search path. Because cmake invokes the
# linker directly, it isn't sufficient to pass -fsanitize=* to the linker.
if (CLANG_CL AND (LLVM_BUILD_INSTRUMENTED OR LLVM_USE_SANITIZER))
execute_process(
COMMAND ${CMAKE_CXX_COMPILER} /clang:-print-resource-dir
OUTPUT_VARIABLE clang_resource_dir
ERROR_VARIABLE clang_cl_stderr
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE clang_cl_exit_code)
if (NOT "${clang_cl_exit_code}" STREQUAL "0")
message(FATAL_ERROR
"Unable to invoke clang-cl to find resource dir: ${clang_cl_stderr}")
endif()
file(TO_CMAKE_PATH "${clang_resource_dir}" clang_resource_dir)
append("/libpath:${clang_resource_dir}/lib/windows"
CMAKE_EXE_LINKER_FLAGS
CMAKE_SHARED_LINKER_FLAGS)
endif()
if(LLVM_PROFDATA_FILE AND EXISTS ${LLVM_PROFDATA_FILE})
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" )
append("-fprofile-instr-use=\"${LLVM_PROFDATA_FILE}\""