/** * @file opjitconv.c * Convert a jit dump file to an ELF file * * @remark Copyright 2007 OProfile authors * @remark Read the file COPYING * * @author Jens Wilke * @Modifications Maynard Johnson * @Modifications Daniel Hansel * @Modifications Gisle Dankel * * Copyright IBM Corporation 2007 * */ #include "opjitconv.h" #include "opd_printf.h" #include "op_file.h" #include "op_libiberty.h" #include <dirent.h> #include <fnmatch.h> #include <errno.h> #include <fcntl.h> #include <limits.h> #include <pwd.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #include <wait.h> /* * list head. The linked list is used during parsing (parse_all) to * hold all jitentry elements. After parsing, the program works on the * array structures (entries_symbols_ascending, entries_address_ascending) * and the linked list is not used any more. */ struct jitentry * jitentry_list = NULL; struct jitentry_debug_line * jitentry_debug_line_list = NULL; /* Global variable for asymbols so we can free the storage later. */ asymbol ** syms; /* jit dump header information */ enum bfd_architecture dump_bfd_arch; int dump_bfd_mach; char const * dump_bfd_target_name; /* user information for special user 'oprofile' */ struct passwd * pw_oprofile; char sys_cmd_buffer[PATH_MAX + 1]; /* the bfd handle of the ELF file we write */ bfd * cur_bfd; /* count of jitentries in the list */ u32 entry_count; /* maximul space in the entry arrays, needed to add entries */ u32 max_entry_count; /* array pointing to all jit entries, sorted by symbol names */ struct jitentry ** entries_symbols_ascending; /* array pointing to all jit entries sorted by address */ struct jitentry ** entries_address_ascending; /* debug flag, print some information */ int debug; /* * Front-end processing from this point to end of the source. * From main(), the general flow is as follows: * 1. Find all anonymous samples directories * 2. Find all JIT dump files * 3. For each JIT dump file: * 3.1 Find matching anon samples dir (from list retrieved in step 1) * 3.2 mmap the JIT dump file * 3.3 Call op_jit_convert to create ELF file if necessary */ /* Callback function used for get_matching_pathnames() call to obtain * matching path names. */ static void get_pathname(char const * pathname, void * name_list) { struct list_head * names = (struct list_head *) name_list; struct pathname * pn = xmalloc(sizeof(struct pathname)); pn->name = xstrdup(pathname); list_add(&pn->neighbor, names); } static void delete_pathname(struct pathname * pname) { free(pname->name); list_del(&pname->neighbor); free(pname); } static void delete_path_names_list(struct list_head * list) { struct list_head * pos1, * pos2; list_for_each_safe(pos1, pos2, list) { struct pathname * pname = list_entry(pos1, struct pathname, neighbor); delete_pathname(pname); } } static int mmap_jitdump(char const * dumpfile, struct op_jitdump_info * file_info) { int rc = OP_JIT_CONV_OK; int dumpfd; dumpfd = open(dumpfile, O_RDONLY); if (dumpfd < 0) { if (errno == ENOENT) rc = OP_JIT_CONV_NO_DUMPFILE; else rc = OP_JIT_CONV_FAIL; goto out; } rc = fstat(dumpfd, &file_info->dmp_file_stat); if (rc < 0) { perror("opjitconv:fstat on dumpfile"); rc = OP_JIT_CONV_FAIL; goto out; } file_info->dmp_file = mmap(0, file_info->dmp_file_stat.st_size, PROT_READ, MAP_PRIVATE, dumpfd, 0); if (file_info->dmp_file == MAP_FAILED) { perror("opjitconv:mmap\n"); rc = OP_JIT_CONV_FAIL; } out: return rc; } static char const * find_anon_dir_match(struct list_head * anon_dirs, char const * proc_id) { struct list_head * pos; char match_filter[10]; snprintf(match_filter, 10, "*/%s.*", proc_id); list_for_each(pos, anon_dirs) { struct pathname * anon_dir = list_entry(pos, struct pathname, neighbor); if (!fnmatch(match_filter, anon_dir->name, 0)) return anon_dir->name; } return NULL; } int change_owner(char * path) { int rc = OP_JIT_CONV_OK; int fd; fd = open(path, 0); if (fd < 0) { printf("opjitconv: File cannot be opened for changing ownership.\n"); rc = OP_JIT_CONV_FAIL; goto out; } if (fchown(fd, pw_oprofile->pw_uid, pw_oprofile->pw_gid) != 0) { printf("opjitconv: Changing ownership failed (%s).\n", strerror(errno)); close(fd); rc = OP_JIT_CONV_FAIL; goto out; } close(fd); out: return rc; } /* Copies the given file to the temporary working directory and sets ownership * to 'oprofile:oprofile'. */ int copy_dumpfile(char const * dumpfile, char * tmp_dumpfile) { int rc = OP_JIT_CONV_OK; sprintf(sys_cmd_buffer, "/bin/cp -p %s %s", dumpfile, tmp_dumpfile); if (system(sys_cmd_buffer) != 0) { printf("opjitconv: Calling system() to copy files failed.\n"); rc = OP_JIT_CONV_FAIL; goto out; } if (change_owner(tmp_dumpfile) != 0) { printf("opjitconv: Changing ownership of temporary dump file failed.\n"); rc = OP_JIT_CONV_FAIL; goto out; } out: return rc; } /* Copies the created ELF file located in the temporary working directory to the * final destination (i.e. given ELF file name) and sets ownership to the * current user. */ int copy_elffile(char * elf_file, char * tmp_elffile) { int rc = OP_JIT_CONV_OK; int fd; sprintf(sys_cmd_buffer, "/bin/cp -p %s %s", tmp_elffile, elf_file); if (system(sys_cmd_buffer) != 0) { printf("opjitconv: Calling system() to copy files failed.\n"); rc = OP_JIT_CONV_FAIL; goto out; } fd = open(elf_file, 0); if (fd < 0) { printf("opjitconv: File cannot be opened for changing ownership.\n"); rc = OP_JIT_CONV_FAIL; goto out; } if (fchown(fd, getuid(), getgid()) != 0) { printf("opjitconv: Changing ownership failed (%s).\n", strerror(errno)); close(fd); rc = OP_JIT_CONV_FAIL; goto out; } close(fd); out: return rc; } /* Look for an anonymous samples directory that matches the process ID * given by the passed JIT dmp_pathname. If none is found, it's an error * since by agreement, all JIT dump files should be removed every time * the user does --reset. If we do find the matching samples directory, * we create an ELF file (<proc_id>.jo) and place it in that directory. */ static int process_jit_dumpfile(char const * dmp_pathname, struct list_head * anon_sample_dirs, unsigned long long start_time, unsigned long long end_time, char * tmp_conv_dir) { int result_dir_length, proc_id_length; int rc = OP_JIT_CONV_OK; int jofd; struct stat file_stat; time_t dumpfile_modtime; struct op_jitdump_info dmp_info; char * elf_file = NULL; char * proc_id = NULL; char const * anon_dir; char const * dumpfilename = rindex(dmp_pathname, '/'); /* temporary copy of dump file created for conversion step */ char * tmp_dumpfile; /* temporary ELF file created during conversion step */ char * tmp_elffile; verbprintf(debug, "Processing dumpfile %s\n", dmp_pathname); /* Check if the dump file is a symbolic link. * We should not trust symbolic links because we only produce normal dump * files (no links). */ if (lstat(dmp_pathname, &file_stat) == -1) { printf("opjitconv: lstat for dumpfile failed (%s).\n", strerror(errno)); rc = OP_JIT_CONV_FAIL; goto out; } if (S_ISLNK(file_stat.st_mode)) { printf("opjitconv: dumpfile path is corrupt (symbolic links not allowed).\n"); rc = OP_JIT_CONV_FAIL; goto out; } if (dumpfilename) { size_t tmp_conv_dir_length = strlen(tmp_conv_dir); char const * dot_dump = rindex(++dumpfilename, '.'); if (!dot_dump) goto chk_proc_id; proc_id_length = dot_dump - dumpfilename; proc_id = xmalloc(proc_id_length + 1); memcpy(proc_id, dumpfilename, proc_id_length); proc_id[proc_id_length] = '\0'; verbprintf(debug, "Found JIT dumpfile for process %s\n", proc_id); tmp_dumpfile = xmalloc(tmp_conv_dir_length + 1 + strlen(dumpfilename) + 1); strncpy(tmp_dumpfile, tmp_conv_dir, tmp_conv_dir_length); tmp_dumpfile[tmp_conv_dir_length] = '\0'; strcat(tmp_dumpfile, "/"); strcat(tmp_dumpfile, dumpfilename); } chk_proc_id: if (!proc_id) { printf("opjitconv: dumpfile path is corrupt.\n"); rc = OP_JIT_CONV_FAIL; goto out; } if (!(anon_dir = find_anon_dir_match(anon_sample_dirs, proc_id))) { printf("Possible error: No matching anon samples for %s\n", dmp_pathname); rc = OP_JIT_CONV_NO_MATCHING_ANON_SAMPLES; goto free_res1; } if (copy_dumpfile(dmp_pathname, tmp_dumpfile) != OP_JIT_CONV_OK) goto free_res1; if ((rc = mmap_jitdump(tmp_dumpfile, &dmp_info)) == OP_JIT_CONV_OK) { char * anon_path_seg = rindex(anon_dir, '/'); if (!anon_path_seg) { printf("opjitconv: Bad path for anon sample: %s\n", anon_dir); rc = OP_JIT_CONV_FAIL; goto free_res2; } result_dir_length = ++anon_path_seg - anon_dir; /* create final ELF file name */ elf_file = xmalloc(result_dir_length + strlen(proc_id) + strlen(".jo") + 1); strncpy(elf_file, anon_dir, result_dir_length); elf_file[result_dir_length] = '\0'; strcat(elf_file, proc_id); strcat(elf_file, ".jo"); /* create temporary ELF file name */ tmp_elffile = xmalloc(strlen(tmp_conv_dir) + 1 + strlen(proc_id) + strlen(".jo") + 1); strncpy(tmp_elffile, tmp_conv_dir, strlen(tmp_conv_dir)); tmp_elffile[strlen(tmp_conv_dir)] = '\0'; strcat(tmp_elffile, "/"); strcat(tmp_elffile, proc_id); strcat(tmp_elffile, ".jo"); // Check if final ELF file exists already jofd = open(elf_file, O_RDONLY); if (jofd < 0) goto create_elf; rc = fstat(jofd, &file_stat); if (rc < 0) { perror("opjitconv:fstat on .jo file"); rc = OP_JIT_CONV_FAIL; goto free_res3; } if (dmp_info.dmp_file_stat.st_mtime > dmp_info.dmp_file_stat.st_ctime) dumpfile_modtime = dmp_info.dmp_file_stat.st_mtime; else dumpfile_modtime = dmp_info.dmp_file_stat.st_ctime; /* Final ELF file already exists, so if dumpfile has not been * modified since the ELF file's mod time, we don't need to * do ELF creation again. */ if (!(file_stat.st_ctime < dumpfile_modtime || file_stat.st_mtime < dumpfile_modtime)) { rc = OP_JIT_CONV_ALREADY_DONE; goto free_res3; } create_elf: verbprintf(debug, "Converting %s to %s\n", dmp_pathname, elf_file); /* Set eGID of the special user 'oprofile'. */ if (setegid(pw_oprofile->pw_gid) != 0) { perror("opjitconv: setegid to special user failed"); rc = OP_JIT_CONV_FAIL; goto free_res3; } /* Set eUID of the special user 'oprofile'. */ if (seteuid(pw_oprofile->pw_uid) != 0) { perror("opjitconv: seteuid to special user failed"); rc = OP_JIT_CONV_FAIL; goto free_res3; } /* Convert the dump file as the special user 'oprofile'. */ rc = op_jit_convert(dmp_info, tmp_elffile, start_time, end_time); /* Set eUID back to the original user. */ if (seteuid(getuid()) != 0) { perror("opjitconv: seteuid to original user failed"); rc = OP_JIT_CONV_FAIL; goto free_res3; } /* Set eGID back to the original user. */ if (setegid(getgid()) != 0) { perror("opjitconv: setegid to original user failed"); rc = OP_JIT_CONV_FAIL; goto free_res3; } rc = copy_elffile(elf_file, tmp_elffile); free_res3: free(elf_file); free(tmp_elffile); free_res2: munmap(dmp_info.dmp_file, dmp_info.dmp_file_stat.st_size); } free_res1: free(proc_id); free(tmp_dumpfile); out: return rc; } /* If non-NULL value is returned, caller is responsible for freeing memory.*/ static char * get_procid_from_dirname(char * dirname) { char * ret = NULL; if (dirname) { char * proc_id; int proc_id_length; char * fname = rindex(dirname, '/'); char const * dot = index(++fname, '.'); if (!dot) goto out; proc_id_length = dot - fname; proc_id = xmalloc(proc_id_length + 1); memcpy(proc_id, fname, proc_id_length); proc_id[proc_id_length] = '\0'; ret = proc_id; } out: return ret; } static void filter_anon_samples_list(struct list_head * anon_dirs) { struct procid { struct procid * next; char * pid; }; struct procid * pid_list = NULL; struct procid * id, * nxt; struct list_head * pos1, * pos2; list_for_each_safe(pos1, pos2, anon_dirs) { struct pathname * pname = list_entry(pos1, struct pathname, neighbor); char * proc_id = get_procid_from_dirname(pname->name); if (proc_id) { int found = 0; for (id = pid_list; id != NULL; id = id->next) { if (!strcmp(id->pid, proc_id)) { /* Already have an entry for this * process ID, so delete this entry * from anon_dirs. */ free(pname->name); list_del(&pname->neighbor); free(pname); found = 1; } } if (!found) { struct procid * this_proc = xmalloc(sizeof(struct procid)); this_proc->pid = proc_id; this_proc->next = pid_list; pid_list = this_proc; } } else { printf("Unexpected result in processing anon sample" " directory\n"); } } for (id = pid_list; id; id = nxt) { free(id->pid); nxt = id->next; free(id); } } static int op_process_jit_dumpfiles(char const * session_dir, unsigned long long start_time, unsigned long long end_time) { struct list_head * pos1, * pos2; int rc = OP_JIT_CONV_OK; char jitdumpfile[PATH_MAX + 1]; char oprofile_tmp_template[] = "/tmp/oprofile.XXXXXX"; char const * jitdump_dir = "/var/lib/oprofile/jitdump/"; LIST_HEAD(jd_fnames); char const * anon_dir_filter = "*/{dep}/{anon:anon}/[0-9]*.*"; LIST_HEAD(anon_dnames); char const * samples_subdir = "/samples/current"; int samples_dir_len = strlen(session_dir) + strlen(samples_subdir); char * samples_dir; /* temporary working directory for dump file conversion step */ char * tmp_conv_dir; /* Create a temporary working directory used for the conversion step. */ tmp_conv_dir = mkdtemp(oprofile_tmp_template); if (tmp_conv_dir == NULL) { printf("opjitconv: Temporary working directory cannot be created.\n"); rc = OP_JIT_CONV_FAIL; goto out; } if ((rc = get_matching_pathnames(&jd_fnames, get_pathname, jitdump_dir, "*.dump", NO_RECURSION)) < 0 || list_empty(&jd_fnames)) goto rm_tmp; /* Get user information (i.e. UID and GID) for special user 'oprofile'. */ pw_oprofile = getpwnam("oprofile"); if (pw_oprofile == NULL) { printf("opjitconv: User information for special user oprofile cannot be found.\n"); rc = OP_JIT_CONV_FAIL; goto rm_tmp; } /* Change ownership of the temporary working directory to prevent other users * to attack conversion process. */ if (change_owner(tmp_conv_dir) != 0) { printf("opjitconv: Changing ownership of temporary directory failed.\n"); rc = OP_JIT_CONV_FAIL; goto rm_tmp; } samples_dir = xmalloc(samples_dir_len + 1); sprintf(samples_dir, "%s%s", session_dir, samples_subdir); if (get_matching_pathnames(&anon_dnames, get_pathname, samples_dir, anon_dir_filter, MATCH_DIR_ONLY_RECURSION) < 0 || list_empty(&anon_dnames)) { rc = OP_JIT_CONV_NO_ANON_SAMPLES; goto rm_tmp; } /* When using get_matching_pathnames to find anon samples, * the list that's returned may contain multiple entries for * one or more processes; e.g., * 6868.0x100000.0x103000 * 6868.0xdfe77000.0xdec40000 * 7012.0x100000.0x103000 * 7012.0xdfe77000.0xdec40000 * * So we must filter the list so there's only one entry per * process. */ filter_anon_samples_list(&anon_dnames); /* get_matching_pathnames returns only filename segment when * NO_RECURSION is passed, so below, we add back the JIT * dump directory path to the name. */ list_for_each_safe(pos1, pos2, &jd_fnames) { struct pathname * dmpfile = list_entry(pos1, struct pathname, neighbor); strncpy(jitdumpfile, jitdump_dir, PATH_MAX); strncat(jitdumpfile, dmpfile->name, PATH_MAX); rc = process_jit_dumpfile(jitdumpfile, &anon_dnames, start_time, end_time, tmp_conv_dir); if (rc == OP_JIT_CONV_FAIL) { verbprintf(debug, "JIT convert error %d\n", rc); goto rm_tmp; } delete_pathname(dmpfile); } delete_path_names_list(&anon_dnames); rm_tmp: /* Delete temporary working directory with all its files * (i.e. dump and ELF file). */ sprintf(sys_cmd_buffer, "/bin/rm -rf %s", tmp_conv_dir); if (system(sys_cmd_buffer) != 0) { printf("opjitconv: Removing temporary working directory failed.\n"); rc = OP_JIT_CONV_TMPDIR_NOT_REMOVED; } out: return rc; } int main(int argc, char ** argv) { unsigned long long start_time, end_time; char const * session_dir; int rc = 0; debug = 0; if (argc > 1 && strcmp(argv[1], "-d") == 0) { debug = 1; argc--; argv++; } if (argc != 4) { printf("Usage: opjitconv [-d] <session_dir> <starttime>" " <endtime>\n"); fflush(stdout); rc = EXIT_FAILURE; goto out; } session_dir = argv[1]; /* * Check for a maximum of 4096 bytes (Linux path name length limit) decremented * by 16 bytes (will be used later for appending samples sub directory). * Integer overflows according to the session dir parameter (user controlled) * are not possible anymore. */ if (strlen(session_dir) > PATH_MAX - 16) { printf("opjitconv: Path name length limit exceeded for session directory: %s\n", session_dir); rc = EXIT_FAILURE; goto out; } start_time = atol(argv[2]); end_time = atol(argv[3]); if (start_time > end_time) { rc = EXIT_FAILURE; goto out; } verbprintf(debug, "start time/end time is %llu/%llu\n", start_time, end_time); rc = op_process_jit_dumpfiles(session_dir, start_time, end_time); if (rc > OP_JIT_CONV_OK) { verbprintf(debug, "opjitconv: Ending with rc = %d. This code" " is usually OK, but can be useful for debugging" " purposes.\n", rc); rc = OP_JIT_CONV_OK; } fflush(stdout); if (rc == OP_JIT_CONV_OK) rc = EXIT_SUCCESS; else rc = EXIT_FAILURE; out: _exit(rc); }