1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- Mode: C++ -*-
3 //
4 // Copyright (C) 2013-2021 Red Hat, Inc.
5 //
6 // Author: Dodji Seketeli
7 
8 /// @file
9 ///
10 /// This is a program aimed at checking that a binary instrumentation
11 /// (bi) file is well formed and valid enough.  It acts by loading an
12 /// input bi file and saving it back to a temporary file.  It then
13 /// runs a diff on the two files and expects the result of the diff to
14 /// be empty.
15 
16 #include <cstdio>
17 #include <cstdlib>
18 #include <cstring>
19 #include <fstream>
20 #include <iostream>
21 #include <memory>
22 #include <string>
23 #include <vector>
24 #include "abg-config.h"
25 #include "abg-tools-utils.h"
26 #include "abg-ir.h"
27 #include "abg-corpus.h"
28 #include "abg-reader.h"
29 #include "abg-dwarf-reader.h"
30 #include "abg-writer.h"
31 #include "abg-suppression.h"
32 
33 using std::string;
34 using std::cerr;
35 using std::cin;
36 using std::cout;
37 using std::ostream;
38 using std::ofstream;
39 using std::vector;
40 using abigail::tools_utils::emit_prefix;
41 using abigail::tools_utils::check_file;
42 using abigail::tools_utils::file_type;
43 using abigail::tools_utils::guess_file_type;
44 using abigail::suppr::suppression_sptr;
45 using abigail::suppr::suppressions_type;
46 using abigail::suppr::read_suppressions;
47 using abigail::corpus;
48 using abigail::corpus_sptr;
49 using abigail::xml_reader::read_translation_unit_from_file;
50 using abigail::xml_reader::read_translation_unit_from_istream;
51 using abigail::xml_reader::read_corpus_from_native_xml;
52 using abigail::xml_reader::read_corpus_from_native_xml_file;
53 using abigail::xml_reader::read_corpus_group_from_input;
54 using abigail::dwarf_reader::read_corpus_from_elf;
55 using abigail::xml_writer::write_translation_unit;
56 using abigail::xml_writer::write_context_sptr;
57 using abigail::xml_writer::create_write_context;
58 using abigail::xml_writer::write_corpus;
59 using abigail::xml_writer::write_corpus_to_archive;
60 
61 struct options
62 {
63   string			wrong_option;
64   string			file_path;
65   bool				display_version;
66   bool				read_from_stdin;
67   bool				read_tu;
68   bool				diff;
69   bool				noout;
70   std::shared_ptr<char>	di_root_path;
71   vector<string>		suppression_paths;
72   string			headers_dir;
73   vector<string>		header_files;
74 
optionsoptions75   options()
76     : display_version(false),
77       read_from_stdin(false),
78       read_tu(false),
79       diff(false),
80       noout(false)
81   {}
82 };//end struct options;
83 
84 static void
display_usage(const string & prog_name,ostream & out)85 display_usage(const string& prog_name, ostream& out)
86 {
87   emit_prefix(prog_name, out)
88     << "usage: " << prog_name << " [options] [<abi-file1>]\n"
89     << " where options can be:\n"
90     << "  --help  display this message\n"
91     << "  --version|-v  display program version information and exit\n"
92     << "  --debug-info-dir <path> the path under which to look for "
93     << "  --headers-dir|--hd <patch> the path to headers of the elf file\n"
94     "debug info for the elf <abi-file>\n"
95     << "  --header-file|--hf <path> the path to one header of the elf file\n"
96     "debug info for the elf <abi-file>\n"
97     << "  --suppressions|--suppr <path> specify a suppression file\n"
98     << "  --diff  for xml inputs, perform a text diff between "
99     "the input and the memory model saved back to disk\n"
100     << "  --noout  do not display anything on stdout\n"
101     << "  --stdin|--  read abi-file content from stdin\n"
102     << "  --tu  expect a single translation unit file\n";
103 }
104 
105 bool
parse_command_line(int argc,char * argv[],options & opts)106 parse_command_line(int argc, char* argv[], options& opts)
107 {
108   if (argc < 2)
109     {
110       opts.read_from_stdin = true;
111       return true;
112     }
113 
114     for (int i = 1; i < argc; ++i)
115       {
116 	if (argv[i][0] != '-')
117 	  {
118 	    if (opts.file_path.empty())
119 	      opts.file_path = argv[i];
120 	    else
121 	      return false;
122 	  }
123 	else if (!strcmp(argv[i], "--help"))
124 	  return false;
125 	else if (!strcmp(argv[i], "--version")
126 		 || !strcmp(argv[i], "-v"))
127 	  {
128 	    opts.display_version = true;
129 	    return true;
130 	  }
131 	else if (!strcmp(argv[i], "--debug-info-dir"))
132 	  {
133 	    if (argc <= i + 1
134 		|| argv[i + 1][0] == '-')
135 	      return false;
136 	    // elfutils wants the root path to the debug info to be
137 	    // absolute.
138 	    opts.di_root_path =
139 	      abigail::tools_utils::make_path_absolute(argv[i + 1]);
140 	    ++i;
141 	  }
142       else if (!strcmp(argv[i], "--headers-dir")
143 	       || !strcmp(argv[i], "--hd"))
144 	{
145 	  int j = i + 1;
146 	  if (j >= argc)
147 	    return false;
148 	  opts.headers_dir = argv[j];
149 	  ++i;
150 	}
151       else if (!strcmp(argv[i], "--header-file")
152 	       || !strcmp(argv[i], "--hf"))
153 	{
154 	  int j = i + 1;
155 	  if (j >= argc)
156 	    return false;
157 	  opts.header_files.push_back(argv[j]);
158 	  ++i;
159 	}
160       else if (!strcmp(argv[i], "--suppressions")
161 	       || !strcmp(argv[i], "--suppr"))
162 	{
163 	  int j = i + 1;
164 	  if (j >= argc)
165 	    {
166 	      opts.wrong_option = argv[i];
167 	      return true;
168 	    }
169 	  opts.suppression_paths.push_back(argv[j]);
170 	  ++i;
171 	}
172 	else if (!strcmp(argv[i], "--stdin"))
173 	  opts.read_from_stdin = true;
174 	else if (!strcmp(argv[i], "--tu"))
175 	  opts.read_tu = true;
176 	else if (!strcmp(argv[i], "--diff"))
177 	  opts.diff = true;
178 	else if (!strcmp(argv[i], "--noout"))
179 	  opts.noout = true;
180 	else
181 	  {
182 	    if (strlen(argv[i]) >= 2 && argv[i][0] == '-' && argv[i][1] == '-')
183 	      opts.wrong_option = argv[i];
184 	    return false;
185 	  }
186       }
187 
188     if (opts.file_path.empty())
189       opts.read_from_stdin = true;
190     return true;
191 }
192 
193 /// Check that the suppression specification files supplied are
194 /// present.  If not, emit an error on stderr.
195 ///
196 /// @param opts the options instance to use.
197 ///
198 /// @return true if all suppression specification files are present,
199 /// false otherwise.
200 static bool
maybe_check_suppression_files(const options & opts)201 maybe_check_suppression_files(const options& opts)
202 {
203   for (vector<string>::const_iterator i = opts.suppression_paths.begin();
204        i != opts.suppression_paths.end();
205        ++i)
206     if (!check_file(*i, cerr, "abidiff"))
207       return false;
208 
209   return true;
210 }
211 
212 /// Set suppression specifications to the @p read_context used to load
213 /// the ABI corpus from the ELF/DWARF file.
214 ///
215 /// These suppression specifications are going to be applied to drop
216 /// some ABI artifacts on the floor (while reading the ELF/DWARF file
217 /// or the native XML ABI file) and thus minimize the size of the
218 /// resulting ABI corpus.
219 ///
220 /// @param read_ctxt the read context to apply the suppression
221 /// specifications to.  Note that the type of this parameter is
222 /// generic (class template) because in practise, it can be either an
223 /// abigail::dwarf_reader::read_context type or an
224 /// abigail::xml_reader::read_context type.
225 ///
226 /// @param opts the options where to get the suppression
227 /// specifications from.
228 template<class ReadContextType>
229 static void
set_suppressions(ReadContextType & read_ctxt,const options & opts)230 set_suppressions(ReadContextType& read_ctxt, const options& opts)
231 {
232   suppressions_type supprs;
233   for (vector<string>::const_iterator i = opts.suppression_paths.begin();
234        i != opts.suppression_paths.end();
235        ++i)
236     read_suppressions(*i, supprs);
237 
238   suppression_sptr suppr =
239     abigail::tools_utils::gen_suppr_spec_from_headers(opts.headers_dir,
240 						      opts.header_files);
241   if (suppr)
242     supprs.push_back(suppr);
243 
244   add_read_context_suppressions(read_ctxt, supprs);
245 }
246 
247 /// Reads a bi (binary instrumentation) file, saves it back to a
248 /// temporary file and run a diff on the two versions.
249 int
main(int argc,char * argv[])250 main(int argc, char* argv[])
251 {
252   options opts;
253   if (!parse_command_line(argc, argv, opts))
254     {
255       if (!opts.wrong_option.empty())
256 	emit_prefix(argv[0], cerr)
257 	  << "unrecognized option: " << opts.wrong_option << "\n";
258       display_usage(argv[0], cerr);
259       return 1;
260     }
261 
262   if (opts.display_version)
263     {
264       emit_prefix(argv[0], cout)
265 	<< abigail::tools_utils::get_library_version_string();
266       return 0;
267     }
268 
269   if (!maybe_check_suppression_files(opts))
270     return 1;
271 
272   abigail::ir::environment_sptr env(new abigail::ir::environment);
273   if (opts.read_from_stdin)
274     {
275       if (!cin.good())
276 	return 1;
277 
278       if (opts.read_tu)
279 	{
280 	  abigail::translation_unit_sptr tu =
281 	    read_translation_unit_from_istream(&cin, env.get());
282 
283 	  if (!tu)
284 	    {
285 	      emit_prefix(argv[0], cerr)
286 		<< "failed to read the ABI instrumentation from stdin\n";
287 	      return 1;
288 	    }
289 
290 	  if (!opts.noout)
291 	    {
292 	      const write_context_sptr& ctxt
293 		  = create_write_context(tu->get_environment(), cout);
294 	      write_translation_unit(*ctxt, *tu, 0);
295 	    }
296 	  return 0;
297 	}
298       else
299 	{
300 	  abigail::xml_reader::read_context_sptr ctxt =
301 	    abigail::xml_reader::create_native_xml_read_context(&cin,
302 								env.get());
303 	  assert(ctxt);
304 	  set_suppressions(*ctxt, opts);
305 	  corpus_sptr corp = abigail::xml_reader::read_corpus_from_input(*ctxt);
306 	  if (!opts.noout)
307 	    {
308 	      const write_context_sptr& ctxt
309 		  = create_write_context(corp->get_environment(), cout);
310 	      write_corpus(*ctxt, corp, /*indent=*/0);
311 	    }
312 	  return 0;
313 	}
314     }
315   else if (!opts.file_path.empty())
316     {
317       if (!check_file(opts.file_path, cerr, argv[0]))
318 	return 1;
319       abigail::translation_unit_sptr tu;
320       abigail::corpus_sptr corp;
321       abigail::corpus_group_sptr group;
322       abigail::dwarf_reader::status s = abigail::dwarf_reader::STATUS_OK;
323       char* di_root_path = 0;
324       file_type type = guess_file_type(opts.file_path);
325 
326       switch (type)
327 	{
328 	case abigail::tools_utils::FILE_TYPE_UNKNOWN:
329 	  emit_prefix(argv[0], cerr)
330 	    << "Unknown file type given in input: " << opts.file_path;
331 	  return 1;
332 	case abigail::tools_utils::FILE_TYPE_NATIVE_BI:
333 	  tu = read_translation_unit_from_file(opts.file_path, env.get());
334 	  break;
335 	case abigail::tools_utils::FILE_TYPE_ELF:
336 	case abigail::tools_utils::FILE_TYPE_AR:
337 	  {
338 	    di_root_path = opts.di_root_path.get();
339 	    vector<char**> di_roots;
340 	    di_roots.push_back(&di_root_path);
341 	    abigail::dwarf_reader::read_context_sptr ctxt =
342 	      abigail::dwarf_reader::create_read_context(opts.file_path,
343 							 di_roots, env.get(),
344 							 /*load_all_types=*/false);
345 	    assert(ctxt);
346 	    set_suppressions(*ctxt, opts);
347 	    corp = read_corpus_from_elf(*ctxt, s);
348 	  }
349 	  break;
350 	case abigail::tools_utils::FILE_TYPE_XML_CORPUS:
351 	  {
352 	    abigail::xml_reader::read_context_sptr ctxt =
353 	      abigail::xml_reader::create_native_xml_read_context(opts.file_path,
354 								  env.get());
355 	    assert(ctxt);
356 	    set_suppressions(*ctxt, opts);
357 	    corp = read_corpus_from_input(*ctxt);
358 	    break;
359 	  }
360 	case abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP:
361 	  {
362 	    abigail::xml_reader::read_context_sptr ctxt =
363 	      abigail::xml_reader::create_native_xml_read_context(opts.file_path,
364 								  env.get());
365 	    assert(ctxt);
366 	    set_suppressions(*ctxt, opts);
367 	    group = read_corpus_group_from_input(*ctxt);
368 	  }
369 	  break;
370 	case abigail::tools_utils::FILE_TYPE_RPM:
371 	  break;
372 	case abigail::tools_utils::FILE_TYPE_SRPM:
373 	  break;
374 	case abigail::tools_utils::FILE_TYPE_DEB:
375 	  break;
376 	case abigail::tools_utils::FILE_TYPE_DIR:
377 	  break;
378 	case abigail::tools_utils::FILE_TYPE_TAR:
379 	  break;
380 	}
381 
382       if (!tu && !corp && !group)
383 	{
384 	  emit_prefix(argv[0], cerr)
385 	    << "failed to read " << opts.file_path << "\n";
386 	  if (!(s & abigail::dwarf_reader::STATUS_OK))
387 	    {
388 	      if (s & abigail::dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND)
389 		{
390 		  cerr << "could not find the debug info";
391 		  if(di_root_path == 0)
392 		    emit_prefix(argv[0], cerr)
393 		      << " Maybe you should consider using the "
394 		      "--debug-info-dir1 option to tell me about the "
395 		      "root directory of the debuginfo? "
396 		      "(e.g, --debug-info-dir1 /usr/lib/debug)\n";
397 		  else
398 		    emit_prefix(argv[0], cerr)
399 		      << "Maybe the root path to the debug "
400 		      "information is wrong?\n";
401 		}
402 	      if (s & abigail::dwarf_reader::STATUS_NO_SYMBOLS_FOUND)
403 		emit_prefix(argv[0], cerr)
404 		  << "could not find the ELF symbols in the file "
405 		  << opts.file_path
406 		  << "\n";
407 	    }
408 	  return 1;
409 	}
410 
411       using abigail::tools_utils::temp_file;
412       using abigail::tools_utils::temp_file_sptr;
413 
414       temp_file_sptr tmp_file = temp_file::create();
415       if (!tmp_file)
416 	{
417 	  emit_prefix(argv[0], cerr) << "failed to create temporary file\n";
418 	  return 1;
419 	}
420 
421       std::ostream& of = opts.diff ? tmp_file->get_stream() : cout;
422       const abigail::ir::environment* env = 0;
423       if (tu)
424 	env = tu->get_environment();
425       else if (corp)
426 	env = corp->get_environment();
427       else if (group)
428 	env = group->get_environment();
429 
430       ABG_ASSERT(env);
431       const write_context_sptr ctxt = create_write_context(env, of);
432 
433       bool is_ok = true;
434 
435       if (tu)
436 	{
437 	  if (!opts.noout)
438 	    is_ok = write_translation_unit(*ctxt, *tu, 0);
439 	}
440       else
441 	{
442 	  if (type == abigail::tools_utils::FILE_TYPE_XML_CORPUS
443 	      ||type == abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP
444 	      || type == abigail::tools_utils::FILE_TYPE_ELF)
445 	    {
446 	      if (!opts.noout)
447 		is_ok = write_corpus(*ctxt, corp, 0);
448 	    }
449 	}
450 
451       if (!is_ok)
452 	{
453 	  string output =
454 	    (type == abigail::tools_utils::FILE_TYPE_NATIVE_BI)
455 	    ? "translation unit"
456 	    : "ABI corpus";
457 	  emit_prefix(argv[0], cerr)
458 	    << "failed to write the translation unit "
459 	    << opts.file_path << " back\n";
460 	}
461 
462       if (is_ok
463 	  && opts.diff
464 	  && ((type == abigail::tools_utils::FILE_TYPE_XML_CORPUS)
465 	      ||type == abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP
466 	      || type == abigail::tools_utils::FILE_TYPE_NATIVE_BI))
467 	{
468 	  string cmd = "diff -u " + opts.file_path + " " + tmp_file->get_path();
469 	  if (system(cmd.c_str()))
470 	    is_ok = false;
471 	}
472 
473       return is_ok ? 0 : 1;
474     }
475 
476   return 1;
477 }
478