diff options
Diffstat (limited to 'src/mmap_cache.c')
-rw-r--r-- | src/mmap_cache.c | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/src/mmap_cache.c b/src/mmap_cache.c new file mode 100644 index 000000000..25f15a2b1 --- /dev/null +++ b/src/mmap_cache.c @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2013 Luca Clementi <luca.clementi@gmail.com> + * Copyright (c) 2013-2020 The strace developers. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "defs.h" +#include <limits.h> + +#include "largefile_wrappers.h" +#include "mmap_cache.h" +#include "mmap_notify.h" +#include "xstring.h" + +static unsigned int mmap_cache_generation; + +static void +mmap_cache_invalidate(struct tcb *tcp, void *unused) +{ + mmap_cache_generation++; + debug_func_msg("tgen=%u, ggen=%u, tcp=%p, cache=%p", + tcp->mmap_cache ? tcp->mmap_cache->generation : 0, + mmap_cache_generation, tcp, + tcp->mmap_cache ? tcp->mmap_cache->entry : 0); +} + +void +mmap_cache_enable(void) +{ + static bool use_mmap_cache; + + if (!use_mmap_cache) { + mmap_notify_register_client(mmap_cache_invalidate, NULL); + use_mmap_cache = true; + } +} + +/* deleting the cache */ +static void +delete_mmap_cache(struct tcb *tcp, const char *caller) +{ + debug_func_msg("tgen=%u, ggen=%u, tcp=%p, cache=%p, caller=%s", + tcp->mmap_cache ? tcp->mmap_cache->generation : 0, + mmap_cache_generation, tcp, + tcp->mmap_cache ? tcp->mmap_cache->entry : 0, caller); + + if (!tcp->mmap_cache) + return; + + while (tcp->mmap_cache->size) { + unsigned int i = --tcp->mmap_cache->size; + free(tcp->mmap_cache->entry[i].binary_filename); + tcp->mmap_cache->entry[i].binary_filename = NULL; + } + + free(tcp->mmap_cache->entry); + tcp->mmap_cache->entry = NULL; + + free(tcp->mmap_cache); + tcp->mmap_cache = NULL; +} + +/* + * caching of /proc/ID/maps for each process to speed up stack tracing + * + * The cache must be refreshed after syscalls that affect memory mappings, + * e.g. mmap, mprotect, munmap, execve. + */ +extern enum mmap_cache_rebuild_result +mmap_cache_rebuild_if_invalid(struct tcb *tcp, const char *caller) +{ + if (tcp->mmap_cache + && tcp->mmap_cache->generation != mmap_cache_generation) + delete_mmap_cache(tcp, caller); + + if (tcp->mmap_cache) + return MMAP_CACHE_REBUILD_READY; + + char filename[sizeof("/proc/4294967296/maps")]; + xsprintf(filename, "/proc/%u/maps", get_proc_pid(tcp)); + + FILE *fp = fopen_stream(filename, "r"); + if (!fp) { + perror_msg("fopen: %s", filename); + return MMAP_CACHE_REBUILD_NOCACHE; + } + + struct mmap_cache_t cache = { + .free_fn = delete_mmap_cache, + .generation = mmap_cache_generation + }; + + /* start with a small dynamically-allocated array and then expand it */ + size_t allocated = 0; + char buffer[PATH_MAX + 80]; + + while (fgets(buffer, sizeof(buffer), fp) != NULL) { + unsigned long start_addr, end_addr, mmap_offset; + char read_bit; + char write_bit; + char exec_bit; + char shared_bit; + unsigned long major, minor; + char binary_path[sizeof(buffer)]; + + if (sscanf(buffer, "%lx-%lx %c%c%c%c %lx %lx:%lx %*d %[^\n]", + &start_addr, &end_addr, + &read_bit, &write_bit, &exec_bit, &shared_bit, + &mmap_offset, + &major, &minor, + binary_path) != 10) + continue; + + /* skip mappings that have unknown protection */ + if (!(read_bit == '-' || read_bit == 'r')) + continue; + if (!(write_bit == '-' || write_bit == 'w')) + continue; + if (!(exec_bit == '-' || exec_bit == 'x')) + continue; + if (!(shared_bit == 'p' || shared_bit == 's')) + continue; + + if (end_addr < start_addr) { + error_msg("%s: unrecognized file format", filename); + break; + } + + struct mmap_cache_entry_t *entry; + /* + * sanity check to make sure that we're storing + * non-overlapping regions in ascending order + */ + if (cache.size > 0) { + entry = &cache.entry[cache.size - 1]; + if (entry->start_addr == start_addr && + entry->end_addr == end_addr) { + /* duplicate entry, e.g. [vsyscall] */ + continue; + } + if (start_addr <= entry->start_addr || + start_addr < entry->end_addr) { + debug_msg("%s: overlapping memory region: " + "\"%s\" [%08lx-%08lx] overlaps with " + "\"%s\" [%08lx-%08lx]", + filename, binary_path, start_addr, + end_addr, entry->binary_filename, + entry->start_addr, entry->end_addr); + continue; + } + } + + if (cache.size >= allocated) + cache.entry = xgrowarray(cache.entry, &allocated, + sizeof(*cache.entry)); + + entry = &cache.entry[cache.size]; + entry->start_addr = start_addr; + entry->end_addr = end_addr; + entry->mmap_offset = mmap_offset; + entry->protections = ( + 0 + | ((read_bit == 'r')? MMAP_CACHE_PROT_READABLE : 0) + | ((write_bit == 'w')? MMAP_CACHE_PROT_WRITABLE : 0) + | ((exec_bit == 'x')? MMAP_CACHE_PROT_EXECUTABLE: 0) + | ((shared_bit == 's')? MMAP_CACHE_PROT_SHARED : 0) + ); + entry->major = major; + entry->minor = minor; + entry->binary_filename = xstrdup(binary_path); + cache.size++; + } + fclose(fp); + + if (!cache.size) + return MMAP_CACHE_REBUILD_NOCACHE; + + tcp->mmap_cache = xmalloc(sizeof(*tcp->mmap_cache)); + memcpy(tcp->mmap_cache, &cache, sizeof(cache)); + + debug_func_msg("tgen=%u, ggen=%u, tcp=%p, cache=%p, caller=%s", + tcp->mmap_cache->generation, mmap_cache_generation, + tcp, tcp->mmap_cache->entry, caller); + + return MMAP_CACHE_REBUILD_RENEWED; +} + +struct mmap_cache_entry_t * +mmap_cache_search(struct tcb *tcp, unsigned long ip) +{ + if (!tcp->mmap_cache) + return NULL; + + int lower = 0; + int upper = (int) tcp->mmap_cache->size - 1; + + while (lower <= upper) { + int mid = (upper + lower) / 2; + struct mmap_cache_entry_t *entry = &tcp->mmap_cache->entry[mid]; + + if (ip >= entry->start_addr && + ip < entry->end_addr) + return entry; + else if (ip < entry->start_addr) + upper = mid - 1; + else + lower = mid + 1; + } + return NULL; +} + +struct mmap_cache_entry_t * +mmap_cache_search_custom(struct tcb *tcp, mmap_cache_search_fn fn, void *data) +{ + for (unsigned int i = 0; i < tcp->mmap_cache->size; i++) { + if (fn(tcp->mmap_cache->entry + i, data)) + return tcp->mmap_cache->entry + i; + } + return NULL; +} |