/**
* @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
* @Modifications Gisle Dankel
*/
#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>
#include <string.h>
#define HASH_SIZE 1024
#define HASH_BITS (HASH_SIZE - 1)
/*
* Note that this value is tempered by the fact that when we miss in the
* anon cache, we'll tear down all the mappings for that tgid. Thus, LRU
* of a mapping can potentially clear out a much larger number of
* mappings.
*/
#define LRU_SIZE 8192
#define LRU_AMOUNT (LRU_SIZE/8)
static struct list_head hashes[HASH_SIZE];
static struct list_head lru;
static size_t nr_lru;
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);
sfile_clear_anon(entry);
list_del(&entry->list);
list_del(&entry->lru_list);
--nr_lru;
free(entry);
if (nr_to_kill-- == 0)
break;
}
}
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);
sfile_clear_anon(entry);
list_del(&entry->list);
list_del(&entry->lru_list);
--nr_lru;
free(entry);
}
}
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, char * name)
{
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;
strncpy(m->name, name, MAX_IMAGE_NAME_SIZE + 1);
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];
vma_t start, 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[MAX_IMAGE_NAME_SIZE + 1];
char name[MAX_IMAGE_NAME_SIZE + 1];
/* Some anon maps have labels like
* [heap], [stack], [vdso], [vsyscall] ...
* Keep track of these labels. If a map has no name, call it "anon".
* Ignore all mappings starting with "/" (file or shared memory object)
*/
strcpy(name, "anon");
ret = sscanf(buf, "%llx-%llx %20s %20s %20s %20s %20s",
&start, &end, tmp, tmp, tmp, tmp, name);
if (ret < 6 || name[0] == '/')
continue;
add_anon_mapping(trans, start, end, name);
}
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);
}