blob: efddd77b9ece2cf954c1357c83ce07d3a6ea2949 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- Mode: C++ -*-
//
// Copyright (C) 2013-2021 Red Hat, Inc.
//
// Author: Dodji Seketeli
/// @file
///
/// This is a program aimed at checking that a binary instrumentation
/// (bi) file is well formed and valid enough. It acts by loading an
/// input bi file and saving it back to a temporary file. It then
/// runs a diff on the two files and expects the result of the diff to
/// be empty.
#include "config.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "abg-config.h"
#include "abg-tools-utils.h"
#include "abg-ir.h"
#include "abg-corpus.h"
#include "abg-reader.h"
#include "abg-dwarf-reader.h"
#ifdef WITH_CTF
#include "abg-ctf-reader.h"
#endif
#include "abg-writer.h"
#include "abg-suppression.h"
using std::string;
using std::cerr;
using std::cin;
using std::cout;
using std::ostream;
using std::ofstream;
using std::vector;
using abigail::tools_utils::emit_prefix;
using abigail::tools_utils::check_file;
using abigail::tools_utils::file_type;
using abigail::tools_utils::guess_file_type;
using abigail::suppr::suppression_sptr;
using abigail::suppr::suppressions_type;
using abigail::suppr::read_suppressions;
using abigail::corpus;
using abigail::corpus_sptr;
using abigail::xml_reader::read_translation_unit_from_file;
using abigail::xml_reader::read_translation_unit_from_istream;
using abigail::xml_reader::read_corpus_from_native_xml;
using abigail::xml_reader::read_corpus_from_native_xml_file;
using abigail::xml_reader::read_corpus_group_from_input;
using abigail::dwarf_reader::read_corpus_from_elf;
using abigail::xml_writer::write_translation_unit;
using abigail::xml_writer::write_context_sptr;
using abigail::xml_writer::create_write_context;
using abigail::xml_writer::write_corpus;
using abigail::xml_writer::write_corpus_to_archive;
struct options
{
string wrong_option;
string file_path;
bool display_version;
bool read_from_stdin;
bool read_tu;
bool diff;
bool noout;
#ifdef WITH_CTF
bool use_ctf;
#endif
std::shared_ptr<char> di_root_path;
vector<string> suppression_paths;
string headers_dir;
vector<string> header_files;
options()
: display_version(false),
read_from_stdin(false),
read_tu(false),
diff(false),
noout(false)
#ifdef WITH_CTF
,
use_ctf(false)
#endif
{}
};//end struct options;
static void
display_usage(const string& prog_name, ostream& out)
{
emit_prefix(prog_name, out)
<< "usage: " << prog_name << " [options] [<abi-file1>]\n"
<< " where options can be:\n"
<< " --help display this message\n"
<< " --version|-v display program version information and exit\n"
<< " --debug-info-dir <path> the path under which to look for "
"debug info for the elf <abi-file>\n"
<< " --headers-dir|--hd <path> the path to headers of the elf file\n"
<< " --header-file|--hf <path> the path to one header of the elf file\n"
<< " --suppressions|--suppr <path> specify a suppression file\n"
<< " --diff for xml inputs, perform a text diff between "
"the input and the memory model saved back to disk\n"
<< " --noout do not display anything on stdout\n"
<< " --stdin read abi-file content from stdin\n"
<< " --tu expect a single translation unit file\n"
#ifdef WITH_CTF
<< " --ctf use CTF instead of DWARF in ELF files\n"
#endif
;
}
bool
parse_command_line(int argc, char* argv[], options& opts)
{
if (argc < 2)
{
opts.read_from_stdin = true;
return true;
}
for (int i = 1; i < argc; ++i)
{
if (argv[i][0] != '-')
{
if (opts.file_path.empty())
opts.file_path = argv[i];
else
return false;
}
else if (!strcmp(argv[i], "--help"))
return false;
else if (!strcmp(argv[i], "--version")
|| !strcmp(argv[i], "-v"))
{
opts.display_version = true;
return true;
}
else if (!strcmp(argv[i], "--debug-info-dir"))
{
if (argc <= i + 1
|| argv[i + 1][0] == '-')
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], "--headers-dir")
|| !strcmp(argv[i], "--hd"))
{
int j = i + 1;
if (j >= argc)
return false;
opts.headers_dir = argv[j];
++i;
}
else if (!strcmp(argv[i], "--header-file")
|| !strcmp(argv[i], "--hf"))
{
int j = i + 1;
if (j >= argc)
return false;
opts.header_files.push_back(argv[j]);
++i;
}
else if (!strcmp(argv[i], "--suppressions")
|| !strcmp(argv[i], "--suppr"))
{
int j = i + 1;
if (j >= argc)
{
opts.wrong_option = argv[i];
return true;
}
opts.suppression_paths.push_back(argv[j]);
++i;
}
else if (!strcmp(argv[i], "--stdin"))
opts.read_from_stdin = true;
else if (!strcmp(argv[i], "--tu"))
opts.read_tu = true;
#ifdef WITH_CTF
else if (!strcmp(argv[i], "--ctf"))
opts.use_ctf = true;
#endif
else if (!strcmp(argv[i], "--diff"))
opts.diff = true;
else if (!strcmp(argv[i], "--noout"))
opts.noout = true;
else
{
if (strlen(argv[i]) >= 2 && argv[i][0] == '-' && argv[i][1] == '-')
opts.wrong_option = argv[i];
return false;
}
}
if (opts.file_path.empty())
opts.read_from_stdin = true;
if (opts.read_from_stdin && !opts.file_path.empty())
{
emit_prefix(argv[0], cout)
<< "WARNING: The \'--stdin\' option is used. The "
<< opts.file_path << " will be ignored automatically\n";
}
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;
return true;
}
/// Set suppression specifications to the @p read_context used to load
/// the ABI corpus from the ELF/DWARF file.
///
/// These suppression specifications are going to be applied to drop
/// some ABI artifacts on the floor (while reading the ELF/DWARF file
/// or the native XML ABI file) and thus minimize the size of the
/// resulting ABI corpus.
///
/// @param read_ctxt the read context to apply the suppression
/// specifications to. Note that the type of this parameter is
/// generic (class template) because in practise, it can be either an
/// abigail::dwarf_reader::read_context type or an
/// abigail::xml_reader::read_context type.
///
/// @param opts the options where to get the suppression
/// specifications from.
template<class ReadContextType>
static void
set_suppressions(ReadContextType& read_ctxt, const options& opts)
{
suppressions_type supprs;
for (vector<string>::const_iterator i = opts.suppression_paths.begin();
i != opts.suppression_paths.end();
++i)
read_suppressions(*i, supprs);
suppression_sptr suppr =
abigail::tools_utils::gen_suppr_spec_from_headers(opts.headers_dir,
opts.header_files);
if (suppr)
supprs.push_back(suppr);
add_read_context_suppressions(read_ctxt, supprs);
}
/// Reads a bi (binary instrumentation) file, saves it back to a
/// temporary file and run a diff on the two versions.
int
main(int argc, char* argv[])
{
options opts;
if (!parse_command_line(argc, argv, opts))
{
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)
{
emit_prefix(argv[0], cout)
<< abigail::tools_utils::get_library_version_string()
<< "\n";
return 0;
}
if (!maybe_check_suppression_files(opts))
return 1;
abigail::ir::environment_sptr env(new abigail::ir::environment);
if (opts.read_from_stdin)
{
if (!cin.good())
return 1;
if (opts.read_tu)
{
abigail::translation_unit_sptr tu =
read_translation_unit_from_istream(&cin, env.get());
if (!tu)
{
emit_prefix(argv[0], cerr)
<< "failed to read the ABI instrumentation from stdin\n";
return 1;
}
if (!opts.noout)
{
const write_context_sptr& ctxt
= create_write_context(tu->get_environment(), cout);
write_translation_unit(*ctxt, *tu, 0);
}
return 0;
}
else
{
abigail::xml_reader::read_context_sptr ctxt =
abigail::xml_reader::create_native_xml_read_context(&cin,
env.get());
assert(ctxt);
set_suppressions(*ctxt, opts);
corpus_sptr corp = abigail::xml_reader::read_corpus_from_input(*ctxt);
if (!opts.noout)
{
const write_context_sptr& ctxt
= create_write_context(corp->get_environment(), cout);
write_corpus(*ctxt, corp, /*indent=*/0);
}
return 0;
}
}
else if (!opts.file_path.empty())
{
if (!check_file(opts.file_path, cerr, argv[0]))
return 1;
abigail::translation_unit_sptr tu;
abigail::corpus_sptr corp;
abigail::corpus_group_sptr group;
abigail::elf_reader::status s = abigail::elf_reader::STATUS_OK;
char* di_root_path = 0;
file_type type = guess_file_type(opts.file_path);
switch (type)
{
case abigail::tools_utils::FILE_TYPE_UNKNOWN:
emit_prefix(argv[0], cerr)
<< "Unknown file type given in input: " << opts.file_path
<< "\n";
return 1;
case abigail::tools_utils::FILE_TYPE_NATIVE_BI:
tu = read_translation_unit_from_file(opts.file_path, env.get());
break;
case abigail::tools_utils::FILE_TYPE_ELF:
case abigail::tools_utils::FILE_TYPE_AR:
{
di_root_path = opts.di_root_path.get();
vector<char**> di_roots;
di_roots.push_back(&di_root_path);
#ifdef WITH_CTF
if (opts.use_ctf)
{
abigail::ctf_reader::read_context_sptr ctxt
= abigail::ctf_reader::create_read_context(opts.file_path,
env.get());
ABG_ASSERT(ctxt);
corp = abigail::ctf_reader::read_corpus(ctxt.get(), s);
}
else
#endif
{
abigail::dwarf_reader::read_context_sptr ctxt =
abigail::dwarf_reader::create_read_context(opts.file_path,
di_roots, env.get(),
/*load_all_types=*/false);
assert(ctxt);
set_suppressions(*ctxt, opts);
corp = read_corpus_from_elf(*ctxt, s);
}
}
break;
case abigail::tools_utils::FILE_TYPE_XML_CORPUS:
{
abigail::xml_reader::read_context_sptr ctxt =
abigail::xml_reader::create_native_xml_read_context(opts.file_path,
env.get());
assert(ctxt);
set_suppressions(*ctxt, opts);
corp = read_corpus_from_input(*ctxt);
break;
}
case abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP:
{
abigail::xml_reader::read_context_sptr ctxt =
abigail::xml_reader::create_native_xml_read_context(opts.file_path,
env.get());
assert(ctxt);
set_suppressions(*ctxt, opts);
group = read_corpus_group_from_input(*ctxt);
}
break;
case abigail::tools_utils::FILE_TYPE_RPM:
break;
case abigail::tools_utils::FILE_TYPE_SRPM:
break;
case abigail::tools_utils::FILE_TYPE_DEB:
break;
case abigail::tools_utils::FILE_TYPE_DIR:
break;
case abigail::tools_utils::FILE_TYPE_TAR:
break;
}
if (!tu && !corp && !group)
{
emit_prefix(argv[0], cerr)
<< "failed to read " << opts.file_path << "\n";
if (!(s & abigail::elf_reader::STATUS_OK))
{
if (s & abigail::elf_reader::STATUS_DEBUG_INFO_NOT_FOUND)
{
cerr << "could not find the debug info";
if(di_root_path == 0)
emit_prefix(argv[0], cerr)
<< " Maybe you should consider using the "
"--debug-info-dir1 option to tell me about the "
"root directory of the debuginfo? "
"(e.g, --debug-info-dir1 /usr/lib/debug)\n";
else
emit_prefix(argv[0], cerr)
<< "Maybe the root path to the debug "
"information is wrong?\n";
}
if (s & abigail::elf_reader::STATUS_NO_SYMBOLS_FOUND)
emit_prefix(argv[0], cerr)
<< "could not find the ELF symbols in the file "
<< opts.file_path
<< "\n";
}
return 1;
}
using abigail::tools_utils::temp_file;
using abigail::tools_utils::temp_file_sptr;
temp_file_sptr tmp_file = temp_file::create();
if (!tmp_file)
{
emit_prefix(argv[0], cerr) << "failed to create temporary file\n";
return 1;
}
std::ostream& of = opts.diff ? tmp_file->get_stream() : cout;
const abigail::ir::environment* env = 0;
if (tu)
env = tu->get_environment();
else if (corp)
env = corp->get_environment();
else if (group)
env = group->get_environment();
ABG_ASSERT(env);
const write_context_sptr ctxt = create_write_context(env, of);
bool is_ok = true;
if (tu)
{
if (!opts.noout)
is_ok = write_translation_unit(*ctxt, *tu, 0);
}
else
{
if (type == abigail::tools_utils::FILE_TYPE_XML_CORPUS
|| type == abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP
|| type == abigail::tools_utils::FILE_TYPE_ELF)
{
if (!opts.noout)
{
if (corp)
is_ok = write_corpus(*ctxt, corp, 0);
else if (group)
is_ok = write_corpus_group(*ctxt, group, 0);
}
}
}
if (!is_ok)
{
string output =
(type == abigail::tools_utils::FILE_TYPE_NATIVE_BI)
? "translation unit"
: "ABI corpus";
emit_prefix(argv[0], cerr)
<< "failed to write the translation unit "
<< opts.file_path << " back\n";
}
if (is_ok
&& opts.diff
&& ((type == abigail::tools_utils::FILE_TYPE_XML_CORPUS)
||type == abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP
|| type == abigail::tools_utils::FILE_TYPE_NATIVE_BI))
{
string cmd = "diff -u " + opts.file_path + " " + tmp_file->get_path();
if (system(cmd.c_str()))
is_ok = false;
}
return is_ok ? 0 : 1;
}
return 1;
}