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