diff --git a/clang/tools/driver/driver.cpp b/clang/tools/driver/driver.cpp --- a/clang/tools/driver/driver.cpp +++ b/clang/tools/driver/driver.cpp @@ -531,6 +531,11 @@ IsCrash = CommandRes < 0 || CommandRes == 70; #ifdef _WIN32 IsCrash |= CommandRes == 3; +#endif +#if LLVM_ON_UNIX + // When running in integrated-cc1 mode, the CrashRecoveryContext returns + // the same code as if the program crashed. On Unix, that is codes >128. + IsCrash |= CommandRes > 128; #endif if (IsCrash) { TheDriver.generateCompilationDiagnostics(*C, *FailingCommand); diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -68,6 +68,17 @@ lld::stdoutOS = &stdoutOS; lld::stderrOS = &stderrOS; + errorHandler().cleanupCallback = []() { + freeArena(); + ObjFile::instances.clear(); + PDBInputFile::instances.clear(); + ImportFile::instances.clear(); + BitcodeFile::instances.clear(); + memset(MergeChunk::instances, 0, sizeof(MergeChunk::instances)); + TpiSource::clear(); + OutputSection::clear(); + }; + errorHandler().logName = args::getFilenameWithoutExe(args[0]); errorHandler().errorLimitExceededMsg = "too many errors emitted, stopping now" @@ -85,13 +96,6 @@ if (canExitEarly) exitLld(errorCount() ? 1 : 0); - freeArena(); - ObjFile::instances.clear(); - ImportFile::instances.clear(); - BitcodeFile::instances.clear(); - memset(MergeChunk::instances, 0, sizeof(MergeChunk::instances)); - TpiSource::clear(); - return !errorCount(); } @@ -1204,6 +1208,7 @@ v.push_back("lld-link (LLVM option parsing)"); for (auto *arg : args.filtered(OPT_mllvm)) v.push_back(arg->getValue()); + cl::ResetAllOptionOccurrences(); cl::ParseCommandLineOptions(v.size(), v.data()); // Handle /errorlimit early, because error() depends on it. diff --git a/lld/COFF/Writer.h b/lld/COFF/Writer.h --- a/lld/COFF/Writer.h +++ b/lld/COFF/Writer.h @@ -50,6 +50,9 @@ void writeHeaderTo(uint8_t *buf); void addContributingPartialSection(PartialSection *sec); + // Clear the output sections static container. + static void clear(); + // Returns the size of this section in an executable memory image. // This may be smaller than the raw size (the raw size is multiple // of disk sector size, so there may be padding at end), or may be diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp --- a/lld/COFF/Writer.cpp +++ b/lld/COFF/Writer.cpp @@ -88,6 +88,8 @@ return osidx == 0 ? nullptr : outputSections[osidx - 1]; } +void OutputSection::clear() { outputSections.clear(); } + namespace { class DebugDirectoryChunk : public NonSectionChunk { @@ -601,9 +603,6 @@ void Writer::run() { ScopedTimer t1(codeLayoutTimer); - // First, clear the output sections from previous runs - outputSections.clear(); - createImportTables(); createSections(); createMiscChunks(); diff --git a/lld/Common/ErrorHandler.cpp b/lld/Common/ErrorHandler.cpp --- a/lld/Common/ErrorHandler.cpp +++ b/lld/Common/ErrorHandler.cpp @@ -13,15 +13,13 @@ #include "llvm/ADT/Twine.h" #include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/DiagnosticPrinter.h" +#include "llvm/Support/CrashRecoveryContext.h" #include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/Process.h" #include "llvm/Support/raw_ostream.h" #include #include -#if !defined(_MSC_VER) && !defined(__MINGW32__) -#include -#endif - using namespace llvm; using namespace lld; @@ -43,14 +41,23 @@ raw_ostream *lld::stdoutOS; raw_ostream *lld::stderrOS; -raw_ostream &lld::outs() { return stdoutOS ? *stdoutOS : llvm::outs(); } -raw_ostream &lld::errs() { return stderrOS ? *stderrOS : llvm::errs(); } - ErrorHandler &lld::errorHandler() { static ErrorHandler handler; return handler; } +raw_ostream &lld::outs() { + if (errorHandler().disableOutput) + return llvm::nulls(); + return stdoutOS ? *stdoutOS : llvm::outs(); +} + +raw_ostream &lld::errs() { + if (errorHandler().disableOutput) + return llvm::nulls(); + return stderrOS ? *stderrOS : llvm::errs(); +} + void lld::exitLld(int val) { // Delete any temporary file, while keeping the memory mapping open. if (errorHandler().outputBuffer) @@ -60,14 +67,15 @@ // In an LTO build, allows us to get the output of -time-passes. // Ensures that the thread pool for the parallel algorithms is stopped to // avoid intermittent crashes on Windows when exiting. - llvm_shutdown(); + if (!CrashRecoveryContext::GetCurrent()) + llvm_shutdown(); { std::lock_guard lock(mu); lld::outs().flush(); lld::errs().flush(); } - _exit(val); + llvm::sys::Process::Exit(val); } void lld::diagnosticHandler(const DiagnosticInfo &di) { @@ -153,13 +161,15 @@ } void ErrorHandler::log(const Twine &msg) { - if (!verbose) + if (!verbose || disableOutput) return; std::lock_guard lock(mu); lld::errs() << logName << ": " << msg << "\n"; } void ErrorHandler::message(const Twine &msg) { + if (disableOutput) + return; std::lock_guard lock(mu); lld::outs() << msg << "\n"; lld::outs().flush(); diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -80,6 +80,27 @@ lld::stdoutOS = &stdoutOS; lld::stderrOS = &stderrOS; + errorHandler().cleanupCallback = []() { + freeArena(); + + inputSections.clear(); + outputSections.clear(); + archiveFiles.clear(); + binaryFiles.clear(); + bitcodeFiles.clear(); + lazyObjFiles.clear(); + objectFiles.clear(); + sharedFiles.clear(); + backwardReferences.clear(); + + tar = nullptr; + memset(&in, 0, sizeof(in)); + + partitions = {Partition()}; + + SharedFile::vernauxNum = 0; + }; + errorHandler().logName = args::getFilenameWithoutExe(args[0]); errorHandler().errorLimitExceededMsg = "too many errors emitted, stopping now (use " @@ -87,27 +108,12 @@ errorHandler().exitEarly = canExitEarly; stderrOS.enable_colors(stderrOS.has_colors()); - inputSections.clear(); - outputSections.clear(); - archiveFiles.clear(); - binaryFiles.clear(); - bitcodeFiles.clear(); - lazyObjFiles.clear(); - objectFiles.clear(); - sharedFiles.clear(); - backwardReferences.clear(); - config = make(); driver = make(); script = make(); symtab = make(); - tar = nullptr; - memset(&in, 0, sizeof(in)); - - partitions = {Partition()}; - - SharedFile::vernauxNum = 0; + partitions = { Partition() }; config->progName = args[0]; @@ -119,7 +125,6 @@ if (canExitEarly) exitLld(errorCount() ? 1 : 0); - freeArena(); return !errorCount(); } @@ -887,6 +892,7 @@ raw_string_ostream os(err); const char *argv[] = {config->progName.data(), opt.data()}; + cl::ResetAllOptionOccurrences(); if (cl::ParseCommandLineOptions(2, argv, "", &os)) return; os.flush(); diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -478,6 +478,8 @@ stderrOS.enable_colors(stderrOS.has_colors()); // TODO: Set up error handler properly, e.g. the errorLimitExceededMsg + errorHandler().cleanupCallback = []() { freeArena(); }; + MachOOptTable parser; opt::InputArgList args = parser.parse(argsArr.slice(1)); @@ -630,6 +632,5 @@ if (canExitEarly) exitLld(errorCount() ? 1 : 0); - freeArena(); return !errorCount(); } diff --git a/lld/include/lld/Common/Driver.h b/lld/include/lld/Common/Driver.h --- a/lld/include/lld/Common/Driver.h +++ b/lld/include/lld/Common/Driver.h @@ -13,6 +13,18 @@ #include "llvm/Support/raw_ostream.h" namespace lld { +struct SafeReturn { + int ret; + bool canRunAgain; +}; + +// Generic entry point when using LLD as a library, safe for re-entry, supports +// crash recovery. Returns a general completion code and a boolean telling +// whether it can be called again. In some cases, a crash could corrupt memory +// and re-entry would not be possible anymore. +SafeReturn safeLldMain(int argc, const char **argv, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS); + namespace coff { bool link(llvm::ArrayRef args, bool canExitEarly, llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS); diff --git a/lld/include/lld/Common/ErrorHandler.h b/lld/include/lld/Common/ErrorHandler.h --- a/lld/include/lld/Common/ErrorHandler.h +++ b/lld/include/lld/Common/ErrorHandler.h @@ -99,6 +99,8 @@ bool fatalWarnings = false; bool verbose = false; bool vsDiagnostics = false; + bool disableOutput = false; + std::function cleanupCallback; void error(const Twine &msg); LLVM_ATTRIBUTE_NORETURN void fatal(const Twine &msg); @@ -106,6 +108,12 @@ void message(const Twine &msg); void warn(const Twine &msg); + void reset() { + if (cleanupCallback) + cleanupCallback(); + *this = ErrorHandler(); + } + std::unique_ptr outputBuffer; private: diff --git a/lld/lib/Driver/DarwinLdDriver.cpp b/lld/lib/Driver/DarwinLdDriver.cpp --- a/lld/lib/Driver/DarwinLdDriver.cpp +++ b/lld/lib/Driver/DarwinLdDriver.cpp @@ -307,6 +307,7 @@ for (unsigned i = 0; i != numArgs; ++i) args[i + 1] = ctx.llvmOptions()[i]; args[numArgs + 1] = nullptr; + llvm::cl::ResetAllOptionOccurrences(); llvm::cl::ParseCommandLineOptions(numArgs + 1, args); } } diff --git a/lld/test/COFF/dll.test b/lld/test/COFF/dll.test --- a/lld/test/COFF/dll.test +++ b/lld/test/COFF/dll.test @@ -13,10 +13,10 @@ EXPORT-NEXT: 3 0x1010 exportfn3 EXPORT-NEXT: 4 0x1010 mangled -# RUN: yaml2obj %p/Inputs/export2.yaml -o %t5.obj -# RUN: rm -f %t5.lib -# RUN: llvm-ar cru %t5.lib %t5.obj -# RUN: lld-link /out:%t5.dll /dll %t.obj %t5.lib /export:mangled2 +# RUN: yaml2obj %p/Inputs/export2.yaml -o %t4.obj +# RUN: rm -f %t4.lib +# RUN: llvm-ar cru %t4.lib %t4.obj +# RUN: lld-link /out:%t5.dll /dll %t.obj %t4.lib /export:mangled2 # RUN: llvm-objdump -p %t5.dll | FileCheck --check-prefix=EXPORT2 %s EXPORT2: Export Table: diff --git a/lld/test/COFF/guardcf-lto.ll b/lld/test/COFF/guardcf-lto.ll --- a/lld/test/COFF/guardcf-lto.ll +++ b/lld/test/COFF/guardcf-lto.ll @@ -8,8 +8,8 @@ ; RUN: llvm-mc -triple x86_64-windows-msvc -filetype=obj %S/Inputs/loadconfig-cfg-x64.s -o %t.ldcfg.obj ; RUN: llvm-as %s -o %t.bc -; RUN: lld-link -entry:main -guard:cf -dll %t.bc %t.lib %t.ldcfg.obj -out:%t.dll -; RUN: llvm-readobj --coff-load-config %t.dll | FileCheck %s +; RUN: lld-link -entry:main -guard:cf -dll %t.bc %t.lib %t.ldcfg.obj -out:%t2.dll +; RUN: llvm-readobj --coff-load-config %t2.dll | FileCheck %s ; There must be *two* entries in the table: DLL entry point, and my_handler. diff --git a/lld/test/COFF/lit.local.cfg b/lld/test/COFF/lit.local.cfg new file mode 100644 --- /dev/null +++ b/lld/test/COFF/lit.local.cfg @@ -0,0 +1 @@ +config.environment['LLD_IN_TEST'] = '2' diff --git a/lld/tools/lld/lld.cpp b/lld/tools/lld/lld.cpp --- a/lld/tools/lld/lld.cpp +++ b/lld/tools/lld/lld.cpp @@ -26,6 +26,7 @@ //===----------------------------------------------------------------------===// #include "lld/Common/Driver.h" +#include "lld/Common/ErrorHandler.h" #include "lld/Common/Memory.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" @@ -33,12 +34,19 @@ #include "llvm/ADT/Triple.h" #include "llvm/ADT/Twine.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/CrashRecoveryContext.h" #include "llvm/Support/Host.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Path.h" #include "llvm/Support/PluginLoader.h" +#include "llvm/Support/Signals.h" #include +#if !defined(_MSC_VER) && !defined(__MINGW32__) +#include // for _exit +#include // for raise +#endif + using namespace lld; using namespace llvm; using namespace llvm::sys; @@ -128,36 +136,103 @@ return parseProgname(arg0); } -// If this function returns true, lld calls _exit() so that it quickly -// exits without invoking destructors of globally allocated objects. -// -// We don't want to do that if we are running tests though, because -// doing that breaks leak sanitizer. So, lit sets this environment variable, -// and we use it to detect whether we are running tests or not. -static bool canExitEarly() { return StringRef(getenv("LLD_IN_TEST")) != "1"; } - /// Universal linker main(). This linker emulates the gnu, darwin, or /// windows linker based on the argv[0] or -flavor option. -int main(int argc, const char **argv) { - InitLLVM x(argc, argv); - +static int lldMain(int argc, const char **argv, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly = true) { std::vector args(argv, argv + argc); switch (parseFlavor(args)) { case Gnu: if (isPETarget(args)) - return !mingw::link(args, canExitEarly(), llvm::outs(), llvm::errs()); - return !elf::link(args, canExitEarly(), llvm::outs(), llvm::errs()); + return !mingw::link(args, exitEarly, stdoutOS, stderrOS); + return !elf::link(args, exitEarly, stdoutOS, stderrOS); case WinLink: - return !coff::link(args, canExitEarly(), llvm::outs(), llvm::errs()); + return !coff::link(args, exitEarly, stdoutOS, stderrOS); case Darwin: - return !mach_o::link(args, canExitEarly(), llvm::outs(), llvm::errs()); + return !mach_o::link(args, exitEarly, stdoutOS, stderrOS); case DarwinNew: - return !macho::link(args, canExitEarly(), llvm::outs(), llvm::errs()); + return !macho::link(args, exitEarly, stdoutOS, stderrOS); case Wasm: - return !wasm::link(args, canExitEarly(), llvm::outs(), llvm::errs()); + return !lld::wasm::link(args, exitEarly, stdoutOS, stderrOS); default: die("lld is a generic driver.\n" "Invoke ld.lld (Unix), ld64.lld (macOS), lld-link (Windows), wasm-ld" " (WebAssembly) instead"); } } + +// Similar to lldMain except that exceptions are caught. +SafeReturn lld::safeLldMain(int argc, const char **argv, + llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS) { + int r = 0; + { + // The crash recovery is here only to be able to recover from arbitrary + // control flow when fatal() is called (through setjmp/longjmp or + // __try/__except). + llvm::CrashRecoveryContext crc; + if (!crc.RunSafely([&]() { + r = lldMain(argc, argv, stdoutOS, stderrOS, /*exitEarly=*/false); + })) + r = crc.RetCode; + } + + // Cleanup memory and reset everything back in pristine condition. This path + // is only taken when LLD is in test, or when it is used as a library. + llvm::CrashRecoveryContext crc; + if (!crc.RunSafely([&]() { errorHandler().reset(); })) { + // The memory is corrupted beyond any possible recovery. + return {r, /*canRunAgain=*/false}; + } + return {r, /*canRunAgain=*/true}; +} + +// When in lit tests, tells how many times the LLD tool should re-execute the +// main loop with the same inputs. When not in test, returns a value of 0 which +// signifies that LLD shall not release any memory after execution, to speed up +// process destruction. +static unsigned inTestVerbosity() { + unsigned v = 0; + StringRef(getenv("LLD_IN_TEST")).getAsInteger(10, v); + return v; +} + +int main(int argc, const char **argv) { + InitLLVM x(argc, argv); + + // Not running in lit tests, just take the shortest codepath with global + // exception handling and no memory cleanup on exit. + if (!inTestVerbosity()) + return lldMain(argc, argv, llvm::outs(), llvm::errs()); + + Optional mainRet; + CrashRecoveryContext::Enable(); + + for (unsigned i = inTestVerbosity(); i > 0; --i) { + // Disable stdout/stderr for all iterations but the last one. + if (i != 1) + errorHandler().disableOutput = true; + + // Execute one iteration. + auto r = safeLldMain(argc, argv, llvm::outs(), llvm::errs()); + if (!r.canRunAgain) + _exit(r.ret); // Exit now, can't re-execute again. + + if (!mainRet) { + mainRet = r.ret; + } else if (r.ret != *mainRet) { + // Exit now, to fail the tests if the result is different between runs. + return r.ret; + } + } +#if LLVM_ON_UNIX + // Re-throw the signal so it can be caught by WIFSIGNALED in + // llvm/lib/Support/Unix/Program.inc. This is required to correctly handle + // usages of `not --crash`. + if (*mainRet > 128) { + llvm::sys::UnregisterHandlers(); + raise(*mainRet - 128); + } +#endif + return *mainRet; +} diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -85,6 +85,8 @@ lld::stdoutOS = &stdoutOS; lld::stderrOS = &stderrOS; + errorHandler().cleanupCallback = []() { freeArena(); }; + errorHandler().logName = args::getFilenameWithoutExe(args[0]); errorHandler().errorLimitExceededMsg = "too many errors emitted, stopping now (use " @@ -103,7 +105,6 @@ if (canExitEarly) exitLld(errorCount() ? 1 : 0); - freeArena(); return !errorCount(); } @@ -772,6 +773,7 @@ v.push_back("wasm-ld (LLVM option parsing)"); for (auto *arg : args.filtered(OPT_mllvm)) v.push_back(arg->getValue()); + cl::ResetAllOptionOccurrences(); cl::ParseCommandLineOptions(v.size(), v.data()); errorHandler().errorLimit = args::getInteger(args, OPT_error_limit, 20); diff --git a/llvm/include/llvm/Support/CrashRecoveryContext.h b/llvm/include/llvm/Support/CrashRecoveryContext.h --- a/llvm/include/llvm/Support/CrashRecoveryContext.h +++ b/llvm/include/llvm/Support/CrashRecoveryContext.h @@ -44,11 +44,11 @@ /// executed in any case, whether crash occurs or not. These actions may be used /// to reclaim resources in the case of crash. class CrashRecoveryContext { - void *Impl; - CrashRecoveryContextCleanup *head; + void *Impl = nullptr; + CrashRecoveryContextCleanup *head = nullptr; public: - CrashRecoveryContext() : Impl(nullptr), head(nullptr) {} + CrashRecoveryContext(); ~CrashRecoveryContext(); /// Register cleanup handler, which is used when the recovery context is diff --git a/llvm/include/llvm/Support/Signals.h b/llvm/include/llvm/Support/Signals.h --- a/llvm/include/llvm/Support/Signals.h +++ b/llvm/include/llvm/Support/Signals.h @@ -115,6 +115,8 @@ /// Context is a system-specific failure context: it is the signal type on /// Unix; the ExceptionContext on Windows. void CleanupOnSignal(uintptr_t Context); + + void UnregisterHandlers(); } // End sys namespace } // End llvm namespace diff --git a/llvm/lib/Support/CrashRecoveryContext.cpp b/llvm/lib/Support/CrashRecoveryContext.cpp --- a/llvm/lib/Support/CrashRecoveryContext.cpp +++ b/llvm/lib/Support/CrashRecoveryContext.cpp @@ -97,6 +97,13 @@ CrashRecoveryContextCleanup::~CrashRecoveryContextCleanup() {} +CrashRecoveryContext::CrashRecoveryContext() { + // On Windows, if abort() was previously triggered (and caught by a previous + // CrashRecoveryContext) the Windows CRT removes our installed signal handler, + // so we need to install it again. + sys::DisableSystemDialogsOnCrash(); +} + CrashRecoveryContext::~CrashRecoveryContext() { // Reclaim registered resources. CrashRecoveryContextCleanup *i = head; @@ -370,9 +377,10 @@ sigaddset(&SigMask, Signal); sigprocmask(SIG_UNBLOCK, &SigMask, nullptr); - // As per convention, -2 indicates a crash or timeout as opposed to failure to - // execute (see llvm/include/llvm/Support/Program.h) - int RetCode = -2; + // Return the same error code as if the program crashed, as mentioned in the + // section "Exit Status for Commands": + // https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html + int RetCode = 128 + Signal; // Don't consider a broken pipe as a crash (see clang/lib/Driver/Driver.cpp) if (Signal == SIGPIPE) diff --git a/llvm/lib/Support/Unix/Signals.inc b/llvm/lib/Support/Unix/Signals.inc --- a/llvm/lib/Support/Unix/Signals.inc +++ b/llvm/lib/Support/Unix/Signals.inc @@ -331,7 +331,7 @@ registerHandler(S, SignalKind::IsInfo); } -static void UnregisterHandlers() { +void sys::UnregisterHandlers() { // Restore all of the signal handlers to how they were before we showed up. for (unsigned i = 0, e = NumRegisteredSignals.load(); i != e; ++i) { sigaction(RegisteredSignalInfo[i].SigNo, @@ -367,7 +367,7 @@ // crashes when we return and the signal reissues. This also ensures that if // we crash in our signal handler that the program will terminate immediately // instead of recursing in the signal handler. - UnregisterHandlers(); + sys::UnregisterHandlers(); // Unmask all potentially blocked kill signals. sigset_t SigMask; diff --git a/llvm/lib/Support/Windows/Signals.inc b/llvm/lib/Support/Windows/Signals.inc --- a/llvm/lib/Support/Windows/Signals.inc +++ b/llvm/lib/Support/Windows/Signals.inc @@ -868,3 +868,5 @@ #pragma GCC diagnostic warning "-Wformat" #pragma GCC diagnostic warning "-Wformat-extra-args" #endif + +void sys::UnregisterHandlers() {}