| 1 | //===- DlltoolDriver.cpp - dlltool.exe-compatible driver ------------------===// |
| 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 | // Defines an interface to a dlltool.exe-compatible driver. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "llvm/ToolDrivers/llvm-dlltool/DlltoolDriver.h" |
| 14 | #include "llvm/ADT/StringSwitch.h" |
| 15 | #include "llvm/Object/Archive.h" |
| 16 | #include "llvm/Object/COFF.h" |
| 17 | #include "llvm/Object/COFFImportFile.h" |
| 18 | #include "llvm/Object/COFFModuleDefinition.h" |
| 19 | #include "llvm/Option/Arg.h" |
| 20 | #include "llvm/Option/ArgList.h" |
| 21 | #include "llvm/Option/OptTable.h" |
| 22 | #include "llvm/Option/Option.h" |
| 23 | #include "llvm/Support/Path.h" |
| 24 | #include "llvm/TargetParser/Host.h" |
| 25 | |
| 26 | #include <optional> |
| 27 | #include <vector> |
| 28 | |
| 29 | using namespace llvm; |
| 30 | using namespace llvm::object; |
| 31 | using namespace llvm::COFF; |
| 32 | |
| 33 | namespace { |
| 34 | |
| 35 | #define OPTTABLE_STR_TABLE_CODE |
| 36 | #include "Options.inc" |
| 37 | #undef OPTTABLE_STR_TABLE_CODE |
| 38 | |
| 39 | enum { |
| 40 | OPT_INVALID = 0, |
| 41 | #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), |
| 42 | #include "Options.inc" |
| 43 | #undef OPTION |
| 44 | }; |
| 45 | |
| 46 | #define OPTTABLE_PREFIXES_TABLE_CODE |
| 47 | #include "Options.inc" |
| 48 | #undef OPTTABLE_PREFIXES_TABLE_CODE |
| 49 | |
| 50 | using namespace llvm::opt; |
| 51 | static constexpr opt::OptTable::Info InfoTable[] = { |
| 52 | #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), |
| 53 | #include "Options.inc" |
| 54 | #undef OPTION |
| 55 | }; |
| 56 | |
| 57 | class DllOptTable : public opt::GenericOptTable { |
| 58 | public: |
| 59 | DllOptTable() |
| 60 | : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable, |
| 61 | false) {} |
| 62 | }; |
| 63 | |
| 64 | // Opens a file. Path has to be resolved already. |
| 65 | std::unique_ptr<MemoryBuffer> openFile(const Twine &Path) { |
| 66 | ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> MB = MemoryBuffer::getFile(Filename: Path); |
| 67 | |
| 68 | if (std::error_code EC = MB.getError()) { |
| 69 | llvm::errs() << "cannot open file " << Path << ": " << EC.message() << "\n" ; |
| 70 | return nullptr; |
| 71 | } |
| 72 | |
| 73 | return std::move(*MB); |
| 74 | } |
| 75 | |
| 76 | MachineTypes getEmulation(StringRef S) { |
| 77 | return StringSwitch<MachineTypes>(S) |
| 78 | .Case(S: "i386" , Value: IMAGE_FILE_MACHINE_I386) |
| 79 | .Case(S: "i386:x86-64" , Value: IMAGE_FILE_MACHINE_AMD64) |
| 80 | .Case(S: "arm" , Value: IMAGE_FILE_MACHINE_ARMNT) |
| 81 | .Case(S: "arm64" , Value: IMAGE_FILE_MACHINE_ARM64) |
| 82 | .Case(S: "arm64ec" , Value: IMAGE_FILE_MACHINE_ARM64EC) |
| 83 | .Case(S: "r4000" , Value: IMAGE_FILE_MACHINE_R4000) |
| 84 | .Default(Value: IMAGE_FILE_MACHINE_UNKNOWN); |
| 85 | } |
| 86 | |
| 87 | MachineTypes getMachine(Triple T) { |
| 88 | switch (T.getArch()) { |
| 89 | case Triple::x86: |
| 90 | return COFF::IMAGE_FILE_MACHINE_I386; |
| 91 | case Triple::x86_64: |
| 92 | return COFF::IMAGE_FILE_MACHINE_AMD64; |
| 93 | case Triple::arm: |
| 94 | return COFF::IMAGE_FILE_MACHINE_ARMNT; |
| 95 | case Triple::aarch64: |
| 96 | return T.isWindowsArm64EC() ? COFF::IMAGE_FILE_MACHINE_ARM64EC |
| 97 | : COFF::IMAGE_FILE_MACHINE_ARM64; |
| 98 | case Triple::mipsel: |
| 99 | return COFF::IMAGE_FILE_MACHINE_R4000; |
| 100 | default: |
| 101 | return COFF::IMAGE_FILE_MACHINE_UNKNOWN; |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | MachineTypes getDefaultMachine() { |
| 106 | return getMachine(T: Triple(sys::getDefaultTargetTriple())); |
| 107 | } |
| 108 | |
| 109 | std::optional<std::string> getPrefix(StringRef Argv0) { |
| 110 | StringRef ProgName = llvm::sys::path::stem(path: Argv0); |
| 111 | // x86_64-w64-mingw32-dlltool -> x86_64-w64-mingw32 |
| 112 | // llvm-dlltool -> None |
| 113 | // aarch64-w64-mingw32-llvm-dlltool-10.exe -> aarch64-w64-mingw32 |
| 114 | ProgName = ProgName.rtrim(Chars: "0123456789.-" ); |
| 115 | if (!ProgName.consume_back_insensitive(Suffix: "dlltool" )) |
| 116 | return std::nullopt; |
| 117 | ProgName.consume_back_insensitive(Suffix: "llvm-" ); |
| 118 | ProgName.consume_back_insensitive(Suffix: "-" ); |
| 119 | return ProgName.str(); |
| 120 | } |
| 121 | |
| 122 | bool parseModuleDefinition(StringRef DefFileName, MachineTypes Machine, |
| 123 | bool AddUnderscores, |
| 124 | std::vector<COFFShortExport> &Exports, |
| 125 | std::string &OutputFile) { |
| 126 | std::unique_ptr<MemoryBuffer> MB = openFile(Path: DefFileName); |
| 127 | if (!MB) |
| 128 | return false; |
| 129 | |
| 130 | if (!MB->getBufferSize()) { |
| 131 | llvm::errs() << "definition file empty\n" ; |
| 132 | return false; |
| 133 | } |
| 134 | |
| 135 | Expected<COFFModuleDefinition> Def = parseCOFFModuleDefinition( |
| 136 | MB: *MB, Machine, /*MingwDef=*/true, AddUnderscores); |
| 137 | if (!Def) { |
| 138 | llvm::errs() << "error parsing definition\n" |
| 139 | << errorToErrorCode(Err: Def.takeError()).message() << "\n" ; |
| 140 | return false; |
| 141 | } |
| 142 | |
| 143 | if (OutputFile.empty()) |
| 144 | OutputFile = std::move(Def->OutputFile); |
| 145 | |
| 146 | // If ExtName is set (if the "ExtName = Name" syntax was used), overwrite |
| 147 | // Name with ExtName and clear ExtName. When only creating an import |
| 148 | // library and not linking, the internal name is irrelevant. This avoids |
| 149 | // cases where writeImportLibrary tries to transplant decoration from |
| 150 | // symbol decoration onto ExtName. |
| 151 | for (COFFShortExport &E : Def->Exports) { |
| 152 | if (!E.ExtName.empty()) { |
| 153 | E.Name = E.ExtName; |
| 154 | E.ExtName.clear(); |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | Exports = std::move(Def->Exports); |
| 159 | return true; |
| 160 | } |
| 161 | |
| 162 | int printError(llvm::Error E, Twine File) { |
| 163 | if (!E) |
| 164 | return 0; |
| 165 | handleAllErrors(E: std::move(E), Handlers: [&](const llvm::ErrorInfoBase &EIB) { |
| 166 | llvm::errs() << "error opening " << File << ": " << EIB.message() << "\n" ; |
| 167 | }); |
| 168 | return 1; |
| 169 | } |
| 170 | |
| 171 | template <typename Callable> |
| 172 | int forEachCoff(object::Archive &Archive, StringRef Name, Callable Callback) { |
| 173 | Error Err = Error::success(); |
| 174 | for (auto &C : Archive.children(Err)) { |
| 175 | Expected<StringRef> NameOrErr = C.getName(); |
| 176 | if (!NameOrErr) |
| 177 | return printError(E: NameOrErr.takeError(), File: Name); |
| 178 | StringRef Name = *NameOrErr; |
| 179 | |
| 180 | Expected<MemoryBufferRef> ChildMB = C.getMemoryBufferRef(); |
| 181 | if (!ChildMB) |
| 182 | return printError(E: ChildMB.takeError(), File: Name); |
| 183 | |
| 184 | if (identify_magic(magic: ChildMB->getBuffer()) == file_magic::coff_object) { |
| 185 | auto Obj = object::COFFObjectFile::create(Object: *ChildMB); |
| 186 | if (!Obj) |
| 187 | return printError(E: Obj.takeError(), File: Name); |
| 188 | if (!Callback(*Obj->get(), Name)) |
| 189 | return 1; |
| 190 | } |
| 191 | } |
| 192 | if (Err) |
| 193 | return printError(E: std::move(Err), File: Name); |
| 194 | return 0; |
| 195 | } |
| 196 | |
| 197 | // To find the named of the imported DLL from an import library, we can either |
| 198 | // inspect the object files that form the import table entries, or we could |
| 199 | // just look at the archive member names, for MSVC style import libraries. |
| 200 | // Looking at the archive member names doesn't work for GNU style import |
| 201 | // libraries though, while inspecting the import table entries works for |
| 202 | // both. (MSVC style import libraries contain a couple regular object files |
| 203 | // for the header/trailers.) |
| 204 | // |
| 205 | // This implementation does the same as GNU dlltool does; look at the |
| 206 | // content of ".idata$7" sections, or for MSVC style libraries, look |
| 207 | // at ".idata$6" sections. |
| 208 | // |
| 209 | // For GNU style import libraries, there are also other data chunks in sections |
| 210 | // named ".idata$7" (entries to the IAT or ILT); these are distinguished |
| 211 | // by seeing that they contain relocations. (They also look like an empty |
| 212 | // string when looking for null termination.) |
| 213 | // |
| 214 | // Alternatively, we could do things differently - look for any .idata$2 |
| 215 | // section; this would be import directory entries. At offset 0xc in them |
| 216 | // there is the RVA of the import DLL name; look for a relocation at this |
| 217 | // spot and locate the symbol that it points at. That symbol may either |
| 218 | // be within the same object file (in the case of MSVC style import libraries) |
| 219 | // or another object file (in the case of GNU import libraries). |
| 220 | bool identifyImportName(const COFFObjectFile &Obj, StringRef ObjName, |
| 221 | std::vector<StringRef> &Names, bool IsMsStyleImplib) { |
| 222 | StringRef TargetName = IsMsStyleImplib ? ".idata$6" : ".idata$7" ; |
| 223 | for (const auto &S : Obj.sections()) { |
| 224 | Expected<StringRef> NameOrErr = S.getName(); |
| 225 | if (!NameOrErr) { |
| 226 | printError(E: NameOrErr.takeError(), File: ObjName); |
| 227 | return false; |
| 228 | } |
| 229 | StringRef Name = *NameOrErr; |
| 230 | if (Name != TargetName) |
| 231 | continue; |
| 232 | |
| 233 | // GNU import libraries contain .idata$7 section in the per function |
| 234 | // objects too, but they contain relocations. |
| 235 | if (!IsMsStyleImplib && !S.relocations().empty()) |
| 236 | continue; |
| 237 | |
| 238 | Expected<StringRef> ContentsOrErr = S.getContents(); |
| 239 | if (!ContentsOrErr) { |
| 240 | printError(E: ContentsOrErr.takeError(), File: ObjName); |
| 241 | return false; |
| 242 | } |
| 243 | StringRef Contents = *ContentsOrErr; |
| 244 | Contents = Contents.substr(Start: 0, N: Contents.find(C: '\0')); |
| 245 | if (Contents.empty()) |
| 246 | continue; |
| 247 | Names.push_back(x: Contents); |
| 248 | return true; |
| 249 | } |
| 250 | return true; |
| 251 | } |
| 252 | |
| 253 | int doIdentify(StringRef File, bool IdentifyStrict) { |
| 254 | ErrorOr<std::unique_ptr<MemoryBuffer>> MaybeBuf = MemoryBuffer::getFile( |
| 255 | Filename: File, /*IsText=*/false, /*RequiredNullTerminator=*/RequiresNullTerminator: false); |
| 256 | if (!MaybeBuf) |
| 257 | return printError(E: errorCodeToError(EC: MaybeBuf.getError()), File); |
| 258 | if (identify_magic(magic: MaybeBuf.get()->getBuffer()) != file_magic::archive) { |
| 259 | llvm::errs() << File << " is not a library\n" ; |
| 260 | return 1; |
| 261 | } |
| 262 | |
| 263 | std::unique_ptr<MemoryBuffer> B = std::move(MaybeBuf.get()); |
| 264 | Error Err = Error::success(); |
| 265 | object::Archive Archive(B->getMemBufferRef(), Err); |
| 266 | if (Err) |
| 267 | return printError(E: std::move(Err), File: B->getBufferIdentifier()); |
| 268 | |
| 269 | bool IsMsStyleImplib = false; |
| 270 | for (const auto &S : Archive.symbols()) { |
| 271 | if (S.getName() == "__NULL_IMPORT_DESCRIPTOR" ) { |
| 272 | IsMsStyleImplib = true; |
| 273 | break; |
| 274 | } |
| 275 | } |
| 276 | std::vector<StringRef> Names; |
| 277 | if (forEachCoff(Archive, Name: B->getBufferIdentifier(), |
| 278 | Callback: [&](const COFFObjectFile &Obj, StringRef ObjName) -> bool { |
| 279 | return identifyImportName(Obj, ObjName, Names, |
| 280 | IsMsStyleImplib); |
| 281 | })) |
| 282 | return 1; |
| 283 | |
| 284 | if (Names.empty()) { |
| 285 | llvm::errs() << "No DLL import name found in " << File << "\n" ; |
| 286 | return 1; |
| 287 | } |
| 288 | if (Names.size() > 1 && IdentifyStrict) { |
| 289 | llvm::errs() << File << "contains imports for two or more DLLs\n" ; |
| 290 | return 1; |
| 291 | } |
| 292 | |
| 293 | for (StringRef S : Names) |
| 294 | llvm::outs() << S << "\n" ; |
| 295 | |
| 296 | return 0; |
| 297 | } |
| 298 | |
| 299 | } // namespace |
| 300 | |
| 301 | int llvm::dlltoolDriverMain(llvm::ArrayRef<const char *> ArgsArr) { |
| 302 | DllOptTable Table; |
| 303 | unsigned MissingIndex; |
| 304 | unsigned MissingCount; |
| 305 | llvm::opt::InputArgList Args = |
| 306 | Table.ParseArgs(Args: ArgsArr.slice(N: 1), MissingArgIndex&: MissingIndex, MissingArgCount&: MissingCount); |
| 307 | if (MissingCount) { |
| 308 | llvm::errs() << Args.getArgString(Index: MissingIndex) << ": missing argument\n" ; |
| 309 | return 1; |
| 310 | } |
| 311 | |
| 312 | // Handle when no input or output is specified |
| 313 | if (Args.hasArgNoClaim(Ids: OPT_INPUT) || |
| 314 | (!Args.hasArgNoClaim(Ids: OPT_d) && !Args.hasArgNoClaim(Ids: OPT_l) && |
| 315 | !Args.hasArgNoClaim(Ids: OPT_I))) { |
| 316 | Table.printHelp(OS&: outs(), Usage: "llvm-dlltool [options] file..." , Title: "llvm-dlltool" , |
| 317 | ShowHidden: false); |
| 318 | llvm::outs() |
| 319 | << "\nTARGETS: i386, i386:x86-64, arm, arm64, arm64ec, r4000\n" ; |
| 320 | return 1; |
| 321 | } |
| 322 | |
| 323 | for (auto *Arg : Args.filtered(Ids: OPT_UNKNOWN)) |
| 324 | llvm::errs() << "ignoring unknown argument: " << Arg->getAsString(Args) |
| 325 | << "\n" ; |
| 326 | |
| 327 | if (Args.hasArg(Ids: OPT_I)) { |
| 328 | return doIdentify(File: Args.getLastArg(Ids: OPT_I)->getValue(), |
| 329 | IdentifyStrict: Args.hasArg(Ids: OPT_identify_strict)); |
| 330 | } |
| 331 | |
| 332 | if (!Args.hasArg(Ids: OPT_d)) { |
| 333 | llvm::errs() << "no definition file specified\n" ; |
| 334 | return 1; |
| 335 | } |
| 336 | |
| 337 | COFF::MachineTypes Machine = getDefaultMachine(); |
| 338 | if (std::optional<std::string> Prefix = getPrefix(Argv0: ArgsArr[0])) { |
| 339 | Triple T(*Prefix); |
| 340 | if (T.getArch() != Triple::UnknownArch) |
| 341 | Machine = getMachine(T); |
| 342 | } |
| 343 | if (auto *Arg = Args.getLastArg(Ids: OPT_m)) |
| 344 | Machine = getEmulation(S: Arg->getValue()); |
| 345 | |
| 346 | if (Machine == IMAGE_FILE_MACHINE_UNKNOWN) { |
| 347 | llvm::errs() << "unknown target\n" ; |
| 348 | return 1; |
| 349 | } |
| 350 | |
| 351 | bool AddUnderscores = !Args.hasArg(Ids: OPT_no_leading_underscore); |
| 352 | |
| 353 | std::string OutputFile; |
| 354 | if (auto *Arg = Args.getLastArg(Ids: OPT_D)) |
| 355 | OutputFile = Arg->getValue(); |
| 356 | |
| 357 | std::vector<COFFShortExport> Exports, NativeExports; |
| 358 | |
| 359 | if (Args.hasArg(Ids: OPT_N)) { |
| 360 | if (!isArm64EC(Machine)) { |
| 361 | llvm::errs() << "native .def file is supported only on arm64ec target\n" ; |
| 362 | return 1; |
| 363 | } |
| 364 | if (!parseModuleDefinition(DefFileName: Args.getLastArg(Ids: OPT_N)->getValue(), |
| 365 | Machine: IMAGE_FILE_MACHINE_ARM64, AddUnderscores, |
| 366 | Exports&: NativeExports, OutputFile)) |
| 367 | return 1; |
| 368 | } |
| 369 | |
| 370 | if (!parseModuleDefinition(DefFileName: Args.getLastArg(Ids: OPT_d)->getValue(), Machine, |
| 371 | AddUnderscores, Exports, OutputFile)) |
| 372 | return 1; |
| 373 | |
| 374 | if (OutputFile.empty()) { |
| 375 | llvm::errs() << "no DLL name specified\n" ; |
| 376 | return 1; |
| 377 | } |
| 378 | |
| 379 | if (Machine == IMAGE_FILE_MACHINE_I386 && Args.hasArg(Ids: OPT_k)) { |
| 380 | for (COFFShortExport &E : Exports) { |
| 381 | if (!E.ImportName.empty() || (!E.Name.empty() && E.Name[0] == '?')) |
| 382 | continue; |
| 383 | E.SymbolName = E.Name; |
| 384 | // Trim off the trailing decoration. Symbols will always have a |
| 385 | // starting prefix here (either _ for cdecl/stdcall, @ for fastcall |
| 386 | // or ? for C++ functions). Vectorcall functions won't have any |
| 387 | // fixed prefix, but the function base name will still be at least |
| 388 | // one char. |
| 389 | E.Name = E.Name.substr(pos: 0, n: E.Name.find(c: '@', pos: 1)); |
| 390 | // By making sure E.SymbolName != E.Name for decorated symbols, |
| 391 | // writeImportLibrary writes these symbols with the type |
| 392 | // IMPORT_NAME_UNDECORATE. |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | std::string Path = std::string(Args.getLastArgValue(Id: OPT_l)); |
| 397 | if (!Path.empty()) { |
| 398 | if (Error E = writeImportLibrary(ImportName: OutputFile, Path, Exports, Machine, |
| 399 | /*MinGW=*/true, NativeExports)) { |
| 400 | handleAllErrors(E: std::move(E), Handlers: [&](const ErrorInfoBase &EI) { |
| 401 | llvm::errs() << EI.message() << "\n" ; |
| 402 | }); |
| 403 | return 1; |
| 404 | } |
| 405 | } |
| 406 | return 0; |
| 407 | } |
| 408 | |