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