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