]>
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)) { | |
122 | struct symbol *pair; | |
123 | ||
124 | sym = rb_entry(nd, struct symbol, rb_node); | |
125 | pair = machine__find_kernel_symbol(&kallsyms, type, sym->start, NULL, NULL); | |
126 | ||
127 | if (pair && pair->start == sym->start) { | |
128 | next_pair: | |
129 | if (strcmp(sym->name, pair->name) == 0) { | |
130 | /* | |
131 | * kallsyms don't have the symbol end, so we | |
132 | * set that by using the next symbol start - 1, | |
133 | * in some cases we get this up to a page | |
134 | * wrong, trace_kmalloc when I was developing | |
135 | * this code was one such example, 2106 bytes | |
136 | * off the real size. More than that and we | |
137 | * _really_ have a problem. | |
138 | */ | |
139 | s64 skew = sym->end - pair->end; | |
140 | if (llabs(skew) < page_size) | |
141 | continue; | |
142 | ||
143 | pr_debug("%#Lx: diff end addr for %s v: %#Lx k: %#Lx\n", | |
144 | sym->start, sym->name, sym->end, pair->end); | |
145 | } else { | |
146 | struct rb_node *nnd = rb_prev(&pair->rb_node); | |
147 | ||
148 | if (nnd) { | |
149 | struct symbol *next = rb_entry(nnd, struct symbol, rb_node); | |
150 | ||
151 | if (next->start == sym->start) { | |
152 | pair = next; | |
153 | goto next_pair; | |
154 | } | |
155 | } | |
156 | pr_debug("%#Lx: diff name v: %s k: %s\n", | |
157 | sym->start, sym->name, pair->name); | |
158 | } | |
159 | } else | |
160 | pr_debug("%#Lx: %s not on kallsyms\n", sym->start, sym->name); | |
161 | ||
162 | err = -1; | |
163 | } | |
164 | ||
165 | if (!verbose) | |
166 | goto out; | |
167 | ||
168 | pr_info("Maps only in vmlinux:\n"); | |
169 | ||
170 | for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { | |
171 | struct map *pos = rb_entry(nd, struct map, rb_node), *pair; | |
172 | /* | |
173 | * If it is the kernel, kallsyms is always "[kernel.kallsyms]", while | |
174 | * the kernel will have the path for the vmlinux file being used, | |
175 | * so use the short name, less descriptive but the same ("[kernel]" in | |
176 | * both cases. | |
177 | */ | |
178 | pair = map_groups__find_by_name(&kallsyms.kmaps, type, | |
179 | (pos->dso->kernel ? | |
180 | pos->dso->short_name : | |
181 | pos->dso->name)); | |
182 | if (pair) | |
183 | pair->priv = 1; | |
184 | else | |
185 | map__fprintf(pos, stderr); | |
186 | } | |
187 | ||
188 | pr_info("Maps in vmlinux with a different name in kallsyms:\n"); | |
189 | ||
190 | for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { | |
191 | struct map *pos = rb_entry(nd, struct map, rb_node), *pair; | |
192 | ||
193 | pair = map_groups__find(&kallsyms.kmaps, type, pos->start); | |
194 | if (pair == NULL || pair->priv) | |
195 | continue; | |
196 | ||
197 | if (pair->start == pos->start) { | |
198 | pair->priv = 1; | |
199 | pr_info(" %Lx-%Lx %Lx %s in kallsyms as", | |
200 | pos->start, pos->end, pos->pgoff, pos->dso->name); | |
201 | if (pos->pgoff != pair->pgoff || pos->end != pair->end) | |
202 | pr_info(": \n*%Lx-%Lx %Lx", | |
203 | pair->start, pair->end, pair->pgoff); | |
204 | pr_info(" %s\n", pair->dso->name); | |
205 | pair->priv = 1; | |
206 | } | |
207 | } | |
208 | ||
209 | pr_info("Maps only in kallsyms:\n"); | |
210 | ||
211 | for (nd = rb_first(&kallsyms.kmaps.maps[type]); | |
212 | nd; nd = rb_next(nd)) { | |
213 | struct map *pos = rb_entry(nd, struct map, rb_node); | |
214 | ||
215 | if (!pos->priv) | |
216 | map__fprintf(pos, stderr); | |
217 | } | |
218 | out: | |
219 | return err; | |
220 | } | |
221 | ||
222 | static struct test { | |
223 | const char *desc; | |
224 | int (*func)(void); | |
225 | } tests[] = { | |
226 | { | |
227 | .desc = "vmlinux symtab matches kallsyms", | |
228 | .func = test__vmlinux_matches_kallsyms, | |
229 | }, | |
230 | { | |
231 | .func = NULL, | |
232 | }, | |
233 | }; | |
234 | ||
235 | static int __cmd_test(void) | |
236 | { | |
237 | int i = 0; | |
238 | ||
239 | page_size = sysconf(_SC_PAGE_SIZE); | |
240 | ||
241 | while (tests[i].func) { | |
242 | int err; | |
243 | pr_info("%2d: %s:", i + 1, tests[i].desc); | |
244 | pr_debug("\n--- start ---\n"); | |
245 | err = tests[i].func(); | |
246 | pr_debug("---- end ----\n%s:", tests[i].desc); | |
247 | pr_info(" %s\n", err ? "FAILED!\n" : "Ok"); | |
248 | ++i; | |
249 | } | |
250 | ||
251 | return 0; | |
252 | } | |
253 | ||
254 | static const char * const test_usage[] = { | |
255 | "perf test [<options>]", | |
256 | NULL, | |
257 | }; | |
258 | ||
259 | static const struct option test_options[] = { | |
8035458f | 260 | OPT_INTEGER('v', "verbose", &verbose, |
1c6a800c ACM |
261 | "be more verbose (show symbol address, etc)"), |
262 | OPT_END() | |
263 | }; | |
264 | ||
265 | int cmd_test(int argc, const char **argv, const char *prefix __used) | |
266 | { | |
267 | argc = parse_options(argc, argv, test_options, test_usage, 0); | |
268 | if (argc) | |
269 | usage_with_options(test_usage, test_options); | |
270 | ||
271 | symbol_conf.priv_size = sizeof(int); | |
272 | symbol_conf.sort_by_name = true; | |
273 | symbol_conf.try_vmlinux_path = true; | |
274 | ||
275 | if (symbol__init() < 0) | |
276 | return -1; | |
277 | ||
278 | setup_pager(); | |
279 | ||
280 | return __cmd_test(); | |
281 | } |