| // Copyright (C) 2016 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "linker/module_merger.h" |
| #include "repr/ir_dumper.h" |
| #include "repr/ir_reader.h" |
| #include "repr/ir_representation.h" |
| #include "repr/symbol/so_file_parser.h" |
| #include "repr/symbol/version_script_parser.h" |
| #include "utils/command_line_utils.h" |
| #include "utils/source_path_utils.h" |
| |
| #include <llvm/ADT/Optional.h> |
| #include <llvm/Support/CommandLine.h> |
| #include <llvm/Support/raw_ostream.h> |
| |
| #include <fstream> |
| #include <functional> |
| #include <iostream> |
| #include <memory> |
| #include <string> |
| #include <thread> |
| #include <vector> |
| |
| #include <stdlib.h> |
| |
| |
| using namespace header_checker; |
| using header_checker::repr::TextFormatIR; |
| using header_checker::utils::CollectAllExportedHeaders; |
| using header_checker::utils::HideIrrelevantCommandLineOptions; |
| using header_checker::utils::ParseRootDirs; |
| using header_checker::utils::RootDir; |
| |
| |
| static llvm::cl::OptionCategory header_linker_category( |
| "header-abi-linker options"); |
| |
| static llvm::cl::list<std::string> dump_files( |
| llvm::cl::Positional, llvm::cl::desc("<dump-files>"), llvm::cl::ZeroOrMore, |
| llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::opt<std::string> linked_dump( |
| "o", llvm::cl::desc("<linked dump>"), llvm::cl::Required, |
| llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::list<std::string> exported_header_dirs( |
| "I", llvm::cl::desc("<export_include_dirs>"), llvm::cl::Prefix, |
| llvm::cl::ZeroOrMore, llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::list<std::string> root_dirs( |
| "root-dir", |
| llvm::cl::desc("Specify the directory that the paths in the dump files " |
| "are relative to. The format is <path>:<replacement> or " |
| "<path>. If this option is not specified, it defaults to " |
| "current working directory."), |
| llvm::cl::ZeroOrMore, llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::opt<std::string> version_script( |
| "v", llvm::cl::desc("<version_script>"), llvm::cl::Optional, |
| llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::list<std::string> excluded_symbol_versions( |
| "exclude-symbol-version", llvm::cl::Optional, |
| llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::list<std::string> excluded_symbol_tags( |
| "exclude-symbol-tag", llvm::cl::Optional, |
| llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::opt<std::string> api( |
| "api", llvm::cl::desc("<api>"), llvm::cl::Optional, |
| llvm::cl::init("current"), |
| llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::opt<std::string> arch( |
| "arch", llvm::cl::desc("<arch>"), llvm::cl::Optional, |
| llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::opt<bool> no_filter( |
| "no-filter", llvm::cl::desc("Do not filter any abi"), llvm::cl::Optional, |
| llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::opt<std::string> so_file( |
| "so", llvm::cl::desc("<path to so file>"), llvm::cl::Optional, |
| llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::opt<TextFormatIR> input_format( |
| "input-format", llvm::cl::desc("Specify format of input dump files"), |
| llvm::cl::values(clEnumValN(TextFormatIR::ProtobufTextFormat, |
| "ProtobufTextFormat", "ProtobufTextFormat"), |
| clEnumValN(TextFormatIR::Json, "Json", "JSON")), |
| llvm::cl::init(TextFormatIR::Json), |
| llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::opt<TextFormatIR> output_format( |
| "output-format", llvm::cl::desc("Specify format of output dump file"), |
| llvm::cl::values(clEnumValN(TextFormatIR::ProtobufTextFormat, |
| "ProtobufTextFormat", "ProtobufTextFormat"), |
| clEnumValN(TextFormatIR::Json, "Json", "JSON")), |
| llvm::cl::init(TextFormatIR::Json), |
| llvm::cl::cat(header_linker_category)); |
| |
| static llvm::cl::opt<std::size_t> sources_per_thread( |
| "sources-per-thread", |
| llvm::cl::desc("Specify number of input dump files each thread parses, for " |
| "debugging merging types"), |
| llvm::cl::init(7), llvm::cl::Hidden); |
| |
| class HeaderAbiLinker { |
| 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) |
| : dump_files_(dump_files), exported_header_dirs_(exported_header_dirs), |
| version_script_(version_script), so_file_(so_file), |
| out_dump_name_(linked_dump), arch_(arch), api_(api), |
| excluded_symbol_versions_(excluded_symbol_versions), |
| excluded_symbol_tags_(excluded_symbol_tags) {} |
| |
| bool LinkAndDump(); |
| |
| private: |
| template <typename T> |
| bool LinkDecl(repr::ModuleIR *dst, |
| const repr::AbiElementMap<T> &src, |
| const std::function<bool(const std::string &)> &symbol_filter); |
| |
| std::unique_ptr<linker::ModuleMerger> ReadInputDumpFiles(); |
| |
| bool ReadExportedSymbols(); |
| |
| bool ReadExportedSymbolsFromVersionScript(); |
| |
| bool ReadExportedSymbolsFromSharedObjectFile(); |
| |
| bool LinkTypes(const repr::ModuleIR &module, repr::ModuleIR *linked_module); |
| |
| bool LinkFunctions(const repr::ModuleIR &module, |
| repr::ModuleIR *linked_module); |
| |
| bool LinkGlobalVars(const repr::ModuleIR &module, |
| repr::ModuleIR *linked_module); |
| |
| bool LinkExportedSymbols(repr::ModuleIR *linked_module); |
| |
| bool LinkExportedSymbols(repr::ModuleIR *linked_module, |
| const repr::ExportedSymbolSet &exported_symbols); |
| |
| template <typename SymbolMap> |
| bool LinkExportedSymbols(repr::ModuleIR *linked_module, |
| const SymbolMap &symbols); |
| |
| // Check whether a symbol name is considered as exported. If both |
| // `shared_object_symbols_` and `version_script_symbols_` exists, the symbol |
| // name must pass the `HasSymbol()` test in both cases. |
| bool IsSymbolExported(const std::string &name) const; |
| |
| private: |
| 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 &out_dump_name_; |
| const std::string &arch_; |
| const std::string &api_; |
| const std::vector<std::string> &excluded_symbol_versions_; |
| const std::vector<std::string> &excluded_symbol_tags_; |
| |
| std::set<std::string> exported_headers_; |
| |
| // Exported symbols |
| std::unique_ptr<repr::ExportedSymbolSet> shared_object_symbols_; |
| |
| std::unique_ptr<repr::ExportedSymbolSet> version_script_symbols_; |
| }; |
| |
| static void DeDuplicateAbiElementsThread( |
| std::vector<std::string>::const_iterator dump_files_begin, |
| std::vector<std::string>::const_iterator dump_files_end, |
| const std::set<std::string> *exported_headers, |
| linker::ModuleMerger *merger) { |
| for (auto it = dump_files_begin; it != dump_files_end; it++) { |
| std::unique_ptr<repr::IRReader> reader = |
| repr::IRReader::CreateIRReader(input_format, exported_headers); |
| assert(reader != nullptr); |
| if (!reader->ReadDump(*it)) { |
| llvm::errs() << "ReadDump failed\n"; |
| ::exit(1); |
| } |
| merger->MergeGraphs(reader->GetModule()); |
| } |
| } |
| |
| std::unique_ptr<linker::ModuleMerger> HeaderAbiLinker::ReadInputDumpFiles() { |
| std::unique_ptr<linker::ModuleMerger> merger( |
| new linker::ModuleMerger(&exported_headers_)); |
| std::size_t max_threads = std::thread::hardware_concurrency(); |
| std::size_t num_threads = std::max<std::size_t>( |
| std::min(dump_files_.size() / sources_per_thread, max_threads), 1); |
| std::vector<std::thread> threads; |
| std::vector<linker::ModuleMerger> thread_mergers; |
| thread_mergers.reserve(num_threads - 1); |
| |
| std::size_t dump_files_index = 0; |
| std::size_t first_end_index = 0; |
| for (std::size_t i = 0; i < num_threads; i++) { |
| std::size_t cnt = dump_files_.size() / num_threads + |
| (i < dump_files_.size() % num_threads ? 1 : 0); |
| if (i == 0) { |
| first_end_index = cnt; |
| } else { |
| thread_mergers.emplace_back(&exported_headers_); |
| threads.emplace_back(DeDuplicateAbiElementsThread, |
| dump_files_.begin() + dump_files_index, |
| dump_files_.begin() + dump_files_index + cnt, |
| &exported_headers_, &thread_mergers.back()); |
| } |
| dump_files_index += cnt; |
| } |
| assert(dump_files_index == dump_files_.size()); |
| |
| DeDuplicateAbiElementsThread(dump_files_.begin(), |
| dump_files_.begin() + first_end_index, |
| &exported_headers_, merger.get()); |
| |
| for (std::size_t i = 0; i < threads.size(); i++) { |
| threads[i].join(); |
| merger->MergeGraphs(thread_mergers[i].GetModule()); |
| } |
| |
| return merger; |
| } |
| |
| bool HeaderAbiLinker::LinkAndDump() { |
| // Extract exported functions and variables from a shared lib or a version |
| // script. |
| if (!ReadExportedSymbols()) { |
| return false; |
| } |
| |
| // Construct the list of exported headers for source location filtering. |
| exported_headers_ = CollectAllExportedHeaders(exported_header_dirs_, |
| ParseRootDirs(root_dirs)); |
| |
| // Read all input ABI dumps. |
| auto merger = ReadInputDumpFiles(); |
| |
| const repr::ModuleIR &module = merger->GetModule(); |
| |
| // Link input ABI dumps. |
| std::unique_ptr<repr::ModuleIR> linked_module( |
| new repr::ModuleIR(&exported_headers_)); |
| |
| if (!LinkExportedSymbols(linked_module.get())) { |
| return false; |
| } |
| |
| if (!LinkTypes(module, linked_module.get()) || |
| !LinkFunctions(module, linked_module.get()) || |
| !LinkGlobalVars(module, linked_module.get())) { |
| llvm::errs() << "Failed to link elements\n"; |
| return false; |
| } |
| |
| // Dump the linked module. |
| std::unique_ptr<repr::IRDumper> ir_dumper = |
| repr::IRDumper::CreateIRDumper(output_format, out_dump_name_); |
| assert(ir_dumper != nullptr); |
| if (!ir_dumper->Dump(*linked_module)) { |
| llvm::errs() << "Failed to serialize the linked output to ostream\n"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| template <typename T> |
| bool HeaderAbiLinker::LinkDecl( |
| repr::ModuleIR *dst, const repr::AbiElementMap<T> &src, |
| const std::function<bool(const std::string &)> &symbol_filter) { |
| assert(dst != nullptr); |
| for (auto &&element : src) { |
| // If we are not using a version script and exported headers are available, |
| // filter out unexported abi. |
| std::string source_file = element.second.GetSourceFile(); |
| // Builtin types will not have source file information. |
| if (!exported_headers_.empty() && !source_file.empty() && |
| exported_headers_.find(source_file) == exported_headers_.end()) { |
| continue; |
| } |
| // Check for the existence of the element in version script / symbol file. |
| if (!symbol_filter(element.first)) { |
| continue; |
| } |
| if (!dst->AddLinkableMessage(element.second)) { |
| llvm::errs() << "Failed to add element to linked dump\n"; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool HeaderAbiLinker::LinkTypes(const repr::ModuleIR &module, |
| repr::ModuleIR *linked_module) { |
| auto no_filter = [](const std::string &symbol) { return true; }; |
| return LinkDecl(linked_module, module.GetRecordTypes(), no_filter) && |
| LinkDecl(linked_module, module.GetEnumTypes(), no_filter) && |
| LinkDecl(linked_module, module.GetFunctionTypes(), no_filter) && |
| LinkDecl(linked_module, module.GetBuiltinTypes(), no_filter) && |
| LinkDecl(linked_module, module.GetPointerTypes(), no_filter) && |
| LinkDecl(linked_module, module.GetRvalueReferenceTypes(), no_filter) && |
| LinkDecl(linked_module, module.GetLvalueReferenceTypes(), no_filter) && |
| LinkDecl(linked_module, module.GetArrayTypes(), no_filter) && |
| LinkDecl(linked_module, module.GetQualifiedTypes(), no_filter); |
| } |
| |
| bool HeaderAbiLinker::IsSymbolExported(const std::string &name) const { |
| if (shared_object_symbols_ && !shared_object_symbols_->HasSymbol(name)) { |
| return false; |
| } |
| if (version_script_symbols_ && !version_script_symbols_->HasSymbol(name)) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool HeaderAbiLinker::LinkFunctions(const repr::ModuleIR &module, |
| repr::ModuleIR *linked_module) { |
| auto symbol_filter = [this](const std::string &linker_set_key) { |
| return IsSymbolExported(linker_set_key); |
| }; |
| return LinkDecl(linked_module, module.GetFunctions(), symbol_filter); |
| } |
| |
| bool HeaderAbiLinker::LinkGlobalVars(const repr::ModuleIR &module, |
| repr::ModuleIR *linked_module) { |
| auto symbol_filter = [this](const std::string &linker_set_key) { |
| return IsSymbolExported(linker_set_key); |
| }; |
| return LinkDecl(linked_module, module.GetGlobalVariables(), symbol_filter); |
| } |
| |
| template <typename SymbolMap> |
| bool HeaderAbiLinker::LinkExportedSymbols(repr::ModuleIR *dst, |
| const SymbolMap &symbols) { |
| for (auto &&symbol : symbols) { |
| if (!IsSymbolExported(symbol.first)) { |
| continue; |
| } |
| if (!dst->AddElfSymbol(symbol.second)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool HeaderAbiLinker::LinkExportedSymbols( |
| repr::ModuleIR *linked_module, |
| const repr::ExportedSymbolSet &exported_symbols) { |
| return (LinkExportedSymbols(linked_module, exported_symbols.GetFunctions()) && |
| LinkExportedSymbols(linked_module, exported_symbols.GetVars())); |
| } |
| |
| bool HeaderAbiLinker::LinkExportedSymbols(repr::ModuleIR *linked_module) { |
| if (shared_object_symbols_) { |
| return LinkExportedSymbols(linked_module, *shared_object_symbols_); |
| } |
| |
| if (version_script_symbols_) { |
| return LinkExportedSymbols(linked_module, *version_script_symbols_); |
| } |
| |
| return false; |
| } |
| |
| bool HeaderAbiLinker::ReadExportedSymbols() { |
| if (so_file_.empty() && version_script_.empty()) { |
| llvm::errs() << "Either shared lib or version script must be specified.\n"; |
| return false; |
| } |
| |
| if (!so_file_.empty()) { |
| if (!ReadExportedSymbolsFromSharedObjectFile()) { |
| llvm::errs() << "Failed to parse the shared library (.so file): " |
| << so_file_ << "\n"; |
| return false; |
| } |
| } |
| |
| if (!version_script_.empty()) { |
| if (!ReadExportedSymbolsFromVersionScript()) { |
| llvm::errs() << "Failed to parse the version script: " << version_script_ |
| << "\n"; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool HeaderAbiLinker::ReadExportedSymbolsFromVersionScript() { |
| llvm::Optional<utils::ApiLevel> api_level = utils::ParseApiLevel(api_); |
| if (!api_level) { |
| llvm::errs() << "-api must be either \"current\" or an integer (e.g. 21)\n"; |
| return false; |
| } |
| |
| std::ifstream stream(version_script_, std::ios_base::in); |
| if (!stream) { |
| llvm::errs() << "Failed to open version script file\n"; |
| return false; |
| } |
| |
| repr::VersionScriptParser parser; |
| parser.SetArch(arch_); |
| parser.SetApiLevel(api_level.value()); |
| for (auto &&version : excluded_symbol_versions_) { |
| parser.AddExcludedSymbolVersion(version); |
| } |
| for (auto &&tag : excluded_symbol_tags_) { |
| parser.AddExcludedSymbolTag(tag); |
| } |
| |
| version_script_symbols_ = parser.Parse(stream); |
| if (!version_script_symbols_) { |
| llvm::errs() << "Failed to parse version script file\n"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool HeaderAbiLinker::ReadExportedSymbolsFromSharedObjectFile() { |
| std::unique_ptr<repr::SoFileParser> so_parser = |
| repr::SoFileParser::Create(so_file_); |
| if (!so_parser) { |
| return false; |
| } |
| |
| shared_object_symbols_ = so_parser->Parse(); |
| if (!shared_object_symbols_) { |
| llvm::errs() << "Failed to parse shared object file\n"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int main(int argc, const char **argv) { |
| HideIrrelevantCommandLineOptions(header_linker_category); |
| llvm::cl::ParseCommandLineOptions(argc, argv, "header-linker"); |
| |
| if (so_file.empty() && version_script.empty()) { |
| llvm::errs() << "One of -so or -v needs to be specified\n"; |
| return -1; |
| } |
| |
| if (no_filter) { |
| static_cast<std::vector<std::string> &>(exported_header_dirs).clear(); |
| } |
| |
| HeaderAbiLinker Linker(dump_files, exported_header_dirs, version_script, |
| so_file, linked_dump, arch, api, |
| excluded_symbol_versions, |
| excluded_symbol_tags); |
| |
| if (!Linker.LinkAndDump()) { |
| llvm::errs() << "Failed to link and dump elements\n"; |
| return -1; |
| } |
| |
| return 0; |
| } |