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 | |