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