Initial support of the serialization of the KMI of a Linux Kernel Tree

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>
This commit is contained in:
Dodji Seketeli 2017-04-06 12:00:51 +02:00
parent 1d6cb6fdbc
commit adb656cc76
8 changed files with 782 additions and 420 deletions

View File

@ -8,6 +8,16 @@ also includes a representation of the globally defined ELF symbols of
the file. The input shared library must contain associated debug
information in `DWARF`_ format.
When given the ``--linux-tree`` option, this program can also handle a
Linux kernel tree. That is, a directory tree that contains both the
vmlinux binary and Linux kernel modules. It analyses those Linux
kernel binaries and emits an XML representation of the interface
between the kernel and its module, to standard output. In this case,
we don't call it an ABI, but a KMI (Kernel Module Interface). The
emitted KMI includes all the globally defined functions and variables,
along with a complete representation of their types. The input
binaries must contain associated debug information in `DWARF`_ format.
Invocation
==========
@ -85,6 +95,28 @@ Options
provided -- then the entire KMI, that is, all publicly defined and
exported functions and global variables by the Linux Kernel
binaries is emitted.
* ``--linux-tree | --lt``
Make ``abidw`` to consider the input path as a path to a directory
containing the vmlinux binary as several kernel modules binaries.
In that case, this program emits the representation of the Kernel
Module Interface (KMI) on the standard output.
Below is an example of usage of ``abidw`` on a Linux Kernel tree.
First, checkout a Linux kernel source tree and build it. Then
install the kernel modules in a directory somewhere. Copy the
vmlinux binary into that directory too. And then serialize the
KMI of that kernel to disk, using ``abidw``: ::
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
$ cd linux && git checkout v4.5
$ make allyesconfig all
$ mkdir build-output
$ make INSTALL_MOD_PATH=./build-output modules_install
$ cp vmlinux build-output/modules/4.5.0
$ abidw --linux-tree build-output/modules/4.5.0 > build-output/linux-4.5.0.kmi
* ``--headers-dir | --hd`` <headers-directory-path-1>

View File

@ -55,6 +55,7 @@ bool ensure_dir_path_created(const string&);
bool ensure_parent_dir_created(const string&);
ostream& emit_prefix(const string& prog_name, ostream& out);
bool check_file(const string& path, ostream& out, const string& prog_name = "");
bool check_dir(const string& path, ostream& out, const string& prog_name="");
bool string_ends_with(const string&, const string&);
bool string_begins_with(const string&, const string&);
bool string_is_ascii(const string&);
@ -67,6 +68,17 @@ bool
gen_suppr_spec_from_kernel_abi_whitelist(const string& abi_whitelist_path,
suppr::suppressions_type& s);
bool
get_binary_paths_from_kernel_dist(const string& dist_root,
string& vmlinux_path,
vector<string>& module_paths,
string& debug_info_root_path);
bool
get_binary_paths_from_kernel_dist(const string& dist_root,
string& vmlinux_path,
vector<string>& module_paths);
string
get_default_system_suppression_file_path();
@ -235,6 +247,14 @@ file_is_kernel_debuginfo_package(string& file_path,
std::tr1::shared_ptr<char>
make_path_absolute(const char*p);
corpus_group_sptr
build_corpus_group_from_kernel_dist_under(const string& root,
vector<string>& suppr_paths,
vector<string>& kabi_wl_paths,
suppr::suppressions_type& supprs,
bool verbose,
environment_sptr& env);
extern const char* PRIVATE_TYPES_SUPPR_SPEC_NAME;
}// end namespace tools_utils

View File

@ -66,6 +66,13 @@ bool
write_corpus_to_archive(const corpus_sptr corp,
const bool annotate = false);
bool
write_corpus(const corpus_sptr corpus,
unsigned indent,
write_context& ctxt,
std::ostream& out,
const bool annotate = false);
bool
write_corpus(const corpus_sptr corpus,
unsigned indent,
@ -78,6 +85,25 @@ write_corpus(const corpus_sptr corpus,
const string& path,
const bool annotate = false);
bool
write_corpus_group(const corpus_group_sptr& group,
unsigned indent,
write_context& ctxt,
std::ostream& out,
const bool annotate = false);
bool
write_corpus_group(const corpus_group_sptr& group,
unsigned indent,
std::ostream& out,
const bool annotate = false);
bool
write_corpus_group(const corpus_group_sptr& group,
unsigned indent,
const string& path,
const bool annotate = false);
}// end namespace xml_writer
}// end namespace abigail

View File

@ -35,6 +35,7 @@
#include <iostream>
#include <sstream>
#include "abg-dwarf-reader.h"
#include "abg-internal.h"
// <headers defining libabigail's API go under here>
ABG_BEGIN_EXPORT_DECLARATIONS
@ -444,6 +445,34 @@ check_file(const string& path, ostream& out, const string& prog_name)
return true;
}
/// Check if a given path exists, is readable and is a directory.
///
/// @param path the path to consider.
///
/// @param out the out stream to report errors to.
///
/// @param prog_name the program name on behalf of which to report the
/// error, if any.
///
/// @return true iff @p path exists and is for a directory.
bool
check_dir(const string& path, ostream& out, const string& prog_name)
{
if (!file_exists(path))
{
emit_prefix(prog_name, out) << "path " << path << " does not exist\n";
return false;
}
if (!is_dir(path))
{
emit_prefix(prog_name, out) << path << " is not a directory\n";
return false;
}
return true;
}
/// Test if a given string ends with a particular suffix.
///
/// @param str the string to consider.
@ -1305,6 +1334,349 @@ load_default_user_suppressions(suppr::suppressions_type& supprs)
read_suppressions(default_user_suppr_path, supprs);
}
/// If we were given suppression specification files or kabi whitelist
/// files, this function parses those, come up with suppression
/// specifications as a result, and set them to the read context.
///
/// @param read_ctxt the read context to consider.
///
/// @param suppr_paths paths to suppression specification files that
/// we were given. If empty, it means we were not given any
/// suppression specification path.
///
/// @param kabi_whitelist_paths paths to kabi whitelist files that we
/// were given. If empty, it means we were not given any kabi
/// whitelist.
///
/// @param supprs the suppressions specifications resulting from
/// parsing the suppression specification files at @p suppr_paths and
/// the kabi whitelist at @p kabi_whitelist_paths.
///
/// @param opts the options to consider.
static void
load_generate_apply_suppressions(dwarf_reader::read_context &read_ctxt,
vector<string>& suppr_paths,
vector<string>& kabi_whitelist_paths,
suppressions_type& supprs)
{
if (supprs.empty())
{
for (vector<string>::const_iterator i = suppr_paths.begin();
i != suppr_paths.end();
++i)
read_suppressions(*i, supprs);
for (vector<string>::const_iterator i =
kabi_whitelist_paths.begin();
i != kabi_whitelist_paths.end();
++i)
gen_suppr_spec_from_kernel_abi_whitelist(*i, supprs);
}
abigail::dwarf_reader::add_read_context_suppressions(read_ctxt, supprs);
}
/// Test if an FTSENT pointer (resulting from fts_read) represents the
/// vmlinux binary.
///
/// @param entry the FTSENT to consider.
///
/// @return true iff @p entry is for a vmlinux binary.
static bool
is_vmlinux(const FTSENT *entry)
{
if (entry == NULL
|| (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
|| entry->fts_info == FTS_ERR
|| entry->fts_info == FTS_NS)
return false;
string fname = entry->fts_name;
if (fname == "vmlinux")
{
string dirname;
dir_name(entry->fts_path, dirname);
if (string_ends_with(dirname, "compressed"))
return false;
return true;
}
return false;
}
/// Test if an FTSENT pointer (resulting from fts_read) represents a a
/// linux kernel module binary.
///
/// @param entry the FTSENT to consider.
///
/// @return true iff @p entry is for a linux kernel module binary.
static bool
is_kernel_module(const FTSENT *entry)
{
if (entry == NULL
|| (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
|| entry->fts_info == FTS_ERR
|| entry->fts_info == FTS_NS)
return false;
string fname = entry->fts_name;
if (string_ends_with(fname, ".ko")
|| string_ends_with(fname, ".ko.xz")
|| string_ends_with(fname, ".ko.gz"))
return true;
return false;
}
/// Find a vmlinux and its kernel modules in a given directory tree.
///
/// @param from the directory tree to start looking from.
///
/// @param vmlinux_path output parameter. This is set to the path
/// where the vmlinux binary is found. This is set iff the returns
/// true.
///
/// @param module_paths output parameter. This is set to the paths of
/// the linux kernel module binaries.
///
/// @return true iff at least the vmlinux binary was found.
static bool
find_vmlinux_and_module_paths(const string& from,
string &vmlinux_path,
vector<string> &module_paths)
{
char* path[] = {const_cast<char*>(from.c_str()), 0};
FTS *file_hierarchy = fts_open(path, FTS_PHYSICAL|FTS_NOCHDIR|FTS_XDEV, 0);
if (!file_hierarchy)
return false;
bool found_vmlinux = false;
FTSENT *entry;
while ((entry = fts_read(file_hierarchy)))
{
// Skip descendents of symbolic links.
if (entry->fts_info == FTS_SL || entry->fts_info == FTS_SLNONE)
{
fts_set(file_hierarchy, entry, FTS_SKIP);
continue;
}
if (!found_vmlinux && is_vmlinux(entry))
{
vmlinux_path = entry->fts_path;
found_vmlinux = true;
}
else if (is_kernel_module(entry))
module_paths.push_back(entry->fts_path);
}
fts_close(file_hierarchy);
return found_vmlinux;
}
/// Get the paths of the vmlinux and kernel module binaries under
/// given directory.
///
/// @param dist_root the directory under which to look for.
///
/// @param vmlinux_path output parameter. The path of the vmlinux
/// binary that was found.
///
/// @param module_paths output parameter. The paths of the kernel
/// module binaries that were found.
///
/// @param debug_info_root_path output parameter. If a debug info
/// sub-directory is found under @p dist_root, it's set to this
/// parameter.
///
/// @return true if at least the path to the vmlinux binary was found.
bool
get_binary_paths_from_kernel_dist(const string& dist_root,
string& vmlinux_path,
vector<string>& module_paths,
string& debug_info_root_path)
{
if (!dir_exists(dist_root))
return false;
// For now, we assume either an Enterprise Linux or a Fedora kernel
// distribution directory.
//
// We also take into account split debug info package for these. In
// this case, the content split debug info package is installed
// under the 'dist_root' directory as well, and its content is
// accessible from <dist_root>/usr/lib/debug directory.
string kernel_modules_root;
string debug_info_root;
if (dir_exists(dist_root + "/lib/modules"))
{
dist_root + "/lib/modules";
debug_info_root = dist_root + "/usr/lib/debug";
}
if (dir_is_empty(debug_info_root))
debug_info_root.clear();
bool found = false;
string from = !debug_info_root.empty() ? debug_info_root : dist_root;
if (find_vmlinux_and_module_paths(from, vmlinux_path, module_paths))
found = true;
debug_info_root_path = debug_info_root;
return found;
}
/// Get the paths of the vmlinux and kernel module binaries under
/// given directory.
///
/// @param dist_root the directory under which to look for.
///
/// @param vmlinux_path output parameter. The path of the vmlinux
/// binary that was found.
///
/// @param module_paths output parameter. The paths of the kernel
/// module binaries that were found.
///
/// @return true if at least the path to the vmlinux binary was found.
bool
get_binary_paths_from_kernel_dist(const string& dist_root,
string& vmlinux_path,
vector<string>& module_paths)
{
string debug_info_root_path;
return get_binary_paths_from_kernel_dist(dist_root,
vmlinux_path,
module_paths,
debug_info_root_path);
}
/// Walk a given directory and build an instance of @ref corpus_group
/// from the vmlinux kernel binary and the linux kernel modules found
/// under that directory and under its sub-directories, recursively.
///
/// The main corpus of the @ref corpus_group is made of the vmlinux
/// binary. The other corpora are made of the linux kernel binaries.
///
/// @param root the path of the directory under which vmlinux and its
/// kernel modules are to be found.
///
/// @param opts the options to use during the search.
///
/// @param env the environment to create the corpus_group in.
corpus_group_sptr
build_corpus_group_from_kernel_dist_under(const string& root,
vector<string>& suppr_paths,
vector<string>& kabi_wl_paths,
suppressions_type& supprs,
bool verbose,
environment_sptr& env)
{
corpus_group_sptr result;
string vmlinux;
vector<string> modules;
string debug_info_root_path;
if (verbose)
std::cout << "Analysing kernel dist root '"
<< root << "' ... " << std::flush;
bool got_binary_paths =
get_binary_paths_from_kernel_dist(root, vmlinux, modules,
debug_info_root_path);
if (verbose)
std::cout << "DONE\n";
if (got_binary_paths)
{
shared_ptr<char> di_root =
make_path_absolute(debug_info_root_path.c_str());
char *di_root_ptr = di_root.get();
abigail::dwarf_reader::status status = abigail::dwarf_reader::STATUS_OK;
corpus_group_sptr group;
if (!vmlinux.empty())
{
dwarf_reader::read_context_sptr ctxt =
dwarf_reader::create_read_context(vmlinux, &di_root_ptr, env.get(),
/*read_all_types=*/false,
/*linux_kernel_mode=*/true);
load_generate_apply_suppressions(*ctxt, suppr_paths,
kabi_wl_paths, supprs);
// If we have been given a whitelist of functions and
// variable symbols to look at, then we can avoid loading
// and analyzing the ELF symbol table.
bool do_ignore_symbol_table = !kabi_wl_paths.empty();
set_ignore_symbol_table(*ctxt, do_ignore_symbol_table);
group.reset(new corpus_group(env.get(), root));
set_read_context_corpus_group(*ctxt, group);
if (verbose)
std::cout << "reading kernel binary '"
<< vmlinux << "' ... " << std::flush;
// Read the vmlinux corpus and add it to the group.
read_and_add_corpus_to_group_from_elf(*ctxt, *group, status);
if (verbose)
std::cout << " DONE\n";
}
if (!group->is_empty())
{
// Now add the corpora of the modules to the corpus group.
int total_nb_modules = modules.size();
int cur_module_index = 1;
for (vector<string>::const_iterator m = modules.begin();
m != modules.end();
++m, ++cur_module_index)
{
if (verbose)
std::cout << "reading module '"
<< *m << "' ("
<< cur_module_index
<< "/" << total_nb_modules
<< ") ... " << std::flush;
dwarf_reader::read_context_sptr module_ctxt =
dwarf_reader::create_read_context(*m, &di_root_ptr, env.get(),
/*read_all_types=*/false,
/*linux_kernel_mode=*/true);
// If we have been given a whitelist of functions and
// variable symbols to look at, then we can avoid loading
// and analyzing the ELF symbol table.
bool do_ignore_symbol_table = !kabi_wl_paths.empty();
set_ignore_symbol_table(*module_ctxt, do_ignore_symbol_table);
load_generate_apply_suppressions(*module_ctxt, suppr_paths,
kabi_wl_paths, supprs);
set_read_context_corpus_group(*module_ctxt, group);
read_and_add_corpus_to_group_from_elf(*module_ctxt,
*group, status);
if (verbose)
std::cout << " DONE\n";
}
result = group;
}
}
return result;
}
}//end namespace tools_utils
using abigail::ir::function_decl;

View File

@ -212,6 +212,13 @@ public:
get_annotate()
{return m_annotate;}
/// Setter of the annotation option.
///
/// @param f the new value of the flag.
void
set_annotate(bool f)
{m_annotate = f;}
/// Getter of the @ref id_manager.
///
/// @return the @ref id_manager used by the current instance of @ref
@ -3906,6 +3913,8 @@ write_corpus_to_archive(const corpus_sptr corp, const bool annotate)
///
/// @param indent the number of white space indentation to use.
///
/// @param ctxt the write context to use.
///
/// @param out the output stream to serialize the ABI corpus to.
///
/// @param annotate whether ABIXML output should be annotated.
@ -3914,14 +3923,13 @@ write_corpus_to_archive(const corpus_sptr corp, const bool annotate)
bool
write_corpus(const corpus_sptr corpus,
unsigned indent,
write_context& ctxt,
std::ostream& out,
const bool annotate)
{
if (!corpus)
return false;
write_context ctxt(corpus->get_environment(), out, annotate);
do_indent_to_level(ctxt, indent, 0);
out << "<abi-corpus";
if (!corpus->get_path().empty())
@ -3941,7 +3949,11 @@ write_corpus(const corpus_sptr corpus,
out << ">\n";
// Write the list of needed corpora
// Write the list of needed corpora.
bool saved_annotate = ctxt.get_annotate();
ctxt.set_annotate(annotate);
if (!corpus->get_needed().empty())
{
do_indent_to_level(ctxt, indent, 1);
@ -3993,6 +4005,8 @@ write_corpus(const corpus_sptr corpus,
do_indent_to_level(ctxt, indent, 0);
out << "</abi-corpus>\n";
ctxt.set_annotate(saved_annotate);
return true;
}
@ -4003,7 +4017,115 @@ write_corpus(const corpus_sptr corpus,
///
/// @param indent the number of white space indentation to use.
///
/// @param out the output file to serialize the ABI corpus to.
/// @param out the output stream to serialize the ABI corpus to.
///
/// @param annotate whether ABIXML output should be annotated.
///
/// @return true upon successful completion, false otherwise.
bool
write_corpus(const corpus_sptr corpus,
unsigned indent,
std::ostream& out,
const bool annotate)
{
if (!corpus)
return false;
write_context ctxt(corpus->get_environment(), out, annotate);
return write_corpus(corpus, indent, ctxt, out, annotate);
}
/// Serialize an ABI corpus group to a single native xml document.
/// The root note of the resulting XML document is 'abi-corpus-group'.
///
/// @param group the corpus group to serialize.
///
/// @param indent the number of white space indentation to use.
///
/// @param ctxt the write context to use.
///
/// @param out the output stream to serialize the ABI corpus to.
///
/// @param annotate whether ABIXML output should be annotated.
///
/// @return true upon successful completion, false otherwise.
bool
write_corpus_group(const corpus_group_sptr& group,
unsigned indent,
write_context& ctxt,
std::ostream& out,
const bool annotate)
{
if (!group)
return false;
do_indent_to_level(ctxt, indent, 0);
out << "<abi-corpus-group";
if (!group->get_path().empty())
out << " path='" << xml::escape_xml_string(group->get_path()) << "'";
if (!group->get_architecture_name().empty())
out << " architecture='" << group->get_architecture_name()<< "'";
if (group->is_empty())
{
out << "/>\n";
return true;
}
out << ">\n";
// Write the list of corpora
for (corpus_group::corpora_type::const_iterator c =
group->get_corpora().begin();
c != group->get_corpora().end();
++c)
write_corpus(*c, get_indent_to_level(ctxt, indent, 1), ctxt, out, annotate);
do_indent_to_level(ctxt, indent, 0);
out << "</abi-corpus-group>\n";
return true;
}
/// Serialize an ABI corpus group to a single native xml document.
/// The root note of the resulting XML document is 'abi-corpus-group'.
///
/// @param group the corpus group to serialize.
///
/// @param indent the number of white space indentation to use.
///
/// @param out the output stream to serialize the ABI corpus to.
///
/// @param annotate whether ABIXML output should be annotated.
///
/// @return true upon successful completion, false otherwise.
bool
write_corpus_group(const corpus_group_sptr& group,
unsigned indent,
std::ostream& out,
const bool annotate)
{
if (!group)
return false;
write_context ctxt(group->get_environment(), out, annotate);
return write_corpus_group(group, indent, ctxt, out, annotate);
}
/// Serialize an ABI corpus to a single native xml document. The root
/// note of the resulting XML document is 'abi-corpus'.
///
/// @param corpus the corpus to serialize.
///
/// @param indent the number of white space indentation to use.
///
/// @param path the output file to serialize the ABI corpus to.
///
/// @param annotate whether ABIXML output should be annotated.
///

View File

@ -53,7 +53,6 @@ using abigail::corpus_sptr;
using abigail::xml_reader::read_translation_unit_from_file;
using abigail::xml_reader::read_corpus_from_native_xml_file;
using abigail::xml_writer::write_translation_unit;
using abigail::xml_writer::write_corpus;
using abigail::workers::queue;
using abigail::workers::task;

View File

@ -55,6 +55,7 @@ using abigail::tools_utils::emit_prefix;
using abigail::tools_utils::temp_file;
using abigail::tools_utils::temp_file_sptr;
using abigail::tools_utils::check_file;
using abigail::tools_utils::build_corpus_group_from_kernel_dist_under;
using abigail::ir::environment_sptr;
using abigail::ir::environment;
using abigail::corpus;
@ -93,6 +94,7 @@ struct options
bool write_corpus_path;
bool load_all_types;
bool linux_kernel_mode;
bool corpus_group_for_linux;
bool show_stats;
bool noout;
bool show_locs;
@ -108,6 +110,7 @@ struct options
write_corpus_path(true),
load_all_types(),
linux_kernel_mode(true),
corpus_group_for_linux(false),
show_stats(),
noout(),
show_locs(true),
@ -142,6 +145,8 @@ display_usage(const string& prog_name, ostream& out)
"a Linux Kernel binary\n"
<< " --kmi-whitelist|-w path to a linux kernel "
"abi whitelist\n"
<< " --linux-tree|--lt emit the ABI for the union of a"
"vmlinux and its modules\n"
<< " --abidiff compare the loaded ABI against itself\n"
<< " --annotate annotate the ABI artifacts emitted in the output\n"
<< " --stats show statistics about various internal stuff\n"
@ -216,6 +221,9 @@ parse_command_line(int argc, char* argv[], options& opts)
opts.kabi_whitelist_paths.push_back(argv[j]);
++i;
}
else if (!strcmp(argv[i], "--linux-tree")
|| !strcmp(argv[i], "--lt"))
opts.corpus_group_for_linux = true;
else if (!strcmp(argv[i], "--noout"))
opts.noout = true;
else if (!strcmp(argv[i], "--no-architecture"))
@ -342,102 +350,37 @@ set_suppressions(read_context& read_ctxt, options& opts)
add_read_context_suppressions(read_ctxt, opts.kabi_whitelist_supprs);
}
int
main(int argc, char* argv[])
/// Load an ABI @ref corpus (the internal representation of the ABI of
/// a binary) and write it out as an abixml.
///
/// @param argv the arguments the program was called with.
///
/// @param env the environment the ABI artifacts are being created in.
///
/// @param context the context of the ELF reading.
///
/// @param opts the options of the program.
///
/// @return the exit code: 0 if everything went fine, non-zero
/// otherwise.
static int
load_corpus_and_write_abixml(char* argv[],
environment_sptr& env,
read_context_sptr& context,
const options& opts)
{
options opts;
int exit_code = 0;
if (!parse_command_line(argc, argv, opts)
|| (opts.in_file_path.empty()
&& !opts.display_version))
{
if (!opts.wrong_option.empty())
emit_prefix(argv[0], cerr)
<< "unrecognized option: " << opts.wrong_option << "\n";
display_usage(argv[0], cerr);
return 1;
}
if (opts.display_version)
{
string major, minor, revision;
abigail::abigail_get_library_version(major, minor, revision);
cout << major << "." << minor << "." << revision << "\n";
return 0;
}
assert(!opts.in_file_path.empty());
if (!abigail::tools_utils::check_file(opts.in_file_path, cerr, argv[0]))
return 1;
if (!maybe_check_suppression_files(opts))
return 1;
abigail::tools_utils::file_type type =
abigail::tools_utils::guess_file_type(opts.in_file_path);
if (type != abigail::tools_utils::FILE_TYPE_ELF
&& type != abigail::tools_utils::FILE_TYPE_AR)
{
emit_prefix(argv[0], cerr)
<< opts.in_file_path << " is not an ELF file\n";
return 1;
}
char* p = opts.di_root_path.get();
environment_sptr env(new environment);
read_context& ctxt = *context;
corpus_sptr corp;
read_context_sptr c = create_read_context(opts.in_file_path,
&p, env.get(),
opts.load_all_types,
opts.linux_kernel_mode);
read_context& ctxt = *c;
set_show_stats(ctxt, opts.show_stats);
set_suppressions(ctxt, opts);
abigail::dwarf_reader::set_do_log(ctxt, opts.do_log);
if (!opts.kabi_whitelist_supprs.empty())
set_ignore_symbol_table(ctxt, true);
if (opts.check_alt_debug_info_path)
{
bool has_alt_di = false;
string alt_di_path;
abigail::dwarf_reader::status status =
abigail::dwarf_reader::has_alt_debug_info(ctxt,
has_alt_di,
alt_di_path);
if (status & abigail::dwarf_reader::STATUS_OK)
{
if (alt_di_path.empty())
;
else
{
cout << "found the alternate debug info file";
if (opts.show_base_name_alt_debug_info_path)
{
tools_utils::base_name(alt_di_path, alt_di_path);
cout << " '" << alt_di_path << "'";
}
cout << "\n";
}
return 0;
}
else
{
emit_prefix(argv[0], cerr)
<< "could not find alternate debug info file\n";
return 1;
}
}
dwarf_reader::status s = dwarf_reader::STATUS_UNKNOWN;
corp = read_corpus_from_elf(ctxt, s);
c.reset();
context.reset();
if (!corp)
{
if (s == dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND)
{
if (p == 0)
if (opts.di_root_path.get() == 0)
{
emit_prefix(argv[0], cerr)
<< "Could not read debug info from "
@ -453,7 +396,8 @@ main(int argc, char* argv[])
{
emit_prefix(argv[0], cerr)
<< "Could not read debug info for '" << opts.in_file_path
<< "' from debug info root directory '" << p
<< "' from debug info root directory '"
<< opts.di_root_path.get()
<< "'\n";
}
}
@ -520,8 +464,170 @@ main(int argc, char* argv[])
return 0;
}
else
abigail::xml_writer::write_corpus(corp, 0, cout, opts.annotate);
exit_code = !abigail::xml_writer::write_corpus(corp, 0, cout,
opts.annotate);
}
return 0;
return exit_code;
}
/// Load a corpus group representing the union of a Linux Kernel
/// vmlinux binary and its modules, and emit an abixml representation
/// for it.
///
/// @param argv the arguments this program was called with.
///
/// @param env the environment the ABI artifacts are created in.
///
/// @param opts the options this program was created with.
///
/// @return the exit code. Zero if everything went well, non-zero
/// otherwise.
static int
load_kernel_corpus_group_and_write_abixml(char* argv[],
environment_sptr& env,
options& opts)
{
if (!(tools_utils::is_dir(opts.in_file_path) && opts.corpus_group_for_linux))
return 0;
int exit_code = 0;
suppressions_type supprs;
corpus_group_sptr group =
build_corpus_group_from_kernel_dist_under(opts.in_file_path,
opts.suppression_paths,
opts.kabi_whitelist_paths,
supprs, opts.do_log, env);
if (!group)
return exit_code;
if (!opts.write_architecture)
group->set_architecture_name("");
if (!opts.write_corpus_path)
group->set_path("");
if (!opts.out_file_path.empty())
{
ofstream of(opts.out_file_path.c_str(), std::ios_base::trunc);
if (!of.is_open())
{
emit_prefix(argv[0], cerr)
<< "could not open output file '"
<< opts.out_file_path << "'\n";
return 1;
}
exit_code = !xml_writer::write_corpus_group(group, 0, of, opts.annotate);
}
else
exit_code = !xml_writer::write_corpus_group(group, 0, cout, opts.annotate);
return exit_code;
}
int
main(int argc, char* argv[])
{
options opts;
if (!parse_command_line(argc, argv, opts)
|| (opts.in_file_path.empty()
&& !opts.display_version))
{
if (!opts.wrong_option.empty())
emit_prefix(argv[0], cerr)
<< "unrecognized option: " << opts.wrong_option << "\n";
display_usage(argv[0], cerr);
return 1;
}
if (opts.display_version)
{
string major, minor, revision;
abigail::abigail_get_library_version(major, minor, revision);
cout << major << "." << minor << "." << revision << "\n";
return 0;
}
assert(!opts.in_file_path.empty());
if (opts.corpus_group_for_linux)
{
if (!abigail::tools_utils::check_dir(opts.in_file_path, cerr, argv[0]))
return 1;
}
else
{
if (!abigail::tools_utils::check_file(opts.in_file_path, cerr, argv[0]))
return 1;
}
if (!maybe_check_suppression_files(opts))
return 1;
abigail::tools_utils::file_type type =
abigail::tools_utils::guess_file_type(opts.in_file_path);
if (type != abigail::tools_utils::FILE_TYPE_ELF
&& type != abigail::tools_utils::FILE_TYPE_AR
&& type != abigail::tools_utils::FILE_TYPE_DIR)
{
emit_prefix(argv[0], cerr)
<< "files of the kind of "<< opts.in_file_path << " are not handled\n";
return 1;
}
char* p = opts.di_root_path.get();
environment_sptr env(new environment);
int exit_code = 0;
if (tools_utils::is_regular_file(opts.in_file_path))
{
read_context_sptr c = create_read_context(opts.in_file_path,
&p, env.get(),
opts.load_all_types,
opts.linux_kernel_mode);
read_context& ctxt = *c;
set_show_stats(ctxt, opts.show_stats);
set_suppressions(ctxt, opts);
abigail::dwarf_reader::set_do_log(ctxt, opts.do_log);
if (!opts.kabi_whitelist_supprs.empty())
set_ignore_symbol_table(ctxt, true);
if (opts.check_alt_debug_info_path)
{
bool has_alt_di = false;
string alt_di_path;
abigail::dwarf_reader::status status =
abigail::dwarf_reader::has_alt_debug_info(ctxt,
has_alt_di,
alt_di_path);
if (status & abigail::dwarf_reader::STATUS_OK)
{
if (alt_di_path.empty())
;
else
{
cout << "found the alternate debug info file";
if (opts.show_base_name_alt_debug_info_path)
{
tools_utils::base_name(alt_di_path, alt_di_path);
cout << " '" << alt_di_path << "'";
}
cout << "\n";
}
return 0;
}
else
{
emit_prefix(argv[0], cerr)
<< "could not find alternate debug info file\n";
return 1;
}
}
exit_code = load_corpus_and_write_abixml(argv, env, c, opts);
}
else
exit_code = load_kernel_corpus_group_and_write_abixml(argv, env, opts);
return exit_code;
}

View File

@ -200,34 +200,6 @@ maybe_check_suppression_files(const options& opts)
return true;
}
/// If the user specified suppression specification files, this
/// function parses those, set them to the options of the program and
/// set the read context with the suppression specification.
///
/// @param read_ctxt the read context to consider.
///
/// @param opts the options to consider.
static void
set_suppressions(read_context &read_ctxt, options& opts)
{
if (opts.read_time_supprs.empty())
{
for (vector<string>::const_iterator i = opts.suppression_paths.begin();
i != opts.suppression_paths.end();
++i)
read_suppressions(*i, opts.read_time_supprs);
for (vector<string>::const_iterator i =
opts.kabi_whitelist_paths.begin();
i != opts.kabi_whitelist_paths.end();
++i)
gen_suppr_spec_from_kernel_abi_whitelist(*i, opts.read_time_supprs);
}
abigail::dwarf_reader::add_read_context_suppressions(read_ctxt,
opts.read_time_supprs);
}
/// Setup the diff context from the program's options.
///
/// @param ctxt the diff context to consider.
@ -262,301 +234,6 @@ set_diff_context(diff_context_sptr ctxt, const options& opts)
ctxt->add_suppressions(opts.diff_time_supprs);
}
/// Test if an FTSENT pointer (resulting from fts_read) represents the
/// vmlinux binary.
///
/// @param entry the FTSENT to consider.
///
/// @return true iff @p entry is for a vmlinux binary.
static bool
is_vmlinux(const FTSENT *entry)
{
if (entry == NULL
|| (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
|| entry->fts_info == FTS_ERR
|| entry->fts_info == FTS_NS)
return false;
string fname = entry->fts_name;
if (fname == "vmlinux")
{
string dirname;
dir_name(entry->fts_path, dirname);
if (string_ends_with(dirname, "compressed"))
return false;
return true;
}
return false;
}
/// Test if an FTSENT pointer (resulting from fts_read) represents a a
/// linux kernel module binary.
///
/// @param entry the FTSENT to consider.
///
/// @return true iff @p entry is for a linux kernel module binary.
static bool
is_kernel_module(const FTSENT *entry)
{
if (entry == NULL
|| (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
|| entry->fts_info == FTS_ERR
|| entry->fts_info == FTS_NS)
return false;
string fname = entry->fts_name;
if (string_ends_with(fname, ".ko")
|| string_ends_with(fname, ".ko.xz")
|| string_ends_with(fname, ".ko.gz"))
return true;
return false;
}
/// Find a vmlinux and its kernel modules in a given directory tree.
///
/// @param from the directory tree to start looking from.
///
/// @param vmlinux_path output parameter. This is set to the path
/// where the vmlinux binary is found. This is set iff the returns
/// true.
///
/// @param module_paths output parameter. This is set to the paths of
/// the linux kernel module binaries.
///
/// @return true iff at least the vmlinux binary was found.
static bool
find_vmlinux_and_module_paths(const string& from,
string &vmlinux_path,
vector<string> &module_paths)
{
char* path[] = {const_cast<char*>(from.c_str()), 0};
FTS *file_hierarchy = fts_open(path, FTS_PHYSICAL|FTS_NOCHDIR|FTS_XDEV, 0);
if (!file_hierarchy)
return false;
bool found_vmlinux = false;
FTSENT *entry;
while ((entry = fts_read(file_hierarchy)))
{
// Skip descendents of symbolic links.
if (entry->fts_info == FTS_SL || entry->fts_info == FTS_SLNONE)
{
fts_set(file_hierarchy, entry, FTS_SKIP);
continue;
}
if (!found_vmlinux && is_vmlinux(entry))
{
vmlinux_path = entry->fts_path;
found_vmlinux = true;
}
else if (is_kernel_module(entry))
module_paths.push_back(entry->fts_path);
}
fts_close(file_hierarchy);
return found_vmlinux;
}
/// Get the paths of the vmlinux and kernel module binaries under
/// given directory.
///
/// @param dist_root the directory under which to look for.
///
/// @param vmlinux_path output parameter. The path of the vmlinux
/// binary that was found.
///
/// @param module_paths output parameter. The paths of the kernel
/// module binaries that were found.
///
/// @param debug_info_root_path output parameter. If a debug info
/// sub-directory is found under @p dist_root, it's set to this
/// parameter.
///
/// @return true if at least the path to the vmlinux binary was found.
static bool
get_binary_paths_from_kernel_dist(const string& dist_root,
string& vmlinux_path,
vector<string>& module_paths,
string& debug_info_root_path)
{
if (!dir_exists(dist_root))
return false;
// For now, we assume either an Enterprise Linux or a Fedora kernel
// distribution directory.
//
// We also take into account split debug info package for these. In
// this case, the content split debug info package is installed
// under the 'dist_root' directory as well, and its content is
// accessible from <dist_root>/usr/lib/debug directory.
string kernel_modules_root;
string debug_info_root;
if (dir_exists(dist_root + "/lib/modules"))
{
dist_root + "/lib/modules";
debug_info_root = dist_root + "/usr/lib/debug";
}
if (dir_is_empty(debug_info_root))
debug_info_root.clear();
bool found = false;
string from = !debug_info_root.empty() ? debug_info_root : dist_root;
if (find_vmlinux_and_module_paths(from, vmlinux_path, module_paths))
found = true;
debug_info_root_path = debug_info_root;
return found;
}
/// Get the paths of the vmlinux and kernel module binaries under
/// given directory.
///
/// @param dist_root the directory under which to look for.
///
/// @param vmlinux_path output parameter. The path of the vmlinux
/// binary that was found.
///
/// @param module_paths output parameter. The paths of the kernel
/// module binaries that were found.
///
/// @return true if at least the path to the vmlinux binary was found.
static bool
get_binary_paths_from_kernel_dist(const string& dist_root,
string& vmlinux_path,
vector<string>& module_paths)
{
string debug_info_root_path;
return get_binary_paths_from_kernel_dist(dist_root,
vmlinux_path,
module_paths,
debug_info_root_path);
}
/// Walk a given directory and build an instance of @ref corpus_group
/// from the vmlinux kernel binary and the linux kernel modules found
/// under that directory and under its sub-directories, recursively.
///
/// The main corpus of the @ref corpus_group is made of the vmlinux
/// binary. The other corpora are made of the linux kernel binaries.
///
/// @param root the path of the directory under which vmlinux and its
/// kernel modules are to be found.
///
/// @param opts the options to use during the search.
///
/// @param env the environment to create the corpus_group in.
static corpus_group_sptr
build_corpus_group_from_kernel_dist_under(const string& root,
options& opts,
environment_sptr& env)
{
corpus_group_sptr result;
string vmlinux;
vector<string> modules;
string debug_info_root_path;
if (opts.verbose)
cout << "Analysing kernel dist root '" << root << "' ... " << std::flush;
bool got_binary_paths =
get_binary_paths_from_kernel_dist(root, vmlinux, modules,
debug_info_root_path);
if (opts.verbose)
cout << "DONE\n";
if (got_binary_paths)
{
shared_ptr<char> di_root =
make_path_absolute(debug_info_root_path.c_str());
char *di_root_ptr = di_root.get();
abigail::dwarf_reader::status status = abigail::dwarf_reader::STATUS_OK;
corpus_group_sptr group;
if (!vmlinux.empty())
{
read_context_sptr ctxt =
create_read_context(vmlinux, &di_root_ptr, env.get(),
/*read_all_types=*/false,
/*linux_kernel_mode=*/true);
set_suppressions(*ctxt, opts);
// If we have been given a whitelist of functions and
// variable symbols to look at, then we can avoid loading
// and analyzing the ELF symbol table.
bool do_ignore_symbol_table = !opts.read_time_supprs.empty();
set_ignore_symbol_table(*ctxt, do_ignore_symbol_table);
group.reset(new corpus_group(env.get(), root));
set_read_context_corpus_group(*ctxt, group);
if (opts.verbose)
cout << "reading kernel binary '"
<< vmlinux << "' ... " << std::flush;
// Read the vmlinux corpus and add it to the group.
read_and_add_corpus_to_group_from_elf(*ctxt, *group, status);
if (opts.verbose)
cout << " DONE\n";
}
if (!group->is_empty())
{
// Now add the corpora of the modules to the corpus group.
int total_nb_modules = modules.size();
int cur_module_index = 1;
for (vector<string>::const_iterator m = modules.begin();
m != modules.end();
++m, ++cur_module_index)
{
if (opts.verbose)
cout << "reading module '"
<< *m << "' ("
<< cur_module_index
<< "/" << total_nb_modules
<< ") ... " << std::flush;
read_context_sptr module_ctxt =
create_read_context(*m, &di_root_ptr, env.get(),
/*read_all_types=*/false,
/*linux_kernel_mode=*/true);
// If we have been given a whitelist of functions and
// variable symbols to look at, then we can avoid loading
// and analyzing the ELF symbol table.
bool do_ignore_symbol_table = !opts.read_time_supprs.empty();
set_ignore_symbol_table(*module_ctxt, do_ignore_symbol_table);
set_suppressions(*module_ctxt, opts);
set_read_context_corpus_group(*module_ctxt, group);
read_and_add_corpus_to_group_from_elf(*module_ctxt,
*group, status);
if (opts.verbose)
cout << " DONE\n";
}
result = group;
}
}
return result;
}
/// Print information about the kernel (and modules) binaries found
/// under a given directory.
///
@ -637,7 +314,11 @@ main(int argc, char* argv[])
{
group1 =
build_corpus_group_from_kernel_dist_under(opts.kernel_dist_root1,
opts, env);
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);
}
@ -645,7 +326,11 @@ main(int argc, char* argv[])
{
group2 =
build_corpus_group_from_kernel_dist_under(opts.kernel_dist_root2,
opts, env);
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);
}