blob: 81556abb23094203584cc69c465268c7d8603fb0 [file] [log] [blame]
/* *******************************************************************************
* Copyright (c) 2010-2014 Google, Inc. All rights reserved.
* Copyright (c) 2011 Massachusetts Institute of Technology All rights reserved.
* Copyright (c) 2000-2010 VMware, Inc. All rights reserved.
* *******************************************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
/* Copyright (c) 2003-2007 Determina Corp. */
/* Copyright (c) 2001-2003 Massachusetts Institute of Technology */
/* Copyright (c) 2000-2001 Hewlett-Packard Company */
/*
* memquery_linux.c - memory querying via /proc/self/maps
*/
#include "../globals.h"
#include "memquery.h"
#include "os_private.h"
#include "module_private.h"
#include <string.h>
#include <sys/mman.h>
#ifndef LINUX
# error Linux-only
#endif
/* Iterator over /proc/self/maps
* Called at arbitrary places, so cannot use fopen.
* The whole thing is serialized, so entries cannot be referenced
* once the iteration ends.
*/
/* Internal data */
typedef struct _maps_iter_t {
file_t maps;
char *newline;
int bufread;
int bufwant;
char *buf;
char *comment_buffer;
} maps_iter_t;
/* lock to guard reads from /proc/self/maps in get_memory_info_from_os(). */
static mutex_t memory_info_buf_lock = INIT_LOCK_FREE(memory_info_buf_lock);
/* lock for iterator where user needs to allocate memory */
static mutex_t maps_iter_buf_lock = INIT_LOCK_FREE(maps_iter_buf_lock);
/* On all supported linux kernels /proc/self/maps -> /proc/$pid/maps
* However, what we want is /proc/$tid/maps, so we can't use "self"
*/
#define PROC_SELF_MAPS "/proc/self/maps"
/* these are defined in /usr/src/linux/fs/proc/array.c */
#define MAPS_LINE_LENGTH 4096
/* for systems with sizeof(void*) == 4: */
#define MAPS_LINE_FORMAT4 "%08lx-%08lx %s %08lx %*s "UINT64_FORMAT_STRING" %4096s"
#define MAPS_LINE_MAX4 49 /* sum of 8 1 8 1 4 1 8 1 5 1 10 1 */
/* for systems with sizeof(void*) == 8: */
#define MAPS_LINE_FORMAT8 "%016lx-%016lx %s %016lx %*s "UINT64_FORMAT_STRING" %4096s"
#define MAPS_LINE_MAX8 73 /* sum of 16 1 16 1 4 1 16 1 5 1 10 1 */
#define MAPS_LINE_MAX MAPS_LINE_MAX8
/* can't use fopen -- strategy: read into buffer, look for newlines.
* fail if single line too large for buffer -- so size it appropriately:
*/
/* since we're called from signal handler, etc., keep our stack usage
* low by using static bufs (it's over 4K after all).
* FIXME: now we're using 16K right here: should we shrink?
*/
#define BUFSIZE (MAPS_LINE_LENGTH+8)
static char buf_scratch[BUFSIZE];
static char comment_buf_scratch[BUFSIZE];
/* To satisfy our two uses (inner use with memory_info_buf_lock versus
* outer use with maps_iter_buf_lock), we have two different locks and
* two different sets of static buffers. This is to avoid lock
* ordering issues: we need an inner lock for use in places like signal
* handlers, but an outer lock when the iterator user allocates memory.
*/
static char buf_iter[BUFSIZE];
static char comment_buf_iter[BUFSIZE];
void
memquery_init(void)
{
ASSERT(sizeof(maps_iter_t) <= MEMQUERY_INTERNAL_DATA_LEN);
}
void
memquery_exit(void)
{
DELETE_LOCK(memory_info_buf_lock);
DELETE_LOCK(maps_iter_buf_lock);
}
bool
memquery_iterator_start(memquery_iter_t *iter, app_pc start, bool may_alloc)
{
char maps_name[24]; /* should only need 16 for 5-digit tid */
maps_iter_t *mi = (maps_iter_t *) &iter->internal;
/* Don't assign the local ptrs until the lock is grabbed to make
* their relationship clear. */
if (may_alloc) {
mutex_lock(&maps_iter_buf_lock);
mi->buf = (char *) &buf_iter;
mi->comment_buffer = (char *) &comment_buf_iter;
} else {
mutex_lock(&memory_info_buf_lock);
mi->buf = (char *) &buf_scratch;
mi->comment_buffer = (char *) &comment_buf_scratch;
}
/* We need the maps for our thread id, not process id.
* "/proc/self/maps" uses pid which fails if primary thread in group
* has exited.
*/
snprintf(maps_name, BUFFER_SIZE_ELEMENTS(maps_name),
"/proc/%d/maps", get_thread_id());
mi->maps = os_open(maps_name, OS_OPEN_READ);
ASSERT(mi->maps != INVALID_FILE);
mi->buf[BUFSIZE-1] = '\0'; /* permanently */
mi->newline = NULL;
mi->bufread = 0;
iter->comment = mi->comment_buffer;
iter->may_alloc = may_alloc;
/* XXX: it's quite difficult to start at the region containing "start":
* we either need to support walking backward a line (complicated by
* the incremental os_read() scheme) or make two passes.
* Thus, the interface doesn't promise we'll start there.
*/
iter->vm_start = NULL;
return true;
}
void
memquery_iterator_stop(memquery_iter_t *iter)
{
maps_iter_t *mi = (maps_iter_t *) &iter->internal;
ASSERT((iter->may_alloc && OWN_MUTEX(&maps_iter_buf_lock)) ||
(!iter->may_alloc && OWN_MUTEX(&memory_info_buf_lock)));
os_close(mi->maps);
if (iter->may_alloc)
mutex_unlock(&maps_iter_buf_lock);
else
mutex_unlock(&memory_info_buf_lock);
}
bool
memquery_iterator_next(memquery_iter_t *iter)
{
maps_iter_t *mi = (maps_iter_t *) &iter->internal;
char perm[16];
char *line;
int len;
app_pc prev_start = iter->vm_start;
ASSERT((iter->may_alloc && OWN_MUTEX(&maps_iter_buf_lock)) ||
(!iter->may_alloc && OWN_MUTEX(&memory_info_buf_lock)));
if (mi->newline == NULL) {
mi->bufwant = BUFSIZE-1;
mi->bufread = os_read(mi->maps, mi->buf, mi->bufwant);
ASSERT(mi->bufread <= mi->bufwant);
LOG(GLOBAL, LOG_VMAREAS, 6,
"get_memory_info_from_os: bytes read %d/want %d\n",
mi->bufread, mi->bufwant);
if (mi->bufread <= 0)
return false;
mi->buf[mi->bufread] = '\0';
mi->newline = strchr(mi->buf, '\n');
line = mi->buf;
} else {
line = mi->newline + 1;
mi->newline = strchr(line, '\n');
if (mi->newline == NULL) {
/* FIXME clean up: factor out repetitive code */
/* shift 1st part of line to start of buf, then read in rest */
/* the memory for the processed part can be reused */
mi->bufwant = line - mi->buf;
ASSERT(mi->bufwant <= mi->bufread);
len = mi->bufread - mi->bufwant; /* what is left from last time */
/* since strings may overlap, should use memmove, not strncpy */
/* FIXME corner case: if len == 0, nothing to move */
memmove(mi->buf, line, len);
mi->bufread = os_read(mi->maps, mi->buf+len, mi->bufwant);
ASSERT(mi->bufread <= mi->bufwant);
if (mi->bufread <= 0)
return false;
mi->bufread += len; /* bufread is total in buf */
mi->buf[mi->bufread] = '\0';
mi->newline = strchr(mi->buf, '\n');
line = mi->buf;
}
}
LOG(GLOBAL, LOG_VMAREAS, 6,
"\nget_memory_info_from_os: newline=[%s]\n",
mi->newline ? mi->newline : "(null)");
/* buffer is big enough to hold at least one line */
ASSERT(mi->newline != NULL);
*mi->newline = '\0';
LOG(GLOBAL, LOG_VMAREAS, 6,
"\nget_memory_info_from_os: line=[%s]\n", line);
mi->comment_buffer[0]='\0';
len = sscanf(line,
#ifdef IA32_ON_IA64
MAPS_LINE_FORMAT8, /* cross-compiling! */
#else
sizeof(void*) == 4 ? MAPS_LINE_FORMAT4 : MAPS_LINE_FORMAT8,
#endif
(unsigned long*)&iter->vm_start, (unsigned long*)&iter->vm_end,
perm, (unsigned long*)&iter->offset, &iter->inode,
mi->comment_buffer);
if (iter->vm_start == iter->vm_end) {
/* i#366 & i#599: Merge an empty regions caused by stack guard pages
* into the stack region if the stack region is less than one page away.
* Otherwise skip it. Some Linux kernels (2.6.32 has been observed)
* have empty entries for the stack guard page. We drop the permissions
* on the guard page, because Linux always insists that it has rwxp
* perms, no matter how we change the protections. The actual stack
* region has the perms we expect.
* XXX: We could get more accurate info if we looked at
* /proc/self/smaps, which has a Size: 4k line for these "empty"
* regions.
*/
app_pc empty_start = iter->vm_start;
bool r;
LOG(GLOBAL, LOG_VMAREAS, 2,
"maps_iterator_next: skipping or merging empty region 0x%08x\n",
iter->vm_start);
/* don't trigger the maps-file-changed check.
* slight risk of a race where we'll pass back earlier/overlapping
* region: we'll live with it.
*/
iter->vm_start = NULL;
r = memquery_iterator_next(iter);
/* We could check to see if we're combining with the [stack] section,
* but that doesn't work if there are multiple stacks or the stack is
* split into multiple maps entries, so we merge any empty region within
* one page of the next region.
*/
if (empty_start <= iter->vm_start &&
iter->vm_start <= empty_start + PAGE_SIZE) {
/* Merge regions if the next region was zero or one page away. */
iter->vm_start = empty_start;
}
return r;
}
if (iter->vm_start <= prev_start) {
/* the maps file has expanded underneath us (presumably due to our
* own committing while iterating): skip ahead */
LOG(GLOBAL, LOG_VMAREAS, 2,
"maps_iterator_next: maps file changed: skipping 0x%08x\n", prev_start);
iter->vm_start = prev_start;
return memquery_iterator_next(iter);
}
if (len<6)
mi->comment_buffer[0]='\0';
iter->prot = permstr_to_memprot(perm);
return true;
}
/***************************************************************************
* LIBRARY BOUNDS
*/
/* See memquery.h for full interface specs.
* Gets the library bounds from walking the map file (as opposed to using our cached
* module_list) since is only used for dr and client libraries which aren't on the list.
*/
int
memquery_library_bounds(const char *name, app_pc *start/*IN/OUT*/, app_pc *end/*OUT*/,
char *fullpath/*OPTIONAL OUT*/, size_t path_size)
{
return memquery_library_bounds_by_iterator(name, start, end, fullpath, path_size);
}
/***************************************************************************
* QUERY
*/
bool
memquery_from_os(const byte *pc, OUT dr_mem_info_t *info, OUT bool *have_type)
{
memquery_iter_t iter;
app_pc last_end = NULL;
app_pc next_start = (app_pc) POINTER_MAX;
bool found = false;
ASSERT(info != NULL);
memquery_iterator_start(&iter, (app_pc) pc, false/*won't alloc*/);
while (memquery_iterator_next(&iter)) {
if (pc >= iter.vm_start && pc < iter.vm_end) {
info->base_pc = iter.vm_start;
info->size = (iter.vm_end - iter.vm_start);
info->prot = iter.prot;
/* On early (pre-Fedora 2) kernels the vsyscall page is listed
* with no permissions at all in the maps file. Here's RHEL4:
* ffffe000-fffff000 ---p 00000000 00:00 0
* We return "rx" as the permissions in that case.
*/
if (vsyscall_page_start != NULL &&
pc >= vsyscall_page_start && pc < vsyscall_page_start+PAGE_SIZE) {
ASSERT(iter.vm_start == vsyscall_page_start);
ASSERT(iter.vm_end - iter.vm_start == PAGE_SIZE);
if (iter.prot == MEMPROT_NONE) {
info->prot = (MEMPROT_READ|MEMPROT_EXEC);
}
}
found = true;
break;
} else if (pc < iter.vm_start) {
next_start = iter.vm_start;
break;
}
last_end = iter.vm_end;
}
memquery_iterator_stop(&iter);
if (!found) {
info->base_pc = last_end;
info->size = (next_start - last_end);
info->prot = MEMPROT_NONE;
info->type = DR_MEMTYPE_FREE;
*have_type = true;
}
return true;
}