/**
* @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);
}