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