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 elf_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 elf_reader::status status = elf_reader::STATUS_UNKNOWN;
62 result = read_corpus_from_elf(*ctxt, status);
63
64 REQUIRE(status != elf_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 elf_reader::status status = read_corpus(binary, corpus_ptr);
73 REQUIRE(!corpus_ptr);
74
75 REQUIRE((status & elf_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 elf_reader::status status = read_corpus(binary, corpus_ptr);
83 REQUIRE(corpus_ptr);
84
85 REQUIRE(status
86 == (elf_reader::STATUS_OK
87 | elf_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 elf_reader::status status =
106 read_corpus(path, corpus_ptr, whitelist_paths);
107 REQUIRE(corpus_ptr);
108
109 REQUIRE((status & elf_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 elf_reader::status status =
248 read_corpus(binary, corpus_ptr, whitelists);
249 REQUIRE(!corpus_ptr);
250 REQUIRE((status & elf_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());
424 CHECK(corpus->lookup_function_symbol("exported_function_gpl")->get_crc());
425 CHECK(corpus->lookup_variable_symbol("exported_variable")->get_crc());
426 CHECK(corpus->lookup_variable_symbol("exported_variable_gpl")->get_crc());
427 }
428 }
429