opjitconv.c revision 8cfa702f803c5ef6a2b062a489a1b2cf66b45b5e
1/**
2 * @file opjitconv.c
3 * Convert a jit dump file to an ELF file
4 *
5 * @remark Copyright 2007 OProfile authors
6 * @remark Read the file COPYING
7 *
8 * @author Jens Wilke
9 * @Modifications Maynard Johnson
10 * @Modifications Daniel Hansel
11 * @Modifications Gisle Dankel
12 *
13 * Copyright IBM Corporation 2007
14 *
15 */
16
17#include "opjitconv.h"
18#include "opd_printf.h"
19#include "op_file.h"
20#include "op_libiberty.h"
21
22#include <dirent.h>
23#include <fnmatch.h>
24#include <errno.h>
25#include <fcntl.h>
26#include <limits.h>
27#include <pwd.h>
28#include <stdint.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <sys/mman.h>
33#include <sys/types.h>
34#include <unistd.h>
35#include <wait.h>
36
37/*
38 * list head.  The linked list is used during parsing (parse_all) to
39 * hold all jitentry elements. After parsing, the program works on the
40 * array structures (entries_symbols_ascending, entries_address_ascending)
41 * and the linked list is not used any more.
42 */
43struct jitentry * jitentry_list = NULL;
44struct jitentry_debug_line * jitentry_debug_line_list = NULL;
45
46/* Global variable for asymbols so we can free the storage later. */
47asymbol ** syms;
48
49/* jit dump header information */
50enum bfd_architecture dump_bfd_arch;
51int dump_bfd_mach;
52char const * dump_bfd_target_name;
53
54/* user information for special user 'oprofile' */
55struct passwd * pw_oprofile;
56
57char sys_cmd_buffer[PATH_MAX + 1];
58
59/* the bfd handle of the ELF file we write */
60bfd * cur_bfd;
61
62/* count of jitentries in the list */
63u32 entry_count;
64/* maximul space in the entry arrays, needed to add entries */
65u32 max_entry_count;
66/* array pointing to all jit entries, sorted by symbol names */
67struct jitentry ** entries_symbols_ascending;
68/* array pointing to all jit entries sorted by address */
69struct jitentry ** entries_address_ascending;
70
71/* debug flag, print some information */
72int debug;
73
74/*
75 *  Front-end processing from this point to end of the source.
76 *    From main(), the general flow is as follows:
77 *      1. Find all anonymous samples directories
78 *      2. Find all JIT dump files
79 *      3. For each JIT dump file:
80 *        3.1 Find matching anon samples dir (from list retrieved in step 1)
81 *        3.2 mmap the JIT dump file
82 *        3.3 Call op_jit_convert to create ELF file if necessary
83 */
84
85/* Callback function used for get_matching_pathnames() call to obtain
86 * matching path names.
87 */
88static void get_pathname(char const * pathname, void * name_list)
89{
90	struct list_head * names = (struct list_head *) name_list;
91	struct pathname * pn = xmalloc(sizeof(struct pathname));
92	pn->name = xstrdup(pathname);
93	list_add(&pn->neighbor, names);
94}
95
96static void delete_pathname(struct pathname * pname)
97{
98	free(pname->name);
99	list_del(&pname->neighbor);
100	free(pname);
101}
102
103
104static void delete_path_names_list(struct list_head * list)
105{
106	struct list_head * pos1, * pos2;
107	list_for_each_safe(pos1, pos2, list) {
108		struct pathname * pname = list_entry(pos1, struct pathname,
109						     neighbor);
110		delete_pathname(pname);
111	}
112}
113
114static int mmap_jitdump(char const * dumpfile,
115	struct op_jitdump_info * file_info)
116{
117	int rc = OP_JIT_CONV_OK;
118	int dumpfd;
119
120	dumpfd = open(dumpfile, O_RDONLY);
121	if (dumpfd < 0) {
122		if (errno == ENOENT)
123			rc = OP_JIT_CONV_NO_DUMPFILE;
124		else
125			rc = OP_JIT_CONV_FAIL;
126		goto out;
127	}
128	rc = fstat(dumpfd, &file_info->dmp_file_stat);
129	if (rc < 0) {
130		perror("opjitconv:fstat on dumpfile");
131		rc = OP_JIT_CONV_FAIL;
132		goto out;
133	}
134	file_info->dmp_file = mmap(0, file_info->dmp_file_stat.st_size,
135				   PROT_READ, MAP_PRIVATE, dumpfd, 0);
136	if (file_info->dmp_file == MAP_FAILED) {
137		perror("opjitconv:mmap\n");
138		rc = OP_JIT_CONV_FAIL;
139	}
140out:
141	return rc;
142}
143
144static char const * find_anon_dir_match(struct list_head * anon_dirs,
145					char const * proc_id)
146{
147	struct list_head * pos;
148	char match_filter[10];
149	snprintf(match_filter, 10, "*/%s.*", proc_id);
150	list_for_each(pos, anon_dirs) {
151		struct pathname * anon_dir =
152			list_entry(pos, struct pathname, neighbor);
153		if (!fnmatch(match_filter, anon_dir->name, 0))
154			return anon_dir->name;
155	}
156	return NULL;
157}
158
159int change_owner(char * path)
160{
161	int rc = OP_JIT_CONV_OK;
162	int fd;
163
164	fd = open(path, 0);
165	if (fd < 0) {
166		printf("opjitconv: File cannot be opened for changing ownership.\n");
167		rc = OP_JIT_CONV_FAIL;
168		goto out;
169	}
170	if (fchown(fd, pw_oprofile->pw_uid, pw_oprofile->pw_gid) != 0) {
171		printf("opjitconv: Changing ownership failed (%s).\n", strerror(errno));
172		close(fd);
173		rc = OP_JIT_CONV_FAIL;
174		goto out;
175	}
176	close(fd);
177
178out:
179	return rc;
180}
181
182/* Copies the given file to the temporary working directory and sets ownership
183 * to 'oprofile:oprofile'.
184 */
185int copy_dumpfile(char const * dumpfile, char * tmp_dumpfile)
186{
187	int rc = OP_JIT_CONV_OK;
188
189	sprintf(sys_cmd_buffer, "/bin/cp -p %s %s", dumpfile, tmp_dumpfile);
190
191	if (system(sys_cmd_buffer) != 0) {
192		printf("opjitconv: Calling system() to copy files failed.\n");
193		rc = OP_JIT_CONV_FAIL;
194		goto out;
195	}
196
197	if (change_owner(tmp_dumpfile) != 0) {
198		printf("opjitconv: Changing ownership of temporary dump file failed.\n");
199		rc = OP_JIT_CONV_FAIL;
200		goto out;
201	}
202
203out:
204	return rc;
205}
206
207/* Copies the created ELF file located in the temporary working directory to the
208 * final destination (i.e. given ELF file name) and sets ownership to the
209 * current user.
210 */
211int copy_elffile(char * elf_file, char * tmp_elffile)
212{
213	int rc = OP_JIT_CONV_OK;
214	int fd;
215
216	sprintf(sys_cmd_buffer, "/bin/cp -p %s %s", tmp_elffile, elf_file);
217	if (system(sys_cmd_buffer) != 0) {
218		printf("opjitconv: Calling system() to copy files failed.\n");
219		rc = OP_JIT_CONV_FAIL;
220		goto out;
221	}
222
223	fd = open(elf_file, 0);
224	if (fd < 0) {
225		printf("opjitconv: File cannot be opened for changing ownership.\n");
226		rc = OP_JIT_CONV_FAIL;
227		goto out;
228	}
229	if (fchown(fd, getuid(), getgid()) != 0) {
230		printf("opjitconv: Changing ownership failed (%s).\n", strerror(errno));
231		close(fd);
232		rc = OP_JIT_CONV_FAIL;
233		goto out;
234	}
235	close(fd);
236
237out:
238	return rc;
239}
240
241/* Look for an anonymous samples directory that matches the process ID
242 * given by the passed JIT dmp_pathname.  If none is found, it's an error
243 * since by agreement, all JIT dump files should be removed every time
244 * the user does --reset.  If we do find the matching samples directory,
245 * we create an ELF file (<proc_id>.jo) and place it in that directory.
246 */
247static int process_jit_dumpfile(char const * dmp_pathname,
248				struct list_head * anon_sample_dirs,
249				unsigned long long start_time,
250				unsigned long long end_time,
251				char * tmp_conv_dir)
252{
253	int result_dir_length, proc_id_length;
254	int rc = OP_JIT_CONV_OK;
255	int jofd;
256	struct stat file_stat;
257	time_t dumpfile_modtime;
258	struct op_jitdump_info dmp_info;
259	char * elf_file = NULL;
260	char * proc_id = NULL;
261	char const * anon_dir;
262	char const * dumpfilename = rindex(dmp_pathname, '/');
263	/* temporary copy of dump file created for conversion step */
264	char * tmp_dumpfile;
265	/* temporary ELF file created during conversion step */
266	char * tmp_elffile;
267
268	verbprintf(debug, "Processing dumpfile %s\n", dmp_pathname);
269
270	/* Check if the dump file is a symbolic link.
271	 * We should not trust symbolic links because we only produce normal dump
272	 * files (no links).
273	 */
274	if (lstat(dmp_pathname, &file_stat) == -1) {
275		printf("opjitconv: lstat for dumpfile failed (%s).\n", strerror(errno));
276		rc = OP_JIT_CONV_FAIL;
277		goto out;
278	}
279	if (S_ISLNK(file_stat.st_mode)) {
280		printf("opjitconv: dumpfile path is corrupt (symbolic links not allowed).\n");
281		rc = OP_JIT_CONV_FAIL;
282		goto out;
283	}
284
285	if (dumpfilename) {
286		size_t tmp_conv_dir_length = strlen(tmp_conv_dir);
287		char const * dot_dump = rindex(++dumpfilename, '.');
288		if (!dot_dump)
289			goto chk_proc_id;
290		proc_id_length = dot_dump - dumpfilename;
291		proc_id = xmalloc(proc_id_length + 1);
292		memcpy(proc_id, dumpfilename, proc_id_length);
293		proc_id[proc_id_length] = '\0';
294		verbprintf(debug, "Found JIT dumpfile for process %s\n",
295			   proc_id);
296
297		tmp_dumpfile = xmalloc(tmp_conv_dir_length + 1 + strlen(dumpfilename) + 1);
298		strncpy(tmp_dumpfile, tmp_conv_dir, tmp_conv_dir_length);
299		tmp_dumpfile[tmp_conv_dir_length] = '\0';
300		strcat(tmp_dumpfile, "/");
301		strcat(tmp_dumpfile, dumpfilename);
302	}
303chk_proc_id:
304	if (!proc_id) {
305		printf("opjitconv: dumpfile path is corrupt.\n");
306		rc = OP_JIT_CONV_FAIL;
307		goto out;
308	}
309	if (!(anon_dir = find_anon_dir_match(anon_sample_dirs, proc_id))) {
310		printf("Possible error: No matching anon samples for %s\n",
311		       dmp_pathname);
312		rc = OP_JIT_CONV_NO_MATCHING_ANON_SAMPLES;
313		goto free_res1;
314	}
315
316	if (copy_dumpfile(dmp_pathname, tmp_dumpfile) != OP_JIT_CONV_OK)
317		goto free_res1;
318
319	if ((rc = mmap_jitdump(tmp_dumpfile, &dmp_info)) == OP_JIT_CONV_OK) {
320		char * anon_path_seg = rindex(anon_dir, '/');
321		if (!anon_path_seg) {
322			printf("opjitconv: Bad path for anon sample: %s\n",
323			       anon_dir);
324			rc = OP_JIT_CONV_FAIL;
325			goto free_res2;
326		}
327		result_dir_length = ++anon_path_seg - anon_dir;
328		/* create final ELF file name */
329		elf_file = xmalloc(result_dir_length +
330				   strlen(proc_id) + strlen(".jo") + 1);
331		strncpy(elf_file, anon_dir, result_dir_length);
332		elf_file[result_dir_length] = '\0';
333		strcat(elf_file, proc_id);
334		strcat(elf_file, ".jo");
335		/* create temporary ELF file name */
336		tmp_elffile = xmalloc(strlen(tmp_conv_dir) + 1 +
337				   strlen(proc_id) + strlen(".jo") + 1);
338		strncpy(tmp_elffile, tmp_conv_dir, strlen(tmp_conv_dir));
339		tmp_elffile[strlen(tmp_conv_dir)] = '\0';
340		strcat(tmp_elffile, "/");
341		strcat(tmp_elffile, proc_id);
342		strcat(tmp_elffile, ".jo");
343
344		// Check if final ELF file exists already
345		jofd = open(elf_file, O_RDONLY);
346		if (jofd < 0)
347			goto create_elf;
348		rc = fstat(jofd, &file_stat);
349		if (rc < 0) {
350			perror("opjitconv:fstat on .jo file");
351			rc = OP_JIT_CONV_FAIL;
352			goto free_res3;
353		}
354		if (dmp_info.dmp_file_stat.st_mtime >
355		    dmp_info.dmp_file_stat.st_ctime)
356			dumpfile_modtime = dmp_info.dmp_file_stat.st_mtime;
357		else
358			dumpfile_modtime = dmp_info.dmp_file_stat.st_ctime;
359
360		/* Final ELF file already exists, so if dumpfile has not been
361		 * modified since the ELF file's mod time, we don't need to
362		 * do ELF creation again.
363		 */
364		if (!(file_stat.st_ctime < dumpfile_modtime ||
365		    file_stat.st_mtime < dumpfile_modtime)) {
366			rc = OP_JIT_CONV_ALREADY_DONE;
367			goto free_res3;
368		}
369
370	create_elf:
371		verbprintf(debug, "Converting %s to %s\n", dmp_pathname,
372			   elf_file);
373		/* Set eGID of the special user 'oprofile'. */
374		if (setegid(pw_oprofile->pw_gid) != 0) {
375			perror("opjitconv: setegid to special user failed");
376			rc = OP_JIT_CONV_FAIL;
377			goto free_res3;
378		}
379		/* Set eUID of the special user 'oprofile'. */
380		if (seteuid(pw_oprofile->pw_uid) != 0) {
381			perror("opjitconv: seteuid to special user failed");
382			rc = OP_JIT_CONV_FAIL;
383			goto free_res3;
384		}
385		/* Convert the dump file as the special user 'oprofile'. */
386		rc = op_jit_convert(dmp_info, tmp_elffile, start_time, end_time);
387		/* Set eUID back to the original user. */
388		if (seteuid(getuid()) != 0) {
389			perror("opjitconv: seteuid to original user failed");
390			rc = OP_JIT_CONV_FAIL;
391			goto free_res3;
392		}
393		/* Set eGID back to the original user. */
394		if (setegid(getgid()) != 0) {
395			perror("opjitconv: setegid to original user failed");
396			rc = OP_JIT_CONV_FAIL;
397			goto free_res3;
398		}
399		rc = copy_elffile(elf_file, tmp_elffile);
400	free_res3:
401		free(elf_file);
402		free(tmp_elffile);
403	free_res2:
404		munmap(dmp_info.dmp_file, dmp_info.dmp_file_stat.st_size);
405	}
406free_res1:
407	free(proc_id);
408	free(tmp_dumpfile);
409out:
410	return rc;
411}
412
413/* If non-NULL value is returned, caller is responsible for freeing memory.*/
414static char * get_procid_from_dirname(char * dirname)
415{
416	char * ret = NULL;
417	if (dirname) {
418		char * proc_id;
419		int proc_id_length;
420		char * fname = rindex(dirname, '/');
421		char const * dot = index(++fname, '.');
422		if (!dot)
423			goto out;
424		proc_id_length = dot - fname;
425		proc_id = xmalloc(proc_id_length + 1);
426		memcpy(proc_id, fname, proc_id_length);
427		proc_id[proc_id_length] = '\0';
428		ret = proc_id;
429	}
430out:
431	return ret;
432}
433static void filter_anon_samples_list(struct list_head * anon_dirs)
434{
435	struct procid {
436		struct procid * next;
437		char * pid;
438	};
439	struct procid * pid_list = NULL;
440	struct procid * id, * nxt;
441	struct list_head * pos1, * pos2;
442	list_for_each_safe(pos1, pos2, anon_dirs) {
443		struct pathname * pname = list_entry(pos1, struct pathname,
444						     neighbor);
445		char * proc_id = get_procid_from_dirname(pname->name);
446		if (proc_id) {
447			int found = 0;
448			for (id = pid_list; id != NULL; id = id->next) {
449				if (!strcmp(id->pid, proc_id)) {
450					/* Already have an entry for this
451					 * process ID, so delete this entry
452					 * from anon_dirs.
453					 */
454					free(pname->name);
455					list_del(&pname->neighbor);
456					free(pname);
457					found = 1;
458				}
459			}
460			if (!found) {
461				struct procid * this_proc =
462					xmalloc(sizeof(struct procid));
463				this_proc->pid = proc_id;
464				this_proc->next = pid_list;
465				pid_list = this_proc;
466			}
467		} else {
468			printf("Unexpected result in processing anon sample"
469			       " directory\n");
470		}
471	}
472	for (id = pid_list; id; id = nxt) {
473		free(id->pid);
474		nxt = id->next;
475		free(id);
476	}
477}
478
479
480static int op_process_jit_dumpfiles(char const * session_dir,
481	unsigned long long start_time, unsigned long long end_time)
482{
483	struct list_head * pos1, * pos2;
484	int rc = OP_JIT_CONV_OK;
485	char jitdumpfile[PATH_MAX + 1];
486	char oprofile_tmp_template[] = "/tmp/oprofile.XXXXXX";
487	char const * jitdump_dir = "/var/lib/oprofile/jitdump/";
488	LIST_HEAD(jd_fnames);
489	char const * anon_dir_filter = "*/{dep}/{anon:anon}/[0-9]*.*";
490	LIST_HEAD(anon_dnames);
491	char const * samples_subdir = "/samples/current";
492	int samples_dir_len = strlen(session_dir) + strlen(samples_subdir);
493	char * samples_dir;
494	/* temporary working directory for dump file conversion step */
495	char * tmp_conv_dir;
496
497	/* Create a temporary working directory used for the conversion step.
498	 */
499	tmp_conv_dir = mkdtemp(oprofile_tmp_template);
500	if (tmp_conv_dir == NULL) {
501		printf("opjitconv: Temporary working directory cannot be created.\n");
502		rc = OP_JIT_CONV_FAIL;
503		goto out;
504	}
505
506	if ((rc = get_matching_pathnames(&jd_fnames, get_pathname,
507		jitdump_dir, "*.dump", NO_RECURSION)) < 0
508			|| list_empty(&jd_fnames))
509		goto rm_tmp;
510
511	/* Get user information (i.e. UID and GID) for special user 'oprofile'.
512	 */
513	pw_oprofile = getpwnam("oprofile");
514	if (pw_oprofile == NULL) {
515		printf("opjitconv: User information for special user oprofile cannot be found.\n");
516		rc = OP_JIT_CONV_FAIL;
517		goto rm_tmp;
518	}
519
520	/* Change ownership of the temporary working directory to prevent other users
521	 * to attack conversion process.
522	 */
523	if (change_owner(tmp_conv_dir) != 0) {
524		printf("opjitconv: Changing ownership of temporary directory failed.\n");
525		rc = OP_JIT_CONV_FAIL;
526		goto rm_tmp;
527	}
528
529	samples_dir = xmalloc(samples_dir_len + 1);
530	sprintf(samples_dir, "%s%s", session_dir, samples_subdir);
531	if (get_matching_pathnames(&anon_dnames, get_pathname,
532				    samples_dir, anon_dir_filter,
533				    MATCH_DIR_ONLY_RECURSION) < 0
534	    || list_empty(&anon_dnames)) {
535		rc = OP_JIT_CONV_NO_ANON_SAMPLES;
536		goto rm_tmp;
537	}
538	/* When using get_matching_pathnames to find anon samples,
539	 * the list that's returned may contain multiple entries for
540	 * one or more processes; e.g.,
541	 *    6868.0x100000.0x103000
542	 *    6868.0xdfe77000.0xdec40000
543	 *    7012.0x100000.0x103000
544	 *    7012.0xdfe77000.0xdec40000
545	 *
546	 * So we must filter the list so there's only one entry per
547	 * process.
548	 */
549	filter_anon_samples_list(&anon_dnames);
550
551	/* get_matching_pathnames returns only filename segment when
552	 * NO_RECURSION is passed, so below, we add back the JIT
553	 * dump directory path to the name.
554	 */
555	list_for_each_safe(pos1, pos2, &jd_fnames) {
556		struct pathname * dmpfile =
557			list_entry(pos1, struct pathname, neighbor);
558		strncpy(jitdumpfile, jitdump_dir, PATH_MAX);
559		strncat(jitdumpfile, dmpfile->name, PATH_MAX);
560		rc = process_jit_dumpfile(jitdumpfile, &anon_dnames,
561					  start_time, end_time, tmp_conv_dir);
562		if (rc == OP_JIT_CONV_FAIL) {
563			verbprintf(debug, "JIT convert error %d\n", rc);
564			goto rm_tmp;
565		}
566		delete_pathname(dmpfile);
567	}
568	delete_path_names_list(&anon_dnames);
569
570rm_tmp:
571	/* Delete temporary working directory with all its files
572	 * (i.e. dump and ELF file).
573	 */
574	sprintf(sys_cmd_buffer, "/bin/rm -rf %s", tmp_conv_dir);
575	if (system(sys_cmd_buffer) != 0) {
576		printf("opjitconv: Removing temporary working directory failed.\n");
577		rc = OP_JIT_CONV_TMPDIR_NOT_REMOVED;
578	}
579
580out:
581	return rc;
582}
583
584int main(int argc, char ** argv)
585{
586	unsigned long long start_time, end_time;
587	char const * session_dir;
588	int rc = 0;
589
590	debug = 0;
591	if (argc > 1 && strcmp(argv[1], "-d") == 0) {
592		debug = 1;
593		argc--;
594		argv++;
595	}
596
597	if (argc != 4) {
598		printf("Usage: opjitconv [-d] <session_dir> <starttime>"
599		       " <endtime>\n");
600		fflush(stdout);
601		rc = EXIT_FAILURE;
602		goto out;
603	}
604
605	session_dir = argv[1];
606	/*
607	 * Check for a maximum of 4096 bytes (Linux path name length limit) decremented
608	 * by 16 bytes (will be used later for appending samples sub directory).
609	 * Integer overflows according to the session dir parameter (user controlled)
610	 * are not possible anymore.
611	 */
612	if (strlen(session_dir) > PATH_MAX - 16) {
613		printf("opjitconv: Path name length limit exceeded for session directory: %s\n", session_dir);
614		rc = EXIT_FAILURE;
615		goto out;
616	}
617
618	start_time = atol(argv[2]);
619	end_time = atol(argv[3]);
620
621	if (start_time > end_time) {
622		rc = EXIT_FAILURE;
623		goto out;
624	}
625	verbprintf(debug, "start time/end time is %llu/%llu\n",
626		   start_time, end_time);
627	rc = op_process_jit_dumpfiles(session_dir, start_time, end_time);
628	if (rc > OP_JIT_CONV_OK) {
629		verbprintf(debug, "opjitconv: Ending with rc = %d. This code"
630			   " is usually OK, but can be useful for debugging"
631			   " purposes.\n", rc);
632		rc = OP_JIT_CONV_OK;
633	}
634	fflush(stdout);
635	if (rc == OP_JIT_CONV_OK)
636		rc = EXIT_SUCCESS;
637	else
638		rc = EXIT_FAILURE;
639out:
640	_exit(rc);
641}
642