mirror of
git://sourceware.org/git/libabigail.git
synced 2024-12-17 07:24:34 +00:00
adb656cc76
We have the kmidiff program that takes two Linux Kernel trees containing the vmlinux binary and its modules and compare their Kernel Module Interface, aka KMI. We need to be able to serialize (save in a file) a representation of that KMI. We need to load that KMI back, and compare two serialized KMIs. This patch implements the serialization of the KMI of a Linux Kernel tree. It actually serializes an instance of abigail::ir::corpus_group that is a collection of instances of abigail::ir::corpus. The KMI of each individual binary (vmlinux or kernel module) is represented by one abigail::ir::corpus. All the corpora share the same definitions of types and decls, whenever that makes sense. The patch thus factorizes the routines used to walk a Linux kernel out of the kmidiff program. These routines are then re-used in the abidw program to make it walk a Linux kernel tree (when the --linux-tree option is provided), load the vmlinux and module binaries as an instance of abigail::corpus_group and serialize it out into an output stream. * include/abg-tools-utils.h (check_dir) (get_binary_paths_from_kernel_dist) (build_corpus_group_from_kernel_dist_under): Declare new functions. The last two functions are being moved from tools/kmidiff.cc so that they can be re-used. * include/abg-writer.h (write_corpus): Declare one overload that takes a write_context parameter. (write_corpus_group): Declare three overloads of this new function. * src/abg-tools-utils.cc (check_dir): Define new function. (load_generate_apply_suppressions, is_vmlinux, is_kernel_module) (find_vmlinux_and_module_paths) (get_binary_paths_from_kernel_dist) (build_corpus_group_from_kernel_dist_under): Define new functions. * src/abg-writer.cc (write_context::set_annotate): Define new member function. (write_corpus): Add an overload that takes a write_context. Adapt the existing overload to make it use this new one. (write_corpus_group): Define this new function and two additional overloads for it. * tools/kmidiff.cc (set_suppressions, is_vmlinux) (is_kernel_module, find_vmlinux_and_module_paths) (get_binary_paths_from_kernel_dist) (build_corpus_group_from_kernel_dist_under): Remove. (main): Adjust the call to build_corpus_group_from_kernel_dist_under as its arguments are now adapted since it's been factorized out into abg-tools-utils.h. * tools/abidw.cc (options::corpus_group_for_linux): Define new data member. (options::options): Adjust. (display_usage): Add help strings for the new --linux-tree option. (load_corpus_and_write_abixml): Factorize this function out of the main function. (load_kernel_corpus_group_and_write_abixml): Define new function. (main): Use the factorized load_corpus_and_write_abixml and the new load_corpus_and_write_abixml functions. * tests/test-read-write.cc: Adjust. * doc/manuals/abidw.rst: Add documentation for the new --linux-tree option. Signed-off-by: Dodji Seketeli <dodji@redhat.com>
359 lines
9.6 KiB
C++
359 lines
9.6 KiB
C++
// -*- Mode: C++ -*-
|
|
//
|
|
// Copyright (C) 2017 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: Dodji Seketeli
|
|
|
|
/// @file
|
|
///
|
|
/// The source code of the Kernel Module Interface Diff tool.
|
|
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <fts.h>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <iostream>
|
|
|
|
#include "abg-config.h"
|
|
#include "abg-tools-utils.h"
|
|
#include "abg-corpus.h"
|
|
#include "abg-dwarf-reader.h"
|
|
#include "abg-comparison.h"
|
|
|
|
using std::string;
|
|
using std::vector;
|
|
using std::ostream;
|
|
using std::cout;
|
|
using std::cerr;
|
|
|
|
using namespace abigail::tools_utils;
|
|
using namespace abigail::dwarf_reader;
|
|
using namespace abigail::ir;
|
|
|
|
using abigail::comparison::diff_context_sptr;
|
|
using abigail::comparison::diff_context;
|
|
using abigail::comparison::translation_unit_diff_sptr;
|
|
using abigail::comparison::corpus_diff;
|
|
using abigail::comparison::corpus_diff_sptr;
|
|
using abigail::comparison::compute_diff;
|
|
using abigail::suppr::suppression_sptr;
|
|
using abigail::suppr::suppressions_type;
|
|
using abigail::suppr::read_suppressions;
|
|
using abigail::tools_utils::gen_suppr_spec_from_kernel_abi_whitelist;
|
|
|
|
/// The options of this program.
|
|
struct options
|
|
{
|
|
bool display_usage;
|
|
bool display_version;
|
|
bool verbose;
|
|
bool missing_operand;
|
|
string wrong_option;
|
|
string kernel_dist_root1;
|
|
string kernel_dist_root2;
|
|
vector<string> kabi_whitelist_paths;
|
|
vector<string> suppression_paths;
|
|
suppressions_type read_time_supprs;
|
|
suppressions_type diff_time_supprs;
|
|
|
|
options()
|
|
: display_usage(),
|
|
display_version(),
|
|
verbose(),
|
|
missing_operand()
|
|
{}
|
|
}; // end struct options.
|
|
|
|
/// Display the usage of the program.
|
|
///
|
|
/// @param prog_name the name of this program.
|
|
///
|
|
/// @param out the output stream the usage stream is sent to.
|
|
static void
|
|
display_usage(const string& prog_name, ostream& out)
|
|
{
|
|
emit_prefix(prog_name, out)
|
|
<< "usage: " << prog_name << " [options] kernel-package1 kernel-package2\n"
|
|
<< " where options can be:\n"
|
|
<< " --help|-h display this message\n"
|
|
<< " --version|-v display program version information and exit\n"
|
|
<< " --verbose display verbose messages\n"
|
|
<< " --suppressions|--suppr <path> specify a suppression file\n"
|
|
<< " --kmi-whitelist|-w <path> path to a kernel module interface "
|
|
"whitelist\n";
|
|
}
|
|
|
|
/// Parse the command line of the program.
|
|
///
|
|
/// @param argc the number of arguments on the command line, including
|
|
/// the program name.
|
|
///
|
|
/// @param argv the arguments on the command line, including the
|
|
/// program name.
|
|
///
|
|
/// @param opts the options resulting from the command line parsing.
|
|
///
|
|
/// @return true iff the command line parsing went fine.
|
|
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.kernel_dist_root1.empty())
|
|
opts.kernel_dist_root1 = argv[i];
|
|
else if (opts.kernel_dist_root2.empty())
|
|
opts.kernel_dist_root2 = argv[i];
|
|
else
|
|
return false;
|
|
}
|
|
else if (!strcmp(argv[i], "--verbose"))
|
|
opts.verbose = true;
|
|
else if (!strcmp(argv[i], "--version")
|
|
|| !strcmp(argv[i], "-v"))
|
|
{
|
|
opts.display_version = true;
|
|
return true;
|
|
}
|
|
else if (!strcmp(argv[i], "--help")
|
|
|| !strcmp(argv[i], "-h"))
|
|
{
|
|
opts.display_usage = true;
|
|
return true;
|
|
}
|
|
else if (!strcmp(argv[i], "--kmi-whitelist")
|
|
|| !strcmp(argv[i], "-w"))
|
|
{
|
|
int j = i + 1;
|
|
if (j >= argc)
|
|
{
|
|
opts.missing_operand = true;
|
|
opts.wrong_option = argv[i];
|
|
return false;
|
|
}
|
|
opts.kabi_whitelist_paths.push_back(argv[j]);
|
|
++i;
|
|
}
|
|
else if (!strcmp(argv[i], "--suppressions")
|
|
|| !strcmp(argv[i], "--suppr"))
|
|
{
|
|
int j = i + 1;
|
|
if (j >= argc)
|
|
{
|
|
opts.missing_operand = true;
|
|
opts.wrong_option = argv[i];
|
|
return false;
|
|
}
|
|
opts.suppression_paths.push_back(argv[j]);
|
|
++i;
|
|
}
|
|
}
|
|
|
|
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, "abidiff"))
|
|
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;
|
|
}
|
|
|
|
/// Setup the diff context from the program's options.
|
|
///
|
|
/// @param ctxt the diff context to consider.
|
|
///
|
|
/// @param opts the options to set the context.
|
|
static void
|
|
set_diff_context(diff_context_sptr ctxt, const options& opts)
|
|
{
|
|
ctxt->default_output_stream(&cout);
|
|
ctxt->error_output_stream(&cerr);
|
|
ctxt->show_relative_offset_changes(true);
|
|
ctxt->show_redundant_changes(false);
|
|
ctxt->show_locs(true);
|
|
ctxt->show_linkage_names(false);
|
|
ctxt->show_added_fns(false);
|
|
ctxt->show_added_vars(false);
|
|
ctxt->show_added_symbols_unreferenced_by_debug_info
|
|
(false);
|
|
ctxt->show_symbols_unreferenced_by_debug_info
|
|
(true);
|
|
|
|
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);
|
|
|
|
if (!opts.diff_time_supprs.empty())
|
|
ctxt->add_suppressions(opts.diff_time_supprs);
|
|
}
|
|
|
|
/// Print information about the kernel (and modules) binaries found
|
|
/// under a given directory.
|
|
///
|
|
/// Note that this function actually look for the modules iff the
|
|
/// --verbose option was provided.
|
|
///
|
|
/// @param root the directory to consider.
|
|
///
|
|
/// @param opts the options to use during the search.
|
|
static void
|
|
print_kernel_dist_binary_paths_under(const string& root, const options &opts)
|
|
{
|
|
string vmlinux;
|
|
vector<string> modules;
|
|
|
|
if (opts.verbose)
|
|
if (get_binary_paths_from_kernel_dist(root, vmlinux, modules))
|
|
{
|
|
cout << "Found kernel binaries under: '" << root << "'\n";
|
|
if (!vmlinux.empty())
|
|
cout << "[linux kernel binary]\n"
|
|
<< " '" << vmlinux << "'\n";
|
|
if (!modules.empty())
|
|
{
|
|
cout << "[linux kernel module binaries]\n";
|
|
for (vector<string>::const_iterator p = modules.begin();
|
|
p != modules.end();
|
|
++p)
|
|
cout << " '" << *p << "' \n";
|
|
}
|
|
cout << "\n";
|
|
}
|
|
}
|
|
|
|
int
|
|
main(int argc, char* argv[])
|
|
{
|
|
options opts;
|
|
if (!parse_command_line(argc, argv, opts))
|
|
{
|
|
emit_prefix(argv[0], cerr)
|
|
<< "unrecognized option: "
|
|
<< opts.wrong_option << "\n"
|
|
<< "try the --help option for more information\n";
|
|
return 1;
|
|
}
|
|
|
|
if (opts.missing_operand)
|
|
{
|
|
emit_prefix(argv[0], cerr)
|
|
<< "missing operand to option: " << opts.wrong_option <<"\n"
|
|
<< "try the --help option for more information\n";
|
|
return 1;
|
|
}
|
|
|
|
if (!maybe_check_suppression_files(opts))
|
|
return 1;
|
|
|
|
if (opts.display_usage)
|
|
{
|
|
display_usage(argv[0], cout);
|
|
return 1;
|
|
}
|
|
|
|
if (opts.display_version)
|
|
{
|
|
string major, minor, revision;
|
|
abigail::abigail_get_library_version(major, minor, revision);
|
|
emit_prefix(argv[0], cout)
|
|
<< major << "." << minor << "." << revision << "\n";
|
|
return 0;
|
|
}
|
|
|
|
environment_sptr env(new environment);
|
|
|
|
corpus_group_sptr group1, group2;
|
|
if (!opts.kernel_dist_root1.empty())
|
|
{
|
|
group1 =
|
|
build_corpus_group_from_kernel_dist_under(opts.kernel_dist_root1,
|
|
opts.suppression_paths,
|
|
opts.kabi_whitelist_paths,
|
|
opts.read_time_supprs,
|
|
opts.verbose,
|
|
env);
|
|
print_kernel_dist_binary_paths_under(opts.kernel_dist_root1, opts);
|
|
}
|
|
|
|
if (!opts.kernel_dist_root2.empty())
|
|
{
|
|
group2 =
|
|
build_corpus_group_from_kernel_dist_under(opts.kernel_dist_root2,
|
|
opts.suppression_paths,
|
|
opts.kabi_whitelist_paths,
|
|
opts.read_time_supprs,
|
|
opts.verbose,
|
|
env);
|
|
print_kernel_dist_binary_paths_under(opts.kernel_dist_root2, opts);
|
|
}
|
|
|
|
abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
|
|
if (group1 && group2)
|
|
{
|
|
diff_context_sptr diff_ctxt(new diff_context);
|
|
set_diff_context(diff_ctxt, opts);
|
|
|
|
corpus_diff_sptr diff= compute_diff(group1, group2, 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 (diff->has_changes())
|
|
diff->report(cout);
|
|
}
|
|
else
|
|
status = abigail::tools_utils::ABIDIFF_ERROR;
|
|
|
|
return status;
|
|
}
|