mirror of
git://sourceware.org/git/libabigail.git
synced 2024-12-18 16:04:34 +00:00
ec9a667f39
* tools/abipkgdiff.cc (parse_command_line): Fix a wrong indentation. Signed-off-by: Dodji Seketeli <dodji@redhat.com>
1765 lines
50 KiB
C++
1765 lines
50 KiB
C++
// -*- Mode: C++ -*-
|
|
//
|
|
// Copyright (C) 2015 Red Hat, Inc.
|
|
//
|
|
// This file is part of the GNU Application Binary Interface Generic
|
|
// Analysis and Instrumentation Library (libabigail). This library is
|
|
// free software; you can redistribute it and/or modify it under the
|
|
// terms of the GNU Lesser General Public License as published by the
|
|
// Free Software Foundation; either version 3, or (at your option) any
|
|
// later version.
|
|
|
|
// This library is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// General Lesser Public License for more details.
|
|
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this program; see the file COPYING-LGPLV3. If
|
|
// not, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
// 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/ to /5 are performed in sequence)
|
|
///
|
|
/// 1/ the first package and its debug info are extracted concurrently.
|
|
/// One thread extracts the package (and maps its content) and another one
|
|
/// extracts the debug info package.
|
|
///
|
|
/// 2/ the second package and its debug info are extracted in parallel.
|
|
/// One thread extracts the package (and maps its content) and another one
|
|
/// extracts the debug info package.
|
|
///
|
|
/// 3/ the file system trees of extracted packages are traversed to
|
|
/// identify existing pairs and a list of arguments for future comparison
|
|
/// is made. The trees are traversed concurrently.
|
|
///
|
|
/// 4/ comparisons are performed concurrently.
|
|
///
|
|
/// 5/ the reports are then emitted to standard output, always in the same
|
|
/// order.
|
|
|
|
// For package configuration macros.
|
|
#include "config.h"
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
#include <vector>
|
|
#include <ftw.h>
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <assert.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <elf.h>
|
|
#include <elfutils/libdw.h>
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
#include "abg-config.h"
|
|
#include "abg-tools-utils.h"
|
|
#include "abg-comparison.h"
|
|
#include "abg-dwarf-reader.h"
|
|
|
|
using std::cout;
|
|
using std::cerr;
|
|
using std::string;
|
|
using std::ostream;
|
|
using std::vector;
|
|
using std::map;
|
|
using std::tr1::shared_ptr;
|
|
using abigail::tools_utils::emit_prefix;
|
|
using abigail::tools_utils::check_file;
|
|
using abigail::tools_utils::guess_file_type;
|
|
using abigail::tools_utils::string_ends_with;
|
|
using abigail::tools_utils::file_type;
|
|
using abigail::tools_utils::make_path_absolute;
|
|
using abigail::tools_utils::abidiff_status;
|
|
using abigail::ir::corpus_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::suppression_sptr;
|
|
using abigail::comparison::suppressions_type;
|
|
using abigail::comparison::read_suppressions;
|
|
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;
|
|
|
|
/// Set to true if the user wants to see verbose information about the
|
|
/// progress of what's being done.
|
|
static bool verbose;
|
|
|
|
/// The key for getting the thread-local elf_file_paths vector, which
|
|
/// contains the set of files of a given package. The vector is populated
|
|
/// by a worker function that is invoked on each file contained in the
|
|
/// package, specifically by the
|
|
/// {first,second}_package_tree_walker_callback_fn() functions. Its content
|
|
/// is relevant only until the mapping of the packages elf files is done.
|
|
static pthread_key_t elf_file_paths_tls_key;
|
|
|
|
/// A convenience typedef for a map of corpus diffs
|
|
typedef map<string, corpus_diff_sptr> corpora_report_map;
|
|
/// This map is used to gather the computed diffs of ELF pairs
|
|
static corpora_report_map reports_map;
|
|
|
|
/// This map is used to keep environments for differing corpora
|
|
/// referenced. The environment needs to be kept alive longer than
|
|
/// all the objects that depend on it.
|
|
static map<corpus_diff_sptr, abigail::ir::environment_sptr> env_map;
|
|
|
|
/// This mutex is used to control access to the reports_map
|
|
static pthread_mutex_t map_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
/// This mutex is used to control access to the pre-computed list of ELF pairs
|
|
static pthread_mutex_t arg_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
/// This points to the set of options shared by all the routines of the
|
|
/// program.
|
|
static struct options *prog_options;
|
|
|
|
/// The options passed to the current program.
|
|
class options
|
|
{
|
|
options();
|
|
|
|
public:
|
|
string wrong_option;
|
|
string prog_name;
|
|
bool display_usage;
|
|
bool display_version;
|
|
bool missing_operand;
|
|
bool abignore;
|
|
bool parallel;
|
|
string package1;
|
|
string package2;
|
|
string debug_package1;
|
|
string debug_package2;
|
|
bool keep_tmp_files;
|
|
bool compare_dso_only;
|
|
bool show_linkage_names;
|
|
bool show_redundant_changes;
|
|
bool show_locs;
|
|
bool show_added_syms;
|
|
bool show_added_binaries;
|
|
bool fail_if_no_debug_info;
|
|
vector<string> suppression_paths;
|
|
|
|
options(const string& program_name)
|
|
: prog_name(program_name),
|
|
display_usage(),
|
|
display_version(),
|
|
missing_operand(),
|
|
abignore(true),
|
|
parallel(true),
|
|
keep_tmp_files(),
|
|
compare_dso_only(),
|
|
show_linkage_names(true),
|
|
show_redundant_changes(),
|
|
show_locs(true),
|
|
show_added_syms(true),
|
|
show_added_binaries(true),
|
|
fail_if_no_debug_info()
|
|
{}
|
|
};
|
|
|
|
/// 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;
|
|
|
|
/// A convenience typedef for a pointer to a function type that
|
|
/// the ftw() function accepts.
|
|
typedef int (*ftw_cb_type)(const char *, const struct stat*, int);
|
|
|
|
/// 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 <string> added_binaries;
|
|
vector <string> 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
|
|
{
|
|
string path_;
|
|
string extracted_dir_path_;
|
|
abigail::tools_utils::file_type type_;
|
|
bool is_debug_info_;
|
|
map<string, elf_file_sptr> path_elf_file_sptr_map_;
|
|
shared_ptr<package> debug_info_package_;
|
|
|
|
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 is_debug_info true if the pacakge is a debug info package.
|
|
package(const string& path,
|
|
const string& dir,
|
|
bool is_debug_info = false)
|
|
: path_(path),
|
|
is_debug_info_(is_debug_info)
|
|
{
|
|
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 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 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;}
|
|
|
|
/// Test if the current package is a debug info package.
|
|
///
|
|
/// @return true iff the current package is a debug info package.
|
|
bool
|
|
is_debug_info() const
|
|
{return is_debug_info_;}
|
|
|
|
/// Set the flag that says if the current package is a debug info package.
|
|
///
|
|
/// @param f the new flag.
|
|
void
|
|
is_debug_info(bool f)
|
|
{is_debug_info_ = f;}
|
|
|
|
/// 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 package associated to the current
|
|
/// package.
|
|
///
|
|
/// @return the debug info package associated to the current
|
|
/// package.
|
|
const shared_ptr<package>&
|
|
debug_info_package() const
|
|
{return debug_info_package_;}
|
|
|
|
/// Setter for the debug info package associated to the current
|
|
/// package.
|
|
///
|
|
/// @param p the new debug info package.
|
|
void
|
|
debug_info_package(const shared_ptr<package> p)
|
|
{debug_info_package_ = p;}
|
|
|
|
/// Erase the content of the temporary extraction directory that has
|
|
/// been populated by the @ref extract_package() function;
|
|
void
|
|
erase_extraction_directory() 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 (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 (verbose)
|
|
emit_prefix("abipkgdiff", cerr) << " FAILED\n";
|
|
}
|
|
else
|
|
{
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr) << " DONE\n";
|
|
}
|
|
}
|
|
|
|
/// Erase the content of all the temporary extraction directories.
|
|
void
|
|
erase_extraction_directories() const
|
|
{
|
|
erase_extraction_directory();
|
|
if (debug_info_package())
|
|
debug_info_package()->erase_extraction_directory();
|
|
}
|
|
};
|
|
|
|
/// Arguments passed to the package extraction functions.
|
|
struct package_descriptor
|
|
{
|
|
package &pkg;
|
|
const options& opts;
|
|
ftw_cb_type callback;
|
|
};
|
|
|
|
/// Arguments passed to the comparison workers.
|
|
struct compare_args
|
|
{
|
|
const elf_file elf1;
|
|
const string& debug_dir1;
|
|
const elf_file elf2;
|
|
const string& debug_dir2;
|
|
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 elf_file &elf2, const string& debug_dir2,
|
|
const options& opts)
|
|
: elf1(elf1), debug_dir1(debug_dir1), elf2(elf2),
|
|
debug_dir2(debug_dir2), opts(opts)
|
|
{}
|
|
};
|
|
/// A convenience typedef for arguments passed to the comparison workers.
|
|
typedef shared_ptr<compare_args> compare_args_sptr;
|
|
|
|
/// 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 time of multi-threading comes.
|
|
|
|
//static __thread string p;
|
|
static string p;
|
|
|
|
if (p.empty())
|
|
{
|
|
const char *tmpdir = getenv("TMPDIR");
|
|
|
|
if (tmpdir != NULL)
|
|
p = tmpdir;
|
|
else
|
|
p = "/tmp";
|
|
|
|
using abigail::tools_utils::get_random_number_as_string;
|
|
|
|
p = p + "/libabigail-tmp-dir-" + get_random_number_as_string();
|
|
}
|
|
|
|
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"
|
|
<< " --suppressions|--suppr <path> specify supression specification path\n"
|
|
<< " --keep-tmp-files don't erase created temporary files\n"
|
|
<< " --dso-only compare shared libraries only\n"
|
|
<< " --no-linkage-name do not display linkage names of "
|
|
"added/removed/changed\n"
|
|
<< " --redundant display redundant changes\n"
|
|
<< " --no-show-locs do not show location information\n"
|
|
<< " --no-added-syms do not display added functions or variables\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"
|
|
<< " --verbose emit verbose progress messages\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.
|
|
///
|
|
/// @return true upon successful completion, false otherwise.
|
|
static bool
|
|
extract_rpm(const string& package_path,
|
|
const string& extracted_package_dir_path)
|
|
{
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< "Extracting package "
|
|
<< 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 (verbose)
|
|
emit_prefix("abipkgdiff", cerr) << "command " << cmd << " FAILED\n";
|
|
}
|
|
|
|
cmd = "mkdir -p " + extracted_package_dir_path + " && cd " +
|
|
extracted_package_dir_path + " && rpm2cpio " + package_path +
|
|
" | cpio -dium --quiet";
|
|
|
|
if (system(cmd.c_str()))
|
|
{
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr) << " FAILED\n";
|
|
return false;
|
|
}
|
|
|
|
if (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.
|
|
///
|
|
/// @return true upon successful completion, false otherwise.
|
|
static bool
|
|
extract_deb(const string& package_path,
|
|
const string& extracted_package_dir_path)
|
|
{
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< "Extracting package "
|
|
<< 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 (verbose)
|
|
emit_prefix("abipkgdiff", cerr) << "command " << cmd << " FAILED\n";
|
|
}
|
|
|
|
cmd = "mkdir -p " + extracted_package_dir_path + " && dpkg -x " +
|
|
package_path + " " + extracted_package_dir_path;
|
|
|
|
if (system(cmd.c_str()))
|
|
{
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr) << " FAILED\n";
|
|
return false;
|
|
}
|
|
|
|
if (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.
|
|
///
|
|
/// @return true upon successful completion, false otherwise.
|
|
static bool
|
|
extract_tar(const string& package_path,
|
|
const string& extracted_package_dir_path)
|
|
{
|
|
if (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 (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 (verbose)
|
|
emit_prefix("abipkgdiff", cerr) << " FAILED\n";
|
|
return false;
|
|
}
|
|
|
|
if (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 second_package the second package to consider.
|
|
static void
|
|
erase_created_temporary_directories(const package& first_package,
|
|
const package& second_package)
|
|
{
|
|
first_package.erase_extraction_directories();
|
|
second_package.erase_extraction_directories();
|
|
}
|
|
|
|
/// Erase the root of all the temporary directories created by the
|
|
/// current thread.
|
|
static void
|
|
erase_created_temporary_directories_parent()
|
|
{
|
|
if (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 (verbose)
|
|
emit_prefix("abipkgdiff", cerr) << "FAILED\n";
|
|
}
|
|
else
|
|
{
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr) << "DONE\n";
|
|
}
|
|
}
|
|
|
|
/// Extract the content of a package.
|
|
///
|
|
/// @param package the package we are looking at.
|
|
static bool
|
|
extract_package(const package& package)
|
|
{
|
|
switch(package.type())
|
|
{
|
|
case abigail::tools_utils::FILE_TYPE_RPM:
|
|
#ifdef WITH_RPM
|
|
if (!extract_rpm(package.path(), package.extracted_dir_path()))
|
|
{
|
|
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()))
|
|
{
|
|
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()))
|
|
{
|
|
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;
|
|
}
|
|
/// A wrapper to call extract_package in a separate thread.
|
|
///
|
|
/// @param pkg the package we want to extract.
|
|
///
|
|
/// @return via pthread_exit() a pointer to a boolean value of true upon
|
|
/// successful completion, false otherwise.
|
|
static void
|
|
pthread_routine_extract_package(void *pkg)
|
|
{
|
|
const package &package = *static_cast<class package*>(pkg);
|
|
pthread_exit(new bool(extract_package(package)));
|
|
}
|
|
|
|
/// A callback function invoked by the ftw() function while walking
|
|
/// the directory of files extracted from the first package.
|
|
///
|
|
/// @param fpath the path to the file being considered.
|
|
///
|
|
/// @param stat the stat struct of the file.
|
|
static int
|
|
first_package_tree_walker_callback_fn(const char *fpath,
|
|
const struct stat *,
|
|
int /*flag*/)
|
|
{
|
|
struct stat s;
|
|
lstat(fpath, &s);
|
|
|
|
if (!S_ISLNK(s.st_mode))
|
|
{
|
|
if (guess_file_type(fpath) == abigail::tools_utils::FILE_TYPE_ELF)
|
|
{
|
|
vector<string> *elf_file_paths
|
|
= static_cast<vector<string>*>(pthread_getspecific(elf_file_paths_tls_key));
|
|
elf_file_paths->push_back(fpath);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// A callback function invoked by the ftw() function while walking
|
|
/// the directory of files extracted from the second package.
|
|
///
|
|
/// @param fpath the path to the file being considered.
|
|
///
|
|
/// @param stat the stat struct of the file.
|
|
static int
|
|
second_package_tree_walker_callback_fn(const char *fpath,
|
|
const struct stat *,
|
|
int /*flag*/)
|
|
{
|
|
struct stat s;
|
|
lstat(fpath, &s);
|
|
|
|
if (!S_ISLNK(s.st_mode))
|
|
{
|
|
if (guess_file_type(fpath) == abigail::tools_utils::FILE_TYPE_ELF)
|
|
{
|
|
vector<string> *elf_file_paths
|
|
= static_cast<vector<string>*>(pthread_getspecific(elf_file_paths_tls_key));
|
|
elf_file_paths->push_back(fpath);
|
|
}
|
|
/// We go through the files of the newer (second) pkg to look for
|
|
/// suppression specifications, matching the "*.abignore" name pattern.
|
|
else if (prog_options->abignore && string_ends_with(fpath, ".abignore"))
|
|
prog_options->suppression_paths.push_back(fpath);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// 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;
|
|
|
|
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);
|
|
ctxt->show_redundant_changes(opts.show_redundant_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->switch_categories_off
|
|
(abigail::comparison::ACCESS_CHANGE_CATEGORY
|
|
| abigail::comparison::COMPATIBLE_TYPE_CHANGE_CATEGORY
|
|
| abigail::comparison::HARMLESS_DECL_NAME_CHANGE_CATEGORY
|
|
| abigail::comparison::NON_VIRT_MEM_FUN_CHANGE_CATEGORY
|
|
| abigail::comparison::STATIC_DATA_MEMBER_CHANGE_CATEGORY
|
|
| abigail::comparison::HARMLESS_ENUM_CHANGE_CATEGORY
|
|
| abigail::comparison::HARMLESS_SYMBOL_ALIAS_CHANGE_CATEORY);
|
|
|
|
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.
|
|
///
|
|
/// @return the status of the comparison.
|
|
static abidiff_status
|
|
compare(const elf_file& elf1,
|
|
const string& debug_dir1,
|
|
const elf_file& elf2,
|
|
const string& debug_dir2,
|
|
const options& opts,
|
|
abigail::ir::environment_sptr &env,
|
|
corpus_diff_sptr &diff)
|
|
{
|
|
char *di_dir1 = (char*) debug_dir1.c_str(),
|
|
*di_dir2 = (char*) debug_dir2.c_str();
|
|
|
|
if (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;
|
|
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< " Reading file "
|
|
<< elf1.path
|
|
<< " ...\n";
|
|
|
|
corpus_sptr corpus1 = read_corpus_from_elf(elf1.path, &di_dir1, env.get(),
|
|
/*load_all_types=*/false,
|
|
c1_status);
|
|
if (!(c1_status & abigail::dwarf_reader::STATUS_OK))
|
|
{
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< "Could not read file '"
|
|
<< elf1.path
|
|
<< "' properly\n";
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
}
|
|
|
|
if (opts.fail_if_no_debug_info
|
|
&& (c1_status & abigail::dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND))
|
|
{
|
|
emit_prefix("abipkgdiff", cerr) << "Could not find debug info file";
|
|
if (di_dir1 && strcmp(di_dir1, ""))
|
|
emit_prefix("abipkgdiff", cerr) << " under " << di_dir1 << "\n";
|
|
else
|
|
emit_prefix("abipkgdiff", cerr) << "\n";
|
|
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
}
|
|
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< " DONE reading file "
|
|
<< elf1.path
|
|
<< " ...\n";
|
|
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< " Reading file "
|
|
<< elf2.path
|
|
<< " ...\n";
|
|
|
|
corpus_sptr corpus2 = read_corpus_from_elf(elf2.path, &di_dir2, env.get(),
|
|
/*load_all_types=*/false,
|
|
c2_status);
|
|
if (!(c2_status & abigail::dwarf_reader::STATUS_OK))
|
|
{
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< "Could not find the read file '"
|
|
<< elf2.path
|
|
<< "' properly\n";
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
}
|
|
|
|
if (opts.fail_if_no_debug_info
|
|
&& (c2_status & abigail::dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND))
|
|
{
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< "Could not find debug info file";
|
|
if (di_dir1 && strcmp(di_dir2, ""))
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< " under " << di_dir2 << "\n";
|
|
else
|
|
emit_prefix("abipkgdiff", cerr) << "\n";
|
|
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
}
|
|
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< " DONE reading file " << elf2.path << " ...\n";
|
|
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< " Comparing the ABIs of: \n"
|
|
<< " " << elf1.path << "\n"
|
|
<< " " << elf2.path << "\n";
|
|
|
|
diff_context_sptr ctxt(new diff_context);
|
|
set_diff_context_from_opts(ctxt, opts);
|
|
diff = compute_diff(corpus1, corpus2, ctxt);
|
|
|
|
if (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;
|
|
}
|
|
|
|
/// A wrapper to call compare in a separate thread.
|
|
/// The result of the comparison is saved to a global corpus map.
|
|
///
|
|
/// @args the vector of argument sets used for comparison.
|
|
///
|
|
/// @return the status of the comparison via pthread_exit().
|
|
static void
|
|
pthread_routine_compare(vector<compare_args_sptr> *args)
|
|
{
|
|
abidiff_status s, status = abigail::tools_utils::ABIDIFF_OK;
|
|
compare_args_sptr a;
|
|
corpus_diff_sptr diff;
|
|
|
|
while (true)
|
|
{
|
|
pthread_mutex_lock(&arg_lock);
|
|
if (args->empty())
|
|
a = compare_args_sptr();
|
|
else
|
|
{
|
|
a = *args->begin();
|
|
args->erase(args->begin());
|
|
}
|
|
pthread_mutex_unlock(&arg_lock);
|
|
|
|
if (!a)
|
|
break;
|
|
|
|
abigail::ir::environment_sptr env(new abigail::ir::environment);
|
|
status |= s = compare(a->elf1, a->debug_dir1,
|
|
a->elf2, a->debug_dir2,
|
|
a->opts, env, diff);
|
|
|
|
const string key = a->elf1.path;
|
|
if ((s & abigail::tools_utils::ABIDIFF_ABI_CHANGE)
|
|
|| (verbose && diff->has_changes()))
|
|
{
|
|
pthread_mutex_lock(&map_lock);
|
|
reports_map[key] = diff;
|
|
// We need to keep the environment around, until the corpus is
|
|
// report()-ed.
|
|
env_map[diff] = env;
|
|
pthread_mutex_unlock(&map_lock);
|
|
}
|
|
else
|
|
{
|
|
pthread_mutex_lock(&map_lock);
|
|
reports_map[key] = corpus_diff_sptr();
|
|
pthread_mutex_unlock(&map_lock);
|
|
}
|
|
}
|
|
|
|
pthread_exit(new abidiff_status(status));
|
|
}
|
|
|
|
/// 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,
|
|
const options& opts,
|
|
ftw_cb_type callback)
|
|
{
|
|
vector<string> *elf_file_paths = new vector<string>;
|
|
pthread_setspecific(elf_file_paths_tls_key, elf_file_paths);
|
|
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< "Analyzing the content of package "
|
|
<< package.path()
|
|
<< " extracted to "
|
|
<< package.extracted_dir_path()
|
|
<< " ...";
|
|
|
|
if (ftw(package.extracted_dir_path().c_str(), callback, 16))
|
|
{
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< "Error while inspecting files in package"
|
|
<< package.extracted_dir_path() << "\n";
|
|
return false;
|
|
}
|
|
|
|
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)
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (e->type != abigail::dwarf_reader::ELF_TYPE_DSO
|
|
&& e->type != abigail::dwarf_reader::ELF_TYPE_EXEC)
|
|
continue;
|
|
}
|
|
|
|
if (e->soname.empty())
|
|
package.path_elf_file_sptr_map()[e->name] = e;
|
|
else
|
|
package.path_elf_file_sptr_map()[e->soname] = e;
|
|
}
|
|
|
|
pthread_setspecific(elf_file_paths_tls_key, /*value=*/NULL);
|
|
delete elf_file_paths;
|
|
|
|
if (verbose)
|
|
emit_prefix("abipkgdiff", cerr) << " DONE\n";
|
|
return true;
|
|
}
|
|
|
|
static inline bool
|
|
pthread_join(pthread_t thr)
|
|
{
|
|
bool *thread_retval;
|
|
if (!pthread_join(thr, reinterpret_cast<void**>(&thread_retval)))
|
|
{
|
|
bool retval = *thread_retval;
|
|
delete thread_retval;
|
|
return retval;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/// Extract the content of a package and map its content.
|
|
/// Also extract its accompanying debuginfo package.
|
|
///
|
|
/// The extracting is done to a temporary directory.
|
|
///
|
|
/// @param a the set of arguments needed for successful extraction;
|
|
/// specifically the package itself, the options the current package has been
|
|
/// called with and a callback to traverse the directory structure.
|
|
///
|
|
/// @return via pthread_exit() true upon successful completion, false
|
|
/// otherwise.
|
|
static void
|
|
pthread_routine_extract_pkg_and_map_its_content(package_descriptor *a)
|
|
{
|
|
pthread_t thr_pkg, thr_debug;
|
|
package& package = a->pkg;
|
|
const options& opts = a->opts;
|
|
ftw_cb_type callback = a->callback;
|
|
bool has_debug_info_pkg, result = true;
|
|
|
|
// The debug-info package usually takes longer to extract than the main
|
|
// package plus that package's mapping for ELFs and optionally suppression
|
|
// specs, so we run it ASAP.
|
|
if ((has_debug_info_pkg = package.debug_info_package()))
|
|
{
|
|
if (pthread_create(&thr_debug, /*attr=*/NULL,
|
|
reinterpret_cast<void*(*)(void*)>(pthread_routine_extract_package),
|
|
package.debug_info_package().get()))
|
|
{
|
|
result = false;
|
|
goto exit;
|
|
}
|
|
|
|
// Wait for debug-info package extraction to complete if we're
|
|
// not running in parallel.
|
|
if (!opts.parallel)
|
|
{
|
|
result = pthread_join(thr_debug);
|
|
if (!result)
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// Extract the package itself.
|
|
if (pthread_create(&thr_pkg, /*attr=*/NULL,
|
|
reinterpret_cast<void*(*)(void*)>(pthread_routine_extract_package),
|
|
&package))
|
|
result = false;
|
|
|
|
// We need to wait for the package's successful extraction, if we
|
|
// want to do a mapping on its files.
|
|
if (result)
|
|
result = pthread_join(thr_pkg);
|
|
|
|
// If extracting the package failed, there's no sense in going further.
|
|
if (result)
|
|
result = create_maps_of_package_content(package, opts, callback);
|
|
|
|
// Let's wait for both extractions to finish before we exit.
|
|
if (has_debug_info_pkg && opts.parallel)
|
|
result &= pthread_join(thr_debug);
|
|
|
|
exit:
|
|
pthread_exit(new bool(result));
|
|
|
|
}
|
|
|
|
/// Prepare the packages for comparison.
|
|
///
|
|
/// This function extracts the content of each package and maps it.
|
|
///
|
|
/// @param first_package the first package to prepare.
|
|
///
|
|
/// @param second_package the second package to prepare.
|
|
///
|
|
/// @param opts the options the current program has been called with.
|
|
///
|
|
/// @return true upon successful completion, false otherwise.
|
|
static bool
|
|
prepare_packages(package& first_package,
|
|
package& second_package,
|
|
const options& opts)
|
|
{
|
|
bool result = true;
|
|
const int npkgs = 2;
|
|
pthread_t extract_thread[npkgs];
|
|
|
|
package_descriptor ea[] =
|
|
{
|
|
{
|
|
first_package,
|
|
opts,
|
|
first_package_tree_walker_callback_fn
|
|
},
|
|
{
|
|
second_package,
|
|
opts,
|
|
second_package_tree_walker_callback_fn
|
|
},
|
|
};
|
|
|
|
// Since we can't pass custom arguments to the callback of ftw(),
|
|
// and we are going to walk two directory trees in parallel, we
|
|
// need a separate thread-local vector of files for each package.
|
|
pthread_key_create(&elf_file_paths_tls_key, /*destructor=*/NULL);
|
|
|
|
for (int i = 0; i < npkgs; ++i)
|
|
{
|
|
pthread_create(&extract_thread[i], /*attr=*/NULL,
|
|
reinterpret_cast<void*(*)(void*)>(pthread_routine_extract_pkg_and_map_its_content),
|
|
&ea[i]);
|
|
|
|
if (!opts.parallel)
|
|
// We're not running in parallel, so wait for the first package set to
|
|
// finish extracting before starting to work on the second one.
|
|
result &= pthread_join(extract_thread[i]);
|
|
}
|
|
|
|
if (opts.parallel)
|
|
{
|
|
// We're running in parallel, so we collect the threads here.
|
|
for (int i = 0; i < npkgs; ++i)
|
|
result &= pthread_join(extract_thread[i]);
|
|
}
|
|
|
|
pthread_key_delete(elf_file_paths_tls_key);
|
|
return result;
|
|
}
|
|
|
|
/// Compare the added sizes of a ELF pair specified by @a1
|
|
/// with the sizes of a ELF pair from @a2.
|
|
///
|
|
/// 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.
|
|
///
|
|
/// @a1 the first set of arguments containing also the size information about
|
|
/// the ELF pair being compared.
|
|
///
|
|
/// @a2 the second set of arguments containing also the size information about
|
|
/// the ELF pair being compared.
|
|
bool
|
|
elf_size_is_greater(const compare_args_sptr &a1, const compare_args_sptr &a2)
|
|
{
|
|
off_t s1 = a1->elf1.size + a1->elf2.size;
|
|
off_t s2 = a2->elf1.size + a2->elf2.size;
|
|
|
|
return s1 > s2;
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// @return the status of the comparison.
|
|
static abidiff_status
|
|
compare(package& first_package,
|
|
package& second_package,
|
|
const options& opts,
|
|
abi_diff& diff)
|
|
{
|
|
if (!prepare_packages(first_package, second_package, opts))
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
|
|
// Setting debug-info path of libraries
|
|
string debug_dir1, debug_dir2, relative_debug_path = "/usr/lib/debug/";
|
|
if (first_package.debug_info_package()
|
|
&& second_package.debug_info_package())
|
|
{
|
|
debug_dir1 =
|
|
first_package.debug_info_package()->extracted_dir_path() +
|
|
relative_debug_path;
|
|
if (second_package.debug_info_package())
|
|
debug_dir2 =
|
|
second_package.debug_info_package()->extracted_dir_path() +
|
|
relative_debug_path;
|
|
}
|
|
|
|
abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
|
|
|
|
vector<compare_args_sptr> elf_pairs;
|
|
|
|
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))
|
|
{
|
|
elf_pairs.push_back(compare_args_sptr(new compare_args(*it->second,
|
|
debug_dir1, *iter->second,
|
|
debug_dir2, opts)));
|
|
}
|
|
else
|
|
{
|
|
diff.removed_binaries.push_back(it->second->name);
|
|
status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE;
|
|
status |= abigail::tools_utils::ABIDIFF_ABI_CHANGE;
|
|
}
|
|
}
|
|
|
|
// 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(elf_pairs.begin(), elf_pairs.end(), elf_size_is_greater);
|
|
|
|
size_t nprocs = opts.parallel ? sysconf(_SC_NPROCESSORS_ONLN) : 1;
|
|
assert(nprocs >= 1);
|
|
// There's no reason to spawn more threads than there are pairs to be diffed.
|
|
nprocs = std::min(nprocs, elf_pairs.size());
|
|
|
|
// if nprocs is zero, it means we have no binary to compare.
|
|
|
|
// We've identified the elf couples to compare, let's spawn NPROCS threads
|
|
// to do comparisons.
|
|
pthread_t thr;
|
|
vector<pthread_t> waitlist;
|
|
for (size_t i = 0; i < nprocs; ++i)
|
|
{
|
|
if (!pthread_create(&thr, /*attr=*/NULL,
|
|
reinterpret_cast<void*(*)(void*)>(pthread_routine_compare),
|
|
&elf_pairs))
|
|
// Record all the threads we will be waiting for.
|
|
waitlist.push_back(thr);
|
|
}
|
|
|
|
// Let's iterate over the valid ELF pairs in-order again, this time
|
|
// waiting for their diffs to come up from the other threads and reporting
|
|
// them ASAP.
|
|
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))
|
|
{
|
|
second_package.path_elf_file_sptr_map().erase(iter);
|
|
while (true)
|
|
{
|
|
pthread_mutex_lock(&map_lock);
|
|
corpora_report_map::iterator d = reports_map.find(it->second->path);
|
|
pthread_mutex_unlock(&map_lock);
|
|
|
|
if (d == reports_map.end())
|
|
// No result yet.
|
|
continue;
|
|
if (!d->second)
|
|
// The objects match.
|
|
break;
|
|
else
|
|
{
|
|
// Diff created -> report it.
|
|
string name = it->second->name;
|
|
diff.changed_binaries.push_back(name);
|
|
const string prefix = " ";
|
|
|
|
cout << "================ changes of '"
|
|
<< name
|
|
<< "'===============\n";
|
|
d->second->report(cout, prefix);
|
|
|
|
cout << "================ end of changes of '"
|
|
<< name
|
|
<< "'===============\n\n";
|
|
|
|
pthread_mutex_lock(&map_lock);
|
|
env_map.erase(d->second);
|
|
reports_map.erase(d);
|
|
pthread_mutex_unlock(&map_lock);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
abidiff_status *s;
|
|
// Join the comparison threads and collect the statuses.
|
|
for (vector<pthread_t>::iterator it = waitlist.begin(); it != waitlist.end();
|
|
++it)
|
|
{
|
|
pthread_join(*it, reinterpret_cast<void **>(&s));
|
|
status |= *s;
|
|
delete s;
|
|
}
|
|
|
|
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->name);
|
|
|
|
if (diff.removed_binaries.size())
|
|
{
|
|
cout << "Removed binaries:\n";
|
|
for (vector<string>::iterator it = diff.removed_binaries.begin();
|
|
it != diff.removed_binaries.end(); ++it)
|
|
cout << " " << *it << "\n";
|
|
}
|
|
|
|
if (opts.show_added_binaries && diff.added_binaries.size())
|
|
{
|
|
cout << "Added binaries:\n";
|
|
for (vector<string>::iterator it = diff.added_binaries.begin();
|
|
it != diff.added_binaries.end(); ++it)
|
|
cout << " " << *it << "\n";
|
|
}
|
|
|
|
if (!opts.keep_tmp_files)
|
|
{
|
|
erase_created_temporary_directories(first_package, second_package);
|
|
erase_created_temporary_directories_parent();
|
|
}
|
|
|
|
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 opts the options the current program has been called with.
|
|
///
|
|
/// @return the status of the comparison.
|
|
static abidiff_status
|
|
compare(package& first_package, package& second_package, const options& opts)
|
|
{
|
|
abi_diff diff;
|
|
return compare(first_package, second_package, opts, diff);
|
|
}
|
|
|
|
/// 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 = abigail::tools_utils::make_path_absolute(argv[i]).get();
|
|
else if (opts.package2.empty())
|
|
opts.package2 = abigail::tools_utils::make_path_absolute(argv[i]).get();
|
|
else
|
|
return false;
|
|
}
|
|
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_package1 =
|
|
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_package2 =
|
|
abigail::tools_utils::make_path_absolute(argv[j]).get();
|
|
++i;
|
|
}
|
|
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], "--no-linkage-name"))
|
|
opts.show_linkage_names = false;
|
|
else if (!strcmp(argv[i], "--redundant"))
|
|
opts.show_redundant_changes = true;
|
|
else if (!strcmp(argv[i], "--no-show-locs"))
|
|
opts.show_locs = false;
|
|
else if (!strcmp(argv[i], "--no-added-syms"))
|
|
opts.show_added_syms = 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"))
|
|
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], "--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], "--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]);
|
|
prog_options = &opts;
|
|
vector<package_sptr> packages;
|
|
if (!parse_command_line(argc, argv, opts))
|
|
{
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< "unrecognized option:" << opts.wrong_option
|
|
<< "\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.display_usage)
|
|
{
|
|
display_usage(argv[0], cout);
|
|
return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
|
|
| abigail::tools_utils::ABIDIFF_ERROR);
|
|
}
|
|
|
|
if (opts.display_version)
|
|
{
|
|
string major, minor, revision;
|
|
abigail::abigail_get_library_version(major, minor, revision);
|
|
cout << major << "." << minor << "." << revision << "\n";
|
|
return 0;
|
|
}
|
|
|
|
if (!maybe_check_suppression_files(opts))
|
|
return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
|
|
| abigail::tools_utils::ABIDIFF_ERROR);
|
|
|
|
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"));
|
|
|
|
if (!opts.debug_package1.empty())
|
|
first_package->debug_info_package
|
|
(package_sptr(new package(opts.debug_package1,
|
|
"debug_package1",
|
|
/*is_debug_info=*/true)));
|
|
|
|
if (!opts.debug_package2.empty())
|
|
second_package->debug_info_package
|
|
(package_sptr(new package(opts.debug_package2,
|
|
"debug_package2",
|
|
/*is_debug_info=*/true)));
|
|
|
|
switch (first_package->type())
|
|
{
|
|
case abigail::tools_utils::FILE_TYPE_RPM:
|
|
if (second_package->type() != abigail::tools_utils::FILE_TYPE_RPM)
|
|
{
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< opts.package2 << " should be an RPM file\n";
|
|
return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
|
|
| abigail::tools_utils::ABIDIFF_ERROR);
|
|
}
|
|
break;
|
|
|
|
case abigail::tools_utils::FILE_TYPE_DEB:
|
|
if (second_package->type() != abigail::tools_utils::FILE_TYPE_DEB)
|
|
{
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< opts.package2 << " 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->type() != abigail::tools_utils::FILE_TYPE_DIR)
|
|
{
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< opts.package2 << " 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->type() != abigail::tools_utils::FILE_TYPE_TAR)
|
|
{
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< opts.package2 << " should be a GNU tar archive\n";
|
|
return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
|
|
| abigail::tools_utils::ABIDIFF_ERROR);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
emit_prefix("abipkgdiff", cerr)
|
|
<< opts.package1 << " should be a valid package file \n";
|
|
return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
|
|
| abigail::tools_utils::ABIDIFF_ERROR);
|
|
}
|
|
|
|
return compare(*first_package, *second_package, opts);
|
|
}
|