blob: ac957ab3170052e7aaa573ae5277eefceaa74423 [file] [log] [blame]
Mike Dodd8cfa7022010-11-17 11:12:26 -08001/**
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}