xref: /openbmc/linux/tools/perf/util/vdso.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
1  // SPDX-License-Identifier: GPL-2.0
2  #include <errno.h>
3  #include <unistd.h>
4  #include <stdio.h>
5  #include <string.h>
6  #include <sys/types.h>
7  #include <sys/stat.h>
8  #include <fcntl.h>
9  #include <stdlib.h>
10  #include <linux/kernel.h>
11  
12  #include "vdso.h"
13  #include "dso.h"
14  #include <internal/lib.h>
15  #include "map.h"
16  #include "symbol.h"
17  #include "machine.h"
18  #include "thread.h"
19  #include "linux/string.h"
20  #include <linux/zalloc.h>
21  #include "debug.h"
22  
23  /*
24   * Include definition of find_map() also used in perf-read-vdso.c for
25   * building perf-read-vdso32 and perf-read-vdsox32.
26   */
27  #include "find-map.c"
28  
29  #define VDSO__TEMP_FILE_NAME "/tmp/perf-vdso.so-XXXXXX"
30  
31  struct vdso_file {
32  	bool found;
33  	bool error;
34  	char temp_file_name[sizeof(VDSO__TEMP_FILE_NAME)];
35  	const char *dso_name;
36  	const char *read_prog;
37  };
38  
39  struct vdso_info {
40  	struct vdso_file vdso;
41  #if BITS_PER_LONG == 64
42  	struct vdso_file vdso32;
43  	struct vdso_file vdsox32;
44  #endif
45  };
46  
vdso_info__new(void)47  static struct vdso_info *vdso_info__new(void)
48  {
49  	static const struct vdso_info vdso_info_init = {
50  		.vdso    = {
51  			.temp_file_name = VDSO__TEMP_FILE_NAME,
52  			.dso_name = DSO__NAME_VDSO,
53  		},
54  #if BITS_PER_LONG == 64
55  		.vdso32  = {
56  			.temp_file_name = VDSO__TEMP_FILE_NAME,
57  			.dso_name = DSO__NAME_VDSO32,
58  			.read_prog = "perf-read-vdso32",
59  		},
60  		.vdsox32  = {
61  			.temp_file_name = VDSO__TEMP_FILE_NAME,
62  			.dso_name = DSO__NAME_VDSOX32,
63  			.read_prog = "perf-read-vdsox32",
64  		},
65  #endif
66  	};
67  
68  	return memdup(&vdso_info_init, sizeof(vdso_info_init));
69  }
70  
get_file(struct vdso_file * vdso_file)71  static char *get_file(struct vdso_file *vdso_file)
72  {
73  	char *vdso = NULL;
74  	char *buf = NULL;
75  	void *start, *end;
76  	size_t size;
77  	int fd;
78  
79  	if (vdso_file->found)
80  		return vdso_file->temp_file_name;
81  
82  	if (vdso_file->error || find_map(&start, &end, VDSO__MAP_NAME))
83  		return NULL;
84  
85  	size = end - start;
86  
87  	buf = memdup(start, size);
88  	if (!buf)
89  		return NULL;
90  
91  	fd = mkstemp(vdso_file->temp_file_name);
92  	if (fd < 0)
93  		goto out;
94  
95  	if (size == (size_t) write(fd, buf, size))
96  		vdso = vdso_file->temp_file_name;
97  
98  	close(fd);
99  
100   out:
101  	free(buf);
102  
103  	vdso_file->found = (vdso != NULL);
104  	vdso_file->error = !vdso_file->found;
105  	return vdso;
106  }
107  
machine__exit_vdso(struct machine * machine)108  void machine__exit_vdso(struct machine *machine)
109  {
110  	struct vdso_info *vdso_info = machine->vdso_info;
111  
112  	if (!vdso_info)
113  		return;
114  
115  	if (vdso_info->vdso.found)
116  		unlink(vdso_info->vdso.temp_file_name);
117  #if BITS_PER_LONG == 64
118  	if (vdso_info->vdso32.found)
119  		unlink(vdso_info->vdso32.temp_file_name);
120  	if (vdso_info->vdsox32.found)
121  		unlink(vdso_info->vdsox32.temp_file_name);
122  #endif
123  
124  	zfree(&machine->vdso_info);
125  }
126  
__machine__addnew_vdso(struct machine * machine,const char * short_name,const char * long_name)127  static struct dso *__machine__addnew_vdso(struct machine *machine, const char *short_name,
128  					  const char *long_name)
129  {
130  	struct dso *dso;
131  
132  	dso = dso__new(short_name);
133  	if (dso != NULL) {
134  		__dsos__add(&machine->dsos, dso);
135  		dso__set_long_name(dso, long_name, false);
136  		/* Put dso here because __dsos_add already got it */
137  		dso__put(dso);
138  	}
139  
140  	return dso;
141  }
142  
machine__thread_dso_type(struct machine * machine,struct thread * thread)143  static enum dso_type machine__thread_dso_type(struct machine *machine,
144  					      struct thread *thread)
145  {
146  	enum dso_type dso_type = DSO__TYPE_UNKNOWN;
147  	struct map_rb_node *rb_node;
148  
149  	maps__for_each_entry(thread__maps(thread), rb_node) {
150  		struct dso *dso = map__dso(rb_node->map);
151  
152  		if (!dso || dso->long_name[0] != '/')
153  			continue;
154  		dso_type = dso__type(dso, machine);
155  		if (dso_type != DSO__TYPE_UNKNOWN)
156  			break;
157  	}
158  
159  	return dso_type;
160  }
161  
162  #if BITS_PER_LONG == 64
163  
vdso__do_copy_compat(FILE * f,int fd)164  static int vdso__do_copy_compat(FILE *f, int fd)
165  {
166  	char buf[4096];
167  	size_t count;
168  
169  	while (1) {
170  		count = fread(buf, 1, sizeof(buf), f);
171  		if (ferror(f))
172  			return -errno;
173  		if (feof(f))
174  			break;
175  		if (count && writen(fd, buf, count) != (ssize_t)count)
176  			return -errno;
177  	}
178  
179  	return 0;
180  }
181  
vdso__copy_compat(const char * prog,int fd)182  static int vdso__copy_compat(const char *prog, int fd)
183  {
184  	FILE *f;
185  	int err;
186  
187  	f = popen(prog, "r");
188  	if (!f)
189  		return -errno;
190  
191  	err = vdso__do_copy_compat(f, fd);
192  
193  	if (pclose(f) == -1)
194  		return -errno;
195  
196  	return err;
197  }
198  
vdso__create_compat_file(const char * prog,char * temp_name)199  static int vdso__create_compat_file(const char *prog, char *temp_name)
200  {
201  	int fd, err;
202  
203  	fd = mkstemp(temp_name);
204  	if (fd < 0)
205  		return -errno;
206  
207  	err = vdso__copy_compat(prog, fd);
208  
209  	if (close(fd) == -1)
210  		return -errno;
211  
212  	return err;
213  }
214  
vdso__get_compat_file(struct vdso_file * vdso_file)215  static const char *vdso__get_compat_file(struct vdso_file *vdso_file)
216  {
217  	int err;
218  
219  	if (vdso_file->found)
220  		return vdso_file->temp_file_name;
221  
222  	if (vdso_file->error)
223  		return NULL;
224  
225  	err = vdso__create_compat_file(vdso_file->read_prog,
226  				       vdso_file->temp_file_name);
227  	if (err) {
228  		pr_err("%s failed, error %d\n", vdso_file->read_prog, err);
229  		vdso_file->error = true;
230  		return NULL;
231  	}
232  
233  	vdso_file->found = true;
234  
235  	return vdso_file->temp_file_name;
236  }
237  
__machine__findnew_compat(struct machine * machine,struct vdso_file * vdso_file)238  static struct dso *__machine__findnew_compat(struct machine *machine,
239  					     struct vdso_file *vdso_file)
240  {
241  	const char *file_name;
242  	struct dso *dso;
243  
244  	dso = __dsos__find(&machine->dsos, vdso_file->dso_name, true);
245  	if (dso)
246  		goto out;
247  
248  	file_name = vdso__get_compat_file(vdso_file);
249  	if (!file_name)
250  		goto out;
251  
252  	dso = __machine__addnew_vdso(machine, vdso_file->dso_name, file_name);
253  out:
254  	return dso;
255  }
256  
__machine__findnew_vdso_compat(struct machine * machine,struct thread * thread,struct vdso_info * vdso_info,struct dso ** dso)257  static int __machine__findnew_vdso_compat(struct machine *machine,
258  					  struct thread *thread,
259  					  struct vdso_info *vdso_info,
260  					  struct dso **dso)
261  {
262  	enum dso_type dso_type;
263  
264  	dso_type = machine__thread_dso_type(machine, thread);
265  
266  #ifndef HAVE_PERF_READ_VDSO32
267  	if (dso_type == DSO__TYPE_32BIT)
268  		return 0;
269  #endif
270  #ifndef HAVE_PERF_READ_VDSOX32
271  	if (dso_type == DSO__TYPE_X32BIT)
272  		return 0;
273  #endif
274  
275  	switch (dso_type) {
276  	case DSO__TYPE_32BIT:
277  		*dso = __machine__findnew_compat(machine, &vdso_info->vdso32);
278  		return 1;
279  	case DSO__TYPE_X32BIT:
280  		*dso = __machine__findnew_compat(machine, &vdso_info->vdsox32);
281  		return 1;
282  	case DSO__TYPE_UNKNOWN:
283  	case DSO__TYPE_64BIT:
284  	default:
285  		return 0;
286  	}
287  }
288  
289  #endif
290  
machine__find_vdso(struct machine * machine,struct thread * thread)291  static struct dso *machine__find_vdso(struct machine *machine,
292  				      struct thread *thread)
293  {
294  	struct dso *dso = NULL;
295  	enum dso_type dso_type;
296  
297  	dso_type = machine__thread_dso_type(machine, thread);
298  	switch (dso_type) {
299  	case DSO__TYPE_32BIT:
300  		dso = __dsos__find(&machine->dsos, DSO__NAME_VDSO32, true);
301  		if (!dso) {
302  			dso = __dsos__find(&machine->dsos, DSO__NAME_VDSO,
303  					   true);
304  			if (dso && dso_type != dso__type(dso, machine))
305  				dso = NULL;
306  		}
307  		break;
308  	case DSO__TYPE_X32BIT:
309  		dso = __dsos__find(&machine->dsos, DSO__NAME_VDSOX32, true);
310  		break;
311  	case DSO__TYPE_64BIT:
312  	case DSO__TYPE_UNKNOWN:
313  	default:
314  		dso = __dsos__find(&machine->dsos, DSO__NAME_VDSO, true);
315  		break;
316  	}
317  
318  	return dso;
319  }
320  
machine__findnew_vdso(struct machine * machine,struct thread * thread)321  struct dso *machine__findnew_vdso(struct machine *machine,
322  				  struct thread *thread)
323  {
324  	struct vdso_info *vdso_info;
325  	struct dso *dso = NULL;
326  
327  	down_write(&machine->dsos.lock);
328  	if (!machine->vdso_info)
329  		machine->vdso_info = vdso_info__new();
330  
331  	vdso_info = machine->vdso_info;
332  	if (!vdso_info)
333  		goto out_unlock;
334  
335  	dso = machine__find_vdso(machine, thread);
336  	if (dso)
337  		goto out_unlock;
338  
339  #if BITS_PER_LONG == 64
340  	if (__machine__findnew_vdso_compat(machine, thread, vdso_info, &dso))
341  		goto out_unlock;
342  #endif
343  
344  	dso = __dsos__find(&machine->dsos, DSO__NAME_VDSO, true);
345  	if (!dso) {
346  		char *file;
347  
348  		file = get_file(&vdso_info->vdso);
349  		if (file)
350  			dso = __machine__addnew_vdso(machine, DSO__NAME_VDSO, file);
351  	}
352  
353  out_unlock:
354  	dso__get(dso);
355  	up_write(&machine->dsos.lock);
356  	return dso;
357  }
358  
dso__is_vdso(struct dso * dso)359  bool dso__is_vdso(struct dso *dso)
360  {
361  	return !strcmp(dso->short_name, DSO__NAME_VDSO) ||
362  	       !strcmp(dso->short_name, DSO__NAME_VDSO32) ||
363  	       !strcmp(dso->short_name, DSO__NAME_VDSOX32);
364  }
365