Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf tools: Deal with kernel module names in '[]' correctly

Before patch ba92732e9808 ('perf kmaps: Check kmaps to make code more
robust'), 'perf report' and 'perf annotate' will segfault if trace data
contains kernel module information like this:

# perf report -D -i ./perf.data
...
0 0 0x188 [0x50]: PERF_RECORD_MMAP -1/0: [0xffffffbff1018000(0xf068000) @ 0]: x [test_module]
...

# perf report -i ./perf.data --objdump=/path/to/objdump --kallsyms=/path/to/kallsyms

perf: Segmentation fault
-------- backtrace --------
/path/to/perf[0x503478]
/lib64/libc.so.6(+0x3545f)[0x7fb201f3745f]
/path/to/perf[0x499b56]
/path/to/perf(dso__load_kallsyms+0x13c)[0x49b56c]
/path/to/perf(dso__load+0x72e)[0x49c21e]
/path/to/perf(map__load+0x6e)[0x4ae9ee]
/path/to/perf(thread__find_addr_map+0x24c)[0x47deec]
/path/to/perf(perf_event__preprocess_sample+0x88)[0x47e238]
/path/to/perf[0x43ad02]
/path/to/perf[0x4b55bc]
/path/to/perf(ordered_events__flush+0xca)[0x4b57ea]
/path/to/perf[0x4b1a01]
/path/to/perf(perf_session__process_events+0x3be)[0x4b428e]
/path/to/perf(cmd_report+0xf11)[0x43bfc1]
/path/to/perf[0x474702]
/path/to/perf(main+0x5f5)[0x42de95]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x7fb201f23bd4]
/path/to/perf[0x42dfc4]

This is because __kmod_path__parse treats '[' leading names as kernel
name instead of names of kernel module.

If perf.data contains build information and the buildid of such modules
can be found, the dso->kernel of it will be set to DSO_TYPE_KERNEL by
__event_process_build_id(), not kernel module.

It will then be passed to dso__load() -> dso__load_kernel_sym() ->
dso__load_kcore() if --kallsyms is provided.

The refered patch adds NULL pointer checker to avoid segfault. However,
such kernel modules are still processed incorrectly.

This patch fixes __kmod_path__parse, makes it treat names like
'[test_module]' as kernel modules.

kmod-path.c is also update to reflect the above changes.

Signed-off-by: Wang Nan <wangnan0@huawei.com>
Acked-by: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Zefan Li <lizefan@huawei.com>
Link: http://lkml.kernel.org/r/1433321541-170245-1-git-send-email-wangnan0@huawei.com
[ Fixed the merged with 0443f36b0de0 ("perf machine: Fix the search
for the kernel DSO on the unified list" ]
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

authored by

Wang Nan and committed by
Arnaldo Carvalho de Melo
1f121b03 4fc62a89

+139 -10
+72
tools/perf/tests/kmod-path.c
··· 34 34 return 0; 35 35 } 36 36 37 + static int test_is_kernel_module(const char *path, int cpumode, bool expect) 38 + { 39 + TEST_ASSERT_VAL("is_kernel_module", 40 + (!!is_kernel_module(path, cpumode)) == (!!expect)); 41 + pr_debug("%s (cpumode: %d) - is_kernel_module: %s\n", 42 + path, cpumode, expect ? "true" : "false"); 43 + return 0; 44 + } 45 + 37 46 #define T(path, an, ae, k, c, n, e) \ 38 47 TEST_ASSERT_VAL("failed", !test(path, an, ae, k, c, n, e)) 48 + 49 + #define M(path, c, e) \ 50 + TEST_ASSERT_VAL("failed", !test_is_kernel_module(path, c, e)) 39 51 40 52 int test__kmod_path__parse(void) 41 53 { ··· 56 44 T("/xxxx/xxxx/x-x.ko", false , true , true, false, NULL , NULL); 57 45 T("/xxxx/xxxx/x-x.ko", true , false , true, false, "[x_x]", NULL); 58 46 T("/xxxx/xxxx/x-x.ko", false , false , true, false, NULL , NULL); 47 + M("/xxxx/xxxx/x-x.ko", PERF_RECORD_MISC_CPUMODE_UNKNOWN, true); 48 + M("/xxxx/xxxx/x-x.ko", PERF_RECORD_MISC_KERNEL, true); 49 + M("/xxxx/xxxx/x-x.ko", PERF_RECORD_MISC_USER, false); 59 50 60 51 /* path alloc_name alloc_ext kmod comp name ext */ 61 52 T("/xxxx/xxxx/x.ko.gz", true , true , true, true, "[x]", "gz"); 62 53 T("/xxxx/xxxx/x.ko.gz", false , true , true, true, NULL , "gz"); 63 54 T("/xxxx/xxxx/x.ko.gz", true , false , true, true, "[x]", NULL); 64 55 T("/xxxx/xxxx/x.ko.gz", false , false , true, true, NULL , NULL); 56 + M("/xxxx/xxxx/x.ko.gz", PERF_RECORD_MISC_CPUMODE_UNKNOWN, true); 57 + M("/xxxx/xxxx/x.ko.gz", PERF_RECORD_MISC_KERNEL, true); 58 + M("/xxxx/xxxx/x.ko.gz", PERF_RECORD_MISC_USER, false); 65 59 66 60 /* path alloc_name alloc_ext kmod comp name ext */ 67 61 T("/xxxx/xxxx/x.gz", true , true , false, true, "x.gz" ,"gz"); 68 62 T("/xxxx/xxxx/x.gz", false , true , false, true, NULL ,"gz"); 69 63 T("/xxxx/xxxx/x.gz", true , false , false, true, "x.gz" , NULL); 70 64 T("/xxxx/xxxx/x.gz", false , false , false, true, NULL , NULL); 65 + M("/xxxx/xxxx/x.gz", PERF_RECORD_MISC_CPUMODE_UNKNOWN, false); 66 + M("/xxxx/xxxx/x.gz", PERF_RECORD_MISC_KERNEL, false); 67 + M("/xxxx/xxxx/x.gz", PERF_RECORD_MISC_USER, false); 71 68 72 69 /* path alloc_name alloc_ext kmod comp name ext */ 73 70 T("x.gz", true , true , false, true, "x.gz", "gz"); 74 71 T("x.gz", false , true , false, true, NULL , "gz"); 75 72 T("x.gz", true , false , false, true, "x.gz", NULL); 76 73 T("x.gz", false , false , false, true, NULL , NULL); 74 + M("x.gz", PERF_RECORD_MISC_CPUMODE_UNKNOWN, false); 75 + M("x.gz", PERF_RECORD_MISC_KERNEL, false); 76 + M("x.gz", PERF_RECORD_MISC_USER, false); 77 77 78 78 /* path alloc_name alloc_ext kmod comp name ext */ 79 79 T("x.ko.gz", true , true , true, true, "[x]", "gz"); 80 80 T("x.ko.gz", false , true , true, true, NULL , "gz"); 81 81 T("x.ko.gz", true , false , true, true, "[x]", NULL); 82 82 T("x.ko.gz", false , false , true, true, NULL , NULL); 83 + M("x.ko.gz", PERF_RECORD_MISC_CPUMODE_UNKNOWN, true); 84 + M("x.ko.gz", PERF_RECORD_MISC_KERNEL, true); 85 + M("x.ko.gz", PERF_RECORD_MISC_USER, false); 86 + 87 + /* path alloc_name alloc_ext kmod comp name ext */ 88 + T("[test_module]", true , true , true, false, "[test_module]", NULL); 89 + T("[test_module]", false , true , true, false, NULL , NULL); 90 + T("[test_module]", true , false , true, false, "[test_module]", NULL); 91 + T("[test_module]", false , false , true, false, NULL , NULL); 92 + M("[test_module]", PERF_RECORD_MISC_CPUMODE_UNKNOWN, true); 93 + M("[test_module]", PERF_RECORD_MISC_KERNEL, true); 94 + M("[test_module]", PERF_RECORD_MISC_USER, false); 95 + 96 + /* path alloc_name alloc_ext kmod comp name ext */ 97 + T("[test.module]", true , true , true, false, "[test.module]", NULL); 98 + T("[test.module]", false , true , true, false, NULL , NULL); 99 + T("[test.module]", true , false , true, false, "[test.module]", NULL); 100 + T("[test.module]", false , false , true, false, NULL , NULL); 101 + M("[test.module]", PERF_RECORD_MISC_CPUMODE_UNKNOWN, true); 102 + M("[test.module]", PERF_RECORD_MISC_KERNEL, true); 103 + M("[test.module]", PERF_RECORD_MISC_USER, false); 104 + 105 + /* path alloc_name alloc_ext kmod comp name ext */ 106 + T("[vdso]", true , true , false, false, "[vdso]", NULL); 107 + T("[vdso]", false , true , false, false, NULL , NULL); 108 + T("[vdso]", true , false , false, false, "[vdso]", NULL); 109 + T("[vdso]", false , false , false, false, NULL , NULL); 110 + M("[vdso]", PERF_RECORD_MISC_CPUMODE_UNKNOWN, false); 111 + M("[vdso]", PERF_RECORD_MISC_KERNEL, false); 112 + M("[vdso]", PERF_RECORD_MISC_USER, false); 113 + 114 + /* path alloc_name alloc_ext kmod comp name ext */ 115 + T("[vsyscall]", true , true , false, false, "[vsyscall]", NULL); 116 + T("[vsyscall]", false , true , false, false, NULL , NULL); 117 + T("[vsyscall]", true , false , false, false, "[vsyscall]", NULL); 118 + T("[vsyscall]", false , false , false, false, NULL , NULL); 119 + M("[vsyscall]", PERF_RECORD_MISC_CPUMODE_UNKNOWN, false); 120 + M("[vsyscall]", PERF_RECORD_MISC_KERNEL, false); 121 + M("[vsyscall]", PERF_RECORD_MISC_USER, false); 122 + 123 + /* path alloc_name alloc_ext kmod comp name ext */ 124 + T("[kernel.kallsyms]", true , true , false, false, "[kernel.kallsyms]", NULL); 125 + T("[kernel.kallsyms]", false , true , false, false, NULL , NULL); 126 + T("[kernel.kallsyms]", true , false , false, false, "[kernel.kallsyms]", NULL); 127 + T("[kernel.kallsyms]", false , false , false, false, NULL , NULL); 128 + M("[kernel.kallsyms]", PERF_RECORD_MISC_CPUMODE_UNKNOWN, false); 129 + M("[kernel.kallsyms]", PERF_RECORD_MISC_KERNEL, false); 130 + M("[kernel.kallsyms]", PERF_RECORD_MISC_USER, false); 83 131 84 132 return 0; 85 133 }
+41 -4
tools/perf/util/dso.c
··· 166 166 return false; 167 167 } 168 168 169 - bool is_kernel_module(const char *pathname) 169 + bool is_kernel_module(const char *pathname, int cpumode) 170 170 { 171 171 struct kmod_path m; 172 + int mode = cpumode & PERF_RECORD_MISC_CPUMODE_MASK; 172 173 173 - if (kmod_path__parse(&m, pathname)) 174 - return NULL; 174 + WARN_ONCE(mode != cpumode, 175 + "Internal error: passing unmasked cpumode (%x) to is_kernel_module", 176 + cpumode); 177 + 178 + switch (mode) { 179 + case PERF_RECORD_MISC_USER: 180 + case PERF_RECORD_MISC_HYPERVISOR: 181 + case PERF_RECORD_MISC_GUEST_USER: 182 + return false; 183 + /* Treat PERF_RECORD_MISC_CPUMODE_UNKNOWN as kernel */ 184 + default: 185 + if (kmod_path__parse(&m, pathname)) { 186 + pr_err("Failed to check whether %s is a kernel module or not. Assume it is.", 187 + pathname); 188 + return true; 189 + } 190 + } 175 191 176 192 return m.kmod; 177 193 } ··· 231 215 { 232 216 const char *name = strrchr(path, '/'); 233 217 const char *ext = strrchr(path, '.'); 218 + bool is_simple_name = false; 234 219 235 220 memset(m, 0x0, sizeof(*m)); 236 221 name = name ? name + 1 : path; 237 222 223 + /* 224 + * '.' is also a valid character for module name. For example: 225 + * [aaa.bbb] is a valid module name. '[' should have higher 226 + * priority than '.ko' suffix. 227 + * 228 + * The kernel names are from machine__mmap_name. Such 229 + * name should belong to kernel itself, not kernel module. 230 + */ 231 + if (name[0] == '[') { 232 + is_simple_name = true; 233 + if ((strncmp(name, "[kernel.kallsyms]", 17) == 0) || 234 + (strncmp(name, "[guest.kernel.kallsyms", 22) == 0) || 235 + (strncmp(name, "[vdso]", 6) == 0) || 236 + (strncmp(name, "[vsyscall]", 10) == 0)) { 237 + m->kmod = false; 238 + 239 + } else 240 + m->kmod = true; 241 + } 242 + 238 243 /* No extension, just return name. */ 239 - if (ext == NULL) { 244 + if ((ext == NULL) || is_simple_name) { 240 245 if (alloc_name) { 241 246 m->name = strdup(name); 242 247 return m->name ? 0 : -ENOMEM;
+1 -1
tools/perf/util/dso.h
··· 220 220 int dso__read_binary_type_filename(const struct dso *dso, enum dso_binary_type type, 221 221 char *root_dir, char *filename, size_t size); 222 222 bool is_supported_compression(const char *ext); 223 - bool is_kernel_module(const char *pathname); 223 + bool is_kernel_module(const char *pathname, int cpumode); 224 224 bool decompress_to_file(const char *ext, const char *filename, int output_fd); 225 225 bool dso__needs_decompress(struct dso *dso); 226 226
+4 -4
tools/perf/util/header.c
··· 1239 1239 { 1240 1240 int err = -1; 1241 1241 struct machine *machine; 1242 - u16 misc; 1242 + u16 cpumode; 1243 1243 struct dso *dso; 1244 1244 enum dso_kernel_type dso_type; 1245 1245 ··· 1247 1247 if (!machine) 1248 1248 goto out; 1249 1249 1250 - misc = bev->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; 1250 + cpumode = bev->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; 1251 1251 1252 - switch (misc) { 1252 + switch (cpumode) { 1253 1253 case PERF_RECORD_MISC_KERNEL: 1254 1254 dso_type = DSO_TYPE_KERNEL; 1255 1255 break; ··· 1270 1270 1271 1271 dso__set_build_id(dso, &bev->build_id); 1272 1272 1273 - if (!is_kernel_module(filename)) 1273 + if (!is_kernel_module(filename, cpumode)) 1274 1274 dso->kernel = dso_type; 1275 1275 1276 1276 build_id__sprintf(dso->build_id, sizeof(dso->build_id),
+21 -1
tools/perf/util/machine.c
··· 1149 1149 struct dso *dso; 1150 1150 1151 1151 list_for_each_entry(dso, &machine->dsos.head, node) { 1152 - if (!dso->kernel || is_kernel_module(dso->long_name)) 1152 + 1153 + /* 1154 + * The cpumode passed to is_kernel_module is not the 1155 + * cpumode of *this* event. If we insist on passing 1156 + * correct cpumode to is_kernel_module, we should 1157 + * record the cpumode when we adding this dso to the 1158 + * linked list. 1159 + * 1160 + * However we don't really need passing correct 1161 + * cpumode. We know the correct cpumode must be kernel 1162 + * mode (if not, we should not link it onto kernel_dsos 1163 + * list). 1164 + * 1165 + * Therefore, we pass PERF_RECORD_MISC_CPUMODE_UNKNOWN. 1166 + * is_kernel_module() treats it as a kernel cpumode. 1167 + */ 1168 + 1169 + if (!dso->kernel || 1170 + is_kernel_module(dso->long_name, 1171 + PERF_RECORD_MISC_CPUMODE_UNKNOWN)) 1153 1172 continue; 1173 + 1154 1174 1155 1175 kernel = dso; 1156 1176 break;