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