| 1 | //===- DriverUtils.cpp ----------------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file contains utility functions for the ctx.driver. Because there |
| 10 | // are so many small functions, we created this separate file to make |
| 11 | // Driver.cpp less cluttered. |
| 12 | // |
| 13 | //===----------------------------------------------------------------------===// |
| 14 | |
| 15 | #include "Config.h" |
| 16 | #include "Driver.h" |
| 17 | #include "lld/Common/CommonLinkerContext.h" |
| 18 | #include "lld/Common/Reproduce.h" |
| 19 | #include "llvm/Option/Option.h" |
| 20 | #include "llvm/Support/CommandLine.h" |
| 21 | #include "llvm/Support/FileSystem.h" |
| 22 | #include "llvm/Support/Path.h" |
| 23 | #include "llvm/Support/TimeProfiler.h" |
| 24 | #include "llvm/TargetParser/Host.h" |
| 25 | #include "llvm/TargetParser/Triple.h" |
| 26 | #include <optional> |
| 27 | |
| 28 | using namespace llvm; |
| 29 | using namespace llvm::sys; |
| 30 | using namespace llvm::opt; |
| 31 | using namespace lld; |
| 32 | using namespace lld::elf; |
| 33 | |
| 34 | // Create OptTable |
| 35 | |
| 36 | #define OPTTABLE_STR_TABLE_CODE |
| 37 | #include "Options.inc" |
| 38 | #undef OPTTABLE_STR_TABLE_CODE |
| 39 | |
| 40 | // Create prefix string literals used in Options.td |
| 41 | #define OPTTABLE_PREFIXES_TABLE_CODE |
| 42 | #include "Options.inc" |
| 43 | #undef OPTTABLE_PREFIXES_TABLE_CODE |
| 44 | |
| 45 | // Create table mapping all options defined in Options.td |
| 46 | static constexpr opt::OptTable::Info optInfo[] = { |
| 47 | #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), |
| 48 | #include "Options.inc" |
| 49 | #undef OPTION |
| 50 | }; |
| 51 | |
| 52 | ELFOptTable::ELFOptTable() |
| 53 | : GenericOptTable(OptionStrTable, OptionPrefixesTable, optInfo) {} |
| 54 | |
| 55 | // Set color diagnostics according to --color-diagnostics={auto,always,never} |
| 56 | // or --no-color-diagnostics flags. |
| 57 | static void handleColorDiagnostics(Ctx &ctx, opt::InputArgList &args) { |
| 58 | auto *arg = args.getLastArg(Ids: OPT_color_diagnostics); |
| 59 | if (!arg) |
| 60 | return; |
| 61 | StringRef s = arg->getValue(); |
| 62 | if (s == "always" ) |
| 63 | ctx.e.errs().enable_colors(enable: true); |
| 64 | else if (s == "never" ) |
| 65 | ctx.e.errs().enable_colors(enable: false); |
| 66 | else if (s != "auto" ) |
| 67 | ErrAlways(ctx) << "unknown option: --color-diagnostics=" << s; |
| 68 | } |
| 69 | |
| 70 | static cl::TokenizerCallback getQuotingStyle(Ctx &ctx, |
| 71 | opt::InputArgList &args) { |
| 72 | if (auto *arg = args.getLastArg(Ids: OPT_rsp_quoting)) { |
| 73 | StringRef s = arg->getValue(); |
| 74 | if (s != "windows" && s != "posix" ) |
| 75 | ErrAlways(ctx) << "invalid response file quoting: " << s; |
| 76 | if (s == "windows" ) |
| 77 | return cl::TokenizeWindowsCommandLine; |
| 78 | return cl::TokenizeGNUCommandLine; |
| 79 | } |
| 80 | if (Triple(sys::getProcessTriple()).isOSWindows()) |
| 81 | return cl::TokenizeWindowsCommandLine; |
| 82 | return cl::TokenizeGNUCommandLine; |
| 83 | } |
| 84 | |
| 85 | // Gold LTO plugin takes a `--plugin-opt foo=bar` option as an alias for |
| 86 | // `--plugin-opt=foo=bar`. We want to handle `--plugin-opt=foo=` as an |
| 87 | // option name and `bar` as a value. Unfortunately, OptParser cannot |
| 88 | // handle an option with a space in it. |
| 89 | // |
| 90 | // In this function, we concatenate command line arguments so that |
| 91 | // `--plugin-opt <foo>` is converted to `--plugin-opt=<foo>`. This is a |
| 92 | // bit hacky, but looks like it is still better than handling --plugin-opt |
| 93 | // options by hand. |
| 94 | static void concatLTOPluginOptions(Ctx &ctx, |
| 95 | SmallVectorImpl<const char *> &args) { |
| 96 | SmallVector<const char *, 256> v; |
| 97 | for (size_t i = 0, e = args.size(); i != e; ++i) { |
| 98 | StringRef s = args[i]; |
| 99 | if ((s == "-plugin-opt" || s == "--plugin-opt" ) && i + 1 != e) { |
| 100 | v.push_back(Elt: ctx.saver.save(S: s + "=" + args[i + 1]).data()); |
| 101 | ++i; |
| 102 | } else { |
| 103 | v.push_back(Elt: args[i]); |
| 104 | } |
| 105 | } |
| 106 | args = std::move(v); |
| 107 | } |
| 108 | |
| 109 | // Parses a given list of options. |
| 110 | opt::InputArgList ELFOptTable::parse(Ctx &ctx, ArrayRef<const char *> argv) { |
| 111 | // Make InputArgList from string vectors. |
| 112 | unsigned missingIndex; |
| 113 | unsigned missingCount; |
| 114 | SmallVector<const char *, 256> vec(argv.data(), argv.data() + argv.size()); |
| 115 | |
| 116 | // We need to get the quoting style for response files before parsing all |
| 117 | // options so we parse here before and ignore all the options but |
| 118 | // --rsp-quoting. |
| 119 | opt::InputArgList args = this->ParseArgs(Args: vec, MissingArgIndex&: missingIndex, MissingArgCount&: missingCount); |
| 120 | |
| 121 | // Expand response files (arguments in the form of @<filename>) |
| 122 | // and then parse the argument again. |
| 123 | cl::ExpandResponseFiles(Saver&: ctx.saver, Tokenizer: getQuotingStyle(ctx, args), Argv&: vec); |
| 124 | concatLTOPluginOptions(ctx, args&: vec); |
| 125 | args = this->ParseArgs(Args: vec, MissingArgIndex&: missingIndex, MissingArgCount&: missingCount); |
| 126 | |
| 127 | handleColorDiagnostics(ctx, args); |
| 128 | if (missingCount) |
| 129 | ErrAlways(ctx) << args.getArgString(Index: missingIndex) << ": missing argument" ; |
| 130 | |
| 131 | for (opt::Arg *arg : args.filtered(Ids: OPT_UNKNOWN)) { |
| 132 | std::string nearest; |
| 133 | if (findNearest(Option: arg->getAsString(Args: args), NearestString&: nearest) > 1) |
| 134 | ErrAlways(ctx) << "unknown argument '" << arg->getAsString(Args: args) << "'" ; |
| 135 | else |
| 136 | ErrAlways(ctx) << "unknown argument '" << arg->getAsString(Args: args) |
| 137 | << "', did you mean '" << nearest << "'" ; |
| 138 | } |
| 139 | return args; |
| 140 | } |
| 141 | |
| 142 | void elf::printHelp(Ctx &ctx) { |
| 143 | auto &outs = ctx.e.outs(); |
| 144 | ELFOptTable().printHelp( |
| 145 | OS&: outs, Usage: (ctx.arg.progName + " [options] file..." ).str().c_str(), Title: "lld" , |
| 146 | ShowHidden: false /*ShowHidden*/, ShowAllAliases: true /*ShowAllAliases*/); |
| 147 | outs << "\n" ; |
| 148 | |
| 149 | // Scripts generated by Libtool versions up to 2021-10 expect /: supported |
| 150 | // targets:.* elf/ in a message for the --help option. If it doesn't match, |
| 151 | // the scripts assume that the linker doesn't support very basic features |
| 152 | // such as shared libraries. Therefore, we need to print out at least "elf". |
| 153 | outs << ctx.arg.progName << ": supported targets: elf\n" ; |
| 154 | } |
| 155 | |
| 156 | static std::string rewritePath(StringRef s) { |
| 157 | if (fs::exists(Path: s)) |
| 158 | return relativeToRoot(path: s); |
| 159 | return std::string(s); |
| 160 | } |
| 161 | |
| 162 | // Reconstructs command line arguments so that so that you can re-run |
| 163 | // the same command with the same inputs. This is for --reproduce. |
| 164 | std::string elf::createResponseFile(const opt::InputArgList &args) { |
| 165 | SmallString<0> data; |
| 166 | raw_svector_ostream os(data); |
| 167 | os << "--chroot .\n" ; |
| 168 | |
| 169 | // Copy the command line to the output while rewriting paths. |
| 170 | for (auto *arg : args) { |
| 171 | switch (arg->getOption().getID()) { |
| 172 | case OPT_reproduce: |
| 173 | break; |
| 174 | case OPT_INPUT: |
| 175 | os << quote(s: rewritePath(s: arg->getValue())) << "\n" ; |
| 176 | break; |
| 177 | case OPT_o: |
| 178 | case OPT_Map: |
| 179 | case OPT_dependency_file: |
| 180 | case OPT_print_archive_stats: |
| 181 | case OPT_why_extract: |
| 182 | // If an output path contains directories, "lld @response.txt" will |
| 183 | // likely fail because the archive we are creating doesn't contain empty |
| 184 | // directories for the output path (-o doesn't create directories). |
| 185 | // Strip directories to prevent the issue. |
| 186 | os << arg->getSpelling(); |
| 187 | if (arg->getOption().getRenderStyle() == opt::Option::RenderSeparateStyle) |
| 188 | os << ' '; |
| 189 | os << quote(s: path::filename(path: arg->getValue())) << '\n'; |
| 190 | break; |
| 191 | case OPT_lto_sample_profile: |
| 192 | os << arg->getSpelling() << quote(s: rewritePath(s: arg->getValue())) << "\n" ; |
| 193 | break; |
| 194 | case OPT_call_graph_ordering_file: |
| 195 | case OPT_default_script: |
| 196 | case OPT_dynamic_list: |
| 197 | case OPT_export_dynamic_symbol_list: |
| 198 | case OPT_just_symbols: |
| 199 | case OPT_library_path: |
| 200 | case OPT_remap_inputs_file: |
| 201 | case OPT_retain_symbols_file: |
| 202 | case OPT_rpath: |
| 203 | case OPT_script: |
| 204 | case OPT_symbol_ordering_file: |
| 205 | case OPT_sysroot: |
| 206 | case OPT_version_script: |
| 207 | os << arg->getSpelling() << " " << quote(s: rewritePath(s: arg->getValue())) |
| 208 | << "\n" ; |
| 209 | break; |
| 210 | default: |
| 211 | os << toString(arg: *arg) << "\n" ; |
| 212 | } |
| 213 | } |
| 214 | return std::string(data); |
| 215 | } |
| 216 | |
| 217 | // Find a file by concatenating given paths. If a resulting path |
| 218 | // starts with "=", the character is replaced with a --sysroot value. |
| 219 | static std::optional<std::string> findFile(Ctx &ctx, StringRef path1, |
| 220 | const Twine &path2) { |
| 221 | SmallString<128> s; |
| 222 | if (path1.starts_with(Prefix: "=" )) |
| 223 | path::append(path&: s, a: ctx.arg.sysroot, b: path1.substr(Start: 1), c: path2); |
| 224 | else |
| 225 | path::append(path&: s, a: path1, b: path2); |
| 226 | |
| 227 | if (fs::exists(Path: s)) |
| 228 | return std::string(s); |
| 229 | return std::nullopt; |
| 230 | } |
| 231 | |
| 232 | std::optional<std::string> elf::findFromSearchPaths(Ctx &ctx, StringRef path) { |
| 233 | for (StringRef dir : ctx.arg.searchPaths) |
| 234 | if (std::optional<std::string> s = findFile(ctx, path1: dir, path2: path)) |
| 235 | return s; |
| 236 | return std::nullopt; |
| 237 | } |
| 238 | |
| 239 | // This is for -l<basename>. We'll look for lib<basename>.so or lib<basename>.a from |
| 240 | // search paths. |
| 241 | std::optional<std::string> elf::searchLibraryBaseName(Ctx &ctx, |
| 242 | StringRef name) { |
| 243 | for (StringRef dir : ctx.arg.searchPaths) { |
| 244 | if (!ctx.arg.isStatic) |
| 245 | if (std::optional<std::string> s = |
| 246 | findFile(ctx, path1: dir, path2: "lib" + name + ".so" )) |
| 247 | return s; |
| 248 | if (std::optional<std::string> s = findFile(ctx, path1: dir, path2: "lib" + name + ".a" )) |
| 249 | return s; |
| 250 | } |
| 251 | return std::nullopt; |
| 252 | } |
| 253 | |
| 254 | // This is for -l<namespec>. |
| 255 | std::optional<std::string> elf::searchLibrary(Ctx &ctx, StringRef name) { |
| 256 | llvm::TimeTraceScope timeScope("Locate library" , name); |
| 257 | if (name.starts_with(Prefix: ":" )) |
| 258 | return findFromSearchPaths(ctx, path: name.substr(Start: 1)); |
| 259 | return searchLibraryBaseName(ctx, name); |
| 260 | } |
| 261 | |
| 262 | // If a linker/version script doesn't exist in the current directory, we also |
| 263 | // look for the script in the '-L' search paths. This matches the behaviour of |
| 264 | // '-T', --version-script=, and linker script INPUT() command in ld.bfd. |
| 265 | std::optional<std::string> elf::searchScript(Ctx &ctx, StringRef name) { |
| 266 | if (fs::exists(Path: name)) |
| 267 | return name.str(); |
| 268 | return findFromSearchPaths(ctx, path: name); |
| 269 | } |
| 270 | |