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