You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3616 lines
107 KiB
3616 lines
107 KiB
// 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 <sys/types.h>
|
|
#include <fts.h>
|
|
#endif
|
|
|
|
// For package configuration macros.
|
|
#include "config.h"
|
|
|
|
#include <assert.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
|
|
// 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 <sys/types.h>
|
|
#include <fts.h>
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#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> 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<string> debug_packages1;
|
|
vector<string> 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<string> kabi_whitelist_packages;
|
|
vector<string> suppression_paths;
|
|
vector<string> 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<string>& 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> 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<elf_file_sptr> added_binaries;
|
|
vector<elf_file_sptr> removed_binaries;
|
|
vector<string> 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<string, elf_file_sptr> path_elf_file_sptr_map_;
|
|
vector<package_sptr> debug_info_packages_;
|
|
package_sptr devel_package_;
|
|
package_sptr kabi_whitelist_package_;
|
|
vector<string> elf_file_paths_;
|
|
set<string> 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<string, elf_file_sptr>&
|
|
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<string, elf_file_sptr>&
|
|
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<package_sptr>&
|
|
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<package_sptr>&
|
|
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<package_sptr> &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<string>&
|
|
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<string>&
|
|
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<string>&
|
|
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<string>&
|
|
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> 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<char*>(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> 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] <package1> <package2>\n"
|
|
<< " where options can be:\n"
|
|
<< " --debug-info-pkg1|--d1 <path> path of debug-info package of package1\n"
|
|
<< " --debug-info-pkg2|--d2 <path> path of debug-info package of package2\n"
|
|
<< " --devel-pkg1|--devel1 <path> path of devel package of pakage1\n"
|
|
<< " --devel-pkg2|--devel2 <path> 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 <path> specify supression specification path\n"
|
|
<< " --linux-kernel-abi-whitelist|-w path to a "
|
|
"linux kernel abi whitelist\n"
|
|
<< " --wp <path> 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<string>::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<string>::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<string>::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<char**> 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<char**> 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<string> &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<string>& whitelist_paths)
|
|
{
|
|
bool is_ok = false;
|
|
char* paths[] = {const_cast<char*>(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<string> 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<package_sptr> 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<package_sptr> &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<package_sptr>::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> 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> 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> 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> 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<string>& 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<string>& interesting_files)
|
|
{
|
|
bool is_ok = false;
|
|
string root;
|
|
real_path(dir, root);
|
|
if (root.empty())
|
|
root = dir;
|
|
|
|
char* paths[] = {const_cast<char*>(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<string> files;
|
|
while ((entry = fts_read(file_hierarchy)))
|
|
maybe_update_package_content(entry, opts, file_name_to_look_for, files);
|
|
|
|
for (unordered_set<string>::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<string> 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<string>::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<compare_task>(task1);
|
|
compare_task_sptr t2 = dynamic_pointer_cast<compare_task>(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<compare_task>(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<string, elf_file_sptr>::iterator it =
|
|
first_package.path_elf_file_sptr_map().begin();
|
|
it != first_package.path_elf_file_sptr_map().end();
|
|
++it)
|
|
{
|
|
map<string, elf_file_sptr>::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<compare_task>(*i);
|
|
cout << t->pretty_output;
|
|
}
|
|
|
|
// Update the count of added binaries.
|
|
for (map<string, elf_file_sptr>::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<elf_file_sptr>::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<elf_file_sptr>::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<string, elf_file_sptr>::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<self_compare_task>(*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<string>::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<string>::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);
|
|
}
|