1/*
2 * build-id.c
3 *
4 * build-id support
5 *
6 * Copyright (C) 2009, 2010 Red Hat Inc.
7 * Copyright (C) 2009, 2010 Arnaldo Carvalho de Melo <acme@redhat.com>
8 */
9#include "util.h"
10#include <stdio.h>
11#include "build-id.h"
12#include "event.h"
13#include "symbol.h"
14#include <linux/kernel.h>
15#include "debug.h"
16#include "session.h"
17#include "tool.h"
18#include "header.h"
19#include "vdso.h"
20
21
22static bool no_buildid_cache;
23
24int build_id__mark_dso_hit(struct perf_tool *tool __maybe_unused,
25			   union perf_event *event,
26			   struct perf_sample *sample,
27			   struct perf_evsel *evsel __maybe_unused,
28			   struct machine *machine)
29{
30	struct addr_location al;
31	u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
32	struct thread *thread = machine__findnew_thread(machine, sample->pid,
33							sample->tid);
34
35	if (thread == NULL) {
36		pr_err("problem processing %d event, skipping it.\n",
37			event->header.type);
38		return -1;
39	}
40
41	thread__find_addr_map(thread, cpumode, MAP__FUNCTION, sample->ip, &al);
42
43	if (al.map != NULL)
44		al.map->dso->hit = 1;
45
46	return 0;
47}
48
49static int perf_event__exit_del_thread(struct perf_tool *tool __maybe_unused,
50				       union perf_event *event,
51				       struct perf_sample *sample
52				       __maybe_unused,
53				       struct machine *machine)
54{
55	struct thread *thread = machine__findnew_thread(machine,
56							event->fork.pid,
57							event->fork.tid);
58
59	dump_printf("(%d:%d):(%d:%d)\n", event->fork.pid, event->fork.tid,
60		    event->fork.ppid, event->fork.ptid);
61
62	if (thread)
63		machine__remove_thread(machine, thread);
64
65	return 0;
66}
67
68struct perf_tool build_id__mark_dso_hit_ops = {
69	.sample	= build_id__mark_dso_hit,
70	.mmap	= perf_event__process_mmap,
71	.mmap2	= perf_event__process_mmap2,
72	.fork	= perf_event__process_fork,
73	.exit	= perf_event__exit_del_thread,
74	.attr		 = perf_event__process_attr,
75	.build_id	 = perf_event__process_build_id,
76};
77
78int build_id__sprintf(const u8 *build_id, int len, char *bf)
79{
80	char *bid = bf;
81	const u8 *raw = build_id;
82	int i;
83
84	for (i = 0; i < len; ++i) {
85		sprintf(bid, "%02x", *raw);
86		++raw;
87		bid += 2;
88	}
89
90	return raw - build_id;
91}
92
93/* asnprintf consolidates asprintf and snprintf */
94static int asnprintf(char **strp, size_t size, const char *fmt, ...)
95{
96	va_list ap;
97	int ret;
98
99	if (!strp)
100		return -EINVAL;
101
102	va_start(ap, fmt);
103	if (*strp)
104		ret = vsnprintf(*strp, size, fmt, ap);
105	else
106		ret = vasprintf(strp, fmt, ap);
107	va_end(ap);
108
109	return ret;
110}
111
112static char *build_id__filename(const char *sbuild_id, char *bf, size_t size)
113{
114	char *tmp = bf;
115	int ret = asnprintf(&bf, size, "%s/.build-id/%.2s/%s", buildid_dir,
116			    sbuild_id, sbuild_id + 2);
117	if (ret < 0 || (tmp && size < (unsigned int)ret))
118		return NULL;
119	return bf;
120}
121
122char *dso__build_id_filename(const struct dso *dso, char *bf, size_t size)
123{
124	char build_id_hex[BUILD_ID_SIZE * 2 + 1];
125
126	if (!dso->has_build_id)
127		return NULL;
128
129	build_id__sprintf(dso->build_id, sizeof(dso->build_id), build_id_hex);
130	return build_id__filename(build_id_hex, bf, size);
131}
132
133#define dsos__for_each_with_build_id(pos, head)	\
134	list_for_each_entry(pos, head, node)	\
135		if (!pos->has_build_id)		\
136			continue;		\
137		else
138
139static int write_buildid(const char *name, size_t name_len, u8 *build_id,
140			 pid_t pid, u16 misc, int fd)
141{
142	int err;
143	struct build_id_event b;
144	size_t len;
145
146	len = name_len + 1;
147	len = PERF_ALIGN(len, NAME_ALIGN);
148
149	memset(&b, 0, sizeof(b));
150	memcpy(&b.build_id, build_id, BUILD_ID_SIZE);
151	b.pid = pid;
152	b.header.misc = misc;
153	b.header.size = sizeof(b) + len;
154
155	err = writen(fd, &b, sizeof(b));
156	if (err < 0)
157		return err;
158
159	return write_padded(fd, name, name_len + 1, len);
160}
161
162static int __dsos__write_buildid_table(struct list_head *head,
163				       struct machine *machine,
164				       pid_t pid, u16 misc, int fd)
165{
166	char nm[PATH_MAX];
167	struct dso *pos;
168
169	dsos__for_each_with_build_id(pos, head) {
170		int err;
171		const char *name;
172		size_t name_len;
173
174		if (!pos->hit)
175			continue;
176
177		if (dso__is_vdso(pos)) {
178			name = pos->short_name;
179			name_len = pos->short_name_len + 1;
180		} else if (dso__is_kcore(pos)) {
181			machine__mmap_name(machine, nm, sizeof(nm));
182			name = nm;
183			name_len = strlen(nm) + 1;
184		} else {
185			name = pos->long_name;
186			name_len = pos->long_name_len + 1;
187		}
188
189		err = write_buildid(name, name_len, pos->build_id,
190				    pid, misc, fd);
191		if (err)
192			return err;
193	}
194
195	return 0;
196}
197
198static int machine__write_buildid_table(struct machine *machine, int fd)
199{
200	int err;
201	u16 kmisc = PERF_RECORD_MISC_KERNEL,
202	    umisc = PERF_RECORD_MISC_USER;
203
204	if (!machine__is_host(machine)) {
205		kmisc = PERF_RECORD_MISC_GUEST_KERNEL;
206		umisc = PERF_RECORD_MISC_GUEST_USER;
207	}
208
209	err = __dsos__write_buildid_table(&machine->kernel_dsos.head, machine,
210					  machine->pid, kmisc, fd);
211	if (err == 0)
212		err = __dsos__write_buildid_table(&machine->user_dsos.head,
213						  machine, machine->pid, umisc,
214						  fd);
215	return err;
216}
217
218int perf_session__write_buildid_table(struct perf_session *session, int fd)
219{
220	struct rb_node *nd;
221	int err = machine__write_buildid_table(&session->machines.host, fd);
222
223	if (err)
224		return err;
225
226	for (nd = rb_first(&session->machines.guests); nd; nd = rb_next(nd)) {
227		struct machine *pos = rb_entry(nd, struct machine, rb_node);
228		err = machine__write_buildid_table(pos, fd);
229		if (err)
230			break;
231	}
232	return err;
233}
234
235static int __dsos__hit_all(struct list_head *head)
236{
237	struct dso *pos;
238
239	list_for_each_entry(pos, head, node)
240		pos->hit = true;
241
242	return 0;
243}
244
245static int machine__hit_all_dsos(struct machine *machine)
246{
247	int err;
248
249	err = __dsos__hit_all(&machine->kernel_dsos.head);
250	if (err)
251		return err;
252
253	return __dsos__hit_all(&machine->user_dsos.head);
254}
255
256int dsos__hit_all(struct perf_session *session)
257{
258	struct rb_node *nd;
259	int err;
260
261	err = machine__hit_all_dsos(&session->machines.host);
262	if (err)
263		return err;
264
265	for (nd = rb_first(&session->machines.guests); nd; nd = rb_next(nd)) {
266		struct machine *pos = rb_entry(nd, struct machine, rb_node);
267
268		err = machine__hit_all_dsos(pos);
269		if (err)
270			return err;
271	}
272
273	return 0;
274}
275
276void disable_buildid_cache(void)
277{
278	no_buildid_cache = true;
279}
280
281static char *build_id_cache__dirname_from_path(const char *name,
282					       bool is_kallsyms, bool is_vdso)
283{
284	char *realname = (char *)name, *filename;
285	bool slash = is_kallsyms || is_vdso;
286
287	if (!slash) {
288		realname = realpath(name, NULL);
289		if (!realname)
290			return NULL;
291	}
292
293	if (asprintf(&filename, "%s%s%s", buildid_dir, slash ? "/" : "",
294		     is_vdso ? DSO__NAME_VDSO : realname) < 0)
295		filename = NULL;
296
297	if (!slash)
298		free(realname);
299
300	return filename;
301}
302
303int build_id_cache__list_build_ids(const char *pathname,
304				   struct strlist **result)
305{
306	struct strlist *list;
307	char *dir_name;
308	DIR *dir;
309	struct dirent *d;
310	int ret = 0;
311
312	list = strlist__new(true, NULL);
313	dir_name = build_id_cache__dirname_from_path(pathname, false, false);
314	if (!list || !dir_name) {
315		ret = -ENOMEM;
316		goto out;
317	}
318
319	/* List up all dirents */
320	dir = opendir(dir_name);
321	if (!dir) {
322		ret = -errno;
323		goto out;
324	}
325
326	while ((d = readdir(dir)) != NULL) {
327		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
328			continue;
329		strlist__add(list, d->d_name);
330	}
331	closedir(dir);
332
333out:
334	free(dir_name);
335	if (ret)
336		strlist__delete(list);
337	else
338		*result = list;
339
340	return ret;
341}
342
343int build_id_cache__add_s(const char *sbuild_id, const char *name,
344			  bool is_kallsyms, bool is_vdso)
345{
346	const size_t size = PATH_MAX;
347	char *realname = NULL, *filename = NULL, *dir_name = NULL,
348	     *linkname = zalloc(size), *targetname, *tmp;
349	int err = -1;
350
351	if (!is_kallsyms) {
352		realname = realpath(name, NULL);
353		if (!realname)
354			goto out_free;
355	}
356
357	dir_name = build_id_cache__dirname_from_path(name, is_kallsyms, is_vdso);
358	if (!dir_name)
359		goto out_free;
360
361	if (mkdir_p(dir_name, 0755))
362		goto out_free;
363
364	if (asprintf(&filename, "%s/%s", dir_name, sbuild_id) < 0) {
365		filename = NULL;
366		goto out_free;
367	}
368
369	if (access(filename, F_OK)) {
370		if (is_kallsyms) {
371			 if (copyfile("/proc/kallsyms", filename))
372				goto out_free;
373		} else if (link(realname, filename) && errno != EEXIST &&
374				copyfile(name, filename))
375			goto out_free;
376	}
377
378	if (!build_id__filename(sbuild_id, linkname, size))
379		goto out_free;
380	tmp = strrchr(linkname, '/');
381	*tmp = '\0';
382
383	if (access(linkname, X_OK) && mkdir_p(linkname, 0755))
384		goto out_free;
385
386	*tmp = '/';
387	targetname = filename + strlen(buildid_dir) - 5;
388	memcpy(targetname, "../..", 5);
389
390	if (symlink(targetname, linkname) == 0)
391		err = 0;
392out_free:
393	if (!is_kallsyms)
394		free(realname);
395	free(filename);
396	free(dir_name);
397	free(linkname);
398	return err;
399}
400
401static int build_id_cache__add_b(const u8 *build_id, size_t build_id_size,
402				 const char *name, bool is_kallsyms,
403				 bool is_vdso)
404{
405	char sbuild_id[BUILD_ID_SIZE * 2 + 1];
406
407	build_id__sprintf(build_id, build_id_size, sbuild_id);
408
409	return build_id_cache__add_s(sbuild_id, name, is_kallsyms, is_vdso);
410}
411
412bool build_id_cache__cached(const char *sbuild_id)
413{
414	bool ret = false;
415	char *filename = build_id__filename(sbuild_id, NULL, 0);
416
417	if (filename && !access(filename, F_OK))
418		ret = true;
419	free(filename);
420
421	return ret;
422}
423
424int build_id_cache__remove_s(const char *sbuild_id)
425{
426	const size_t size = PATH_MAX;
427	char *filename = zalloc(size),
428	     *linkname = zalloc(size), *tmp;
429	int err = -1;
430
431	if (filename == NULL || linkname == NULL)
432		goto out_free;
433
434	if (!build_id__filename(sbuild_id, linkname, size))
435		goto out_free;
436
437	if (access(linkname, F_OK))
438		goto out_free;
439
440	if (readlink(linkname, filename, size - 1) < 0)
441		goto out_free;
442
443	if (unlink(linkname))
444		goto out_free;
445
446	/*
447	 * Since the link is relative, we must make it absolute:
448	 */
449	tmp = strrchr(linkname, '/') + 1;
450	snprintf(tmp, size - (tmp - linkname), "%s", filename);
451
452	if (unlink(linkname))
453		goto out_free;
454
455	err = 0;
456out_free:
457	free(filename);
458	free(linkname);
459	return err;
460}
461
462static int dso__cache_build_id(struct dso *dso, struct machine *machine)
463{
464	bool is_kallsyms = dso->kernel && dso->long_name[0] != '/';
465	bool is_vdso = dso__is_vdso(dso);
466	const char *name = dso->long_name;
467	char nm[PATH_MAX];
468
469	if (dso__is_kcore(dso)) {
470		is_kallsyms = true;
471		machine__mmap_name(machine, nm, sizeof(nm));
472		name = nm;
473	}
474	return build_id_cache__add_b(dso->build_id, sizeof(dso->build_id), name,
475				     is_kallsyms, is_vdso);
476}
477
478static int __dsos__cache_build_ids(struct list_head *head,
479				   struct machine *machine)
480{
481	struct dso *pos;
482	int err = 0;
483
484	dsos__for_each_with_build_id(pos, head)
485		if (dso__cache_build_id(pos, machine))
486			err = -1;
487
488	return err;
489}
490
491static int machine__cache_build_ids(struct machine *machine)
492{
493	int ret = __dsos__cache_build_ids(&machine->kernel_dsos.head, machine);
494	ret |= __dsos__cache_build_ids(&machine->user_dsos.head, machine);
495	return ret;
496}
497
498int perf_session__cache_build_ids(struct perf_session *session)
499{
500	struct rb_node *nd;
501	int ret;
502
503	if (no_buildid_cache)
504		return 0;
505
506	if (mkdir(buildid_dir, 0755) != 0 && errno != EEXIST)
507		return -1;
508
509	ret = machine__cache_build_ids(&session->machines.host);
510
511	for (nd = rb_first(&session->machines.guests); nd; nd = rb_next(nd)) {
512		struct machine *pos = rb_entry(nd, struct machine, rb_node);
513		ret |= machine__cache_build_ids(pos);
514	}
515	return ret ? -1 : 0;
516}
517
518static bool machine__read_build_ids(struct machine *machine, bool with_hits)
519{
520	bool ret;
521
522	ret  = __dsos__read_build_ids(&machine->kernel_dsos.head, with_hits);
523	ret |= __dsos__read_build_ids(&machine->user_dsos.head, with_hits);
524	return ret;
525}
526
527bool perf_session__read_build_ids(struct perf_session *session, bool with_hits)
528{
529	struct rb_node *nd;
530	bool ret = machine__read_build_ids(&session->machines.host, with_hits);
531
532	for (nd = rb_first(&session->machines.guests); nd; nd = rb_next(nd)) {
533		struct machine *pos = rb_entry(nd, struct machine, rb_node);
534		ret |= machine__read_build_ids(pos, with_hits);
535	}
536
537	return ret;
538}
539