1 // Copyright (C) 2016 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "repr/ir_representation.h"
16 #include "repr/ir_dumper.h"
17 #include "repr/ir_reader.h"
18 #include "repr/symbol/so_file_parser.h"
19 #include "repr/symbol/version_script_parser.h"
20 #include "utils/header_abi_util.h"
21
22 #include <llvm/Support/CommandLine.h>
23 #include <llvm/Support/raw_ostream.h>
24
25 #include <fstream>
26 #include <functional>
27 #include <iostream>
28 #include <memory>
29 #include <mutex>
30 #include <string>
31 #include <thread>
32 #include <vector>
33
34 #include <stdlib.h>
35
36
37 using namespace header_checker;
38 using header_checker::repr::TextFormatIR;
39 using header_checker::utils::CollectAllExportedHeaders;
40
41
42 static constexpr std::size_t kSourcesPerBatchThread = 7;
43
44 static llvm::cl::OptionCategory header_linker_category(
45 "header-abi-linker options");
46
47 static llvm::cl::list<std::string> dump_files(
48 llvm::cl::Positional, llvm::cl::desc("<dump-files>"), llvm::cl::Required,
49 llvm::cl::cat(header_linker_category), llvm::cl::OneOrMore);
50
51 static llvm::cl::opt<std::string> linked_dump(
52 "o", llvm::cl::desc("<linked dump>"), llvm::cl::Required,
53 llvm::cl::cat(header_linker_category));
54
55 static llvm::cl::list<std::string> exported_header_dirs(
56 "I", llvm::cl::desc("<export_include_dirs>"), llvm::cl::Prefix,
57 llvm::cl::ZeroOrMore, llvm::cl::cat(header_linker_category));
58
59 static llvm::cl::opt<std::string> version_script(
60 "v", llvm::cl::desc("<version_script>"), llvm::cl::Optional,
61 llvm::cl::cat(header_linker_category));
62
63 static llvm::cl::list<std::string> excluded_symbol_versions(
64 "exclude-symbol-version", llvm::cl::Optional,
65 llvm::cl::cat(header_linker_category));
66
67 static llvm::cl::list<std::string> excluded_symbol_tags(
68 "exclude-symbol-tag", llvm::cl::Optional,
69 llvm::cl::cat(header_linker_category));
70
71 static llvm::cl::opt<std::string> api(
72 "api", llvm::cl::desc("<api>"), llvm::cl::Optional,
73 llvm::cl::init("current"),
74 llvm::cl::cat(header_linker_category));
75
76 static llvm::cl::opt<std::string> arch(
77 "arch", llvm::cl::desc("<arch>"), llvm::cl::Optional,
78 llvm::cl::cat(header_linker_category));
79
80 static llvm::cl::opt<bool> no_filter(
81 "no-filter", llvm::cl::desc("Do not filter any abi"), llvm::cl::Optional,
82 llvm::cl::cat(header_linker_category));
83
84 static llvm::cl::opt<std::string> so_file(
85 "so", llvm::cl::desc("<path to so file>"), llvm::cl::Optional,
86 llvm::cl::cat(header_linker_category));
87
88 static llvm::cl::opt<TextFormatIR> input_format(
89 "input-format", llvm::cl::desc("Specify format of input dump files"),
90 llvm::cl::values(clEnumValN(TextFormatIR::ProtobufTextFormat,
91 "ProtobufTextFormat", "ProtobufTextFormat"),
92 clEnumValN(TextFormatIR::Json, "Json", "JSON")),
93 llvm::cl::init(TextFormatIR::Json),
94 llvm::cl::cat(header_linker_category));
95
96 static llvm::cl::opt<TextFormatIR> output_format(
97 "output-format", llvm::cl::desc("Specify format of output dump file"),
98 llvm::cl::values(clEnumValN(TextFormatIR::ProtobufTextFormat,
99 "ProtobufTextFormat", "ProtobufTextFormat"),
100 clEnumValN(TextFormatIR::Json, "Json", "JSON")),
101 llvm::cl::init(TextFormatIR::Json),
102 llvm::cl::cat(header_linker_category));
103
104 class HeaderAbiLinker {
105 public:
HeaderAbiLinker(const std::vector<std::string> & dump_files,const std::vector<std::string> & exported_header_dirs,const std::string & version_script,const std::string & so_file,const std::string & linked_dump,const std::string & arch,const std::string & api,const std::vector<std::string> & excluded_symbol_versions,const std::vector<std::string> & excluded_symbol_tags)106 HeaderAbiLinker(
107 const std::vector<std::string> &dump_files,
108 const std::vector<std::string> &exported_header_dirs,
109 const std::string &version_script,
110 const std::string &so_file,
111 const std::string &linked_dump,
112 const std::string &arch,
113 const std::string &api,
114 const std::vector<std::string> &excluded_symbol_versions,
115 const std::vector<std::string> &excluded_symbol_tags)
116 : dump_files_(dump_files), exported_header_dirs_(exported_header_dirs),
117 version_script_(version_script), so_file_(so_file),
118 out_dump_name_(linked_dump), arch_(arch), api_(api),
119 excluded_symbol_versions_(excluded_symbol_versions),
120 excluded_symbol_tags_(excluded_symbol_tags) {}
121
122 bool LinkAndDump();
123
124 private:
125 template <typename T>
126 bool LinkDecl(repr::ModuleIR *dst,
127 const repr::AbiElementMap<T> &src,
128 const std::function<bool(const std::string &)> &symbol_filter);
129
130 std::unique_ptr<repr::IRReader> ReadInputDumpFiles();
131
132 bool ReadExportedSymbols();
133
134 bool ReadExportedSymbolsFromVersionScript();
135
136 bool ReadExportedSymbolsFromSharedObjectFile();
137
138 bool LinkTypes(repr::ModuleIR &module, repr::ModuleIR *linked_module);
139
140 bool LinkFunctions(repr::ModuleIR &module, repr::ModuleIR *linked_module);
141
142 bool LinkGlobalVars(repr::ModuleIR &module, repr::ModuleIR *linked_module);
143
144 bool LinkExportedSymbols(repr::ModuleIR *linked_module);
145
146 bool LinkExportedSymbols(repr::ModuleIR *linked_module,
147 const repr::ExportedSymbolSet &exported_symbols);
148
149 template <typename SymbolMap>
150 bool LinkExportedSymbols(repr::ModuleIR *linked_module,
151 const SymbolMap &symbols);
152
153 // Check whether a symbol name is considered as exported. If both
154 // `shared_object_symbols_` and `version_script_symbols_` exists, the symbol
155 // name must pass the `HasSymbol()` test in both cases.
156 bool IsSymbolExported(const std::string &name) const;
157
158 private:
159 const std::vector<std::string> &dump_files_;
160 const std::vector<std::string> &exported_header_dirs_;
161 const std::string &version_script_;
162 const std::string &so_file_;
163 const std::string &out_dump_name_;
164 const std::string &arch_;
165 const std::string &api_;
166 const std::vector<std::string> &excluded_symbol_versions_;
167 const std::vector<std::string> &excluded_symbol_tags_;
168
169 std::set<std::string> exported_headers_;
170
171 // Exported symbols
172 std::unique_ptr<repr::ExportedSymbolSet> shared_object_symbols_;
173
174 std::unique_ptr<repr::ExportedSymbolSet> version_script_symbols_;
175 };
176
DeDuplicateAbiElementsThread(const std::vector<std::string> & dump_files,const std::set<std::string> * exported_headers,repr::IRReader * greader,std::mutex * greader_lock,std::atomic<std::size_t> * cnt)177 static void DeDuplicateAbiElementsThread(
178 const std::vector<std::string> &dump_files,
179 const std::set<std::string> *exported_headers,
180 repr::IRReader *greader, std::mutex *greader_lock,
181 std::atomic<std::size_t> *cnt) {
182 std::unique_ptr<repr::IRReader> local_reader =
183 repr::IRReader::CreateIRReader(input_format, exported_headers);
184
185 auto begin_it = dump_files.begin();
186 std::size_t num_sources = dump_files.size();
187 while (1) {
188 std::size_t i = cnt->fetch_add(kSourcesPerBatchThread);
189 if (i >= num_sources) {
190 break;
191 }
192 std::size_t end = std::min(i + kSourcesPerBatchThread, num_sources);
193 for (auto it = begin_it; it != begin_it + end; it++) {
194 std::unique_ptr<repr::IRReader> reader =
195 repr::IRReader::CreateIRReader(input_format, exported_headers);
196 assert(reader != nullptr);
197 if (!reader->ReadDump(*it)) {
198 llvm::errs() << "ReadDump failed\n";
199 ::exit(1);
200 }
201 // This merge is needed since the iterators might not be contigous.
202 local_reader->MergeGraphs(*reader);
203 }
204 }
205
206 std::lock_guard<std::mutex> lock(*greader_lock);
207 greader->MergeGraphs(*local_reader);
208 }
209
210 std::unique_ptr<repr::IRReader>
ReadInputDumpFiles()211 HeaderAbiLinker::ReadInputDumpFiles() {
212 std::unique_ptr<repr::IRReader> greader =
213 repr::IRReader::CreateIRReader(input_format, &exported_headers_);
214
215 std::size_t max_threads = std::thread::hardware_concurrency();
216 std::size_t num_threads = kSourcesPerBatchThread < dump_files_.size() ?
217 std::min(dump_files_.size() / kSourcesPerBatchThread, max_threads) : 0;
218 std::vector<std::thread> threads;
219 std::atomic<std::size_t> cnt(0);
220 std::mutex greader_lock;
221 for (std::size_t i = 1; i < num_threads; i++) {
222 threads.emplace_back(DeDuplicateAbiElementsThread, dump_files_,
223 &exported_headers_, greader.get(), &greader_lock,
224 &cnt);
225 }
226 DeDuplicateAbiElementsThread(dump_files_, &exported_headers_, greader.get(),
227 &greader_lock, &cnt);
228 for (auto &thread : threads) {
229 thread.join();
230 }
231
232 return greader;
233 }
234
LinkAndDump()235 bool HeaderAbiLinker::LinkAndDump() {
236 // Extract exported functions and variables from a shared lib or a version
237 // script.
238 if (!ReadExportedSymbols()) {
239 return false;
240 }
241
242 // Construct the list of exported headers for source location filtering.
243 exported_headers_ = CollectAllExportedHeaders(exported_header_dirs_);
244
245 // Read all input ABI dumps.
246 auto greader = ReadInputDumpFiles();
247
248 repr::ModuleIR &module = greader->GetModule();
249
250 // Link input ABI dumps.
251 std::unique_ptr<repr::ModuleIR> linked_module(
252 new repr::ModuleIR(&exported_headers_));
253
254 if (!LinkExportedSymbols(linked_module.get())) {
255 return false;
256 }
257
258 if (!LinkTypes(module, linked_module.get()) ||
259 !LinkFunctions(module, linked_module.get()) ||
260 !LinkGlobalVars(module, linked_module.get())) {
261 llvm::errs() << "Failed to link elements\n";
262 return false;
263 }
264
265 // Dump the linked module.
266 std::unique_ptr<repr::IRDumper> ir_dumper =
267 repr::IRDumper::CreateIRDumper(output_format, out_dump_name_);
268 assert(ir_dumper != nullptr);
269 if (!ir_dumper->Dump(*linked_module)) {
270 llvm::errs() << "Failed to serialize the linked output to ostream\n";
271 return false;
272 }
273
274 return true;
275 }
276
277 template <typename T>
LinkDecl(repr::ModuleIR * dst,const repr::AbiElementMap<T> & src,const std::function<bool (const std::string &)> & symbol_filter)278 bool HeaderAbiLinker::LinkDecl(
279 repr::ModuleIR *dst, const repr::AbiElementMap<T> &src,
280 const std::function<bool(const std::string &)> &symbol_filter) {
281 assert(dst != nullptr);
282 for (auto &&element : src) {
283 // If we are not using a version script and exported headers are available,
284 // filter out unexported abi.
285 std::string source_file = element.second.GetSourceFile();
286 // Builtin types will not have source file information.
287 if (!exported_headers_.empty() && !source_file.empty() &&
288 exported_headers_.find(source_file) == exported_headers_.end()) {
289 continue;
290 }
291 // Check for the existence of the element in version script / symbol file.
292 if (!symbol_filter(element.first)) {
293 continue;
294 }
295 if (!dst->AddLinkableMessage(element.second)) {
296 llvm::errs() << "Failed to add element to linked dump\n";
297 return false;
298 }
299 }
300 return true;
301 }
302
LinkTypes(repr::ModuleIR & module,repr::ModuleIR * linked_module)303 bool HeaderAbiLinker::LinkTypes(repr::ModuleIR &module,
304 repr::ModuleIR *linked_module) {
305 auto no_filter = [](const std::string &symbol) { return true; };
306 return LinkDecl(linked_module, module.GetRecordTypes(), no_filter) &&
307 LinkDecl(linked_module, module.GetEnumTypes(), no_filter) &&
308 LinkDecl(linked_module, module.GetFunctionTypes(), no_filter) &&
309 LinkDecl(linked_module, module.GetBuiltinTypes(), no_filter) &&
310 LinkDecl(linked_module, module.GetPointerTypes(), no_filter) &&
311 LinkDecl(linked_module, module.GetRvalueReferenceTypes(), no_filter) &&
312 LinkDecl(linked_module, module.GetLvalueReferenceTypes(), no_filter) &&
313 LinkDecl(linked_module, module.GetArrayTypes(), no_filter) &&
314 LinkDecl(linked_module, module.GetQualifiedTypes(), no_filter);
315 }
316
IsSymbolExported(const std::string & name) const317 bool HeaderAbiLinker::IsSymbolExported(const std::string &name) const {
318 if (shared_object_symbols_ && !shared_object_symbols_->HasSymbol(name)) {
319 return false;
320 }
321 if (version_script_symbols_ && !version_script_symbols_->HasSymbol(name)) {
322 return false;
323 }
324 return true;
325 }
326
LinkFunctions(repr::ModuleIR & module,repr::ModuleIR * linked_module)327 bool HeaderAbiLinker::LinkFunctions(repr::ModuleIR &module,
328 repr::ModuleIR *linked_module) {
329 auto symbol_filter = [this](const std::string &linker_set_key) {
330 return IsSymbolExported(linker_set_key);
331 };
332 return LinkDecl(linked_module, module.GetFunctions(), symbol_filter);
333 }
334
LinkGlobalVars(repr::ModuleIR & module,repr::ModuleIR * linked_module)335 bool HeaderAbiLinker::LinkGlobalVars(repr::ModuleIR &module,
336 repr::ModuleIR *linked_module) {
337 auto symbol_filter = [this](const std::string &linker_set_key) {
338 return IsSymbolExported(linker_set_key);
339 };
340 return LinkDecl(linked_module, module.GetGlobalVariables(), symbol_filter);
341 }
342
343 template <typename SymbolMap>
LinkExportedSymbols(repr::ModuleIR * dst,const SymbolMap & symbols)344 bool HeaderAbiLinker::LinkExportedSymbols(repr::ModuleIR *dst,
345 const SymbolMap &symbols) {
346 for (auto &&symbol : symbols) {
347 if (!IsSymbolExported(symbol.first)) {
348 continue;
349 }
350 if (!dst->AddElfSymbol(symbol.second)) {
351 return false;
352 }
353 }
354 return true;
355 }
356
LinkExportedSymbols(repr::ModuleIR * linked_module,const repr::ExportedSymbolSet & exported_symbols)357 bool HeaderAbiLinker::LinkExportedSymbols(
358 repr::ModuleIR *linked_module,
359 const repr::ExportedSymbolSet &exported_symbols) {
360 return (LinkExportedSymbols(linked_module, exported_symbols.GetFunctions()) &&
361 LinkExportedSymbols(linked_module, exported_symbols.GetVars()));
362 }
363
LinkExportedSymbols(repr::ModuleIR * linked_module)364 bool HeaderAbiLinker::LinkExportedSymbols(repr::ModuleIR *linked_module) {
365 if (shared_object_symbols_) {
366 return LinkExportedSymbols(linked_module, *shared_object_symbols_);
367 }
368
369 if (version_script_symbols_) {
370 return LinkExportedSymbols(linked_module, *version_script_symbols_);
371 }
372
373 return false;
374 }
375
ReadExportedSymbols()376 bool HeaderAbiLinker::ReadExportedSymbols() {
377 if (so_file_.empty() && version_script_.empty()) {
378 llvm::errs() << "Either shared lib or version script must be specified.\n";
379 return false;
380 }
381
382 if (!so_file_.empty()) {
383 if (!ReadExportedSymbolsFromSharedObjectFile()) {
384 llvm::errs() << "Failed to parse the shared library (.so file): "
385 << so_file_ << "\n";
386 return false;
387 }
388 }
389
390 if (!version_script_.empty()) {
391 if (!ReadExportedSymbolsFromVersionScript()) {
392 llvm::errs() << "Failed to parse the version script: " << version_script_
393 << "\n";
394 return false;
395 }
396 }
397
398 return true;
399 }
400
ReadExportedSymbolsFromVersionScript()401 bool HeaderAbiLinker::ReadExportedSymbolsFromVersionScript() {
402 std::optional<utils::ApiLevel> api_level = utils::ParseApiLevel(api_);
403 if (!api_level) {
404 llvm::errs() << "-api must be either \"current\" or an integer (e.g. 21)\n";
405 return false;
406 }
407
408 std::ifstream stream(version_script_, std::ios_base::in);
409 if (!stream) {
410 llvm::errs() << "Failed to open version script file\n";
411 return false;
412 }
413
414 repr::VersionScriptParser parser;
415 parser.SetArch(arch_);
416 parser.SetApiLevel(api_level.value());
417 for (auto &&version : excluded_symbol_versions_) {
418 parser.AddExcludedSymbolVersion(version);
419 }
420 for (auto &&tag : excluded_symbol_tags_) {
421 parser.AddExcludedSymbolTag(tag);
422 }
423
424 version_script_symbols_ = parser.Parse(stream);
425 if (!version_script_symbols_) {
426 llvm::errs() << "Failed to parse version script file\n";
427 return false;
428 }
429
430 return true;
431 }
432
ReadExportedSymbolsFromSharedObjectFile()433 bool HeaderAbiLinker::ReadExportedSymbolsFromSharedObjectFile() {
434 std::unique_ptr<repr::SoFileParser> so_parser =
435 repr::SoFileParser::Create(so_file_);
436 if (!so_parser) {
437 return false;
438 }
439
440 shared_object_symbols_ = so_parser->Parse();
441 if (!shared_object_symbols_) {
442 llvm::errs() << "Failed to parse shared object file\n";
443 return false;
444 }
445
446 return true;
447 }
448
449 // Hide irrelevant command line options defined in LLVM libraries.
HideIrrelevantCommandLineOptions()450 static void HideIrrelevantCommandLineOptions() {
451 llvm::StringMap<llvm::cl::Option *> &map = llvm::cl::getRegisteredOptions();
452 for (llvm::StringMapEntry<llvm::cl::Option *> &p : map) {
453 if (p.second->Category == &header_linker_category) {
454 continue;
455 }
456 if (p.first().startswith("help")) {
457 continue;
458 }
459 p.second->setHiddenFlag(llvm::cl::Hidden);
460 }
461 }
462
main(int argc,const char ** argv)463 int main(int argc, const char **argv) {
464 HideIrrelevantCommandLineOptions();
465 llvm::cl::ParseCommandLineOptions(argc, argv, "header-linker");
466
467 if (so_file.empty() && version_script.empty()) {
468 llvm::errs() << "One of -so or -v needs to be specified\n";
469 return -1;
470 }
471
472 if (no_filter) {
473 static_cast<std::vector<std::string> &>(exported_header_dirs).clear();
474 }
475
476 HeaderAbiLinker Linker(dump_files, exported_header_dirs, version_script,
477 so_file, linked_dump, arch, api,
478 excluded_symbol_versions,
479 excluded_symbol_tags);
480
481 if (!Linker.LinkAndDump()) {
482 llvm::errs() << "Failed to link and dump elements\n";
483 return -1;
484 }
485
486 return 0;
487 }
488