• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- Mode: C++ -*-
3 //
4 // Copyright (C) 2020 Google, Inc.
5 //
6 // Author: Matthias Maennich
7 
8 /// @file
9 ///
10 /// This program tests symtab invariants through abg-corpus.
11 
12 #include <iostream>
13 #include <limits>
14 #include <string>
15 #include <vector>
16 
17 #include "abg-corpus.h"
18 #include "abg-dwarf-reader.h"
19 #include "abg-fwd.h"
20 #include "abg-ir.h"
21 #include "abg-tools-utils.h"
22 #include "lib/catch.hpp"
23 #include "test-utils.h"
24 
25 using namespace abigail;
26 
27 using dwarf_reader::create_read_context;
28 using dwarf_reader::read_context_sptr;
29 using dwarf_reader::read_corpus_from_elf;
30 using ir::environment;
31 using ir::environment_sptr;
32 using suppr::suppressions_type;
33 
34 static const std::string test_data_dir =
35     std::string(abigail::tests::get_src_dir()) + "/tests/data/test-symtab/";
36 
37 dwarf_reader::status
read_corpus(const std::string & path,corpus_sptr & result,const std::vector<std::string> & whitelist_paths=std::vector<std::string> ())38 read_corpus(const std::string&		    path,
39 	    corpus_sptr&		    result,
40 	    const std::vector<std::string>& whitelist_paths =
41 		std::vector<std::string>())
42 {
43   const std::string& absolute_path = test_data_dir + path;
44 
45   environment_sptr	    env(new environment);
46   const std::vector<char**> debug_info_root_paths;
47   read_context_sptr	    ctxt =
48       create_read_context(absolute_path, debug_info_root_paths, env.get(),
49 			  /* load_all_type = */ true,
50 			  /* linux_kernel_mode = */ true);
51 
52   if (!whitelist_paths.empty())
53     {
54       const suppressions_type& wl_suppr =
55 	tools_utils::gen_suppr_spec_from_kernel_abi_whitelists(
56 	  whitelist_paths);
57       REQUIRE_FALSE(wl_suppr.empty());
58       dwarf_reader::add_read_context_suppressions(*ctxt, wl_suppr);
59     }
60 
61   dwarf_reader::status status = dwarf_reader::STATUS_UNKNOWN;
62   result = read_corpus_from_elf(*ctxt, status);
63 
64   REQUIRE(status != dwarf_reader::STATUS_UNKNOWN);
65   return status;
66 }
67 
68 TEST_CASE("Symtab::Empty", "[symtab, basic]")
69 {
70   const std::string	     binary = "basic/empty.so";
71   corpus_sptr		     corpus_ptr;
72   const dwarf_reader::status status = read_corpus(binary, corpus_ptr);
73   REQUIRE(!corpus_ptr);
74 
75   REQUIRE((status & dwarf_reader::STATUS_NO_SYMBOLS_FOUND));
76 }
77 
78 TEST_CASE("Symtab::NoDebugInfo", "[symtab, basic]")
79 {
80   const std::string	     binary = "basic/no_debug_info.so";
81   corpus_sptr		     corpus_ptr;
82   const dwarf_reader::status status = read_corpus(binary, corpus_ptr);
83   REQUIRE(corpus_ptr);
84 
85   REQUIRE(status
86 	  == (dwarf_reader::STATUS_OK
87 	      | dwarf_reader::STATUS_DEBUG_INFO_NOT_FOUND));
88 }
89 
90 // this value indicates in the following helper method, that we do not want to
91 // assert for this particular value. In other words, N is a placeholder for an
92 // arbitrary value.
93 #define N std::numeric_limits<size_t>::max()
94 
95 corpus_sptr
assert_symbol_count(const std::string & path,size_t function_symbols=0,size_t variable_symbols=0,size_t undefined_function_symbols=0,size_t undefined_variable_symbols=0,const std::vector<std::string> & whitelist_paths=std::vector<std::string> ())96 assert_symbol_count(const std::string& path,
97 		    size_t	       function_symbols = 0,
98 		    size_t	       variable_symbols = 0,
99 		    size_t	       undefined_function_symbols = 0,
100 		    size_t	       undefined_variable_symbols = 0,
101 		    const std::vector<std::string>& whitelist_paths =
102 			std::vector<std::string>())
103 {
104   corpus_sptr		     corpus_ptr;
105   const dwarf_reader::status status =
106     read_corpus(path, corpus_ptr, whitelist_paths);
107   REQUIRE(corpus_ptr);
108 
109   REQUIRE((status & dwarf_reader::STATUS_OK));
110   const corpus& corpus = *corpus_ptr;
111 
112   size_t total_symbols = 0;
113 
114   if (function_symbols != N)
115     {
116       CHECK(corpus.get_sorted_fun_symbols().size() == function_symbols);
117       CHECK(corpus.get_fun_symbol_map().size() == function_symbols);
118       total_symbols += function_symbols;
119     }
120   if (variable_symbols != N)
121     {
122       CHECK(corpus.get_sorted_var_symbols().size() == variable_symbols);
123       CHECK(corpus.get_var_symbol_map().size() == variable_symbols);
124       total_symbols += variable_symbols;
125     }
126   if (undefined_variable_symbols != N)
127     {
128       CHECK(corpus.get_sorted_undefined_fun_symbols().size()
129 	    == undefined_function_symbols);
130       CHECK(corpus.get_undefined_fun_symbol_map().size()
131 	    == undefined_function_symbols);
132       total_symbols += undefined_function_symbols;
133     }
134   if (undefined_function_symbols != N)
135     {
136       CHECK(corpus.get_sorted_undefined_var_symbols().size()
137 	    == undefined_variable_symbols);
138       CHECK(corpus.get_undefined_var_symbol_map().size()
139 	    == undefined_variable_symbols);
140       total_symbols += undefined_variable_symbols;
141     }
142 
143   // assert the corpus reports being empty consistently with the symbol count
144   CHECK(corpus.is_empty() == (total_symbols == 0));
145 
146   return corpus_ptr;
147 }
148 
149 TEST_CASE("Symtab::SimpleSymtabs", "[symtab, basic]")
150 {
151   GIVEN("a binary with no exported symbols")
152   {
153     // TODO: should pass, but does currently not as empty tables are treated
154     //       like the error case, but this is an edge case anyway.
155     // assert_symbol_count("empty.so");
156   }
157 
158   GIVEN("a binary with a single exported function")
159   {
160     const std::string	   binary = "basic/single_function.so";
161     const corpus_sptr&	   corpus = assert_symbol_count(binary, 1, 0);
162     const elf_symbol_sptr& symbol =
163 	corpus->lookup_function_symbol("exported_function");
164     REQUIRE(symbol);
165     CHECK(!corpus->lookup_variable_symbol("exported_function"));
166     CHECK(symbol == corpus->lookup_function_symbol(*symbol));
167     CHECK(symbol != corpus->lookup_variable_symbol(*symbol));
168   }
169 
170   GIVEN("a binary with a single exported variable")
171   {
172     const std::string	   binary = "basic/single_variable.so";
173     const corpus_sptr&	   corpus = assert_symbol_count(binary, 0, 1);
174     const elf_symbol_sptr& symbol =
175 	corpus->lookup_variable_symbol("exported_variable");
176     REQUIRE(symbol);
177     CHECK(!corpus->lookup_function_symbol("exported_variable"));
178     CHECK(symbol == corpus->lookup_variable_symbol(*symbol));
179     CHECK(symbol != corpus->lookup_function_symbol(*symbol));
180   }
181 
182   GIVEN("a binary with one function and one variable exported")
183   {
184     const std::string  binary = "basic/one_function_one_variable.so";
185     const corpus_sptr& corpus = assert_symbol_count(binary, 1, 1);
186     CHECK(corpus->lookup_function_symbol("exported_function"));
187     CHECK(!corpus->lookup_variable_symbol("exported_function"));
188     CHECK(corpus->lookup_variable_symbol("exported_variable"));
189     CHECK(!corpus->lookup_function_symbol("exported_variable"));
190   }
191 
192   GIVEN("a binary with a single undefined function")
193   {
194     const std::string  binary = "basic/single_undefined_function.so";
195     const corpus_sptr corpus = assert_symbol_count(binary, 0, 0, 1, 0);
196   }
197 
198   GIVEN("a binary with a single undefined variable")
199   {
200     const std::string  binary = "basic/single_undefined_variable.so";
201     const corpus_sptr corpus = assert_symbol_count(binary, 0, 0, 0, 1);
202   }
203 
204   GIVEN("a binary with one function and one variable undefined")
205   {
206     const std::string  binary = "basic/one_function_one_variable_undefined.so";
207     const corpus_sptr corpus = assert_symbol_count(binary, 0, 0, 1, 1);
208   }
209 }
210 
211 TEST_CASE("Symtab::SymtabWithWhitelist", "[symtab, whitelist]")
212 {
213   GIVEN("a binary with one function and one variable exported")
214   {
215     const std::string binary = "basic/one_function_one_variable.so";
216 
217     GIVEN("we read the binary without any whitelists")
218     {
219       const corpus_sptr& corpus = assert_symbol_count(binary, 1, 1);
220       CHECK(corpus->lookup_function_symbol("exported_function"));
221       CHECK(!corpus->lookup_variable_symbol("exported_function"));
222       CHECK(corpus->lookup_variable_symbol("exported_variable"));
223       CHECK(!corpus->lookup_function_symbol("exported_variable"));
224     }
225 
226     GIVEN("we read the binary with all symbols on the whitelists")
227     {
228       std::vector<std::string> whitelists;
229       whitelists.push_back(test_data_dir
230 			   + "basic/one_function_one_variable_all.whitelist");
231       const corpus_sptr& corpus =
232 	assert_symbol_count(binary, 1, 1, 0, 0, whitelists);
233       CHECK(corpus->lookup_function_symbol("exported_function"));
234       CHECK(!corpus->lookup_variable_symbol("exported_function"));
235       CHECK(corpus->lookup_variable_symbol("exported_variable"));
236       CHECK(!corpus->lookup_function_symbol("exported_variable"));
237     }
238 
239     GIVEN("we read the binary with only irrelevant symbols whitelisted")
240     {
241       std::vector<std::string> whitelists;
242       whitelists.push_back(
243 	test_data_dir
244 	+ "basic/one_function_one_variable_irrelevant.whitelist");
245 
246       corpus_sptr		 corpus_ptr;
247       const dwarf_reader::status status =
248 	read_corpus(binary, corpus_ptr, whitelists);
249       REQUIRE(!corpus_ptr);
250       REQUIRE((status & dwarf_reader::STATUS_NO_SYMBOLS_FOUND));
251     }
252 
253     GIVEN("we read the binary with only the function whitelisted")
254     {
255       std::vector<std::string> whitelists;
256       whitelists.push_back(
257 	test_data_dir + "basic/one_function_one_variable_function.whitelist");
258       const corpus_sptr& corpus =
259 	assert_symbol_count(binary, 1, 0, 0, 0, whitelists);
260       CHECK(corpus->lookup_function_symbol("exported_function"));
261       CHECK(!corpus->lookup_variable_symbol("exported_function"));
262       CHECK(!corpus->lookup_variable_symbol("exported_variable"));
263       CHECK(!corpus->lookup_function_symbol("exported_variable"));
264     }
265 
266     GIVEN("we read the binary with only the variable whitelisted")
267     {
268       std::vector<std::string> whitelists;
269       whitelists.push_back(
270 	test_data_dir + "basic/one_function_one_variable_variable.whitelist");
271       const corpus_sptr& corpus =
272 	assert_symbol_count(binary, 0, 1, 0, 0, whitelists);
273       CHECK(!corpus->lookup_function_symbol("exported_function"));
274       CHECK(!corpus->lookup_variable_symbol("exported_function"));
275       CHECK(corpus->lookup_variable_symbol("exported_variable"));
276       CHECK(!corpus->lookup_function_symbol("exported_variable"));
277     }
278   }
279 }
280 
281 TEST_CASE("Symtab::AliasedFunctionSymbols", "[symtab, functions, aliases]")
282 {
283   const std::string  binary = "basic/aliases.so";
284   const corpus_sptr& corpus = assert_symbol_count(binary, 5, 5);
285 
286   // The main symbol is not necessarily the one that is aliased to in the
287   // code So, this can't be decided by just looking at ELF. Hence acquire the
288   // main symbol.
289   const elf_symbol_sptr& main_symbol =
290     corpus->lookup_function_symbol("exported_function")->get_main_symbol();
291   REQUIRE(main_symbol);
292 
293   // But since we know that 'exported_function' is the main symbol and this
294   // can be discovered from DWARF
295   CHECK(corpus->lookup_function_symbol("exported_function")->is_main_symbol());
296 
297   CHECK(corpus->lookup_function_symbol("exported_function")
298 	  ->get_number_of_aliases() == 4);
299 
300   CHECK(main_symbol->has_aliases());
301   CHECK(main_symbol->get_number_of_aliases() == 4);
302   CHECK(main_symbol->get_main_symbol() == main_symbol);
303 }
304 
305 TEST_CASE("Symtab::AliasedVariableSymbols", "[symtab, variables, aliases]")
306 {
307   const std::string  binary = "basic/aliases.so";
308   const corpus_sptr& corpus = assert_symbol_count(binary, 5, 5);
309   // The main symbol is not necessarily the one that is aliased to in the
310   // code So, this can't be decided by just looking at ELF. Hence acquire the
311   // main symbol.
312   const elf_symbol_sptr& main_symbol =
313     corpus->lookup_variable_symbol("exported_variable")->get_main_symbol();
314   REQUIRE(main_symbol);
315 
316   // But since we know that 'exported_function' is the main symbol and this
317   // can be discovered from DWARF
318   CHECK(corpus->lookup_variable_symbol("exported_variable")->is_main_symbol());
319 
320   CHECK(corpus->lookup_variable_symbol("exported_variable")
321 	  ->get_number_of_aliases() == 4);
322 
323   CHECK(main_symbol->has_aliases());
324   CHECK(main_symbol->get_number_of_aliases() == 4);
325   CHECK(main_symbol->get_main_symbol() == main_symbol);
326 }
327 
328 static const char* kernel_versions[] = { "4.14", "4.19", "5.4", "5.6" };
329 static const size_t nr_kernel_versions =
330     sizeof(kernel_versions) / sizeof(kernel_versions[0]);
331 
332 TEST_CASE("Symtab::SimpleKernelSymtabs", "[symtab, basic, kernel, ksymtab]")
333 {
334   for (size_t i = 0; i < nr_kernel_versions; ++i)
335     {
336       const std::string base_path =
337 	  "kernel-" + std::string(kernel_versions[i]) + "/";
338 
339       GIVEN("The binaries in " + base_path)
340       {
341 
342 	GIVEN("a kernel module with no exported symbols")
343 	{
344 	  // TODO: should pass, but does currently not as empty tables are
345 	  // treated
346 	  //       like the error case, but this is an edge case anyway.
347 	  // assert_symbol_count(base_path + "empty.so");
348 	}
349 
350 	GIVEN("a kernel module with a single exported function")
351 	{
352 	  const std::string	 binary = base_path + "single_function.ko";
353 	  const corpus_sptr&	 corpus = assert_symbol_count(binary, 1, 0);
354 	  const elf_symbol_sptr& symbol =
355 	      corpus->lookup_function_symbol("exported_function");
356 	  REQUIRE(symbol);
357 	  CHECK(!corpus->lookup_variable_symbol("exported_function"));
358 	  CHECK(symbol == corpus->lookup_function_symbol(*symbol));
359 	  CHECK(symbol != corpus->lookup_variable_symbol(*symbol));
360 	}
361 
362 	GIVEN("a kernel module with a single GPL exported function")
363 	{
364 	  const std::string	 binary = base_path + "single_function_gpl.ko";
365 	  const corpus_sptr&	 corpus = assert_symbol_count(binary, 1, 0);
366 	  const elf_symbol_sptr& symbol =
367 	      corpus->lookup_function_symbol("exported_function_gpl");
368 	  REQUIRE(symbol);
369 	  CHECK(!corpus->lookup_variable_symbol("exported_function_gpl"));
370 	  CHECK(symbol == corpus->lookup_function_symbol(*symbol));
371 	  CHECK(symbol != corpus->lookup_variable_symbol(*symbol));
372 	}
373 
374 	GIVEN("a binary with a single exported variable")
375 	{
376 	  const std::string	 binary = base_path + "single_variable.ko";
377 	  const corpus_sptr&	 corpus = assert_symbol_count(binary, 0, 1);
378 	  const elf_symbol_sptr& symbol =
379 	      corpus->lookup_variable_symbol("exported_variable");
380 	  REQUIRE(symbol);
381 	  CHECK(!corpus->lookup_function_symbol("exported_variable"));
382 	  CHECK(symbol == corpus->lookup_variable_symbol(*symbol));
383 	  CHECK(symbol != corpus->lookup_function_symbol(*symbol));
384 	}
385 
386 	GIVEN("a binary with a single GPL exported variable")
387 	{
388 	  const std::string	 binary = base_path + "single_variable_gpl.ko";
389 	  const corpus_sptr&	 corpus = assert_symbol_count(binary, 0, 1);
390 	  const elf_symbol_sptr& symbol =
391 	      corpus->lookup_variable_symbol("exported_variable_gpl");
392 	  REQUIRE(symbol);
393 	  CHECK(!corpus->lookup_function_symbol("exported_variable_gpl"));
394 	  CHECK(symbol == corpus->lookup_variable_symbol(*symbol));
395 	  CHECK(symbol != corpus->lookup_function_symbol(*symbol));
396 	}
397 
398 	GIVEN("a binary with one function and one variable (GPL) exported")
399 	{
400 	  const std::string  binary = base_path + "one_of_each.ko";
401 	  const corpus_sptr& corpus = assert_symbol_count(binary, 2, 2);
402 	  CHECK(corpus->lookup_function_symbol("exported_function"));
403 	  CHECK(!corpus->lookup_variable_symbol("exported_function"));
404 	  CHECK(corpus->lookup_function_symbol("exported_function_gpl"));
405 	  CHECK(!corpus->lookup_variable_symbol("exported_function_gpl"));
406 	  CHECK(corpus->lookup_variable_symbol("exported_variable"));
407 	  CHECK(!corpus->lookup_function_symbol("exported_variable"));
408 	  CHECK(corpus->lookup_variable_symbol("exported_variable_gpl"));
409 	  CHECK(!corpus->lookup_function_symbol("exported_variable_gpl"));
410 	}
411       }
412     }
413 }
414 
415 TEST_CASE("Symtab::KernelSymtabsWithCRC", "[symtab, crc, kernel, ksymtab]")
416 {
417   const std::string base_path = "kernel-modversions/";
418 
419   GIVEN("a binary with one function and one variable (GPL) exported")
420   {
421     const std::string  binary = base_path + "one_of_each.ko";
422     const corpus_sptr& corpus = assert_symbol_count(binary, 2, 2);
423     CHECK(corpus->lookup_function_symbol("exported_function")->get_crc() != 0);
424     CHECK(corpus->lookup_function_symbol("exported_function_gpl")->get_crc() != 0);
425     CHECK(corpus->lookup_variable_symbol("exported_variable")->get_crc() != 0);
426     CHECK(corpus->lookup_variable_symbol("exported_variable_gpl")->get_crc() != 0);
427   }
428 }
429