blob: 74064186ae3e1146b0fb4bd29c6fe207dc4e05ee [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2010-2014 Google, Inc. All rights reserved.
* Copyright (c) 2002-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) 2002-2003 Massachusetts Institute of Technology */
/*
* vmareas.c - virtual memory executable areas
*/
#include "globals.h"
/* all of this for selfmod handling */
#include "fragment.h"
#include "instr.h"
#include "decode.h"
#include "decode_fast.h"
#include "link.h"
#include "disassemble.h"
#include "fcache.h"
#include "hotpatch.h"
#include "moduledb.h"
#include "module_shared.h"
#include "perscache.h"
#include "translate.h"
#ifdef WINDOWS
# include "events.h" /* event log messages - not supported yet on Linux */
#endif
#ifdef CLIENT_INTERFACE
# include "instrument.h"
#endif
#ifdef DEBUG
# include "synch.h" /* all_threads_synch_lock */
#endif
#include <string.h>
enum {
/* VM_ flags to distinguish region types
* We also use some FRAG_ flags (but in a separate field so no value space overlap)
* Adjacent regions w/ different flags are never merged.
*/
VM_WRITABLE = 0x0001, /* app memory writable? */
/* UNMOD_IMAGE means the region was mmapped in and has been read-only since then
* this excludes even loader modifications (IAT update, relocate, etc.) on win32!
*/
VM_UNMOD_IMAGE = 0x0002,
VM_DELETE_ME = 0x0004, /* on delete queue -- for thread-local only */
/* NOTE : if a new area is added that overlaps an existing area with a
* different VM_WAS_FUTURE flag, the areas will be merged with the flag
* taken from the new area, see FIXME in add_vm_area */
VM_WAS_FUTURE = 0x0008, /* moved from future list to exec list */
VM_DR_HEAP = 0x0010, /* DR heap area */
VM_ONCE_ONLY = 0x0020, /* on future list but should be removed on
* first exec */
/* FIXME case 7877, 3744: need to properly merge pageprot regions with
* existing selfmod regions before we can truly separate this. For now we
* continue to treat selfmod as pageprot.
*/
VM_MADE_READONLY = VM_WRITABLE/* FIXME: should be 0x0040 -- see above */,
/* DR has marked this region read
* only for consistency, should only be used
* in conjunction with VM_WRITABLE */
VM_DELAY_READONLY = 0x0080, /* dr has not yet marked this region read
* only for consistency, should only be used
* in conjunction with VM_WRITABLE */
#ifdef PROGRAM_SHEPHERDING
/* re-verify this region for code origins policies every time it is
* encountered. only used with selfmod regions that are only allowed if
* they match patterns, to prevent other threads from writing non-pattern
* code though and executing after the region has been approved.
* xref case 4020. can remove once we split code origins list from
* cache consistency list (case 3744).
*/
VM_PATTERN_REVERIFY = 0x0100,
#endif
VM_DRIVER_ADDRESS = 0x0200,
/* a driver hooker area, needed for case 9022. Note we can
* normally read properties only of user mode addresses, so we
* have to probe addresses in this area. Also note that we're
* still executing all of this code in user mode e.g. there is no
* mode switch, no conforming segments, etc.
*/
/* Does this region contain a persisted cache?
* Must also be FRAG_COARSE_GRAIN of course.
* This is a shortcut to reading custom.client->persisted.
* This is not guaranteed to be set on shared_data: only on executable_areas.
*/
VM_PERSISTED_CACHE = 0x0400,
/* Case 10584: avoid flush synch when no code has been executed */
VM_EXECUTED_FROM = 0x0800,
/* A workaround for lock rank issues: we delay adding loaded persisted
* units to shared_data until first asked about.
* This flags is NOT propagated on vmarea splits.
*/
VM_ADD_TO_SHARED_DATA = 0x1000,
};
/* simple way to disable sandboxing */
#define SANDBOX_FLAG() (INTERNAL_OPTION(cache_consistency) ? FRAG_SELFMOD_SANDBOXED : 0)
/* Fields only used for written_areas */
typedef struct _ro_vs_sandbox_data_t {
/* written_count only used for written_areas vector.
* if > 0, areas will NOT be merged, so we can keep separate
* counts by page (hopefully not making the list too long).
*/
uint written_count;
/* Used only for -sandbox2ro_threshold. It's only in the
* written_areas vector b/c executable_areas has its regions removed
* on a flush while threads could still be accessing counters in
* selfmod fragments in the cache. We lose some granularity here but
* it's not a big deal.
* We could make these both ushorts, but it'd be more of a pain
* to increment this counter from the cache then, worrying about overflows.
*/
uint selfmod_execs;
#ifdef DEBUG
uint ro2s_xfers;
uint s2ro_xfers;
#endif
} ro_vs_sandbox_data_t;
/* Our executable area list has three types of areas. Each type can be merged
* with adjacent areas of the same type but not with any of the other types!
* 1) originally RO code == we leave alone
* 2) originally RW code == we mark RO
* 3) originally RW code, written to from within itself == we leave RW and sandbox
* We keep all three types in the same list b/c any particular address interval
* can only be of one type at any one time, and all three are executable, meaning
* code cache code was copied from there.
*/
typedef struct vm_area_t {
app_pc start;
app_pc end; /* open end interval */
/* We have two different flags fields to allow easy use of the FRAG_ flags.
* The two combined are used to distinguish different regions.
* Adjacent regions w/ different flags are never merged.
*/
/* Flags that start with VM_ */
uint vm_flags;
/* Flags that start with FRAG_
* In use now are FRAG_SELFMOD_SANDBOXED and FRAG_DYNGEN.
*/
uint frag_flags;
#ifdef DEBUG
char *comment;
#endif
/********************
* custom fields not used in all vectors
* FIXME: separate into separately-allocated piece? or have a struct
* extension (poor man's subclass, like trace_t, etc.) and make our vector
* iterators handle it?
* once we have a generic interval data structure (case 6208) this
* hardcoding of individual uses will go away.
*/
union {
/* Used in per-thread and shared vectors, not in master area lists.
* We identify vectors using this via VECTOR_FRAGMENT_LIST, needed
* b/c {add,remove}_vm_area have special behavior for frags.
*/
fragment_t *frags;
/* for clients' custom use via vmvector interfaces */
void *client;
} custom;
} vm_area_t;
/* for each thread we record all executable areas, to make it faster
* to decide whether we need to flush any fragments on an munmap
*/
typedef struct thread_data_t {
vm_area_vector_t areas;
/* cached pointer to last area encountered by thread */
vm_area_t *last_area;
/* FIXME: for locality would be nice to have per-thread last_shared_area
* (cannot put shared in private last_area, that would void its usefulness
* since couldn't tell if area really in shared list or not)
* but then have to update all other threads whenever change shared
* vmarea vector, so for now we use a global last_area
*/
/* cached pointer of a PC in the last page decoded by thread -- set only
* in thread-private structures, not in shared structures like shared_data */
app_pc last_decode_area_page_pc;
bool last_decode_area_valid; /* since no sentinel exists */
#ifdef PROGRAM_SHEPHERDING
uint thrown_exceptions; /* number of responses to execution violations */
#endif
} thread_data_t;
#define SHOULD_LOCK_VECTOR(v) \
(TEST(VECTOR_SHARED, (v)->flags) && \
!TEST(VECTOR_NO_LOCK, (v)->flags) && \
!self_owns_write_lock(&(v)->lock))
#define LOCK_VECTOR(v, release_lock, RW) do { \
if (SHOULD_LOCK_VECTOR(v)) { \
(release_lock) = true; \
RW##_lock(&(v)->lock); \
} \
else \
(release_lock) = false; \
} while (0);
#define UNLOCK_VECTOR(v, release_lock, RW) do { \
if ((release_lock)) { \
ASSERT(TEST(VECTOR_SHARED, (v)->flags)); \
ASSERT(!TEST(VECTOR_NO_LOCK, (v)->flags)); \
ASSERT_OWN_READWRITE_LOCK(true, &(v)->lock); \
RW##_unlock(&v->lock); \
} \
} while (0);
/* these two global vectors store all executable areas and all dynamo
* areas (executable or otherwise).
* executable_areas' custom field is used to store coarse unit info.
* for a FRAG_COARSE_GRAIN region, an info struct is always present, even
* if not yet executed from (initially, or after a flush).
*/
static vm_area_vector_t *executable_areas;
static vm_area_vector_t *dynamo_areas;
/* Protected by executable_areas lock; used only to delete coarse_info_t
* while holding executable_areas lock during execute-less flushes
* (case 10995). Extra layer of indirection to get on heap and avoid .data
* unprotection.
*/
static coarse_info_t **coarse_to_delete;
/* used for DYNAMO_OPTION(handle_DR_modify),
* DYNAMO_OPTION(handle_ntdll_modify) == DR_MODIFY_NOP or
* DYNAMO_OPTION(patch_proof_list)
*/
static vm_area_vector_t *pretend_writable_areas;
/* used for DYNAMO_OPTION(patch_proof_list) areas to watch */
vm_area_vector_t *patch_proof_areas;
/* used for DYNAMO_OPTION(emulate_IAT_writes), though in future may be
* expanded, so not just ifdef WINDOWS or ifdef PROGRAM_SHEPHERDING
*/
vm_area_vector_t *emulate_write_areas;
/* used for DYNAMO_OPTION(IAT_convert)
* IAT or GOT areas of all mapped DLLs - note the exact regions are added here.
* While the IATs for modules in native_exec_areas are not added here -
* note that any module's IAT may still be importing native modules.
*/
vm_area_vector_t *IAT_areas;
/* Keeps persistent written-to and execution counts for switching back and
* forth from page prot to sandboxing.
*/
static vm_area_vector_t *written_areas;
static void free_written_area(void *data);
#ifdef PROGRAM_SHEPHERDING
/* for executable_if_flush and executable_if_alloc, we need a future list, so their regions
* are considered executable until de-allocated -- even if written to!
*/
static vm_area_vector_t *futureexec_areas;
# ifdef WINDOWS
/* FIXME: for -xdata_rct we only need start pc called on, so htable would do,
* once we have reusable htable for storing single pc
*/
static vm_area_vector_t *app_flushed_areas;
# endif
#endif
/* tamper resistant region see tamper_resistant_region_add() for current use.
* If needed this should be turned into a vm_area_vector_t as well.
*/
static app_pc tamper_resistant_region_start, tamper_resistant_region_end;
/* shared_data is synchronized via either single_thread_in_DR or
* the vector lock (cannot use bb_building_lock b/c both trace building
* and pc translation need read access and neither can/should grab
* the bb building lock, plus it's cleaner to not depend on it, and now
* with -shared_traces it's not sufficient).
* N.B.: the vector lock is used to protect not just the vector, but also
* the whole thread_data_t struct (including last_area) and sequences
* of vector operations.
* Kept on the heap for selfprot (case 7957).
*/
static thread_data_t *shared_data; /* set in vm_areas_reset_init() */
typedef struct _pending_delete_t {
#ifdef DEBUG
/* record bounds of original deleted region, for debugging only */
app_pc start;
app_pc end;
#endif
/* list of unlinked fragments that are waiting to be deleted */
fragment_t *frags;
/* ref count and timestamp to determine when it's safe to delete them */
uint ref_count;
uint flushtime_deleted;
/* we use a simple linked list of entries */
struct _pending_delete_t *next;
} pending_delete_t;
/* We keep these list pointers on the heap for selfprot (case 8074). */
typedef struct _deletion_lists_t {
/* Unlike private vm lists, we cannot simply mark shared_data vm areas as
* deleted since new fragments come in concurrently, so we have to have a
* separate list of flushed-but-not-yet-deleted areas. We can't use a
* vm_area_vector_t b/c newly flushed fragments spoil our ref count by resetting
* it, so we keep a linked list of fragment lists.
*/
pending_delete_t *shared_delete;
/* We maintain the tail solely for fcache_free_pending_units() */
pending_delete_t *shared_delete_tail;
/* count used for reset threshold */
uint shared_delete_count;
/* shared lazy deletion: a list of fragment_t chained via next_vmarea that
* are pending deletion, but are only freed when a shared deletion event
* shows that it is safe to do so.
*/
fragment_t *lazy_delete_list;
/* stores the end of the list, for appending */
fragment_t *lazy_delete_tail;
/* stores the length of the lazy list */
uint lazy_delete_count;
/* ensure only one thread tries to move to pending deletion list */
bool move_pending;
} deletion_lists_t;
static deletion_lists_t *todelete;
typedef struct _last_deallocated_t {
/* case 9330 - we want to detect races during DLL unloads, and to
* silence a reported violation during unload. At least DLLs are
* expected to be already serialized by the loader so keeping only
* one is sufficient (note Win2K3 doesn't hold lock only during
* process initialization). We'll also keep references to the
* last DLL that was unloaded for diagnostics. Although, that is
* not reliable enough when multiple DLLs are involved - case 6061
* should be used for better tracking after unload.
*/
/* Yet loss of integrity is tolerable, as long as detected. Since
* we currently mark all mappings they are not necessarily
* serialized (and potentially other apps can directly map, so
* can't really count on the loader lock for integrity). We
* should make sure that we do not set unload_in_progress unless
* [last_unload_base, last_unload_size) is really still the
* current module.
*/
bool unload_in_progress;
app_pc last_unload_base;
size_t last_unload_size;
/* FIXME: we may want to overload the above or add different
* fields for non image (MEM_MAPPED) unmaps, and DGC (MEM_PRIVATE)
* frees. Note that we avoid keeping lists of active unloads, or
* even to deal with case 9371 we would need intersection of
* overlapping app syscalls. If we serialize app syscalls as
* proposed case 545 a single one will be sufficient.
*/
} last_deallocated_t;
static last_deallocated_t *last_deallocated;
/* synchronization currently used only for the contents of
* last_deallocated: last_unload_base and last_unload_size
*/
DECLARE_CXTSWPROT_VAR(static mutex_t last_deallocated_lock,
INIT_LOCK_FREE(last_deallocated_lock));
/* synchronization for shared_delete, not a rw lock since readers usually write */
DECLARE_CXTSWPROT_VAR(mutex_t shared_delete_lock, INIT_LOCK_FREE(shared_delete_lock));
/* synchronization for the lazy deletion list */
DECLARE_CXTSWPROT_VAR(static mutex_t lazy_delete_lock, INIT_LOCK_FREE(lazy_delete_lock));
/* multi_entry_t allocation is either global or local heap */
#define MULTI_ALLOC_DC(dc, flags) FRAGMENT_ALLOC_DC(dc, flags)
#define GET_DATA(dc, flags) \
(((dc) == GLOBAL_DCONTEXT || TEST(FRAG_SHARED, (flags))) ? shared_data : \
(thread_data_t *) (dc)->vm_areas_field)
#define GET_VECTOR(dc, flags) \
(((dc) == GLOBAL_DCONTEXT || TEST(FRAG_SHARED, (flags))) ? \
(TEST(FRAG_WAS_DELETED, (flags)) ? NULL : &shared_data->areas) : \
(&((thread_data_t *)(dc)->vm_areas_field)->areas))
#define SHARED_VECTOR_RWLOCK(v, rw, op) do { \
if (TEST(VECTOR_SHARED, (v)->flags)) { \
ASSERT(SHARED_FRAGMENTS_ENABLED()); \
rw##_##op(&(v)->lock); \
} \
} while (0)
#define ASSERT_VMAREA_DATA_PROTECTED(data, RW) \
ASSERT_OWN_##RW##_LOCK((data == shared_data && \
!INTERNAL_OPTION(single_thread_in_DR)), \
&shared_data->areas.lock)
/* FIXME: find a way to assert that an area by itself is synchronized if
* it points into a vector for the routines that take in only areas
*/
#ifdef DEBUG
# define ASSERT_VMAREA_VECTOR_PROTECTED(v, RW) do { \
ASSERT_OWN_##RW##_LOCK(SHOULD_LOCK_VECTOR(v) && \
!dynamo_exited, &(v)->lock); \
if ((v) == dynamo_areas) { \
ASSERT(dynamo_areas_uptodate || dynamo_areas_synching); \
} \
} while (0);
#else
# define ASSERT_VMAREA_VECTOR_PROTECTED(v, RW) /* nothing */
#endif
/* size of security violation string - must be at least 16 */
#define MAXIMUM_VIOLATION_NAME_LENGTH 16
#define VMVECTOR_INITIALIZE_VECTOR(v, flags, lockname) do { \
vmvector_init_vector((v), (flags)); \
ASSIGN_INIT_READWRITE_LOCK_FREE((v)->lock, lockname); \
} while (0);
/* forward declarations */
static void
vmvector_free_vector(dcontext_t *dcontext, vm_area_vector_t *v);
static void
vm_area_clean_fraglist(dcontext_t *dcontext, vm_area_t *area);
static bool
lookup_addr(vm_area_vector_t *v, app_pc addr, vm_area_t **area);
#if defined(DEBUG) && defined(INTERNAL)
static void
print_fraglist(dcontext_t *dcontext, vm_area_t *area, const char *prefix);
static void
print_written_areas(file_t outf);
#endif
#ifdef DEBUG
static void
exec_area_bounds_match(dcontext_t *dcontext, thread_data_t *data);
#endif
static void
update_dynamo_vm_areas(bool have_writelock);
static void
dynamo_vm_areas_start_reading(void);
static void
dynamo_vm_areas_done_reading(void);
#ifdef PROGRAM_SHEPHERDING
static bool
remove_futureexec_vm_area(app_pc start, app_pc end);
DECLARE_CXTSWPROT_VAR(static mutex_t threads_killed_lock,
INIT_LOCK_FREE(threads_killed_lock));
void
mark_unload_future_added(app_pc module_base, size_t size);
#endif
static void
vm_area_coarse_region_freeze(dcontext_t *dcontext, coarse_info_t *info,
vm_area_t *area, bool in_place);
#ifdef SIMULATE_ATTACK
/* synch simulate_at string parsing */
DECLARE_CXTSWPROT_VAR(static mutex_t simulate_lock, INIT_LOCK_FREE(simulate_lock));
#endif
/* used to determine when we need to do another heap walk to keep
* dynamo vm areas up to date (can't do it incrementally b/c of
* circular dependencies).
* protected for both read and write by dynamo_areas->lock
*/
/* Case 3045: areas inside the vmheap reservation are not added to the list,
* so the vector is considered uptodate until we run out of reservation
*/
DECLARE_FREQPROT_VAR(static bool dynamo_areas_uptodate, true);
#ifdef DEBUG
/* used for debugging to tell when uptodate can be false.
* protected for both read and write by dynamo_areas->lock
*/
DECLARE_FREQPROT_VAR(static bool dynamo_areas_synching, false);
#endif
/* HACK to make dynamo_areas->lock recursive
* protected for both read and write by dynamo_areas->lock
* FIXME: provide general rwlock w/ write portion recursive
*/
DECLARE_CXTSWPROT_VAR(uint dynamo_areas_recursion, 0);
/* used for DR area debugging */
bool vm_areas_exited = false;
/***************************************************
* flushing by walking entire hashtable is too slow, so we keep a list of
* all fragments in each region.
* to save memory, we use the fragment_t struct as the linked list entry
* for these lists. However, some fragments are on multiple lists due to
* crossing boundaries (usually traces). For those, the other entries are
* pointed to by an "also" field, and the entries themselves use this struct,
* which plays games (similar to fcache's empty_slot_t) to be able to be used
* like a fragment_t struct in the lists.
*
* this is better than the old fragment_t->app_{min,max}_pc performance wise,
* and granularity-wise for blocks that bounce over regions, but worse
* granularity-wise since if want to flush singe page in text
* section, will end up flushing entire region. especially scary in face of
* merges of adjacent regions, but merges are rare for images since
* they usually have more than just text, so texts aren't adjacent.
*
* FIXME: better way, now that fcache supports multiple units, is to have
* a separate unit for each source vmarea. common case will be a flush to
* an un-merged or clipped area, so just toss whole unit.
*/
typedef struct _multi_entry_t {
fragment_t *f; /* backpointer */
/* flags MUST be at same location as fragment_t->flags
* we set flags==FRAG_IS_EXTRA_VMAREA to indicate a multi_entry_t
* we also use FRAG_SHARED to indicate that a multi_entry_t is on global heap
*/
uint flags;
/* officially all list entries are fragment_t *, really some are multi_entry_t */
fragment_t *next_vmarea;
fragment_t *prev_vmarea;
fragment_t *also_vmarea; /* if in multiple areas */
/* need to be able to look up vmarea: area not stored since vmareas
* shift and merge, so we store original pc */
app_pc pc;
} multi_entry_t;
/* macros to make dealing with both fragment_t and multi_entry_t easier */
#define FRAG_MULTI(f) (TEST(FRAG_IS_EXTRA_VMAREA, (f)->flags))
#define FRAG_MULTI_INIT(f) (TESTALL((FRAG_IS_EXTRA_VMAREA|FRAG_IS_EXTRA_VMAREA_INIT), (f)->flags))
#define FRAG_NEXT(f) ((TEST(FRAG_IS_EXTRA_VMAREA, (f)->flags)) ? \
((multi_entry_t *)(f))->next_vmarea : (f)->next_vmarea)
#define FRAG_NEXT_ASSIGN(f, val) do { \
if (TEST(FRAG_IS_EXTRA_VMAREA, (f)->flags)) \
((multi_entry_t *)(f))->next_vmarea = (val); \
else \
(f)->next_vmarea = (val); \
} while (0)
#define FRAG_PREV(f) ((TEST(FRAG_IS_EXTRA_VMAREA, (f)->flags)) ? \
((multi_entry_t *)(f))->prev_vmarea : (f)->prev_vmarea)
#define FRAG_PREV_ASSIGN(f, val) do { \
if (TEST(FRAG_IS_EXTRA_VMAREA, (f)->flags)) \
((multi_entry_t *)(f))->prev_vmarea = (val); \
else \
(f)->prev_vmarea = (val); \
} while (0)
/* Case 8419: also_vmarea is invalid once we 1st-stage-delete a fragment */
#define FRAG_ALSO(f) ((TEST(FRAG_IS_EXTRA_VMAREA, (f)->flags)) ? \
((multi_entry_t *)(f))->also_vmarea : \
(ASSERT(!TEST(FRAG_WAS_DELETED, (f)->flags)), (f)->also.also_vmarea))
/* Only call this one to avoid the assert when you know it's safe */
#define FRAG_ALSO_DEL_OK(f) ((TEST(FRAG_IS_EXTRA_VMAREA, (f)->flags)) ? \
((multi_entry_t *)(f))->also_vmarea : (f)->also.also_vmarea)
#define FRAG_ALSO_ASSIGN(f, val) do { \
if (TEST(FRAG_IS_EXTRA_VMAREA, (f)->flags)) \
((multi_entry_t *)(f))->also_vmarea = (val); \
else { \
ASSERT(!TEST(FRAG_WAS_DELETED, (f)->flags)); \
(f)->also.also_vmarea = (val); \
} \
} while (0)
/* assumption: if multiple units, fragment_t is on list of region owning tag */
#define FRAG_PC(f) ((TEST(FRAG_IS_EXTRA_VMAREA, (f)->flags)) ? \
((multi_entry_t *)(f))->pc : (f)->tag)
#define FRAG_PC_ASSIGN(f, val) do { \
if (TEST(FRAG_IS_EXTRA_VMAREA, (f)->flags)) \
((multi_entry_t *)(f))->pc = (val); \
else \
ASSERT_NOT_REACHED(); \
} while (0)
#define FRAG_FRAG(fr) ((TEST(FRAG_IS_EXTRA_VMAREA, (fr)->flags)) ? \
((multi_entry_t *)(fr))->f : (fr))
#define FRAG_FRAG_ASSIGN(fr, val) do { \
if (TEST(FRAG_IS_EXTRA_VMAREA, (fr)->flags)) \
((multi_entry_t *)(fr))->f = (val); \
else \
ASSERT_NOT_REACHED(); \
} while (0)
#define FRAG_ID(fr) ((TEST(FRAG_IS_EXTRA_VMAREA, (fr)->flags)) ? \
((multi_entry_t *)(fr))->f->id : (fr)->id)
/***************************************************/
/* FIXME : is problematic to page align subpage regions */
static void
vm_make_writable(byte *pc, size_t size)
{
byte *start_pc = (byte *)ALIGN_BACKWARD(pc, PAGE_SIZE);
size_t final_size = ALIGN_FORWARD(size + (pc - start_pc), PAGE_SIZE);
DEBUG_DECLARE(bool ok = )
make_writable(start_pc, final_size);
ASSERT(ok);
ASSERT(INTERNAL_OPTION(cache_consistency));
}
static void
vm_make_unwritable(byte *pc, size_t size)
{
byte *start_pc = (byte *)ALIGN_BACKWARD(pc, PAGE_SIZE);
size_t final_size = ALIGN_FORWARD(size + (pc - start_pc), PAGE_SIZE);
ASSERT(INTERNAL_OPTION(cache_consistency));
make_unwritable(start_pc, final_size);
/* case 8308: We should never call vm_make_unwritable if
* -sandbox_writable is on, or if -sandbox_non_text is on and this
* is a non-text region.
*/
ASSERT(!DYNAMO_OPTION(sandbox_writable));
DOCHECK(1, {
if (DYNAMO_OPTION(sandbox_non_text)) {
app_pc modbase = get_module_base(pc);
ASSERT(modbase != NULL && is_range_in_code_section(modbase, pc,
pc + size,
NULL, NULL));
}
});
}
/* since dynamorio changes some readwrite memory regions to read only,
* this changes all regions memory permissions back to what they should be,
* since dynamorio uses this mechanism to ensure code cache coherency,
* once this method is called stale code could be executed out of the
* code cache */
void
revert_memory_regions()
{
int i;
/* executable_areas doesn't exist in thin_client mode. */
ASSERT(!DYNAMO_OPTION(thin_client));
read_lock(&executable_areas->lock);
for (i = 0; i < executable_areas->length; i++) {
if (TEST(VM_MADE_READONLY, executable_areas->buf[i].vm_flags)) {
/* this is a region that dynamorio has marked read only, fix */
LOG(GLOBAL, LOG_VMAREAS, 1,
" fixing permissions for RW executable area "PFX"-"PFX" %s\n",
executable_areas->buf[i].start, executable_areas->buf[i].end,
executable_areas->buf[i].comment);
vm_make_writable(executable_areas->buf[i].start,
executable_areas->buf[i].end -
executable_areas->buf[i].start);
}
}
read_unlock(&executable_areas->lock);
}
static void
print_vm_flags(uint vm_flags, uint frag_flags, file_t outf)
{
print_file(outf, " %s%s%s%s",
(vm_flags & VM_WRITABLE) != 0 ? "W" : "-",
(vm_flags & VM_WAS_FUTURE) != 0 ? "F" : "-",
(frag_flags & FRAG_SELFMOD_SANDBOXED) != 0 ? "S" : "-",
TEST(FRAG_COARSE_GRAIN, frag_flags) ? "C" : "-");
#ifdef PROGRAM_SHEPHERDING
print_file(outf, "%s%s",
TEST(VM_PATTERN_REVERIFY, vm_flags) ? "P" : "-",
(frag_flags & FRAG_DYNGEN) != 0 ? "D" : "-");
#endif
}
/* ok to pass NULL for v, only used to identify use of custom field */
static void
print_vm_area(vm_area_vector_t *v, vm_area_t *area, file_t outf, const char *prefix)
{
print_file(outf, "%s"PFX"-"PFX, prefix, area->start, area->end);
print_vm_flags(area->vm_flags, area->frag_flags, outf);
if (v == executable_areas && TEST(FRAG_COARSE_GRAIN, area->frag_flags)) {
coarse_info_t *info = (coarse_info_t *) area->custom.client;
if (info != NULL) {
if (info->persisted)
print_file(outf, "R");
else if (info->frozen)
print_file(outf, "Z");
else
print_file(outf, "-");
}
}
#ifdef DEBUG
print_file(outf, " %s", area->comment);
DOLOG(1, LOG_VMAREAS, {
IF_NO_MEMQUERY(extern vm_area_vector_t *all_memory_areas;)
app_pc modbase =
/* avoid rank order violation */
IF_NO_MEMQUERY(v == all_memory_areas ? NULL :)
get_module_base(area->start);
if (modbase != NULL &&
/* avoid rank order violations */
v != dynamo_areas &&
v != written_areas &&
/* we free module list before vmareas */
!dynamo_exited_and_cleaned &&
is_mapped_as_image(modbase)/*avoid asserts in getting name */) {
const char *name;
os_get_module_info_lock();
os_get_module_name(modbase, &name);
print_file(outf, " %s", name == NULL ? "" : name);
os_get_module_info_unlock();
}
});
#endif
if (v == written_areas) {
ro_vs_sandbox_data_t *ro2s = (ro_vs_sandbox_data_t *) area->custom.client;
#ifdef DEBUG
if (ro2s != NULL) { /* can be null if in middle of adding */
uint tot_w = ro2s->ro2s_xfers * DYNAMO_OPTION(ro2sandbox_threshold);
uint tot_s = ro2s->s2ro_xfers * DYNAMO_OPTION(sandbox2ro_threshold);
print_file(outf, " w %3d, %3d tot; x %3d, %5d tot; ro2s %d, s2ro %d",
ro2s->written_count, tot_w, ro2s->selfmod_execs, tot_s,
ro2s->ro2s_xfers, ro2s->s2ro_xfers);
}
#else
print_file(outf, " written %3d, exec %5d",
ro2s->written_count, ro2s->selfmod_execs);
#endif
}
print_file(outf, "\n");
}
/* Assumes caller holds v->lock for coherency */
static void
print_vm_areas(vm_area_vector_t *v, file_t outf)
{
int i;
ASSERT_VMAREA_VECTOR_PROTECTED(v, READWRITE);
for(i = 0; i < v->length; i++) {
print_vm_area(v, &v->buf[i], outf, " ");
}
}
#if defined(DEBUG) && defined(INTERNAL)
static void
print_contig_vm_areas(vm_area_vector_t *v, app_pc start, app_pc end, file_t outf,
const char *prefix)
{
vm_area_t *new_area;
app_pc pc = start;
do {
lookup_addr(v, pc, &new_area);
if (new_area == NULL)
break;
print_vm_area(v, new_area, outf, prefix);
pc = new_area->end + 1;
} while (new_area->end < end);
}
#endif
#if defined(DEBUG) && defined(INTERNAL)
static void
print_pending_list(file_t outf)
{
pending_delete_t *pend;
int i;
ASSERT_OWN_MUTEX(true, &shared_delete_lock);
for (i = 0, pend = todelete->shared_delete; pend != NULL; i++, pend = pend->next) {
print_file(outf, "%d: "PFX"-"PFX" ref=%d, stamp=%d\n",
i, pend->start, pend->end, pend->ref_count, pend->flushtime_deleted);
}
}
#endif
/* If v requires a lock and the calling thread does not hold that lock,
* this routine acquires the lock and returns true; else it returns false.
*/
static bool
writelock_if_not_already(vm_area_vector_t *v)
{
if (TEST(VECTOR_SHARED, v->flags) && !self_owns_write_lock(&v->lock)) {
SHARED_VECTOR_RWLOCK(v, write, lock);
return true;
}
return false;
}
static void
vm_area_vector_check_size(vm_area_vector_t *v)
{
/* only called by add_vm_area which does the assert that the vector is
* protected */
/* check if at capacity */
if (v->size == v->length){
if (v->length == 0) {
v->size = INTERNAL_OPTION(vmarea_initial_size);
v->buf = (vm_area_t*) global_heap_alloc(v->size*sizeof(struct vm_area_t)
HEAPACCT(ACCT_VMAREAS));
}
else {
/* FIXME: case 4471 we should be doubling size here */
int new_size = (INTERNAL_OPTION(vmarea_increment_size) + v->length);
STATS_INC(num_vmareas_resized);
v->buf = global_heap_realloc(v->buf, v->size, new_size,
sizeof(struct vm_area_t)
HEAPACCT(ACCT_VMAREAS));
v->size = new_size;
}
ASSERT(v->buf != NULL);
}
}
static void
vm_area_merge_fraglists(vm_area_t *dst, vm_area_t *src)
{
/* caller must hold write lock for vector of course: FIXME: assert that here */
LOG(THREAD_GET, LOG_VMAREAS, 2,
"\tmerging frag lists for "PFX"-"PFX" and "PFX"-"PFX"\n",
src->start, src->end, dst->start, dst->end);
if (dst->custom.frags == NULL)
dst->custom.frags = src->custom.frags;
else if (src->custom.frags == NULL)
return;
else {
/* put src's frags at end of dst's frags */
fragment_t *top1 = dst->custom.frags;
fragment_t *top2 = src->custom.frags;
fragment_t *tmp = FRAG_PREV(top1);
FRAG_NEXT_ASSIGN(tmp, top2);
FRAG_PREV_ASSIGN(top1, FRAG_PREV(top2));
FRAG_PREV_ASSIGN(top2, tmp);
DOLOG(4, LOG_VMAREAS, {
print_fraglist(get_thread_private_dcontext(),
dst, "after merging fraglists:");
});
}
}
/* Assumes caller holds v->lock, if necessary.
* Does not return the area added since it may be merged or split depending
* on existing areas->
* If a last_area points into this vector, the caller must make sure to
* clear or update the last_area pointer.
* FIXME: make it easier to keep them in synch -- too easy to add_vm_area
* somewhere to a thread vector and forget to clear last_area.
* Adds a new area to v, merging it with adjacent areas of the same type.
* A new area is only allowed to overlap an old area of a different type if it
* meets certain criteria (see asserts below). For VM_WAS_FUTURE and
* VM_ONCE_ONLY we may clear the flag from an existing region if the new
* region doesn't have the flag and overlaps the existing region. Otherwise
* the new area is split such that the overlapping portion remains part of
* the old area. This tries to keep entire new area from becoming selfmod
* for instance. FIXME : for VM_WAS_FUTURE and VM_ONCE_ONLY may want to split
* region if only paritally overlapping
*
* FIXME: change add_vm_area to return NULL when merged, and otherwise
* return the new complete area, so callers don't have to do a separate lookup
* to access the added area.
*/
static void
add_vm_area(vm_area_vector_t *v, app_pc start, app_pc end,
uint vm_flags, uint frag_flags, void *data _IF_DEBUG(const char *comment))
{
int i, j, diff;
/* if we have overlap, we extend an existing area -- else we add a new area */
int overlap_start = -1, overlap_end = -1;
DEBUG_DECLARE(uint flagignore;)
ASSERT(start < end);
ASSERT_VMAREA_VECTOR_PROTECTED(v, WRITE);
LOG(GLOBAL, LOG_VMAREAS, 4, "in add_vm_area "PFX" "PFX" %s\n", start, end, comment);
/* N.B.: new area could span multiple existing areas! */
for (i = 0; i < v->length; i++) {
/* look for overlap, or adjacency of same type (including all flags, and never
* merge adjacent if keeping write counts)
*/
if ((start < v->buf[i].end && end > v->buf[i].start) ||
(start <= v->buf[i].end && end >= v->buf[i].start &&
vm_flags == v->buf[i].vm_flags &&
frag_flags == v->buf[i].frag_flags &&
/* never merge coarse-grain */
!TEST(FRAG_COARSE_GRAIN, v->buf[i].frag_flags) &&
!TEST(VECTOR_NEVER_MERGE_ADJACENT, v->flags) &&
(v->should_merge_func == NULL ||
v->should_merge_func(true/*adjacent*/, data, v->buf[i].custom.client)))) {
ASSERT(!(start < v->buf[i].end && end > v->buf[i].start) ||
!TEST(VECTOR_NEVER_OVERLAP, v->flags));
if (overlap_start == -1) {
/* assume we'll simply expand an existing area rather than
* add a new one -- we'll reset this if we hit merge conflicts */
overlap_start = i;
}
/* overlapping regions of different properties are often
* problematic so we add a lot of debugging output
*/
DOLOG(4, LOG_VMAREAS, {
LOG(GLOBAL, LOG_VMAREAS, 1,
"==================================================\n"
"add_vm_area "PFX"-"PFX" %s %x-%x overlaps "PFX"-"PFX" %s %x-%x\n",
start, end, comment, vm_flags, frag_flags,
v->buf[i].start, v->buf[i].end,
v->buf[i].comment, v->buf[i].vm_flags, v->buf[i].frag_flags);
print_vm_areas(v, GLOBAL);
/* rank order problem if holding heap_unit_lock, so only print
* if not holding a lock for v right now, though ok to print
* for shared vm areas since its lock is higher than the lock
* for executable/written areas
*/
if (v != dynamo_areas &&
(!TEST(VECTOR_SHARED, v->flags) || v == &shared_data->areas)) {
LOG(GLOBAL, LOG_VMAREAS, 1, "\nexecutable areas:\n");
print_executable_areas(GLOBAL);
LOG(GLOBAL, LOG_VMAREAS, 1, "\nwritten areas:\n");
print_written_areas(GLOBAL);
}
LOG(GLOBAL, LOG_VMAREAS, 1,
"==================================================\n\n");
});
/* we have some restrictions on overlapping regions with
* different flags */
/* no restrictions on WAS_FUTURE flag, but if new region is
* not was future and old region is then should drop from old
* region FIXME : partial overlap? we don't really care about
* this flag anyways */
if (TEST(VM_WAS_FUTURE, v->buf[i].vm_flags) &&
!TEST(VM_WAS_FUTURE, vm_flags)) {
v->buf[i].vm_flags &= ~VM_WAS_FUTURE;
LOG(GLOBAL, LOG_VMAREAS, 1,
"Warning : removing was_future flag from area "PFX
"-"PFX" %s that overlaps new area "PFX"-"PFX" %s\n",
v->buf[i].start, v->buf[i].end, v->buf[i].comment,
start, end, comment);
}
/* no restrictions on ONCE_ONLY flag, but if new region is not
* should drop fom existing region FIXME : partial overlap? is
* not much of an additional security risk */
if (TEST(VM_ONCE_ONLY, v->buf[i].vm_flags) &&
!TEST(VM_ONCE_ONLY, vm_flags)) {
v->buf[i].vm_flags &= ~VM_ONCE_ONLY;
LOG(GLOBAL, LOG_VMAREAS, 1,
"Warning : removing once_only flag from area "PFX
"-"PFX" %s that overlaps new area "PFX"-"PFX" %s\n",
v->buf[i].start, v->buf[i].end, v->buf[i].comment,
start, end, comment);
}
/* shouldn't be adding unmod image over existing not unmod image,
* reverse could happen with os region merging though */
ASSERT(TEST(VM_UNMOD_IMAGE, v->buf[i].vm_flags) ||
!TEST(VM_UNMOD_IMAGE, vm_flags));
/* for VM_WRITABLE only allow new region to not be writable and
* existing region to be writable to handle cases of os region
* merging due to our consistency protection changes */
ASSERT(TEST(VM_WRITABLE, v->buf[i].vm_flags) ||
!TEST(VM_WRITABLE, vm_flags));
/* FIXME: case 7877: if new is VM_MADE_READONLY and old is not, we
* must mark old overlapping portion as VM_MADE_READONLY. Things only
* worked now b/c VM_MADE_READONLY==VM_WRITABLE, so we can add
* pageprot regions that overlap w/ selfmod.
*/
#ifdef PROGRAM_SHEPHERDING
/* !VM_PATTERN_REVERIFY trumps having the flag on, so for new having
* the flag and old not, we're fine, but when old has it we'd like
* to remove it from the overlap portion: FIXME: need better merging
* control, also see all the partial overlap fixmes above.
* for this flag not a big deal, just a possible perf hit as we
* re-check every time.
*/
#endif
/* disallow any other vm_flag differences */
DODEBUG({ flagignore = VM_UNMOD_IMAGE | VM_WAS_FUTURE |
VM_ONCE_ONLY | VM_WRITABLE; });
#ifdef PROGRAM_SHEPHERDING
DODEBUG({ flagignore = flagignore | VM_PATTERN_REVERIFY; });
#endif
ASSERT((v->buf[i].vm_flags & ~flagignore) == (vm_flags & ~flagignore));
/* new region must be more innocent with respect to selfmod */
ASSERT(TEST(FRAG_SELFMOD_SANDBOXED, v->buf[i].frag_flags) ||
!TEST(FRAG_SELFMOD_SANDBOXED, frag_flags));
/* disallow other frag_flag differences */
#ifndef PROGRAM_SHEPHERDING
ASSERT((v->buf[i].frag_flags & ~FRAG_SELFMOD_SANDBOXED) ==
(frag_flags & ~FRAG_SELFMOD_SANDBOXED));
#else
# ifdef DGC_DIAGNOSTICS
/* FIXME : no restrictions on differing FRAG_DYNGEN_RESTRICTED
* flags? */
ASSERT((v->buf[i].frag_flags &
~(FRAG_SELFMOD_SANDBOXED|FRAG_DYNGEN|FRAG_DYNGEN_RESTRICTED)) ==
(frag_flags &
~(FRAG_SELFMOD_SANDBOXED|FRAG_DYNGEN|FRAG_DYNGEN_RESTRICTED)));
# else
ASSERT((v->buf[i].frag_flags &
~(FRAG_SELFMOD_SANDBOXED|FRAG_DYNGEN)) ==
(frag_flags &
~(FRAG_SELFMOD_SANDBOXED|FRAG_DYNGEN)));
# endif
/* shouldn't add non-dyngen overlapping existing dyngen, FIXME
* is the reverse possible? right now we allow it */
ASSERT(TEST(FRAG_DYNGEN, frag_flags) ||
!TEST(FRAG_DYNGEN, v->buf[i].frag_flags));
#endif
/* Never split FRAG_COARSE_GRAIN */
ASSERT(TEST(FRAG_COARSE_GRAIN, frag_flags) ||
!TEST(FRAG_COARSE_GRAIN, v->buf[i].frag_flags));
/* for overlapping region: must overlap same type -- else split */
if ((vm_flags != v->buf[i].vm_flags || frag_flags != v->buf[i].frag_flags) &&
(v->should_merge_func == NULL ||
!v->should_merge_func(false/*not adjacent*/,
data, v->buf[i].custom.client))) {
LOG(GLOBAL, LOG_VMAREAS, 1,
"add_vm_area "PFX"-"PFX" %s vm_flags=0x%08x "
"frag_flags=0x%08x\n overlaps diff type "PFX"-"PFX" %s"
"vm_flags=0x%08x frag_flags=0x%08x\n in vect at "PFX"\n",
start, end, comment, vm_flags, frag_flags,
v->buf[i].start, v->buf[i].end, v->buf[i].comment,
v->buf[i].vm_flags, v->buf[i].frag_flags, v);
LOG(GLOBAL, LOG_VMAREAS, 3,
"before splitting b/c adding "PFX"-"PFX":\n",
start, end);
DOLOG(3, LOG_VMAREAS, { print_vm_areas(v, GLOBAL); });
/* split off the overlapping part from the new region
* reasoning: old regions get marked selfmod, then see new code,
* its region overlaps old selfmod -- don't make new all selfmod,
* split off the part that hasn't been proved selfmod yet.
* since we never split the old region, we don't need to worry
* about splitting its frags list.
*/
if (start < v->buf[i].start) {
if (end > v->buf[i].end) {
void *add_data = data;
/* need two areas, one for either side */
LOG(GLOBAL, LOG_VMAREAS, 3,
"=> will add "PFX"-"PFX" after i\n", v->buf[i].end, end);
/* safe to recurse here, new area will be after the area
* we are currently looking at in the vector */
if (v->split_payload_func != NULL)
add_data = v->split_payload_func(data);
add_vm_area(v, v->buf[i].end, end, vm_flags, frag_flags,
add_data _IF_DEBUG(comment));
}
/* if had been merging, let this routine finish that off -- else,
* need to add a new area
*/
end = v->buf[i].start;
if (overlap_start == i) {
/* no merging */
overlap_start = -1;
}
LOG(GLOBAL, LOG_VMAREAS, 3,
"=> will add/merge "PFX"-"PFX" before i\n", start, end);
overlap_end = i;
break;
} else if (end > v->buf[i].end) {
/* shift area of consideration to end of i, and keep going,
* can't act now since don't know areas overlapping beyond i
*/
LOG(GLOBAL, LOG_VMAREAS, 3,
"=> ignoring "PFX"-"PFX", only adding "PFX"-"PFX"\n",
start, v->buf[i].end, v->buf[i].end, end);
start = v->buf[i].end;
/* reset overlap vars */
ASSERT(overlap_start <= i);
overlap_start = -1;
} else {
/* completely inside -- ok, we'll leave it that way and won't split */
LOG(GLOBAL, LOG_VMAREAS, 3,
"=> ignoring "PFX"-"PFX", forcing to be part of "PFX"-"PFX"\n",
start, end, v->buf[i].start, v->buf[i].end);
}
ASSERT(end > start);
}
} else if (overlap_start > -1) {
overlap_end = i; /* not inclusive */
break;
} else if (end <= v->buf[i].start)
break;
}
if (overlap_start == -1) {
/* brand-new area, goes before v->buf[i] */
struct vm_area_t new_area = {start, end, vm_flags, frag_flags, /* rest 0 */};
#ifdef DEBUG
/* get comment */
size_t len = strlen(comment);
ASSERT(len < 1024);
new_area.comment = (char *) global_heap_alloc(len+1 HEAPACCT(ACCT_VMAREAS));
strncpy(new_area.comment, comment, len);
new_area.comment[len] = '\0'; /* if max no null */
#endif
new_area.custom.client = data;
LOG(GLOBAL, LOG_VMAREAS, 3, "=> adding "PFX"-"PFX"\n", start, end);
vm_area_vector_check_size(v);
/* shift subsequent entries */
for (j = v->length; j > i; j--)
v->buf[j] = v->buf[j-1];
v->buf[i] = new_area;
/* assumption: no overlaps between areas in list! */
#ifdef DEBUG
if (!((i == 0 || v->buf[i-1].end <= v->buf[i].start) &&
(i == v->length || v->buf[i].end <= v->buf[i+1].start))) {
LOG(GLOBAL, LOG_VMAREAS, 1,
"ERROR: add_vm_area illegal overlap "PFX" "PFX" %s\n", start, end, comment);
print_vm_areas(v, GLOBAL);
}
#endif
ASSERT((i == 0 || v->buf[i-1].end <= v->buf[i].start) &&
(i == v->length || v->buf[i].end <= v->buf[i+1].start));
v->length++;
STATS_TRACK_MAX(max_vmareas_length, v->length);
DOSTATS({
if (v == dynamo_areas)
STATS_TRACK_MAX(max_DRareas_length, v->length);
else if (v == executable_areas)
STATS_TRACK_MAX(max_execareas_length, v->length);
});
#ifdef WINDOWS
DOSTATS({
extern vm_area_vector_t *loaded_module_areas;
if (v == loaded_module_areas)
STATS_TRACK_MAX(max_modareas_length, v->length);
});
#endif
} else {
/* overlaps one or more areas, modify first to equal entire range,
* delete rest
*/
if (overlap_end == -1)
overlap_end = v->length;
LOG(GLOBAL, LOG_VMAREAS, 3, "=> changing "PFX"-"PFX,
v->buf[overlap_start].start, v->buf[overlap_start].end);
if (start < v->buf[overlap_start].start)
v->buf[overlap_start].start = start;
if (end > v->buf[overlap_end-1].end)
v->buf[overlap_start].end = end;
else
v->buf[overlap_start].end = v->buf[overlap_end-1].end;
if (v->merge_payload_func != NULL) {
v->buf[overlap_start].custom.client =
v->merge_payload_func(data, v->buf[overlap_start].custom.client);
} else if (v->free_payload_func != NULL) {
/* if a merge exists we assume it will free if necessary */
v->free_payload_func(v->buf[overlap_start].custom.client);
}
LOG(GLOBAL, LOG_VMAREAS, 3, " to "PFX"-"PFX"\n",
v->buf[overlap_start].start, v->buf[overlap_start].end);
/* when merge, use which comment? could combine them all
* FIXME
*/
/* now delete */
for (i = overlap_start+1; i < overlap_end; i++) {
LOG(GLOBAL, LOG_VMAREAS, 3, "=> completely removing "PFX"-"PFX" %s\n",
v->buf[i].start, v->buf[i].end, v->buf[i].comment);
#ifdef DEBUG
global_heap_free(v->buf[i].comment, strlen(v->buf[i].comment)+1
HEAPACCT(ACCT_VMAREAS));
#endif
if (v->merge_payload_func != NULL) {
v->buf[overlap_start].custom.client =
v->merge_payload_func(v->buf[overlap_start].custom.client,
v->buf[i].custom.client);
} else if (v->free_payload_func != NULL) {
/* if a merge exists we assume it will free if necessary */
v->free_payload_func(v->buf[i].custom.client);
}
/* merge frags lists */
/* FIXME: switch this to a merge_payload_func. It won't be able
* to print out the bounds, and it will have to do the work of
* vm_area_clean_fraglist() on each merge, but we could then get
* rid of VECTOR_FRAGMENT_LIST.
*/
if (TEST(VECTOR_FRAGMENT_LIST, v->flags) && v->buf[i].custom.frags != NULL)
vm_area_merge_fraglists(&v->buf[overlap_start], &v->buf[i]);
}
diff = overlap_end - (overlap_start+1);
for (i = overlap_start+1; i < v->length-diff; i++)
v->buf[i] = v->buf[i+diff];
v->length -= diff;
i = overlap_start; /* for return value */
if (TEST(VECTOR_FRAGMENT_LIST, v->flags) && v->buf[i].custom.frags != NULL) {
dcontext_t *dcontext = get_thread_private_dcontext();
ASSERT(dcontext != NULL);
/* have to remove all alsos that are now in same area as frag */
vm_area_clean_fraglist(dcontext, &v->buf[i]);
}
}
DOLOG(5, LOG_VMAREAS, { print_vm_areas(v, GLOBAL); });
}
static void
adjust_coarse_unit_bounds(vm_area_t *area, bool if_invalid)
{
coarse_info_t *info = (coarse_info_t *) area->custom.client;
ASSERT(TEST(FRAG_COARSE_GRAIN, area->frag_flags));
ASSERT(!RUNNING_WITHOUT_CODE_CACHE());
ASSERT(info != NULL);
if (info == NULL) /* be paranoid */
return;
/* FIXME: we'd like to grab info->lock but we have a rank order w/
* exec_areas lock -- so instead we rely on all-thread-synch flushing
* being the only reason to get here; an empty flush won't have synchall,
* but we won't be able to get_executable_area_coarse_info w/o the
* exec areas write lock so we're ok there.
*/
ASSERT(dynamo_all_threads_synched ||
(!TEST(VM_EXECUTED_FROM, area->vm_flags) &&
READWRITE_LOCK_HELD(&executable_areas->lock)));
if (!if_invalid && TEST(PERSCACHE_CODE_INVALID, info->flags)) {
/* Don't change bounds of primary or secondary; we expect vm_area_t to
* be merged back to this size post-rebind; if not, we'll throw out this
* pcache at validation time due to not matching the vm_area_t.
*/
return;
}
while (info != NULL) { /* loop over primary and secondary unit */
/* We should have reset this coarse info when flushing */
ASSERT(info->cache == NULL);
ASSERT(!info->frozen && !info->persisted);
/* No longer covers the removed region */
if (info->base_pc < area->start)
info->base_pc = area->start;
if (info->end_pc > area->end)
info->end_pc = area->end;
ASSERT(info->frozen || info->non_frozen == NULL);
info = info->non_frozen;
ASSERT(info == NULL || !info->frozen);
}
}
/* Assumes caller holds v->lock, if necessary
* Returns false if no area contains start..end
* Ignores type of area -- removes all within start..end
* Caller should probably clear last_area as well
*/
static bool
remove_vm_area(vm_area_vector_t *v, app_pc start, app_pc end, bool restore_prot)
{
int i, diff;
int overlap_start = -1, overlap_end = -1;
bool add_new_area = false;
vm_area_t new_area = {0}; /* used only when add_new_area, wimpy compiler */
/* FIXME: cleaner test? shared_data copies flags, but uses
* custom.frags and not custom.client
*/
bool official_coarse_vector = (v == executable_areas);
ASSERT_VMAREA_VECTOR_PROTECTED(v, WRITE);
LOG(GLOBAL, LOG_VMAREAS, 4, "in remove_vm_area "PFX" "PFX"\n", start, end);
/* N.B.: removed area could span multiple areas! */
for (i = 0; i < v->length; i++) {
/* look for overlap */
if (start < v->buf[i].end && end > v->buf[i].start) {
if (overlap_start == -1)
overlap_start = i;
} else if (overlap_start > -1) {
overlap_end = i; /* not inclusive */
break;
} else if (end <= v->buf[i].start)
break;
}
if (overlap_start == -1)
return false;
if (overlap_end == -1)
overlap_end = v->length;
/* since it's sorted and there are no overlaps, we do not have to re-sort.
* we just delete entire intervals affected, and shorten non-entire
*/
if (start > v->buf[overlap_start].start) {
/* need to split? */
if (overlap_start == overlap_end-1 && end < v->buf[overlap_start].end) {
/* don't call add_vm_area now, that will mess up our vector */
new_area = v->buf[overlap_start]; /* make a copy */
new_area.start = end;
/* rest of fields are correct */
add_new_area = true;
}
/* move ending bound backward */
LOG(GLOBAL, LOG_VMAREAS, 3, "\tchanging "PFX"-"PFX" to "PFX"-"PFX"\n",
v->buf[overlap_start].start, v->buf[overlap_start].end,
v->buf[overlap_start].start, start);
if (restore_prot && TEST(VM_MADE_READONLY, v->buf[overlap_start].vm_flags)) {
vm_make_writable(start, end - start);
}
v->buf[overlap_start].end = start;
/* FIXME: add a vmvector callback function for changing bounds? */
if (TEST(FRAG_COARSE_GRAIN, v->buf[overlap_start].frag_flags) &&
official_coarse_vector) {
adjust_coarse_unit_bounds(&v->buf[overlap_start], false/*leave invalid*/);
}
overlap_start++; /* don't delete me */
}
if (end < v->buf[overlap_end-1].end) {
/* move starting bound forward */
LOG(GLOBAL, LOG_VMAREAS, 3, "\tchanging "PFX"-"PFX" to "PFX"-"PFX"\n",
v->buf[overlap_end-1].start, v->buf[overlap_end-1].end,
end, v->buf[overlap_end-1].end);
if (restore_prot && TEST(VM_MADE_READONLY, v->buf[overlap_end-1].vm_flags)) {
vm_make_writable(v->buf[overlap_end-1].start, end - v->buf[overlap_end-1].start);
}
v->buf[overlap_end-1].start = end;
/* FIXME: add a vmvector callback function for changing bounds? */
if (TEST(FRAG_COARSE_GRAIN, v->buf[overlap_end-1].frag_flags) &&
official_coarse_vector) {
adjust_coarse_unit_bounds(&v->buf[overlap_end-1], false/*leave invalid*/);
}
overlap_end--; /* don't delete me */
}
/* now delete */
if (overlap_start < overlap_end) {
for (i = overlap_start; i < overlap_end; i++) {
LOG(GLOBAL, LOG_VMAREAS, 3, "\tcompletely removing "PFX"-"PFX" %s\n",
v->buf[i].start, v->buf[i].end, v->buf[i].comment);
if (restore_prot && TEST(VM_MADE_READONLY, v->buf[i].vm_flags)) {
vm_make_writable(v->buf[i].start, v->buf[i].end - v->buf[i].start);
}
/* FIXME: use a free_payload_func instead of this custom
* code. But then we couldn't assert on the bounds and on
* VM_EXECUTED_FROM. Could add bounds to callback params, but
* vm_flags are not exposed to vmvector interface...
*/
if (TEST(FRAG_COARSE_GRAIN, v->buf[i].frag_flags) &&
official_coarse_vector) {
coarse_info_t *info = (coarse_info_t *) v->buf[i].custom.client;
coarse_info_t *next_info;
ASSERT(info != NULL);
ASSERT(!RUNNING_WITHOUT_CODE_CACHE());
while (info != NULL) { /* loop over primary and secondary unit */
ASSERT(info->base_pc >= v->buf[i].start &&
info->end_pc <= v->buf[i].end);
ASSERT(info->frozen || info->non_frozen == NULL);
/* Should have already freed fields, unless we flushed a region
* that has not been executed from (case 10995): in which case
* we must delay as we cannot grab change_linking_lock or
* special_heap_lock or info->lock while holding exec_areas lock.
*/
if (info->cache != NULL) {
ASSERT(info->persisted);
ASSERT(!TEST(VM_EXECUTED_FROM, v->buf[i].vm_flags));
ASSERT(info->non_frozen != NULL);
ASSERT(coarse_to_delete != NULL);
/* Both primary and secondary must be un-executed */
info->non_frozen->non_frozen = *coarse_to_delete;
*coarse_to_delete = info;
info = NULL;
} else {
ASSERT(info->cache == NULL && info->stubs == NULL);
next_info = info->non_frozen;
coarse_unit_free(GLOBAL_DCONTEXT, info);
info = next_info;
ASSERT(info == NULL || !info->frozen);
}
}
v->buf[i].custom.client = NULL;
}
if (v->free_payload_func != NULL) {
v->free_payload_func(v->buf[i].custom.client);
}
#ifdef DEBUG
global_heap_free(v->buf[i].comment, strlen(v->buf[i].comment)+1
HEAPACCT(ACCT_VMAREAS));
#endif
/* frags list should always be null here (flush should have happened,
* etc.) */
ASSERT(!TEST(VECTOR_FRAGMENT_LIST, v->flags) || v->buf[i].custom.frags == NULL);
}
diff = overlap_end - overlap_start;
for (i = overlap_start; i < v->length-diff; i++)
v->buf[i] = v->buf[i+diff];
#ifdef DEBUG
memset(v->buf + v->length - diff, 0, diff * sizeof(vm_area_t));
#endif
v->length -= diff;
}
if (add_new_area) {
/* Case 8640: Do not propagate coarse-grain-ness to split-off region,
* for now only for simplicity. FIXME: come up with better policy. We
* do keep it on original part of split region. FIXME: assert that
* there the unit is fully flushed. Better to remove in
* vm_area_allsynch_flush_fragments() and then re-add if warranted?
*/
new_area.frag_flags &= ~FRAG_COARSE_GRAIN;
/* With flush of partial module region w/o remove (e.g., from
* -unsafe_ignore_IAT_writes) we can have VM_ADD_TO_SHARED_DATA set
*/
new_area.vm_flags &= ~VM_ADD_TO_SHARED_DATA;
LOG(GLOBAL, LOG_VMAREAS, 3, "\tadding "PFX"-"PFX"\n", new_area.start, new_area.end);
/* we copied v->buf[overlap_start] above and so already have a copy
* of the client field
*/
if (v->split_payload_func != NULL) {
new_area.custom.client = v->split_payload_func(new_area.custom.client);
} /* else, just keep the copy */
add_vm_area(v, new_area.start, new_area.end, new_area.vm_flags,
new_area.frag_flags, new_area.custom.client
_IF_DEBUG(new_area.comment));
}
DOLOG(5, LOG_VMAREAS, { print_vm_areas(v, GLOBAL); });
return true;
}
/* Returns true if start..end overlaps any area in v.
* If end==NULL, assumes that end is very top of address space (wraparound).
* If area!=NULL, sets *area to an overlapping area in v
* If index!=NULL, sets *index to the vector index of area; if no match
* is found, sets *index to the index before [start,end) (may be -1).
* If first, makes sure *area is the 1st overlapping area
* Assumes caller holds v->lock, if necessary
* N.B.: the pointer returned by this routine is volatile! Only use it while
* you have exclusive control over the vector v, either by holding its lock
* or by being its owning thread if it has no lock.
*/
static bool
binary_search(vm_area_vector_t *v, app_pc start, app_pc end, vm_area_t **area/*OUT*/,
int *index/*OUT*/, bool first)
{
/* BINARY SEARCH -- assumes the vector is kept sorted by add & remove! */
int min = 0;
int max = v->length - 1;
ASSERT(start < end || end == NULL /* wraparound */);
ASSERT_VMAREA_VECTOR_PROTECTED(v, READWRITE);
LOG(GLOBAL, LOG_VMAREAS, 7, "Binary search for "PFX"-"PFX" on this vector:\n",
start, end);
DOLOG(7, LOG_VMAREAS, { print_vm_areas(v, GLOBAL); });
/* binary search */
while (max >= min) {
int i = (min + max) / 2;
if (end != NULL && end <= v->buf[i].start)
max = i - 1;
else if (start >= v->buf[i].end)
min = i + 1;
else {
if (area != NULL || index != NULL) {
if (first) {
/* caller wants 1st matching area */
for (; i >= 1 && v->buf[i-1].end > start; i--)
;
}
/* returning pointer to volatile array dangerous -- see comment above */
if (area != NULL)
*area = &(v->buf[i]);
if (index != NULL)
*index = i;
}
LOG(GLOBAL, LOG_VMAREAS, 7, "\tfound "PFX"-"PFX" in area "PFX"-"PFX"\n",
start, end, v->buf[i].start, v->buf[i].end);
return true;
}
}
/* now max < min */
LOG(GLOBAL, LOG_VMAREAS, 7, "\tdid not find "PFX"-"PFX"!\n", start, end);
if (index != NULL) {
ASSERT((max < 0 || v->buf[max].end <= start) &&
(min > v->length - 1 || v->buf[min].start >= end));
*index = max;
}
return false;
}
/* lookup an addr in the current area
* RETURN true if address area is found, false otherwise
* if area is non NULL it is set to the area found
* Assumes caller holds v->lock, if necessary
* N.B.: the pointer returned by this routine is volatile! Only use it while
* you have exclusive control over the vector v, either by holding its lock
* or by being its owning thread if it has no lock.
*/
/* FIXME: change lookup_addr to two routines, one for readers which
* returns a copy, and the other for writers who must hold a lock
* across all uses of the pointer
*/
static bool
lookup_addr(vm_area_vector_t *v, app_pc addr, vm_area_t **area)
{
/* binary search asserts v is protected */
return binary_search(v, addr, addr+1/*open end*/, area, NULL, false);
}
/* returns true if the passed in area overlaps any known executable areas
* Assumes caller holds v->lock, if necessary
*/
static bool
vm_area_overlap(vm_area_vector_t *v, app_pc start, app_pc end)
{
/* binary search asserts v is protected */
return binary_search(v, start, end, NULL, NULL, false);
}
/*********************** EXPORTED ROUTINES **********************/
/* thread-shared initialization that should be repeated after a reset */
void
vm_areas_reset_init(void)
{
memset(shared_data, 0, sizeof(*shared_data));
VMVECTOR_INITIALIZE_VECTOR(&shared_data->areas,
VECTOR_SHARED | VECTOR_FRAGMENT_LIST, shared_vm_areas);
}
void
dynamo_vm_areas_init()
{
VMVECTOR_ALLOC_VECTOR(dynamo_areas, GLOBAL_DCONTEXT, VECTOR_SHARED,
dynamo_areas);
}
/* calls find_executable_vm_areas to get per-process map
* N.B.: add_dynamo_vm_area can be called before this init routine!
* N.B.: this is called after vm_areas_thread_init()
*/
int
vm_areas_init()
{
int areas;
/* Case 7957: we allocate all vm vectors on the heap for self-prot reasons.
* We're already paying the indirection cost by passing their addresses
* to generic routines, after all.
*/
VMVECTOR_ALLOC_VECTOR(executable_areas, GLOBAL_DCONTEXT, VECTOR_SHARED,
executable_areas);
VMVECTOR_ALLOC_VECTOR(pretend_writable_areas, GLOBAL_DCONTEXT, VECTOR_SHARED,
pretend_writable_areas);
VMVECTOR_ALLOC_VECTOR(patch_proof_areas, GLOBAL_DCONTEXT, VECTOR_SHARED,
patch_proof_areas);
VMVECTOR_ALLOC_VECTOR(emulate_write_areas, GLOBAL_DCONTEXT, VECTOR_SHARED,
emulate_write_areas);
VMVECTOR_ALLOC_VECTOR(IAT_areas, GLOBAL_DCONTEXT, VECTOR_SHARED,
IAT_areas);
VMVECTOR_ALLOC_VECTOR(written_areas, GLOBAL_DCONTEXT,
VECTOR_SHARED | VECTOR_NEVER_MERGE,
written_areas);
vmvector_set_callbacks(written_areas, free_written_area, NULL, NULL, NULL);
#ifdef PROGRAM_SHEPHERDING
VMVECTOR_ALLOC_VECTOR(futureexec_areas, GLOBAL_DCONTEXT, VECTOR_SHARED,
futureexec_areas);
# ifdef WINDOWS
VMVECTOR_ALLOC_VECTOR(app_flushed_areas, GLOBAL_DCONTEXT, VECTOR_SHARED,
app_flushed_areas);
# endif
#endif
shared_data = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, thread_data_t, ACCT_VMAREAS, PROTECTED);
todelete = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, deletion_lists_t, ACCT_VMAREAS, PROTECTED);
memset(todelete, 0, sizeof(*todelete));
coarse_to_delete = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, coarse_info_t *,
ACCT_VMAREAS, PROTECTED);
*coarse_to_delete = NULL;
if (DYNAMO_OPTION(unloaded_target_exception)) {
last_deallocated = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, last_deallocated_t,
ACCT_VMAREAS, PROTECTED);
memset(last_deallocated, 0, sizeof(*last_deallocated));
} else
ASSERT(last_deallocated == NULL);
vm_areas_reset_init();
/* initialize dynamo list first */
LOG(GLOBAL, LOG_VMAREAS, 2,
"\n--------------------------------------------------------------------------\n");
dynamo_vm_areas_lock();
areas = find_dynamo_library_vm_areas();
dynamo_vm_areas_unlock();
/* initialize executable list
* this routine calls app_memory_allocation() w/ dcontext==NULL and so we
* won't go adding rwx regions, like the linux stack, to our list, even w/
* -executable_if_alloc
*/
areas = find_executable_vm_areas();
DOLOG(1, LOG_VMAREAS, {
if (areas > 0) {
LOG(GLOBAL, LOG_VMAREAS, 1, "\nExecution is allowed in %d areas\n", areas);
print_executable_areas(GLOBAL);
}
LOG(GLOBAL, LOG_VMAREAS, 2,
"--------------------------------------------------------------------------\n");
});
return areas;
}
static void
vm_areas_statistics()
{
#ifdef PROGRAM_SHEPHERDING
DOLOG(1, LOG_VMAREAS|LOG_STATS, {
uint top; uint bottom;
divide_uint64_print(GLOBAL_STAT(looked_up_in_last_area),
GLOBAL_STAT(checked_addresses), true, 2, &top, &bottom);
LOG(GLOBAL, LOG_VMAREAS|LOG_STATS, 1,
"Code Origin: %d address lookups, %d in last area, hit ratio %u.%.2u\n",
GLOBAL_STAT(checked_addresses), GLOBAL_STAT(looked_up_in_last_area),
top, bottom);
});
#endif /* PROGRAM_SHEPHERDING */
DOLOG(1, LOG_VMAREAS, {
LOG(GLOBAL, LOG_VMAREAS, 1, "\nexecutable_areas at exit:\n");
print_executable_areas(GLOBAL);
});
}
/* Free all thread-shared state not critical to forward progress;
* vm_areas_reset_init() will be called before continuing.
*/
void
vm_areas_reset_free(void)
{
if (SHARED_FRAGMENTS_ENABLED()) {
/* all deletion entries should be removed in fragment_exit(),
* else we'd have to free the frags lists and entries here
*/
ASSERT(todelete->shared_delete == NULL);
ASSERT(todelete->shared_delete_tail == NULL);
/* FIXME: don't free lock so init has less work */
vmvector_free_vector(GLOBAL_DCONTEXT, &shared_data->areas);
}
/* vm_area_coarse_units_reset_free() is called in fragment_reset_free() */
}
int
vm_areas_exit()
{
vm_areas_exited = true;
vm_areas_statistics();
if (DYNAMO_OPTION(thin_client)) {
vmvector_delete_vector(GLOBAL_DCONTEXT, dynamo_areas);
dynamo_areas = NULL;
/* For thin_client none of the following areas should have been
* initialized because they aren't used.
* FIXME: wonder if I can do something like this for -client and see
* what I am using unnecessarily.
*/
ASSERT(shared_data == NULL);
ASSERT(todelete == NULL);
ASSERT(executable_areas == NULL);
ASSERT(pretend_writable_areas == NULL);
ASSERT(patch_proof_areas == NULL);
ASSERT(emulate_write_areas == NULL);
ASSERT(written_areas == NULL);
#ifdef PROGRAM_SHEPHERDING
ASSERT(futureexec_areas == NULL);
IF_WINDOWS(ASSERT(app_flushed_areas == NULL);)
#endif
ASSERT(IAT_areas == NULL);
return 0;
}
vm_areas_reset_free();
DELETE_LOCK(shared_delete_lock);
DELETE_LOCK(lazy_delete_lock);
ASSERT(todelete->lazy_delete_count == 0);
ASSERT(!todelete->move_pending);
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, shared_data, thread_data_t, ACCT_VMAREAS, PROTECTED);
shared_data = NULL;
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, todelete, deletion_lists_t, ACCT_VMAREAS, PROTECTED);
todelete = NULL;
ASSERT(coarse_to_delete != NULL);
/* should be freed immediately after each use, during a no-exec flush */
ASSERT(*coarse_to_delete == NULL);
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, coarse_to_delete, coarse_info_t *,
ACCT_VMAREAS, PROTECTED);
if (DYNAMO_OPTION(unloaded_target_exception)) {
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, last_deallocated,
last_deallocated_t, ACCT_VMAREAS, PROTECTED);
last_deallocated = NULL;
} else
ASSERT(last_deallocated == NULL);
DELETE_LOCK(last_deallocated_lock);
vmvector_delete_vector(GLOBAL_DCONTEXT, executable_areas);
executable_areas = NULL;
DOLOG(1, LOG_VMAREAS, {
if (dynamo_areas->buf != NULL) {
LOG(GLOBAL, LOG_VMAREAS, 1, "DR regions at exit are:\n");
print_dynamo_areas(GLOBAL);
LOG(GLOBAL, LOG_VMAREAS, 1, "\n");
}
});
vmvector_delete_vector(GLOBAL_DCONTEXT, dynamo_areas);
dynamo_areas = NULL;
DOLOG(1, LOG_VMAREAS, {
if (written_areas->buf != NULL) {
LOG(GLOBAL, LOG_VMAREAS, 1, "Code write and selfmod exec counts:\n");
print_written_areas(GLOBAL);
LOG(GLOBAL, LOG_VMAREAS, 1, "\n");
}
});
vmvector_delete_vector(GLOBAL_DCONTEXT, pretend_writable_areas);
pretend_writable_areas = NULL;
vmvector_delete_vector(GLOBAL_DCONTEXT, patch_proof_areas);
patch_proof_areas = NULL;
vmvector_delete_vector(GLOBAL_DCONTEXT, emulate_write_areas);
emulate_write_areas = NULL;
vmvector_delete_vector(GLOBAL_DCONTEXT, written_areas);
written_areas = NULL;
#ifdef PROGRAM_SHEPHERDING
DOLOG(1, LOG_VMAREAS, {
if (futureexec_areas->buf != NULL)
LOG(GLOBAL, LOG_VMAREAS, 1, "futureexec %d regions at exit are:\n",
futureexec_areas->length);
print_futureexec_areas(GLOBAL);
});
vmvector_delete_vector(GLOBAL_DCONTEXT, futureexec_areas);
futureexec_areas = NULL;
DELETE_LOCK(threads_killed_lock);
# ifdef WINDOWS
ASSERT(DYNAMO_OPTION(xdata_rct) || vmvector_empty(app_flushed_areas));
vmvector_delete_vector(GLOBAL_DCONTEXT, app_flushed_areas);
app_flushed_areas = NULL;
# endif
#endif
#ifdef SIMULATE_ATTACK
DELETE_LOCK(simulate_lock);
#endif
vmvector_delete_vector(GLOBAL_DCONTEXT, IAT_areas);
IAT_areas = NULL;
return 0;
}
void
vm_areas_thread_reset_init(dcontext_t *dcontext)
{
thread_data_t *data = (thread_data_t *) dcontext->vm_areas_field;
memset(dcontext->vm_areas_field, 0, sizeof(thread_data_t));
VMVECTOR_INITIALIZE_VECTOR(&data->areas, VECTOR_FRAGMENT_LIST, thread_vm_areas);
/* data->areas.lock is never used, but we may want to grab it one day,
e.g. to print other thread areas */
}
/* N.B.: this is called before vm_areas_init() */
void
vm_areas_thread_init(dcontext_t *dcontext)
{
thread_data_t *data = HEAP_TYPE_ALLOC(dcontext, thread_data_t, ACCT_OTHER, PROTECTED);
dcontext->vm_areas_field = data;
vm_areas_thread_reset_init(dcontext);
}
void
vm_areas_thread_reset_free(dcontext_t *dcontext)
{
/* we free the local areas vector so it will match fragments post-reset
* FIXME: put it in nonpersistent heap
*/
thread_data_t *data = (thread_data_t *) dcontext->vm_areas_field;
/* yes, we end up using global heap for the thread-local area
* vector...not a big deal, but FIXME sometime
*/
vmvector_free_vector(GLOBAL_DCONTEXT, &data->areas);
}
void
vm_areas_thread_exit(dcontext_t *dcontext)
{
vm_areas_thread_reset_free(dcontext);
#ifdef DEBUG
/* for non-debug we do fast exit path and don't free local heap */
HEAP_TYPE_FREE(dcontext, dcontext->vm_areas_field, thread_data_t, ACCT_OTHER, PROTECTED);
#endif
}
/****************************************************************************
* external interface to vm_area_vector_t
*
* FIXME: add user data field to vector and to add routine
* FIXME: have init and destroy routines so don't have to expose
* vm_area_vector_t struct or declare vector in this file
*/
void
vmvector_set_callbacks(vm_area_vector_t *v,
void (*free_func)(void*),
void *(*split_func)(void*),
bool (*should_merge_func)(bool, void*, void*),
void *(*merge_func)(void*, void*))
{
bool release_lock; /* 'true' means this routine needs to unlock */
ASSERT(v != NULL);
LOCK_VECTOR(v, release_lock, read);
v->free_payload_func = free_func;
v->split_payload_func = split_func;
v->should_merge_func = should_merge_func;
v->merge_payload_func = merge_func;
UNLOCK_VECTOR(v, release_lock, read);
}
void
vmvector_print(vm_area_vector_t *v, file_t outf)
{
bool release_lock; /* 'true' means this routine needs to unlock */
LOCK_VECTOR(v, release_lock, read);
print_vm_areas(v, outf);
UNLOCK_VECTOR(v, release_lock, read);
}
void
vmvector_add(vm_area_vector_t *v, app_pc start, app_pc end, void *data)
{
bool release_lock; /* 'true' means this routine needs to unlock */
LOCK_VECTOR(v, release_lock, write);
ASSERT_OWN_WRITE_LOCK(SHOULD_LOCK_VECTOR(v), &v->lock);
add_vm_area(v, start, end, 0, 0, data _IF_DEBUG(""));
UNLOCK_VECTOR(v, release_lock, write);
}
void *
vmvector_add_replace(vm_area_vector_t *v, app_pc start, app_pc end, void *data)
{
bool overlap;
vm_area_t *area = NULL;
void *old_data = NULL;
bool release_lock; /* 'true' means this routine needs to unlock */
LOCK_VECTOR(v, release_lock, write);
ASSERT_OWN_WRITE_LOCK(SHOULD_LOCK_VECTOR(v), &v->lock);
overlap = lookup_addr(v, start, &area);
if (overlap && start == area->start && end == area->end) {
old_data = area->custom.client;
area->custom.client = data;
} else
add_vm_area(v, start, end, 0, 0, data _IF_DEBUG(""));
UNLOCK_VECTOR(v, release_lock, write);
return old_data;
}
bool
vmvector_remove(vm_area_vector_t *v, app_pc start, app_pc end)
{
bool ok;
bool release_lock; /* 'true' means this routine needs to unlock */
LOCK_VECTOR(v, release_lock, write);
ASSERT_OWN_WRITE_LOCK(SHOULD_LOCK_VECTOR(v), &v->lock);
ok = remove_vm_area(v, start, end, false);
UNLOCK_VECTOR(v, release_lock, write);
return ok;
}
/* Looks up area encapsulating target pc and removes.
* returns true if found and removed, and optional area boundaries are set
* returns false if not found
*/
bool
vmvector_remove_containing_area(vm_area_vector_t *v, app_pc pc,
app_pc *area_start /* OUT optional */,
app_pc *area_end /* OUT optional */)
{
vm_area_t *a;
bool ok;
bool release_lock; /* 'true' means this routine needs to unlock */
/* common path should be to find one, and would need write lock to
* remove */
LOCK_VECTOR(v, release_lock, write);
ASSERT_OWN_WRITE_LOCK(SHOULD_LOCK_VECTOR(v), &v->lock);
ok = lookup_addr(v, pc, &a);
if (ok) {
if (area_start != NULL)
*area_start = a->start;
if (area_end != NULL)
*area_end = a->end;
remove_vm_area(v, a->start, a->end, false);
}
UNLOCK_VECTOR(v, release_lock, write);
return ok;
}
bool
vmvector_overlap(vm_area_vector_t *v, app_pc start, app_pc end)
{
bool overlap;
bool release_lock; /* 'true' means this routine needs to unlock */
if (vmvector_empty(v))
return false;
LOCK_VECTOR(v, release_lock, read);
ASSERT_OWN_READWRITE_LOCK(SHOULD_LOCK_VECTOR(v), &v->lock);
overlap = vm_area_overlap(v, start, end);
UNLOCK_VECTOR(v, release_lock, read);
return overlap;
}
/* returns custom data field, or NULL if not found. NOTE: Access to
* custom data needs explicit synchronization in addition to
* vm_area_vector_t's locks!
*/
void *
vmvector_lookup(vm_area_vector_t *v, app_pc pc)
{
void *data = NULL;
vmvector_lookup_data(v, pc, NULL, NULL, &data);
return data;
}
/* Looks up if pc is in a vmarea and optionally returns the areas's bounds
* and any custom data. NOTE: Access to custom data needs explicit
* synchronization in addition to vm_area_vector_t's locks!
*/
bool
vmvector_lookup_data(vm_area_vector_t *v, app_pc pc,
app_pc *start /* OUT */, app_pc *end /* OUT */,
void **data /* OUT */)
{
bool overlap;
vm_area_t *area = NULL;
bool release_lock; /* 'true' means this routine needs to unlock */
LOCK_VECTOR(v, release_lock, read);
ASSERT_OWN_READWRITE_LOCK(SHOULD_LOCK_VECTOR(v), &v->lock);
overlap = lookup_addr(v, pc, &area);
if (overlap) {
if (start != NULL)
*start = area->start;
if (end != NULL)
*end = area->end;
if (data != NULL)
*data = area->custom.client;
}
UNLOCK_VECTOR(v, release_lock, read);
return overlap;
}
/* Returns false if pc is in a vmarea in v.
* Otherwise, returns the start pc of the vmarea prior to pc in prev and
* the start pc of the vmarea after pc in next.
* FIXME: most callers will call this and vmvector_lookup_data():
* should this routine do both to avoid an extra binary search?
*/
bool
vmvector_lookup_prev_next(vm_area_vector_t *v, app_pc pc,
OUT app_pc *prev, OUT app_pc *next)
{
bool success;
int index;
bool release_lock; /* 'true' means this routine needs to unlock */
LOCK_VECTOR(v, release_lock, read);
ASSERT_OWN_READWRITE_LOCK(SHOULD_LOCK_VECTOR(v), &v->lock);
success = !binary_search(v, pc, pc+1, NULL, &index, false);
if (success) {
if (prev != NULL) {
if (index == -1)
*prev = NULL;
else
*prev = v->buf[index].start;
}
if (next != NULL) {
if (index >= v->length - 1)
*next = (app_pc) POINTER_MAX;
else
*next = v->buf[index+1].start;
}
}
UNLOCK_VECTOR(v, release_lock, read);
return success;
}
/* Sets custom data field if a vmarea is present. Returns true if found,
* false if not found. NOTE: Access to custom data needs explicit
* synchronization in addition to vm_area_vector_t's locks!
*/
bool
vmvector_modify_data(vm_area_vector_t *v, app_pc start, app_pc end, void *data)
{
bool overlap;
vm_area_t *area = NULL;
bool release_lock; /* 'true' means this routine needs to unlock */
LOCK_VECTOR(v, release_lock, write);
ASSERT_OWN_WRITE_LOCK(SHOULD_LOCK_VECTOR(v), &v->lock);
overlap = lookup_addr(v, start, &area);
if (overlap && start == area->start && end == area->end)
area->custom.client = data;
UNLOCK_VECTOR(v, release_lock, write);
return overlap;
}
/* this routine does NOT initialize the rw lock! use VMVECTOR_INITIALIZE_VECTOR */
void
vmvector_init_vector(vm_area_vector_t *v, uint flags)
{
memset(v, 0, sizeof(*v));
v->flags = flags;
}
/* this routine does NOT initialize the rw lock! use VMVECTOR_ALLOC_VECTOR instead */
vm_area_vector_t *
vmvector_create_vector(dcontext_t *dcontext, uint flags)
{
vm_area_vector_t *v =
HEAP_TYPE_ALLOC(dcontext, vm_area_vector_t, ACCT_VMAREAS, PROTECTED);
vmvector_init_vector(v, flags);
return v;
}
/* frees the fields of vm_area_vector_t v (not v itself) */
void
vmvector_reset_vector(dcontext_t *dcontext, vm_area_vector_t *v)
{
DODEBUG({
int i;
/* walk areas and delete coarse info and comments */
for (i = 0; i < v->length; i++) {
/* FIXME: this code is duplicated in remove_vm_area() */
if (TEST(FRAG_COARSE_GRAIN, v->buf[i].frag_flags) &&
/* FIXME: cleaner test? shared_data copies flags, but uses
* custom.frags and not custom.client
*/
v == executable_areas) {
coarse_info_t *info = (coarse_info_t *) v->buf[i].custom.client;
coarse_info_t *next_info;
ASSERT(!RUNNING_WITHOUT_CODE_CACHE());
ASSERT(info != NULL);
while (info != NULL) { /* loop over primary and secondary unit */
next_info = info->non_frozen;
ASSERT(info->frozen || info->non_frozen == NULL);
coarse_unit_free(GLOBAL_DCONTEXT, info);
info = next_info;
ASSERT(info == NULL || !info->frozen);
}
v->buf[i].custom.client = NULL;
}
global_heap_free(v->buf[i].comment, strlen(v->buf[i].comment)+1
HEAPACCT(ACCT_VMAREAS));
}
});
/* with thread shared cache it is in fact possible to have no thread local vmareas */
if (v->buf != NULL) {
/* FIXME: walk through and make sure frags lists are all freed */
global_heap_free(v->buf, v->size*sizeof(struct vm_area_t) HEAPACCT(ACCT_VMAREAS));
v->size = 0;
v->length = 0;
v->buf = NULL;
} else
ASSERT(v->size == 0 && v->length == 0);
}
static void
vmvector_free_vector(dcontext_t *dcontext, vm_area_vector_t *v)
{
vmvector_reset_vector(dcontext, v);
DELETE_READWRITE_LOCK(v->lock);
}
/* frees the vm_area_vector_t v and its associated memory */
void
vmvector_delete_vector(dcontext_t *dcontext, vm_area_vector_t *v)
{
if (v->free_payload_func != NULL) {
int i;
for (i = 0; i < v->length; i++) {
v->free_payload_func(v->buf[i].custom.client);
}
}
vmvector_free_vector(dcontext, v);
HEAP_TYPE_FREE(dcontext, v, vm_area_vector_t, ACCT_VMAREAS, PROTECTED);
}
/* vmvector iterator */
/* initialize an iterator, has to be released with
* vmvector_iterator_stop. The iterator doesn't support mutations.
* In fact shared vectors should detect a deadlock
* if vmvector_add() and vmvector_remove() is erroneously called.
*/
void
vmvector_iterator_start(vm_area_vector_t *v, vmvector_iterator_t *vmvi)
{
ASSERT(v != NULL);
ASSERT(vmvi != NULL);
if (SHOULD_LOCK_VECTOR(v))
read_lock(&v->lock);
vmvi->vector = v;
vmvi->index = -1;
}
bool
vmvector_iterator_hasnext(vmvector_iterator_t *vmvi)
{
ASSERT_VMAREA_VECTOR_PROTECTED(vmvi->vector, READWRITE);
return (vmvi->index + 1) < vmvi->vector->length;
}
void
vmvector_iterator_startover(vmvector_iterator_t *vmvi)
{
ASSERT_VMAREA_VECTOR_PROTECTED(vmvi->vector, READWRITE);
vmvi->index = -1;
}
/* iterator accessor
* has to be initialized with vmvector_iterator_start, and should be
* called only when vmvector_iterator_hasnext() is true
*
* returns custom data and
* sets the area boundaries in area_start and area_end
*
* does not increment the iterator
*/
void*
vmvector_iterator_peek(vmvector_iterator_t *vmvi, /* IN/OUT */
app_pc *area_start /* OUT */, app_pc *area_end /* OUT */)
{
int idx = vmvi->index + 1;
ASSERT(vmvector_iterator_hasnext(vmvi));
ASSERT_VMAREA_VECTOR_PROTECTED(vmvi->vector, READWRITE);
ASSERT(idx < vmvi->vector->length);
if (area_start != NULL)
*area_start = vmvi->vector->buf[idx].start;
if (area_end != NULL)
*area_end = vmvi->vector->buf[idx].end;
return vmvi->vector->buf[idx].custom.client;
}
/* iterator accessor
* has to be initialized with vmvector_iterator_start, and should be
* called only when vmvector_iterator_hasnext() is true
*
* returns custom data and
* sets the area boundaries in area_start and area_end
*/
void*
vmvector_iterator_next(vmvector_iterator_t *vmvi, /* IN/OUT */
app_pc *area_start /* OUT */, app_pc *area_end /* OUT */)
{
void *res = vmvector_iterator_peek(vmvi, area_start, area_end);
vmvi->index++;
return res;
}
void
vmvector_iterator_stop(vmvector_iterator_t *vmvi)
{
ASSERT_VMAREA_VECTOR_PROTECTED(vmvi->vector, READWRITE);
if (SHOULD_LOCK_VECTOR(vmvi->vector))
read_unlock(&vmvi->vector->lock);
DODEBUG({
vmvi->vector = NULL; /* crash incorrect reuse */
vmvi->index = -1;
});
}
/****************************************************************************
* routines specific to our own vectors
*/
void
print_executable_areas(file_t outf)
{
vmvector_print(executable_areas, outf);
}
void
print_dynamo_areas(file_t outf)
{
dynamo_vm_areas_start_reading();
print_vm_areas(dynamo_areas, outf);
dynamo_vm_areas_done_reading();
}
#ifdef PROGRAM_SHEPHERDING
void
print_futureexec_areas(file_t outf)
{
vmvector_print(futureexec_areas, outf);
}
#endif
#if defined(DEBUG) && defined(INTERNAL)
static void
print_written_areas(file_t outf)
{
vmvector_print(written_areas, outf);
}
#endif
static void
free_written_area(void *data)
{
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, (ro_vs_sandbox_data_t *) data,
ro_vs_sandbox_data_t, ACCT_VMAREAS, UNPROTECTED);
}
/* Functions as a lookup routine if an entry is already present.
* Returns true if an entry was already present, false if not, in which
* case an entry containing tag with suggested bounds of [start, end)
* (actual bounds may be smaller to avoid overlap) is added.
*/
static bool
add_written_area(vm_area_vector_t *v, app_pc tag, app_pc start,
app_pc end, vm_area_t **area)
{
vm_area_t *a = NULL;
bool already;
DEBUG_DECLARE(bool ok;)
/* currently only one vector */
ASSERT(v == written_areas);
ASSERT_OWN_WRITE_LOCK(true, &v->lock);
ASSERT(tag >= start && tag < end);
/* re-adding fails for written_areas since no merging, so lookup first */
already = lookup_addr(v, tag, &a);
if (!already) {
app_pc prev_start, next_start;
LOG(GLOBAL, LOG_VMAREAS, 2,
"new written executable vm area: "PFX"-"PFX"\n",
start, end);
/* case 9179: With no flags, any overlap (in non-tag portion of [start,
* end)) will result in a merge: so we'll inherit and share counts from
* any adjacent region(s): maybe better to split? Rare in any case and
* not critical. In case of simultaneous overlap, we take counter from
* first region, since that's how add_vm_area does the merge.
*/
/* we can't merge b/c we have hardcoded counter pointers in code
* in the cache, so we make sure to only add the non-overlap
*/
DEBUG_DECLARE(ok = ) vmvector_lookup_prev_next(v, tag, &prev_start, &next_start);
ASSERT(ok); /* else already should be true */
if (prev_start != NULL) {
vm_area_t *prev_area = NULL;
DEBUG_DECLARE(ok = ) lookup_addr(v, prev_start, &prev_area);
ASSERT(ok); /* we hold the lock after all */
if (prev_area->end > start)
start = prev_area->end;
}
if (next_start < (app_pc) POINTER_MAX && end > next_start)
end = next_start;
add_vm_area(v, start, end, /* no flags */ 0, 0, NULL _IF_DEBUG(""));
DEBUG_DECLARE(ok = ) lookup_addr(v, tag, &a);
ASSERT(ok && a != NULL);
/* If we merged, we already have an ro2s struct */
/* FIXME: now that we have merge callback support, should just pass
* a struct into add_vm_area and avoid this post-lookup
*/
if (a->custom.client == NULL) {
/* Since selfmod_execs is written from the cache this must be
* unprotected. Attacker changing selfmod_execs or written_count
* shouldn't be able to cause problems.
*/
ro_vs_sandbox_data_t *ro2s =
HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, ro_vs_sandbox_data_t,
ACCT_VMAREAS, UNPROTECTED);
/* selfmod_execs is inc-ed from the cache, and if it crosses a cache
* line we could have a problem with large thresholds. We assert on
* 32-bit alignment here, which our heap alloc currently provides, to
* ensure no cache line is crossed.
*/
ASSERT(ALIGNED(ro2s, sizeof(uint)));
memset(ro2s, 0, sizeof(*ro2s));
a->custom.client = (void *) ro2s;
}
} else {
LOG(GLOBAL, LOG_VMAREAS, 3,
"request for written area "PFX"-"PFX" vs existing "PFX"-"PFX"\n",
start, end, a->start, a->end);
}
ASSERT(a != NULL);
if (area != NULL)
*area = a;
return already;
}
#ifdef WINDOWS
/* Adjusts a new executable area with respect to the IAT.
* Returns whether it should remain coarse or not.
*/
static bool
add_executable_vm_area_check_IAT(app_pc *start /*IN/OUT*/, app_pc *end /*IN/OUT*/,
uint vm_flags,
vm_area_t **existing_area /*OUT*/,
coarse_info_t **info_out /*OUT*/,
coarse_info_t **tofree /*OUT*/,
app_pc *delay_start /*OUT*/,
app_pc *delay_end /*OUT*/)
{
bool keep_coarse = false;
app_pc IAT_start = NULL, IAT_end = NULL;
app_pc orig_start = *start, orig_end = *end;
ASSERT(existing_area != NULL && info_out != NULL && tofree != NULL);
ASSERT(delay_start != NULL && delay_end != NULL);
if (DYNAMO_OPTION(coarse_merge_iat) &&
get_module_base(*start) != NULL &&
get_IAT_section_bounds(get_module_base(*start), &IAT_start, &IAT_end) &&
/* case 1094{5,7}: to match the assumptions of case 10600 we adjust
* to post-IAT even if the IAT is in the middle, if it's toward the front
*/
(*start >= IAT_start || (IAT_start - *start < *end - IAT_end)) &&
*start < IAT_end &&
/* be paranoid: multi-page IAT where hooker fooled our loader matching
* could add just 1st page of IAT? */
*end > IAT_end /* for == avoid an empty region */) {
/* If a pre-IAT region exists, split if off separately (case 10945).
* We want to keep as coarse, but we need the post-IAT region to be the
* primary coarse and the one we try to load a pcache for: so we delay
* the add.
* FIXME: should we do a general split around the IAT and make both sides
* coarse with larger the primary instead of assuming pre-IAT is smaller?
*/
if (orig_start < IAT_start) {
LOG(GLOBAL, LOG_VMAREAS, 2,
"splitting pre-IAT "PFX"-"PFX" off from exec area "PFX"-"PFX"\n",
orig_start, IAT_start, orig_start, orig_end);
*delay_start = orig_start;
*delay_end = IAT_start;
DOCHECK(1, {
/* When IAT is in the middle of +rx region we expect .orpc */
app_pc orpc_start = NULL;
app_pc orpc_end = NULL;
get_named_section_bounds(get_module_base(orig_start), ".orpc",
&orpc_start, &orpc_end);
ASSERT_CURIOSITY(orpc_start == orig_start && orpc_end == IAT_start);
});
}
/* Just abandon [*start, IAT_end) */
*start = IAT_end;
ASSERT(*end > *start);
LOG(GLOBAL, LOG_VMAREAS, 2,
"adjusting exec area "PFX"-"PFX" to post-IAT "PFX"-"PFX"\n",
orig_start, *end, *start, *end);
} else {
LOG(GLOBAL, LOG_VMAREAS, 2,
"NOT adjusting exec area "PFX"-"PFX" vs IAT "PFX"-"PFX"\n",
orig_start, *end, IAT_start, IAT_end);
}
if (TEST(VM_UNMOD_IMAGE, vm_flags))
keep_coarse = true;
else {
/* Keep the coarse-grain flag for modified pages only if IAT pages.
* We want to avoid repeated coarse flushes, so we are
* very conservative about marking if_rx_text regions coarse: we count on
* our IAT loader check to make this a do-once.
* FIXME: Should extend this to also merge on left with .orpc:
* .orpc at page 1, IAT on page 2, and .text continuing on
*/
ASSERT(ALIGNED(*end, PAGE_SIZE));
if (DYNAMO_OPTION(coarse_merge_iat) &&
vm_flags == 0 /* no other flags */ &&
/* FIXME: used our stored bounds */
is_IAT(orig_start, orig_end, true/*page-align*/, NULL, NULL) &&
is_module_patch_region(GLOBAL_DCONTEXT, orig_start, orig_end,
true/*be conservative*/) &&
/* We stored the IAT code at +rw time */
os_module_cmp_IAT_code(orig_start)) {
vm_area_t *area = NULL;
bool all_new = !executable_vm_area_overlap(orig_start, orig_end-1,
true/*wlock*/);
ASSERT(IAT_start != NULL); /* should have found bounds above */
if (all_new && /* elseif assumes next call happened */
lookup_addr(executable_areas, *end, &area) &&
TEST(FRAG_COARSE_GRAIN, area->frag_flags) &&
/* Only merge if no execution has yet occurred: else this
* must not be normal rebinding */
!TEST(VM_EXECUTED_FROM, area->vm_flags) &&
/* Should be marked invalid; else no loader +rw => not rebinding */
area->custom.client != NULL &&
TEST(PERSCACHE_CODE_INVALID,
((coarse_info_t *)area->custom.client)->flags)) {
/* Case 8640: merge IAT page back in to coarse area.
* Easier to merge here than in add_vm_area.
*/
coarse_info_t *info = (coarse_info_t *) area->custom.client;
keep_coarse = true;
LOG(GLOBAL, LOG_VMAREAS, 2,
"merging post-IAT ("PFX"-"PFX") with "PFX"-"PFX"\n",
IAT_end, orig_end, area->start, area->end);
ASSERT(area != NULL);
ASSERT(area->start == *end);
ASSERT(IAT_end > orig_start && IAT_end < area->start);
ASSERT(*start == IAT_end); /* set up above */
*end = area->end;
area->start = *start;
*existing_area = area;
STATS_INC(coarse_merge_IAT);
/* If info was loaded prior to rebinding just use it.
* Else, we need a fresh coarse_info_t if persisted, so rather than
* adjust_coarse_unit_bounds on info we must free it.
* Due to lock constraints we can't do that while holding
* exec areas lock.
*/
/* Bounds should match exactly, since we did not adjust them
* on the flush; if they don't, don't use the pcache. */
if (info->base_pc == area->start && info->end_pc == area->end) {
info->flags &= ~PERSCACHE_CODE_INVALID;
*info_out = info;
STATS_INC(coarse_marked_valid);
LOG(GLOBAL, LOG_VMAREAS, 2,
"\tkeeping now-valid info %s "PFX"-"PFX"\n",
info->module, info->base_pc, info->end_pc);
} else {
/* Go ahead and merge, but don't use this pcache */
ASSERT_CURIOSITY(false && "post-rebind pcache bounds mismatch");
*tofree = info;
area->custom.client = NULL;
/* FIXME: we'll try to load again: prevent that? We
* know the image hasn't been modified so no real danger. */
STATS_INC(perscache_rebind_load);
}
} else if (all_new && area == NULL /*nothing following*/) {
/* Code section is a single page, so was completely flushed
* We'll try to re-load the pcache.
* FIXME: we already merged the persisted rct tables into
* the live tables when we flushed the pcache: so now
* we'll have redundancy, and if we flush again we'll waste
* time tryingn to re-add (we do check for dups).
*/
ASSERT(!lookup_addr(executable_areas, *start, NULL));
LOG(GLOBAL, LOG_VMAREAS, 2,
"marking IAT/code region ("PFX"-"PFX" vs "PFX"-"PFX") as coarse\n",
IAT_start, IAT_end, orig_start, orig_end);
keep_coarse = true;
STATS_INC(coarse_merge_IAT); /* we use same stat */
} else {
LOG(GLOBAL, LOG_VMAREAS, 2,
"NOT merging IAT-containing "PFX"-"PFX": abuts non-inv-coarse\n",
orig_start, orig_end);
DOCHECK(1, {
if (all_new && area != NULL &&
TEST(FRAG_COARSE_GRAIN, area->frag_flags) &&
TEST(VM_EXECUTED_FROM, area->vm_flags)) {
coarse_info_t *info = (coarse_info_t *) area->custom.client;
ASSERT(!info->persisted);
ASSERT(!TEST(PERSCACHE_CODE_INVALID, info->flags));
}
});
}
} else {
LOG(GLOBAL, LOG_VMAREAS, 2,
"NOT merging .text "PFX"-"PFX" vs IAT "PFX"-"PFX" %d %d %d %d %d\n",
orig_start, orig_end, IAT_start, IAT_end,
DYNAMO_OPTION(coarse_merge_iat), vm_flags == 0,
is_IAT(orig_start, *end, true/*page-align*/, NULL, NULL),
is_module_patch_region(GLOBAL_DCONTEXT, orig_start, orig_end,
true/*be conservative*/),
os_module_cmp_IAT_code(orig_start));
}
}
return keep_coarse;
}
#endif
static void
add_executable_vm_area_helper(app_pc start, app_pc end, uint vm_flags, uint frag_flags,
coarse_info_t *info _IF_DEBUG(const char *comment))
{
ASSERT_OWN_WRITE_LOCK(true, &executable_areas->lock);
add_vm_area(executable_areas, start, end,
vm_flags, frag_flags, NULL _IF_DEBUG(comment));
if (TEST(VM_WRITABLE, vm_flags)) {
/* N.B.: the writable flag indicates the natural state of the memory,
* not what we have made it be -- we make it read-only before adding
* to the executable list!
* FIXME: win32 callback's intercept_call code appears in fragments
* and is writable...would like to fix that, and coalesce that memory
* with the generated routines or something
*/
LOG(GLOBAL, LOG_VMAREAS, 2,
"WARNING: new executable vm area is writable: "PFX"-"PFX" %s\n",
start, end, comment);
#if 0
/* this syslog causes services.exe to hang (ref case 666) once case 666
* is fixed re-enable if desired FIXME */
SYSLOG_INTERNAL_WARNING_ONCE("new executable vm area is writable.");
#endif
}
#ifdef PROGRAM_SHEPHERDING
if (!DYNAMO_OPTION(selfmod_futureexec) &&
TEST(FRAG_SELFMOD_SANDBOXED, frag_flags)) {
/* We do not need future entries for selfmod regions. We mark
* the futures as once-only when they are selfmod at future add time, and
* here we catch those who weren't selfmod then but are now.
*/
remove_futureexec_vm_area(start, end);
}
#endif
if (TEST(FRAG_COARSE_GRAIN, frag_flags)) {
vm_area_t *area = NULL;
DEBUG_DECLARE(bool found = )
lookup_addr(executable_areas, start, &area);
ASSERT(found && area != NULL);
/* case 9521: always have one non-frozen coarse unit per coarse region */
if (info == NULL || info->frozen) {
coarse_info_t *new_info = coarse_unit_create(start, end,
(info == NULL) ? NULL :
&info->module_md5,
true/*for execution*/);
LOG(GLOBAL, LOG_VMAREAS, 1, "new %scoarse unit %s "PFX"-"PFX"\n",
info == NULL ? "" : "secondary ", new_info->module, start, end);
if (info == NULL)
info = new_info;
else
info->non_frozen = new_info;
}
area->custom.client = (void *) info;
}
DOLOG(2, LOG_VMAREAS, {
/* new area could have been split into multiple */
print_contig_vm_areas(executable_areas, start, end, GLOBAL, "new executable vm area: ");
});
}
static coarse_info_t *
vm_area_load_coarse_unit(app_pc start, app_pc end, uint vm_flags, uint frag_flags,
bool delayed _IF_DEBUG(const char *comment))
{
coarse_info_t *info;
/* We load persisted cache files at mmap time primarily for RCT
* tables; but to avoid duplicated code, and for simplicity, we do
* so if -use_persisted even if not -use_persisted_rct.
*/
dcontext_t *dcontext = get_thread_private_dcontext();
ASSERT_OWN_WRITE_LOCK(true, &executable_areas->lock);
/* FIXME: we're called before 1st thread is set up. Only a problem
* right now for rac_entries_resurrect() w/ private after-call
* which won't happen w/ -coarse_units that requires shared bbs.
*/
info = coarse_unit_load(dcontext == NULL ? GLOBAL_DCONTEXT : dcontext,
start, end, true/*for execution*/);
if (info != NULL) {
ASSERT(info->base_pc >= start && info->end_pc <= end);
LOG(GLOBAL, LOG_VMAREAS, 1,
"using persisted coarse unit %s "PFX"-"PFX" for "PFX"-"PFX"\n",
info->module, info->base_pc, info->end_pc, start, end);
/* Case 8640/9653/8639: adjust region bounds so that a
* cache consistency event outside the persisted region
* does not invalidate it (mainly targeting loader rebinding).
* We count on FRAG_COARSE_GRAIN preventing any merging of regions.
* We could delay this until code validation, as RCT tables don't care,
* and then we could avoid splitting the region in case validation
* fails: but our plan for lazy per-page validation (case 10601)
* means we can fail post-split even that way. So we go ahead and split
* up front here. For 4.4 we should move this to 1st exec.
*/
if (delayed && (info->base_pc > start || info->end_pc < end)) {
/* we already added a region for the whole range earlier */
remove_vm_area(executable_areas, start, end, false/*leave writability*/);
add_executable_vm_area_helper(info->base_pc, info->end_pc,
vm_flags, frag_flags, info
_IF_DEBUG(comment));
}
if (info->base_pc > start) {
add_executable_vm_area_helper(start, info->base_pc,
vm_flags, frag_flags, NULL
_IF_DEBUG(comment));
start = info->base_pc;
}
if (info->end_pc < end) {
add_executable_vm_area_helper(info->end_pc, end,
vm_flags, frag_flags, NULL
_IF_DEBUG(comment));
end = info->end_pc;
}
/* if !delayed we'll add the region for the unit in caller */
ASSERT(info->frozen && info->persisted);
vm_flags |= VM_PERSISTED_CACHE;
/* For 4.4 we would mark as PERSCACHE_CODE_INVALID here and
* mark valid only at 1st execution when we do md5 checks;
* for 4.3 we're valid until a rebind action.
*/
ASSERT(!TEST(PERSCACHE_CODE_INVALID, info->flags));
/* We must add to shared_data, but we cannot here due to lock
* rank issues (shared_vm_areas lock is higher rank than
* executable_areas, and we have callers doing flushes and
* already holding executable_areas), so we delay.
*/
vm_flags |= VM_ADD_TO_SHARED_DATA;
}
return info;
}
/* NOTE : caller is responsible for ensuring that consistency conditions are
* met, thus if the region is writable the caller must either mark it read
* only or pass in the VM_DELAY_READONLY flag in which case
* check_thread_vm_area will mark it read only when a thread goes to build a
* block from the region */
static bool
add_executable_vm_area(app_pc start, app_pc end, uint vm_flags, uint frag_flags,
bool have_writelock _IF_DEBUG(const char *comment))
{
vm_area_t *existing_area = NULL;
coarse_info_t *info = NULL;
coarse_info_t *tofree = NULL;
app_pc delay_start = NULL, delay_end = NULL;
/* only expect to see the *_READONLY flags on WRITABLE regions */
ASSERT(!TEST(VM_DELAY_READONLY, vm_flags) ||
TEST(VM_WRITABLE, vm_flags));
ASSERT(!TEST(VM_MADE_READONLY, vm_flags) ||
TEST(VM_WRITABLE, vm_flags));
#ifdef DEBUG /* can't use DODEBUG b/c of ifdef inside */
{
/* we only expect certain flags */
uint expect = VM_WRITABLE|VM_UNMOD_IMAGE|VM_MADE_READONLY|
VM_DELAY_READONLY|VM_WAS_FUTURE|VM_EXECUTED_FROM|VM_DRIVER_ADDRESS;
# ifdef PROGRAM_SHEPHERDING
expect |= VM_PATTERN_REVERIFY;
# endif
ASSERT(!TESTANY(~expect, vm_flags));
}
#endif /* DEBUG */
if (!have_writelock) {
#ifdef HOT_PATCHING_INTERFACE
/* case 9970: need to check hotp vs perscache; rank order hotp < exec_areas */
if (DYNAMO_OPTION(hot_patching))
read_lock(hotp_get_lock());
#endif
write_lock(&executable_areas->lock);
}
ASSERT_OWN_WRITE_LOCK(true, &executable_areas->lock);
/* FIXME: rather than change all callers who already hold exec_areas lock
* to first grab hotp lock, we don't support perscache in those cases.
* We expect to only be adding a coarse-grain area for module loads.
*/
ASSERT(!TEST(FRAG_COARSE_GRAIN, frag_flags) || !have_writelock);
if (TEST(FRAG_COARSE_GRAIN, frag_flags) && !have_writelock) {
#ifdef WINDOWS
if (!add_executable_vm_area_check_IAT(&start, &end, vm_flags,
&existing_area, &info, &tofree,
&delay_start, &delay_end))
frag_flags &= ~FRAG_COARSE_GRAIN;
#else
ASSERT(TEST(VM_UNMOD_IMAGE, vm_flags));
#endif
ASSERT(!RUNNING_WITHOUT_CODE_CACHE());
if (TEST(FRAG_COARSE_GRAIN, frag_flags) && DYNAMO_OPTION(use_persisted) &&
info == NULL
/* if clients are present, don't load until after they're initialized */
IF_CLIENT_INTERFACE(&& (dynamo_initialized ||
IS_INTERNAL_STRING_OPTION_EMPTY(client_lib)))) {
info = vm_area_load_coarse_unit(start, end, vm_flags, frag_flags, false
_IF_DEBUG(comment));
}
}
if (existing_area == NULL) {
add_executable_vm_area_helper(start, end, vm_flags, frag_flags, info
_IF_DEBUG(comment));
} else {
/* we shouldn't need the other parts of _helper() */
ASSERT(!TEST(VM_WRITABLE, vm_flags));
#ifdef PROGRAM_SHEPHERDING
ASSERT(DYNAMO_OPTION(selfmod_futureexec) ||
!TEST(FRAG_SELFMOD_SANDBOXED, frag_flags));
#endif
}
if (delay_start != NULL) {
ASSERT(delay_end > delay_start);
add_executable_vm_area_helper(delay_start, delay_end, vm_flags, frag_flags, NULL
_IF_DEBUG(comment));
}
DOLOG(2, LOG_VMAREAS, {
/* new area could have been split into multiple */
print_contig_vm_areas(executable_areas, start, end, GLOBAL, "new executable vm area: ");
});
if (!have_writelock) {
write_unlock(&executable_areas->lock);
#ifdef HOT_PATCHING_INTERFACE
if (DYNAMO_OPTION(hot_patching))
read_unlock(hotp_get_lock());
#endif
}
if (tofree != NULL) {
/* Since change_linking_lock and info->lock are higher rank than exec areas we
* must free down here. FIXME: this should move to 1st exec for 4.4.
*/
ASSERT(tofree->non_frozen == NULL);
coarse_unit_reset_free(GLOBAL_DCONTEXT, tofree, false/*no locks*/,
true/*unlink*/, true/*give up primary*/);
coarse_unit_free(GLOBAL_DCONTEXT, tofree);
}
return true;
}
/* Used to add dr allocated memory regions that may execute out of the cache */
/* NOTE : region is assumed to not be writable, caller is responsible for
* ensuring this (see fixme in signal.c adding sigreturn code)
*/
bool
add_executable_region(app_pc start, size_t size _IF_DEBUG(const char *comment))
{
return add_executable_vm_area(start, start+size, 0, 0, false/*no lock*/
_IF_DEBUG(comment));
}
/* remove an executable area from the area list
* the caller is responsible for ensuring that all threads' local vm lists
* are updated by calling flush_fragments_and_remove_region (can't just
* remove local vm areas and leave existing fragments hanging...)
*/
static bool
remove_executable_vm_area(app_pc start, app_pc end, bool have_writelock)
{
bool ok;
LOG(GLOBAL, LOG_VMAREAS, 2, "removing executable vm area: "PFX"-"PFX"\n",
start, end);
if (!have_writelock)
write_lock(&executable_areas->lock);
ok = remove_vm_area(executable_areas, start, end, true/*restore writability!*/);
if (!have_writelock)
write_unlock(&executable_areas->lock);
return ok;
}
/* removes a region from the executable list */
/* NOTE :the caller is responsible for ensuring that all threads' local
* vm lists are updated by calling flush_fragments_and_remove_region
*/
bool
remove_executable_region(app_pc start, size_t size, bool have_writelock)
{
return remove_executable_vm_area(start, start+size, have_writelock);
}
#ifdef CLIENT_INTERFACE
/* To give clients a chance to process pcaches as we load them, we
* delay the loading until we've initialized the clients.
*/
void
vm_area_delay_load_coarse_units(void)
{
int i;
ASSERT(!dynamo_initialized);
if (!DYNAMO_OPTION(use_persisted) ||
/* we already loaded if there's no client */
IS_INTERNAL_STRING_OPTION_EMPTY(client_lib))
return;
write_lock(&executable_areas->lock);
for (i = 0; i < executable_areas->length; i++) {
if (TEST(FRAG_COARSE_GRAIN, executable_areas->buf[i].frag_flags)) {
vm_area_t *a = &executable_areas->buf[i];
/* store cur_info b/c a might be blown away */
coarse_info_t *cur_info = (coarse_info_t *) a->custom.client;
if (cur_info == NULL || !cur_info->frozen) {
coarse_info_t *info =
vm_area_load_coarse_unit(a->start, a->end, a->vm_flags,
a->frag_flags, true _IF_DEBUG(a->comment));
if (info != NULL) {
/* re-acquire a and i */
DEBUG_DECLARE(bool ok = )
binary_search(executable_areas, info->base_pc,
info->base_pc+1/*open end*/, &a, &i, false);
ASSERT(ok);
if (cur_info != NULL)
info->non_frozen = cur_info;
a->custom.client = (void *) info;
}
} else
ASSERT_NOT_REACHED(); /* shouldn't have been loaded already */
}
}
write_unlock(&executable_areas->lock);
}
#endif
/* case 10995: we have to delay freeing un-executed coarse units until
* we can release the exec areas lock when we flush an un-executed region.
* This routine frees the queued-up coarse units, and releases the
* executable areas lock, which the caller must hold.
*/
bool
free_nonexec_coarse_and_unlock()
{
bool freed_any = false;
coarse_info_t *info = NULL;
coarse_info_t *next_info;
/* We must hold the exec areas lock while traversing the to-delete list,
* yet we cannot delete while holding it, so we use a temp var
*/
ASSERT_OWN_WRITE_LOCK(true, &executable_areas->lock);
ASSERT(coarse_to_delete != NULL);
if (coarse_to_delete != NULL/*paranoid*/ && *coarse_to_delete != NULL) {
freed_any = true;
info = *coarse_to_delete;
*coarse_to_delete = NULL;
}
/* Now we can unlock, and then it's safe to delete */
executable_areas_unlock();
if (freed_any) {
/* units are chained by non_frozen field */
while (info != NULL) {
next_info = info->non_frozen;
if (info->cache != NULL) {
ASSERT(info->persisted);
/* We shouldn't need to unlink since no execution has occurred
* (lazy linking)
*/
ASSERT(info->incoming == NULL);
ASSERT(!coarse_unit_outgoing_linked(GLOBAL_DCONTEXT, info));
}
coarse_unit_reset_free(GLOBAL_DCONTEXT, info,
false/*no locks*/, false/*!unlink*/,
true/*give up primary*/);
coarse_unit_free(GLOBAL_DCONTEXT, info);
info = next_info;
}
}
return freed_any;
}
#ifdef PROGRAM_SHEPHERDING
/* add a "future executable area" (e.g., mapped EW) to the future list
*
* FIXME: now that this is vmareas.c-internal we should change it to
* take in direct VM_ flags, and make separate flags for each future-adding
* code origins policy. Then we can have policy-specific removal from future list.
*/
static bool
add_futureexec_vm_area(app_pc start, app_pc end, bool once_only
_IF_DEBUG(const char *comment))
{
/* FIXME: don't add portions that overlap w/ exec areas */
LOG(GLOBAL, LOG_VMAREAS, 2, "new FUTURE executable vm area: "PFX"-"PFX" %s%s\n",
start, end, (once_only?"ONCE ":""), comment);
if (DYNAMO_OPTION(unloaded_target_exception)) {
/* case 9371 - to avoid possible misclassification in a tight race
* between NtUnmapViewOfSection and a consecutive future area
* allocated in the same place, we clear our the unload in progress flag
*/
mark_unload_future_added(start, end - start);
}
write_lock(&futureexec_areas->lock);
add_vm_area(futureexec_areas, start, end,
(once_only ? VM_ONCE_ONLY : 0),
0 /* frag_flags */, NULL _IF_DEBUG(comment));
write_unlock(&futureexec_areas->lock);
return true;
}
/* remove a "future executable area" from the future list */
static bool
remove_futureexec_vm_area(app_pc start, app_pc end)
{
bool ok;
LOG(GLOBAL, LOG_VMAREAS, 2, "removing FUTURE executable vm area: "PFX"-"PFX"\n",
start, end);
write_lock(&futureexec_areas->lock);
ok = remove_vm_area(futureexec_areas, start, end, false);
write_unlock(&futureexec_areas->lock);
return ok;
}
/* returns true if the passed in area overlaps any known future executable areas */