1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <linux/compiler.h> |
3 | #include <linux/rbtree.h> |
4 | #include <inttypes.h> |
5 | #include <string.h> |
6 | #include <ctype.h> |
7 | #include <stdlib.h> |
8 | #include "dso.h" |
9 | #include "map.h" |
10 | #include "symbol.h" |
11 | #include <internal/lib.h> // page_size |
12 | #include "tests.h" |
13 | #include "debug.h" |
14 | #include "machine.h" |
15 | |
16 | #define UM(x) map__unmap_ip(kallsyms_map, (x)) |
17 | |
18 | static bool is_ignored_symbol(const char *name, char type) |
19 | { |
20 | /* Symbol names that exactly match to the following are ignored.*/ |
21 | static const char * const ignored_symbols[] = { |
22 | /* |
23 | * Symbols which vary between passes. Passes 1 and 2 must have |
24 | * identical symbol lists. The kallsyms_* symbols below are |
25 | * only added after pass 1, they would be included in pass 2 |
26 | * when --all-symbols is specified so exclude them to get a |
27 | * stable symbol list. |
28 | */ |
29 | "kallsyms_addresses" , |
30 | "kallsyms_offsets" , |
31 | "kallsyms_relative_base" , |
32 | "kallsyms_num_syms" , |
33 | "kallsyms_names" , |
34 | "kallsyms_markers" , |
35 | "kallsyms_token_table" , |
36 | "kallsyms_token_index" , |
37 | /* Exclude linker generated symbols which vary between passes */ |
38 | "_SDA_BASE_" , /* ppc */ |
39 | "_SDA2_BASE_" , /* ppc */ |
40 | NULL |
41 | }; |
42 | |
43 | /* Symbol names that begin with the following are ignored.*/ |
44 | static const char * const ignored_prefixes[] = { |
45 | "$" , /* local symbols for ARM, MIPS, etc. */ |
46 | ".L" , /* local labels, .LBB,.Ltmpxxx,.L__unnamed_xx,.LASANPC, etc. */ |
47 | "__crc_" , /* modversions */ |
48 | "__efistub_" , /* arm64 EFI stub namespace */ |
49 | "__kvm_nvhe_$" , /* arm64 local symbols in non-VHE KVM namespace */ |
50 | "__kvm_nvhe_.L" , /* arm64 local symbols in non-VHE KVM namespace */ |
51 | "__AArch64ADRPThunk_" , /* arm64 lld */ |
52 | "__ARMV5PILongThunk_" , /* arm lld */ |
53 | "__ARMV7PILongThunk_" , |
54 | "__ThumbV7PILongThunk_" , |
55 | "__LA25Thunk_" , /* mips lld */ |
56 | "__microLA25Thunk_" , |
57 | NULL |
58 | }; |
59 | |
60 | /* Symbol names that end with the following are ignored.*/ |
61 | static const char * const ignored_suffixes[] = { |
62 | "_from_arm" , /* arm */ |
63 | "_from_thumb" , /* arm */ |
64 | "_veneer" , /* arm */ |
65 | NULL |
66 | }; |
67 | |
68 | /* Symbol names that contain the following are ignored.*/ |
69 | static const char * const ignored_matches[] = { |
70 | ".long_branch." , /* ppc stub */ |
71 | ".plt_branch." , /* ppc stub */ |
72 | NULL |
73 | }; |
74 | |
75 | const char * const *p; |
76 | |
77 | for (p = ignored_symbols; *p; p++) |
78 | if (!strcmp(name, *p)) |
79 | return true; |
80 | |
81 | for (p = ignored_prefixes; *p; p++) |
82 | if (!strncmp(name, *p, strlen(*p))) |
83 | return true; |
84 | |
85 | for (p = ignored_suffixes; *p; p++) { |
86 | int l = strlen(name) - strlen(*p); |
87 | |
88 | if (l >= 0 && !strcmp(name + l, *p)) |
89 | return true; |
90 | } |
91 | |
92 | for (p = ignored_matches; *p; p++) { |
93 | if (strstr(name, *p)) |
94 | return true; |
95 | } |
96 | |
97 | if (type == 'U' || type == 'u') |
98 | return true; |
99 | /* exclude debugging symbols */ |
100 | if (type == 'N' || type == 'n') |
101 | return true; |
102 | |
103 | if (toupper(type) == 'A') { |
104 | /* Keep these useful absolute symbols */ |
105 | if (strcmp(name, "__kernel_syscall_via_break" ) && |
106 | strcmp(name, "__kernel_syscall_via_epc" ) && |
107 | strcmp(name, "__kernel_sigtramp" ) && |
108 | strcmp(name, "__gp" )) |
109 | return true; |
110 | } |
111 | |
112 | return false; |
113 | } |
114 | |
115 | struct test__vmlinux_matches_kallsyms_cb_args { |
116 | struct machine kallsyms; |
117 | struct map *vmlinux_map; |
118 | bool ; |
119 | }; |
120 | |
121 | static int test__vmlinux_matches_kallsyms_cb1(struct map *map, void *data) |
122 | { |
123 | struct test__vmlinux_matches_kallsyms_cb_args *args = data; |
124 | struct dso *dso = map__dso(map); |
125 | /* |
126 | * If it is the kernel, kallsyms is always "[kernel.kallsyms]", while |
127 | * the kernel will have the path for the vmlinux file being used, so use |
128 | * the short name, less descriptive but the same ("[kernel]" in both |
129 | * cases. |
130 | */ |
131 | struct map *pair = maps__find_by_name(args->kallsyms.kmaps, |
132 | (dso->kernel ? dso->short_name : dso->name)); |
133 | |
134 | if (pair) { |
135 | map__set_priv(pair, 1); |
136 | map__put(pair); |
137 | } else { |
138 | if (!args->header_printed) { |
139 | pr_info("WARN: Maps only in vmlinux:\n" ); |
140 | args->header_printed = true; |
141 | } |
142 | map__fprintf(map, stderr); |
143 | } |
144 | return 0; |
145 | } |
146 | |
147 | static int test__vmlinux_matches_kallsyms_cb2(struct map *map, void *data) |
148 | { |
149 | struct test__vmlinux_matches_kallsyms_cb_args *args = data; |
150 | struct map *pair; |
151 | u64 mem_start = map__unmap_ip(args->vmlinux_map, map__start(map)); |
152 | u64 mem_end = map__unmap_ip(args->vmlinux_map, map__end(map)); |
153 | |
154 | pair = maps__find(args->kallsyms.kmaps, mem_start); |
155 | |
156 | if (pair != NULL && !map__priv(pair) && map__start(pair) == mem_start) { |
157 | struct dso *dso = map__dso(map); |
158 | |
159 | if (!args->header_printed) { |
160 | pr_info("WARN: Maps in vmlinux with a different name in kallsyms:\n" ); |
161 | args->header_printed = true; |
162 | } |
163 | |
164 | pr_info("WARN: %" PRIx64 "-%" PRIx64 " %" PRIx64 " %s in kallsyms as" , |
165 | map__start(map), map__end(map), map__pgoff(map), dso->name); |
166 | if (mem_end != map__end(pair)) |
167 | pr_info(":\nWARN: *%" PRIx64 "-%" PRIx64 " %" PRIx64, |
168 | map__start(pair), map__end(pair), map__pgoff(pair)); |
169 | pr_info(" %s\n" , dso->name); |
170 | map__set_priv(pair, 1); |
171 | } |
172 | map__put(pair); |
173 | return 0; |
174 | } |
175 | |
176 | static int test__vmlinux_matches_kallsyms_cb3(struct map *map, void *data) |
177 | { |
178 | struct test__vmlinux_matches_kallsyms_cb_args *args = data; |
179 | |
180 | if (!map__priv(map)) { |
181 | if (!args->header_printed) { |
182 | pr_info("WARN: Maps only in kallsyms:\n" ); |
183 | args->header_printed = true; |
184 | } |
185 | map__fprintf(map, stderr); |
186 | } |
187 | return 0; |
188 | } |
189 | |
190 | static int test__vmlinux_matches_kallsyms(struct test_suite *test __maybe_unused, |
191 | int subtest __maybe_unused) |
192 | { |
193 | int err = TEST_FAIL; |
194 | struct rb_node *nd; |
195 | struct symbol *sym; |
196 | struct map *kallsyms_map; |
197 | struct machine vmlinux; |
198 | struct maps *maps; |
199 | u64 mem_start, mem_end; |
200 | struct test__vmlinux_matches_kallsyms_cb_args args; |
201 | |
202 | /* |
203 | * Step 1: |
204 | * |
205 | * Init the machines that will hold kernel, modules obtained from |
206 | * both vmlinux + .ko files and from /proc/kallsyms split by modules. |
207 | */ |
208 | machine__init(&args.kallsyms, "" , HOST_KERNEL_ID); |
209 | machine__init(&vmlinux, "" , HOST_KERNEL_ID); |
210 | |
211 | maps = machine__kernel_maps(&vmlinux); |
212 | |
213 | /* |
214 | * Step 2: |
215 | * |
216 | * Create the kernel maps for kallsyms and the DSO where we will then |
217 | * load /proc/kallsyms. Also create the modules maps from /proc/modules |
218 | * and find the .ko files that match them in /lib/modules/`uname -r`/. |
219 | */ |
220 | if (machine__create_kernel_maps(&args.kallsyms) < 0) { |
221 | pr_debug("machine__create_kernel_maps failed" ); |
222 | err = TEST_SKIP; |
223 | goto out; |
224 | } |
225 | |
226 | /* |
227 | * Step 3: |
228 | * |
229 | * Load and split /proc/kallsyms into multiple maps, one per module. |
230 | * Do not use kcore, as this test was designed before kcore support |
231 | * and has parts that only make sense if using the non-kcore code. |
232 | * XXX: extend it to stress the kcorre code as well, hint: the list |
233 | * of modules extracted from /proc/kcore, in its current form, can't |
234 | * be compacted against the list of modules found in the "vmlinux" |
235 | * code and with the one got from /proc/modules from the "kallsyms" code. |
236 | */ |
237 | if (machine__load_kallsyms(&args.kallsyms, "/proc/kallsyms" ) <= 0) { |
238 | pr_debug("machine__load_kallsyms failed" ); |
239 | err = TEST_SKIP; |
240 | goto out; |
241 | } |
242 | |
243 | /* |
244 | * Step 4: |
245 | * |
246 | * kallsyms will be internally on demand sorted by name so that we can |
247 | * find the reference relocation * symbol, i.e. the symbol we will use |
248 | * to see if the running kernel was relocated by checking if it has the |
249 | * same value in the vmlinux file we load. |
250 | */ |
251 | kallsyms_map = machine__kernel_map(&args.kallsyms); |
252 | |
253 | /* |
254 | * Step 5: |
255 | * |
256 | * Now repeat step 2, this time for the vmlinux file we'll auto-locate. |
257 | */ |
258 | if (machine__create_kernel_maps(&vmlinux) < 0) { |
259 | pr_info("machine__create_kernel_maps failed" ); |
260 | goto out; |
261 | } |
262 | |
263 | args.vmlinux_map = machine__kernel_map(&vmlinux); |
264 | |
265 | /* |
266 | * Step 6: |
267 | * |
268 | * Locate a vmlinux file in the vmlinux path that has a buildid that |
269 | * matches the one of the running kernel. |
270 | * |
271 | * While doing that look if we find the ref reloc symbol, if we find it |
272 | * we'll have its ref_reloc_symbol.unrelocated_addr and then |
273 | * maps__reloc_vmlinux will notice and set proper ->[un]map_ip routines |
274 | * to fixup the symbols. |
275 | */ |
276 | if (machine__load_vmlinux_path(&vmlinux) <= 0) { |
277 | pr_info("Couldn't find a vmlinux that matches the kernel running on this machine, skipping test\n" ); |
278 | err = TEST_SKIP; |
279 | goto out; |
280 | } |
281 | |
282 | err = 0; |
283 | /* |
284 | * Step 7: |
285 | * |
286 | * Now look at the symbols in the vmlinux DSO and check if we find all of them |
287 | * in the kallsyms dso. For the ones that are in both, check its names and |
288 | * end addresses too. |
289 | */ |
290 | map__for_each_symbol(args.vmlinux_map, sym, nd) { |
291 | struct symbol *pair, *first_pair; |
292 | |
293 | sym = rb_entry(nd, struct symbol, rb_node); |
294 | |
295 | if (sym->start == sym->end) |
296 | continue; |
297 | |
298 | mem_start = map__unmap_ip(args.vmlinux_map, sym->start); |
299 | mem_end = map__unmap_ip(args.vmlinux_map, sym->end); |
300 | |
301 | first_pair = machine__find_kernel_symbol(&args.kallsyms, mem_start, NULL); |
302 | pair = first_pair; |
303 | |
304 | if (pair && UM(pair->start) == mem_start) { |
305 | next_pair: |
306 | if (arch__compare_symbol_names(sym->name, pair->name) == 0) { |
307 | /* |
308 | * kallsyms don't have the symbol end, so we |
309 | * set that by using the next symbol start - 1, |
310 | * in some cases we get this up to a page |
311 | * wrong, trace_kmalloc when I was developing |
312 | * this code was one such example, 2106 bytes |
313 | * off the real size. More than that and we |
314 | * _really_ have a problem. |
315 | */ |
316 | s64 skew = mem_end - UM(pair->end); |
317 | if (llabs(skew) >= page_size) |
318 | pr_debug("WARN: %#" PRIx64 ": diff end addr for %s v: %#" PRIx64 " k: %#" PRIx64 "\n" , |
319 | mem_start, sym->name, mem_end, |
320 | UM(pair->end)); |
321 | |
322 | /* |
323 | * Do not count this as a failure, because we |
324 | * could really find a case where it's not |
325 | * possible to get proper function end from |
326 | * kallsyms. |
327 | */ |
328 | continue; |
329 | } else { |
330 | pair = machine__find_kernel_symbol_by_name(&args.kallsyms, |
331 | sym->name, NULL); |
332 | if (pair) { |
333 | if (UM(pair->start) == mem_start) |
334 | goto next_pair; |
335 | |
336 | pr_debug("WARN: %#" PRIx64 ": diff name v: %s k: %s\n" , |
337 | mem_start, sym->name, pair->name); |
338 | } else { |
339 | pr_debug("WARN: %#" PRIx64 ": diff name v: %s k: %s\n" , |
340 | mem_start, sym->name, first_pair->name); |
341 | } |
342 | |
343 | continue; |
344 | } |
345 | } else if (mem_start == map__end(args.kallsyms.vmlinux_map)) { |
346 | /* |
347 | * Ignore aliases to _etext, i.e. to the end of the kernel text area, |
348 | * such as __indirect_thunk_end. |
349 | */ |
350 | continue; |
351 | } else if (is_ignored_symbol(name: sym->name, type: sym->type)) { |
352 | /* |
353 | * Ignore hidden symbols, see scripts/kallsyms.c for the details |
354 | */ |
355 | continue; |
356 | } else { |
357 | pr_debug("ERR : %#" PRIx64 ": %s not on kallsyms\n" , |
358 | mem_start, sym->name); |
359 | } |
360 | |
361 | err = -1; |
362 | } |
363 | |
364 | if (verbose <= 0) |
365 | goto out; |
366 | |
367 | args.header_printed = false; |
368 | maps__for_each_map(maps, test__vmlinux_matches_kallsyms_cb1, &args); |
369 | |
370 | args.header_printed = false; |
371 | maps__for_each_map(maps, test__vmlinux_matches_kallsyms_cb2, &args); |
372 | |
373 | args.header_printed = false; |
374 | maps = machine__kernel_maps(&args.kallsyms); |
375 | maps__for_each_map(maps, test__vmlinux_matches_kallsyms_cb3, &args); |
376 | |
377 | out: |
378 | machine__exit(&args.kallsyms); |
379 | machine__exit(&vmlinux); |
380 | return err; |
381 | } |
382 | |
383 | DEFINE_SUITE("vmlinux symtab matches kallsyms" , vmlinux_matches_kallsyms); |
384 | |