mirror of
git://sourceware.org/git/libabigail.git
synced 2024-12-22 09:49:59 +00:00
6d4472066b
When comparing the ABI of the input ELF binary with that same ABI saved to abixml and read back again, there can be some minor and harmless changes that are seen, because libabigail makes some approximations for performance reasons. For instance, if there are two types that are equivalent, but have different names (because of typedefs) then libabigail will consider that they are the same type, and might save them (to abixml) and read them back (from abixml) in different order. That can lead to subtle changes that are reported (and filtered out) by the command "abidw --abixml". This patch arranges for abidw --abixml to avoid emitting a report saying that a filtered out change was detected, as those cases are considered OK. The patch also fixes a little issue where abidw would abort because the user forgot to provide the binary to analyze, on the command line. * tools/abidw.cc (set_diff_context): New function. (main): Use that new function. Do not show any output for --abidiff if only compatible changes were detected. Also, do not abort if no input binary was giving. Signed-off-by: Dodji Seketeli <dodji@redhat.com>
353 lines
9.8 KiB
C++
353 lines
9.8 KiB
C++
// -*- Mode: C++ -*-
|
|
//
|
|
// Copyright (C) 2013-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: Dodji Seketeli
|
|
|
|
/// @file
|
|
///
|
|
/// This program reads an elf file, try to load its debug info (in
|
|
/// DWARF format) and emit it back in a set of "text sections" in native
|
|
/// libabigail XML format.
|
|
|
|
#include <unistd.h>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <tr1/memory>
|
|
#include "abg-tools-utils.h"
|
|
#include "abg-corpus.h"
|
|
#include "abg-dwarf-reader.h"
|
|
#include "abg-writer.h"
|
|
#include "abg-reader.h"
|
|
#include "abg-comparison.h"
|
|
|
|
using std::string;
|
|
using std::cerr;
|
|
using std::cout;
|
|
using std::ostream;
|
|
using std::ofstream;
|
|
using std::tr1::shared_ptr;
|
|
using abigail::tools_utils::temp_file;
|
|
using abigail::tools_utils::temp_file_sptr;
|
|
using abigail::comparison::corpus_diff;
|
|
using abigail::comparison::corpus_diff_sptr;
|
|
using abigail::comparison::compute_diff;
|
|
using abigail::comparison::diff_context_sptr;
|
|
using abigail::comparison::diff_context;
|
|
using abigail::xml_writer::write_corpus_to_native_xml;
|
|
using abigail::xml_reader::read_corpus_from_native_xml_file;
|
|
|
|
struct options
|
|
{
|
|
string in_file_path;
|
|
string out_file_path;
|
|
shared_ptr<char> di_root_path;
|
|
bool check_alt_debug_info_path;
|
|
bool show_base_name_alt_debug_info_path;
|
|
bool write_architecture;
|
|
bool load_all_types;
|
|
bool show_stats;
|
|
bool noout;
|
|
bool abidiff;
|
|
|
|
options()
|
|
: check_alt_debug_info_path(),
|
|
show_base_name_alt_debug_info_path(),
|
|
write_architecture(true),
|
|
load_all_types(),
|
|
show_stats(),
|
|
noout(),
|
|
abidiff()
|
|
{}
|
|
};
|
|
|
|
static void
|
|
display_usage(const string& prog_name, ostream& out)
|
|
{
|
|
out << "usage: " << prog_name << " [options] [<path-to-elf-file>]\n"
|
|
<< " where options can be: \n"
|
|
<< " --help|-h display this message\n"
|
|
<< " --debug-info-dir|-d <dir-path> look for debug info under 'dir-path'\n"
|
|
<< " --out-file <file-path> write the output to 'file-path'\n"
|
|
<< " --noout do not emit anything after reading the binary\n"
|
|
<< " --no-architecture do not emit architecture info in the output\n"
|
|
<< " --check-alternate-debug-info <elf-path> check alternate debug info "
|
|
"of <elf-path>\n"
|
|
<< " --check-alternate-debug-info-base-name <elf-path> check alternate "
|
|
"debug info of <elf-path>, and show its base name\n"
|
|
<< " --load-all-types read all types including those not reachable from"
|
|
"exported declarations\n"
|
|
<< " --abidiff compare the loaded ABI against itself\n"
|
|
<< " --stats show statistics about various internal stuff\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.in_file_path.empty())
|
|
opts.in_file_path = argv[i];
|
|
else
|
|
return false;
|
|
}
|
|
else if (!strcmp(argv[i], "--debug-info-dir")
|
|
|| !strcmp(argv[i], "-d"))
|
|
{
|
|
if (argc <= i + 1
|
|
|| argv[i + 1][0] == '-'
|
|
|| !opts.out_file_path.empty())
|
|
return false;
|
|
// elfutils wants the root path to the debug info to be
|
|
// absolute.
|
|
opts.di_root_path =
|
|
abigail::tools_utils::make_path_absolute(argv[i + 1]);
|
|
++i;
|
|
}
|
|
else if (!strcmp(argv[i], "--out-file"))
|
|
{
|
|
if (argc <= i + 1
|
|
|| argv[i + 1][0] == '-'
|
|
|| !opts.out_file_path.empty())
|
|
return false;
|
|
|
|
opts.out_file_path = argv[i + 1];
|
|
++i;
|
|
}
|
|
else if (!strcmp(argv[i], "--noout"))
|
|
opts.noout = true;
|
|
else if (!strcmp(argv[i], "--no-architecture"))
|
|
opts.write_architecture = false;
|
|
else if (!strcmp(argv[i], "--check-alternate-debug-info")
|
|
|| !strcmp(argv[i], "--check-alternate-debug-info-base-name"))
|
|
{
|
|
if (argc <= i + 1
|
|
|| argv[i + 1][0] == '-'
|
|
|| !opts.in_file_path.empty())
|
|
return false;
|
|
if (!strcmp(argv[i], "--check-alternate-debug-info-base-name"))
|
|
opts.show_base_name_alt_debug_info_path = true;
|
|
opts.check_alt_debug_info_path = true;
|
|
opts.in_file_path = argv[i + 1];
|
|
++i;
|
|
}
|
|
else if (!strcmp(argv[i], "--load-all-types"))
|
|
opts.load_all_types = true;
|
|
else if (!strcmp(argv[i], "--abidiff"))
|
|
opts.abidiff = true;
|
|
else if (!strcmp(argv[i], "--stats"))
|
|
opts.show_stats = true;
|
|
else if (!strcmp(argv[i], "--help")
|
|
|| !strcmp(argv[i], "--h"))
|
|
return false;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Initialize the context use for driving ABI comparison.
|
|
///
|
|
/// @param ctxt the context to initialize.
|
|
static void
|
|
set_diff_context(diff_context_sptr& ctxt)
|
|
{
|
|
ctxt->default_output_stream(&cerr);
|
|
ctxt->error_output_stream(&cerr);
|
|
// Filter out changes that are not meaningful from an ABI
|
|
// standpoint, from the diff output.
|
|
ctxt->switch_categories_off
|
|
(abigail::comparison::ACCESS_CHANGE_CATEGORY
|
|
| abigail::comparison::COMPATIBLE_TYPE_CHANGE_CATEGORY
|
|
| abigail::comparison::HARMLESS_DECL_NAME_CHANGE_CATEGORY);
|
|
}
|
|
|
|
int
|
|
main(int argc, char* argv[])
|
|
{
|
|
options opts;
|
|
|
|
if (!parse_command_line(argc, argv, opts)
|
|
|| opts.in_file_path.empty())
|
|
{
|
|
display_usage(argv[0], cerr);
|
|
return 1;
|
|
}
|
|
|
|
assert(!opts.in_file_path.empty());
|
|
if (!abigail::tools_utils::check_file(opts.in_file_path, cerr))
|
|
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)
|
|
{
|
|
cerr << opts.in_file_path << " is not an ELF file\n";
|
|
return 1;
|
|
}
|
|
|
|
using abigail::ir::environment_sptr;
|
|
using abigail::ir::environment;
|
|
using abigail::corpus;
|
|
using abigail::corpus_sptr;
|
|
using abigail::translation_units;
|
|
using abigail::dwarf_reader::read_context;
|
|
using abigail::dwarf_reader::read_context_sptr;
|
|
using abigail::dwarf_reader::read_corpus_from_elf;
|
|
using abigail::dwarf_reader::create_read_context;
|
|
using namespace abigail;
|
|
|
|
char* p = opts.di_root_path.get();
|
|
environment_sptr env(new environment);
|
|
corpus_sptr corp;
|
|
read_context_sptr c = create_read_context(opts.in_file_path,
|
|
&p, env.get(),
|
|
opts.load_all_types);
|
|
read_context& ctxt = *c;
|
|
set_show_stats(ctxt, opts.show_stats);
|
|
|
|
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
|
|
{
|
|
if (opts.show_base_name_alt_debug_info_path)
|
|
tools_utils::base_name(alt_di_path, alt_di_path);
|
|
|
|
cout << "found the alternate debug info file '"
|
|
<< alt_di_path
|
|
<< "'\n";
|
|
}
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
if (!corp)
|
|
{
|
|
if (s == dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND)
|
|
{
|
|
if (p == 0)
|
|
{
|
|
cerr <<
|
|
"Could not read debug info from "
|
|
<< opts.in_file_path << "\n";
|
|
|
|
cerr << "You might want to supply the root directory where "
|
|
"to search debug info from, using the "
|
|
"--debug-info-dir option "
|
|
"(e.g --debug-info-dir /usr/lib/debug)\n";
|
|
}
|
|
else
|
|
{
|
|
cerr << "Could not read debug info for '" << opts.in_file_path
|
|
<< "' from debug info root directory '" << p
|
|
<< "'\n";
|
|
}
|
|
}
|
|
else if (s == dwarf_reader::STATUS_NO_SYMBOLS_FOUND)
|
|
cerr << "Could not read ELF symbol information from "
|
|
<< opts.in_file_path << "\n";
|
|
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
if (opts.abidiff)
|
|
{
|
|
// Save the abi in abixml format in a temporary file, read
|
|
// it back, and compare the ABI of what we've read back
|
|
// against the ABI of the input ELF file.
|
|
temp_file_sptr tmp_file = temp_file::create();
|
|
write_corpus_to_native_xml(corp, 0, tmp_file->get_stream());
|
|
tmp_file->get_stream().flush();
|
|
corpus_sptr corp2 =
|
|
read_corpus_from_native_xml_file(tmp_file->get_path(),
|
|
env.get());
|
|
if (!corp2)
|
|
{
|
|
cerr << "Could not read temporary XML representation of "
|
|
"elf file back\n";
|
|
return 1;
|
|
}
|
|
diff_context_sptr ctxt(new diff_context);
|
|
set_diff_context(ctxt);
|
|
corpus_diff_sptr diff = compute_diff(corp, corp2, ctxt);
|
|
bool has_error = diff->has_incompatible_changes();
|
|
if (has_error)
|
|
{
|
|
diff->report(cerr);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (opts.noout)
|
|
return 0;
|
|
|
|
if (!opts.write_architecture)
|
|
corp->set_architecture_name("");
|
|
|
|
if (!opts.out_file_path.empty())
|
|
{
|
|
ofstream of(opts.out_file_path.c_str(), std::ios_base::trunc);
|
|
if (!of.is_open())
|
|
{
|
|
cerr << "could not open output file '"
|
|
<< opts.out_file_path << "'\n";
|
|
return 1;
|
|
}
|
|
abigail::xml_writer::write_corpus_to_native_xml(corp, 0, of);
|
|
of.close();
|
|
return 0;
|
|
}
|
|
else
|
|
abigail::xml_writer::write_corpus_to_native_xml(corp, 0, cout);
|
|
}
|
|
|
|
return 0;
|
|
}
|