• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- Mode: C++ -*-
3 //
4 // Copyright (C) 2017-2023 Red Hat, Inc.
5 //
6 // Author: Dodji Seketeli
7 
8 /// @file
9 ///
10 /// The source code of the Kernel Module Interface Diff tool.
11 
12 #include "config.h"
13 #include <sys/types.h>
14 #include <dirent.h>
15 #include <cstring>
16 #include <string>
17 #include <vector>
18 #include <iostream>
19 
20 #include "abg-config.h"
21 #include "abg-tools-utils.h"
22 #include "abg-corpus.h"
23 #include "abg-dwarf-reader.h"
24 #include "abg-reader.h"
25 #include "abg-comparison.h"
26 
27 using std::string;
28 using std::vector;
29 using std::ostream;
30 using std::cout;
31 using std::cerr;
32 using abg_compat::optional;
33 
34 using namespace abigail::tools_utils;
35 using namespace abigail::ir;
36 using namespace abigail;
37 
38 using abigail::comparison::diff_context_sptr;
39 using abigail::comparison::diff_context;
40 using abigail::comparison::translation_unit_diff_sptr;
41 using abigail::comparison::corpus_diff;
42 using abigail::comparison::corpus_diff_sptr;
43 using abigail::comparison::compute_diff;
44 using abigail::comparison::get_default_harmless_categories_bitmap;
45 using abigail::comparison::get_default_harmful_categories_bitmap;
46 using abigail::suppr::suppression_sptr;
47 using abigail::suppr::suppressions_type;
48 using abigail::suppr::read_suppressions;
49 using abigail::tools_utils::guess_file_type;
50 using abigail::tools_utils::file_type;
51 
52 /// The options of this program.
53 struct options
54 {
55   bool			display_usage;
56   bool			display_version;
57   bool			verbose;
58   bool			missing_operand;
59   bool			perform_change_categorization;
60   bool			leaf_changes_only;
61   bool			show_hexadecimal_values;
62   bool			show_offsets_sizes_in_bits;
63   bool			show_impacted_interfaces;
64   optional<bool>	exported_interfaces_only;
65 #ifdef WITH_CTF
66   bool			use_ctf;
67 #endif
68 #ifdef WITH_BTF
69   bool			use_btf;
70 #endif
71   string		wrong_option;
72   string		kernel_dist_root1;
73   string		kernel_dist_root2;
74   string		vmlinux1;
75   string		vmlinux2;
76   vector<string>	kabi_whitelist_paths;
77   vector<string>	suppression_paths;
78   suppressions_type	read_time_supprs;
79   suppressions_type	diff_time_supprs;
80   shared_ptr<char>	di_root_path1;
81   shared_ptr<char>	di_root_path2;
82 
optionsoptions83   options()
84     : display_usage(),
85       display_version(),
86       verbose(),
87       missing_operand(),
88       perform_change_categorization(true),
89       leaf_changes_only(true),
90       show_hexadecimal_values(true),
91       show_offsets_sizes_in_bits(false),
92       show_impacted_interfaces(false)
93 #ifdef WITH_CTF
94       ,
95       use_ctf(false)
96 #endif
97 #ifdef WITH_BTF
98     ,
99       use_btf(false)
100 #endif
101   {}
102 }; // end struct options.
103 
104 /// Display the usage of the program.
105 ///
106 /// @param prog_name the name of this program.
107 ///
108 /// @param out the output stream the usage stream is sent to.
109 static void
display_usage(const string & prog_name,ostream & out)110 display_usage(const string& prog_name, ostream& out)
111 {
112   emit_prefix(prog_name, out)
113     << "usage: " << prog_name << " [options] kernel-modules-dir1 kernel-modules-dir2\n"
114     << " where options can be:\n"
115     << " --help|-h  display this message\n"
116     << " --version|-v  display program version information and exit\n"
117     << " --verbose  display verbose messages\n"
118     << " --debug-info-dir1|--d1 <path> the root for the debug info of "
119 	"the first kernel\n"
120     << " --debug-info-dir2|--d2 <path> the root for the debug info of "
121 	"the second kernel\n"
122     << " --vmlinux1|--l1 <path>  the path to the first vmlinux\n"
123     << " --vmlinux2|--l2 <path>  the path to the second vmlinux\n"
124     << " --suppressions|--suppr <path>  specify a suppression file\n"
125     << " --kmi-whitelist|-w <path>  path to a kernel module interface "
126     "whitelist\n"
127 #ifdef WITH_CTF
128     << " --ctf use CTF instead of DWARF in ELF files\n"
129 #endif
130 #ifdef WITH_BTF
131     << " --btf use BTF instead of DWARF in ELF files\n"
132 #endif
133     << " --no-change-categorization | -x don't perform categorization "
134     "of changes, for speed purposes\n"
135     << " --impacted-interfaces|-i  show interfaces impacted by ABI changes\n"
136     << " --full-impact|-f  show the full impact of changes on top-most "
137 	 "interfaces\n"
138     << " --exported-interfaces-only  analyze exported interfaces only\n"
139     << " --allow-non-exported-interfaces  analyze interfaces that "
140     "might not be exported\n"
141     << " --show-bytes  show size and offsets in bytes\n"
142     << " --show-bits  show size and offsets in bits\n"
143     << " --show-hex  show size and offset in hexadecimal\n"
144     << " --show-dec  show size and offset in decimal\n";
145 }
146 
147 /// Parse the command line of the program.
148 ///
149 /// @param argc the number of arguments on the command line, including
150 /// the program name.
151 ///
152 /// @param argv the arguments on the command line, including the
153 /// program name.
154 ///
155 /// @param opts the options resulting from the command line parsing.
156 ///
157 /// @return true iff the command line parsing went fine.
158 bool
parse_command_line(int argc,char * argv[],options & opts)159 parse_command_line(int argc, char* argv[], options& opts)
160 {
161   if (argc < 2)
162     return false;
163 
164   for (int i = 1; i < argc; ++i)
165     {
166       if (argv[i][0] != '-')
167 	{
168 	  if (opts.kernel_dist_root1.empty())
169 	    opts.kernel_dist_root1 = argv[i];
170 	  else if (opts.kernel_dist_root2.empty())
171 	    opts.kernel_dist_root2 = argv[i];
172 	  else
173 	    return false;
174 	}
175       else if (!strcmp(argv[i], "--verbose"))
176 	  opts.verbose = true;
177       else if (!strcmp(argv[i], "--version")
178 	       || !strcmp(argv[i], "-v"))
179 	{
180 	  opts.display_version = true;
181 	  return true;
182 	}
183       else if (!strcmp(argv[i], "--help")
184 	       || !strcmp(argv[i], "-h"))
185 	{
186 	  opts.display_usage = true;
187 	  return true;
188 	}
189       else if (!strcmp(argv[i], "--debug-info-dir1")
190 	       || !strcmp(argv[i], "--d1"))
191 	{
192 	  int j = i + 1;
193 	  if (j >= argc)
194 	    {
195 	      opts.missing_operand = true;
196 	      opts.wrong_option = argv[i];
197 	      return true;
198 	    }
199 	  // elfutils wants the root path to the debug info to be
200 	  // absolute.
201 	  opts.di_root_path1 =
202 	    abigail::tools_utils::make_path_absolute(argv[j]);
203 	  ++i;
204 	}
205       else if (!strcmp(argv[i], "--debug-info-dir2")
206 	       || !strcmp(argv[i], "--d2"))
207 	{
208 	  int j = i + 1;
209 	  if (j >= argc)
210 	    {
211 	      opts.missing_operand = true;
212 	      opts.wrong_option = argv[i];
213 	      return true;
214 	    }
215 	  // elfutils wants the root path to the debug info to be
216 	  // absolute.
217 	  opts.di_root_path2 =
218 	    abigail::tools_utils::make_path_absolute(argv[j]);
219 	  ++i;
220 	}
221       else if (!strcmp(argv[i], "--vmlinux1")
222 	       || !strcmp(argv[i], "--l1"))
223 	{
224 	  int j = i + 1;
225 	  if (j >= argc)
226 	    {
227 	      opts.missing_operand = true;
228 	      opts.wrong_option = argv[i];
229 	      return false;
230 	    }
231 	  opts.vmlinux1 = argv[j];
232 	  ++i;
233 	}
234       else if (!strcmp(argv[i], "--vmlinux2")
235 	       || !strcmp(argv[i], "--l2"))
236 	{
237 	  int j = i + 1;
238 	  if (j >= argc)
239 	    {
240 	      opts.missing_operand = true;
241 	      opts.wrong_option = argv[i];
242 	      return false;
243 	    }
244 	  opts.vmlinux2 = argv[j];
245 	  ++i;
246 	}
247       else if (!strcmp(argv[i], "--kmi-whitelist")
248 	       || !strcmp(argv[i], "-w"))
249 	{
250 	  int j = i + 1;
251 	  if (j >= argc)
252 	    {
253 	      opts.missing_operand = true;
254 	      opts.wrong_option = argv[i];
255 	      return false;
256 	    }
257 	  opts.kabi_whitelist_paths.push_back(argv[j]);
258 	  ++i;
259 	}
260       else if (!strcmp(argv[i], "--suppressions")
261 	       || !strcmp(argv[i], "--suppr"))
262 	{
263 	  int j = i + 1;
264 	  if (j >= argc)
265 	    {
266 	      opts.missing_operand = true;
267 	      opts.wrong_option = argv[i];
268 	      return false;
269 	    }
270 	  opts.suppression_paths.push_back(argv[j]);
271 	  ++i;
272 	}
273 #ifdef WITH_CTF
274       else if (!strcmp(argv[i], "--ctf"))
275 	opts.use_ctf = true;
276 #endif
277 #ifdef WITH_BTF
278       else if (!strcmp(argv[i], "--btf"))
279 	opts.use_btf = true;
280 #endif
281       else if (!strcmp(argv[i], "--no-change-categorization")
282 	       || !strcmp(argv[i], "-x"))
283 	opts.perform_change_categorization = false;
284       else if (!strcmp(argv[i], "--impacted-interfaces")
285 	       || !strcmp(argv[i], "-i"))
286 	opts.show_impacted_interfaces = true;
287       else if (!strcmp(argv[i], "--full-impact")
288 	       || !strcmp(argv[i], "-f"))
289 	opts.leaf_changes_only = false;
290       else if (!strcmp(argv[i], "--exported-interfaces-only"))
291 	opts.exported_interfaces_only = true;
292       else if (!strcmp(argv[i], "--allow-non-exported-interfaces"))
293 	opts.exported_interfaces_only = false;
294       else if (!strcmp(argv[i], "--show-bytes"))
295 	opts.show_offsets_sizes_in_bits = false;
296       else if (!strcmp(argv[i], "--show-bits"))
297 	opts.show_offsets_sizes_in_bits = true;
298       else if (!strcmp(argv[i], "--show-hex"))
299 	opts.show_hexadecimal_values = true;
300       else if (!strcmp(argv[i], "--show-dec"))
301 	opts.show_hexadecimal_values = false;
302       else
303 	{
304 	  opts.wrong_option = argv[i];
305 	  return false;
306 	}
307     }
308 
309   return true;
310 }
311 
312 /// Check that the suppression specification files supplied are
313 /// present.  If not, emit an error on stderr.
314 ///
315 /// @param opts the options instance to use.
316 ///
317 /// @return true if all suppression specification files are present,
318 /// false otherwise.
319 static bool
maybe_check_suppression_files(const options & opts)320 maybe_check_suppression_files(const options& opts)
321 {
322   for (vector<string>::const_iterator i = opts.suppression_paths.begin();
323        i != opts.suppression_paths.end();
324        ++i)
325     if (!check_file(*i, cerr, "abidiff"))
326       return false;
327 
328   for (vector<string>::const_iterator i =
329 	 opts.kabi_whitelist_paths.begin();
330        i != opts.kabi_whitelist_paths.end();
331        ++i)
332     if (!check_file(*i, cerr, "abidiff"))
333       return false;
334 
335   return true;
336 }
337 
338 /// Setup the diff context from the program's options.
339 ///
340 /// @param ctxt the diff context to consider.
341 ///
342 /// @param opts the options to set the context.
343 static void
set_diff_context(diff_context_sptr ctxt,const options & opts)344 set_diff_context(diff_context_sptr ctxt, const options& opts)
345 {
346   ctxt->default_output_stream(&cout);
347   ctxt->error_output_stream(&cerr);
348   ctxt->show_relative_offset_changes(true);
349   ctxt->show_redundant_changes(false);
350   ctxt->show_locs(true);
351   ctxt->show_linkage_names(false);
352   ctxt->show_symbols_unreferenced_by_debug_info
353     (true);
354   ctxt->perform_change_categorization(opts.perform_change_categorization);
355   ctxt->show_leaf_changes_only(opts.leaf_changes_only);
356   ctxt->show_impacted_interfaces(opts.show_impacted_interfaces);
357   ctxt->show_hex_values(opts.show_hexadecimal_values);
358   ctxt->show_offsets_sizes_in_bits(opts.show_offsets_sizes_in_bits);
359 
360   ctxt->switch_categories_off(get_default_harmless_categories_bitmap());
361 
362   if (!opts.diff_time_supprs.empty())
363     ctxt->add_suppressions(opts.diff_time_supprs);
364 }
365 
366 /// Print information about the kernel (and modules) binaries found
367 /// under a given directory.
368 ///
369 /// Note that this function actually look for the modules iff the
370 /// --verbose option was provided.
371 ///
372 /// @param root the directory to consider.
373 ///
374 /// @param opts the options to use during the search.
375 static void
print_kernel_dist_binary_paths_under(const string & root,const options & opts)376 print_kernel_dist_binary_paths_under(const string& root, const options &opts)
377 {
378   string vmlinux;
379   vector<string> modules;
380 
381   if (opts.verbose)
382     if (get_binary_paths_from_kernel_dist(root, /*debug_info_root_path*/"",
383 					  vmlinux, modules))
384        {
385 	 cout << "Found kernel binaries under: '" << root << "'\n";
386 	 if (!vmlinux.empty())
387 	   cout << "[linux kernel binary]\n"
388 		<< "        '" << vmlinux << "'\n";
389 	 if (!modules.empty())
390 	   {
391 	     cout << "[linux kernel module binaries]\n";
392 	     for (vector<string>::const_iterator p = modules.begin();
393 		  p != modules.end();
394 		  ++p)
395 	       cout << "        '" << *p << "' \n";
396 	   }
397 	 cout << "\n";
398        }
399 }
400 
401 int
main(int argc,char * argv[])402 main(int argc, char* argv[])
403 {
404   options opts;
405   if (!parse_command_line(argc, argv, opts))
406     {
407       emit_prefix(argv[0], cerr)
408 	<< "unrecognized option: "
409 	<< opts.wrong_option << "\n"
410 	<< "try the --help option for more information\n";
411       return 1;
412     }
413 
414   if (opts.missing_operand)
415     {
416       emit_prefix(argv[0], cerr)
417 	<< "missing operand to option: " << opts.wrong_option <<"\n"
418 	<< "try the --help option for more information\n";
419       return 1;
420     }
421 
422   if (!maybe_check_suppression_files(opts))
423     return 1;
424 
425   if (opts.display_usage)
426     {
427       display_usage(argv[0], cout);
428       return 1;
429     }
430 
431   if (opts.display_version)
432     {
433       emit_prefix(argv[0], cout)
434 	<< abigail::tools_utils::get_library_version_string()
435 	<< "\n";
436       return 0;
437     }
438 
439   environment env;
440 
441   if (opts.exported_interfaces_only.has_value())
442     env.analyze_exported_interfaces_only(*opts.exported_interfaces_only);
443 
444   corpus_group_sptr group1, group2;
445   string debug_info_root_dir;
446   corpus::origin requested_fe_kind = corpus::DWARF_ORIGIN;
447 #ifdef WITH_CTF
448   if (opts.use_ctf)
449     requested_fe_kind = corpus::CTF_ORIGIN;
450 #endif
451 #ifdef WITH_BTF
452   if (opts.use_btf)
453     requested_fe_kind = corpus::BTF_ORIGIN;
454 #endif
455 
456   if (!opts.kernel_dist_root1.empty())
457     {
458       file_type ftype = guess_file_type(opts.kernel_dist_root1);
459       if (ftype == FILE_TYPE_DIR)
460 	{
461 	  debug_info_root_dir = opts.di_root_path1.get()
462 	    ? opts.di_root_path1.get()
463 	    : "";
464 
465 	  group1 =
466 	    build_corpus_group_from_kernel_dist_under(opts.kernel_dist_root1,
467 						      debug_info_root_dir,
468 						      opts.vmlinux1,
469 						      opts.suppression_paths,
470 						      opts.kabi_whitelist_paths,
471 						      opts.read_time_supprs,
472 						      opts.verbose, env,
473 						      requested_fe_kind);
474 	  print_kernel_dist_binary_paths_under(opts.kernel_dist_root1, opts);
475 	}
476       else if (ftype == FILE_TYPE_XML_CORPUS_GROUP)
477 	group1 =
478 	  abixml::read_corpus_group_from_abixml_file(opts.kernel_dist_root1,
479 						     env);
480 
481     }
482 
483   if (!opts.kernel_dist_root2.empty())
484     {
485       file_type ftype = guess_file_type(opts.kernel_dist_root2);
486       if (ftype == FILE_TYPE_DIR)
487 	{
488 	  debug_info_root_dir = opts.di_root_path2.get()
489 	    ? opts.di_root_path2.get()
490 	    : "";
491 	  group2 =
492 	    build_corpus_group_from_kernel_dist_under(opts.kernel_dist_root2,
493 						      debug_info_root_dir,
494 						      opts.vmlinux2,
495 						      opts.suppression_paths,
496 						      opts.kabi_whitelist_paths,
497 						      opts.read_time_supprs,
498 						      opts.verbose, env,
499 						      requested_fe_kind);
500 	  print_kernel_dist_binary_paths_under(opts.kernel_dist_root2, opts);
501 	}
502       else if (ftype == FILE_TYPE_XML_CORPUS_GROUP)
503 	group2 =
504 	  abixml::read_corpus_group_from_abixml_file(opts.kernel_dist_root2,
505 						     env);
506     }
507 
508   abidiff_status status = abigail::tools_utils::ABIDIFF_OK;
509   if (group1 && group2)
510     {
511       diff_context_sptr diff_ctxt(new diff_context);
512       set_diff_context(diff_ctxt, opts);
513 
514       corpus_diff_sptr diff= compute_diff(group1, group2, diff_ctxt);
515 
516       if (diff->has_net_changes())
517 	status = abigail::tools_utils::ABIDIFF_ABI_CHANGE;
518 
519       if (diff->has_incompatible_changes())
520 	status |= abigail::tools_utils::ABIDIFF_ABI_INCOMPATIBLE_CHANGE;
521 
522       if (diff->has_changes())
523 	diff->report(cout);
524     }
525   else
526     status = abigail::tools_utils::ABIDIFF_ERROR;
527 
528   return status;
529 }
530