• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- Mode: C++ -*-
3 //
4 // Copyright (C) 2013-2023 Red Hat, Inc.
5 //
6 // Author: Dodji Seketeli
7 
8 /// @file
9 ///
10 /// This program reads an elf file, try to load its debug info (in
11 /// DWARF format) and emit it back in a set of "text sections" in native
12 /// libabigail XML format.
13 
14 #include "config.h"
15 #include <unistd.h>
16 #include <cassert>
17 #include <cstdio>
18 #include <cstdlib>
19 #include <cstring>
20 #include <fstream>
21 #include <iostream>
22 #include <memory>
23 #include <string>
24 #include <vector>
25 #include <set>
26 #include "abg-config.h"
27 #include "abg-tools-utils.h"
28 #include "abg-corpus.h"
29 #include "abg-dwarf-reader.h"
30 #ifdef WITH_CTF
31 #include "abg-ctf-reader.h"
32 #endif
33 #ifdef WITH_BTF
34 #include "abg-btf-reader.h"
35 #endif
36 #include "abg-writer.h"
37 #include "abg-reader.h"
38 #include "abg-comparison.h"
39 #include "abg-suppression.h"
40 
41 using std::string;
42 using std::cerr;
43 using std::cout;
44 using std::ostream;
45 using std::ofstream;
46 using std::vector;
47 using std::set;
48 using std::shared_ptr;
49 using std::static_pointer_cast;
50 using abg_compat::optional;
51 using abigail::tools_utils::emit_prefix;
52 using abigail::tools_utils::temp_file;
53 using abigail::tools_utils::temp_file_sptr;
54 using abigail::tools_utils::check_file;
55 using abigail::tools_utils::build_corpus_group_from_kernel_dist_under;
56 using abigail::tools_utils::timer;
57 using abigail::tools_utils::create_best_elf_based_reader;
58 using abigail::tools_utils::stick_corpus_and_dependencies_into_corpus_group;
59 using abigail::tools_utils::stick_corpus_and_binaries_into_corpus_group;
60 using abigail::tools_utils::add_dependencies_into_corpus_group;
61 using abigail::ir::environment_sptr;
62 using abigail::ir::environment;
63 using abigail::corpus;
64 using abigail::corpus_sptr;
65 using abigail::translation_units;
66 using abigail::suppr::suppression_sptr;
67 using abigail::suppr::suppressions_type;
68 using abigail::suppr::read_suppressions;
69 using abigail::comparison::corpus_diff;
70 using abigail::comparison::corpus_diff_sptr;
71 using abigail::comparison::compute_diff;
72 using abigail::comparison::diff_context_sptr;
73 using abigail::comparison::diff_context;
74 using abigail::xml_writer::SEQUENCE_TYPE_ID_STYLE;
75 using abigail::xml_writer::HASH_TYPE_ID_STYLE;
76 using abigail::xml_writer::create_write_context;
77 using abigail::xml_writer::type_id_style_kind;
78 using abigail::xml_writer::write_context_sptr;
79 using abigail::xml_writer::write_corpus;
80 using abigail::xml_writer::write_corpus_group;
81 using abigail::abixml::read_corpus_from_abixml_file;
82 
83 using namespace abigail;
84 
85 struct options
86 {
87   string		wrong_option;
88   string		in_file_path;
89   string		out_file_path;
90   vector<char*>	di_root_paths;
91   vector<char**>	prepared_di_root_paths;
92   vector<string>	headers_dirs;
93   vector<string>	header_files;
94   vector<string>	added_bins_dirs;
95   vector<string>	added_bins;
96   string		vmlinux;
97   vector<string>	suppression_paths;
98   vector<string>	kabi_whitelist_paths;
99   suppressions_type	kabi_whitelist_supprs;
100   bool			display_version;
101   bool			display_abixml_version;
102   bool			check_alt_debug_info_path;
103   bool			show_base_name_alt_debug_info_path;
104   bool			write_architecture;
105   bool			write_corpus_path;
106   bool			write_comp_dir;
107   bool			write_elf_needed;
108   bool			write_parameter_names;
109   bool			short_locs;
110   bool			default_sizes;
111   bool			load_all_types;
112   bool			linux_kernel_mode;
113   bool			corpus_group_for_linux;
114   bool			show_stats;
115   bool			noout;
116   bool			follow_dependencies;
117   bool			list_dependencies;
118 #ifdef WITH_CTF
119   bool			use_ctf;
120 #endif
121 #ifdef WITH_BTF
122   bool			use_btf;
123 #endif
124   bool			show_locs;
125   bool			abidiff;
126 #ifdef WITH_DEBUG_SELF_COMPARISON
127   bool			debug_abidiff;
128 #endif
129 #ifdef WITH_DEBUG_TYPE_CANONICALIZATION
130   bool			debug_type_canonicalization;
131   bool			debug_die_canonicalization;
132 #endif
133   bool			annotate;
134   bool			do_log;
135   bool			drop_private_types;
136   bool			drop_undefined_syms;
137   bool			assume_odr_for_cplusplus;
138   bool			leverage_dwarf_factorization;
139   optional<bool>	exported_interfaces_only;
140   type_id_style_kind	type_id_style;
141 #ifdef WITH_DEBUG_SELF_COMPARISON
142   string		type_id_file_path;
143 #endif
144 
optionsoptions145   options()
146     : display_version(),
147       display_abixml_version(),
148       check_alt_debug_info_path(),
149       show_base_name_alt_debug_info_path(),
150       write_architecture(true),
151       write_corpus_path(true),
152       write_comp_dir(true),
153       write_elf_needed(true),
154       write_parameter_names(true),
155       short_locs(false),
156       default_sizes(true),
157       load_all_types(),
158       linux_kernel_mode(true),
159       corpus_group_for_linux(false),
160       show_stats(),
161       noout(),
162       follow_dependencies(),
163       list_dependencies(),
164 #ifdef WITH_CTF
165       use_ctf(false),
166 #endif
167 #ifdef WITH_BTF
168       use_btf(false),
169 #endif
170       show_locs(true),
171       abidiff(),
172 #ifdef WITH_DEBUG_SELF_COMPARISON
173       debug_abidiff(),
174 #endif
175 #ifdef WITH_DEBUG_TYPE_CANONICALIZATION
176       debug_type_canonicalization(),
177       debug_die_canonicalization(),
178 #endif
179       annotate(),
180       do_log(),
181       drop_private_types(false),
182       drop_undefined_syms(false),
183       assume_odr_for_cplusplus(true),
184       leverage_dwarf_factorization(true),
185       type_id_style(SEQUENCE_TYPE_ID_STYLE)
186   {}
187 
~optionsoptions188   ~options()
189   {
190     for (vector<char*>::iterator i = di_root_paths.begin();
191 	 i != di_root_paths.end();
192 	 ++i)
193       free(*i);
194 
195     prepared_di_root_paths.clear();
196   }
197 };
198 
199 static void
display_usage(const string & prog_name,ostream & out)200 display_usage(const string& prog_name, ostream& out)
201 {
202   emit_prefix(prog_name, out)
203     << "usage: " << prog_name << " [options] [<path-to-elf-file>]\n"
204     << " where options can be: \n"
205     << "  --help|-h  display this message\n"
206     << "  --version|-v  display program version information and exit\n"
207     << "  --abixml-version  display the version of the ABIXML ABI format\n"
208     << "  --debug-info-dir|-d <dir-path>  look for debug info under 'dir-path'\n"
209     << "  --headers-dir|--hd <path> the path to headers of the elf file\n"
210     << "  --header-file|--hf <path> the path one header of the elf file\n"
211     << "  --out-file <file-path>  write the output to 'file-path'\n"
212     << "  --noout  do not emit anything after reading the binary\n"
213     << "  --suppressions|--suppr <path> specify a suppression file\n"
214     << "  --no-architecture  do not emit architecture info in the output\n"
215     << "  --no-corpus-path  do not take the path to the corpora into account\n"
216     << "  --no-show-locs  do not show location information\n"
217     << "  --short-locs  only print filenames rather than paths\n"
218     << "  --drop-private-types  drop private types from representation\n"
219     << "  --drop-undefined-syms  drop undefined symbols from representation\n"
220     << "  --exported-interfaces-only  analyze exported interfaces only\n"
221     << "  --allow-non-exported-interfaces  analyze interfaces that "
222     "might not be exported\n"
223     << "  --no-comp-dir-path  do not show compilation path information\n"
224     << "  --no-elf-needed  do not show the DT_NEEDED information\n"
225     << "  --no-write-default-sizes  do not emit pointer size when it equals"
226     " the default address size of the translation unit\n"
227     << "  --no-parameter-names  do not show names of function parameters\n"
228     << "  --type-id-style <sequence|hash>  type id style (sequence(default): "
229        "\"type-id-\" + number; hash: hex-digits)\n"
230     << "  --check-alternate-debug-info <elf-path>  check alternate debug info "
231     "of <elf-path>\n"
232     << "  --check-alternate-debug-info-base-name <elf-path>  check alternate "
233     "debug info of <elf-path>, and show its base name\n"
234     << "  --load-all-types  read all types including those not reachable from "
235     "exported declarations\n"
236     << "  --no-linux-kernel-mode  don't consider the input binary as "
237        "a Linux Kernel binary\n"
238     << "  --kmi-whitelist|-w  path to a linux kernel "
239     "abi whitelist\n"
240     << "  --linux-tree|--lt  emit the ABI for the union of a "
241     "vmlinux and its modules\n"
242     << "  --vmlinux <path>  the path to the vmlinux binary to consider to emit "
243        "the ABI of the union of vmlinux and its modules\n"
244     << "  --abidiff  compare the loaded ABI against itself\n"
245     << "  --add-binaries <bin1,bin2,...>  build a corpus group with "
246     "the added inaries\n"
247     << "  --follow-dependencies  build a corpus group with the dependencies\n"
248     << "  --list-dependencies  list the dependencies of a given binary\n"
249     << "  --added-binaries-dir|--abd <dir-of-deps>  where to look for dependencies "
250     "or added binaries\n"
251 #ifdef WITH_DEBUG_SELF_COMPARISON
252     << "  --debug-abidiff  debug the process of comparing the loaded ABI against itself\n"
253 #endif
254 #ifdef WITH_DEBUG_TYPE_CANONICALIZATION
255     << "  --debug-tc  debug the type canonicalization process\n"
256     << "  --debug-dc  debug the DIE canonicalization process\n"
257 #endif
258 #ifdef WITH_CTF
259     << "  --ctf use CTF instead of DWARF in ELF files\n"
260 #endif
261     << "  --no-leverage-dwarf-factorization  do not use DWZ optimisations to "
262     "speed-up the analysis of the binary\n"
263     << "  --no-assume-odr-for-cplusplus  do not assume the ODR to speed-up the "
264     "analysis of the binary\n"
265 #ifdef WITH_BTF
266     << "  --btf use BTF instead of DWARF in ELF files\n"
267 #endif
268     << "  --annotate  annotate the ABI artifacts emitted in the output\n"
269     << "  --stats  show statistics about various internal stuff\n"
270     << "  --verbose show verbose messages about internal stuff\n";
271   ;
272 }
273 
274 static bool
parse_command_line(int argc,char * argv[],options & opts)275 parse_command_line(int argc, char* argv[], options& opts)
276 {
277   if (argc < 2)
278     return false;
279 
280   for (int i = 1; i < argc; ++i)
281     {
282       if (argv[i][0] != '-')
283 	{
284 	  if (opts.in_file_path.empty())
285 	    opts.in_file_path = argv[i];
286 	  else
287 	    return false;
288 	}
289       else if (!strcmp(argv[i], "--version")
290 	       || !strcmp(argv[i], "-v"))
291 	opts.display_version = true;
292       else if (!strcmp(argv[i], "--abixml-version")
293 	       || !strcmp(argv[i], "-v"))
294 	opts.display_abixml_version = true;
295       else if (!strcmp(argv[i], "--debug-info-dir")
296 	       || !strcmp(argv[i], "-d"))
297 	{
298 	  if (argc <= i + 1
299 	      || argv[i + 1][0] == '-')
300 	    return false;
301 	  // elfutils wants the root path to the debug info to be
302 	  // absolute.
303 	  opts.di_root_paths.push_back
304 	    (abigail::tools_utils::make_path_absolute_to_be_freed(argv[i + 1]));
305 	  ++i;
306 	}
307       else if (!strcmp(argv[i], "--headers-dir")
308 	       || !strcmp(argv[i], "--hd"))
309 	{
310 	  int j = i + 1;
311 	  if (j >= argc)
312 	    return false;
313 	  opts.headers_dirs.push_back(argv[j]);
314 	  ++i;
315 	}
316       else if (!strcmp(argv[i], "--added-binaries-dir")
317 	       || !strcmp(argv[i], "--abd"))
318 	{
319 	  int j = i + 1;
320 	  if (j >= argc)
321 	    return false;
322 	  opts.added_bins_dirs.push_back(argv[j]);
323 	  ++i;
324 	}
325       else if (!strcmp(argv[i], "--header-file")
326 	       || !strcmp(argv[i], "--hf"))
327 	{
328 	  int j = i + 1;
329 	  if (j >= argc)
330 	    return false;
331 	  opts.header_files.push_back(argv[j]);
332 	  ++i;
333 	}
334       else if (!strcmp(argv[i], "--out-file"))
335 	{
336 	  if (argc <= i + 1
337 	      || argv[i + 1][0] == '-'
338 	      || !opts.out_file_path.empty())
339 	    return false;
340 
341 	  opts.out_file_path = argv[i + 1];
342 	  ++i;
343 	}
344       else if (!strcmp(argv[i], "--suppressions")
345 	       || !strcmp(argv[i], "--suppr"))
346 	{
347 	  int j = i + 1;
348 	  if (j >= argc)
349 	    return false;
350 	  opts.suppression_paths.push_back(argv[j]);
351 	  ++i;
352 	}
353       else if (!strcmp(argv[i], "--kmi-whitelist")
354 	       || !strcmp(argv[i], "-w"))
355 	{
356 	  int j = i + 1;
357 	  if (j >= argc)
358 	    return false;
359 	  opts.kabi_whitelist_paths.push_back(argv[j]);
360 	  ++i;
361 	}
362       else if (!strcmp(argv[i], "--linux-tree")
363 	       || !strcmp(argv[i], "--lt"))
364 	opts.corpus_group_for_linux = true;
365       else if (!strcmp(argv[i], "--vmlinux"))
366 	{
367 	  int j = i + 1;
368 	  if (j >= argc)
369 	    return false;
370 	  opts.vmlinux = argv[j];
371 	  ++i;
372 	}
373       else if (!strcmp(argv[i], "--noout"))
374 	opts.noout = true;
375       else if (!strcmp(argv[i], "--follow-dependencies"))
376 	opts.follow_dependencies = true;
377       else if (!strcmp(argv[i], "--list-dependencies"))
378 	opts.list_dependencies = true;
379       else if (!strncmp(argv[i], "--add-binaries=",
380 			strlen("--add-binaries=")))
381 	tools_utils::get_comma_separated_args_of_option(argv[i],
382 							"--add-binaries=",
383 							opts.added_bins);
384       else if (!strcmp(argv[i], "--add-binaries"))
385 	{
386 	  int j = i + 1;
387 	  if (j >= argc)
388 	    return false;
389 
390 	  string s = argv[j];
391 	  if (s.find(','))
392 	    tools_utils::split_string(s, ",", opts.added_bins);
393 	  else
394 	    opts.added_bins.push_back(s);
395 	  ++i;
396 	}
397 #ifdef WITH_CTF
398         else if (!strcmp(argv[i], "--ctf"))
399           opts.use_ctf = true;
400 #endif
401 #ifdef WITH_BTF
402         else if (!strcmp(argv[i], "--btf"))
403           opts.use_btf = true;
404 #endif
405       else if (!strcmp(argv[i], "--no-architecture"))
406 	opts.write_architecture = false;
407       else if (!strcmp(argv[i], "--no-corpus-path"))
408 	opts.write_corpus_path = false;
409       else if (!strcmp(argv[i], "--no-show-locs"))
410 	opts.show_locs = false;
411       else if (!strcmp(argv[i], "--short-locs"))
412 	opts.short_locs = true;
413       else if (!strcmp(argv[i], "--no-comp-dir-path"))
414 	opts.write_comp_dir = false;
415       else if (!strcmp(argv[i], "--no-elf-needed"))
416 	opts.write_elf_needed = false;
417       else if (!strcmp(argv[i], "--no-write-default-sizes"))
418 	opts.default_sizes = false;
419       else if (!strcmp(argv[i], "--no-parameter-names"))
420 	opts.write_parameter_names = false;
421       else if (!strcmp(argv[i], "--type-id-style"))
422         {
423           ++i;
424           if (i >= argc)
425             return false;
426           if (!strcmp(argv[i], "sequence"))
427             opts.type_id_style = SEQUENCE_TYPE_ID_STYLE;
428           else if (!strcmp(argv[i], "hash"))
429             opts.type_id_style = HASH_TYPE_ID_STYLE;
430           else
431             return false;
432         }
433       else if (!strcmp(argv[i], "--check-alternate-debug-info")
434 	       || !strcmp(argv[i], "--check-alternate-debug-info-base-name"))
435 	{
436 	  if (argc <= i + 1
437 	      || argv[i + 1][0] == '-'
438 	      || !opts.in_file_path.empty())
439 	    return false;
440 	  if (!strcmp(argv[i], "--check-alternate-debug-info-base-name"))
441 	    opts.show_base_name_alt_debug_info_path = true;
442 	  opts.check_alt_debug_info_path = true;
443 	  opts.in_file_path = argv[i + 1];
444 	  ++i;
445 	}
446       else if (!strcmp(argv[i], "--load-all-types"))
447 	opts.load_all_types = true;
448       else if (!strcmp(argv[i], "--drop-private-types"))
449 	opts.drop_private_types = true;
450       else if (!strcmp(argv[i], "--drop-undefined-syms"))
451 	opts.drop_undefined_syms = true;
452       else if (!strcmp(argv[i], "--exported-interfaces-only"))
453 	opts.exported_interfaces_only = true;
454       else if (!strcmp(argv[i], "--allow-non-exported-interfaces"))
455 	opts.exported_interfaces_only = false;
456       else if (!strcmp(argv[i], "--no-linux-kernel-mode"))
457 	opts.linux_kernel_mode = false;
458       else if (!strcmp(argv[i], "--abidiff"))
459 	opts.abidiff = true;
460 #ifdef WITH_DEBUG_SELF_COMPARISON
461       else if (!strcmp(argv[i], "--debug-abidiff"))
462 	{
463 	  opts.abidiff = true;
464 	  opts.debug_abidiff = true;
465 	}
466 #endif
467 #ifdef WITH_DEBUG_TYPE_CANONICALIZATION
468       else if (!strcmp(argv[i], "--debug-tc")
469 	       || !strcmp(argv[i], "debug-type-canonicalization"))
470 	opts.debug_type_canonicalization = true;
471       else if (!strcmp(argv[i], "--debug-dc")
472 	       || !strcmp(argv[i], "debug-die-canonicalization"))
473 	opts.debug_die_canonicalization = true;
474 #endif
475       else if (!strcmp (argv[i], "--no-assume-odr-for-cplusplus"))
476 	opts.assume_odr_for_cplusplus = false;
477       else if (!strcmp (argv[i], "--no-leverage-dwarf-factorization"))
478 	opts.leverage_dwarf_factorization = false;
479       else if (!strcmp(argv[i], "--annotate"))
480 	opts.annotate = true;
481       else if (!strcmp(argv[i], "--stats"))
482 	opts.show_stats = true;
483       else if (!strcmp(argv[i], "--verbose"))
484 	opts.do_log = true;
485       else if (!strcmp(argv[i], "--help")
486 	       || !strcmp(argv[i], "--h"))
487 	return false;
488       else
489 	{
490 	  if (strlen(argv[i]) >= 2 && argv[i][0] == '-' && argv[i][1] == '-')
491 	    opts.wrong_option = argv[i];
492 	  return false;
493 	}
494     }
495 
496   return true;
497 }
498 
499 /// Initialize the context use for driving ABI comparison.
500 ///
501 /// @param ctxt the context to initialize.
502 static void
set_diff_context(diff_context_sptr & ctxt)503 set_diff_context(diff_context_sptr& ctxt)
504 {
505   ctxt->default_output_stream(&cerr);
506   ctxt->error_output_stream(&cerr);
507   // Filter out changes that are not meaningful from an ABI
508   // standpoint, from the diff output.
509   ctxt->switch_categories_off
510     (abigail::comparison::ACCESS_CHANGE_CATEGORY
511      | abigail::comparison::COMPATIBLE_TYPE_CHANGE_CATEGORY
512      | abigail::comparison::HARMLESS_DECL_NAME_CHANGE_CATEGORY);
513 }
514 
515 /// Check that the suppression specification files supplied are
516 /// present.  If not, emit an error on stderr.
517 ///
518 /// @param opts the options instance to use.
519 ///
520 /// @return true if all suppression specification files are present,
521 /// false otherwise.
522 static bool
maybe_check_suppression_files(const options & opts)523 maybe_check_suppression_files(const options& opts)
524 {
525   for (vector<string>::const_iterator i = opts.suppression_paths.begin();
526        i != opts.suppression_paths.end();
527        ++i)
528     if (!check_file(*i, cerr, "abidw"))
529       return false;
530 
531   for (vector<string>::const_iterator i =
532 	 opts.kabi_whitelist_paths.begin();
533        i != opts.kabi_whitelist_paths.end();
534        ++i)
535     if (!check_file(*i, cerr, "abidw"))
536       return false;
537 
538   return true;
539 }
540 
541 /// Check that the header files supplied are present.
542 /// If not, emit an error on stderr.
543 ///
544 /// @param opts the options instance to use.
545 ///
546 /// @return true if all header files are present, false otherwise.
547 static bool
maybe_check_header_files(const options & opts)548 maybe_check_header_files(const options& opts)
549 {
550   for (vector<string>::const_iterator file = opts.header_files.begin();
551        file != opts.header_files.end();
552        ++file)
553     if (!check_file(*file, cerr, "abidw"))
554       return false;
555 
556   return true;
557 }
558 
559 /// Set suppression specifications to the @p read_context used to load
560 /// the ABI corpus from the ELF/DWARF file.
561 ///
562 /// These suppression specifications are going to be applied to drop
563 /// some ABI artifacts on the floor (while reading the ELF/DWARF file)
564 /// and thus minimize the size of the resulting ABI corpus.
565 ///
566 /// @param read_ctxt the read context to apply the suppression
567 /// specifications to.
568 ///
569 /// @param opts the options where to get the suppression
570 /// specifications from.
571 static void
set_suppressions(abigail::elf_based_reader & rdr,options & opts)572 set_suppressions(abigail::elf_based_reader& rdr, options& opts)
573 {
574   suppressions_type supprs;
575   for (vector<string>::const_iterator i = opts.suppression_paths.begin();
576        i != opts.suppression_paths.end();
577        ++i)
578     read_suppressions(*i, supprs);
579 
580   suppression_sptr suppr =
581     abigail::tools_utils::gen_suppr_spec_from_headers(opts.headers_dirs,
582 						      opts.header_files);
583   if (suppr)
584     {
585       if (opts.drop_private_types)
586 	suppr->set_drops_artifact_from_ir(true);
587       supprs.push_back(suppr);
588     }
589 
590   using abigail::tools_utils::gen_suppr_spec_from_kernel_abi_whitelists;
591   const suppressions_type& wl_suppr =
592       gen_suppr_spec_from_kernel_abi_whitelists(opts.kabi_whitelist_paths);
593 
594   opts.kabi_whitelist_supprs.insert(opts.kabi_whitelist_supprs.end(),
595 				    wl_suppr.begin(), wl_suppr.end());
596 
597   rdr.add_suppressions(supprs);
598   rdr.add_suppressions(opts.kabi_whitelist_supprs);
599 }
600 
601 /// Set a bunch of tunable buttons on the ELF-based reader from the
602 /// command-line options.
603 ///
604 /// @param rdr the reader to tune.
605 ///
606 /// @param opts the command line options.
607 static void
set_generic_options(abigail::elf_based_reader & rdr,options & opts)608 set_generic_options(abigail::elf_based_reader& rdr, options& opts)
609 {
610   rdr.options().drop_undefined_syms = opts.drop_undefined_syms;
611   rdr.options().show_stats = opts.show_stats;
612   rdr.options().do_log = opts.do_log;
613   rdr.options().leverage_dwarf_factorization =
614     opts.leverage_dwarf_factorization;
615   rdr.options().assume_odr_for_cplusplus =
616     opts.assume_odr_for_cplusplus;
617 }
618 
619 /// Load an ABI @ref corpus (the internal representation of the ABI of
620 /// a binary) and write it out as an abixml.
621 ///
622 /// @param argv the arguments the program was called with.
623 ///
624 /// @param env the environment the ABI artifacts are being created in.
625 ///
626 /// @param opts the options of the program.
627 ///
628 /// @return the exit code: 0 if everything went fine, non-zero
629 /// otherwise.
630 static int
load_corpus_and_write_abixml(char * argv[],environment & env,options & opts)631 load_corpus_and_write_abixml(char* argv[],
632 			     environment& env,
633 			     options& opts)
634 {
635   int exit_code = 0;
636   timer t;
637 
638 #ifdef WITH_DEBUG_SELF_COMPARISON
639   if (opts.debug_abidiff)
640     env.self_comparison_debug_is_on(true);
641 #endif
642 
643 #ifdef WITH_DEBUG_TYPE_CANONICALIZATION
644   if (opts.debug_type_canonicalization)
645     env.debug_type_canonicalization_is_on(true);
646   if (opts.debug_die_canonicalization)
647     env.debug_die_canonicalization_is_on(true);
648 #endif
649 
650   corpus_sptr corp;
651   corpus_group_sptr corp_group;
652   fe_iface::status s = fe_iface::STATUS_UNKNOWN;
653   corpus::origin requested_fe_kind = corpus::DWARF_ORIGIN;
654 #ifdef WITH_CTF
655   if (opts.use_ctf)
656     requested_fe_kind = corpus::CTF_ORIGIN;
657 #endif
658 #ifdef WITH_BTF
659   if (opts.use_btf)
660     requested_fe_kind = corpus::BTF_ORIGIN;
661 #endif
662 
663   // First of all, create a reader to read the ABI from the file
664   // specfied in opts ...
665   abigail::elf_based_reader_sptr reader =
666     create_best_elf_based_reader(opts.in_file_path,
667 				 opts.prepared_di_root_paths,
668 				 env, requested_fe_kind,
669 				 opts.load_all_types,
670 				 opts.linux_kernel_mode);
671   ABG_ASSERT(reader);
672 
673   // ... then tune a bunch of "buttons" on the newly created reader
674   // ...
675   set_generic_options(*reader, opts);
676   set_suppressions(*reader, opts);
677 
678   // If the user asked us to check if we found the "alternate debug
679   // info file" associated to the input binary, then proceed to do so
680   // ...
681   if (opts.check_alt_debug_info_path)
682     {
683       string alt_di_path = reader->alternate_dwarf_debug_info_path();
684       if (!alt_di_path.empty())
685 	{
686 	  cout << "found the alternate debug info file";
687 	  if (opts.show_base_name_alt_debug_info_path)
688 	    {
689 	      tools_utils::base_name(alt_di_path, alt_di_path);
690 	      cout << " '" << alt_di_path << "'";
691 	    }
692 	  cout << "\n";
693 	  return 0;
694 	}
695       else
696 	{
697 	  emit_prefix(argv[0], cerr)
698 	    << "could not find alternate debug info file\n";
699 	  return 1;
700 	}
701     }
702 
703   // ... ff we are asked to only analyze exported interfaces (to stay
704   // concise), then take that into account ...
705   if (opts.exported_interfaces_only.has_value())
706     env.analyze_exported_interfaces_only(*opts.exported_interfaces_only);
707 
708   // And now, really read/analyze the ABI of the input file.
709   t.start();
710   corp = reader->read_corpus(s);
711   t.stop();
712   if (opts.do_log)
713     emit_prefix(argv[0], cerr)
714       << "read corpus from elf file in: " << t << "\n";
715 
716   if (opts.do_log)
717     emit_prefix(argv[0], cerr)
718       << "reset reader ELF in: " << t << "\n";
719 
720   // If we couldn't create a corpus, emit some (hopefully) useful
721   // diagnostics and return and error.
722   if (!corp)
723     {
724       if (s == fe_iface::STATUS_DEBUG_INFO_NOT_FOUND)
725 	{
726 	  if (opts.di_root_paths.empty())
727 	    {
728 	      emit_prefix(argv[0], cerr)
729 		<< "Could not read debug info from "
730 		<< opts.in_file_path << "\n";
731 
732 	      emit_prefix(argv[0], cerr)
733 		<< "You might want to supply the root directory where "
734 		"to search debug info from, using the "
735 		"--debug-info-dir option "
736 		"(e.g --debug-info-dir /usr/lib/debug)\n";
737 	    }
738 	  else
739 	    {
740 	      emit_prefix(argv[0], cerr)
741 		<< "Could not read debug info for '" << opts.in_file_path
742 		<< "' from debug info root directory '";
743 	      for (vector<char*>::const_iterator i =
744 		     opts.di_root_paths.begin();
745 		   i != opts.di_root_paths.end();
746 		   ++i)
747 		{
748 		  if (i != opts.di_root_paths.begin())
749 		    cerr << ", ";
750 		  cerr << *i;
751 		}
752 	    }
753 	}
754       else if (s == fe_iface::STATUS_NO_SYMBOLS_FOUND)
755 	emit_prefix(argv[0], cerr)
756 	  << "Could not read ELF symbol information from "
757 	  << opts.in_file_path << "\n";
758       else if (s & fe_iface::STATUS_ALT_DEBUG_INFO_NOT_FOUND)
759 	{
760 	  emit_prefix(argv[0], cerr)
761 	    << "Could not read alternate debug info file";
762 	  if (!reader->alternate_dwarf_debug_info_path().empty())
763 	    cerr << " '" << reader->alternate_dwarf_debug_info_path() << "'";
764 	  cerr << " for '"
765 	    << opts.in_file_path << "'.\n";
766 	  emit_prefix(argv[0], cerr)
767 	    << "You might have forgotten to install some "
768 	    "additional needed debug info\n";
769 	}
770 
771       return 1;
772     }
773 
774   if (opts.list_dependencies)
775     {
776       // Show the dependencies of the corpus and display them.
777       set<string> dependencies;
778       if (tools_utils::get_dependencies(*corp, opts.added_bins_dirs,
779 					dependencies))
780 	{
781 	  cout << "Dependencies of '" << corp->get_path()
782 	       << "':\n\t";
783 	  int n = 0;
784 	  for (const auto& dep : dependencies)
785 	    {
786 	      if (n)
787 		cout << ", ";
788 	      cout << dep;
789 	      ++n;
790 	    }
791 	  cout << "\n";
792 	}
793     }
794 
795   if (!opts.added_bins.empty())
796     corp_group =
797       stick_corpus_and_binaries_into_corpus_group(reader, corp,
798 						  opts.added_bins,
799 						  opts.added_bins_dirs);
800 
801   if (opts.follow_dependencies)
802     {
803       // load the dependencies of the corpus and put them all into a
804       // corpus group.
805 
806       // If a corpus_group already exists, use that one ...
807       if (!corp_group->is_empty())
808 	add_dependencies_into_corpus_group(reader, *corp,
809 					   opts.added_bins_dirs,
810 					   *corp_group);
811       else
812 	// .. otherwise, create a new corpus group.
813 	corp_group =
814 	  stick_corpus_and_dependencies_into_corpus_group(reader, corp,
815 							  opts.added_bins_dirs);
816     }
817 
818   // Clear some resources to gain back some space.
819   t.start();
820   reader.reset();
821   t.stop();
822 
823   // Now create a write context and write out an ABI XML description
824   // of the read corpus.
825   t.start();
826   const write_context_sptr& write_ctxt = create_write_context(env, cout);
827   set_common_options(*write_ctxt, opts);
828   t.stop();
829 
830   if (opts.do_log)
831     emit_prefix(argv[0], cerr)
832       << "created & initialized write context in: "
833       << t << "\n";
834 
835   if (opts.abidiff)
836     {
837       // Save the abi in abixml format in a temporary file, read
838       // it back, and compare the ABI of what we've read back
839       // against the ABI of the input ELF file.
840       temp_file_sptr tmp_file = temp_file::create();
841       set_ostream(*write_ctxt, tmp_file->get_stream());
842       if (corp_group)
843 	write_corpus_group(*write_ctxt, corp_group, 0);
844       else
845 	write_corpus(*write_ctxt, corp, 0);
846       tmp_file->get_stream().flush();
847 
848 #ifdef WITH_DEBUG_SELF_COMPARISON
849       if (opts.debug_abidiff)
850         {
851           opts.type_id_file_path = tmp_file->get_path() + string(".typeid");
852           write_canonical_type_ids(*write_ctxt, opts.type_id_file_path);
853         }
854 #endif
855       fe_iface_sptr rdr = abixml::create_reader(tmp_file->get_path(), env);
856 
857 #ifdef WITH_DEBUG_SELF_COMPARISON
858       if (opts.debug_abidiff
859           && !opts.type_id_file_path.empty())
860         load_canonical_type_ids(*rdr, opts.type_id_file_path);
861 #endif
862       t.start();
863       fe_iface::status sts;
864       corpus_sptr corp2;
865       corpus_group_sptr corp_group2;
866 
867       if (corp_group)
868 	corp_group2 = abixml::read_corpus_group_from_input(*rdr);
869       else
870       corp2 = rdr->read_corpus(sts);
871 
872       t.stop();
873       if (opts.do_log)
874         emit_prefix(argv[0], cerr)
875           << "Read corpus in: " << t << "\n";
876 
877       if (!corp2)
878         {
879           emit_prefix(argv[0], cerr)
880             << "Could not read temporary XML representation of "
881             "elf file back\n";
882           return 1;
883         }
884 
885       diff_context_sptr ctxt(new diff_context);
886       set_diff_context(ctxt);
887       ctxt->show_locs(opts.show_locs);
888       t.start();
889       corpus_diff_sptr diff =
890 	corp_group2
891 	? compute_diff(corp_group, corp_group2, ctxt)
892 	: compute_diff(corp, corp2, ctxt);
893 
894       t.stop();
895       if (opts.do_log)
896         emit_prefix(argv[0], cerr)
897           << "computed diff in: " << t << "\n";
898 
899       bool has_error = diff->has_changes();
900       if (has_error)
901         {
902           t.start();
903           diff->report(cerr);
904           t.stop();
905           if (opts.do_log)
906             emit_prefix(argv[0], cerr)
907               << "emitted report in: " << t << "\n";
908           return 1;
909         }
910       return 0;
911     }
912 
913 #ifdef WITH_DEBUG_SELF_COMPARISON
914   if (opts.debug_abidiff
915       && !opts.type_id_file_path.empty())
916     remove(opts.type_id_file_path.c_str());
917 #endif
918 
919   if (opts.noout)
920     return 0;
921 
922   if (!opts.out_file_path.empty())
923     {
924       ofstream of(opts.out_file_path.c_str(), std::ios_base::trunc);
925       if (!of.is_open())
926         {
927           emit_prefix(argv[0], cerr)
928             << "could not open output file '"
929             << opts.out_file_path << "'\n";
930           return 1;
931         }
932       set_ostream(*write_ctxt, of);
933       t.start();
934       if (corp_group)
935 	write_corpus_group(*write_ctxt, corp_group, 0);
936       else
937 	write_corpus(*write_ctxt, corp, 0);
938       t.stop();
939       if (opts.do_log)
940         emit_prefix(argv[0], cerr)
941           << "emitted abixml output in: " << t << "\n";
942       of.close();
943       return 0;
944     }
945   else
946     {
947       t.start();
948       exit_code =
949 	corp_group
950 	? !write_corpus_group(*write_ctxt, corp_group, 0)
951 	: !write_corpus(*write_ctxt, corp, 0);
952       t.stop();
953       if (opts.do_log)
954         emit_prefix(argv[0], cerr)
955           << "emitted abixml out in: " << t << "\n";
956     }
957 
958   return exit_code;
959 }
960 
961 /// Load a corpus group representing the union of a Linux Kernel
962 /// vmlinux binary and its modules, and emit an abixml representation
963 /// for it.
964 ///
965 /// @param argv the arguments this program was called with.
966 ///
967 /// @param env the environment the ABI artifacts are created in.
968 ///
969 /// @param opts the options this program was created with.
970 ///
971 /// @return the exit code.  Zero if everything went well, non-zero
972 /// otherwise.
973 static int
load_kernel_corpus_group_and_write_abixml(char * argv[],environment & env,options & opts)974 load_kernel_corpus_group_and_write_abixml(char* argv[],
975 					  environment& env,
976 					  options& opts)
977 {
978   if (!(tools_utils::is_dir(opts.in_file_path) && opts.corpus_group_for_linux))
979     return 1;
980 
981   int exit_code = 0;
982 
983   if (!opts.vmlinux.empty())
984     if (!abigail::tools_utils::check_file(opts.vmlinux, cerr, argv[0]))
985       return 1;
986 
987   timer t, global_timer;
988   suppressions_type supprs;
989 
990   if (opts.exported_interfaces_only.has_value())
991     env.analyze_exported_interfaces_only(*opts.exported_interfaces_only);
992 
993   if (opts.do_log)
994     emit_prefix(argv[0], cerr)
995       << "going to build ABI representation of the Linux Kernel ...\n";
996 
997   global_timer.start();
998   t.start();
999   corpus::origin requested_fe_kind =
1000 #ifdef WITH_CTF
1001     opts.use_ctf ? corpus::CTF_ORIGIN :
1002 #endif
1003     corpus::DWARF_ORIGIN;
1004   corpus_group_sptr group =
1005     build_corpus_group_from_kernel_dist_under(opts.in_file_path,
1006 					      /*debug_info_root=*/"",
1007 					      opts.vmlinux,
1008 					      opts.suppression_paths,
1009 					      opts.kabi_whitelist_paths,
1010 					      supprs, opts.do_log, env,
1011 					      requested_fe_kind);
1012   t.stop();
1013 
1014   if (opts.do_log)
1015     {
1016       emit_prefix(argv[0], cerr)
1017 	<< "built ABI representation of the Linux Kernel in: "
1018 	<< t << "\n";
1019     }
1020 
1021   if (!group)
1022     return 1;
1023 
1024   if (!opts.noout)
1025     {
1026       const xml_writer::write_context_sptr& ctxt
1027 	  = xml_writer::create_write_context(env, cout);
1028       set_common_options(*ctxt, opts);
1029 
1030       if (!opts.out_file_path.empty())
1031 	{
1032 	  ofstream of(opts.out_file_path.c_str(), std::ios_base::trunc);
1033 	  if (!of.is_open())
1034 	    {
1035 	      emit_prefix(argv[0], cerr)
1036 		<< "could not open output file '"
1037 		<< opts.out_file_path << "'\n";
1038 	      return 1;
1039 	    }
1040 
1041 	  if (opts.do_log)
1042 	    emit_prefix(argv[0], cerr)
1043 	      << "emitting the abixml output ...\n";
1044 	  set_ostream(*ctxt, of);
1045 	  t.start();
1046 	  exit_code = !write_corpus_group(*ctxt, group, 0);
1047 	  t.stop();
1048 	  if (opts.do_log)
1049 	    emit_prefix(argv[0], cerr)
1050 	      << "emitted abixml output in: " << t << "\n";
1051 	}
1052       else
1053 	{
1054 	  if (opts.do_log)
1055 	    emit_prefix(argv[0], cerr)
1056 	      << "emitting the abixml output ...\n";
1057 	  t.start();
1058 	  exit_code = !write_corpus_group(*ctxt, group, 0);
1059 	  t.stop();
1060 	  if (opts.do_log)
1061 	    emit_prefix(argv[0], cerr)
1062 	      << "emitted abixml output in: " << t << "\n";
1063 	}
1064     }
1065 
1066   global_timer.stop();
1067   if (opts.do_log)
1068     emit_prefix(argv[0], cerr)
1069       << "total processing done in " << global_timer << "\n";
1070   return exit_code;
1071 }
1072 
1073 /// Convert options::di_root_paths into
1074 /// options::prepared_di_root_paths which is the suitable type format
1075 /// that the dwarf_reader expects.
1076 ///
1077 /// @param o the options to consider.
1078 static void
prepare_di_root_paths(options & o)1079 prepare_di_root_paths(options& o)
1080 {
1081   tools_utils::convert_char_stars_to_char_star_stars(o.di_root_paths,
1082 						     o.prepared_di_root_paths);
1083 }
1084 
1085 int
main(int argc,char * argv[])1086 main(int argc, char* argv[])
1087 {
1088   options opts;
1089 
1090   if (!parse_command_line(argc, argv, opts)
1091       || (opts.in_file_path.empty()
1092 	  && !opts.display_version
1093 	  && !opts.display_abixml_version))
1094     {
1095       if (!opts.wrong_option.empty())
1096 	emit_prefix(argv[0], cerr)
1097 	  << "unrecognized option: " << opts.wrong_option << "\n";
1098       display_usage(argv[0], cerr);
1099       return 1;
1100     }
1101 
1102   if (opts.display_version)
1103     {
1104       emit_prefix(argv[0], cout)
1105 	<< abigail::tools_utils::get_library_version_string()
1106 	<< "\n";
1107       return 0;
1108     }
1109 
1110     if (opts.display_abixml_version)
1111       {
1112 	emit_prefix(argv[0], cout)
1113 	  << abigail::tools_utils::get_abixml_version_string()
1114 	  << "\n";
1115 	return 0;
1116       }
1117 
1118   ABG_ASSERT(!opts.in_file_path.empty());
1119   if (opts.corpus_group_for_linux)
1120     {
1121       if (!abigail::tools_utils::check_dir(opts.in_file_path, cerr, argv[0]))
1122 	return 1;
1123     }
1124   else
1125     {
1126       if (!abigail::tools_utils::check_file(opts.in_file_path, cerr, argv[0]))
1127 	return 1;
1128     }
1129 
1130   prepare_di_root_paths(opts);
1131 
1132   if (!maybe_check_suppression_files(opts))
1133     return 1;
1134 
1135   if (!maybe_check_header_files(opts))
1136     return 1;
1137 
1138   abigail::tools_utils::file_type type =
1139     abigail::tools_utils::guess_file_type(opts.in_file_path);
1140   if (type != abigail::tools_utils::FILE_TYPE_ELF
1141       && type != abigail::tools_utils::FILE_TYPE_AR
1142       && type != abigail::tools_utils::FILE_TYPE_DIR)
1143     {
1144       emit_prefix(argv[0], cerr)
1145 	<< "files of the kind of "<< opts.in_file_path << " are not handled\n";
1146       return 1;
1147     }
1148 
1149   environment env;
1150   int exit_code = 0;
1151 
1152   if (tools_utils::is_regular_file(opts.in_file_path))
1153     exit_code = load_corpus_and_write_abixml(argv, env, opts);
1154   else
1155     exit_code = load_kernel_corpus_group_and_write_abixml(argv, env, opts);
1156 
1157   return exit_code;
1158 }
1159