blob: f3d14faa742c14e109a3dbc3d9dc14c1c66870ff [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2012-2014 Google, 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 Google, 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 GOOGLE, 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.
*/
/* Cross-platform logic for executing parts of the app natively alongside DR's
* code cache.
*
* At Determina, native exec was used primarily to avoid security violation
* false positives in JITs. For instrumentation clients, it can offer improved
* performance when dealing with libraries that don't need to be instrumented.
* However, we cannot guarantee that we won't lose control or violate
* transparency.
*/
#include "native_exec.h"
#include "../globals.h"
#include "../vmareas.h"
#include "../module_shared.h"
#include "arch_exports.h"
#include "instr.h"
#include "instrlist.h"
#include "decode_fast.h"
#include "monitor.h"
#define PRE instrlist_preinsert
/* list of native_exec module regions
*/
vm_area_vector_t *native_exec_areas;
static const app_pc retstub_start = (app_pc) back_from_native_retstubs;
#ifdef DEBUG
static const app_pc retstub_end = (app_pc) back_from_native_retstubs_end;
#endif
void
native_exec_init(void)
{
native_module_init();
if (!DYNAMO_OPTION(native_exec) || DYNAMO_OPTION(thin_client))
return;
VMVECTOR_ALLOC_VECTOR(native_exec_areas, GLOBAL_DCONTEXT, VECTOR_SHARED,
native_exec_areas);
ASSERT(retstub_end == retstub_start +
MAX_NATIVE_RETSTACK * BACK_FROM_NATIVE_RETSTUB_SIZE);
}
void
native_exec_exit(void)
{
native_module_exit();
if (native_exec_areas == NULL)
return;
vmvector_delete_vector(GLOBAL_DCONTEXT, native_exec_areas);
native_exec_areas = NULL;
}
static bool
is_dr_native_pc(app_pc pc)
{
#ifdef DR_APP_EXPORTS
if (pc == (app_pc) dr_app_running_under_dynamorio
IF_LINUX(|| pc == (app_pc) dr_app_handle_mbr_target))
return true;
#endif /* DR_APP_EXPORTS */
return false;
}
bool
is_native_pc(app_pc pc)
{
/* only used for native exec */
ASSERT(DYNAMO_OPTION(native_exec) && !vmvector_empty(native_exec_areas));
return (is_dr_native_pc(pc) || vmvector_overlap(native_exec_areas, pc, pc+1));
}
static bool
on_native_exec_list(const char *modname)
{
bool onlist = false;
ASSERT(!DYNAMO_OPTION(thin_client));
if (!DYNAMO_OPTION(native_exec))
return false;
if (!IS_STRING_OPTION_EMPTY(native_exec_default_list)) {
string_option_read_lock();
LOG(THREAD_GET, LOG_INTERP|LOG_VMAREAS, 4,
"on_native_exec_list: module %s vs default list %s\n",
(modname == NULL ? "null" : modname),
DYNAMO_OPTION(native_exec_default_list));
onlist = check_filter(DYNAMO_OPTION(native_exec_default_list), modname);
string_option_read_unlock();
}
if (!onlist &&
!IS_STRING_OPTION_EMPTY(native_exec_list)) {
string_option_read_lock();
LOG(THREAD_GET, LOG_INTERP|LOG_VMAREAS, 4,
"on_native_exec_list: module %s vs append list %s\n",
modname==NULL?"null":modname, DYNAMO_OPTION(native_exec_list));
onlist = check_filter(DYNAMO_OPTION(native_exec_list), modname);
string_option_read_unlock();
}
return onlist;
}
static bool
check_and_mark_native_exec(module_area_t *ma, bool add)
{
bool is_native = false;
const char *name = GET_MODULE_NAME(&ma->names);
ASSERT(os_get_module_info_locked());
if (DYNAMO_OPTION(native_exec) && name != NULL &&
on_native_exec_list(name)) {
LOG(GLOBAL, LOG_INTERP|LOG_VMAREAS, 1,
"module %s is on native_exec list\n", name);
is_native = true;
}
if (add && is_native) {
RSTATS_INC(num_native_module_loads);
vmvector_add(native_exec_areas, ma->start, ma->end, NULL);
} else if (!add) {
/* If we're removing and it's native, it should be on there already. If
* it's not native, then it shouldn't be present, but we'll remove
* whatever is there.
*/
DEBUG_DECLARE(bool present =)
vmvector_remove(native_exec_areas, ma->start, ma->end);
ASSERT_CURIOSITY((is_native && present) || (!is_native && !present));
}
return is_native;
}
void
native_exec_module_load(module_area_t *ma, bool at_map)
{
bool is_native = check_and_mark_native_exec(ma, true/*add*/);
if (is_native && DYNAMO_OPTION(native_exec_retakeover))
native_module_hook(ma, at_map);
}
void
native_exec_module_unload(module_area_t *ma)
{
bool is_native = check_and_mark_native_exec(ma, false/*!add*/);
if (DYNAMO_OPTION(native_exec_retakeover)) {
if (is_native)
native_module_unhook(ma);
#ifdef UNIX
else
native_module_nonnative_mod_unload(ma);
#endif
}
}
/* Clean call called on every fcache to native transition. Turns on and off
* asynch handling and updates some state. Called from native bbs built by
* build_native_exec_bb() in arch/interp.c.
*
* N.B.: all the actions of this routine are mirrored in insert_enter_native(),
* so any changes here should be mirrored there.
*/
static void
entering_native(dcontext_t *dcontext)
{
#ifdef WINDOWS
/* turn off asynch interception for this thread while native
* FIXME: what if callbacks and apcs are destined for other modules?
* should instead run dispatcher under DR every time, if going to native dll
* will go native then? have issues w/ missing the cb ret, though...
* N.B.: if allow some asynch, have to find another place to store the real
* return addr (currently in next_tag)
*
* We can't revert memory prots, since other threads are under DR
* control, but we do handle our-fault write faults in native threads.
*/
set_asynch_interception(dcontext->owning_thread, false);
#endif
/* FIXME: setting same var that set_asynch_interception is! */
dcontext->thread_record->under_dynamo_control = false;
ASSERT(!is_building_trace(dcontext));
set_last_exit(dcontext, (linkstub_t *) get_native_exec_linkstub());
/* we need to match dr_app_stop() so we pop the kstack */
KSTOP_NOT_MATCHING(dispatch_num_exits);
/* now we're in app! */
dcontext->whereami = WHERE_APP;
SYSLOG_INTERNAL_WARNING_ONCE("entered at least one module natively");
STATS_INC(num_native_module_enter);
}
/* We replace the actual return target on the app stack with a stub pc
* so that the control transfer back to code cache or DR after native module
* returns.
*/
static bool
prepare_return_from_native_via_stub(dcontext_t *dcontext, app_pc *app_sp)
{
#ifdef UNIX
app_pc stub_pc;
ASSERT(DYNAMO_OPTION(native_exec_retakeover) && !is_native_pc(*app_sp));
/* i#1238-c#4: the inline asm stub does not support kstats, so we
* only support it when native_exec_opt is on, which turns kstats off.
*/
if (!DYNAMO_OPTION(native_exec_opt))
return false;
stub_pc = native_module_get_ret_stub(dcontext, *app_sp);
if (stub_pc == NULL)
return false;
*app_sp = stub_pc;
return true;
#endif
return false;
}
static void
prepare_return_from_native_via_stack(dcontext_t *dcontext, app_pc *app_sp)
{
uint i;
ASSERT(DYNAMO_OPTION(native_exec_retakeover) && !is_native_pc(*app_sp));
/* Push the retaddr and stack location onto our stack. The current entry
* should be free and we should have enough space.
* XXX: it would be nice to abort in a release build, but this can be perf
* critical.
*/
i = dcontext->native_retstack_cur;
ASSERT(i < MAX_NATIVE_RETSTACK);
dcontext->native_retstack[i].retaddr = *app_sp;
dcontext->native_retstack[i].retloc = (app_pc) app_sp;
dcontext->native_retstack_cur = i + 1;
/* i#978: We use a different return stub for every nested call to native
* code. Each stub pushes a different index into the retstack. We could
* use the SP at return time to try to find the app's return address, but
* because of ret imm8 instructions, that's not robust.
*/
*app_sp = retstub_start + i * BACK_FROM_NATIVE_RETSTUB_SIZE;
}
void
call_to_native(app_pc *app_sp)
{
dcontext_t *dcontext;
ENTERING_DR();
dcontext = get_thread_private_dcontext();
ASSERT(dcontext != NULL);
/* i#1090: If the return address is also in a native module, then leave it
* alone. This happens on:
* - native call
* - native call tail_caller@plt
* - non-native jmp native@plt # TOS is native PC: don't swap
* - native ret # should stay native
* XXX: Doing a vmvector binary search on every call to native is expensive.
*/
if (DYNAMO_OPTION(native_exec_retakeover) && !is_native_pc(*app_sp)) {
/* We try to use stub for fast return-from-native handling, if fails
* (e.g., on Windows or optimization disabled), fall back to use the stack.
*/
if (!prepare_return_from_native_via_stub(dcontext, app_sp))
prepare_return_from_native_via_stack(dcontext, app_sp);
}
LOG(THREAD, LOG_ASYNCH, 1,
"!!!! Entering module NATIVELY, retaddr="PFX"\n\n", *app_sp);
entering_native(dcontext);
EXITING_DR();
}
/* N.B.: all the actions of this routine are mirrored in insert_return_to_native(),
* so any changes here should be mirrored there.
*/
void
return_to_native(void)
{
dcontext_t *dcontext;
ENTERING_DR();
dcontext = get_thread_private_dcontext();
ASSERT(dcontext != NULL);
entering_native(dcontext);
EXITING_DR();
}
/* Re-enters DR at the target PC. Used on returns back from native modules and
* calls out of native modules. Inverse of entering_native().
*/
static void
back_from_native_common(dcontext_t *dcontext, priv_mcontext_t *mc, app_pc target)
{
/* ASSUMPTION: was native entire time, don't need to initialize dcontext
* or anything, and next_tag is still there!
*/
ASSERT(dcontext->whereami == WHERE_APP);
ASSERT(dcontext->last_exit == get_native_exec_linkstub());
ASSERT(!is_native_pc(target));
dcontext->next_tag = target;
/* tell dispatch() why we're coming there */
dcontext->whereami = WHERE_FCACHE;
#ifdef WINDOWS
/* asynch back on */
set_asynch_interception(dcontext->owning_thread, true);
#endif
/* XXX: setting same var that set_asynch_interception is! */
dcontext->thread_record->under_dynamo_control = true;
*get_mcontext(dcontext) = *mc;
/* clear pc */
get_mcontext(dcontext)->pc = 0;
DOLOG(2, LOG_TOP, {
byte *cur_esp;
GET_STACK_PTR(cur_esp);
LOG(THREAD, LOG_TOP, 2, "%s: next_tag="PFX", cur xsp="PFX", mc->xsp="PFX"\n",
__FUNCTION__, dcontext->next_tag, cur_esp, mc->xsp);
});
call_switch_stack(dcontext, dcontext->dstack, dispatch,
NULL/*not on initstack*/, false/*shouldn't return*/);
ASSERT_NOT_REACHED();
}
/* Pops all return address pairs off the native return stack up to and including
* retidx. Returns the return address corresponding to retidx. This assumes
* that the app is only doing unwinding, and not re-entering frames after
* returning past them.
*/
static app_pc
pop_retaddr_for_index(dcontext_t *dcontext, int retidx, app_pc xsp)
{
ASSERT(dcontext != NULL);
ASSERT(retidx >= 0 && retidx < MAX_NATIVE_RETSTACK &&
(uint)retidx < dcontext->native_retstack_cur);
DOCHECK(CHKLVL_ASSERTS, {
/* Because of ret imm8 instrs, we can't assert that the current xsp is
* one slot off from the xsp after the call. We can assert that it's
* within 256 bytes, though.
*/
app_pc retloc = dcontext->native_retstack[retidx].retloc;
ASSERT(xsp >= retloc && xsp <= retloc + 256 + sizeof(void*) &&
"failed to find current sp in native_retstack");
});
/* Not zeroing out the [retidx:cur] range for performance. */
dcontext->native_retstack_cur = retidx;
return dcontext->native_retstack[retidx].retaddr;
}
/* Re-enters DR after a call to a native module returns. Called from the asm
* routine back_from_native() in x86.asm.
*/
void
return_from_native(priv_mcontext_t *mc)
{
dcontext_t *dcontext;
app_pc target;
int retidx;
ENTERING_DR();
dcontext = get_thread_private_dcontext();
ASSERT(dcontext != NULL);
SYSLOG_INTERNAL_WARNING_ONCE("returned from at least one native module");
retidx = native_get_retstack_idx(mc);
target = pop_retaddr_for_index(dcontext, retidx, (app_pc) mc->xsp);
ASSERT(!is_native_pc(target) &&
"shouldn't return from native to native PC (i#1090?)");
LOG(THREAD, LOG_ASYNCH, 1, "\n!!!! Returned from NATIVE module to "PFX"\n",
target);
back_from_native_common(dcontext, mc, target); /* noreturn */
ASSERT_NOT_REACHED();
}
/* Re-enters DR on calls from native modules to non-native modules. Called from
* x86.asm.
*/
void
native_module_callout(priv_mcontext_t *mc, app_pc target)
{
dcontext_t *dcontext;
ENTERING_DR();
dcontext = get_thread_private_dcontext();
ASSERT(dcontext != NULL);
ASSERT(DYNAMO_OPTION(native_exec_retakeover));
LOG(THREAD, LOG_ASYNCH, 4, "%s: cross-module call to %p\n",
__FUNCTION__, target);
back_from_native_common(dcontext, mc, target);
ASSERT_NOT_REACHED();
}
/* Update next_tag with the real app return address. next_tag should currently
* be equal to a return stub PC. We compute the offset of the stub, and then
* divide by the length of each stub to get the index into the return stub.
*/
void
interpret_back_from_native(dcontext_t *dcontext)
{
app_pc xsp = (app_pc) get_mcontext(dcontext)->xsp;
ptr_int_t offset = dcontext->next_tag - retstub_start;
int retidx;
ASSERT(native_exec_is_back_from_native(dcontext->next_tag));
ASSERT(offset % BACK_FROM_NATIVE_RETSTUB_SIZE == 0);
retidx = (int)offset / BACK_FROM_NATIVE_RETSTUB_SIZE;
dcontext->next_tag = pop_retaddr_for_index(dcontext, retidx, xsp);
LOG(THREAD, LOG_ASYNCH, 2, "%s: tried to interpret back_from_native, "
"interpreting retaddr "PFX" instead\n", __FUNCTION__, dcontext->next_tag);
ASSERT(!is_native_pc(dcontext->next_tag));
}
void
put_back_native_retaddrs(dcontext_t *dcontext)
{
retaddr_and_retloc_t *retstack = dcontext->native_retstack;
uint i;
ASSERT(dcontext->native_retstack_cur < MAX_NATIVE_RETSTACK);
for (i = 0; i < dcontext->native_retstack_cur; i++) {
app_pc *retloc = (app_pc *) retstack[i].retloc;
ASSERT(*retloc >= retstub_start && *retloc < retstub_end);
*retloc = retstack[i].retaddr;
}
dcontext->native_retstack_cur = 0;
#ifdef HOT_PATCHING_INTERFACE
/* In hotp_only mode, a thread can be !under_dynamo_control
* and have no native_exec_retloc. For hotp_only, there
* should be no need to restore a return value on the stack
* as the thread has been native from the start and not
* half-way through as it would in the regular hot patching
* mode, i.e., with the code cache. See case 7681.
*/
if (i == 0) {
ASSERT(DYNAMO_OPTION(hotp_only));
} else {
ASSERT(!DYNAMO_OPTION(hotp_only));
}
#endif
}