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