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