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