1//===- yaml2obj - Convert YAML to a binary object file --------------------===//
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 program takes a YAML description of an object file and outputs the
10// binary equivalent.
11//
12// This is used for writing tests that require binary files.
13//
14//===----------------------------------------------------------------------===//
15
16#include "llvm/ObjectYAML/yaml2obj.h"
17#include "llvm/ADT/StringExtras.h"
18#include "llvm/ObjectYAML/ObjectYAML.h"
19#include "llvm/Option/ArgList.h"
20#include "llvm/Option/OptTable.h"
21#include "llvm/Option/Option.h"
22#include "llvm/Support/CommandLine.h"
23#include "llvm/Support/FileSystem.h"
24#include "llvm/Support/InitLLVM.h"
25#include "llvm/Support/MemoryBuffer.h"
26#include "llvm/Support/StringSaver.h"
27#include "llvm/Support/ToolOutputFile.h"
28#include "llvm/Support/WithColor.h"
29#include "llvm/Support/YAMLTraits.h"
30#include "llvm/Support/raw_ostream.h"
31#include <optional>
32#include <system_error>
33
34using namespace llvm;
35
36namespace {
37enum ID {
38 OPT_INVALID = 0, // This is not an option ID.
39#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
40#include "Opts.inc"
41#undef OPTION
42};
43
44using namespace llvm::opt;
45#define OPTTABLE_CODE
46#include "Opts.inc"
47
48class Yaml2ObjOptTable : public opt::OptTable {
49public:
50 Yaml2ObjOptTable() : OptTable(optionTables()) { setDashDashParsing(true); }
51};
52} // namespace
53
54static std::optional<std::string> preprocess(StringRef Buf,
55 ArrayRef<std::string> D,
56 yaml::ErrorHandler ErrHandler) {
57 DenseMap<StringRef, StringRef> Defines;
58 for (StringRef Define : D) {
59 StringRef Macro, Definition;
60 std::tie(args&: Macro, args&: Definition) = Define.split(Separator: '=');
61 if (!Define.count(C: '=') || Macro.empty()) {
62 ErrHandler("invalid syntax for -D: " + Define);
63 return {};
64 }
65 if (!Defines.try_emplace(Key: Macro, Args&: Definition).second) {
66 ErrHandler("'" + Macro + "'" + " redefined");
67 return {};
68 }
69 }
70
71 std::string Preprocessed;
72 while (!Buf.empty()) {
73 if (Buf.starts_with(Prefix: "[[")) {
74 size_t I = Buf.find_first_of(Chars: "[]", From: 2);
75 if (Buf.substr(Start: I).starts_with(Prefix: "]]")) {
76 StringRef MacroExpr = Buf.substr(Start: 2, N: I - 2);
77 StringRef Macro;
78 StringRef Default;
79 std::tie(args&: Macro, args&: Default) = MacroExpr.split(Separator: '=');
80
81 // When the -D option is requested, we use the provided value.
82 // Otherwise we use a default macro value if present.
83 auto It = Defines.find(Val: Macro);
84 std::optional<StringRef> Value;
85 if (It != Defines.end())
86 Value = It->second;
87 else if (!Default.empty() || MacroExpr.ends_with(Suffix: "="))
88 Value = Default;
89
90 if (Value) {
91 Preprocessed += *Value;
92 Buf = Buf.substr(Start: I + 2);
93 continue;
94 }
95 }
96 }
97
98 Preprocessed += Buf[0];
99 Buf = Buf.substr(Start: 1);
100 }
101
102 return Preprocessed;
103}
104
105template <class T>
106static void parseIntArg(const opt::InputArgList &Args, int ID, T &Value,
107 yaml::ErrorHandler ErrHandler) {
108 if (const opt::Arg *A = Args.getLastArg(Ids: ID)) {
109 StringRef V(A->getValue());
110 if (!to_integer(V, Value, 0)) {
111 ErrHandler("expected an integer, but got '" + V + "'");
112 exit(status: 1);
113 }
114 }
115}
116
117int main(int argc, char **argv) {
118 InitLLVM X(argc, argv);
119 constexpr StringRef ProgName = "yaml2obj";
120 auto ErrHandler = [&](const Twine &Msg) {
121 WithColor::error(OS&: errs(), Prefix: ProgName) << Msg << "\n";
122 };
123
124 BumpPtrAllocator A;
125 StringSaver Saver(A);
126 Yaml2ObjOptTable Tbl;
127 opt::InputArgList Args =
128 Tbl.parseArgs(Argc: argc, Argv: argv, Unknown: OPT_UNKNOWN, Saver, ErrorFn: [&](StringRef Msg) {
129 ErrHandler(Msg);
130 exit(status: 1);
131 });
132 if (Args.hasArg(Ids: OPT_help)) {
133 Tbl.printHelp(OS&: outs(), Usage: "yaml2obj [options] <input file>",
134 Title: "Create an object file from a YAML description");
135 return 0;
136 }
137 if (Args.hasArg(Ids: OPT_version)) {
138 cl::PrintVersionMessage();
139 return 0;
140 }
141
142 std::vector<std::string> Inputs = Args.getAllArgValues(Id: OPT_INPUT);
143 if (Inputs.size() > 1) {
144 ErrHandler("too many input files");
145 return 1;
146 }
147 StringRef Input = Inputs.empty() ? StringRef("-") : StringRef(Inputs[0]);
148 StringRef OutputFilename = Args.getLastArgValue(Id: OPT_o, Default: "-");
149 unsigned DocNum = 1;
150 parseIntArg(Args, ID: OPT_docnum_EQ, Value&: DocNum, ErrHandler);
151 uint64_t MaxSize = 10 * 1024 * 1024;
152 parseIntArg(Args, ID: OPT_max_size_EQ, Value&: MaxSize, ErrHandler);
153
154 std::error_code EC;
155 std::unique_ptr<ToolOutputFile> Out(
156 new ToolOutputFile(OutputFilename, EC, sys::fs::OF_None));
157 if (EC) {
158 ErrHandler("failed to open '" + OutputFilename + "': " + EC.message());
159 return 1;
160 }
161
162 ErrorOr<std::unique_ptr<MemoryBuffer>> Buf =
163 MemoryBuffer::getFileOrSTDIN(Filename: Input, /*IsText=*/true);
164 if (std::error_code EC = Buf.getError()) {
165 WithColor::error(OS&: errs(), Prefix: ProgName) << Input << ": " << EC.message() << '\n';
166 return 1;
167 }
168
169 std::optional<std::string> Buffer = preprocess(
170 Buf: Buf.get()->getBuffer(), D: Args.getAllArgValues(Id: OPT_D), ErrHandler);
171 if (!Buffer)
172 return 1;
173
174 if (Args.hasArg(Ids: OPT_E)) {
175 Out->os() << Buffer;
176 } else {
177 yaml::Input YIn(*Buffer);
178
179 if (!convertYAML(YIn, Out&: Out->os(), ErrHandler, DocNum,
180 MaxSize: MaxSize == 0 ? UINT64_MAX : MaxSize))
181 return 1;
182 }
183
184 Out->keep();
185 Out->os().flush();
186 return 0;
187}
188