blob: f4c8b2a6dcc6e818e32d61191be9d743a39a00a5 [file] [log] [blame]
/**
* @file opd_anon.c
* Anonymous region handling.
*
* Our caching of maps has some problems: if we get tgid reuse,
* and it's the same application, we might end up with wrong
* maps. The same happens in an unmap-remap case. There's not much
* we can do about this, we just hope it's not too common...
*
* What is relatively common is expanding anon maps, which leaves us
* with lots of separate sample files.
*
* @remark Copyright 2005 OProfile authors
* @remark Read the file COPYING
*
* @author John Levon
*/
#include "opd_anon.h"
#include "opd_trans.h"
#include "opd_sfile.h"
#include "opd_printf.h"
#include "op_libiberty.h"
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#define HASH_SIZE 1024
#define HASH_BITS (HASH_SIZE - 1)
#define LRU_SIZE 1000
#define LRU_AMOUNT (LRU_SIZE/5)
static struct list_head hashes[HASH_SIZE];
static struct list_head lru;
static size_t nr_lru;
/* never called in buffer-processing context, so we don't need
* to update struct transient. Note the 'U' of LRU is based on
* parsing time, not actual use.
*/
static void do_lru(struct transient * trans)
{
size_t nr_to_kill = LRU_AMOUNT;
struct list_head * pos;
struct list_head * pos2;
struct anon_mapping * entry;
list_for_each_safe(pos, pos2, &lru) {
entry = list_entry(pos, struct anon_mapping, lru_list);
if (trans->anon == entry)
clear_trans_current(trans);
if (trans->last_anon == entry)
clear_trans_last(trans);
list_del(&entry->list);
list_del(&entry->lru_list);
--nr_lru;
free(entry);
if (nr_to_kill-- == 0)
break;
}
sfile_clear_anon();
}
static unsigned long hash_anon(pid_t tgid, cookie_t app)
{
return ((app >> DCOOKIE_SHIFT) ^ (tgid >> 2)) & (HASH_SIZE - 1);
}
static void clear_anon_maps(struct transient * trans)
{
unsigned long hash = hash_anon(trans->tgid, trans->app_cookie);
pid_t tgid = trans->tgid;
cookie_t app = trans->app_cookie;
struct list_head * pos;
struct list_head * pos2;
struct anon_mapping * entry;
clear_trans_current(trans);
list_for_each_safe(pos, pos2, &hashes[hash]) {
entry = list_entry(pos, struct anon_mapping, list);
if (entry->tgid == tgid && entry->app_cookie == app) {
if (trans->last_anon == entry)
clear_trans_last(trans);
list_del(&entry->list);
list_del(&entry->lru_list);
--nr_lru;
free(entry);
}
}
sfile_clear_anon();
if (vmisc) {
char const * name = verbose_cookie(app);
printf("Cleared anon maps for tgid %u (%s).\n", tgid, name);
}
}
static void
add_anon_mapping(struct transient * trans, vma_t start, vma_t end)
{
unsigned long hash = hash_anon(trans->tgid, trans->app_cookie);
struct anon_mapping * m = xmalloc(sizeof(struct anon_mapping));
m->tgid = trans->tgid;
m->app_cookie = trans->app_cookie;
m->start = start;
m->end = end;
list_add_tail(&m->list, &hashes[hash]);
list_add_tail(&m->lru_list, &lru);
if (++nr_lru == LRU_SIZE)
do_lru(trans);
if (vmisc) {
char const * name = verbose_cookie(m->app_cookie);
printf("Added anon map 0x%llx-0x%llx for tgid %u (%s).\n",
start, end, m->tgid, name);
}
}
/* 42000000-4212f000 r-xp 00000000 16:03 424334 /lib/tls/libc-2.3.2.so */
static void get_anon_maps(struct transient * trans)
{
FILE * fp = NULL;
char buf[PATH_MAX];
unsigned long start;
unsigned long end;
int ret;
snprintf(buf, PATH_MAX, "/proc/%d/maps", trans->tgid);
fp = fopen(buf, "r");
if (!fp)
return;
while (fgets(buf, PATH_MAX, fp) != NULL) {
char tmp[20];
/* Note that this actually includes all mappings,
* since we want stuff like [heap]
*/
ret = sscanf(buf, "%lx-%lx %20s %20s %20s %20s %20s",
&start, &end, tmp, tmp, tmp, tmp, tmp);
if (ret < 6)
continue;
add_anon_mapping(trans, start, end);
}
fclose(fp);
}
static int
anon_match(struct transient const * trans, struct anon_mapping const * anon)
{
if (!anon)
return 0;
if (trans->tgid != anon->tgid)
return 0;
if (trans->app_cookie != anon->app_cookie)
return 0;
if (trans->pc < anon->start)
return 0;
return (trans->pc < anon->end);
}
struct anon_mapping * find_anon_mapping(struct transient * trans)
{
unsigned long hash = hash_anon(trans->tgid, trans->app_cookie);
struct list_head * pos;
struct anon_mapping * entry;
int tried = 0;
if (anon_match(trans, trans->anon))
return (trans->anon);
retry:
list_for_each(pos, &hashes[hash]) {
entry = list_entry(pos, struct anon_mapping, list);
if (anon_match(trans, entry))
goto success;
}
if (!tried) {
clear_anon_maps(trans);
get_anon_maps(trans);
tried = 1;
goto retry;
}
return NULL;
success:
/*
* Typically, there's one big mapping that matches. Let's go
* faster.
*/
list_del(&entry->list);
list_add(&entry->list, &hashes[hash]);
verbprintf(vmisc, "Found range 0x%llx-0x%llx for tgid %u, pc %llx.\n",
entry->start, entry->end, (unsigned int)entry->tgid,
trans->pc);
return entry;
}
void anon_init(void)
{
size_t i;
for (i = 0; i < HASH_SIZE; ++i)
list_init(&hashes[i]);
list_init(&lru);
}