• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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