mirror of
git://sourceware.org/git/libabigail.git
synced 2024-12-24 18:52:15 +00:00
c7a71ba2d1
There are rare use cases where we do not want to compare the SONAME when testing libraries for compatiblity or diffing libraries. This adds an option to ignore the SONAME when doing the comparison. In these cases, we will edit the application's DT_NEEDED to point to the other library. This reuses the show_soname_change() function and slightly changes its meaning to not only control if the sonames are printed but also if they are compared. There didn't seem to be any other users of this function and slight semantic change seemed harmless. * doc/manuals/abicompat.rst - added new option * doc/manuals/abidiff.rst - added new option to manpage * src/abg-comparison.cc (compute_diff): don't bother comparing the sonames if you aren't going to print them. * tools/abicompat.cc (options::ignore_soname): Add new data member. (parse_command_line): Support the new --ignore-soname command line option. (display_usage): Add a description string for the new --ignore-soname command line option. (create_diff_context): Set the diff_context::show_soname_change from the new options::ignore_soname data member. * tools/abidiff.cc (options::ignore_soname): Add new data member. (display_usage): Add a description string for the new --ignore-soname command line option. (parse_command_line): Support the new --ignore-soname command line option. (set_diff_context_from_opts): Set the diff_context::show_soname_change from the new options::ignore_soname. Signed-off-by: Ben Woodard <woodard@redhat.com> Signed-off-by: Dodji Seketeli <dodji@redhat.com>
821 lines
25 KiB
C++
821 lines
25 KiB
C++
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
// -*- Mode: C++ -*-
|
|
//
|
|
// Copyright (C) 2014-2020 Red Hat, Inc.
|
|
//
|
|
// Author: Dodji Seketeli
|
|
|
|
/// @file
|
|
///
|
|
/// This program reads a program A, one library L in version V which A
|
|
/// links against, and the same library L in a different version, V+P.
|
|
/// The program then checks that A is still ABI compatible with L in
|
|
/// version V+P.
|
|
///
|
|
/// The program also comes with a "weak mode" in which just the
|
|
/// application and the library in version V+P need to be provided by
|
|
/// the user. In that case, the types of functions and variables of
|
|
/// the library that are consumed by the application are compared to
|
|
/// the types of the functions and variables expected by the
|
|
/// application. If they match exactly, then the types of functions
|
|
/// and variables that the application expects from the library are
|
|
/// honoured by the library. Otherwise, the library might provide
|
|
/// functions and variables that mean something different from what
|
|
/// the application expects and that might signal an ABI
|
|
/// incompatibility between what the application expects and what the
|
|
/// library provides.
|
|
|
|
#include <unistd.h>
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <string>
|
|
#include "abg-config.h"
|
|
#include "abg-tools-utils.h"
|
|
#include "abg-corpus.h"
|
|
#include "abg-dwarf-reader.h"
|
|
#include "abg-comparison.h"
|
|
#include "abg-suppression.h"
|
|
|
|
using std::string;
|
|
using std::cerr;
|
|
using std::cout;
|
|
using std::ostream;
|
|
using std::ofstream;
|
|
using std::vector;
|
|
using std::shared_ptr;
|
|
|
|
using abigail::tools_utils::emit_prefix;
|
|
|
|
class options
|
|
{
|
|
options();
|
|
|
|
public:
|
|
string prog_name;
|
|
string unknow_option;
|
|
string app_path;
|
|
string lib1_path;
|
|
string lib2_path;
|
|
shared_ptr<char> app_di_root_path;
|
|
shared_ptr<char> lib1_di_root_path;
|
|
shared_ptr<char> lib2_di_root_path;
|
|
vector<string> suppression_paths;
|
|
bool display_help;
|
|
bool display_version;
|
|
bool weak_mode;
|
|
bool list_undefined_symbols_only;
|
|
bool show_base_names;
|
|
bool show_redundant;
|
|
bool redundant_opt_set;
|
|
bool no_redundant_opt_set;
|
|
bool show_locs;
|
|
bool ignore_soname;
|
|
|
|
options(const char* program_name)
|
|
:prog_name(program_name),
|
|
display_help(),
|
|
display_version(),
|
|
weak_mode(),
|
|
list_undefined_symbols_only(),
|
|
show_base_names(),
|
|
show_redundant(true),
|
|
redundant_opt_set(),
|
|
no_redundant_opt_set(),
|
|
show_locs(true),
|
|
ignore_soname(false)
|
|
{}
|
|
}; // end struct options
|
|
|
|
static void
|
|
display_usage(const string& prog_name, ostream& out)
|
|
{
|
|
emit_prefix(prog_name, out)
|
|
<< "usage: " << prog_name
|
|
<< " [options] [application-path] [lib-v1-path] [lib-v2-path]"
|
|
<< "\n"
|
|
<< " where options can be: \n"
|
|
<< " --help|-h display this help message\n"
|
|
<< " --version|-v show program version information and exit\n"
|
|
<< " --list-undefined-symbols|-u display the list of "
|
|
"undefined symbols of the application\n"
|
|
<< " --show-base-names|b in the report, only show the base names "
|
|
" of the files; not the full paths\n"
|
|
<< " --app-debug-info-dir|--appd <path-to-app-debug-info> set the path "
|
|
"to the debug information directory for the application\n"
|
|
<< " --lib-debug-info-dir1|--libd1 <path-to-lib-debug-info1> set the path "
|
|
"to the debug information directory for the first library\n"
|
|
<< " --lib-debug-info-dir2|--libd2 <path-to-lib-debug-info2> set the path "
|
|
"to the debug information directory for the second library\n"
|
|
<< " --suppressions|--suppr <path> specify a suppression file\n"
|
|
<< " --no-redundant do not display redundant changes\n"
|
|
<< " --no-show-locs do now show location information\n"
|
|
<< " --ignore-soname do not take the SONAMEs into account\n"
|
|
<< " --redundant display redundant changes (this is the default)\n"
|
|
<< " --weak-mode check compatibility between the application and "
|
|
"just one version of the library.\n"
|
|
;
|
|
}
|
|
|
|
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.app_path.empty())
|
|
opts.app_path = argv[i];
|
|
else if (opts.lib1_path.empty())
|
|
opts.lib1_path = argv[i];
|
|
else if (opts.lib2_path.empty())
|
|
opts.lib2_path = argv[i];
|
|
else
|
|
return false;
|
|
}
|
|
else if (!strcmp(argv[i], "--version")
|
|
|| !strcmp(argv[i], "-v"))
|
|
{
|
|
opts.display_version = true;
|
|
return true;
|
|
}
|
|
else if (!strcmp(argv[i], "--list-undefined-symbols")
|
|
|| !strcmp(argv[i], "-u"))
|
|
opts.list_undefined_symbols_only = true;
|
|
else if (!strcmp(argv[i], "--show-base-names")
|
|
|| !strcmp(argv[i], "-b"))
|
|
opts.show_base_names = true;
|
|
else if (!strcmp(argv[i], "--app-debug-info-dir")
|
|
|| !strcmp(argv[i], "--appd"))
|
|
{
|
|
if (argc <= i + 1
|
|
|| argv[i + 1][0] == '-')
|
|
return false;
|
|
// elfutils wants the root path to the debug info to be
|
|
// absolute.
|
|
opts.app_di_root_path =
|
|
abigail::tools_utils::make_path_absolute(argv[i + 1]);
|
|
++i;
|
|
}
|
|
else if (!strcmp(argv[i], "--lib-debug-info-dir1")
|
|
|| !strcmp(argv[i], "--libd1"))
|
|
{
|
|
if (argc <= i + 1
|
|
|| argv[i + 1][0] == '-')
|
|
return false;
|
|
// elfutils wants the root path to the debug info to be
|
|
// absolute.
|
|
opts.lib1_di_root_path =
|
|
abigail::tools_utils::make_path_absolute(argv[i + 1]);
|
|
++i;
|
|
}
|
|
else if (!strcmp(argv[i], "--lib-debug-info-dir2")
|
|
|| !strcmp(argv[i], "--libd2"))
|
|
{
|
|
if (argc <= i + 1
|
|
|| argv[i + 1][0] == '-')
|
|
return false;
|
|
// elfutils wants the root path to the debug info to be
|
|
// absolute.
|
|
opts.lib2_di_root_path =
|
|
abigail::tools_utils::make_path_absolute(argv[i + 1]);
|
|
++i;
|
|
}
|
|
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], "--redundant"))
|
|
{
|
|
opts.show_redundant = true;
|
|
opts.redundant_opt_set = true;
|
|
}
|
|
else if (!strcmp(argv[i], "--no-redundant"))
|
|
{
|
|
opts.show_redundant = false;
|
|
opts.no_redundant_opt_set = true;
|
|
}
|
|
else if (!strcmp(argv[i], "--no-show-locs"))
|
|
opts.show_locs = false;
|
|
else if (!strcmp(argv[i], "--ignore-soname"))
|
|
opts.ignore_soname=true;
|
|
else if (!strcmp(argv[i], "--help")
|
|
|| !strcmp(argv[i], "-h"))
|
|
{
|
|
opts.display_help = true;
|
|
return true;
|
|
}
|
|
else if (!strcmp(argv[i], "--weak-mode"))
|
|
opts.weak_mode = true;
|
|
else
|
|
{
|
|
opts.unknow_option = argv[i];
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!opts.list_undefined_symbols_only)
|
|
{
|
|
if (opts.app_path.empty()
|
|
|| opts.lib1_path.empty())
|
|
return false;
|
|
if (!opts.weak_mode && opts.lib2_path.empty())
|
|
opts.weak_mode = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
using abigail::tools_utils::check_file;
|
|
using abigail::tools_utils::base_name;
|
|
using abigail::tools_utils::abidiff_status;
|
|
using abigail::ir::environment;
|
|
using abigail::ir::environment_sptr;
|
|
using abigail::corpus;
|
|
using abigail::corpus_sptr;
|
|
using abigail::ir::elf_symbols;
|
|
using abigail::ir::demangle_cplus_mangled_name;
|
|
using abigail::ir::type_base_sptr;
|
|
using abigail::ir::function_type_sptr;
|
|
using abigail::ir::function_decl;
|
|
using abigail::ir::var_decl;
|
|
using abigail::elf_reader::status;
|
|
using abigail::dwarf_reader::read_corpus_from_elf;
|
|
using abigail::comparison::diff_context_sptr;
|
|
using abigail::comparison::diff_context;
|
|
using abigail::comparison::diff_sptr;
|
|
using abigail::comparison::corpus_diff;
|
|
using abigail::comparison::corpus_diff_sptr;
|
|
using abigail::comparison::function_type_diff_sptr;
|
|
using abigail::comparison::compute_diff;
|
|
using abigail::suppr::suppression_sptr;
|
|
using abigail::suppr::suppressions_type;
|
|
using abigail::suppr::read_suppressions;
|
|
|
|
/// Create the context of a diff.
|
|
///
|
|
/// Create the diff context, initialize it and return a smart pointer
|
|
/// to it.
|
|
///
|
|
/// @param opts the options of the program.
|
|
///
|
|
/// @return a smart pointer to the newly created diff context.
|
|
static diff_context_sptr
|
|
create_diff_context(const options& opts)
|
|
{
|
|
diff_context_sptr ctxt(new diff_context());
|
|
ctxt->show_added_fns(false);
|
|
ctxt->show_added_vars(false);
|
|
ctxt->show_added_symbols_unreferenced_by_debug_info(false);
|
|
ctxt->show_linkage_names(true);
|
|
ctxt->show_redundant_changes(opts.show_redundant);
|
|
ctxt->show_locs(opts.show_locs);
|
|
// Intentional logic flip of ignore_soname
|
|
ctxt->show_soname_change(!opts.ignore_soname);
|
|
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_CATEGORY);
|
|
|
|
// Load suppression specifications, if there are any.
|
|
suppressions_type supprs;
|
|
for (vector<string>::const_iterator i = opts.suppression_paths.begin();
|
|
i != opts.suppression_paths.end();
|
|
++i)
|
|
if (check_file(*i, cerr, opts.prog_name))
|
|
read_suppressions(*i, supprs);
|
|
|
|
if (!supprs.empty())
|
|
ctxt->add_suppressions(supprs);
|
|
|
|
return ctxt;
|
|
}
|
|
|
|
/// Perform a compatibility check of an application corpus linked
|
|
/// against a first version of library corpus, with a second version
|
|
/// of the same library.
|
|
///
|
|
/// @param opts the options the tool got invoked with.
|
|
///
|
|
/// @param ctxt the context of the diff to be performed.
|
|
///
|
|
/// @param app_corpus the application corpus to consider.
|
|
///
|
|
/// @param lib1_corpus the library corpus that got linked with the
|
|
/// application which corpus is @p app_corpus.
|
|
///
|
|
/// @param lib2_corpus the second version of the library corpus @p
|
|
/// lib1_corpus. This function checks that the functions and
|
|
/// variables that @p app_corpus expects from lib1_corpus are still
|
|
/// present in @p lib2_corpus and that their types mean the same
|
|
/// thing.
|
|
///
|
|
/// @return a status bitfield.
|
|
static abidiff_status
|
|
perform_compat_check_in_normal_mode(options& opts,
|
|
diff_context_sptr& ctxt,
|
|
corpus_sptr app_corpus,
|
|
corpus_sptr lib1_corpus,
|
|
corpus_sptr lib2_corpus)
|
|
{
|
|
ABG_ASSERT(lib1_corpus);
|
|
ABG_ASSERT(lib2_corpus);
|
|
ABG_ASSERT(app_corpus);
|
|
|
|
abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
|
|
|
|
// compare lib1 and lib2 only by looking at the functions and
|
|
// variables which symbols are those undefined in the app.
|
|
|
|
for (elf_symbols::const_iterator i =
|
|
app_corpus->get_sorted_undefined_fun_symbols().begin();
|
|
i != app_corpus->get_sorted_undefined_fun_symbols().end();
|
|
++i)
|
|
{
|
|
string id = (*i)->get_id_string();
|
|
lib1_corpus->get_sym_ids_of_fns_to_keep().push_back(id);
|
|
lib2_corpus->get_sym_ids_of_fns_to_keep().push_back(id);
|
|
}
|
|
for (elf_symbols::const_iterator i =
|
|
app_corpus->get_sorted_undefined_var_symbols().begin();
|
|
i != app_corpus->get_sorted_undefined_var_symbols().end();
|
|
++i)
|
|
{
|
|
string id = (*i)->get_id_string();
|
|
lib1_corpus->get_sym_ids_of_vars_to_keep().push_back(id);
|
|
lib2_corpus->get_sym_ids_of_vars_to_keep().push_back(id);
|
|
}
|
|
|
|
if (!app_corpus->get_sorted_undefined_var_symbols().empty()
|
|
|| !app_corpus->get_sorted_undefined_fun_symbols().empty())
|
|
{
|
|
lib1_corpus->maybe_drop_some_exported_decls();
|
|
lib2_corpus->maybe_drop_some_exported_decls();
|
|
}
|
|
|
|
// Now really do the diffing.
|
|
corpus_diff_sptr changes = compute_diff(lib1_corpus, lib2_corpus, ctxt);
|
|
|
|
if (changes->has_net_changes())
|
|
{
|
|
string app_path = opts.app_path,
|
|
lib1_path = opts.lib1_path,
|
|
lib2_path = opts.lib2_path;
|
|
|
|
if (opts.show_base_names)
|
|
{
|
|
base_name(opts.app_path, app_path);
|
|
base_name(opts.lib1_path, lib1_path);
|
|
base_name(opts.lib2_path, lib2_path);
|
|
}
|
|
|
|
status |= abigail::tools_utils::ABIDIFF_ABI_CHANGE;
|
|
|
|
bool abi_broke_for_sure = changes->has_incompatible_changes();
|
|
|
|
cout << "ELF file '" << app_path << "'";
|
|
if (abi_broke_for_sure)
|
|
{
|
|
cout << " is not ";
|
|
status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE;
|
|
}
|
|
else
|
|
cout << " might not be ";
|
|
|
|
cout << "ABI compatible with '" << lib2_path
|
|
<< "' due to differences with '" << lib1_path
|
|
<< "' below:\n";
|
|
changes->report(cout);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/// A description of a change of the type of a function. It contains
|
|
/// the declaration of the function we are interested in, as well as
|
|
/// the differences found in the type of that function.
|
|
struct fn_change
|
|
{
|
|
function_decl* decl;
|
|
function_type_diff_sptr diff;
|
|
|
|
fn_change()
|
|
: decl()
|
|
{}
|
|
|
|
fn_change(function_decl* decl,
|
|
function_type_diff_sptr difference)
|
|
: decl(decl),
|
|
diff(difference)
|
|
{}
|
|
}; // end struct fn_change
|
|
|
|
/// An description of a change of the type of a variable. It contains
|
|
/// the declaration of the variable we are interested in, as well as
|
|
/// the differences found in the type of that variable.
|
|
struct var_change
|
|
{
|
|
var_decl* decl;
|
|
diff_sptr diff;
|
|
|
|
var_change()
|
|
: decl()
|
|
{}
|
|
|
|
var_change(var_decl* var,
|
|
diff_sptr difference)
|
|
: decl(var),
|
|
diff(difference)
|
|
{}
|
|
}; // end struct var_change
|
|
|
|
/// Perform a compatibility check of an application corpus and a
|
|
/// library corpus.
|
|
///
|
|
/// The types of the variables and functions exported by the library
|
|
/// and consumed by the application are compared with the types
|
|
/// expected by the application. This function checks that the types
|
|
/// mean the same thing; otherwise it emits on standard output type
|
|
/// layout differences found.
|
|
///
|
|
/// @param opts the options the tool got invoked with.
|
|
///
|
|
/// @param app_corpus the application corpus to consider.
|
|
///
|
|
/// @param lib_corpus the library corpus to consider.
|
|
///
|
|
/// @return a status bitfield.
|
|
static abidiff_status
|
|
perform_compat_check_in_weak_mode(options& opts,
|
|
diff_context_sptr& ctxt,
|
|
corpus_sptr app_corpus,
|
|
corpus_sptr lib_corpus)
|
|
{
|
|
ABG_ASSERT(lib_corpus);
|
|
ABG_ASSERT(app_corpus);
|
|
|
|
abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
|
|
|
|
// Functions and variables defined and exported by lib_corpus which
|
|
// symbols are undefined in app_corpus are the artifacts we are
|
|
// interested in.
|
|
//
|
|
// So let's drop all functions and variables from lib_corpus that
|
|
// are so that their symbols are *NOT* undefined in app_corpus.
|
|
//
|
|
// In other words, let's only keep the functiond and variables from
|
|
// lib_corpus that are consumed by app_corpus.
|
|
|
|
for (elf_symbols::const_iterator i =
|
|
app_corpus->get_sorted_undefined_fun_symbols().begin();
|
|
i != app_corpus->get_sorted_undefined_fun_symbols().end();
|
|
++i)
|
|
{
|
|
string id = (*i)->get_id_string();
|
|
lib_corpus->get_sym_ids_of_fns_to_keep().push_back(id);
|
|
}
|
|
|
|
for (elf_symbols::const_iterator i =
|
|
app_corpus->get_sorted_undefined_var_symbols().begin();
|
|
i != app_corpus->get_sorted_undefined_var_symbols().end();
|
|
++i)
|
|
{
|
|
string id = (*i)->get_id_string();
|
|
lib_corpus->get_sym_ids_of_vars_to_keep().push_back(id);
|
|
}
|
|
|
|
if (!app_corpus->get_sorted_undefined_var_symbols().empty()
|
|
|| !app_corpus->get_sorted_undefined_fun_symbols().empty())
|
|
lib_corpus->maybe_drop_some_exported_decls();
|
|
|
|
// OK now, lib_corpus only contains functions and variables which
|
|
// symbol are consumed by app_corpus.
|
|
|
|
// So we are now going to compare the functions that are exported by
|
|
// lib_corpus against those that app_corpus expects.
|
|
//
|
|
// In other words, the functions which symbols are defined by
|
|
// lib_corpus are going to be compared to the functions and
|
|
// variables which are undefined in app_corpus.
|
|
|
|
{
|
|
function_type_sptr lib_fn_type, app_fn_type;
|
|
vector<fn_change> fn_changes;
|
|
for (corpus::functions::const_iterator i =
|
|
lib_corpus->get_functions().begin();
|
|
i != lib_corpus->get_functions().end();
|
|
++i)
|
|
{
|
|
// lib_fn_type contains the type of a function that is defined
|
|
// in lib_corpus.
|
|
lib_fn_type = (*i)->get_type();
|
|
ABG_ASSERT(lib_fn_type);
|
|
|
|
// app_fn_type contains the the "version" of lib_fn_type that
|
|
// is expected by app_corpus.
|
|
app_fn_type = lookup_or_synthesize_fn_type(lib_fn_type, *app_corpus);
|
|
|
|
// Now lets compare the type expected by app_corpus against
|
|
// the type actually provided by lib_fn_type.
|
|
function_type_diff_sptr fn_type_diff;
|
|
if (app_fn_type)
|
|
fn_type_diff = compute_diff(app_fn_type, lib_fn_type, ctxt);
|
|
|
|
// If the two types of functions are different, then let's
|
|
// store their difference in the "fn_changes" vector.
|
|
if (fn_type_diff && fn_type_diff->to_be_reported())
|
|
fn_changes.push_back(fn_change(*i, fn_type_diff));
|
|
}
|
|
|
|
string lib1_path = opts.lib1_path, app_path = opts.app_path;
|
|
if (opts.show_base_names)
|
|
{
|
|
base_name(opts.lib1_path, lib1_path);
|
|
base_name(opts.app_path, app_path);
|
|
}
|
|
|
|
// If some function changes were detected, then report them.
|
|
if (!fn_changes.empty())
|
|
{
|
|
cout << "functions defined in library "
|
|
<< "'" << lib1_path << "'\n"
|
|
<< "have sub-types that are different from what application "
|
|
<< "'" << app_path << "' "
|
|
<< "expects:\n\n";
|
|
for (vector<fn_change>::const_iterator i = fn_changes.begin();
|
|
i != fn_changes.end();
|
|
++i)
|
|
{
|
|
cout << " "
|
|
<< i->decl->get_pretty_representation()
|
|
<< ":\n";
|
|
i->diff->report(cout, " ");
|
|
cout << "\n";
|
|
}
|
|
}
|
|
|
|
if (!fn_changes.empty())
|
|
status |= abigail::tools_utils::ABIDIFF_ABI_CHANGE;
|
|
|
|
// OK now, let's do something similar for *variables* changes.
|
|
//
|
|
// That is, let's compare the variables expected by app_corpus
|
|
// against the variables actually provided by lib_corpus and
|
|
// report the difference that might have been found.
|
|
|
|
type_base_sptr lib_var_type, app_var_type;
|
|
vector<var_change> var_changes;
|
|
for (corpus::variables::const_iterator i =
|
|
lib_corpus->get_variables().begin();
|
|
i != lib_corpus->get_variables().end();
|
|
++i)
|
|
{
|
|
lib_var_type = (*i)->get_type();
|
|
ABG_ASSERT(lib_var_type);
|
|
app_var_type = lookup_type(lib_var_type, *app_corpus);
|
|
diff_sptr type_diff;
|
|
if (app_var_type)
|
|
type_diff = compute_diff(app_var_type, lib_var_type, ctxt);
|
|
if (type_diff && type_diff->to_be_reported())
|
|
var_changes.push_back(var_change(*i, type_diff));
|
|
}
|
|
if (!var_changes.empty())
|
|
{
|
|
cout << "variables defined in library "
|
|
<< "'" << lib1_path << "'\n"
|
|
<< "have sub-types that are different from what application "
|
|
<< "'" << app_path << "' "
|
|
<< "expects:\n\n";
|
|
for (vector<var_change>::const_iterator i = var_changes.begin();
|
|
i != var_changes.end();
|
|
++i)
|
|
{
|
|
cout << " "
|
|
<< i->decl->get_pretty_representation()
|
|
<< ":\n";
|
|
i->diff->report(cout, " ");
|
|
cout << "\n";
|
|
}
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
int
|
|
main(int argc, char* argv[])
|
|
{
|
|
options opts(argv[0]);
|
|
|
|
if (!parse_command_line(argc, argv, opts))
|
|
{
|
|
if (!opts.unknow_option.empty())
|
|
{
|
|
emit_prefix(argv[0], cerr)
|
|
<< "unrecognized option: " << opts.unknow_option << "\n"
|
|
<< "try the --help option for more information\n";
|
|
return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
|
|
| abigail::tools_utils::ABIDIFF_ERROR);
|
|
}
|
|
|
|
emit_prefix(argv[0], cerr)
|
|
<< "wrong invocation\n"
|
|
<< "try the --help option for more information\n";
|
|
return (abigail::tools_utils::ABIDIFF_USAGE_ERROR
|
|
| abigail::tools_utils::ABIDIFF_ERROR);
|
|
}
|
|
|
|
if (opts.display_help)
|
|
{
|
|
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.weak_mode && !opts.lib2_path.empty())
|
|
{
|
|
emit_prefix(argv[0], cout)
|
|
<< "WARNING: The \'--weak-mode\' option is used. The "
|
|
<< opts.lib2_path << " will be ignored automatically\n";
|
|
}
|
|
|
|
if (opts.redundant_opt_set && opts.no_redundant_opt_set)
|
|
{
|
|
emit_prefix(argv[0], cerr)
|
|
<< "ERROR: The \'--redundant\' and '--no-redundant' option are in conflict. "
|
|
<< "Please select only one option to use.\n";
|
|
return 1;
|
|
}
|
|
|
|
ABG_ASSERT(!opts.app_path.empty());
|
|
if (!abigail::tools_utils::check_file(opts.app_path, cerr, opts.prog_name))
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
|
|
abigail::tools_utils::file_type type =
|
|
abigail::tools_utils::guess_file_type(opts.app_path);
|
|
if (type != abigail::tools_utils::FILE_TYPE_ELF)
|
|
{
|
|
emit_prefix(argv[0], cerr)
|
|
<< opts.app_path << " is not an ELF file\n";
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
}
|
|
|
|
// Create the context of the diff
|
|
diff_context_sptr ctxt = create_diff_context(opts);
|
|
|
|
// Check if any suppression specification prevents us from
|
|
// performing the compatibility checking.
|
|
suppressions_type& supprs = ctxt->suppressions();
|
|
bool files_suppressed = (file_is_suppressed(opts.app_path, supprs)
|
|
|| file_is_suppressed(opts.lib1_path, supprs)
|
|
||file_is_suppressed(opts.lib2_path, supprs));
|
|
|
|
if (files_suppressed)
|
|
// We don't have to compare anything because a user
|
|
// suppression specification file instructs us to avoid
|
|
// loading either one of the input files.
|
|
return abigail::tools_utils::ABIDIFF_OK;
|
|
|
|
// Read the application ELF file.
|
|
char * app_di_root = opts.app_di_root_path.get();
|
|
vector<char**> app_di_roots;
|
|
app_di_roots.push_back(&app_di_root);
|
|
status status = abigail::elf_reader::STATUS_UNKNOWN;
|
|
environment_sptr env(new environment);
|
|
corpus_sptr app_corpus=
|
|
read_corpus_from_elf(opts.app_path,
|
|
app_di_roots, env.get(),
|
|
/*load_all_types=*/opts.weak_mode,
|
|
status);
|
|
|
|
if (status & abigail::elf_reader::STATUS_NO_SYMBOLS_FOUND)
|
|
{
|
|
emit_prefix(argv[0], cerr)
|
|
<< "could not read symbols from " << opts.app_path << "\n";
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
}
|
|
if (!(status & abigail::elf_reader::STATUS_OK))
|
|
{
|
|
emit_prefix(argv[0], cerr)
|
|
<< "could not read file " << opts.app_path << "\n";
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
}
|
|
|
|
if (opts.list_undefined_symbols_only)
|
|
{
|
|
for (elf_symbols::const_iterator i =
|
|
app_corpus->get_sorted_undefined_fun_symbols().begin();
|
|
i != app_corpus->get_sorted_undefined_fun_symbols().end();
|
|
++i)
|
|
{
|
|
string id = (*i)->get_id_string();
|
|
string sym_name = (*i)->get_name();
|
|
string demangled_name = demangle_cplus_mangled_name(sym_name);
|
|
if (demangled_name != sym_name)
|
|
cout << demangled_name << " {" << id << "}\n";
|
|
else
|
|
cout << id << "\n";
|
|
}
|
|
return abigail::tools_utils::ABIDIFF_OK;
|
|
}
|
|
|
|
// Read the first version of the library.
|
|
ABG_ASSERT(!opts.lib1_path.empty());
|
|
if (!abigail::tools_utils::check_file(opts.lib1_path, cerr, opts.prog_name))
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
type = abigail::tools_utils::guess_file_type(opts.lib1_path);
|
|
if (type != abigail::tools_utils::FILE_TYPE_ELF)
|
|
{
|
|
emit_prefix(argv[0], cerr) << opts.lib1_path << " is not an ELF file\n";
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
}
|
|
|
|
char * lib1_di_root = opts.lib1_di_root_path.get();
|
|
vector<char**> lib1_di_roots;
|
|
lib1_di_roots.push_back(&lib1_di_root);
|
|
corpus_sptr lib1_corpus = read_corpus_from_elf(opts.lib1_path,
|
|
lib1_di_roots, env.get(),
|
|
/*load_all_types=*/false,
|
|
status);
|
|
if (status & abigail::elf_reader::STATUS_DEBUG_INFO_NOT_FOUND)
|
|
emit_prefix(argv[0], cerr)
|
|
<< "could not read debug info for " << opts.lib1_path << "\n";
|
|
if (status & abigail::elf_reader::STATUS_NO_SYMBOLS_FOUND)
|
|
{
|
|
cerr << "could not read symbols from " << opts.lib1_path << "\n";
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
}
|
|
if (!(status & abigail::elf_reader::STATUS_OK))
|
|
{
|
|
emit_prefix(argv[0], cerr)
|
|
<< "could not read file " << opts.lib1_path << "\n";
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
}
|
|
|
|
// Read the second version of the library.
|
|
corpus_sptr lib2_corpus;
|
|
if (!opts.weak_mode)
|
|
{
|
|
ABG_ASSERT(!opts.lib2_path.empty());
|
|
char * lib2_di_root = opts.lib2_di_root_path.get();
|
|
vector<char**> lib2_di_roots;
|
|
lib2_di_roots.push_back(&lib2_di_root);
|
|
lib2_corpus = read_corpus_from_elf(opts.lib2_path,
|
|
lib2_di_roots, env.get(),
|
|
/*load_all_types=*/false,
|
|
status);
|
|
if (status & abigail::elf_reader::STATUS_DEBUG_INFO_NOT_FOUND)
|
|
emit_prefix(argv[0], cerr)
|
|
<< "could not read debug info for " << opts.lib2_path << "\n";
|
|
if (status & abigail::elf_reader::STATUS_NO_SYMBOLS_FOUND)
|
|
{
|
|
emit_prefix(argv[0], cerr)
|
|
<< "could not read symbols from " << opts.lib2_path << "\n";
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
}
|
|
if (!(status & abigail::elf_reader::STATUS_OK))
|
|
{
|
|
emit_prefix(argv[0], cerr)
|
|
<< "could not read file " << opts.lib2_path << "\n";
|
|
return abigail::tools_utils::ABIDIFF_ERROR;
|
|
}
|
|
}
|
|
|
|
abidiff_status s = abigail::tools_utils::ABIDIFF_OK;
|
|
|
|
if (opts.weak_mode)
|
|
s = perform_compat_check_in_weak_mode(opts, ctxt,
|
|
app_corpus,
|
|
lib1_corpus);
|
|
else
|
|
s = perform_compat_check_in_normal_mode(opts, ctxt,
|
|
app_corpus,
|
|
lib1_corpus,
|
|
lib2_corpus);
|
|
|
|
return s;
|
|
}
|