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