Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
0a4e1ae6 JO |
2 | #include <linux/compiler.h> |
3 | #include <linux/rbtree.h> | |
fd20e811 | 4 | #include <inttypes.h> |
0a4e1ae6 | 5 | #include <string.h> |
215a0d30 | 6 | #include <stdlib.h> |
4a3cec84 | 7 | #include "dso.h" |
0a4e1ae6 JO |
8 | #include "map.h" |
9 | #include "symbol.h" | |
fb71c86c | 10 | #include "util.h" // page_size |
0a4e1ae6 JO |
11 | #include "tests.h" |
12 | #include "debug.h" | |
13 | #include "machine.h" | |
14 | ||
82e75d00 AH |
15 | #define UM(x) kallsyms_map->unmap_ip(kallsyms_map, (x)) |
16 | ||
81f17c90 | 17 | int test__vmlinux_matches_kallsyms(struct test *test __maybe_unused, int subtest __maybe_unused) |
0a4e1ae6 JO |
18 | { |
19 | int err = -1; | |
20 | struct rb_node *nd; | |
21 | struct symbol *sym; | |
4bb7123d | 22 | struct map *kallsyms_map, *vmlinux_map, *map; |
0a4e1ae6 | 23 | struct machine kallsyms, vmlinux; |
68a74186 | 24 | struct maps *maps = machine__kernel_maps(&vmlinux); |
d380b348 | 25 | u64 mem_start, mem_end; |
54da0769 | 26 | bool header_printed; |
0a4e1ae6 JO |
27 | |
28 | /* | |
29 | * Step 1: | |
30 | * | |
31 | * Init the machines that will hold kernel, modules obtained from | |
32 | * both vmlinux + .ko files and from /proc/kallsyms split by modules. | |
33 | */ | |
34 | machine__init(&kallsyms, "", HOST_KERNEL_ID); | |
35 | machine__init(&vmlinux, "", HOST_KERNEL_ID); | |
36 | ||
37 | /* | |
38 | * Step 2: | |
39 | * | |
40 | * Create the kernel maps for kallsyms and the DSO where we will then | |
41 | * load /proc/kallsyms. Also create the modules maps from /proc/modules | |
42 | * and find the .ko files that match them in /lib/modules/`uname -r`/. | |
43 | */ | |
44 | if (machine__create_kernel_maps(&kallsyms) < 0) { | |
45 | pr_debug("machine__create_kernel_maps "); | |
c0aab59f | 46 | goto out; |
0a4e1ae6 JO |
47 | } |
48 | ||
49 | /* | |
50 | * Step 3: | |
51 | * | |
52 | * Load and split /proc/kallsyms into multiple maps, one per module. | |
53d0fe68 ACM |
53 | * Do not use kcore, as this test was designed before kcore support |
54 | * and has parts that only make sense if using the non-kcore code. | |
55 | * XXX: extend it to stress the kcorre code as well, hint: the list | |
56 | * of modules extracted from /proc/kcore, in its current form, can't | |
57 | * be compacted against the list of modules found in the "vmlinux" | |
58 | * code and with the one got from /proc/modules from the "kallsyms" code. | |
0a4e1ae6 | 59 | */ |
329f0ade | 60 | if (machine__load_kallsyms(&kallsyms, "/proc/kallsyms") <= 0) { |
0a4e1ae6 JO |
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 | */ | |
a5e813c6 | 73 | kallsyms_map = machine__kernel_map(&kallsyms); |
0a4e1ae6 | 74 | |
0a4e1ae6 JO |
75 | /* |
76 | * Step 5: | |
77 | * | |
78 | * Now repeat step 2, this time for the vmlinux file we'll auto-locate. | |
79 | */ | |
80 | if (machine__create_kernel_maps(&vmlinux) < 0) { | |
81 | pr_debug("machine__create_kernel_maps "); | |
82 | goto out; | |
83 | } | |
84 | ||
a5e813c6 | 85 | vmlinux_map = machine__kernel_map(&vmlinux); |
0a4e1ae6 JO |
86 | |
87 | /* | |
88 | * Step 6: | |
89 | * | |
90 | * Locate a vmlinux file in the vmlinux path that has a buildid that | |
91 | * matches the one of the running kernel. | |
92 | * | |
93 | * While doing that look if we find the ref reloc symbol, if we find it | |
94 | * we'll have its ref_reloc_symbol.unrelocated_addr and then | |
95 | * maps__reloc_vmlinux will notice and set proper ->[un]map_ip routines | |
96 | * to fixup the symbols. | |
97 | */ | |
1d1a2654 | 98 | if (machine__load_vmlinux_path(&vmlinux) <= 0) { |
531f67bb ACM |
99 | pr_debug("Couldn't find a vmlinux that matches the kernel running on this machine, skipping test\n"); |
100 | err = TEST_SKIP; | |
0a4e1ae6 JO |
101 | goto out; |
102 | } | |
103 | ||
104 | err = 0; | |
105 | /* | |
106 | * Step 7: | |
107 | * | |
108 | * Now look at the symbols in the vmlinux DSO and check if we find all of them | |
109 | * in the kallsyms dso. For the ones that are in both, check its names and | |
110 | * end addresses too. | |
111 | */ | |
d05b861e | 112 | map__for_each_symbol(vmlinux_map, sym, nd) { |
0a4e1ae6 | 113 | struct symbol *pair, *first_pair; |
0a4e1ae6 JO |
114 | |
115 | sym = rb_entry(nd, struct symbol, rb_node); | |
116 | ||
117 | if (sym->start == sym->end) | |
118 | continue; | |
119 | ||
d380b348 AH |
120 | mem_start = vmlinux_map->unmap_ip(vmlinux_map, sym->start); |
121 | mem_end = vmlinux_map->unmap_ip(vmlinux_map, sym->end); | |
122 | ||
107cad95 | 123 | first_pair = machine__find_kernel_symbol(&kallsyms, mem_start, NULL); |
0a4e1ae6 JO |
124 | pair = first_pair; |
125 | ||
82e75d00 | 126 | if (pair && UM(pair->start) == mem_start) { |
0a4e1ae6 | 127 | next_pair: |
ab6e9a99 | 128 | if (arch__compare_symbol_names(sym->name, pair->name) == 0) { |
0a4e1ae6 JO |
129 | /* |
130 | * kallsyms don't have the symbol end, so we | |
131 | * set that by using the next symbol start - 1, | |
132 | * in some cases we get this up to a page | |
133 | * wrong, trace_kmalloc when I was developing | |
134 | * this code was one such example, 2106 bytes | |
135 | * off the real size. More than that and we | |
136 | * _really_ have a problem. | |
137 | */ | |
82e75d00 | 138 | s64 skew = mem_end - UM(pair->end); |
5888a8c2 | 139 | if (llabs(skew) >= page_size) |
e267769e | 140 | pr_debug("WARN: %#" PRIx64 ": diff end addr for %s v: %#" PRIx64 " k: %#" PRIx64 "\n", |
d380b348 | 141 | mem_start, sym->name, mem_end, |
82e75d00 | 142 | UM(pair->end)); |
5888a8c2 JO |
143 | |
144 | /* | |
145 | * Do not count this as a failure, because we | |
146 | * could really find a case where it's not | |
147 | * possible to get proper function end from | |
148 | * kallsyms. | |
149 | */ | |
150 | continue; | |
0a4e1ae6 | 151 | } else { |
107cad95 | 152 | pair = machine__find_kernel_symbol_by_name(&kallsyms, sym->name, NULL); |
ab414dcd ACM |
153 | if (pair) { |
154 | if (UM(pair->start) == mem_start) | |
0a4e1ae6 | 155 | goto next_pair; |
0a4e1ae6 | 156 | |
7e1b6595 | 157 | pr_debug("WARN: %#" PRIx64 ": diff name v: %s k: %s\n", |
ab414dcd | 158 | mem_start, sym->name, pair->name); |
6566feaf | 159 | } else { |
7e1b6595 | 160 | pr_debug("WARN: %#" PRIx64 ": diff name v: %s k: %s\n", |
6566feaf | 161 | mem_start, sym->name, first_pair->name); |
0a4e1ae6 | 162 | } |
7e1b6595 ACM |
163 | |
164 | continue; | |
0a4e1ae6 | 165 | } |
93f678b9 ACM |
166 | } else if (mem_start == kallsyms.vmlinux_map->end) { |
167 | /* | |
168 | * Ignore aliases to _etext, i.e. to the end of the kernel text area, | |
169 | * such as __indirect_thunk_end. | |
170 | */ | |
171 | continue; | |
172 | } else { | |
e267769e | 173 | pr_debug("ERR : %#" PRIx64 ": %s not on kallsyms\n", |
d380b348 | 174 | mem_start, sym->name); |
93f678b9 | 175 | } |
0a4e1ae6 JO |
176 | |
177 | err = -1; | |
178 | } | |
179 | ||
bb963e16 | 180 | if (verbose <= 0) |
0a4e1ae6 JO |
181 | goto out; |
182 | ||
54da0769 | 183 | header_printed = false; |
0a4e1ae6 | 184 | |
4bb7123d ACM |
185 | for (map = maps__first(maps); map; map = map__next(map)) { |
186 | struct map * | |
0a4e1ae6 JO |
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 | */ | |
83cf774b | 193 | pair = map_groups__find_by_name(&kallsyms.kmaps, |
4bb7123d ACM |
194 | (map->dso->kernel ? |
195 | map->dso->short_name : | |
196 | map->dso->name)); | |
54da0769 | 197 | if (pair) { |
0a4e1ae6 | 198 | pair->priv = 1; |
54da0769 ACM |
199 | } else { |
200 | if (!header_printed) { | |
201 | pr_info("WARN: Maps only in vmlinux:\n"); | |
202 | header_printed = true; | |
203 | } | |
4bb7123d | 204 | map__fprintf(map, stderr); |
54da0769 | 205 | } |
0a4e1ae6 JO |
206 | } |
207 | ||
54da0769 | 208 | header_printed = false; |
0a4e1ae6 | 209 | |
4bb7123d ACM |
210 | for (map = maps__first(maps); map; map = map__next(map)) { |
211 | struct map *pair; | |
0a4e1ae6 | 212 | |
4bb7123d ACM |
213 | mem_start = vmlinux_map->unmap_ip(vmlinux_map, map->start); |
214 | mem_end = vmlinux_map->unmap_ip(vmlinux_map, map->end); | |
d380b348 | 215 | |
abe5449d | 216 | pair = map_groups__find(&kallsyms.kmaps, mem_start); |
0a4e1ae6 JO |
217 | if (pair == NULL || pair->priv) |
218 | continue; | |
219 | ||
d380b348 | 220 | if (pair->start == mem_start) { |
54da0769 ACM |
221 | if (!header_printed) { |
222 | pr_info("WARN: Maps in vmlinux with a different name in kallsyms:\n"); | |
223 | header_printed = true; | |
224 | } | |
225 | ||
e267769e | 226 | pr_info("WARN: %" PRIx64 "-%" PRIx64 " %" PRIx64 " %s in kallsyms as", |
4bb7123d | 227 | map->start, map->end, map->pgoff, map->dso->name); |
d380b348 | 228 | if (mem_end != pair->end) |
e267769e | 229 | pr_info(":\nWARN: *%" PRIx64 "-%" PRIx64 " %" PRIx64, |
0a4e1ae6 JO |
230 | pair->start, pair->end, pair->pgoff); |
231 | pr_info(" %s\n", pair->dso->name); | |
232 | pair->priv = 1; | |
233 | } | |
234 | } | |
235 | ||
54da0769 | 236 | header_printed = false; |
0a4e1ae6 | 237 | |
68a74186 | 238 | maps = machine__kernel_maps(&kallsyms); |
0a4e1ae6 | 239 | |
4bb7123d | 240 | for (map = maps__first(maps); map; map = map__next(map)) { |
54da0769 ACM |
241 | if (!map->priv) { |
242 | if (!header_printed) { | |
243 | pr_info("WARN: Maps only in kallsyms:\n"); | |
244 | header_printed = true; | |
245 | } | |
4bb7123d | 246 | map__fprintf(map, stderr); |
54da0769 | 247 | } |
0a4e1ae6 JO |
248 | } |
249 | out: | |
c0aab59f ACM |
250 | machine__exit(&kallsyms); |
251 | machine__exit(&vmlinux); | |
0a4e1ae6 JO |
252 | return err; |
253 | } |