1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * build-id.c |
4 | * |
5 | * build-id support |
6 | * |
7 | * Copyright (C) 2009, 2010 Red Hat Inc. |
8 | * Copyright (C) 2009, 2010 Arnaldo Carvalho de Melo <acme@redhat.com> |
9 | */ |
10 | #include "util.h" // lsdir(), mkdir_p(), rm_rf() |
11 | #include <dirent.h> |
12 | #include <errno.h> |
13 | #include <stdio.h> |
14 | #include <sys/stat.h> |
15 | #include <sys/types.h> |
16 | #include "util/copyfile.h" |
17 | #include "dso.h" |
18 | #include "build-id.h" |
19 | #include "event.h" |
20 | #include "namespaces.h" |
21 | #include "map.h" |
22 | #include "symbol.h" |
23 | #include "thread.h" |
24 | #include <linux/kernel.h> |
25 | #include "debug.h" |
26 | #include "session.h" |
27 | #include "tool.h" |
28 | #include "header.h" |
29 | #include "vdso.h" |
30 | #include "path.h" |
31 | #include "probe-file.h" |
32 | #include "strlist.h" |
33 | |
34 | #ifdef HAVE_DEBUGINFOD_SUPPORT |
35 | #include <elfutils/debuginfod.h> |
36 | #endif |
37 | |
38 | #include <linux/ctype.h> |
39 | #include <linux/zalloc.h> |
40 | #include <linux/string.h> |
41 | #include <asm/bug.h> |
42 | |
43 | static bool no_buildid_cache; |
44 | |
45 | int build_id__mark_dso_hit(struct perf_tool *tool __maybe_unused, |
46 | union perf_event *event, |
47 | struct perf_sample *sample, |
48 | struct evsel *evsel __maybe_unused, |
49 | struct machine *machine) |
50 | { |
51 | struct addr_location al; |
52 | struct thread *thread = machine__findnew_thread(machine, pid: sample->pid, |
53 | tid: sample->tid); |
54 | |
55 | if (thread == NULL) { |
56 | pr_err("problem processing %d event, skipping it.\n" , |
57 | event->header.type); |
58 | return -1; |
59 | } |
60 | |
61 | addr_location__init(al: &al); |
62 | if (thread__find_map(thread, cpumode: sample->cpumode, addr: sample->ip, al: &al)) |
63 | map__dso(map: al.map)->hit = 1; |
64 | |
65 | addr_location__exit(al: &al); |
66 | thread__put(thread); |
67 | return 0; |
68 | } |
69 | |
70 | static int perf_event__exit_del_thread(struct perf_tool *tool __maybe_unused, |
71 | union perf_event *event, |
72 | struct perf_sample *sample |
73 | __maybe_unused, |
74 | struct machine *machine) |
75 | { |
76 | struct thread *thread = machine__findnew_thread(machine, |
77 | pid: event->fork.pid, |
78 | tid: event->fork.tid); |
79 | |
80 | dump_printf(fmt: "(%d:%d):(%d:%d)\n" , event->fork.pid, event->fork.tid, |
81 | event->fork.ppid, event->fork.ptid); |
82 | |
83 | if (thread) { |
84 | machine__remove_thread(machine, th: thread); |
85 | thread__put(thread); |
86 | } |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | struct perf_tool build_id__mark_dso_hit_ops = { |
92 | .sample = build_id__mark_dso_hit, |
93 | .mmap = perf_event__process_mmap, |
94 | .mmap2 = perf_event__process_mmap2, |
95 | .fork = perf_event__process_fork, |
96 | .exit = perf_event__exit_del_thread, |
97 | .attr = perf_event__process_attr, |
98 | .build_id = perf_event__process_build_id, |
99 | .ordered_events = true, |
100 | }; |
101 | |
102 | int build_id__sprintf(const struct build_id *build_id, char *bf) |
103 | { |
104 | char *bid = bf; |
105 | const u8 *raw = build_id->data; |
106 | size_t i; |
107 | |
108 | bf[0] = 0x0; |
109 | |
110 | for (i = 0; i < build_id->size; ++i) { |
111 | sprintf(buf: bid, fmt: "%02x" , *raw); |
112 | ++raw; |
113 | bid += 2; |
114 | } |
115 | |
116 | return (bid - bf) + 1; |
117 | } |
118 | |
119 | int sysfs__sprintf_build_id(const char *root_dir, char *sbuild_id) |
120 | { |
121 | char notes[PATH_MAX]; |
122 | struct build_id bid; |
123 | int ret; |
124 | |
125 | if (!root_dir) |
126 | root_dir = "" ; |
127 | |
128 | scnprintf(buf: notes, size: sizeof(notes), fmt: "%s/sys/kernel/notes" , root_dir); |
129 | |
130 | ret = sysfs__read_build_id(filename: notes, bid: &bid); |
131 | if (ret < 0) |
132 | return ret; |
133 | |
134 | return build_id__sprintf(build_id: &bid, bf: sbuild_id); |
135 | } |
136 | |
137 | int filename__sprintf_build_id(const char *pathname, char *sbuild_id) |
138 | { |
139 | struct build_id bid; |
140 | int ret; |
141 | |
142 | ret = filename__read_build_id(filename: pathname, id: &bid); |
143 | if (ret < 0) |
144 | return ret; |
145 | |
146 | return build_id__sprintf(build_id: &bid, bf: sbuild_id); |
147 | } |
148 | |
149 | /* asnprintf consolidates asprintf and snprintf */ |
150 | static int asnprintf(char **strp, size_t size, const char *fmt, ...) |
151 | { |
152 | va_list ap; |
153 | int ret; |
154 | |
155 | if (!strp) |
156 | return -EINVAL; |
157 | |
158 | va_start(ap, fmt); |
159 | if (*strp) |
160 | ret = vsnprintf(buf: *strp, size, fmt, args: ap); |
161 | else |
162 | ret = vasprintf(strp, fmt, ap); |
163 | va_end(ap); |
164 | |
165 | return ret; |
166 | } |
167 | |
168 | char *build_id_cache__kallsyms_path(const char *sbuild_id, char *bf, |
169 | size_t size) |
170 | { |
171 | bool retry_old = true; |
172 | |
173 | snprintf(buf: bf, size, fmt: "%s/%s/%s/kallsyms" , |
174 | buildid_dir, DSO__NAME_KALLSYMS, sbuild_id); |
175 | retry: |
176 | if (!access(bf, F_OK)) |
177 | return bf; |
178 | if (retry_old) { |
179 | /* Try old style kallsyms cache */ |
180 | snprintf(buf: bf, size, fmt: "%s/%s/%s" , |
181 | buildid_dir, DSO__NAME_KALLSYMS, sbuild_id); |
182 | retry_old = false; |
183 | goto retry; |
184 | } |
185 | |
186 | return NULL; |
187 | } |
188 | |
189 | char *build_id_cache__linkname(const char *sbuild_id, char *bf, size_t size) |
190 | { |
191 | char *tmp = bf; |
192 | int ret = asnprintf(strp: &bf, size, fmt: "%s/.build-id/%.2s/%s" , buildid_dir, |
193 | sbuild_id, sbuild_id + 2); |
194 | if (ret < 0 || (tmp && size < (unsigned int)ret)) |
195 | return NULL; |
196 | return bf; |
197 | } |
198 | |
199 | /* The caller is responsible to free the returned buffer. */ |
200 | char *build_id_cache__origname(const char *sbuild_id) |
201 | { |
202 | char *linkname; |
203 | char buf[PATH_MAX]; |
204 | char *ret = NULL, *p; |
205 | size_t offs = 5; /* == strlen("../..") */ |
206 | ssize_t len; |
207 | |
208 | linkname = build_id_cache__linkname(sbuild_id, NULL, size: 0); |
209 | if (!linkname) |
210 | return NULL; |
211 | |
212 | len = readlink(linkname, buf, sizeof(buf) - 1); |
213 | if (len <= 0) |
214 | goto out; |
215 | buf[len] = '\0'; |
216 | |
217 | /* The link should be "../..<origpath>/<sbuild_id>" */ |
218 | p = strrchr(buf, '/'); /* Cut off the "/<sbuild_id>" */ |
219 | if (p && (p > buf + offs)) { |
220 | *p = '\0'; |
221 | if (buf[offs + 1] == '[') |
222 | offs++; /* |
223 | * This is a DSO name, like [kernel.kallsyms]. |
224 | * Skip the first '/', since this is not the |
225 | * cache of a regular file. |
226 | */ |
227 | ret = strdup(buf + offs); /* Skip "../..[/]" */ |
228 | } |
229 | out: |
230 | free(linkname); |
231 | return ret; |
232 | } |
233 | |
234 | /* Check if the given build_id cache is valid on current running system */ |
235 | static bool build_id_cache__valid_id(char *sbuild_id) |
236 | { |
237 | char real_sbuild_id[SBUILD_ID_SIZE] = "" ; |
238 | char *pathname; |
239 | int ret = 0; |
240 | bool result = false; |
241 | |
242 | pathname = build_id_cache__origname(sbuild_id); |
243 | if (!pathname) |
244 | return false; |
245 | |
246 | if (!strcmp(pathname, DSO__NAME_KALLSYMS)) |
247 | ret = sysfs__sprintf_build_id(root_dir: "/" , sbuild_id: real_sbuild_id); |
248 | else if (pathname[0] == '/') |
249 | ret = filename__sprintf_build_id(pathname, sbuild_id: real_sbuild_id); |
250 | else |
251 | ret = -EINVAL; /* Should we support other special DSO cache? */ |
252 | if (ret >= 0) |
253 | result = (strcmp(sbuild_id, real_sbuild_id) == 0); |
254 | free(pathname); |
255 | |
256 | return result; |
257 | } |
258 | |
259 | static const char *build_id_cache__basename(bool is_kallsyms, bool is_vdso, |
260 | bool is_debug) |
261 | { |
262 | return is_kallsyms ? "kallsyms" : (is_vdso ? "vdso" : (is_debug ? |
263 | "debug" : "elf" )); |
264 | } |
265 | |
266 | char *__dso__build_id_filename(const struct dso *dso, char *bf, size_t size, |
267 | bool is_debug, bool is_kallsyms) |
268 | { |
269 | bool is_vdso = dso__is_vdso(dso: (struct dso *)dso); |
270 | char sbuild_id[SBUILD_ID_SIZE]; |
271 | char *linkname; |
272 | bool alloc = (bf == NULL); |
273 | int ret; |
274 | |
275 | if (!dso->has_build_id) |
276 | return NULL; |
277 | |
278 | build_id__sprintf(build_id: &dso->bid, bf: sbuild_id); |
279 | linkname = build_id_cache__linkname(sbuild_id, NULL, size: 0); |
280 | if (!linkname) |
281 | return NULL; |
282 | |
283 | /* Check if old style build_id cache */ |
284 | if (is_regular_file(file: linkname)) |
285 | ret = asnprintf(strp: &bf, size, fmt: "%s" , linkname); |
286 | else |
287 | ret = asnprintf(strp: &bf, size, fmt: "%s/%s" , linkname, |
288 | build_id_cache__basename(is_kallsyms, is_vdso, |
289 | is_debug)); |
290 | if (ret < 0 || (!alloc && size < (unsigned int)ret)) |
291 | bf = NULL; |
292 | free(linkname); |
293 | |
294 | return bf; |
295 | } |
296 | |
297 | char *dso__build_id_filename(const struct dso *dso, char *bf, size_t size, |
298 | bool is_debug) |
299 | { |
300 | bool is_kallsyms = dso__is_kallsyms(dso: (struct dso *)dso); |
301 | |
302 | return __dso__build_id_filename(dso, bf, size, is_debug, is_kallsyms); |
303 | } |
304 | |
305 | static int write_buildid(const char *name, size_t name_len, struct build_id *bid, |
306 | pid_t pid, u16 misc, struct feat_fd *fd) |
307 | { |
308 | int err; |
309 | struct b; |
310 | size_t len; |
311 | |
312 | len = name_len + 1; |
313 | len = PERF_ALIGN(len, NAME_ALIGN); |
314 | |
315 | memset(&b, 0, sizeof(b)); |
316 | memcpy(&b.data, bid->data, bid->size); |
317 | b.size = (u8) bid->size; |
318 | misc |= PERF_RECORD_MISC_BUILD_ID_SIZE; |
319 | b.pid = pid; |
320 | b.header.misc = misc; |
321 | b.header.size = sizeof(b) + len; |
322 | |
323 | err = do_write(fd, buf: &b, size: sizeof(b)); |
324 | if (err < 0) |
325 | return err; |
326 | |
327 | return write_padded(fd, bf: name, count: name_len + 1, count_aligned: len); |
328 | } |
329 | |
330 | static int machine__write_buildid_table(struct machine *machine, |
331 | struct feat_fd *fd) |
332 | { |
333 | int err = 0; |
334 | struct dso *pos; |
335 | u16 kmisc = PERF_RECORD_MISC_KERNEL, |
336 | umisc = PERF_RECORD_MISC_USER; |
337 | |
338 | if (!machine__is_host(machine)) { |
339 | kmisc = PERF_RECORD_MISC_GUEST_KERNEL; |
340 | umisc = PERF_RECORD_MISC_GUEST_USER; |
341 | } |
342 | |
343 | dsos__for_each_with_build_id(pos, &machine->dsos.head) { |
344 | const char *name; |
345 | size_t name_len; |
346 | bool in_kernel = false; |
347 | |
348 | if (!pos->hit && !dso__is_vdso(dso: pos)) |
349 | continue; |
350 | |
351 | if (dso__is_vdso(dso: pos)) { |
352 | name = pos->short_name; |
353 | name_len = pos->short_name_len; |
354 | } else if (dso__is_kcore(dso: pos)) { |
355 | name = machine->mmap_name; |
356 | name_len = strlen(name); |
357 | } else { |
358 | name = pos->long_name; |
359 | name_len = pos->long_name_len; |
360 | } |
361 | |
362 | in_kernel = pos->kernel || |
363 | is_kernel_module(pathname: name, |
364 | PERF_RECORD_MISC_CPUMODE_UNKNOWN); |
365 | err = write_buildid(name, name_len, bid: &pos->bid, pid: machine->pid, |
366 | misc: in_kernel ? kmisc : umisc, fd); |
367 | if (err) |
368 | break; |
369 | } |
370 | |
371 | return err; |
372 | } |
373 | |
374 | int perf_session__write_buildid_table(struct perf_session *session, |
375 | struct feat_fd *fd) |
376 | { |
377 | struct rb_node *nd; |
378 | int err = machine__write_buildid_table(machine: &session->machines.host, fd); |
379 | |
380 | if (err) |
381 | return err; |
382 | |
383 | for (nd = rb_first_cached(&session->machines.guests); nd; |
384 | nd = rb_next(nd)) { |
385 | struct machine *pos = rb_entry(nd, struct machine, rb_node); |
386 | err = machine__write_buildid_table(machine: pos, fd); |
387 | if (err) |
388 | break; |
389 | } |
390 | return err; |
391 | } |
392 | |
393 | static int __dsos__hit_all(struct list_head *head) |
394 | { |
395 | struct dso *pos; |
396 | |
397 | list_for_each_entry(pos, head, node) |
398 | pos->hit = true; |
399 | |
400 | return 0; |
401 | } |
402 | |
403 | static int machine__hit_all_dsos(struct machine *machine) |
404 | { |
405 | return __dsos__hit_all(head: &machine->dsos.head); |
406 | } |
407 | |
408 | int dsos__hit_all(struct perf_session *session) |
409 | { |
410 | struct rb_node *nd; |
411 | int err; |
412 | |
413 | err = machine__hit_all_dsos(machine: &session->machines.host); |
414 | if (err) |
415 | return err; |
416 | |
417 | for (nd = rb_first_cached(&session->machines.guests); nd; |
418 | nd = rb_next(nd)) { |
419 | struct machine *pos = rb_entry(nd, struct machine, rb_node); |
420 | |
421 | err = machine__hit_all_dsos(machine: pos); |
422 | if (err) |
423 | return err; |
424 | } |
425 | |
426 | return 0; |
427 | } |
428 | |
429 | void disable_buildid_cache(void) |
430 | { |
431 | no_buildid_cache = true; |
432 | } |
433 | |
434 | static bool lsdir_bid_head_filter(const char *name __maybe_unused, |
435 | struct dirent *d) |
436 | { |
437 | return (strlen(d->d_name) == 2) && |
438 | isxdigit(d->d_name[0]) && isxdigit(d->d_name[1]); |
439 | } |
440 | |
441 | static bool lsdir_bid_tail_filter(const char *name __maybe_unused, |
442 | struct dirent *d) |
443 | { |
444 | int i = 0; |
445 | while (isxdigit(d->d_name[i]) && i < SBUILD_ID_SIZE - 3) |
446 | i++; |
447 | return (i >= SBUILD_ID_MIN_SIZE - 3) && (i <= SBUILD_ID_SIZE - 3) && |
448 | (d->d_name[i] == '\0'); |
449 | } |
450 | |
451 | struct strlist *build_id_cache__list_all(bool validonly) |
452 | { |
453 | struct strlist *toplist, *linklist = NULL, *bidlist; |
454 | struct str_node *nd, *nd2; |
455 | char *topdir, *linkdir = NULL; |
456 | char sbuild_id[SBUILD_ID_SIZE]; |
457 | |
458 | /* for filename__ functions */ |
459 | if (validonly) |
460 | symbol__init(NULL); |
461 | |
462 | /* Open the top-level directory */ |
463 | if (asprintf(&topdir, "%s/.build-id/" , buildid_dir) < 0) |
464 | return NULL; |
465 | |
466 | bidlist = strlist__new(NULL, NULL); |
467 | if (!bidlist) |
468 | goto out; |
469 | |
470 | toplist = lsdir(name: topdir, filter: lsdir_bid_head_filter); |
471 | if (!toplist) { |
472 | pr_debug("Error in lsdir(%s): %d\n" , topdir, errno); |
473 | /* If there is no buildid cache, return an empty list */ |
474 | if (errno == ENOENT) |
475 | goto out; |
476 | goto err_out; |
477 | } |
478 | |
479 | strlist__for_each_entry(nd, toplist) { |
480 | if (asprintf(&linkdir, "%s/%s" , topdir, nd->s) < 0) |
481 | goto err_out; |
482 | /* Open the lower-level directory */ |
483 | linklist = lsdir(name: linkdir, filter: lsdir_bid_tail_filter); |
484 | if (!linklist) { |
485 | pr_debug("Error in lsdir(%s): %d\n" , linkdir, errno); |
486 | goto err_out; |
487 | } |
488 | strlist__for_each_entry(nd2, linklist) { |
489 | if (snprintf(buf: sbuild_id, SBUILD_ID_SIZE, fmt: "%s%s" , |
490 | nd->s, nd2->s) > SBUILD_ID_SIZE - 1) |
491 | goto err_out; |
492 | if (validonly && !build_id_cache__valid_id(sbuild_id)) |
493 | continue; |
494 | if (strlist__add(slist: bidlist, str: sbuild_id) < 0) |
495 | goto err_out; |
496 | } |
497 | strlist__delete(slist: linklist); |
498 | zfree(&linkdir); |
499 | } |
500 | |
501 | out_free: |
502 | strlist__delete(slist: toplist); |
503 | out: |
504 | free(topdir); |
505 | |
506 | return bidlist; |
507 | |
508 | err_out: |
509 | strlist__delete(slist: linklist); |
510 | zfree(&linkdir); |
511 | strlist__delete(slist: bidlist); |
512 | bidlist = NULL; |
513 | goto out_free; |
514 | } |
515 | |
516 | static bool str_is_build_id(const char *maybe_sbuild_id, size_t len) |
517 | { |
518 | size_t i; |
519 | |
520 | for (i = 0; i < len; i++) { |
521 | if (!isxdigit(maybe_sbuild_id[i])) |
522 | return false; |
523 | } |
524 | return true; |
525 | } |
526 | |
527 | /* Return the valid complete build-id */ |
528 | char *build_id_cache__complement(const char *incomplete_sbuild_id) |
529 | { |
530 | struct strlist *bidlist; |
531 | struct str_node *nd, *cand = NULL; |
532 | char *sbuild_id = NULL; |
533 | size_t len = strlen(incomplete_sbuild_id); |
534 | |
535 | if (len >= SBUILD_ID_SIZE || |
536 | !str_is_build_id(maybe_sbuild_id: incomplete_sbuild_id, len)) |
537 | return NULL; |
538 | |
539 | bidlist = build_id_cache__list_all(validonly: true); |
540 | if (!bidlist) |
541 | return NULL; |
542 | |
543 | strlist__for_each_entry(nd, bidlist) { |
544 | if (strncmp(nd->s, incomplete_sbuild_id, len) != 0) |
545 | continue; |
546 | if (cand) { /* Error: There are more than 2 candidates. */ |
547 | cand = NULL; |
548 | break; |
549 | } |
550 | cand = nd; |
551 | } |
552 | if (cand) |
553 | sbuild_id = strdup(cand->s); |
554 | strlist__delete(slist: bidlist); |
555 | |
556 | return sbuild_id; |
557 | } |
558 | |
559 | char *build_id_cache__cachedir(const char *sbuild_id, const char *name, |
560 | struct nsinfo *nsi, bool is_kallsyms, |
561 | bool is_vdso) |
562 | { |
563 | char *realname = NULL, *filename; |
564 | bool slash = is_kallsyms || is_vdso; |
565 | |
566 | if (!slash) |
567 | realname = nsinfo__realpath(path: name, nsi); |
568 | |
569 | if (asprintf(&filename, "%s%s%s%s%s" , buildid_dir, slash ? "/" : "" , |
570 | is_vdso ? DSO__NAME_VDSO : (realname ? realname : name), |
571 | sbuild_id ? "/" : "" , sbuild_id ?: "" ) < 0) |
572 | filename = NULL; |
573 | |
574 | free(realname); |
575 | return filename; |
576 | } |
577 | |
578 | int build_id_cache__list_build_ids(const char *pathname, struct nsinfo *nsi, |
579 | struct strlist **result) |
580 | { |
581 | char *dir_name; |
582 | int ret = 0; |
583 | |
584 | dir_name = build_id_cache__cachedir(NULL, name: pathname, nsi, is_kallsyms: false, is_vdso: false); |
585 | if (!dir_name) |
586 | return -ENOMEM; |
587 | |
588 | *result = lsdir(name: dir_name, filter: lsdir_no_dot_filter); |
589 | if (!*result) |
590 | ret = -errno; |
591 | free(dir_name); |
592 | |
593 | return ret; |
594 | } |
595 | |
596 | #if defined(HAVE_LIBELF_SUPPORT) && defined(HAVE_GELF_GETNOTE_SUPPORT) |
597 | static int build_id_cache__add_sdt_cache(const char *sbuild_id, |
598 | const char *realname, |
599 | struct nsinfo *nsi) |
600 | { |
601 | struct probe_cache *cache; |
602 | int ret; |
603 | struct nscookie nsc; |
604 | |
605 | cache = probe_cache__new(sbuild_id, nsi); |
606 | if (!cache) |
607 | return -1; |
608 | |
609 | nsinfo__mountns_enter(nsi, &nsc); |
610 | ret = probe_cache__scan_sdt(cache, realname); |
611 | nsinfo__mountns_exit(&nsc); |
612 | if (ret >= 0) { |
613 | pr_debug4("Found %d SDTs in %s\n" , ret, realname); |
614 | if (probe_cache__commit(cache) < 0) |
615 | ret = -1; |
616 | } |
617 | probe_cache__delete(cache); |
618 | return ret; |
619 | } |
620 | #else |
621 | #define build_id_cache__add_sdt_cache(sbuild_id, realname, nsi) (0) |
622 | #endif |
623 | |
624 | static char *build_id_cache__find_debug(const char *sbuild_id, |
625 | struct nsinfo *nsi, |
626 | const char *root_dir) |
627 | { |
628 | const char *dirname = "/usr/lib/debug/.build-id/" ; |
629 | char *realname = NULL; |
630 | char dirbuf[PATH_MAX]; |
631 | char *debugfile; |
632 | struct nscookie nsc; |
633 | size_t len = 0; |
634 | |
635 | debugfile = calloc(1, PATH_MAX); |
636 | if (!debugfile) |
637 | goto out; |
638 | |
639 | if (root_dir) { |
640 | path__join(bf: dirbuf, PATH_MAX, path1: root_dir, path2: dirname); |
641 | dirname = dirbuf; |
642 | } |
643 | |
644 | len = __symbol__join_symfs(bf: debugfile, PATH_MAX, path: dirname); |
645 | snprintf(buf: debugfile + len, PATH_MAX - len, fmt: "%.2s/%s.debug" , sbuild_id, |
646 | sbuild_id + 2); |
647 | |
648 | nsinfo__mountns_enter(nsi, nc: &nsc); |
649 | realname = realpath(debugfile, NULL); |
650 | if (realname && access(realname, R_OK)) |
651 | zfree(&realname); |
652 | nsinfo__mountns_exit(nc: &nsc); |
653 | |
654 | #ifdef HAVE_DEBUGINFOD_SUPPORT |
655 | if (realname == NULL) { |
656 | debuginfod_client* c; |
657 | |
658 | pr_debug("Downloading debug info with build id %s\n" , sbuild_id); |
659 | |
660 | c = debuginfod_begin(); |
661 | if (c != NULL) { |
662 | int fd = debuginfod_find_debuginfo(c, |
663 | (const unsigned char*)sbuild_id, 0, |
664 | &realname); |
665 | if (fd >= 0) |
666 | close(fd); /* retaining reference by realname */ |
667 | debuginfod_end(c); |
668 | } |
669 | } |
670 | #endif |
671 | |
672 | out: |
673 | free(debugfile); |
674 | return realname; |
675 | } |
676 | |
677 | int |
678 | build_id_cache__add(const char *sbuild_id, const char *name, const char *realname, |
679 | struct nsinfo *nsi, bool is_kallsyms, bool is_vdso, |
680 | const char *proper_name, const char *root_dir) |
681 | { |
682 | const size_t size = PATH_MAX; |
683 | char *filename = NULL, *dir_name = NULL, *linkname = zalloc(size), *tmp; |
684 | char *debugfile = NULL; |
685 | int err = -1; |
686 | |
687 | if (!proper_name) |
688 | proper_name = name; |
689 | |
690 | dir_name = build_id_cache__cachedir(sbuild_id, name: proper_name, nsi, is_kallsyms, |
691 | is_vdso); |
692 | if (!dir_name) |
693 | goto out_free; |
694 | |
695 | /* Remove old style build-id cache */ |
696 | if (is_regular_file(file: dir_name)) |
697 | if (unlink(dir_name)) |
698 | goto out_free; |
699 | |
700 | if (mkdir_p(path: dir_name, mode: 0755)) |
701 | goto out_free; |
702 | |
703 | /* Save the allocated buildid dirname */ |
704 | if (asprintf(&filename, "%s/%s" , dir_name, |
705 | build_id_cache__basename(is_kallsyms, is_vdso, |
706 | is_debug: false)) < 0) { |
707 | filename = NULL; |
708 | goto out_free; |
709 | } |
710 | |
711 | if (access(filename, F_OK)) { |
712 | if (is_kallsyms) { |
713 | if (copyfile("/proc/kallsyms" , filename)) |
714 | goto out_free; |
715 | } else if (nsi && nsinfo__need_setns(nsi)) { |
716 | if (copyfile_ns(name, filename, nsi)) |
717 | goto out_free; |
718 | } else if (link(realname, filename) && errno != EEXIST) { |
719 | struct stat f_stat; |
720 | |
721 | if (!(stat(name, &f_stat) < 0) && |
722 | copyfile_mode(name, filename, f_stat.st_mode)) |
723 | goto out_free; |
724 | } |
725 | } |
726 | |
727 | /* Some binaries are stripped, but have .debug files with their symbol |
728 | * table. Check to see if we can locate one of those, since the elf |
729 | * file itself may not be very useful to users of our tools without a |
730 | * symtab. |
731 | */ |
732 | if (!is_kallsyms && !is_vdso && |
733 | strncmp(".ko" , name + strlen(name) - 3, 3)) { |
734 | debugfile = build_id_cache__find_debug(sbuild_id, nsi, root_dir); |
735 | if (debugfile) { |
736 | zfree(&filename); |
737 | if (asprintf(&filename, "%s/%s" , dir_name, |
738 | build_id_cache__basename(is_kallsyms: false, is_vdso: false, is_debug: true)) < 0) { |
739 | filename = NULL; |
740 | goto out_free; |
741 | } |
742 | if (access(filename, F_OK)) { |
743 | if (nsi && nsinfo__need_setns(nsi)) { |
744 | if (copyfile_ns(debugfile, filename, |
745 | nsi)) |
746 | goto out_free; |
747 | } else if (link(debugfile, filename) && |
748 | errno != EEXIST && |
749 | copyfile(debugfile, filename)) |
750 | goto out_free; |
751 | } |
752 | } |
753 | } |
754 | |
755 | if (!build_id_cache__linkname(sbuild_id, bf: linkname, size)) |
756 | goto out_free; |
757 | tmp = strrchr(linkname, '/'); |
758 | *tmp = '\0'; |
759 | |
760 | if (access(linkname, X_OK) && mkdir_p(linkname, 0755)) |
761 | goto out_free; |
762 | |
763 | *tmp = '/'; |
764 | tmp = dir_name + strlen(buildid_dir) - 5; |
765 | memcpy(tmp, "../.." , 5); |
766 | |
767 | if (symlink(tmp, linkname) == 0) { |
768 | err = 0; |
769 | } else if (errno == EEXIST) { |
770 | char path[PATH_MAX]; |
771 | ssize_t len; |
772 | |
773 | len = readlink(linkname, path, sizeof(path) - 1); |
774 | if (len <= 0) { |
775 | pr_err("Can't read link: %s\n" , linkname); |
776 | goto out_free; |
777 | } |
778 | path[len] = '\0'; |
779 | |
780 | if (strcmp(tmp, path)) { |
781 | pr_debug("build <%s> already linked to %s\n" , |
782 | sbuild_id, linkname); |
783 | } |
784 | err = 0; |
785 | } |
786 | |
787 | /* Update SDT cache : error is just warned */ |
788 | if (realname && |
789 | build_id_cache__add_sdt_cache(sbuild_id, realname, nsi) < 0) |
790 | pr_debug4("Failed to update/scan SDT cache for %s\n" , realname); |
791 | |
792 | out_free: |
793 | free(filename); |
794 | free(debugfile); |
795 | free(dir_name); |
796 | free(linkname); |
797 | return err; |
798 | } |
799 | |
800 | int __build_id_cache__add_s(const char *sbuild_id, const char *name, |
801 | struct nsinfo *nsi, bool is_kallsyms, bool is_vdso, |
802 | const char *proper_name, const char *root_dir) |
803 | { |
804 | char *realname = NULL; |
805 | int err = -1; |
806 | |
807 | if (!is_kallsyms) { |
808 | if (!is_vdso) |
809 | realname = nsinfo__realpath(path: name, nsi); |
810 | else |
811 | realname = realpath(name, NULL); |
812 | if (!realname) |
813 | goto out_free; |
814 | } |
815 | |
816 | err = build_id_cache__add(sbuild_id, name, realname, nsi, |
817 | is_kallsyms, is_vdso, proper_name, root_dir); |
818 | out_free: |
819 | if (!is_kallsyms) |
820 | free(realname); |
821 | return err; |
822 | } |
823 | |
824 | static int build_id_cache__add_b(const struct build_id *bid, |
825 | const char *name, struct nsinfo *nsi, |
826 | bool is_kallsyms, bool is_vdso, |
827 | const char *proper_name, |
828 | const char *root_dir) |
829 | { |
830 | char sbuild_id[SBUILD_ID_SIZE]; |
831 | |
832 | build_id__sprintf(build_id: bid, bf: sbuild_id); |
833 | |
834 | return __build_id_cache__add_s(sbuild_id, name, nsi, is_kallsyms, |
835 | is_vdso, proper_name, root_dir); |
836 | } |
837 | |
838 | bool build_id_cache__cached(const char *sbuild_id) |
839 | { |
840 | bool ret = false; |
841 | char *filename = build_id_cache__linkname(sbuild_id, NULL, size: 0); |
842 | |
843 | if (filename && !access(filename, F_OK)) |
844 | ret = true; |
845 | free(filename); |
846 | |
847 | return ret; |
848 | } |
849 | |
850 | int build_id_cache__remove_s(const char *sbuild_id) |
851 | { |
852 | const size_t size = PATH_MAX; |
853 | char *filename = zalloc(size), |
854 | *linkname = zalloc(size), *tmp; |
855 | int err = -1; |
856 | |
857 | if (filename == NULL || linkname == NULL) |
858 | goto out_free; |
859 | |
860 | if (!build_id_cache__linkname(sbuild_id, bf: linkname, size)) |
861 | goto out_free; |
862 | |
863 | if (access(linkname, F_OK)) |
864 | goto out_free; |
865 | |
866 | if (readlink(linkname, filename, size - 1) < 0) |
867 | goto out_free; |
868 | |
869 | if (unlink(linkname)) |
870 | goto out_free; |
871 | |
872 | /* |
873 | * Since the link is relative, we must make it absolute: |
874 | */ |
875 | tmp = strrchr(linkname, '/') + 1; |
876 | snprintf(buf: tmp, size: size - (tmp - linkname), fmt: "%s" , filename); |
877 | |
878 | if (rm_rf(path: linkname)) |
879 | goto out_free; |
880 | |
881 | err = 0; |
882 | out_free: |
883 | free(filename); |
884 | free(linkname); |
885 | return err; |
886 | } |
887 | |
888 | static int filename__read_build_id_ns(const char *filename, |
889 | struct build_id *bid, |
890 | struct nsinfo *nsi) |
891 | { |
892 | struct nscookie nsc; |
893 | int ret; |
894 | |
895 | nsinfo__mountns_enter(nsi, nc: &nsc); |
896 | ret = filename__read_build_id(filename, id: bid); |
897 | nsinfo__mountns_exit(nc: &nsc); |
898 | |
899 | return ret; |
900 | } |
901 | |
902 | static bool dso__build_id_mismatch(struct dso *dso, const char *name) |
903 | { |
904 | struct build_id bid; |
905 | bool ret = false; |
906 | |
907 | mutex_lock(&dso->lock); |
908 | if (filename__read_build_id_ns(filename: name, bid: &bid, nsi: dso->nsinfo) >= 0) |
909 | ret = !dso__build_id_equal(dso, bid: &bid); |
910 | |
911 | mutex_unlock(lock: &dso->lock); |
912 | |
913 | return ret; |
914 | } |
915 | |
916 | static int dso__cache_build_id(struct dso *dso, struct machine *machine, |
917 | void *priv __maybe_unused) |
918 | { |
919 | bool is_kallsyms = dso__is_kallsyms(dso); |
920 | bool is_vdso = dso__is_vdso(dso); |
921 | const char *name = dso->long_name; |
922 | const char *proper_name = NULL; |
923 | const char *root_dir = NULL; |
924 | char *allocated_name = NULL; |
925 | int ret = 0; |
926 | |
927 | if (!dso->has_build_id) |
928 | return 0; |
929 | |
930 | if (dso__is_kcore(dso)) { |
931 | is_kallsyms = true; |
932 | name = machine->mmap_name; |
933 | } |
934 | |
935 | if (!machine__is_host(machine)) { |
936 | if (*machine->root_dir) { |
937 | root_dir = machine->root_dir; |
938 | ret = asprintf(&allocated_name, "%s/%s" , root_dir, name); |
939 | if (ret < 0) |
940 | return ret; |
941 | proper_name = name; |
942 | name = allocated_name; |
943 | } else if (is_kallsyms) { |
944 | /* Cannot get guest kallsyms */ |
945 | return 0; |
946 | } |
947 | } |
948 | |
949 | if (!is_kallsyms && dso__build_id_mismatch(dso, name)) |
950 | goto out_free; |
951 | |
952 | mutex_lock(&dso->lock); |
953 | ret = build_id_cache__add_b(bid: &dso->bid, name, nsi: dso->nsinfo, |
954 | is_kallsyms, is_vdso, proper_name, root_dir); |
955 | mutex_unlock(lock: &dso->lock); |
956 | out_free: |
957 | free(allocated_name); |
958 | return ret; |
959 | } |
960 | |
961 | static int |
962 | machines__for_each_dso(struct machines *machines, machine__dso_t fn, void *priv) |
963 | { |
964 | int ret = machine__for_each_dso(machine: &machines->host, fn, priv); |
965 | struct rb_node *nd; |
966 | |
967 | for (nd = rb_first_cached(&machines->guests); nd; |
968 | nd = rb_next(nd)) { |
969 | struct machine *pos = rb_entry(nd, struct machine, rb_node); |
970 | |
971 | ret |= machine__for_each_dso(machine: pos, fn, priv); |
972 | } |
973 | return ret ? -1 : 0; |
974 | } |
975 | |
976 | int __perf_session__cache_build_ids(struct perf_session *session, |
977 | machine__dso_t fn, void *priv) |
978 | { |
979 | if (no_buildid_cache) |
980 | return 0; |
981 | |
982 | if (mkdir(buildid_dir, 0755) != 0 && errno != EEXIST) |
983 | return -1; |
984 | |
985 | return machines__for_each_dso(machines: &session->machines, fn, priv) ? -1 : 0; |
986 | } |
987 | |
988 | int perf_session__cache_build_ids(struct perf_session *session) |
989 | { |
990 | return __perf_session__cache_build_ids(session, fn: dso__cache_build_id, NULL); |
991 | } |
992 | |
993 | static bool machine__read_build_ids(struct machine *machine, bool with_hits) |
994 | { |
995 | return __dsos__read_build_ids(head: &machine->dsos.head, with_hits); |
996 | } |
997 | |
998 | bool perf_session__read_build_ids(struct perf_session *session, bool with_hits) |
999 | { |
1000 | struct rb_node *nd; |
1001 | bool ret = machine__read_build_ids(machine: &session->machines.host, with_hits); |
1002 | |
1003 | for (nd = rb_first_cached(&session->machines.guests); nd; |
1004 | nd = rb_next(nd)) { |
1005 | struct machine *pos = rb_entry(nd, struct machine, rb_node); |
1006 | ret |= machine__read_build_ids(machine: pos, with_hits); |
1007 | } |
1008 | |
1009 | return ret; |
1010 | } |
1011 | |
1012 | void build_id__init(struct build_id *bid, const u8 *data, size_t size) |
1013 | { |
1014 | WARN_ON(size > BUILD_ID_SIZE); |
1015 | memcpy(bid->data, data, size); |
1016 | bid->size = size; |
1017 | } |
1018 | |
1019 | bool build_id__is_defined(const struct build_id *bid) |
1020 | { |
1021 | return bid && bid->size ? !!memchr_inv(p: bid->data, c: 0, size: bid->size) : false; |
1022 | } |
1023 | |