/** * @file jitsymbol.c * Handle symbols found in jitted code dump * * @remark Copyright 2007 OProfile authors * @remark Read the file COPYING * * @author Jens Wilke * @Modifications Maynard Johnson * @Modifications Philippe Elie * @Modifications Daniel Hansel * * Copyright IBM Corporation 2007 * */ #include "opjitconv.h" #include "opd_printf.h" #include "op_libiberty.h" #include "op_types.h" #include <stddef.h> #include <stdlib.h> #include <stdint.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <limits.h> typedef int (*compare_symbol)(void const *, void const *); /* count the entries in the jitentry_list */ static u32 count_entries(void) { struct jitentry const * entry; u32 cnt = 0; for (entry = jitentry_list; entry; entry = entry->next) cnt++; return cnt; } static void fill_entry_array(struct jitentry * entries[]) { int i = 0; struct jitentry * entry; for (entry = jitentry_list; entry; entry = entry->next) entries[i++] = entry; } /* create an array pointing to the jitentry structures which is sorted * according to the comparator rule given by parameter compar */ static struct jitentry ** create_sorted_array(compare_symbol compare) { struct jitentry ** array = xmalloc(sizeof(struct jitentry *) * entry_count); fill_entry_array(array); qsort(array, entry_count, sizeof(struct jitentry *), compare); return array; } /* comparator method for qsort which sorts jitentries by symbol_name */ static int cmp_symbolname(void const * a, void const * b) { struct jitentry * a0 = *(struct jitentry **) a; struct jitentry * b0 = *(struct jitentry **) b; return strcmp(a0->symbol_name, b0->symbol_name); } /* comparator method for qsort which sorts jitentries by address */ static int cmp_address(void const * a, void const * b) { struct jitentry * a0 = *(struct jitentry **) a; struct jitentry * b0 = *(struct jitentry **) b; if (a0->vma < b0->vma) return -1; if (a0->vma == b0->vma) return 0; return 1; } /* resort address_ascending array */ static void resort_address(void) { u32 i; qsort(entries_address_ascending, entry_count, sizeof(struct jitentry *), cmp_address); // lower entry_count if entries are invalidated for (i = 0; i < entry_count; ++i) { if (entries_address_ascending[i]->vma) break; } if (i) { entry_count -= i; memmove(&entries_address_ascending[0], &entries_address_ascending[i], sizeof(struct jitentry *) * entry_count); } } /* Copy address_ascending array to entries_symbols_ascending and resort it. */ static void resort_symbol(void) { memcpy(entries_symbols_ascending, entries_address_ascending, sizeof(struct jitentry *) * entry_count); qsort(entries_symbols_ascending, entry_count, sizeof(struct jitentry *), cmp_symbolname); } /* allocate, populate and sort the jitentry arrays */ void create_arrays(void) { max_entry_count = entry_count = count_entries(); entries_symbols_ascending = create_sorted_array(cmp_symbolname); entries_address_ascending = create_sorted_array(cmp_address); } /* add a new create jitentry to the array. mallocs new arrays if space is * needed */ static void insert_entry(struct jitentry * entry) { if (entry_count == max_entry_count) { if (max_entry_count < UINT32_MAX - 18) max_entry_count += 18; else if (max_entry_count < UINT32_MAX) max_entry_count += 1; else { fprintf(stderr, "Amount of JIT dump file entries is too large.\n"); exit(EXIT_FAILURE); } entries_symbols_ascending = (struct jitentry **) xrealloc(entries_symbols_ascending, sizeof(struct jitentry *) * max_entry_count); entries_address_ascending = (struct jitentry **) xrealloc(entries_address_ascending, sizeof(struct jitentry *) * max_entry_count); } entries_address_ascending[entry_count++] = entry; } /* add a suffix to the name to differenciate it */ static char * replacement_name(char * s, int i) { int cnt = 1; int j = i; char * res; while ((j = j/10)) cnt++; cnt += 2 + strlen(s); res = xmalloc(cnt); snprintf(res, cnt, "%s~%i", s, i); return res; } /* * Mark the entry so it is not included in the ELF file. We do this by * writing a 0 address as magic vma and sorting * it out later */ static void invalidate_entry(struct jitentry * e) { verbprintf(debug, "invalidate_entry: addr=%llx, name=%s\n", e->vma, e->symbol_name); e->vma = 0; } /* * Invalidate all symbols that are not alive at sampling start time. */ static void invalidate_earlybirds(unsigned long long start_time) { u32 i; int flag; struct jitentry * a; flag = 0; for (i = 0; i < entry_count; i++) { a = entries_address_ascending[i]; if (a->life_end < start_time) { invalidate_entry(a); flag = 1; } } if (flag) { resort_address(); resort_symbol(); } } /* select the symbol with the longest life time in the index range */ static int select_one(int start_idx, int end_idx) { int i; int candidate = OP_JIT_CONV_FAIL; unsigned long long lifetime = 0; unsigned long long x; struct jitentry const * e; for (i = start_idx; i <= end_idx; i++) { e = entries_address_ascending[i]; x = e->life_end - e->life_start; if (candidate == -1 || x > lifetime) { candidate = i; lifetime = x; } } return candidate; } /* * We decided to keep one entry in favor of the other. Instead of dropping * the overlapping entry we split or truncate it to not overlap any more. * * Looking on the address regions, we may have the following situation: * * split: |------------| * keep: |---| * * The split entry may be splitted in a left part and a right part. E.g.: * * split0/1: |--| |---| * keep: |---| * * However, both parts may or may not exist. */ static void split_entry(struct jitentry * split, struct jitentry const * keep) { unsigned long long start_addr_keep = keep->vma; unsigned long long end_addr_keep = keep->vma + keep->code_size; unsigned long long end_addr_split = split->vma + split->code_size; unsigned long long start_addr_split = split->vma; // do we need a right part? if (end_addr_split > end_addr_keep) { struct jitentry * new_entry = xcalloc(1, sizeof(struct jitentry)); char * s = NULL; /* Check for max. length to avoid possible integer overflow. */ if (strlen(split->symbol_name) > SIZE_MAX - 3) { fprintf(stderr, "Length of symbol name is too large.\n"); exit(EXIT_FAILURE); } else { s = xmalloc(strlen(split->symbol_name) + 3); strcpy(s, split->symbol_name); strcat(s, "#1"); } new_entry->vma = end_addr_keep; new_entry->code_size = end_addr_split - end_addr_keep; new_entry->symbol_name = s; new_entry->sym_name_malloced = 1; new_entry->life_start = split->life_start; new_entry->life_end = split->life_end; // the right part does not have an associated code, because we // don't know whether the split part begins at an opcode new_entry->code = NULL; verbprintf(debug, "split right (new) name=%s, start=%llx," " end=%llx\n", new_entry->symbol_name, new_entry->vma, new_entry->vma + new_entry->code_size); insert_entry(new_entry); } // do we need a left part? if (start_addr_split < start_addr_keep) { char * s = NULL; /* Check for max. length to avoid possible integer overflow. */ if (strlen(split->symbol_name) > SIZE_MAX - 3) { fprintf(stderr, "Length of symbol name is too large.\n"); exit(EXIT_FAILURE); } else { s = xmalloc(strlen(split->symbol_name) + 3); strcpy(s, split->symbol_name); strcat(s, "#0"); } split->code_size = start_addr_keep - start_addr_split; if (split->sym_name_malloced) free(split->symbol_name); split->symbol_name = s; split->sym_name_malloced = 1; verbprintf(debug, "split left name=%s, start=%llx, end=%llx\n", split->symbol_name, split->vma, split->vma + split->code_size); } else { invalidate_entry(split); } } /* * Scans through the index range and splits/truncates entries that overlap * with the one indexed by keep_idx. Returns the total lifetime of the symbols * found to overlap. * Returns ULONG_MAX on error. */ static unsigned long long eliminate_overlaps(int start_idx, int end_idx, int keep_idx) { unsigned long long retval; struct jitentry const * keep = entries_address_ascending[keep_idx]; struct jitentry * e; unsigned long long start_addr_keep = keep->vma; unsigned long long end_addr_keep = keep->vma + keep->code_size; unsigned long long start_addr_entry, end_addr_entry; int i; unsigned long long min_start = keep->life_start; unsigned long long max_end = keep->life_end; for (i = start_idx; i <= end_idx; i++) { if (i == keep_idx) continue; e = entries_address_ascending[i]; start_addr_entry = e->vma; end_addr_entry = e->vma + e->code_size; if (debug) { if (!(start_addr_entry <= end_addr_entry)) { verbprintf(debug, "assert failed:" " start_addr_entry <=" " end_addr_entry\n"); retval = ULONG_MAX; goto out; } if (!(start_addr_keep <= end_addr_keep)) { verbprintf(debug, "assert failed: " "start_addr_keep <=" " end_addr_keep\n"); retval = ULONG_MAX; goto out; } } if (start_addr_entry < end_addr_keep && end_addr_entry > start_addr_keep) { if (e->life_start < min_start) min_start = e->life_start; if (e->life_end > max_end) max_end = e->life_end; split_entry(e, keep); } } retval = max_end - min_start; out: return retval; } /* * Within the index range (into the array entries_address_ascending), find the * symbol with the maximal lifetime and split/truncate all symbols that overlap * with it (i.e. that there won't be any overlaps anymore). */ static int handle_overlap_region(int start_idx, int end_idx) { int rc = OP_JIT_CONV_OK; int idx; struct jitentry * e; int cnt; char * name; int i, j; unsigned long long totaltime; if (debug) { for (i = start_idx; i <= end_idx; i++) { e = entries_address_ascending[i]; verbprintf(debug, "overlap idx=%i, name=%s, " "start=%llx, end=%llx, life_start=%lli, " "life_end=%lli, lifetime=%lli\n", i, e->symbol_name, e->vma, e->vma + e->code_size, e->life_start, e->life_end, e->life_end - e->life_start); } } idx = select_one(start_idx, end_idx); totaltime = eliminate_overlaps(start_idx, end_idx, idx); if (totaltime == ULONG_MAX) { rc = OP_JIT_CONV_FAIL; goto out; } e = entries_address_ascending[idx]; cnt = 1; j = (e->life_end - e->life_start) * 100 / totaltime; while ((j = j/10)) cnt++; // Mark symbol name with a %% to indicate the overlap. cnt += strlen(e->symbol_name) + 2 + 1; name = xmalloc(cnt); snprintf(name, cnt, "%s%%%llu", e->symbol_name, (e->life_end - e->life_start) * 100 / totaltime); if (e->sym_name_malloced) free(e->symbol_name); e->symbol_name = name; e->sym_name_malloced = 1; verbprintf(debug, "selected idx=%i, name=%s\n", idx, e->symbol_name); out: return rc; } /* * one scan through the symbols to find overlaps. * return 1 if an overlap is found. this is repeated until no more overlap * is there. * Process: There may be more than two overlapping symbols with each other. * The index range of symbols found to overlap are passed to * handle_overlap_region. */ static int scan_overlaps(void) { int i, j; unsigned long long end_addr, end_addr2; struct jitentry const * a; int flag = 0; // entry_count can be incremented by split_entry() during the loop, // save the inital value as loop count int loop_count = entry_count; verbprintf(debug,"count=%i, scan overlaps...\n", entry_count); i = 0; end_addr = 0; for (j = 1; j < loop_count; j++) { /** * Take a symbol [i] and look for the next symbol(s) [j] that are overlapping * symbol [i]. If a symbol [j] is found that is not overlapping symbol [i] the * symbols [i]..[j-1] are handled to be not overlapping anymore. * See descriptions of handle_overlap_region() and eliminate_overlaps() for * further details of this handling. * E.g. possible cases to be handled could be: * * sym1 |-----------| * sym2 |---| * * sym1 |--------| * sym2 |--------| * * sym1 |--------| * sym2 |-------| * sym3 |---------| * * But in the following case * * sym1 |--------| * sym2 |-------------| * sym3 |----------| * * sym3 would not overlap with sym1. Therefore handle_overlap_regio() would * only be called for sym1 up to sym2. */ a = entries_address_ascending[j - 1]; end_addr2 = a->vma + a->code_size; if (end_addr2 > end_addr) end_addr = end_addr2; a = entries_address_ascending[j]; if (end_addr <= a->vma) { if (i != j - 1) { if (handle_overlap_region(i, j - 1) == OP_JIT_CONV_FAIL) { flag = OP_JIT_CONV_FAIL; goto out; } flag = 1; } i = j; } } if (i != j - 1) { if (handle_overlap_region(i, j - 1) == OP_JIT_CONV_FAIL) flag = OP_JIT_CONV_FAIL; else flag = 1; } out: return flag; } /* search for symbols that have overlapping address ranges and decide for * one */ int resolve_overlaps(unsigned long long start_time) { int rc = OP_JIT_CONV_OK; int cnt = 0; invalidate_earlybirds(start_time); while ((rc = scan_overlaps()) && rc != OP_JIT_CONV_FAIL) { resort_address(); if (cnt == 0) { verbprintf(debug, "WARNING: overlaps detected. " "Removing overlapping JIT methods\n"); } cnt++; } if (cnt > 0 && rc != OP_JIT_CONV_FAIL) resort_symbol(); return rc; } /* * scan through the sorted array and replace identical symbol names by unique * ones by adding a counter value. */ void disambiguate_symbol_names(void) { u32 j; int cnt, rep_cnt; struct jitentry * a; struct jitentry * b; rep_cnt = 0; for (j = 1; j < entry_count; j++) { a = entries_symbols_ascending[j - 1]; cnt = 1; do { b = entries_symbols_ascending[j]; if (strcmp(a->symbol_name, b->symbol_name) == 0) { if (b->sym_name_malloced) free(b->symbol_name); b->symbol_name = replacement_name(a->symbol_name, cnt); b->sym_name_malloced = 1; j++; cnt++; rep_cnt++; } else { break; } } while (j < entry_count); } /* recurse to avoid that the added suffix also creates a collision */ if (rep_cnt) { qsort(entries_symbols_ascending, entry_count, sizeof(struct jitentry *), cmp_symbolname); disambiguate_symbol_names(); } }