Commit | Line | Data |
---|---|---|
1c6a800c ACM |
1 | /* |
2 | * builtin-test.c | |
3 | * | |
4 | * Builtin regression testing command: ever growing number of sanity tests | |
5 | */ | |
6 | #include "builtin.h" | |
7 | ||
8 | #include "util/cache.h" | |
9 | #include "util/debug.h" | |
10 | #include "util/parse-options.h" | |
11 | #include "util/session.h" | |
12 | #include "util/symbol.h" | |
13 | #include "util/thread.h" | |
14 | ||
15 | static long page_size; | |
16 | ||
17 | static int vmlinux_matches_kallsyms_filter(struct map *map __used, struct symbol *sym) | |
18 | { | |
19 | bool *visited = symbol__priv(sym); | |
20 | *visited = true; | |
21 | return 0; | |
22 | } | |
23 | ||
24 | static int test__vmlinux_matches_kallsyms(void) | |
25 | { | |
26 | int err = -1; | |
27 | struct rb_node *nd; | |
28 | struct symbol *sym; | |
29 | struct map *kallsyms_map, *vmlinux_map; | |
30 | struct machine kallsyms, vmlinux; | |
31 | enum map_type type = MAP__FUNCTION; | |
32 | struct ref_reloc_sym ref_reloc_sym = { .name = "_stext", }; | |
33 | ||
34 | /* | |
35 | * Step 1: | |
36 | * | |
37 | * Init the machines that will hold kernel, modules obtained from | |
38 | * both vmlinux + .ko files and from /proc/kallsyms split by modules. | |
39 | */ | |
40 | machine__init(&kallsyms, "", HOST_KERNEL_ID); | |
41 | machine__init(&vmlinux, "", HOST_KERNEL_ID); | |
42 | ||
43 | /* | |
44 | * Step 2: | |
45 | * | |
46 | * Create the kernel maps for kallsyms and the DSO where we will then | |
47 | * load /proc/kallsyms. Also create the modules maps from /proc/modules | |
48 | * and find the .ko files that match them in /lib/modules/`uname -r`/. | |
49 | */ | |
50 | if (machine__create_kernel_maps(&kallsyms) < 0) { | |
51 | pr_debug("machine__create_kernel_maps "); | |
52 | return -1; | |
53 | } | |
54 | ||
55 | /* | |
56 | * Step 3: | |
57 | * | |
58 | * Load and split /proc/kallsyms into multiple maps, one per module. | |
59 | */ | |
60 | if (machine__load_kallsyms(&kallsyms, "/proc/kallsyms", type, NULL) <= 0) { | |
61 | pr_debug("dso__load_kallsyms "); | |
62 | goto out; | |
63 | } | |
64 | ||
65 | /* | |
66 | * Step 4: | |
67 | * | |
68 | * kallsyms will be internally on demand sorted by name so that we can | |
69 | * find the reference relocation * symbol, i.e. the symbol we will use | |
70 | * to see if the running kernel was relocated by checking if it has the | |
71 | * same value in the vmlinux file we load. | |
72 | */ | |
73 | kallsyms_map = machine__kernel_map(&kallsyms, type); | |
74 | ||
75 | sym = map__find_symbol_by_name(kallsyms_map, ref_reloc_sym.name, NULL); | |
76 | if (sym == NULL) { | |
77 | pr_debug("dso__find_symbol_by_name "); | |
78 | goto out; | |
79 | } | |
80 | ||
81 | ref_reloc_sym.addr = sym->start; | |
82 | ||
83 | /* | |
84 | * Step 5: | |
85 | * | |
86 | * Now repeat step 2, this time for the vmlinux file we'll auto-locate. | |
87 | */ | |
88 | if (machine__create_kernel_maps(&vmlinux) < 0) { | |
89 | pr_debug("machine__create_kernel_maps "); | |
90 | goto out; | |
91 | } | |
92 | ||
93 | vmlinux_map = machine__kernel_map(&vmlinux, type); | |
94 | map__kmap(vmlinux_map)->ref_reloc_sym = &ref_reloc_sym; | |
95 | ||
96 | /* | |
97 | * Step 6: | |
98 | * | |
99 | * Locate a vmlinux file in the vmlinux path that has a buildid that | |
100 | * matches the one of the running kernel. | |
101 | * | |
102 | * While doing that look if we find the ref reloc symbol, if we find it | |
103 | * we'll have its ref_reloc_symbol.unrelocated_addr and then | |
104 | * maps__reloc_vmlinux will notice and set proper ->[un]map_ip routines | |
105 | * to fixup the symbols. | |
106 | */ | |
107 | if (machine__load_vmlinux_path(&vmlinux, type, | |
108 | vmlinux_matches_kallsyms_filter) <= 0) { | |
109 | pr_debug("machine__load_vmlinux_path "); | |
110 | goto out; | |
111 | } | |
112 | ||
113 | err = 0; | |
114 | /* | |
115 | * Step 7: | |
116 | * | |
117 | * Now look at the symbols in the vmlinux DSO and check if we find all of them | |
118 | * in the kallsyms dso. For the ones that are in both, check its names and | |
119 | * end addresses too. | |
120 | */ | |
121 | for (nd = rb_first(&vmlinux_map->dso->symbols[type]); nd; nd = rb_next(nd)) { | |
d3678758 ACM |
122 | struct symbol *pair, *first_pair; |
123 | bool backwards = true; | |
1c6a800c ACM |
124 | |
125 | sym = rb_entry(nd, struct symbol, rb_node); | |
d3678758 ACM |
126 | |
127 | if (sym->start == sym->end) | |
128 | continue; | |
129 | ||
130 | first_pair = machine__find_kernel_symbol(&kallsyms, type, sym->start, NULL, NULL); | |
131 | pair = first_pair; | |
1c6a800c ACM |
132 | |
133 | if (pair && pair->start == sym->start) { | |
134 | next_pair: | |
135 | if (strcmp(sym->name, pair->name) == 0) { | |
136 | /* | |
137 | * kallsyms don't have the symbol end, so we | |
138 | * set that by using the next symbol start - 1, | |
139 | * in some cases we get this up to a page | |
140 | * wrong, trace_kmalloc when I was developing | |
141 | * this code was one such example, 2106 bytes | |
142 | * off the real size. More than that and we | |
143 | * _really_ have a problem. | |
144 | */ | |
145 | s64 skew = sym->end - pair->end; | |
146 | if (llabs(skew) < page_size) | |
147 | continue; | |
148 | ||
149 | pr_debug("%#Lx: diff end addr for %s v: %#Lx k: %#Lx\n", | |
150 | sym->start, sym->name, sym->end, pair->end); | |
151 | } else { | |
d3678758 ACM |
152 | struct rb_node *nnd; |
153 | detour: | |
154 | nnd = backwards ? rb_prev(&pair->rb_node) : | |
155 | rb_next(&pair->rb_node); | |
1c6a800c ACM |
156 | if (nnd) { |
157 | struct symbol *next = rb_entry(nnd, struct symbol, rb_node); | |
158 | ||
159 | if (next->start == sym->start) { | |
160 | pair = next; | |
161 | goto next_pair; | |
162 | } | |
163 | } | |
d3678758 ACM |
164 | |
165 | if (backwards) { | |
166 | backwards = false; | |
167 | pair = first_pair; | |
168 | goto detour; | |
169 | } | |
170 | ||
1c6a800c ACM |
171 | pr_debug("%#Lx: diff name v: %s k: %s\n", |
172 | sym->start, sym->name, pair->name); | |
173 | } | |
174 | } else | |
175 | pr_debug("%#Lx: %s not on kallsyms\n", sym->start, sym->name); | |
176 | ||
177 | err = -1; | |
178 | } | |
179 | ||
180 | if (!verbose) | |
181 | goto out; | |
182 | ||
183 | pr_info("Maps only in vmlinux:\n"); | |
184 | ||
185 | for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { | |
186 | struct map *pos = rb_entry(nd, struct map, rb_node), *pair; | |
187 | /* | |
188 | * If it is the kernel, kallsyms is always "[kernel.kallsyms]", while | |
189 | * the kernel will have the path for the vmlinux file being used, | |
190 | * so use the short name, less descriptive but the same ("[kernel]" in | |
191 | * both cases. | |
192 | */ | |
193 | pair = map_groups__find_by_name(&kallsyms.kmaps, type, | |
194 | (pos->dso->kernel ? | |
195 | pos->dso->short_name : | |
196 | pos->dso->name)); | |
197 | if (pair) | |
198 | pair->priv = 1; | |
199 | else | |
200 | map__fprintf(pos, stderr); | |
201 | } | |
202 | ||
203 | pr_info("Maps in vmlinux with a different name in kallsyms:\n"); | |
204 | ||
205 | for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { | |
206 | struct map *pos = rb_entry(nd, struct map, rb_node), *pair; | |
207 | ||
208 | pair = map_groups__find(&kallsyms.kmaps, type, pos->start); | |
209 | if (pair == NULL || pair->priv) | |
210 | continue; | |
211 | ||
212 | if (pair->start == pos->start) { | |
213 | pair->priv = 1; | |
214 | pr_info(" %Lx-%Lx %Lx %s in kallsyms as", | |
215 | pos->start, pos->end, pos->pgoff, pos->dso->name); | |
216 | if (pos->pgoff != pair->pgoff || pos->end != pair->end) | |
217 | pr_info(": \n*%Lx-%Lx %Lx", | |
218 | pair->start, pair->end, pair->pgoff); | |
219 | pr_info(" %s\n", pair->dso->name); | |
220 | pair->priv = 1; | |
221 | } | |
222 | } | |
223 | ||
224 | pr_info("Maps only in kallsyms:\n"); | |
225 | ||
226 | for (nd = rb_first(&kallsyms.kmaps.maps[type]); | |
227 | nd; nd = rb_next(nd)) { | |
228 | struct map *pos = rb_entry(nd, struct map, rb_node); | |
229 | ||
230 | if (!pos->priv) | |
231 | map__fprintf(pos, stderr); | |
232 | } | |
233 | out: | |
234 | return err; | |
235 | } | |
236 | ||
0252208e | 237 | #include "util/cpumap.h" |
d854861c ACM |
238 | #include "util/evsel.h" |
239 | #include <sys/types.h> | |
240 | ||
241 | static int trace_event__id(const char *event_name) | |
242 | { | |
243 | char *filename; | |
244 | int err = -1, fd; | |
245 | ||
246 | if (asprintf(&filename, | |
247 | "/sys/kernel/debug/tracing/events/syscalls/%s/id", | |
248 | event_name) < 0) | |
249 | return -1; | |
250 | ||
251 | fd = open(filename, O_RDONLY); | |
252 | if (fd >= 0) { | |
253 | char id[16]; | |
254 | if (read(fd, id, sizeof(id)) > 0) | |
255 | err = atoi(id); | |
256 | close(fd); | |
257 | } | |
258 | ||
259 | free(filename); | |
260 | return err; | |
261 | } | |
262 | ||
263 | static int test__open_syscall_event(void) | |
264 | { | |
265 | int err = -1, fd; | |
266 | struct thread_map *threads; | |
267 | struct perf_evsel *evsel; | |
23a2f3ab | 268 | struct perf_event_attr attr; |
d854861c ACM |
269 | unsigned int nr_open_calls = 111, i; |
270 | int id = trace_event__id("sys_enter_open"); | |
271 | ||
272 | if (id < 0) { | |
454a3bbe | 273 | pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); |
d854861c ACM |
274 | return -1; |
275 | } | |
276 | ||
277 | threads = thread_map__new(-1, getpid()); | |
278 | if (threads == NULL) { | |
454a3bbe | 279 | pr_debug("thread_map__new\n"); |
d854861c ACM |
280 | return -1; |
281 | } | |
282 | ||
23a2f3ab LM |
283 | memset(&attr, 0, sizeof(attr)); |
284 | attr.type = PERF_TYPE_TRACEPOINT; | |
285 | attr.config = id; | |
286 | evsel = perf_evsel__new(&attr, 0); | |
d854861c | 287 | if (evsel == NULL) { |
454a3bbe | 288 | pr_debug("perf_evsel__new\n"); |
d854861c ACM |
289 | goto out_thread_map_delete; |
290 | } | |
291 | ||
292 | if (perf_evsel__open_per_thread(evsel, threads) < 0) { | |
454a3bbe ACM |
293 | pr_debug("failed to open counter: %s, " |
294 | "tweak /proc/sys/kernel/perf_event_paranoid?\n", | |
295 | strerror(errno)); | |
d854861c ACM |
296 | goto out_evsel_delete; |
297 | } | |
298 | ||
299 | for (i = 0; i < nr_open_calls; ++i) { | |
300 | fd = open("/etc/passwd", O_RDONLY); | |
301 | close(fd); | |
302 | } | |
303 | ||
304 | if (perf_evsel__read_on_cpu(evsel, 0, 0) < 0) { | |
454a3bbe | 305 | pr_debug("perf_evsel__open_read_on_cpu\n"); |
d854861c ACM |
306 | goto out_close_fd; |
307 | } | |
308 | ||
454a3bbe ACM |
309 | if (evsel->counts->cpu[0].val != nr_open_calls) { |
310 | pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls, got %Ld\n", | |
d854861c | 311 | nr_open_calls, evsel->counts->cpu[0].val); |
454a3bbe ACM |
312 | goto out_close_fd; |
313 | } | |
d854861c ACM |
314 | |
315 | err = 0; | |
316 | out_close_fd: | |
317 | perf_evsel__close_fd(evsel, 1, threads->nr); | |
318 | out_evsel_delete: | |
319 | perf_evsel__delete(evsel); | |
320 | out_thread_map_delete: | |
321 | thread_map__delete(threads); | |
322 | return err; | |
323 | } | |
324 | ||
0252208e ACM |
325 | #include <sched.h> |
326 | ||
327 | static int test__open_syscall_event_on_all_cpus(void) | |
328 | { | |
329 | int err = -1, fd, cpu; | |
330 | struct thread_map *threads; | |
331 | struct cpu_map *cpus; | |
332 | struct perf_evsel *evsel; | |
333 | struct perf_event_attr attr; | |
334 | unsigned int nr_open_calls = 111, i; | |
335 | cpu_set_t *cpu_set; | |
336 | size_t cpu_set_size; | |
337 | int id = trace_event__id("sys_enter_open"); | |
338 | ||
339 | if (id < 0) { | |
340 | pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); | |
341 | return -1; | |
342 | } | |
343 | ||
344 | threads = thread_map__new(-1, getpid()); | |
345 | if (threads == NULL) { | |
346 | pr_debug("thread_map__new\n"); | |
347 | return -1; | |
348 | } | |
349 | ||
350 | cpus = cpu_map__new(NULL); | |
351 | if (threads == NULL) { | |
352 | pr_debug("thread_map__new\n"); | |
353 | return -1; | |
354 | } | |
355 | ||
356 | cpu_set = CPU_ALLOC(cpus->nr); | |
357 | ||
358 | if (cpu_set == NULL) | |
359 | goto out_thread_map_delete; | |
360 | ||
361 | cpu_set_size = CPU_ALLOC_SIZE(cpus->nr); | |
362 | CPU_ZERO_S(cpu_set_size, cpu_set); | |
363 | ||
364 | memset(&attr, 0, sizeof(attr)); | |
365 | attr.type = PERF_TYPE_TRACEPOINT; | |
366 | attr.config = id; | |
367 | evsel = perf_evsel__new(&attr, 0); | |
368 | if (evsel == NULL) { | |
369 | pr_debug("perf_evsel__new\n"); | |
370 | goto out_cpu_free; | |
371 | } | |
372 | ||
373 | if (perf_evsel__open(evsel, cpus, threads) < 0) { | |
374 | pr_debug("failed to open counter: %s, " | |
375 | "tweak /proc/sys/kernel/perf_event_paranoid?\n", | |
376 | strerror(errno)); | |
377 | goto out_evsel_delete; | |
378 | } | |
379 | ||
380 | for (cpu = 0; cpu < cpus->nr; ++cpu) { | |
381 | unsigned int ncalls = nr_open_calls + cpu; | |
382 | ||
383 | CPU_SET(cpu, cpu_set); | |
384 | sched_setaffinity(0, cpu_set_size, cpu_set); | |
385 | for (i = 0; i < ncalls; ++i) { | |
386 | fd = open("/etc/passwd", O_RDONLY); | |
387 | close(fd); | |
388 | } | |
389 | CPU_CLR(cpu, cpu_set); | |
390 | } | |
391 | ||
392 | /* | |
393 | * Here we need to explicitely preallocate the counts, as if | |
394 | * we use the auto allocation it will allocate just for 1 cpu, | |
395 | * as we start by cpu 0. | |
396 | */ | |
397 | if (perf_evsel__alloc_counts(evsel, cpus->nr) < 0) { | |
398 | pr_debug("perf_evsel__alloc_counts(ncpus=%d)\n", cpus->nr); | |
399 | goto out_close_fd; | |
400 | } | |
401 | ||
402 | for (cpu = 0; cpu < cpus->nr; ++cpu) { | |
403 | unsigned int expected; | |
404 | ||
405 | if (perf_evsel__read_on_cpu(evsel, cpu, 0) < 0) { | |
406 | pr_debug("perf_evsel__open_read_on_cpu\n"); | |
407 | goto out_close_fd; | |
408 | } | |
409 | ||
410 | expected = nr_open_calls + cpu; | |
411 | if (evsel->counts->cpu[cpu].val != expected) { | |
412 | pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls on cpu %d, got %Ld\n", | |
413 | expected, cpu, evsel->counts->cpu[cpu].val); | |
414 | goto out_close_fd; | |
415 | } | |
416 | } | |
417 | ||
418 | err = 0; | |
419 | out_close_fd: | |
420 | perf_evsel__close_fd(evsel, 1, threads->nr); | |
421 | out_evsel_delete: | |
422 | perf_evsel__delete(evsel); | |
423 | out_cpu_free: | |
424 | CPU_FREE(cpu_set); | |
425 | out_thread_map_delete: | |
426 | thread_map__delete(threads); | |
427 | return err; | |
428 | } | |
429 | ||
1c6a800c ACM |
430 | static struct test { |
431 | const char *desc; | |
432 | int (*func)(void); | |
433 | } tests[] = { | |
434 | { | |
435 | .desc = "vmlinux symtab matches kallsyms", | |
436 | .func = test__vmlinux_matches_kallsyms, | |
437 | }, | |
d854861c ACM |
438 | { |
439 | .desc = "detect open syscall event", | |
440 | .func = test__open_syscall_event, | |
441 | }, | |
0252208e ACM |
442 | { |
443 | .desc = "detect open syscall event on all cpus", | |
444 | .func = test__open_syscall_event_on_all_cpus, | |
445 | }, | |
1c6a800c ACM |
446 | { |
447 | .func = NULL, | |
448 | }, | |
449 | }; | |
450 | ||
451 | static int __cmd_test(void) | |
452 | { | |
453 | int i = 0; | |
454 | ||
455 | page_size = sysconf(_SC_PAGE_SIZE); | |
456 | ||
457 | while (tests[i].func) { | |
458 | int err; | |
459 | pr_info("%2d: %s:", i + 1, tests[i].desc); | |
460 | pr_debug("\n--- start ---\n"); | |
461 | err = tests[i].func(); | |
462 | pr_debug("---- end ----\n%s:", tests[i].desc); | |
463 | pr_info(" %s\n", err ? "FAILED!\n" : "Ok"); | |
464 | ++i; | |
465 | } | |
466 | ||
467 | return 0; | |
468 | } | |
469 | ||
470 | static const char * const test_usage[] = { | |
471 | "perf test [<options>]", | |
472 | NULL, | |
473 | }; | |
474 | ||
475 | static const struct option test_options[] = { | |
8035458f | 476 | OPT_INTEGER('v', "verbose", &verbose, |
1c6a800c ACM |
477 | "be more verbose (show symbol address, etc)"), |
478 | OPT_END() | |
479 | }; | |
480 | ||
481 | int cmd_test(int argc, const char **argv, const char *prefix __used) | |
482 | { | |
483 | argc = parse_options(argc, argv, test_options, test_usage, 0); | |
484 | if (argc) | |
485 | usage_with_options(test_usage, test_options); | |
486 | ||
487 | symbol_conf.priv_size = sizeof(int); | |
488 | symbol_conf.sort_by_name = true; | |
489 | symbol_conf.try_vmlinux_path = true; | |
490 | ||
491 | if (symbol__init() < 0) | |
492 | return -1; | |
493 | ||
494 | setup_pager(); | |
495 | ||
496 | return __cmd_test(); | |
497 | } |