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/COFF.h" |
16 | #include "llvm/Object/COFFImportFile.h" |
17 | #include "llvm/Object/COFFModuleDefinition.h" |
18 | #include "llvm/Option/Arg.h" |
19 | #include "llvm/Option/ArgList.h" |
20 | #include "llvm/Option/OptTable.h" |
21 | #include "llvm/Option/Option.h" |
22 | #include "llvm/Support/Path.h" |
23 | #include "llvm/TargetParser/Host.h" |
24 | |
25 | #include <optional> |
26 | #include <vector> |
27 | |
28 | using namespace llvm; |
29 | using namespace llvm::object; |
30 | using namespace llvm::COFF; |
31 | |
32 | namespace { |
33 | |
34 | enum { |
35 | OPT_INVALID = 0, |
36 | #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), |
37 | #include "Options.inc" |
38 | #undef OPTION |
39 | }; |
40 | |
41 | #define PREFIX(NAME, VALUE) \ |
42 | static constexpr StringLiteral NAME##_init[] = VALUE; \ |
43 | static constexpr ArrayRef<StringLiteral> NAME(NAME##_init, \ |
44 | std::size(NAME##_init) - 1); |
45 | #include "Options.inc" |
46 | #undef PREFIX |
47 | |
48 | using namespace llvm::opt; |
49 | static constexpr opt::OptTable::Info InfoTable[] = { |
50 | #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), |
51 | #include "Options.inc" |
52 | #undef OPTION |
53 | }; |
54 | |
55 | class DllOptTable : public opt::GenericOptTable { |
56 | public: |
57 | DllOptTable() : opt::GenericOptTable(InfoTable, false) {} |
58 | }; |
59 | |
60 | // Opens a file. Path has to be resolved already. |
61 | std::unique_ptr<MemoryBuffer> openFile(const Twine &Path) { |
62 | ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> MB = MemoryBuffer::getFile(Filename: Path); |
63 | |
64 | if (std::error_code EC = MB.getError()) { |
65 | llvm::errs() << "cannot open file " << Path << ": " << EC.message() << "\n" ; |
66 | return nullptr; |
67 | } |
68 | |
69 | return std::move(*MB); |
70 | } |
71 | |
72 | MachineTypes getEmulation(StringRef S) { |
73 | return StringSwitch<MachineTypes>(S) |
74 | .Case(S: "i386" , Value: IMAGE_FILE_MACHINE_I386) |
75 | .Case(S: "i386:x86-64" , Value: IMAGE_FILE_MACHINE_AMD64) |
76 | .Case(S: "arm" , Value: IMAGE_FILE_MACHINE_ARMNT) |
77 | .Case(S: "arm64" , Value: IMAGE_FILE_MACHINE_ARM64) |
78 | .Case(S: "arm64ec" , Value: IMAGE_FILE_MACHINE_ARM64EC) |
79 | .Default(Value: IMAGE_FILE_MACHINE_UNKNOWN); |
80 | } |
81 | |
82 | MachineTypes getMachine(Triple T) { |
83 | switch (T.getArch()) { |
84 | case Triple::x86: |
85 | return COFF::IMAGE_FILE_MACHINE_I386; |
86 | case Triple::x86_64: |
87 | return COFF::IMAGE_FILE_MACHINE_AMD64; |
88 | case Triple::arm: |
89 | return COFF::IMAGE_FILE_MACHINE_ARMNT; |
90 | case Triple::aarch64: |
91 | return T.isWindowsArm64EC() ? COFF::IMAGE_FILE_MACHINE_ARM64EC |
92 | : COFF::IMAGE_FILE_MACHINE_ARM64; |
93 | default: |
94 | return COFF::IMAGE_FILE_MACHINE_UNKNOWN; |
95 | } |
96 | } |
97 | |
98 | MachineTypes getDefaultMachine() { |
99 | return getMachine(T: Triple(sys::getDefaultTargetTriple())); |
100 | } |
101 | |
102 | std::optional<std::string> getPrefix(StringRef Argv0) { |
103 | StringRef ProgName = llvm::sys::path::stem(path: Argv0); |
104 | // x86_64-w64-mingw32-dlltool -> x86_64-w64-mingw32 |
105 | // llvm-dlltool -> None |
106 | // aarch64-w64-mingw32-llvm-dlltool-10.exe -> aarch64-w64-mingw32 |
107 | ProgName = ProgName.rtrim(Chars: "0123456789.-" ); |
108 | if (!ProgName.consume_back_insensitive(Suffix: "dlltool" )) |
109 | return std::nullopt; |
110 | ProgName.consume_back_insensitive(Suffix: "llvm-" ); |
111 | ProgName.consume_back_insensitive(Suffix: "-" ); |
112 | return ProgName.str(); |
113 | } |
114 | |
115 | bool parseModuleDefinition(StringRef DefFileName, MachineTypes Machine, |
116 | bool AddUnderscores, |
117 | std::vector<COFFShortExport> &Exports, |
118 | std::string &OutputFile) { |
119 | std::unique_ptr<MemoryBuffer> MB = openFile(Path: DefFileName); |
120 | if (!MB) |
121 | return false; |
122 | |
123 | if (!MB->getBufferSize()) { |
124 | llvm::errs() << "definition file empty\n" ; |
125 | return false; |
126 | } |
127 | |
128 | Expected<COFFModuleDefinition> Def = parseCOFFModuleDefinition( |
129 | MB: *MB, Machine, /*MingwDef=*/true, AddUnderscores); |
130 | if (!Def) { |
131 | llvm::errs() << "error parsing definition\n" |
132 | << errorToErrorCode(Err: Def.takeError()).message() << "\n" ; |
133 | return false; |
134 | } |
135 | |
136 | if (OutputFile.empty()) |
137 | OutputFile = std::move(Def->OutputFile); |
138 | |
139 | // If ExtName is set (if the "ExtName = Name" syntax was used), overwrite |
140 | // Name with ExtName and clear ExtName. When only creating an import |
141 | // library and not linking, the internal name is irrelevant. This avoids |
142 | // cases where writeImportLibrary tries to transplant decoration from |
143 | // symbol decoration onto ExtName. |
144 | for (COFFShortExport &E : Def->Exports) { |
145 | if (!E.ExtName.empty()) { |
146 | E.Name = E.ExtName; |
147 | E.ExtName.clear(); |
148 | } |
149 | } |
150 | |
151 | Exports = std::move(Def->Exports); |
152 | return true; |
153 | } |
154 | |
155 | } // namespace |
156 | |
157 | int llvm::dlltoolDriverMain(llvm::ArrayRef<const char *> ArgsArr) { |
158 | DllOptTable Table; |
159 | unsigned MissingIndex; |
160 | unsigned MissingCount; |
161 | llvm::opt::InputArgList Args = |
162 | Table.ParseArgs(Args: ArgsArr.slice(N: 1), MissingArgIndex&: MissingIndex, MissingArgCount&: MissingCount); |
163 | if (MissingCount) { |
164 | llvm::errs() << Args.getArgString(Index: MissingIndex) << ": missing argument\n" ; |
165 | return 1; |
166 | } |
167 | |
168 | // Handle when no input or output is specified |
169 | if (Args.hasArgNoClaim(Ids: OPT_INPUT) || |
170 | (!Args.hasArgNoClaim(Ids: OPT_d) && !Args.hasArgNoClaim(Ids: OPT_l))) { |
171 | Table.printHelp(OS&: outs(), Usage: "llvm-dlltool [options] file..." , Title: "llvm-dlltool" , |
172 | ShowHidden: false); |
173 | llvm::outs() << "\nTARGETS: i386, i386:x86-64, arm, arm64, arm64ec\n" ; |
174 | return 1; |
175 | } |
176 | |
177 | for (auto *Arg : Args.filtered(Ids: OPT_UNKNOWN)) |
178 | llvm::errs() << "ignoring unknown argument: " << Arg->getAsString(Args) |
179 | << "\n" ; |
180 | |
181 | if (!Args.hasArg(Ids: OPT_d)) { |
182 | llvm::errs() << "no definition file specified\n" ; |
183 | return 1; |
184 | } |
185 | |
186 | COFF::MachineTypes Machine = getDefaultMachine(); |
187 | if (std::optional<std::string> Prefix = getPrefix(Argv0: ArgsArr[0])) { |
188 | Triple T(*Prefix); |
189 | if (T.getArch() != Triple::UnknownArch) |
190 | Machine = getMachine(T); |
191 | } |
192 | if (auto *Arg = Args.getLastArg(Ids: OPT_m)) |
193 | Machine = getEmulation(S: Arg->getValue()); |
194 | |
195 | if (Machine == IMAGE_FILE_MACHINE_UNKNOWN) { |
196 | llvm::errs() << "unknown target\n" ; |
197 | return 1; |
198 | } |
199 | |
200 | bool AddUnderscores = !Args.hasArg(Ids: OPT_no_leading_underscore); |
201 | |
202 | std::string OutputFile; |
203 | if (auto *Arg = Args.getLastArg(Ids: OPT_D)) |
204 | OutputFile = Arg->getValue(); |
205 | |
206 | std::vector<COFFShortExport> Exports, NativeExports; |
207 | |
208 | if (Args.hasArg(Ids: OPT_N)) { |
209 | if (!isArm64EC(Machine)) { |
210 | llvm::errs() << "native .def file is supported only on arm64ec target\n" ; |
211 | return 1; |
212 | } |
213 | if (!parseModuleDefinition(DefFileName: Args.getLastArg(Ids: OPT_N)->getValue(), |
214 | Machine: IMAGE_FILE_MACHINE_ARM64, AddUnderscores, |
215 | Exports&: NativeExports, OutputFile)) |
216 | return 1; |
217 | } |
218 | |
219 | if (!parseModuleDefinition(DefFileName: Args.getLastArg(Ids: OPT_d)->getValue(), Machine, |
220 | AddUnderscores, Exports, OutputFile)) |
221 | return 1; |
222 | |
223 | if (OutputFile.empty()) { |
224 | llvm::errs() << "no DLL name specified\n" ; |
225 | return 1; |
226 | } |
227 | |
228 | if (Machine == IMAGE_FILE_MACHINE_I386 && Args.hasArg(Ids: OPT_k)) { |
229 | for (COFFShortExport &E : Exports) { |
230 | if (!E.ImportName.empty() || (!E.Name.empty() && E.Name[0] == '?')) |
231 | continue; |
232 | E.SymbolName = E.Name; |
233 | // Trim off the trailing decoration. Symbols will always have a |
234 | // starting prefix here (either _ for cdecl/stdcall, @ for fastcall |
235 | // or ? for C++ functions). Vectorcall functions won't have any |
236 | // fixed prefix, but the function base name will still be at least |
237 | // one char. |
238 | E.Name = E.Name.substr(pos: 0, n: E.Name.find(c: '@', pos: 1)); |
239 | // By making sure E.SymbolName != E.Name for decorated symbols, |
240 | // writeImportLibrary writes these symbols with the type |
241 | // IMPORT_NAME_UNDECORATE. |
242 | } |
243 | } |
244 | |
245 | std::string Path = std::string(Args.getLastArgValue(Id: OPT_l)); |
246 | if (!Path.empty() && writeImportLibrary(ImportName: OutputFile, Path, Exports, Machine, |
247 | /*MinGW=*/true, NativeExports)) |
248 | return 1; |
249 | return 0; |
250 | } |
251 | |