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 | ||
d854861c ACM |
237 | #include "util/evsel.h" |
238 | #include <sys/types.h> | |
239 | ||
240 | static int trace_event__id(const char *event_name) | |
241 | { | |
242 | char *filename; | |
243 | int err = -1, fd; | |
244 | ||
245 | if (asprintf(&filename, | |
246 | "/sys/kernel/debug/tracing/events/syscalls/%s/id", | |
247 | event_name) < 0) | |
248 | return -1; | |
249 | ||
250 | fd = open(filename, O_RDONLY); | |
251 | if (fd >= 0) { | |
252 | char id[16]; | |
253 | if (read(fd, id, sizeof(id)) > 0) | |
254 | err = atoi(id); | |
255 | close(fd); | |
256 | } | |
257 | ||
258 | free(filename); | |
259 | return err; | |
260 | } | |
261 | ||
262 | static int test__open_syscall_event(void) | |
263 | { | |
264 | int err = -1, fd; | |
265 | struct thread_map *threads; | |
266 | struct perf_evsel *evsel; | |
23a2f3ab | 267 | struct perf_event_attr attr; |
d854861c ACM |
268 | unsigned int nr_open_calls = 111, i; |
269 | int id = trace_event__id("sys_enter_open"); | |
270 | ||
271 | if (id < 0) { | |
454a3bbe | 272 | pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); |
d854861c ACM |
273 | return -1; |
274 | } | |
275 | ||
276 | threads = thread_map__new(-1, getpid()); | |
277 | if (threads == NULL) { | |
454a3bbe | 278 | pr_debug("thread_map__new\n"); |
d854861c ACM |
279 | return -1; |
280 | } | |
281 | ||
23a2f3ab LM |
282 | memset(&attr, 0, sizeof(attr)); |
283 | attr.type = PERF_TYPE_TRACEPOINT; | |
284 | attr.config = id; | |
285 | evsel = perf_evsel__new(&attr, 0); | |
d854861c | 286 | if (evsel == NULL) { |
454a3bbe | 287 | pr_debug("perf_evsel__new\n"); |
d854861c ACM |
288 | goto out_thread_map_delete; |
289 | } | |
290 | ||
291 | if (perf_evsel__open_per_thread(evsel, threads) < 0) { | |
454a3bbe ACM |
292 | pr_debug("failed to open counter: %s, " |
293 | "tweak /proc/sys/kernel/perf_event_paranoid?\n", | |
294 | strerror(errno)); | |
d854861c ACM |
295 | goto out_evsel_delete; |
296 | } | |
297 | ||
298 | for (i = 0; i < nr_open_calls; ++i) { | |
299 | fd = open("/etc/passwd", O_RDONLY); | |
300 | close(fd); | |
301 | } | |
302 | ||
303 | if (perf_evsel__read_on_cpu(evsel, 0, 0) < 0) { | |
454a3bbe | 304 | pr_debug("perf_evsel__open_read_on_cpu\n"); |
d854861c ACM |
305 | goto out_close_fd; |
306 | } | |
307 | ||
454a3bbe ACM |
308 | if (evsel->counts->cpu[0].val != nr_open_calls) { |
309 | pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls, got %Ld\n", | |
d854861c | 310 | nr_open_calls, evsel->counts->cpu[0].val); |
454a3bbe ACM |
311 | goto out_close_fd; |
312 | } | |
d854861c ACM |
313 | |
314 | err = 0; | |
315 | out_close_fd: | |
316 | perf_evsel__close_fd(evsel, 1, threads->nr); | |
317 | out_evsel_delete: | |
318 | perf_evsel__delete(evsel); | |
319 | out_thread_map_delete: | |
320 | thread_map__delete(threads); | |
321 | return err; | |
322 | } | |
323 | ||
1c6a800c ACM |
324 | static struct test { |
325 | const char *desc; | |
326 | int (*func)(void); | |
327 | } tests[] = { | |
328 | { | |
329 | .desc = "vmlinux symtab matches kallsyms", | |
330 | .func = test__vmlinux_matches_kallsyms, | |
331 | }, | |
d854861c ACM |
332 | { |
333 | .desc = "detect open syscall event", | |
334 | .func = test__open_syscall_event, | |
335 | }, | |
1c6a800c ACM |
336 | { |
337 | .func = NULL, | |
338 | }, | |
339 | }; | |
340 | ||
341 | static int __cmd_test(void) | |
342 | { | |
343 | int i = 0; | |
344 | ||
345 | page_size = sysconf(_SC_PAGE_SIZE); | |
346 | ||
347 | while (tests[i].func) { | |
348 | int err; | |
349 | pr_info("%2d: %s:", i + 1, tests[i].desc); | |
350 | pr_debug("\n--- start ---\n"); | |
351 | err = tests[i].func(); | |
352 | pr_debug("---- end ----\n%s:", tests[i].desc); | |
353 | pr_info(" %s\n", err ? "FAILED!\n" : "Ok"); | |
354 | ++i; | |
355 | } | |
356 | ||
357 | return 0; | |
358 | } | |
359 | ||
360 | static const char * const test_usage[] = { | |
361 | "perf test [<options>]", | |
362 | NULL, | |
363 | }; | |
364 | ||
365 | static const struct option test_options[] = { | |
8035458f | 366 | OPT_INTEGER('v', "verbose", &verbose, |
1c6a800c ACM |
367 | "be more verbose (show symbol address, etc)"), |
368 | OPT_END() | |
369 | }; | |
370 | ||
371 | int cmd_test(int argc, const char **argv, const char *prefix __used) | |
372 | { | |
373 | argc = parse_options(argc, argv, test_options, test_usage, 0); | |
374 | if (argc) | |
375 | usage_with_options(test_usage, test_options); | |
376 | ||
377 | symbol_conf.priv_size = sizeof(int); | |
378 | symbol_conf.sort_by_name = true; | |
379 | symbol_conf.try_vmlinux_path = true; | |
380 | ||
381 | if (symbol__init() < 0) | |
382 | return -1; | |
383 | ||
384 | setup_pager(); | |
385 | ||
386 | return __cmd_test(); | |
387 | } |