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