// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // -*- Mode: C++ -*- // // Copyright (C) 2015-2020 Red Hat, Inc. // // Author: Sinny Kumari /// @file /// This program compares the ABIs of binaries inside two packages. /// /// For now, the supported package formats are Deb and RPM, but /// support for other formats would be greatly appreciated. /// /// The program takes the two packages to compare as well as their /// associated debug info packages. /// /// The program extracts the content of the two packages into a /// temporary directory , looks for the ELF binaries in there, /// compares their ABIs and emit a report about the changes. /// As this program uses libpthread to perform several tasks /// concurrently, here is a coarse grain description of the sequence /// of actions performed, including where things are done /// concurrently. /// /// (steps 1/ and 2/ are performed concurrently. Then steps 3/ and 4/ /// are performed in sequence) /// /// 1/ the first package and its ancillary packages (debug info and /// devel packages) are extracted concurrently. /// There is one thread per package being extracted. So if there are /// 3 thread packages (one package, one debug info package and one /// devel package), then there are 3 threads to extracts them. Then /// when the extracting is done, another thread performs the analysis /// of th1 extracted content. /// /// 2/ A similar thing is done for the second package. /// /// 3/ comparisons are performed concurrently. /// /// 4/ the reports are then emitted to standard output, always in the same /// order. // In case we have a bad fts we include this before config.h because // it can't handle _FILE_OFFSET_BITS. Everything we need here is fine // if its declarations just come first. Also, include sys/types.h // before fts. On some systems fts.h is not self contained. #ifdef BAD_FTS #include #include #endif // For package configuration macros. #include "config.h" #include #include #include // If fts.h is included before config.h, its indirect inclusions may // not give us the right LFS aliases of these functions, so map them // manually. #ifdef BAD_FTS #ifdef _FILE_OFFSET_BITS #define open open64 #define fopen fopen64 #endif #else #include #include #endif #include #include #include #include #include #include #include #include #include #include #include "abg-workers.h" #include "abg-config.h" #include "abg-tools-utils.h" #include "abg-comparison.h" #include "abg-suppression.h" #include "abg-dwarf-reader.h" #include "abg-reader.h" #include "abg-writer.h" using std::cout; using std::cerr; using std::string; using std::ostream; using std::ofstream; using std::vector; using std::map; using std::unordered_set; using std::set; using std::ostringstream; using std::shared_ptr; using std::dynamic_pointer_cast; using abigail::workers::task; using abigail::workers::task_sptr; using abigail::workers::queue; using abigail::tools_utils::maybe_get_symlink_target_file_path; using abigail::tools_utils::file_exists; using abigail::tools_utils::is_dir; using abigail::tools_utils::emit_prefix; using abigail::tools_utils::check_file; using abigail::tools_utils::ensure_dir_path_created; using abigail::tools_utils::guess_file_type; using abigail::tools_utils::string_ends_with; using abigail::tools_utils::dir_name; using abigail::tools_utils::real_path; using abigail::tools_utils::string_suffix; using abigail::tools_utils::sorted_strings_common_prefix; using abigail::tools_utils::file_type; using abigail::tools_utils::make_path_absolute; using abigail::tools_utils::base_name; using abigail::tools_utils::get_rpm_arch; using abigail::tools_utils::file_is_kernel_package; using abigail::tools_utils::gen_suppr_spec_from_headers; using abigail::tools_utils::get_default_system_suppression_file_path; using abigail::tools_utils::get_default_user_suppression_file_path; using abigail::tools_utils::get_vmlinux_path_from_kernel_dist; using abigail::tools_utils::get_dsos_provided_by_rpm; using abigail::tools_utils::build_corpus_group_from_kernel_dist_under; using abigail::tools_utils::load_default_system_suppressions; using abigail::tools_utils::load_default_user_suppressions; using abigail::tools_utils::abidiff_status; using abigail::ir::corpus_sptr; using abigail::ir::corpus_group_sptr; using abigail::comparison::diff_context; using abigail::comparison::diff_context_sptr; using abigail::comparison::compute_diff; using abigail::comparison::corpus_diff_sptr; using abigail::comparison::get_default_harmless_categories_bitmap; using abigail::comparison::get_default_harmful_categories_bitmap; using abigail::suppr::suppression_sptr; using abigail::suppr::suppressions_type; using abigail::suppr::read_suppressions; using abigail::dwarf_reader::read_context_sptr; using abigail::dwarf_reader::create_read_context; using abigail::dwarf_reader::get_soname_of_elf_file; using abigail::dwarf_reader::get_type_of_elf_file; using abigail::dwarf_reader::read_corpus_from_elf; using abigail::xml_writer::create_write_context; using abigail::xml_writer::write_context_sptr; using abigail::xml_writer::write_corpus; class package; /// Convenience typedef for a shared pointer to a @ref package. typedef shared_ptr package_sptr; /// The options passed to the current program. class options { options(); public: string wrong_option; string wrong_arg; string prog_name; bool display_usage; bool display_version; bool missing_operand; bool nonexistent_file; bool abignore; bool parallel; string package1; string package2; vector debug_packages1; vector debug_packages2; string devel_package1; string devel_package2; size_t num_workers; bool verbose; bool drop_private_types; bool show_relative_offset_changes; bool no_default_suppression; bool keep_tmp_files; bool compare_dso_only; bool compare_private_dsos; bool leaf_changes_only; bool show_all_types; bool show_hexadecimal_values; bool show_offsets_sizes_in_bits; bool show_impacted_interfaces; bool show_full_impact_report; bool show_linkage_names; bool show_redundant_changes; bool show_harmless_changes; bool show_locs; bool show_added_syms; bool show_symbols_not_referenced_by_debug_info; bool show_added_binaries; bool fail_if_no_debug_info; bool show_identical_binaries; bool self_check; vector kabi_whitelist_packages; vector suppression_paths; vector kabi_whitelist_paths; suppressions_type kabi_suppressions; package_sptr pkg1; package_sptr pkg2; options(const string& program_name) : prog_name(program_name), display_usage(), display_version(), missing_operand(), nonexistent_file(), abignore(true), parallel(true), verbose(), drop_private_types(), show_relative_offset_changes(true), no_default_suppression(), keep_tmp_files(), compare_dso_only(), compare_private_dsos(), leaf_changes_only(), show_all_types(), show_hexadecimal_values(), show_offsets_sizes_in_bits(true), show_impacted_interfaces(), show_full_impact_report(), show_linkage_names(true), show_redundant_changes(), show_harmless_changes(), show_locs(true), show_added_syms(true), show_symbols_not_referenced_by_debug_info(true), show_added_binaries(true), fail_if_no_debug_info(), show_identical_binaries(), self_check() { // set num_workers to the default number of threads of the // underlying maching. This is the default value for the number // of workers to use in workers queues throughout the code. num_workers = abigail::workers::get_number_of_threads(); } }; static bool get_interesting_files_under_dir(const string dir, const string& file_name_to_look_for, options& opts, vector& interesting_files); /// Abstract ELF files from the packages which ABIs ought to be /// compared class elf_file { private: elf_file(); public: string path; string name; string soname; off_t size; abigail::dwarf_reader::elf_type type; /// The path to the elf file. /// /// @param path the path to the elf file. elf_file(const string& path) : path(path) { abigail::tools_utils::base_name(path, name); get_soname_of_elf_file(path, soname); get_type_of_elf_file(path, type); struct stat estat; stat(path.c_str(), &estat); size = estat.st_size; } }; /// A convenience typedef for a shared pointer to elf_file. typedef shared_ptr elf_file_sptr; /// Abstract the result of comparing two packages. /// /// This contains the the paths of the set of added binaries, removed /// binaries, and binaries whic ABI changed. struct abi_diff { vector added_binaries; vector removed_binaries; vector changed_binaries; /// Test if the current diff carries changes. /// /// @return true iff the current diff carries changes. bool has_changes() { return (!added_binaries.empty() || !removed_binaries.empty() ||!changed_binaries.empty()); } }; /// Abstracts a package. class package { public: /// The kind of package we are looking at. enum kind { /// Main package. Contains binaries to ABI-compare. KIND_MAIN = 0, /// Devel package. Contains public headers files in which public /// types are defined. KIND_DEVEL, /// Debug info package. Contains the debug info for the binaries /// int he main packge. KIND_DEBUG_INFO, /// Contains kernel ABI whitelists KIND_KABI_WHITELISTS, /// Source package. Contains the source of the binaries in the /// main package. KIND_SRC }; private: string path_; string extracted_dir_path_; string common_paths_prefix_; abigail::tools_utils::file_type type_; kind kind_; map path_elf_file_sptr_map_; vector debug_info_packages_; package_sptr devel_package_; package_sptr kabi_whitelist_package_; vector elf_file_paths_; set public_dso_sonames_; public: /// Constructor for the @ref package type. /// /// @param path the path to the package. /// /// @parm dir the temporary directory where to extract the content /// of the package. /// /// @param pkg_kind the kind of package. package(const string& path, const string& dir, kind pkg_kind = package::KIND_MAIN) : path_(path), kind_(pkg_kind) { type_ = guess_file_type(path); if (type_ == abigail::tools_utils::FILE_TYPE_DIR) extracted_dir_path_ = path; else extracted_dir_path_ = extracted_packages_parent_dir() + "/" + dir; } /// Getter of the path of the package. /// /// @return the path of the package. const string& path() const {return path_;} /// Setter of the path of the package. /// /// @param s the new path. void path(const string& s) {path_ = s;} /// Getter of the base name of the package. /// /// @return the base name of the package. string base_name() const { string name; abigail::tools_utils::base_name(path(), name); return name; } /// Getter for the path to the root dir where the packages are /// extracted. /// /// @return the path to the root dir where the packages are /// extracted. static const string& extracted_packages_parent_dir(); /// Getter for the path to the directory where the packages are /// extracted for the current thread. /// /// @return the path to the directory where the packages are /// extracted for the current thread. const string& extracted_dir_path() const {return extracted_dir_path_;} /// Setter for the path to the directory where the packages are /// extracted for the current thread. /// /// @param p the new path. void extracted_dir_path(const string& p) {extracted_dir_path_ = p;} /// Getter of the the prefix that is common to all the paths of all /// the elements of the package. /// /// @return the common path prefix of package elements. const string& common_paths_prefix() const {return common_paths_prefix_;} /// Getter of the the prefix that is common to all the paths of all /// the elements of the package. /// /// @return the common path prefix of package elements. string& common_paths_prefix() {return common_paths_prefix_;} /// Setter of the the prefix that is common to all the paths of all /// the elements of the package. /// /// ///@param p the new prefix. void common_paths_prefix(const string& p) {common_paths_prefix_ = p;} /// Getter for the file type of the current package. /// /// @return the file type of the current package. abigail::tools_utils::file_type type() const {return type_;} /// Setter for the file type of the current package. /// /// @param t the new file type. void type(abigail::tools_utils::file_type t) {type_ = t;} /// Get the package kind /// /// @return the package kind kind get_kind() const {return kind_;} /// Set the package kind /// /// @param k the package kind. void set_kind(kind k) {kind_ = k;} /// Getter for the path <-> elf_file map. /// /// @return the the path <-> elf_file map. const map& path_elf_file_sptr_map() const {return path_elf_file_sptr_map_;} /// Getter for the path <-> elf_file map. /// /// @return the the path <-> elf_file map. map& path_elf_file_sptr_map() {return path_elf_file_sptr_map_;} /// Getter for the debug info packages associated to the current /// package. /// /// There can indeed be several debug info packages needed for one /// input package, as the debug info for that input package can be /// split across several debuginfo packages. /// /// @return the debug info packages associated to the current /// package. const vector& debug_info_packages() const {return debug_info_packages_;} /// Getter for the debug info packages associated to the current /// package. /// /// There can indeed be several debug info packages needed for one /// input package, as the debug info for that input package can be /// split across several debuginfo packages. /// /// @return the debug info packages associated to the current /// package. vector& debug_info_packages() {return debug_info_packages_;} /// Setter for the debug info packages associated to the current /// package. /// /// There can indeed be several debug info packages needed for one /// input package, as the debug info for that input package can be /// split across several debuginfo packages. /// /// @param p the new debug info package. void debug_info_packages(const vector &p) {debug_info_packages_ = p;} /// Getter for the devel package associated to the current package. /// /// @return the devel package associated to the current package. const package_sptr& devel_package() const {return devel_package_;} /// Setter of the devel package associated to the current package. /// /// @param p the new devel package associated to the current package. void devel_package(const package_sptr& p) {devel_package_ = p;} /// Getter of the associated kernel abi whitelist package, if any. /// /// @return the associated kernel abi whitelist package. const package_sptr kabi_whitelist_package() const {return kabi_whitelist_package_;} /// Setter of the associated kernel abi whitelist package. /// /// @param p the new kernel abi whitelist package. void kabi_whitelist_package(const package_sptr& p) {kabi_whitelist_package_ = p;} /// Getter of the path to the elf files of the package. /// /// @return the path tothe elf files of the package. const vector& elf_file_paths() const {return elf_file_paths_;} /// Getter of the path to the elf files of the package. /// /// @return the path tothe elf files of the package. vector& elf_file_paths() {return elf_file_paths_;} /// Getter of the SONAMEs of the public DSOs carried by this /// package. /// /// This is relevant only if the --private-dso option was *NOT* /// provided. /// /// @return the SONAMEs of the public DSOs carried by this package. const set& public_dso_sonames() const {return public_dso_sonames_;} /// Getter of the SONAMEs of the public DSOs carried by this /// package. /// /// This is relevant only if the --private-dso option was *NOT* /// provided. /// /// @return the SONAMEs of the public DSOs carried by this package. set& public_dso_sonames() {return public_dso_sonames_;} /// Convert the absolute path of an element of this package into a /// path relative to the root path pointing to this package. /// /// That is, suppose the content of a package named 'pkg' is located /// at /root/path/pkg. Suppose an element of that package is named /// is at '/root/path/pkg/somewhere/inside/element'. /// /// This function will return the path: /// /pkg/somewhere/inside/element. /// /// @param path the path to consider. /// /// @param converted_path the resulting converted path. This is set /// iff the function returns true. /// /// @return true if the path could be converted to being relative to /// the extracted directory. bool convert_path_to_relative(const string& path, string& converted_path) const { string root = extracted_dir_path_; real_path(root, root); string p = path; real_path(p, p); return string_suffix(p, root, converted_path); } // Convert the absolute path of an element of this package into a // path relative to the prefix common to the paths of all elements // of the package. // // @param path the path to conver. // // @param converted_path the resulting converted path. This is set // iff the function returns true. // // @return true iff the function could successfully convert @p path // and put the result into @p converted_path. bool convert_path_to_unique_suffix(const string& path, string& converted_path) const {return string_suffix(path, common_paths_prefix(), converted_path);} /// Retrieve the set of "interesting" package element paths by /// walking the package. /// /// And then compute the path prefix that is common to all the /// collected elements. /// /// @param the options of this application. void load_elf_file_paths(options& opts) { if (!common_paths_prefix().empty() || !elf_file_paths().empty()) // We have already loaded the elf file paths, don't do it again. return; get_interesting_files_under_dir(extracted_dir_path(), /*file_name_to_look_for=*/"", opts, elf_file_paths()); std::sort(elf_file_paths().begin(), elf_file_paths().end()); string common_prefix; sorted_strings_common_prefix(elf_file_paths(), common_paths_prefix()); } /// Create the path of an ABI file to be associated with a given /// binary. /// /// @param elf_file_path the path to the binary to consider. /// /// @param abi_file_path the resulting ABI file path. This is set /// iff the function return true. /// /// @return true if the ABI file path could be constructed and the /// directory tree containing it could be created. In that case, /// the resulting ABI file path is set to the @p abi_file_path /// output parameter. bool create_abi_file_path(const string &elf_file_path, string &abi_file_path) const { string abi_path, dir, parent; if (!abigail::tools_utils::string_suffix(elf_file_path, extracted_dir_path(), abi_path)) return false; abi_path = extracted_dir_path() + "/abixml" + abi_path + ".abi"; if (!abigail::tools_utils::ensure_parent_dir_created(abi_path)) return false; abi_file_path = abi_path; return true; } /// Erase the content of the temporary extraction directory that has /// been populated by the @ref extract_package() function; /// /// @param opts the options passed to the current program. void erase_extraction_directory(const options &opts) const { if (type() == abigail::tools_utils::FILE_TYPE_DIR) // If we are comparing two directories, do not erase the // directory as it was provided by the user; it's not a // temporary directory we created ourselves. return; if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Erasing temporary extraction directory " << extracted_dir_path() << " ..."; string cmd = "rm -rf " + extracted_dir_path(); if (system(cmd.c_str())) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " FAILED\n"; } else { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " DONE\n"; } } /// Erase the content of all the temporary extraction directories. /// /// @param opts the options passed to the current program. void erase_extraction_directories(const options &opts) const { erase_extraction_directory(opts); if (!debug_info_packages().empty()) debug_info_packages().front()->erase_extraction_directory(opts); if (devel_package()) devel_package()->erase_extraction_directory(opts); if (kabi_whitelist_package()) kabi_whitelist_package()->erase_extraction_directory(opts); } }; // end class package. /// Arguments passed to the comparison tasks. struct compare_args { const elf_file elf1; const string& debug_dir1; const suppressions_type private_types_suppr1; const elf_file elf2; const string& debug_dir2; const suppressions_type private_types_suppr2; const options& opts; /// Constructor for compare_args, which is used to pass /// information to the comparison threads. /// /// @param elf1 the first elf file to consider. /// /// @param debug_dir1 the directory where the debug info file for @p /// elf1 is stored. /// /// @param elf2 the second elf file to consider. /// /// @param debug_dir2 the directory where the debug info file for @p /// elf2 is stored. /// /// @param opts the options the current program has been called with. compare_args(const elf_file &elf1, const string& debug_dir1, const suppressions_type& priv_types_suppr1, const elf_file &elf2, const string& debug_dir2, const suppressions_type& priv_types_suppr2, const options& opts) : elf1(elf1), debug_dir1(debug_dir1), private_types_suppr1(priv_types_suppr1), elf2(elf2), debug_dir2(debug_dir2), private_types_suppr2(priv_types_suppr2), opts(opts) {} }; // end struct compare_args /// A convenience typedef for arguments passed to the comparison workers. typedef shared_ptr compare_args_sptr; static bool extract_package_and_map_its_content(const package_sptr &pkg, options &opts); /// Getter for the path to the parent directory under which packages /// extracted by the current thread are placed. /// /// @return the path to the parent directory under which packages /// extracted by the current thread are placed. const string& package::extracted_packages_parent_dir() { // I tried to declare this in thread-local storage, but GCC 4.4.7 // won't let me. So for now, I am just making it static. I'll deal // with this later when I have to. //static __thread string p; static string p; if (p.empty()) { const char *cachedir = getenv("XDG_CACHE_HOME"); if (cachedir != NULL) p = cachedir; else { const char* s = getenv("HOME"); if (s != NULL) p = s; if (p.empty()) { s = getenv("TMPDIR"); if (s != NULL) p = s; else p = "/tmp"; } p += "/.cache/libabigail"; } // Create the cache directory if it doesn't exist ABG_ASSERT(ensure_dir_path_created(p)); string libabigail_tmp_dir_template = p; libabigail_tmp_dir_template += "/abipkgdiff-tmp-dir-XXXXXX"; if (!mkdtemp(const_cast(libabigail_tmp_dir_template.c_str()))) abort(); p = libabigail_tmp_dir_template; } return p; } /// A convenience typedef for shared_ptr of package. typedef shared_ptr package_sptr; /// Show the usage of this program. /// /// @param prog_name the name of the program. /// /// @param out the output stream to emit the usage to . static void display_usage(const string& prog_name, ostream& out) { emit_prefix(prog_name, out) << "usage: " << prog_name << " [options] \n" << " where options can be:\n" << " --debug-info-pkg1|--d1 path of debug-info package of package1\n" << " --debug-info-pkg2|--d2 path of debug-info package of package2\n" << " --devel-pkg1|--devel1 path of devel package of pakage1\n" << " --devel-pkg2|--devel2 path of devel package of pakage1\n" << " --drop-private-types drop private types from " "internal representation\n" << " --no-default-suppression don't load any default " "suppression specifications\n" << " --suppressions|--suppr specify supression specification path\n" << " --linux-kernel-abi-whitelist|-w path to a " "linux kernel abi whitelist\n" << " --wp path to a linux kernel abi whitelist package\n" << " --keep-tmp-files don't erase created temporary files\n" << " --dso-only compare shared libraries only\n" << " --private-dso compare DSOs that are private " "to the package as well\n" << " --leaf-changes-only|-l only show leaf changes, " "so no change impact analysis (implies --redundant)\n" << " --impacted-interfaces|-i display interfaces impacted by leaf changes\n" << " --full-impact|-f when comparing kernel packages, show the " "full impact analysis report rather than the default leaf changes reports\n" << " --non-reachable-types|-t consider types non reachable" " from public interfaces\n" << " --no-linkage-name do not display linkage names of " "added/removed/changed\n" << " --redundant display redundant changes\n" << " --harmless display the harmless changes\n" << " --no-show-locs do not show location information\n" << " --show-bytes show size and offsets in bytes\n" << " --show-bits show size and offsets in bits\n" << " --show-hex show size and offset in hexadecimal\n" << " --show-dec show size and offset in decimal\n" << " --no-show-relative-offset-changes do not show relative" " offset changes\n" << " --no-added-syms do not display added functions or variables\n" << " --no-unreferenced-symbols do not display changes " "about symbols not referenced by debug info\n" << " --no-added-binaries do not display added binaries\n" << " --no-abignore do not look for *.abignore files\n" << " --no-parallel do not execute in parallel\n" << " --fail-no-dbg fail if no debug info was found\n" << " --show-identical-binaries show the names of identical binaries\n" << " --verbose emit verbose progress messages\n" << " --self-check perform a sanity check by comparing " "binaries inside the input package against their ABIXML representation\n" << " --help|-h display this help message\n" << " --version|-v display program version information" " and exit\n"; } #ifdef WITH_RPM /// Extract an RPM package. /// /// @param package_path the path to the package to extract. /// /// @param extracted_package_dir_path the path where to extract the /// package to. /// /// @param opts the options passed to the current program. /// /// @return true upon successful completion, false otherwise. static bool extract_rpm(const string& package_path, const string& extracted_package_dir_path, const options &opts) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Extracting package " << package_path << " to " << extracted_package_dir_path << " ..."; string cmd = "test -d " + extracted_package_dir_path + " || mkdir -p " + extracted_package_dir_path + " ; cd " + extracted_package_dir_path + " && rpm2cpio " + package_path + " | cpio -dium --quiet"; if (system(cmd.c_str())) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " FAILED\n"; return false; } if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " DONE\n"; return true; } #endif // WITH_RPM #ifdef WITH_DEB /// Extract a Debian binary package. /// /// @param package_path the path to the package to extract. /// /// @param extracted_package_dir_path the path where to extract the /// package to. /// /// @param opts the options passed to the current program. /// /// @return true upon successful completion, false otherwise. static bool extract_deb(const string& package_path, const string& extracted_package_dir_path, const options &opts) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Extracting package " << package_path << " to " << extracted_package_dir_path << " ...\n"; string cmd = "mkdir -p " + extracted_package_dir_path + " && dpkg -x " + package_path + " " + extracted_package_dir_path; if (system(cmd.c_str())) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " FAILED\n"; return false; } if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " DONE\n"; return true; } #endif // WITH_DEB #ifdef WITH_TAR /// Extract a GNU Tar archive. /// /// @param package_path the path to the archive to extract. /// /// @param extracted_package_dir_path the path where to extract the /// archive to. /// /// @param opts the options passed to the current program. /// /// @return true upon successful completion, false otherwise. static bool extract_tar(const string& package_path, const string& extracted_package_dir_path, const options &opts) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Extracting tar archive " << package_path << " to " << extracted_package_dir_path << " ..."; string cmd = "test -d " + extracted_package_dir_path + " && rm -rf " + extracted_package_dir_path; if (system(cmd.c_str())) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "command " << cmd << " FAILED\n"; } cmd = "mkdir -p " + extracted_package_dir_path + " && cd " + extracted_package_dir_path + " && tar -xf " + package_path; if (system(cmd.c_str())) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " FAILED\n"; return false; } if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " DONE\n"; return true; } #endif // WITH_TAR /// Erase the temporary directories created for the extraction of two /// packages. /// /// @param first_package the first package to consider. /// /// @param opts the options passed to the current program. /// /// @param second_package the second package to consider. static void erase_created_temporary_directories(const package& first_package, const package& second_package, const options &opts) { first_package.erase_extraction_directories(opts); second_package.erase_extraction_directories(opts); } /// Erase the root of all the temporary directories created by the /// current thread. static void erase_created_temporary_directories_parent(const options &opts) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Erasing temporary extraction parent directory " << package::extracted_packages_parent_dir() << " ..."; string cmd = "rm -rf " + package::extracted_packages_parent_dir(); if (system(cmd.c_str())) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "FAILED\n"; } else { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "DONE\n"; } } /// Extract the content of a package. /// /// @param package the package we are looking at. /// /// @param opts the options passed to the current program. static bool extract_package(const package& package, const options &opts) { switch(package.type()) { case abigail::tools_utils::FILE_TYPE_RPM: #ifdef WITH_RPM if (!extract_rpm(package.path(), package.extracted_dir_path(), opts)) { emit_prefix("abipkgdiff", cerr) << "Error while extracting package " << package.path() << "\n"; return false; } return true; #else emit_prefix("abipkgdiff", cerr) << "Support for rpm hasn't been enabled. Please consider " "enabling it at package configure time\n"; return false; #endif // WITH_RPM break; case abigail::tools_utils::FILE_TYPE_DEB: #ifdef WITH_DEB if (!extract_deb(package.path(), package.extracted_dir_path(), opts)) { emit_prefix("abipkgdiff", cerr) << "Error while extracting package" << package.path() << "\n"; return false; } return true; #else emit_prefix("abipkgdiff", cerr) << "Support for deb hasn't been enabled. Please consider " "enabling it at package configure time\n"; return false; #endif // WITH_DEB break; case abigail::tools_utils::FILE_TYPE_DIR: // The input package is just a directory that contains binaries, // there is nothing to extract. break; case abigail::tools_utils::FILE_TYPE_TAR: #ifdef WITH_TAR if (!extract_tar(package.path(), package.extracted_dir_path(), opts)) { emit_prefix("abipkgdiff", cerr) << "Error while extracting GNU tar archive " << package.path() << "\n"; return false; } return true; #else emit_prefix("abipkgdiff", cerr) << "Support for GNU tar hasn't been enabled. Please consider " "enabling it at package configure time\n"; return false; #endif // WITH_TAR break; default: return false; } return true; } /// Check that the suppression specification files supplied are /// present. If not, emit an error on stderr. /// /// @param opts the options instance to use. /// /// @return true if all suppression specification files are present, /// false otherwise. static bool maybe_check_suppression_files(const options& opts) { for (vector::const_iterator i = opts.suppression_paths.begin(); i != opts.suppression_paths.end(); ++i) if (!check_file(*i, cerr, opts.prog_name)) return false; for (vector::const_iterator i = opts.kabi_whitelist_paths.begin(); i != opts.kabi_whitelist_paths.end(); ++i) if (!check_file(*i, cerr, "abidiff")) return false; return true; } /// Update the diff context from the @ref options data structure. /// /// @param ctxt the diff context to update. /// /// @param opts the instance of @ref options to consider. static void set_diff_context_from_opts(diff_context_sptr ctxt, const options& opts) { ctxt->default_output_stream(&cout); ctxt->error_output_stream(&cerr); // See comment in abidiff.cc's set_diff_context_from_opts. ctxt->show_redundant_changes(opts.show_redundant_changes || opts.leaf_changes_only); ctxt->show_leaf_changes_only(opts.leaf_changes_only); ctxt->show_impacted_interfaces(opts.show_impacted_interfaces); ctxt->show_unreachable_types(opts.show_all_types); ctxt->show_hex_values(opts.show_hexadecimal_values); ctxt->show_offsets_sizes_in_bits(opts.show_offsets_sizes_in_bits); ctxt->show_relative_offset_changes(opts.show_relative_offset_changes); ctxt->show_locs(opts.show_locs); ctxt->show_linkage_names(opts.show_linkage_names); ctxt->show_added_fns(opts.show_added_syms); ctxt->show_added_vars(opts.show_added_syms); ctxt->show_added_symbols_unreferenced_by_debug_info (opts.show_added_syms); ctxt->show_symbols_unreferenced_by_debug_info (opts.show_symbols_not_referenced_by_debug_info); if (!opts.show_harmless_changes) ctxt->switch_categories_off(get_default_harmless_categories_bitmap()); suppressions_type supprs; for (vector::const_iterator i = opts.suppression_paths.begin(); i != opts.suppression_paths.end(); ++i) read_suppressions(*i, supprs); ctxt->add_suppressions(supprs); } /// Compare the ABI two elf files, using their associated debug info. /// /// The result of the comparison is emitted to standard output. /// /// @param elf1 the first elf file to consider. /// /// @param debug_dir1 the directory where the debug info file for @p /// elf1 is stored. /// The result of the comparison is saved to a global corpus map. /// /// @param elf2 the second eld file to consider. /// @args the list of argument sets used for comparison /// /// @param debug_dir2 the directory where the debug info file for @p /// elf2 is stored. /// /// @param opts the options the current program has been called with. /// /// @param env the environment encapsulating the entire comparison. /// /// @param diff the shared pointer to be set to the result of the comparison. /// /// @param detailed_error_status is this pointer is non-null and if /// the function returns ABIDIFF_ERROR, then the function sets the /// pointed-to parameter to the abigail::dwarf_reader::status value /// that gives details about the rror. /// /// @return the status of the comparison. static abidiff_status compare(const elf_file& elf1, const string& debug_dir1, const suppressions_type& priv_types_supprs1, const elf_file& elf2, const string& debug_dir2, const suppressions_type& priv_types_supprs2, const options& opts, abigail::ir::environment_sptr &env, corpus_diff_sptr &diff, diff_context_sptr &ctxt, abigail::dwarf_reader::status *detailed_error_status = 0) { char *di_dir1 = (char*) debug_dir1.c_str(), *di_dir2 = (char*) debug_dir2.c_str(); vector di_dirs1, di_dirs2; di_dirs1.push_back(&di_dir1); di_dirs2.push_back(&di_dir2); if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Comparing the ABIs of file " << elf1.path << " and " << elf2.path << "...\n"; abigail::dwarf_reader::status c1_status = abigail::dwarf_reader::STATUS_OK, c2_status = abigail::dwarf_reader::STATUS_OK; ctxt.reset(new diff_context); set_diff_context_from_opts(ctxt, opts); suppressions_type& supprs = ctxt->suppressions(); bool files_suppressed = (file_is_suppressed(elf1.path, supprs) ||file_is_suppressed(elf2.path, supprs)); if (files_suppressed) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " input file " << elf1.path << " or " << elf2.path << " has been suppressed by a suppression specification.\n" << " Not reading any of them\n"; return abigail::tools_utils::ABIDIFF_OK; } // Add the first private type suppressions set to the set of // suppressions. for (suppressions_type::const_iterator i = priv_types_supprs1.begin(); i != priv_types_supprs1.end(); ++i) supprs.push_back(*i); // Add the second private type suppressions set to the set of // suppressions. for (suppressions_type::const_iterator i = priv_types_supprs2.begin(); i != priv_types_supprs2.end(); ++i) supprs.push_back(*i); if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Reading file " << elf1.path << " ...\n"; corpus_sptr corpus1; { read_context_sptr c = create_read_context(elf1.path, di_dirs1, env.get(), /*load_all_types=*/opts.show_all_types); add_read_context_suppressions(*c, priv_types_supprs1); if (!opts.kabi_suppressions.empty()) add_read_context_suppressions(*c, opts.kabi_suppressions); corpus1 = read_corpus_from_elf(*c, c1_status); bool bail_out = false; if (!(c1_status & abigail::dwarf_reader::STATUS_OK)) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Could not read file '" << elf1.path << "' properly\n"; if (detailed_error_status) *detailed_error_status = c1_status; bail_out = true; } if (opts.fail_if_no_debug_info) { bool debug_info_error = false; if (c1_status & abigail::dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "while reading file" << elf1.path << "\n"; emit_prefix("abipkgdiff", cerr) << "Could not find debug info file"; if (di_dir1 && strcmp(di_dir1, "")) cerr << " under " << di_dir1 << "\n"; else cerr << "\n"; if (detailed_error_status) *detailed_error_status = c1_status; debug_info_error = true; } if (c1_status & abigail::dwarf_reader::STATUS_ALT_DEBUG_INFO_NOT_FOUND) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "while reading file" << elf1.path << "\n"; emit_prefix("abipkgdiff", cerr) << "Could not find alternate debug info file"; string alt_di_path; abigail::dwarf_reader::refers_to_alt_debug_info(*c, alt_di_path); if (!alt_di_path.empty()) cerr << ": " << alt_di_path << "\n"; else cerr << "\n"; if (detailed_error_status) *detailed_error_status = c1_status; debug_info_error = true; } if (debug_info_error) bail_out = true; } if (bail_out) return abigail::tools_utils::ABIDIFF_ERROR; } if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "DONE reading file " << elf1.path << "\n"; if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Reading file " << elf2.path << " ...\n"; corpus_sptr corpus2; { read_context_sptr c = create_read_context(elf2.path, di_dirs2, env.get(), /*load_all_types=*/opts.show_all_types); add_read_context_suppressions(*c, priv_types_supprs2); if (!opts.kabi_suppressions.empty()) add_read_context_suppressions(*c, opts.kabi_suppressions); corpus2 = read_corpus_from_elf(*c, c2_status); bool bail_out = false; if (!(c2_status & abigail::dwarf_reader::STATUS_OK)) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Could not find the read file '" << elf2.path << "' properly\n"; if (detailed_error_status) *detailed_error_status = c2_status; bail_out = true; } if (opts.fail_if_no_debug_info) { bool debug_info_error = false; if (c2_status & abigail::dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "while reading file" << elf2.path << "\n"; emit_prefix("abipkgdiff", cerr) << "Could not find debug info file"; if (di_dir2 && strcmp(di_dir2, "")) cerr << " under " << di_dir2 << "\n"; else cerr << "\n"; if (detailed_error_status) *detailed_error_status = c2_status; debug_info_error = true; } if (c2_status & abigail::dwarf_reader::STATUS_ALT_DEBUG_INFO_NOT_FOUND) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "while reading file" << elf2.path << "\n"; emit_prefix("abipkgdiff", cerr) << "Could not find alternate debug info file"; string alt_di_path; abigail::dwarf_reader::refers_to_alt_debug_info(*c, alt_di_path); if (!alt_di_path.empty()) cerr << ": " << alt_di_path << "\n"; else cerr << "\n"; if (detailed_error_status) *detailed_error_status = c2_status; debug_info_error = true; } if (debug_info_error) bail_out = true; } if (bail_out) return abigail::tools_utils::ABIDIFF_ERROR; } if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " DONE reading file " << elf2.path << "\n"; if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " Comparing the ABIs of: \n" << " " << elf1.path << "\n" << " " << elf2.path << "\n"; diff = compute_diff(corpus1, corpus2, ctxt); if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Comparing the ABIs of file " << elf1.path << " and " << elf2.path << " is DONE\n"; abidiff_status s = abigail::tools_utils::ABIDIFF_OK; if (diff->has_net_changes()) s |= abigail::tools_utils::ABIDIFF_ABI_CHANGE; if (diff->has_incompatible_changes()) s |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE; return s; } /// Compare an ELF file to its ABIXML representation. /// /// @param elf the ELF file to compare. /// /// @param debug_dir the debug directory of the ELF file. /// /// @param opts the options passed the user. /// /// @param env the environment to use for the comparison. /// /// @param diff the diff object resulting from the comparison of @p /// elf against its ABIXML representation. /// /// @param ctxt the resulting diff context used for the comparison /// that yielded @p diff. /// /// @param detailed_error_status the detailed error satus returned by /// this function. /// /// @return the status of the self comparison. static abidiff_status compare_to_self(const elf_file& elf, const string& debug_dir, const options& opts, abigail::ir::environment_sptr &env, corpus_diff_sptr &diff, diff_context_sptr &ctxt, abigail::dwarf_reader::status *detailed_error_status = 0) { char *di_dir = (char*) debug_dir.c_str(); vector di_dirs; di_dirs.push_back(&di_dir); abigail::dwarf_reader::status c_status = abigail::dwarf_reader::STATUS_OK; if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Comparing the ABI of file '" << elf.path << "' against itself ...\n"; if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Reading file " << elf.path << " ...\n"; corpus_sptr corp; { read_context_sptr c = create_read_context(elf.path, di_dirs, env.get(), /*read_all_types=*/opts.show_all_types); corp = read_corpus_from_elf(*c, c_status); if (!(c_status & abigail::dwarf_reader::STATUS_OK)) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Could not read file '" << elf.path << "' propertly\n"; if (detailed_error_status) *detailed_error_status = c_status; return abigail::tools_utils::ABIDIFF_ERROR; } if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Read file '" << elf.path << "' OK\n"; ABG_ASSERT(corp); } corpus_sptr reread_corp; string abi_file_path; { if (!opts.pkg1->create_abi_file_path(elf.path, abi_file_path)) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Could not create the directory tree to store the abi for '" << elf.path << "'\n"; } ofstream of(abi_file_path.c_str(), std::ios_base::trunc); { const abigail::xml_writer::write_context_sptr c = abigail::xml_writer::create_write_context(env.get(), of); if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Writting ABIXML file '" << abi_file_path << "' ...\n"; if (!write_corpus(*c, corp, 0)) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Could not write the ABIXML file to '" << abi_file_path << "'\n"; return abigail::tools_utils::ABIDIFF_ERROR; } of.flush(); of.close(); if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Wrote ABIXML file '" << abi_file_path << "' OK\n"; } { abigail::xml_reader::read_context_sptr c = abigail::xml_reader::create_native_xml_read_context(abi_file_path, env.get()); if (!c) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Could not create read context for ABIXML file '" << abi_file_path << "'\n"; return abigail::tools_utils::ABIDIFF_ERROR; } if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Reading ABIXML file '" << abi_file_path << "' ...\n"; reread_corp = read_corpus_from_input(*c); if (!reread_corp) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Could not read temporary ABIXML file '" << abi_file_path << "'\n"; return abigail::tools_utils::ABIDIFF_ERROR; } if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Read file '" << abi_file_path << "' OK\n"; } } ctxt.reset(new diff_context); set_diff_context_from_opts(ctxt, opts); if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Comparing the ABIs of: \n" << " '" << corp->get_path() << "' against \n" << " '" << abi_file_path << "'...\n"; diff = compute_diff(corp, reread_corp, ctxt); if (opts.verbose) emit_prefix("abipkgdfiff", cerr) << "... Comparing the ABIs: DONE\n"; abidiff_status s = abigail::tools_utils::ABIDIFF_OK; if (diff->has_changes()) s |= abigail::tools_utils::ABIDIFF_ABI_CHANGE; if (diff->has_incompatible_changes()) s |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE; if (opts.verbose) emit_prefix("abipkgdfiff", cerr) << "Comparison against self " << (s == abigail::tools_utils::ABIDIFF_OK ? "SUCCEEDED" : "FAILED") << '\n'; return s; } /// If devel packages were associated to the main package we are /// looking at, use the names of the header files (extracted from the /// package) to generate suppression specification to filter out types /// that are not defined in those header files. /// /// Filtering out types not defined in publi headers amounts to filter /// out types that are deemed private to the package we are looking /// at. /// /// If the function succeeds, it returns a non-empty vector of /// suppression specifications. /// /// @param pkg the main package we are looking at. /// /// @param opts the options of the current program. /// /// @return a vector of suppression_sptr. If no suppressions /// specification were constructed, the returned vector is empty. static suppressions_type create_private_types_suppressions(const package& pkg, const options &opts) { suppressions_type supprs; package_sptr devel_pkg = pkg.devel_package(); if (!devel_pkg || !file_exists(devel_pkg->extracted_dir_path()) || !is_dir(devel_pkg->extracted_dir_path())) return supprs; string headers_path = devel_pkg->extracted_dir_path(); if (devel_pkg->type() == abigail::tools_utils::FILE_TYPE_RPM ||devel_pkg->type() == abigail::tools_utils::FILE_TYPE_DEB) // For RPM and DEB packages, header files are under the // /usr/include sub-directories. headers_path += "/usr/include"; if (!is_dir(headers_path)) return supprs; suppression_sptr suppr = gen_suppr_spec_from_headers(headers_path); if (suppr) { if (opts.drop_private_types) suppr->set_drops_artifact_from_ir(true); supprs.push_back(suppr); } return supprs; } /// If the user wants to avoid comparing DSOs that are private to this /// package, then we build the set of public DSOs as advertised in the /// package's "provides" property. /// /// Note that at the moment this function only works for RPMs. It /// doesn't yet support other packaging formats. /// /// @param pkg the package to consider. /// /// @param opts the options of this program. /// /// @return true iff the set of public DSOs was built. static bool maybe_create_public_dso_sonames_set(package& pkg, const options &opts) { if (opts.compare_private_dsos || !pkg.public_dso_sonames().empty()) return false; if (pkg.type() == abigail::tools_utils::FILE_TYPE_RPM) return get_dsos_provided_by_rpm(pkg.path(), pkg.public_dso_sonames()); // We don't support this yet for non-RPM packages. return false; } /// Test if we should only compare the public DSOs of a given package. /// /// @param pkg the package to consider. /// /// @param opts the options of this program static bool must_compare_public_dso_only(package& pkg, options& opts) { if (pkg.type() == abigail::tools_utils::FILE_TYPE_RPM && !opts.compare_private_dsos) return true; return false; } /// While walking a file directory, check if a directory entry is a /// kabi whitelist of a particular architecture. /// /// If it is, then save its file path in a vector of whitelists. /// /// @param entry the directory entry to consider. /// /// @param arch the architecture to consider. /// /// @param whitelists out parameter. If @p entry is the whitelist we /// are looking for, add its path to this output parameter. static void maybe_collect_kabi_whitelists(const FTSENT *entry, const string arch, vector &whitelists) { if (entry == NULL || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL) || entry->fts_info == FTS_ERR || entry->fts_info == FTS_NS) return; string path = entry->fts_path; maybe_get_symlink_target_file_path(path, path); string kabi_whitelist_name = "kabi_whitelist_" + arch; if (string_ends_with(path, kabi_whitelist_name)) whitelists.push_back(path); } /// Get the kabi whitelist for a particular architecture under a given /// directory. /// /// @param dir the directory to look at. /// /// @param arch the architecture to consider. /// /// @param whitelist_paths the vector where to add the whitelists /// found. Note that a whitelist is added to this parameter iff the /// function returns true. /// /// @return true iff the function found a whitelist at least. static bool get_kabi_whitelists_from_arch_under_dir(const string& dir, const string& arch, vector& whitelist_paths) { bool is_ok = false; char* paths[] = {const_cast(dir.c_str()), 0}; FTS *file_hierarchy = fts_open(paths, FTS_LOGICAL|FTS_NOCHDIR, NULL); if (!file_hierarchy) return is_ok; FTSENT *entry; while ((entry = fts_read(file_hierarchy))) maybe_collect_kabi_whitelists(entry, arch, whitelist_paths); fts_close(file_hierarchy); return true; } /// Find a kabi whitelist in a linux kernel RPM package. /// /// Note that the linux kernel RPM package must have been extracted /// somewhere already. /// /// This function then looks for the whitelist under the /lib/modules /// directory inside the extracted content of the package. If it /// finds it and saves its file path in the /// options::kabi_whitelist_paths data member. /// /// @param pkg the linux kernel package to consider. /// /// @param opts the options the program was invoked with. static bool maybe_handle_kabi_whitelist_pkg(const package& pkg, options &opts) { if (opts.kabi_whitelist_packages.empty() || !opts.kabi_whitelist_paths.empty() || !pkg.kabi_whitelist_package()) return false; if (pkg.type() != abigail::tools_utils::FILE_TYPE_RPM) return false; string pkg_name = pkg.base_name(); bool is_linux_kernel_package = file_is_kernel_package(pkg_name, pkg.type()); if (!is_linux_kernel_package) return false; package_sptr kabi_wl_pkg = pkg.kabi_whitelist_package(); assert(kabi_wl_pkg); if (!file_exists(kabi_wl_pkg->extracted_dir_path()) || !is_dir(kabi_wl_pkg->extracted_dir_path())) return false; string rpm_arch; if (!get_rpm_arch(pkg_name, rpm_arch)) return false; string kabi_wl_path = kabi_wl_pkg->extracted_dir_path(); kabi_wl_path += "/lib/modules"; vector whitelist_paths; get_kabi_whitelists_from_arch_under_dir(kabi_wl_path, rpm_arch, whitelist_paths); if (!whitelist_paths.empty()) { std::sort(whitelist_paths.begin(), whitelist_paths.end()); opts.kabi_whitelist_paths.push_back(whitelist_paths.back()); } return true; } /// The task that performs the extraction of the content of several /// packages into a temporary directory. /// /// If this task has several packages to extract, then it extracts /// them in sequence. /// /// Note that several instances of tasks can perform their jobs (i.e /// extract packages in sequence) in parallel. class pkg_extraction_task : public task { pkg_extraction_task(); public: vector pkgs; const options &opts; bool is_ok; pkg_extraction_task(const package_sptr &p, const options &o) : opts(o), is_ok(true) {pkgs.push_back(p);} pkg_extraction_task(const vector &packages, const options &o) : pkgs(packages), opts(o), is_ok(true) {} /// The job performed by the current task, which is to extract its /// packages in sequence. This job is to be performed in parallel /// with other jobs of other tasks. virtual void perform() { for (vector::const_iterator p = pkgs.begin(); p != pkgs.end(); ++p) is_ok &= extract_package(**p, opts); } }; //end class pkg_extraction_task /// A convenience typedef for a shared pointer to @f pkg_extraction_task. typedef shared_ptr pkg_extraction_task_sptr; /// The worker task which job is to prepares a package. /// /// Preparing a package means: /// /// 1/ Extract the package and its ancillary packages. /// /// 2/ Analyze the extracted content, map that content so that we /// determine what the ELF files to be analyze are. class pkg_prepare_task : public abigail::workers::task { pkg_prepare_task(); public: package_sptr pkg; options &opts; bool is_ok; pkg_prepare_task(package_sptr &p, options &o) : pkg(p), opts(o), is_ok(false) {} /// The job performed by this task. virtual void perform() { is_ok = pkg && extract_package_and_map_its_content(pkg, opts); } }; //end class pkg_prepare_task /// A convenience typedef for a shared_ptr to @ref pkg_prepare_task typedef shared_ptr pkg_prepare_task_sptr; /// The worker task which job is to compare two ELF binaries class compare_task : public abigail::workers::task { public: compare_args_sptr args; abidiff_status status; ostringstream out; string pretty_output; compare_task() : status(abigail::tools_utils::ABIDIFF_OK) {} compare_task(const compare_args_sptr& a) : args(a), status(abigail::tools_utils::ABIDIFF_OK) {} /// The job performed by the task. /// /// This compares two ELF files, gets the resulting test report and /// stores it in an output stream. virtual void perform() { abigail::ir::environment_sptr env(new abigail::ir::environment); diff_context_sptr ctxt; corpus_diff_sptr diff; abigail::dwarf_reader::status detailed_status = abigail::dwarf_reader::STATUS_UNKNOWN; status |= compare(args->elf1, args->debug_dir1, args->private_types_suppr1, args->elf2, args->debug_dir2, args->private_types_suppr2, args->opts, env, diff, ctxt, &detailed_status); // If there is an ABI change, tell the user about it. if ((status & abigail::tools_utils::ABIDIFF_ABI_CHANGE) ||( diff && diff->has_net_changes())) { diff->report(out, /*prefix=*/" "); string name = args->elf1.name; pretty_output += string("================ changes of '") + name + "'===============\n" + out.str() + "================ end of changes of '" + name + "'===============\n\n"; } else { if (args->opts.show_identical_binaries) out << "No ABI change detected\n"; } // If an error happened while comparing the two binaries, tell the // user about it. if (status & abigail::tools_utils::ABIDIFF_ERROR) { string diagnostic = abigail::dwarf_reader::status_to_diagnostic_string(detailed_status); if (diagnostic.empty()) diagnostic = "Unknown error. Please run the tool again with --verbose\n"; string name = args->elf1.name; pretty_output += "==== Error happened during processing of '" + name + "' ====\n"; pretty_output += diagnostic; pretty_output += "==== End of error for '" + name + "' ====\n"; } } }; // end class compare_task /// Convenience typedef for a shared_ptr of @ref compare_task. typedef shared_ptr compare_task_sptr; /// The worker task which job is to compare an ELF binary to its ABI /// representation. class self_compare_task : public compare_task { public: self_compare_task(const compare_args_sptr& a) : compare_task(a) {} /// The job performed by the task. /// /// This compares an ELF file to its ABIXML representation and /// expects the result to be the empty set. virtual void perform() { abigail::ir::environment_sptr env(new abigail::ir::environment); diff_context_sptr ctxt; corpus_diff_sptr diff; abigail::dwarf_reader::status detailed_status = abigail::dwarf_reader::STATUS_UNKNOWN; status |= compare_to_self(args->elf1, args->debug_dir1, args->opts, env, diff, ctxt, &detailed_status); string name = args->elf1.name; if (status == abigail::tools_utils::ABIDIFF_OK) pretty_output += "==== SELF CHECK SUCCEEDED for '"+ name + "' ====\n"; else if ((status & abigail::tools_utils::ABIDIFF_ABI_CHANGE) ||( diff && diff->has_net_changes())) { // There is an ABI change, tell the user about it. diff->report(out, /*indent=*/" "); pretty_output += string("======== comparing'") + name + "' to itself wrongly yielded result: ===========\n" + out.str() + "===SELF CHECK FAILED for '"+ name + "'\n"; } // If an error happened while comparing the two binaries, tell the // user about it. if (status & abigail::tools_utils::ABIDIFF_ERROR) { string diagnostic = abigail::dwarf_reader::status_to_diagnostic_string(detailed_status); if (diagnostic.empty()) diagnostic = "Unknown error. Please run the tool again with --verbose\n"; string name = args->elf1.name; pretty_output += "==== Error happened during self check of '" + name + "' ====\n"; pretty_output += diagnostic; pretty_output += "==== SELF CHECK FAILED for '" + name + "' ====\n"; } } }; // end class self_compare /// Convenience typedef for a shared_ptr of @ref compare_task. typedef shared_ptr self_compare_task_sptr; /// This function is a sub-routine of create_maps_of_package_content. /// /// It's called during the walking of the directory tree containing /// the extracted content of package. It's called with an entry of /// that directory tree. /// /// Depending on the kind of file this function is called on, it /// updates the vector of paths of the directory and the set of /// suppression paths found. /// /// @param entry the directory entry to analyze. /// /// @param opts the options of the current program. /// /// @param file_name_to_look_for if this parameter is set, the /// function only looks for a file name which name is the same as the /// value of this parameter. /// /// @param paths out parameter. This is the set of meaningful paths /// of the current directory tree being analyzed. These paths are /// those that are going to be involved in ABI comparison. static void maybe_update_package_content(const FTSENT *entry, options &opts, const string& file_name_to_look_for, unordered_set& paths) { if (entry == NULL || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL) || entry->fts_info == FTS_ERR || entry->fts_info == FTS_NS) return; string path = entry->fts_path; maybe_get_symlink_target_file_path(path, path); if (!file_name_to_look_for.empty()) { string name; abigail::tools_utils::base_name(path, name); if (name == file_name_to_look_for) paths.insert(path); return; } if (guess_file_type(path) == abigail::tools_utils::FILE_TYPE_ELF) paths.insert(path); else if (opts.abignore && string_ends_with(path, ".abignore")) opts.suppression_paths.push_back(path); } /// Walk a given directory to collect files that are "interesting" to /// analyze. By default, "interesting" means interesting from either /// a kernel package or a userspace binary analysis point of view. /// /// @param dir the directory to walk. /// /// @param file_name_to_look_for if this parameter is set, only a file /// with this name is going to be collected. /// /// @param interesting_files out parameter. This parameter is /// populated with the interesting files found by the function iff the /// function returns true. /// /// @return true iff the function completed successfully. static bool get_interesting_files_under_dir(const string dir, const string& file_name_to_look_for, options& opts, vector& interesting_files) { bool is_ok = false; string root; real_path(dir, root); if (root.empty()) root = dir; char* paths[] = {const_cast(root.c_str()), 0}; FTS *file_hierarchy = fts_open(paths, FTS_LOGICAL|FTS_NOCHDIR, NULL); if (!file_hierarchy) return is_ok; FTSENT *entry; unordered_set files; while ((entry = fts_read(file_hierarchy))) maybe_update_package_content(entry, opts, file_name_to_look_for, files); for (unordered_set::const_iterator i = files.begin(); i != files.end(); ++i) interesting_files.push_back(*i); fts_close(file_hierarchy); is_ok = true; return is_ok; } /// Create maps of the content of a given package. /// /// The maps contain relevant metadata about the content of the /// files. These maps are used afterwards during the comparison of /// the content of the package. Note that the maps are stored in the /// object that represents that package. /// /// @param package the package to consider. /// /// @param opts the options the current program has been called with. /// /// @param true upon successful completion, false otherwise. static bool create_maps_of_package_content(package& package, options& opts) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Analyzing the content of package " << package.path() << " extracted to " << package.extracted_dir_path() << " ...\n"; bool is_ok = true; vector elf_file_paths; // if package is linux kernel package and its associated debug // info package looks like a kernel debuginfo package, then try to // go find the vmlinux file in that debug info file. string pkg_name = package.base_name(); bool is_linux_kernel_package = file_is_kernel_package(pkg_name, package.type()); if (is_linux_kernel_package) { // For a linux kernel package, no analysis is done. It'll be // done later at comparison time by // compare_prepared_linux_kernel_packages is_ok = true; if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " Analysis of " << package.path() << " DONE\n"; return is_ok; } is_ok &= get_interesting_files_under_dir(package.extracted_dir_path(), /*file_name_to_look_for=*/"", opts, elf_file_paths); if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "Found " << elf_file_paths.size() << " files in " << package.extracted_dir_path() << "\n"; // determine if all files have the same prefix. Compute that prefix // and stick it into the package! That prefix is going to be used // later by the package::convert_path_to_unique_suffix method. package.load_elf_file_paths(opts); maybe_create_public_dso_sonames_set(package, opts); for (vector::const_iterator file = elf_file_paths.begin(); file != elf_file_paths.end(); ++file) { elf_file_sptr e (new elf_file(*file)); if (opts.compare_dso_only) { if (e->type != abigail::dwarf_reader::ELF_TYPE_DSO) { if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "skipping non-DSO file " << e->path << "\n"; continue; } } else { if (e->type != abigail::dwarf_reader::ELF_TYPE_DSO && e->type != abigail::dwarf_reader::ELF_TYPE_EXEC && e->type != abigail::dwarf_reader::ELF_TYPE_PI_EXEC) { if (is_linux_kernel_package) { if (e->type == abigail::dwarf_reader::ELF_TYPE_RELOCATABLE) { // This is a Linux Kernel module. ; } } else if (opts.verbose) { emit_prefix("abipkgdiff", cerr) << "skipping non-DSO non-executable file " << e->path << "\n"; continue; } } } if (e->soname.empty()) { if (e->type == abigail::dwarf_reader::ELF_TYPE_DSO && must_compare_public_dso_only(package, opts)) { // We are instructed to compare public DSOs only. Yet // this DSO does not have a soname. so it can not be a // public DSO. Let's skip it. if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "DSO " << e->path << " does not have a soname so it's private. Skipping it\n"; continue; } // Several binaries at different paths can have the same // base name. So let's consider the full path of the binary // inside the extracted directory. string key = e->name; package.convert_path_to_unique_suffix(e->path, key); package.path_elf_file_sptr_map()[key] = e; if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "mapped binary with key '" << key << "'" << "\n"; } else { // Several binaries at different paths can have the same // soname. So let's *also* consider the full path of the // binary inside the extracted directory, not just the // soname. string key = e->soname; if (must_compare_public_dso_only(package, opts)) { if (package.public_dso_sonames().find(key) == package.public_dso_sonames().end()) { // We are instructed to compare public DSOs only and // this one seems to be private. So skip it. if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "DSO " << e->path << " of soname " << key << " seems to be private. Skipping it\n"; continue; } } if (package.convert_path_to_unique_suffix(e->path, key)) { dir_name(key, key); key += string("/@soname:") + e->soname; } package.path_elf_file_sptr_map()[key] = e; if (opts.verbose) emit_prefix("abipkgdiff", cerr) << "mapped binary with key '" << key << "'" << "\n"; } } if (opts.verbose) emit_prefix("abipkgdiff", cerr) << " Analysis of " << package.path() << " DONE\n"; is_ok = true; return is_ok; } /// Extract the content of a package (and its ancillary packages) and /// map its content. /// /// First, the content of the package and its ancillary packages are /// extracted, in parallel. /// /// Then, after that extraction is done, the content of the package if /// walked and analyzed. /// /// @param pkg the package to extract and to analyze. /// /// @param opts the options of the current program. /// /// @return true iff the extraction and analyzing went well. static bool extract_package_and_map_its_content(const package_sptr &pkg, options &opts) { assert(pkg); pkg_extraction_task_sptr main_pkg_extraction; pkg_extraction_task_sptr dbg_extraction; pkg_extraction_task_sptr devel_extraction; pkg_extraction_task_sptr kabi_whitelist_extraction; size_t NUM_EXTRACTIONS = 1; main_pkg_extraction.reset(new pkg_extraction_task(pkg, opts)); if (!pkg->debug_info_packages().empty()) { dbg_extraction.reset(new pkg_extraction_task(pkg->debug_info_packages(), opts)); ++NUM_EXTRACTIONS; } if (package_sptr devel_pkg = pkg->devel_package()) { devel_extraction.reset(new pkg_extraction_task(devel_pkg, opts)); ++NUM_EXTRACTIONS; } if (package_sptr kabi_wl_pkg = pkg->kabi_whitelist_package()) { kabi_whitelist_extraction.reset(new pkg_extraction_task(kabi_wl_pkg, opts)); ++NUM_EXTRACTIONS; } size_t num_workers = (opts.parallel ? std::min(opts.num_workers, NUM_EXTRACTIONS) : 1); abigail::workers::queue extraction_queue(num_workers); // Perform the extraction of the NUM_WORKERS packages in parallel. extraction_queue.schedule_task(dbg_extraction); extraction_queue.schedule_task(main_pkg_extraction); extraction_queue.schedule_task(devel_extraction); extraction_queue.schedule_task(kabi_whitelist_extraction); // Wait for the extraction to be done. extraction_queue.wait_for_workers_to_complete(); // Analyze and map the content of the extracted package. bool is_ok = false; if (main_pkg_extraction->is_ok) is_ok = create_maps_of_package_content(*pkg, opts); if (is_ok) maybe_handle_kabi_whitelist_pkg(*pkg, opts); return is_ok; } /// Extract the two packages (and their ancillary packages) and /// analyze their content, so that we later know what files from the /// first package to compare against what files from the second /// package. /// /// Note that preparing the first package and its ancillary packages /// happens in parallel with preparing the second package and its /// ancillary packages. The function then waits for the two /// preparations to complete before returning. /// /// @param first_package the first package to consider. /// /// @param second_package the second package to consider. /// /// @param opts the options of the current program. /// /// @return true iff the preparation went well. static bool prepare_packages(package_sptr &first_package, package_sptr &second_package, options &opts) { pkg_prepare_task_sptr first_pkg_prepare; pkg_prepare_task_sptr second_pkg_prepare; size_t NUM_PREPARATIONS = 2; first_pkg_prepare.reset(new pkg_prepare_task(first_package, opts)); second_pkg_prepare.reset(new pkg_prepare_task(second_package, opts)); size_t num_workers = (opts.parallel ? std::min(opts.num_workers, NUM_PREPARATIONS) : 1); abigail::workers::queue preparation_queue(num_workers); preparation_queue.schedule_task(first_pkg_prepare); preparation_queue.schedule_task(second_pkg_prepare); preparation_queue.wait_for_workers_to_complete(); return first_pkg_prepare->is_ok && second_pkg_prepare->is_ok; } /// Prepare one package for the sake of comparing it to its ABIXML /// representation. /// /// The preparation entails unpacking the content of the package into /// a temporary directory and mapping its content. /// /// @param pkg the package to prepare. /// /// @param opts the options provided by the user. /// /// @return true iff the preparation succeeded. static bool prepare_package(package_sptr& pkg, options &opts) {return extract_package_and_map_its_content(pkg, opts);} /// Compare the added sizes of an ELF pair (specified by a comparison /// task that compares two ELF files) against the added sizes of a /// second ELF pair. /// /// Larger filesize strongly raises the possibility of larger debug-info, /// hence longer diff time. For a package containing several relatively /// large and small ELFs, it is often more efficient to start working on /// the larger ones first. This function is used to order the pairs by /// size, starting from the largest. /// /// @param t1 the first comparison task that compares a pair of ELF /// files. /// /// @param t2 the second comparison task that compares a pair of ELF /// files. /// /// @return true if @p task1 is greater than @p task2. bool elf_size_is_greater(const task_sptr &task1, const task_sptr &task2) { compare_task_sptr t1 = dynamic_pointer_cast(task1); compare_task_sptr t2 = dynamic_pointer_cast(task2); ABG_ASSERT(t1->args && t2->args); off_t s1 = t1->args->elf1.size + t1->args->elf2.size; off_t s2 = t2->args->elf1.size + t2->args->elf2.size; if (s1 != s2) return s1 > s2; // The sizes of the compared binaries are the same. So sort them // lexicographically. return t1->args->elf1.name < t2->args->elf1.name; } /// This type is used to notify the calling thread that the comparison /// of two ELF files is done. class comparison_done_notify : public abigail::workers::queue::task_done_notify { comparison_done_notify(); public: abi_diff& diff; abidiff_status status; comparison_done_notify(abi_diff &d) : diff(d), status(abigail::tools_utils::ABIDIFF_OK) {} /// This operator is invoked by the worker queue whenever a /// comparison task is done. /// /// The operator collects the status of the job of the task and also /// updates the the count of binaries that have ABI changes. /// /// @param task_done the task that is done. virtual void operator()(const task_sptr& task_done) { compare_task_sptr comp_task = dynamic_pointer_cast(task_done); assert(comp_task); status |= comp_task->status; if (status != abigail::tools_utils::ABIDIFF_OK) { string name = comp_task->args->elf1.name; if (status & abigail::tools_utils::ABIDIFF_ABI_CHANGE) diff.changed_binaries.push_back(name); } } }; // end struct comparison_done_notify /// Erase the temporary directories that might have been created while /// handling two packages, unless the user asked to keep the temporary /// directories around. /// /// @param first_package the first package to consider. /// /// @param second_package the second package to consider. /// /// @param opts the options passed to the program. static void maybe_erase_temp_dirs(package& first_package, package& second_package, options& opts) { if (opts.keep_tmp_files) return; erase_created_temporary_directories(first_package, second_package, opts); erase_created_temporary_directories_parent(opts); } /// Compare the ABI of two prepared packages that contain userspace /// binaries. /// /// A prepared package is a package which content has been extracted /// and mapped. /// /// @param first_package the first package to consider. /// /// @param second_package the second package to consider. /// /// @param options the options the current program has been called /// with. /// /// @param diff out parameter. If this function returns true, then /// this parameter is set to the result of the comparison. /// /// @param opts the options of the current program. /// /// @return the status of the comparison. static abidiff_status compare_prepared_userspace_packages(package& first_package, package& second_package, abi_diff& diff, options& opts) { abidiff_status status = abigail::tools_utils::ABIDIFF_OK; abigail::workers::queue::tasks_type compare_tasks; string pkg_name = first_package.base_name(); // Setting debug-info path of libraries string debug_dir1, debug_dir2, relative_debug_path = "/usr/lib/debug/"; if (!first_package.debug_info_packages().empty() && !second_package.debug_info_packages().empty()) { debug_dir1 = first_package.debug_info_packages().front()->extracted_dir_path() + relative_debug_path; debug_dir2 = second_package.debug_info_packages().front()->extracted_dir_path() + relative_debug_path; } for (map::iterator it = first_package.path_elf_file_sptr_map().begin(); it != first_package.path_elf_file_sptr_map().end(); ++it) { map::iterator iter = second_package.path_elf_file_sptr_map().find(it->first); if (iter != second_package.path_elf_file_sptr_map().end() && (iter->second->type == abigail::dwarf_reader::ELF_TYPE_DSO || iter->second->type == abigail::dwarf_reader::ELF_TYPE_EXEC || iter->second->type == abigail::dwarf_reader::ELF_TYPE_PI_EXEC || iter->second->type == abigail::dwarf_reader::ELF_TYPE_RELOCATABLE)) { if (iter->second->type != abigail::dwarf_reader::ELF_TYPE_RELOCATABLE) { compare_args_sptr args (new compare_args(*it->second, debug_dir1, create_private_types_suppressions (first_package, opts), *iter->second, debug_dir2, create_private_types_suppressions (second_package, opts), opts)); compare_task_sptr t(new compare_task(args)); compare_tasks.push_back(t); } second_package.path_elf_file_sptr_map().erase(iter); } else if (iter == second_package.path_elf_file_sptr_map().end()) { diff.removed_binaries.push_back(it->second); status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE; status |= abigail::tools_utils::ABIDIFF_ABI_CHANGE; } } if (compare_tasks.empty()) { maybe_erase_temp_dirs(first_package, second_package, opts); return abigail::tools_utils::ABIDIFF_OK; } // Larger elfs are processed first, since it's usually safe to assume // their debug-info is larger as well, but the results are still // in a map ordered by looked up in elf.name order. std::sort(compare_tasks.begin(), compare_tasks.end(), elf_size_is_greater); // There's no reason to spawn more workers than there are ELF pairs // to be compared. size_t num_workers = (opts.parallel ? std::min(opts.num_workers, compare_tasks.size()) : 1); assert(num_workers >= 1); comparison_done_notify notifier(diff); abigail::workers::queue comparison_queue(num_workers, notifier); // Compare all the binaries, in parallel and then wait for the // comparisons to complete. comparison_queue.schedule_tasks(compare_tasks); comparison_queue.wait_for_workers_to_complete(); // Get the set of comparison tasks that were perform and sort them. queue::tasks_type& done_tasks = comparison_queue.get_completed_tasks(); std::sort(done_tasks.begin(), done_tasks.end(), elf_size_is_greater); // Print the reports of the comparison to standard output. for (queue::tasks_type::const_iterator i = done_tasks.begin(); i != done_tasks.end(); ++i) { compare_task_sptr t = dynamic_pointer_cast(*i); cout << t->pretty_output; } // Update the count of added binaries. for (map::iterator it = second_package.path_elf_file_sptr_map().begin(); it != second_package.path_elf_file_sptr_map().end(); ++it) diff.added_binaries.push_back(it->second); // Print information about removed binaries on standard output. if (diff.removed_binaries.size()) { cout << "Removed binaries:\n"; for (vector::iterator it = diff.removed_binaries.begin(); it != diff.removed_binaries.end(); ++it) { string relative_path; first_package.convert_path_to_relative((*it)->path, relative_path); cout << " [D] " << relative_path << ", "; string soname; get_soname_of_elf_file((*it)->path, soname); if (!soname.empty()) cout << "SONAME: " << soname; else cout << "no SONAME"; cout << "\n"; } } // Print information about added binaries on standard output. if (opts.show_added_binaries && diff.added_binaries.size()) { cout << "Added binaries:\n"; for (vector::iterator it = diff.added_binaries.begin(); it != diff.added_binaries.end(); ++it) { string relative_path; second_package.convert_path_to_relative((*it)->path, relative_path); cout << " [A] " << relative_path << ", "; string soname; get_soname_of_elf_file((*it)->path, soname); if (!soname.empty()) cout << "SONAME: " << soname; else cout << "no SONAME"; cout << "\n"; } } // Erase temporary directory tree we might have left behind. maybe_erase_temp_dirs(first_package, second_package, opts); status = notifier.status; return status; } /// In the context of the unpacked content of a given package, compare /// the binaries inside the package against their ABIXML /// representation. This should yield the empty set. /// /// @param pkg (unpacked) package /// /// @param diff the representation of the changes between the binaries /// and their ABIXML. This should obviously be the empty set. /// /// @param diff a textual representation of the diff. /// /// @param opts the options provided by the user. static abidiff_status self_compare_prepared_userspace_package(package& pkg, abi_diff& diff, options& opts) { abidiff_status status = abigail::tools_utils::ABIDIFF_OK; abigail::workers::queue::tasks_type self_compare_tasks; string pkg_name = pkg.base_name(); // Setting debug-info path of libraries string debug_dir, relative_debug_path = "/usr/lib/debug/"; if (!pkg.debug_info_packages().empty()) debug_dir = pkg.debug_info_packages().front()->extracted_dir_path() + relative_debug_path; suppressions_type supprs; for (map::iterator it = pkg.path_elf_file_sptr_map().begin(); it != pkg.path_elf_file_sptr_map().end(); ++it) { if (it != pkg.path_elf_file_sptr_map().end() && (it->second->type == abigail::dwarf_reader::ELF_TYPE_DSO || it->second->type == abigail::dwarf_reader::ELF_TYPE_EXEC || it->second->type == abigail::dwarf_reader::ELF_TYPE_PI_EXEC || it->second->type == abigail::dwarf_reader::ELF_TYPE_RELOCATABLE)) { if (it->second->type != abigail::dwarf_reader::ELF_TYPE_RELOCATABLE) { compare_args_sptr args (new compare_args(*it->second, debug_dir, supprs, *it->second, debug_dir, supprs, opts)); self_compare_task_sptr t(new self_compare_task(args)); self_compare_tasks.push_back(t); } } } if (self_compare_tasks.empty()) { maybe_erase_temp_dirs(pkg, pkg, opts); return abigail::tools_utils::ABIDIFF_OK; } // Larger elfs are processed first, since it's usually safe to assume // their debug-info is larger as well, but the results are still // in a map ordered by looked up in elf.name order. std::sort(self_compare_tasks.begin(), self_compare_tasks.end(), elf_size_is_greater); // There's no reason to spawn more workers than there are ELF pairs // to be compared. size_t num_workers = (opts.parallel ? std::min(opts.num_workers, self_compare_tasks.size()) : 1); assert(num_workers >= 1); comparison_done_notify notifier(diff); abigail::workers::queue comparison_queue(num_workers, notifier); // Compare all the binaries, in parallel and then wait for the // comparisons to complete. comparison_queue.schedule_tasks(self_compare_tasks); comparison_queue.wait_for_workers_to_complete(); // Get the set of comparison tasks that were perform and sort them. queue::tasks_type& done_tasks = comparison_queue.get_completed_tasks(); std::sort(done_tasks.begin(), done_tasks.end(), elf_size_is_greater); // Print the reports of the comparison to standard output. for (queue::tasks_type::const_iterator i = done_tasks.begin(); i != done_tasks.end(); ++i) { self_compare_task_sptr t = dynamic_pointer_cast(*i); if (t) cout << t->pretty_output; } // Erase temporary directory tree we might have left behind. maybe_erase_temp_dirs(pkg, pkg, opts); status = notifier.status; return status; } /// Compare the ABI of two prepared packages that contain linux kernel /// binaries. /// /// A prepared package is a package which content has been extracted /// and mapped. /// /// @param first_package the first package to consider. /// /// @param second_package the second package to consider. /// /// @param options the options the current program has been called /// with. /// /// @param diff out parameter. If this function returns true, then /// this parameter is set to the result of the comparison. /// /// @param opts the options of the current program. /// /// @return the status of the comparison. static abidiff_status compare_prepared_linux_kernel_packages(package& first_package, package& second_package, options& opts) { abidiff_status status = abigail::tools_utils::ABIDIFF_OK; string pkg_name = first_package.base_name(); // Setting debug-info path of binaries string debug_dir1, debug_dir2, relative_debug_path = "/usr/lib/debug/"; if (!first_package.debug_info_packages().empty() && !second_package.debug_info_packages().empty()) { debug_dir1 = first_package.debug_info_packages().front()->extracted_dir_path() + relative_debug_path; debug_dir2 = second_package.debug_info_packages().front()->extracted_dir_path() + relative_debug_path; } string vmlinux_path1, vmlinux_path2; if (!get_vmlinux_path_from_kernel_dist(debug_dir1, vmlinux_path1)) return abigail::tools_utils::ABIDIFF_ERROR; if (!get_vmlinux_path_from_kernel_dist(debug_dir2, vmlinux_path2)) return abigail::tools_utils::ABIDIFF_ERROR; string dist_root1 = first_package.extracted_dir_path(); string dist_root2 = second_package.extracted_dir_path(); abigail::ir::environment_sptr env(new abigail::ir::environment); suppressions_type supprs; corpus_group_sptr corpus1, corpus2; corpus1 = build_corpus_group_from_kernel_dist_under(dist_root1, debug_dir1, vmlinux_path1, opts.suppression_paths, opts.kabi_whitelist_paths, supprs, opts.verbose, env); if (!corpus1) return abigail::tools_utils::ABIDIFF_ERROR; corpus2 = build_corpus_group_from_kernel_dist_under(dist_root2, debug_dir2, vmlinux_path2, opts.suppression_paths, opts.kabi_whitelist_paths, supprs, opts.verbose, env); if (!corpus2) return abigail::tools_utils::ABIDIFF_ERROR; diff_context_sptr diff_ctxt(new diff_context); set_diff_context_from_opts(diff_ctxt, opts); corpus_diff_sptr diff = compute_diff(corpus1, corpus2, diff_ctxt); if (diff->has_net_changes()) status |= abigail::tools_utils::ABIDIFF_ABI_CHANGE; if (diff->has_incompatible_changes()) status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE; if (status & abigail::tools_utils::ABIDIFF_ABI_CHANGE) { cout << "== Kernel ABI changes between packages '" << first_package.path() << "' and '" << second_package.path() << "' are: ===\n"; diff->report(cout); cout << "== End of kernel ABI changes between packages '" << first_package.path() << "' and '" << second_package.path() << "' ===\n\n"; } return status; } /// Compare the ABI of two prepared packages. /// /// A prepared package is a package which content has been extracted /// and mapped. /// /// @param first_package the first package to consider. /// /// @param second_package the second package to consider. /// /// @param options the options the current program has been called /// with. /// /// @param diff out parameter. If this function returns true, then /// this parameter is set to the result of the comparison. /// /// @param opts the options of the current program. /// /// @return the status of the comparison. static abidiff_status compare_prepared_package(package& first_package, package& second_package, abi_diff& diff, options& opts) { abidiff_status status = abigail::tools_utils::ABIDIFF_OK; if (abigail::tools_utils::file_is_kernel_package(first_package.base_name(), first_package.type())) { opts.show_symbols_not_referenced_by_debug_info = false; status = compare_prepared_linux_kernel_packages(first_package, second_package, opts); } else status = compare_prepared_userspace_packages(first_package, second_package, diff, opts); return status; } /// Compare binaries in a package against their ABIXML /// representations. /// /// @param pkg the package to consider. /// /// @param diff the textual representation of the resulting /// comparison. /// /// @param opts the options provided by the user /// /// @return the status of the comparison. static abidiff_status self_compare_prepared_package(package& pkg, abi_diff& diff, options& opts) { abidiff_status status = abigail::tools_utils::ABIDIFF_OK; status = self_compare_prepared_userspace_package(pkg, diff, opts); return status; } /// Compare the ABI of two packages /// /// @param first_package the first package to consider. /// /// @param second_package the second package to consider. /// /// @param options the options the current program has been called /// with. /// /// @param diff out parameter. If this function returns true, then /// this parameter is set to the result of the comparison. /// /// @param opts the options of the current program. /// /// @return the status of the comparison. static abidiff_status compare(package_sptr& first_package, package_sptr& second_package, abi_diff& diff, options& opts) { // Prepare (extract and analyze the contents) the packages and their // ancillary packages. // // Note that the package preparations happens in parallel. if (!prepare_packages(first_package, second_package, opts)) { maybe_erase_temp_dirs(*first_package, *second_package, opts); return abigail::tools_utils::ABIDIFF_ERROR; } return compare_prepared_package(*first_package, *second_package, diff, opts); } /// Compare binaries in a package against their ABIXML /// representations. /// /// @param pkg the package to consider. /// /// @param opts the options provided by the user /// /// @return the status of the comparison. static abidiff_status compare_to_self(package_sptr& pkg, options& opts) { if (!prepare_package(pkg, opts)) return abigail::tools_utils::ABIDIFF_ERROR; abi_diff diff; return self_compare_prepared_package(*pkg, diff, opts); } /// Compare the ABI of two packages. /// /// @param first_package the first package to consider. /// /// @param second_package the second package to consider. /// /// @param opts the options the current program has been called with. /// /// @return the status of the comparison. static abidiff_status compare(package_sptr& first_package, package_sptr& second_package, options& opts) { abi_diff diff; return compare(first_package, second_package, diff, opts); } /// Parse the command line of the current program. /// /// @param argc the number of arguments in the @p argv parameter. /// /// @param argv the array of arguemnts passed to the function. The /// first argument is the name of this program. /// /// @param opts the resulting options. /// /// @return true upon successful parsing. static bool parse_command_line(int argc, char* argv[], options& opts) { if (argc < 2) return false; for (int i = 1; i < argc; ++i) { if (argv[i][0] != '-') { if (opts.package1.empty()) { opts.package1 = make_path_absolute(argv[i]).get(); opts.nonexistent_file = !file_exists(opts.package1); } else if (opts.package2.empty()) { opts.package2 = make_path_absolute(argv[i]).get(); opts.nonexistent_file = !file_exists(opts.package2); } else { opts.wrong_arg = argv[i]; return false; } if (opts.nonexistent_file) { opts.wrong_option = argv[i]; return true; } } else if (!strcmp(argv[i], "--debug-info-pkg1") || !strcmp(argv[i], "--d1")) { int j = i + 1; if (j >= argc) { opts.missing_operand = true; opts.wrong_option = argv[i]; return true; } opts.debug_packages1.push_back (abigail::tools_utils::make_path_absolute(argv[j]).get()); ++i; } else if (!strcmp(argv[i], "--debug-info-pkg2") || !strcmp(argv[i], "--d2")) { int j = i + 1; if (j >= argc) { opts.missing_operand = true; opts.wrong_option = argv[i]; return true; } opts.debug_packages2.push_back (abigail::tools_utils::make_path_absolute(argv[j]).get()); ++i; } else if (!strcmp(argv[i], "--devel-pkg1") || !strcmp(argv[i], "--devel1")) { int j = i + 1; if (j >= argc) { opts.missing_operand = true; opts.wrong_option = argv[i]; return true; } opts.devel_package1 = abigail::tools_utils::make_path_absolute(argv[j]).get(); ++i; } else if (!strcmp(argv[i], "--devel-pkg2") || !strcmp(argv[i], "--devel2")) { int j = i + 1; if (j >= argc) { opts.missing_operand = true; opts.wrong_option = argv[i]; return true; } opts.devel_package2 = abigail::tools_utils::make_path_absolute(argv[j]).get(); ++i; } else if (!strcmp(argv[i], "--drop-private-types")) opts.drop_private_types = true; else if (!strcmp(argv[i], "--no-default-suppression")) opts.no_default_suppression = true; else if (!strcmp(argv[i], "--keep-tmp-files")) opts.keep_tmp_files = true; else if (!strcmp(argv[i], "--dso-only")) opts.compare_dso_only = true; else if (!strcmp(argv[i], "--private-dso")) opts.compare_private_dsos = true; else if (!strcmp(argv[i], "--leaf-changes-only") ||!strcmp(argv[i], "-l")) opts.leaf_changes_only = true; else if (!strcmp(argv[i], "--impacted-interfaces") ||!strcmp(argv[i], "-i")) opts.show_impacted_interfaces = true; else if (!strcmp(argv[i], "--non-reachable-types") ||!strcmp(argv[i], "-t")) opts.show_all_types = true; else if (!strcmp(argv[i], "--full-impact") ||!strcmp(argv[i], "-f")) opts.show_full_impact_report = true; else if (!strcmp(argv[i], "--no-linkage-name")) opts.show_linkage_names = false; else if (!strcmp(argv[i], "--redundant")) opts.show_redundant_changes = true; else if (!strcmp(argv[i], "--harmless")) opts.show_harmless_changes = true; else if (!strcmp(argv[i], "--no-show-locs")) opts.show_locs = false; else if (!strcmp(argv[i], "--show-bytes")) opts.show_offsets_sizes_in_bits = false; else if (!strcmp(argv[i], "--show-bits")) opts.show_offsets_sizes_in_bits = true; else if (!strcmp(argv[i], "--show-hex")) opts.show_hexadecimal_values = true; else if (!strcmp(argv[i], "--show-dec")) opts.show_hexadecimal_values = false; else if (!strcmp(argv[i], "--no-show-relative-offset-changes")) opts.show_relative_offset_changes = false; else if (!strcmp(argv[i], "--no-added-syms")) opts.show_added_syms = false; else if (!strcmp(argv[i], "--no-unreferenced-symbols")) opts.show_symbols_not_referenced_by_debug_info = false; else if (!strcmp(argv[i], "--no-added-binaries")) opts.show_added_binaries = false; else if (!strcmp(argv[i], "--fail-no-dbg")) opts.fail_if_no_debug_info = true; else if (!strcmp(argv[i], "--verbose")) opts.verbose = true; else if (!strcmp(argv[i], "--no-abignore")) opts.abignore = false; else if (!strcmp(argv[i], "--no-parallel")) opts.parallel = false; else if (!strcmp(argv[i], "--show-identical-binaries")) opts.show_identical_binaries = true; else if (!strcmp(argv[i], "--self-check")) opts.self_check = true; else if (!strcmp(argv[i], "--suppressions") || !strcmp(argv[i], "--suppr")) { int j = i + 1; if (j >= argc) return false; opts.suppression_paths.push_back(argv[j]); ++i; } else if (!strcmp(argv[i], "--linux-kernel-abi-whitelist") || !strcmp(argv[i], "-w")) { int j = i + 1; if (j >= argc) { opts.missing_operand = true; opts.wrong_option = argv[i]; return true; } if (guess_file_type(argv[j]) == abigail::tools_utils::FILE_TYPE_RPM) // The kernel abi whitelist is actually a whitelist // *package*. Take that into account. opts.kabi_whitelist_packages.push_back (make_path_absolute(argv[j]).get()); else // We assume the kernel abi whitelist is a white list // file. opts.kabi_whitelist_paths.push_back(argv[j]); ++i; } else if (!strcmp(argv[i], "--wp")) { int j = i + 1; if (j >= argc) { opts.missing_operand = true; opts.wrong_option = argv[i]; return true; } opts.kabi_whitelist_packages.push_back (make_path_absolute(argv[j]).get()); ++i; } else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) { opts.display_usage = true; return true; } else if (!strcmp(argv[i], "--version") || !strcmp(argv[i], "-v")) { opts.display_version = true; return true; } else { if (strlen(argv[i]) >= 2 && argv[i][0] == '-' && argv[i][1] == '-') opts.wrong_option = argv[i]; return false; } } return true; } int main(int argc, char* argv[]) { options opts(argv[0]); if (!parse_command_line(argc, argv, opts)) { if (!opts.wrong_option.empty()) emit_prefix("abipkgdiff", cerr) << "unrecognized option: " << opts.wrong_option << "\ntry the --help option for more information\n"; else emit_prefix("abipkgdiff", cerr) << "unrecognized argument: " << opts.wrong_arg << "\ntry the --help option for more information\n"; return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } if (opts.missing_operand) { emit_prefix("abipkgdiff", cerr) << "missing operand\n" "try the --help option for more information\n"; return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } if (opts.nonexistent_file) { string input_file; base_name(opts.wrong_option, input_file); emit_prefix("abipkgdiff", cerr) << "The input file " << input_file << " doesn't exist\n" "try the --help option for more information\n"; return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } if (opts.kabi_whitelist_packages.size() > 2) { emit_prefix("abipkgdiff", cerr) << "no more than 2 Linux kernel white list packages can be provided\n"; return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } if (opts.display_usage) { display_usage(argv[0], cout); return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } if (opts.display_version) { emit_prefix(argv[0], cout) << abigail::tools_utils::get_library_version_string() << "\n"; return 0; } if (!opts.no_default_suppression && opts.suppression_paths.empty()) { // Load the default system and user suppressions. string default_system_suppr_file = get_default_system_suppression_file_path(); if (file_exists(default_system_suppr_file)) opts.suppression_paths.push_back(default_system_suppr_file); string default_user_suppr_file = get_default_user_suppression_file_path(); if (file_exists(default_user_suppr_file)) opts.suppression_paths.push_back(default_user_suppr_file); } if (!maybe_check_suppression_files(opts)) return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); bool need_just_one_input_package = opts.self_check; if (need_just_one_input_package) { bool bail_out = false; if (!opts.package2.empty()) { // We don't need the second package, we'll ignore it later // down below. ; } if (opts.package1.empty()) { // We need at least one package to work with! emit_prefix("abipkgdiff", cerr) << "missing input package\n"; if (bail_out) return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } } else if(opts.package1.empty() || opts.package2.empty()) { emit_prefix("abipkgdiff", cerr) << "please enter two packages to compare" << "\n"; return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } package_sptr first_package(new package(opts.package1, "package1")); package_sptr second_package(new package(opts.package2, "package2")); opts.pkg1 = first_package; opts.pkg2 = second_package; for (vector::const_iterator p = opts.debug_packages1.begin(); p != opts.debug_packages1.end(); ++p) first_package->debug_info_packages().push_back (package_sptr(new package(*p, "debug_package1", /*pkg_kind=*/package::KIND_DEBUG_INFO))); for (vector::const_iterator p = opts.debug_packages2.begin(); p != opts.debug_packages2.end(); ++p) second_package->debug_info_packages().push_back (package_sptr(new package(*p, "debug_package2", /*pkg_kind=*/package::KIND_DEBUG_INFO))); if (!opts.devel_package1.empty()) first_package->devel_package (package_sptr(new package(opts.devel_package1, "devel_package1", /*pkg_kind=*/package::KIND_DEVEL))); ; if (!opts.devel_package2.empty()) second_package->devel_package (package_sptr(new package(opts.devel_package2, "devel_package2", /*pkg_kind=*/package::KIND_DEVEL))); if (!opts.kabi_whitelist_packages.empty()) { first_package->kabi_whitelist_package (package_sptr(new package (opts.kabi_whitelist_packages[0], "kabi_whitelist_package1", /*pkg_kind=*/package::KIND_KABI_WHITELISTS))); if (opts.kabi_whitelist_packages.size() >= 2) second_package->kabi_whitelist_package (package_sptr(new package (opts.kabi_whitelist_packages[1], "kabi_whitelist_package2", /*pkg_kind=*/package::KIND_KABI_WHITELISTS))); } string package_name; switch (first_package->type()) { case abigail::tools_utils::FILE_TYPE_RPM: if (!second_package->path().empty() && second_package->type() != abigail::tools_utils::FILE_TYPE_RPM) { base_name(opts.package2, package_name); emit_prefix("abipkgdiff", cerr) << package_name << " should be an RPM file\n"; return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } if (file_is_kernel_package(first_package->base_name(), abigail::tools_utils::FILE_TYPE_RPM) || file_is_kernel_package(second_package->base_name(), abigail::tools_utils::FILE_TYPE_RPM)) { if (file_is_kernel_package(first_package->base_name(), abigail::tools_utils::FILE_TYPE_RPM) != file_is_kernel_package(second_package->base_name(), abigail::tools_utils::FILE_TYPE_RPM)) { emit_prefix("abipkgdiff", cerr) << "a Linux kernel package can only be compared to another " "Linux kernel package\n"; return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } if (first_package->debug_info_packages().empty() || (!second_package->path().empty() && second_package->debug_info_packages().empty())) { emit_prefix("abipkgdiff", cerr) << "a Linux Kernel package must be accompanied with its " "debug info package\n"; return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } // We are looking at kernel packages. If the user provided // the --full-impact option then it means we want to display // the default libabigail report format where a full impact // analysis is done for each ABI change. // // Otherwise, let's just emit the leaf change report. if (opts.show_full_impact_report) opts.leaf_changes_only = false; else opts.leaf_changes_only = true; } break; case abigail::tools_utils::FILE_TYPE_DEB: if (!second_package->path().empty() && second_package->type() != abigail::tools_utils::FILE_TYPE_DEB) { base_name(opts.package2, package_name); emit_prefix("abipkgdiff", cerr) << package_name << " should be a DEB file\n"; return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } break; case abigail::tools_utils::FILE_TYPE_DIR: if (!second_package->path().empty() && second_package->type() != abigail::tools_utils::FILE_TYPE_DIR) { base_name(opts.package2, package_name); emit_prefix("abipkgdiff", cerr) << package_name << " should be a directory\n"; return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } break; case abigail::tools_utils::FILE_TYPE_TAR: if (!second_package->path().empty() && second_package->type() != abigail::tools_utils::FILE_TYPE_TAR) { base_name(opts.package2, package_name); emit_prefix("abipkgdiff", cerr) << package_name << " should be a GNU tar archive\n"; return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } break; default: base_name(opts.package1, package_name); emit_prefix("abipkgdiff", cerr) << package_name << " should be a valid package file \n"; return (abigail::tools_utils::ABIDIFF_USAGE_ERROR | abigail::tools_utils::ABIDIFF_ERROR); } if (opts.self_check) return compare_to_self(first_package, opts); return compare(first_package, second_package, opts); }