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