| /* ********************************************************** |
| * Copyright (c) 2011-2014 Google, Inc. All rights reserved. |
| * Copyright (c) 2000-2010 VMware, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of VMware, Inc. nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| /* Copyright (c) 2003-2007 Determina Corp. */ |
| /* Copyright (c) 2001-2003 Massachusetts Institute of Technology */ |
| /* Copyright (c) 2000-2001 Hewlett-Packard Company */ |
| |
| /* |
| * dispatch.c - central dynamo control manager |
| */ |
| |
| #include "globals.h" |
| #include "link.h" |
| #include "fragment.h" |
| #include "fcache.h" |
| #include "monitor.h" |
| #include "synch.h" |
| #include "perscache.h" |
| #include "native_exec.h" |
| #include "translate.h" |
| #include <string.h> /* for strstr */ |
| |
| #ifdef CLIENT_INTERFACE |
| # include "emit.h" |
| # include "arch.h" |
| # include "instrument.h" |
| #endif |
| |
| #ifdef DGC_DIAGNOSTICS |
| # include "instr.h" |
| # include "disassemble.h" |
| #endif |
| |
| #ifdef RCT_IND_BRANCH |
| # include "rct.h" |
| #endif |
| |
| #ifdef VMX86_SERVER |
| # include "vmkuw.h" |
| #endif |
| |
| /* forward declarations */ |
| static void |
| dispatch_enter_dynamorio(dcontext_t *dcontext); |
| |
| static bool |
| dispatch_enter_fcache(dcontext_t *dcontext, fragment_t *targetf); |
| |
| static void |
| dispatch_enter_fcache_stats(dcontext_t *dcontext, fragment_t *targetf); |
| |
| static void |
| enter_fcache(dcontext_t *dcontext, fcache_enter_func_t entry, cache_pc pc); |
| |
| static void |
| dispatch_enter_native(dcontext_t *dcontext); |
| |
| static void |
| dispatch_exit_fcache(dcontext_t *dcontext); |
| |
| static void |
| dispatch_exit_fcache_stats(dcontext_t *dcontext); |
| |
| static void |
| handle_post_system_call(dcontext_t *dcontext); |
| |
| static void |
| handle_special_tag(dcontext_t *dcontext); |
| |
| #ifdef WINDOWS |
| static void |
| handle_callback_return(dcontext_t *dcontext); |
| #endif |
| |
| #ifdef CLIENT_INTERFACE |
| /* PR 356503: detect clients making syscalls via sysenter */ |
| static inline void |
| found_client_sysenter(void) |
| { |
| CLIENT_ASSERT(false, "Is your client invoking raw system calls via vdso sysenter? " |
| "While such behavior is not recommended and can create problems, " |
| "it may work with the -sysenter_is_int80 runtime option."); |
| } |
| #endif |
| |
| static bool |
| exited_due_to_ni_syscall(dcontext_t *dcontext) |
| { |
| if (TESTANY(LINK_NI_SYSCALL_ALL, dcontext->last_exit->flags)) |
| return true; |
| if (TEST(LINK_SPECIAL_EXIT, dcontext->last_exit->flags) && |
| (dcontext->upcontext.upcontext.exit_reason == EXIT_REASON_NI_SYSCALL_INT_0x81 || |
| dcontext->upcontext.upcontext.exit_reason == EXIT_REASON_NI_SYSCALL_INT_0x82)) |
| return true; |
| return false; |
| } |
| |
| /* This is the central hub of control management in DynamoRIO. |
| * It is entered with a clean dstack at startup and after every cache |
| * exit, whether normal or kernel-mediated via a trampoline context switch. |
| * Having no stack state kept across cache executions avoids |
| * self-protection issues with the dstack. |
| */ |
| void |
| dispatch(dcontext_t *dcontext) |
| { |
| fragment_t *targetf; |
| fragment_t coarse_f; |
| |
| #ifdef HAVE_TLS |
| ASSERT(dcontext == get_thread_private_dcontext()); |
| #else |
| # ifdef UNIX |
| /* CAUTION: for !HAVE_TLS, upon a fork, the child's |
| * get_thread_private_dcontext() will return NULL because its thread |
| * id is different and tls_table hasn't been updated yet (will be |
| * done in post_system_call()). NULL dcontext thus returned causes |
| * logging/core dumping to malfunction; kstats trigger asserts. |
| */ |
| ASSERT(dcontext == get_thread_private_dcontext() || pid_cached != get_process_id()); |
| # endif |
| #endif |
| |
| dispatch_enter_dynamorio(dcontext); |
| LOG(THREAD, LOG_INTERP, 2, "\ndispatch: target = "PFX"\n", dcontext->next_tag); |
| |
| /* This is really a 1-iter loop most of the time: we only iterate |
| * when we obtain a target fragment but then fail to enter the |
| * cache due to flushing before we get there. |
| */ |
| do { |
| if (is_in_dynamo_dll(dcontext->next_tag) || |
| dcontext->next_tag == BACK_TO_NATIVE_AFTER_SYSCALL) { |
| handle_special_tag(dcontext); |
| } |
| /* Neither hotp_only nor thin_client should have any fragment |
| * fcache related work to do. |
| */ |
| ASSERT(!RUNNING_WITHOUT_CODE_CACHE()); |
| targetf = fragment_lookup_fine_and_coarse(dcontext, dcontext->next_tag, |
| &coarse_f, dcontext->last_exit); |
| #ifdef UNIX |
| /* i#1276: dcontext->next_tag could be a special stub pc used by |
| * DR to maintain control in hybrid execution, in which case the |
| * target should be replaced with correct app target. |
| */ |
| if (targetf == NULL && |
| DYNAMO_OPTION(native_exec) && DYNAMO_OPTION(native_exec_opt) && |
| native_exec_replace_next_tag(dcontext)) |
| continue; |
| #endif |
| do { |
| if (targetf != NULL) { |
| KSTART(monitor_enter); |
| /* invoke monitor to continue or start a trace |
| * may result in changing or nullifying targetf |
| */ |
| targetf = monitor_cache_enter(dcontext, targetf); |
| KSTOP_NOT_MATCHING(monitor_enter); /* or monitor_enter_thci */ |
| } |
| if (targetf != NULL) |
| break; |
| /* must call outside of USE_BB_BUILDING_LOCK guard for bb_lock_would_have: */ |
| SHARED_BB_LOCK(); |
| if (USE_BB_BUILDING_LOCK() || targetf == NULL) { |
| /* must re-lookup while holding lock and keep the lock until we've |
| * built the bb and added it to the lookup table |
| * FIXME: optimize away redundant lookup: flags to know why came out? |
| */ |
| targetf = fragment_lookup_fine_and_coarse(dcontext, dcontext->next_tag, |
| &coarse_f, dcontext->last_exit); |
| } |
| if (targetf == NULL) { |
| SELF_PROTECT_LOCAL(dcontext, WRITABLE); |
| targetf = |
| build_basic_block_fragment(dcontext, dcontext->next_tag, |
| 0, true/*link*/, true/*visible*/ |
| _IF_CLIENT(false/*!for_trace*/) |
| _IF_CLIENT(NULL)); |
| SELF_PROTECT_LOCAL(dcontext, READONLY); |
| } |
| if (targetf != NULL && TEST(FRAG_COARSE_GRAIN, targetf->flags)) { |
| /* targetf is a static temp fragment protected by bb_building_lock, |
| * so we must make a local copy to use before releasing the lock. |
| * FIXME: best to pass local wrapper to build_basic_block_fragment |
| * and all the way through emit and link? Would need linkstubs |
| * tailing the fragment_t. |
| */ |
| ASSERT(USE_BB_BUILDING_LOCK_STEADY_STATE()); |
| fragment_coarse_wrapper(&coarse_f, targetf->tag, |
| FCACHE_ENTRY_PC(targetf)); |
| targetf = &coarse_f; |
| } |
| SHARED_BB_UNLOCK(); |
| if (targetf == NULL) |
| break; |
| /* loop around and re-do monitor check */ |
| } while (true); |
| |
| if (targetf != NULL) { |
| if (dispatch_enter_fcache(dcontext, targetf)) { |
| /* won't reach here: will re-enter dispatch() with a clean stack */ |
| ASSERT_NOT_REACHED(); |
| } else |
| targetf = NULL; /* targetf was flushed */ |
| } |
| } while (true); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* returns true if pc is a point at which DynamoRIO should stop interpreting */ |
| bool |
| is_stopping_point(dcontext_t *dcontext, app_pc pc) |
| { |
| if ((pc == BACK_TO_NATIVE_AFTER_SYSCALL && |
| /* case 6253: app may xfer to this "address" in which case pass |
| * exception to app |
| */ |
| dcontext->native_exec_postsyscall != NULL) |
| #ifdef DR_APP_EXPORTS |
| || (!automatic_startup && |
| (pc == (app_pc)dynamorio_app_exit || |
| /* FIXME: Is this a holdover from long ago? dymamo_thread_exit |
| * should not be called from the cache. |
| */ |
| pc == (app_pc)dynamo_thread_exit || |
| pc == (app_pc)dr_app_stop)) |
| #endif |
| #ifdef WINDOWS |
| /* we go all the way to NtTerminateThread/NtTerminateProcess */ |
| #else /* UNIX */ |
| /* we go all the way to SYS_exit or SYS_{,t,tg}kill(SIGABRT) */ |
| #endif |
| ) |
| return true; |
| |
| return false; |
| } |
| |
| static void |
| dispatch_enter_fcache_stats(dcontext_t *dcontext, fragment_t *targetf) |
| { |
| #ifdef DEBUG |
| # ifdef DGC_DIAGNOSTICS |
| if (TEST(FRAG_DYNGEN, targetf->flags) && !is_dyngen_vsyscall(targetf->tag)) { |
| char buf[MAXIMUM_SYMBOL_LENGTH]; |
| bool stack = is_address_on_stack(dcontext, targetf->tag); |
| LOG(THREAD, LOG_DISPATCH, 1, "Entry into dyngen F%d("PFX"%s%s) via:", |
| targetf->id, targetf->tag, |
| stack ? " stack":"", |
| (targetf->flags & FRAG_DYNGEN_RESTRICTED) != 0 ? " BAD":""); |
| if (!LINKSTUB_FAKE(dcontext->last_exit)) { |
| app_pc translated_pc; |
| /* can't recreate if fragment is deleted -- but should be fake then */ |
| ASSERT(!TEST(FRAG_WAS_DELETED, dcontext->last_fragment->flags)); |
| translated_pc = recreate_app_pc(dcontext, EXIT_CTI_PC(dcontext->last_fragment, |
| dcontext->last_exit), |
| dcontext->last_fragment); |
| if (translated_pc != NULL) { |
| disassemble(dcontext, translated_pc, THREAD); |
| print_symbolic_address(translated_pc, buf, sizeof(buf), false); |
| LOG(THREAD, LOG_DISPATCH, 1, " %s\n", buf); |
| } |
| if (!stack && |
| (strstr(buf, "user32.dll") != NULL || strstr(buf, "USER32.DLL") != NULL)) { |
| /* try to find who set up user32 callback */ |
| dump_mcontext_callstack(dcontext); |
| } |
| DOLOG(stack ? 1U : 2U, LOG_DISPATCH, { |
| LOG(THREAD, LOG_DISPATCH, 1, "Originating bb:\n"); |
| disassemble_app_bb(dcontext, dcontext->last_fragment->tag, THREAD); |
| }); |
| } else { |
| /* FIXME: print type from last_exit */ |
| LOG(THREAD, LOG_DISPATCH, 1, "\n"); |
| } |
| if (stack) { |
| /* try to understand where code is on stack */ |
| LOG(THREAD, LOG_DISPATCH, 1, "cur esp="PFX" ebp="PFX"\n", |
| get_mcontext(dcontext)->xsp, get_mcontext(dcontext)->xbp); |
| dump_mcontext_callstack(dcontext); |
| } |
| } |
| # endif |
| |
| if (stats->loglevel >= 2 && (stats->logmask & LOG_DISPATCH) != 0) { |
| /* FIXME: this should use a different mask - and get printed at level 2 when turned on */ |
| DOLOG(4, LOG_DISPATCH, { |
| dump_mcontext(get_mcontext(dcontext), THREAD, DUMP_NOT_XML); }); |
| DOLOG(6, LOG_DISPATCH, { dump_mcontext_callstack(dcontext); }); |
| DOKSTATS({ DOLOG(6, LOG_DISPATCH, { kstats_dump_stack(dcontext); }); }); |
| LOG(THREAD, LOG_DISPATCH, 2, "Entry into F%d("PFX")."PFX" %s%s%s", |
| targetf->id, |
| targetf->tag, |
| FCACHE_ENTRY_PC(targetf), |
| IF_X64_ELSE(FRAG_IS_32(targetf->flags) ? "(32-bit)" : "", ""), |
| TEST(FRAG_COARSE_GRAIN, targetf->flags) ? "(coarse)" : "", |
| ((targetf->flags & FRAG_IS_TRACE_HEAD)!=0)? |
| "(trace head)" : "", |
| ((targetf->flags & FRAG_IS_TRACE)!=0)? "(trace)" : ""); |
| LOG(THREAD, LOG_DISPATCH, 2, "%s", |
| TEST(FRAG_SHARED, targetf->flags) ? "(shared)":""); |
| # ifdef DGC_DIAGNOSTICS |
| LOG(THREAD, LOG_DISPATCH, 2, "%s", |
| TEST(FRAG_DYNGEN, targetf->flags) ? "(dyngen)":""); |
| # endif |
| LOG(THREAD, LOG_DISPATCH, 2, "\n"); |
| |
| DOLOG(3, LOG_SYMBOLS, { |
| char symbuf[MAXIMUM_SYMBOL_LENGTH]; |
| print_symbolic_address(targetf->tag, |
| symbuf, sizeof(symbuf), true); |
| LOG(THREAD, LOG_SYMBOLS, 3, "\t%s\n", symbuf); |
| }); |
| } |
| #endif /* DEBUG */ |
| } |
| |
| /* Executes a target fragment in the fragment cache */ |
| static bool |
| dispatch_enter_fcache(dcontext_t *dcontext, fragment_t *targetf) |
| { |
| fcache_enter_func_t fcache_enter; |
| ASSERT(targetf != NULL); |
| /* ensure we don't take over when we should be going native */ |
| ASSERT(dcontext->native_exec_postsyscall == NULL); |
| |
| /* We wait until here, rather than at cache exit time, to do lazy |
| * linking so we can link to newly created fragments. |
| */ |
| if (dcontext->last_exit == get_coarse_exit_linkstub() || |
| /* We need to lazy link if either of src or tgt is coarse */ |
| (LINKSTUB_DIRECT(dcontext->last_exit->flags) && |
| TEST(FRAG_COARSE_GRAIN, targetf->flags))) { |
| coarse_lazy_link(dcontext, targetf); |
| } |
| |
| if (!enter_nolinking(dcontext, targetf, true)) { |
| /* not actually entering cache, so back to couldbelinking */ |
| enter_couldbelinking(dcontext, NULL, true); |
| LOG(THREAD, LOG_DISPATCH, 2, "Just flushed targetf, next_tag is "PFX"\n", |
| dcontext->next_tag); |
| STATS_INC(num_entrances_aborted); |
| /* shared entrance cannot-tell-if-deleted -> invalidate targetf |
| * but then may double-do the trace! |
| * FIXME: for now, we abort every time, ok to abort twice (first time |
| * b/c there was a real flush of targetf), but could be perf hit. |
| */ |
| trace_abort(dcontext); |
| return false; |
| } |
| |
| dispatch_enter_fcache_stats(dcontext, targetf); |
| |
| /* FIXME: for now we do this before the synch point to avoid complexity of |
| * missing a KSTART(fcache_* for cases like NtSetContextThread where a thread |
| * appears back at dispatch() from the synch point w/o ever entering the cache. |
| * To truly fix we need to have the NtSetContextThread handler determine |
| * whether its suspended target is at this synch point or in the cache. |
| */ |
| DOKSTATS({ |
| /* stopped in dispatch_exit_fcache_stats */ |
| if (TEST(FRAG_IS_TRACE, targetf->flags)) |
| KSTART(fcache_trace_trace); |
| else |
| KSTART(fcache_default); /* fcache_bb_bb or fcache_bb_trace */ |
| /* FIXME: overestimates fcache time by counting in |
| * fcache_enter/fcache_return for it - proper reading of this |
| * value should discount the minimal cost of |
| * fcache_enter/fcache_return for actual code cache times |
| */ |
| /* FIXME: asynch events currently continue their current kstat |
| * until they get back to dispatch, so in-fcache kstats are counting |
| * the in-DR trampoline execution time! |
| */ |
| }); |
| |
| #ifdef WINDOWS |
| /* synch point for suspend, terminate, and detach */ |
| /* assumes mcontext is valid including errno but not pc (which we fix here) |
| * assumes that thread is holding no locks |
| * also assumes past enter_nolinking, so could_be_linking is false |
| * for safety with respect to flush */ |
| /* a fast check before the heavy lifting */ |
| if (should_wait_at_safe_spot(dcontext)) { |
| /* FIXME : we could put this synch point in enter_fcache but would need |
| * to use SYSCALL_PC for syscalls (see issues with that in win32/os.c) |
| */ |
| priv_mcontext_t *mcontext = get_mcontext(dcontext); |
| cache_pc save_pc = mcontext->pc; |
| /* FIXME : implementation choice, we could do recreate_app_pc |
| * (fairly expensive but this is rare) instead of using the tag |
| * which is a little hacky but should always be right */ |
| mcontext->pc = targetf->tag; |
| /* could be targeting interception code or our dll main, would be |
| * incorrect for GetContextThread and racy for detach, though we |
| * would expect it to be very rare */ |
| if (!is_dynamo_address(mcontext->pc)) { |
| check_wait_at_safe_spot(dcontext, THREAD_SYNCH_VALID_MCONTEXT); |
| /* If we don't come back here synch-er is responsible for ensuring |
| * our kstat stack doesn't get off (have to do a KSTART here) -- we |
| * don't want to do the KSTART of fcache_* before this to avoid |
| * counting synch time. |
| */ |
| } else { |
| LOG(THREAD, LOG_SYNCH, 1, |
| "wait_at_safe_spot - unable to wait, targeting dr addr "PFX, |
| mcontext->pc); |
| STATS_INC(no_wait_entries); |
| } |
| mcontext->pc = save_pc; |
| } |
| #endif |
| |
| #if defined(UNIX) && defined(DEBUG) |
| /* i#238/PR 499179: check that libc errno hasn't changed. It's |
| * not worth actually saving+restoring since to we'd also need to |
| * preserve on clean calls, a perf hit. Better to catch all libc |
| * routines that need it and wrap just those. |
| */ |
| ASSERT(get_libc_errno() == dcontext->libc_errno || |
| /* w/ private loader, our errno is disjoint from app's */ |
| IF_CLIENT_INTERFACE_ELSE(INTERNAL_OPTION(private_loader), false) || |
| /* only when pthreads is loaded does libc switch to a per-thread |
| * errno, so our raw thread tests end up using the same errno |
| * for each thread! |
| */ |
| check_filter("linux.thread;linux.clone", |
| get_short_name(get_application_name()))); |
| #endif |
| |
| #if defined(UNIX) && !defined(DGC_DIAGNOSTICS) |
| /* i#107: handle segment register usage conflicts between app and dr: |
| * if the target fragment has an instr that updates the segment selector, |
| * update the corresponding information maintained by DR. |
| */ |
| if (INTERNAL_OPTION(mangle_app_seg) && |
| TEST(FRAG_HAS_MOV_SEG, targetf->flags)) { |
| os_handle_mov_seg(dcontext, targetf->tag); |
| } |
| #endif |
| |
| ASSERT(dr_get_isa_mode(dcontext) == FRAG_ISA_MODE(targetf->flags) |
| IF_X64(|| (dr_get_isa_mode(dcontext) == DR_ISA_IA32 && |
| !FRAG_IS_32(targetf->flags) && DYNAMO_OPTION(x86_to_x64)))); |
| if (TEST(FRAG_SHARED, targetf->flags)) |
| fcache_enter = get_fcache_enter_shared_routine(dcontext); |
| else |
| fcache_enter = get_fcache_enter_private_routine(dcontext); |
| |
| enter_fcache(dcontext, fcache_enter, FCACHE_ENTRY_PC(targetf)); |
| ASSERT_NOT_REACHED(); |
| return true; |
| } |
| |
| /* Enters the cache at the specified entrance routine to execute the |
| * target pc. |
| * Does not return. |
| * Caller must do a KSTART to avoid kstats stack mismatches. |
| * FIXME: only allow access to fcache_enter routine through here? |
| * Indirect routine needs special treatment for handle_callback_return |
| */ |
| static void |
| enter_fcache(dcontext_t *dcontext, fcache_enter_func_t entry, cache_pc pc) |
| { |
| ASSERT(!is_couldbelinking(dcontext)); |
| ASSERT(entry != NULL); |
| ASSERT(pc != NULL); |
| ASSERT(check_should_be_protected(DATASEC_RARELY_PROT)); |
| /* CANNOT hold any locks across cache execution, as our thread synch |
| * assumes none are held |
| */ |
| ASSERT_OWN_NO_LOCKS(); |
| ASSERT(dcontext->try_except.try_except_state == NULL); |
| |
| /* prepare to enter fcache */ |
| LOG(THREAD, LOG_DISPATCH, 4, "fcache_enter = "PFX", target = "PFX"\n", entry, pc); |
| set_fcache_target(dcontext, pc); |
| ASSERT(pc != NULL); |
| |
| #ifdef PROFILE_RDTSC |
| if (dynamo_options.profile_times) { |
| /* prepare to enter fcache */ |
| dcontext->prev_fragment = NULL; |
| |
| /* top ten cache times */ |
| dcontext->cache_frag_count = (uint64) 0; |
| dcontext->cache_enter_time = get_time(); |
| } |
| #endif |
| |
| dcontext->whereami = WHERE_FCACHE; |
| (*entry)(dcontext); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* Handles special tags in DR or elsewhere that do interesting things. |
| * All PCs checked in here must be in DR or be BACK_TO_NATIVE_AFTER_SYSCALL. |
| * Does not return if we've hit a stopping point; otherwise returns with an |
| * updated next_tag for continued dispatch. |
| */ |
| static void |
| handle_special_tag(dcontext_t *dcontext) |
| { |
| if (native_exec_is_back_from_native(dcontext->next_tag)) { |
| /* This can happen if we start interpreting a native module. */ |
| ASSERT(DYNAMO_OPTION(native_exec)); |
| interpret_back_from_native(dcontext); /* updates next_tag */ |
| } |
| |
| if (is_stopping_point(dcontext, dcontext->next_tag)) { |
| LOG(THREAD, LOG_INTERP, 1, |
| "\nFound DynamoRIO stopping point: thread "TIDFMT" returning to app @"PFX"\n", |
| get_thread_id(), dcontext->next_tag); |
| dispatch_enter_native(dcontext); |
| ASSERT_NOT_REACHED(); /* noreturn */ |
| } |
| } |
| |
| #if defined(DR_APP_EXPORTS) || defined(UNIX) |
| static void |
| dispatch_at_stopping_point(dcontext_t *dcontext) |
| { |
| /* start/stop interface */ |
| KSTOP_NOT_MATCHING(dispatch_num_exits); |
| |
| /* if we stop in middle of tracing, thread-shared state may be messed |
| * up (e.g., monitor grabs fragment lock for unlinking), |
| * so abort the trace |
| */ |
| if (is_building_trace(dcontext)) { |
| LOG(THREAD, LOG_INTERP, 1, "squashing trace-in-progress\n"); |
| trace_abort(dcontext); |
| } |
| |
| LOG(THREAD, LOG_INTERP, 1, "\nappstart_cleanup: found stopping point\n"); |
| # ifdef DEBUG |
| # ifdef DR_APP_EXPORTS |
| if (dcontext->next_tag == (app_pc)dynamo_thread_exit) |
| LOG(THREAD, LOG_INTERP, 1, "\t==dynamo_thread_exit\n"); |
| else if (dcontext->next_tag == (app_pc)dynamorio_app_exit) |
| LOG(THREAD, LOG_INTERP, 1, "\t==dynamorio_app_exit\n"); |
| else if (dcontext->next_tag == (app_pc)dr_app_stop) { |
| LOG(THREAD, LOG_INTERP, 1, "\t==dr_app_stop\n"); |
| } |
| # endif |
| # endif |
| |
| dynamo_thread_not_under_dynamo(dcontext); |
| } |
| #endif |
| |
| /* Called when we reach an interpretation stopping point either for |
| * start/stop control of DR or for native_exec. In both cases we give up |
| * control and "go native", but we do not clean up the current thread, |
| * assuming we will either take control back, or the app will explicitly |
| * request we clean up. |
| */ |
| static void |
| dispatch_enter_native(dcontext_t *dcontext) |
| { |
| /* The new fcache_enter's clean dstack design makes it usable for |
| * entering native execution as well as the fcache. |
| */ |
| fcache_enter_func_t go_native = get_fcache_enter_private_routine(dcontext); |
| set_last_exit(dcontext, (linkstub_t *) get_native_exec_linkstub()); |
| ASSERT_OWN_NO_LOCKS(); |
| if (dcontext->next_tag == BACK_TO_NATIVE_AFTER_SYSCALL) { |
| /* we're simply going native again after an intercepted syscall, |
| * not finalizing this thread or anything |
| */ |
| IF_WINDOWS(DEBUG_DECLARE(extern dcontext_t *early_inject_load_helper_dcontext;)) |
| ASSERT(DYNAMO_OPTION(native_exec_syscalls)); /* else wouldn't have intercepted */ |
| |
| /* Assert here we have a reason for going back to native (-native_exec and |
| * non-empty native_exec_areas, RUNNING_WITHOUT_CODE_CACHE, hotp nudge thread |
| * pretending to be native while loading a dll, or on win2k |
| * early_inject_init() pretending to be native to find the inject address). */ |
| ASSERT((DYNAMO_OPTION(native_exec) && native_exec_areas != NULL && |
| !vmvector_empty(native_exec_areas)) || |
| IF_WINDOWS((DYNAMO_OPTION(early_inject) && |
| early_inject_load_helper_dcontext == |
| get_thread_private_dcontext()) ||) |
| IF_HOTP(dcontext->nudge_thread ||) |
| /* clients requesting native execution come here */ |
| IF_CLIENT_INTERFACE(dr_bb_hook_exists() ||) |
| dcontext->currently_stopped || |
| RUNNING_WITHOUT_CODE_CACHE()); |
| ASSERT(dcontext->native_exec_postsyscall != NULL); |
| LOG(THREAD, LOG_ASYNCH, 1, "Returning to native "PFX" after a syscall\n", |
| dcontext->native_exec_postsyscall); |
| dcontext->next_tag = dcontext->native_exec_postsyscall; |
| dcontext->native_exec_postsyscall = NULL; |
| LOG(THREAD, LOG_DISPATCH, 2, "Entry into native_exec after intercepted syscall\n"); |
| /* restore state as though never came out for syscall */ |
| KSTOP_NOT_MATCHING(dispatch_num_exits); |
| #ifdef KSTATS |
| if (!dcontext->currently_stopped) |
| KSTART_DC(dcontext, fcache_default); |
| #endif |
| enter_nolinking(dcontext, NULL, true); |
| } |
| else { |
| #if defined(DR_APP_EXPORTS) || defined(UNIX) |
| dispatch_at_stopping_point(dcontext); |
| enter_nolinking(dcontext, NULL, false); |
| #else |
| ASSERT_NOT_REACHED(); |
| #endif |
| } |
| set_fcache_target(dcontext, dcontext->next_tag); |
| dcontext->whereami = WHERE_APP; |
| (*go_native)(dcontext); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| static void |
| dispatch_enter_dynamorio(dcontext_t *dcontext) |
| { |
| /* We're transitioning to DynamoRIO from somewhere: either the fcache, |
| * the kernel (WHERE_TRAMPOLINE), or the app itself via our start/stop API. |
| * N.B.: set whereami to WHERE_APP iff this is the first dispatch() entry |
| * for this thread! |
| */ |
| where_am_i_t wherewasi = dcontext->whereami; |
| #ifdef UNIX |
| if (!(wherewasi == WHERE_FCACHE || wherewasi == WHERE_TRAMPOLINE || |
| wherewasi == WHERE_APP)) { |
| /* This is probably our own syscalls hitting our own sysenter |
| * hook (PR 212570), since we're not completely user library |
| * independent (PR 206369). |
| * The primary calls I'm worried about are dl{open,close}. |
| * Note that we can't go jump to vsyscall_syscall_end_pc here b/c |
| * fcache_return cleared the dstack, so we can't really recover. |
| * We could put in a custom exit stub and return routine and recover, |
| * but we need to get library independent anyway so it's not worth it. |
| */ |
| ASSERT(get_syscall_method() == SYSCALL_METHOD_SYSENTER); |
| IF_X64(ASSERT_NOT_REACHED()); /* no sysenter support on x64 */ |
| /* PR 356503: clients using libraries that make syscalls can end up here */ |
| IF_CLIENT_INTERFACE(found_client_sysenter()); |
| ASSERT_BUG_NUM(206369, false && |
| "DR's own syscall (via user library) hit the sysenter hook"); |
| } |
| #endif |
| ASSERT(wherewasi == WHERE_FCACHE || wherewasi == WHERE_TRAMPOLINE || |
| wherewasi == WHERE_APP); |
| dcontext->whereami = WHERE_DISPATCH; |
| ASSERT_LOCAL_HEAP_UNPROTECTED(dcontext); |
| ASSERT(check_should_be_protected(DATASEC_RARELY_PROT)); |
| /* CANNOT hold any locks across cache execution, as our thread synch |
| * assumes none are held |
| */ |
| ASSERT_OWN_NO_LOCKS(); |
| |
| #if defined(UNIX) && defined(DEBUG) |
| /* i#238/PR 499179: check that libc errno hasn't changed */ |
| /* w/ private loader, our errno is disjoint from app's */ |
| if (IF_CLIENT_INTERFACE_ELSE(!INTERNAL_OPTION(private_loader), true)) |
| dcontext->libc_errno = get_libc_errno(); |
| #endif |
| |
| DOLOG(2, LOG_INTERP, { |
| if (wherewasi == WHERE_APP) { |
| LOG(THREAD, LOG_INTERP, 2, "\ninitial dispatch: target = "PFX"\n", |
| dcontext->next_tag); |
| dump_mcontext_callstack(dcontext); |
| dump_mcontext(get_mcontext(dcontext), THREAD, DUMP_NOT_XML); |
| } |
| }); |
| |
| /* We have to perform some tasks with last_exit early, before we |
| * become couldbelinking -- the rest are done in dispatch_exit_fcache(). |
| * It's ok to de-reference last_exit since even though deleter may assume |
| * no one has ptrs to it, cannot delete until we're officially out of the |
| * cache, which doesn't happen until enter_couldbelinking -- still kind of |
| * messy that we're violating assumption of no ptrs... |
| */ |
| |
| if (wherewasi == WHERE_APP) { /* first entrance */ |
| if (dcontext->last_exit == get_syscall_linkstub()) { |
| /* i#813: the app hit our post-sysenter hook while native. |
| * XXX: should we try to process ni syscalls here? But we're only |
| * seeing post- and not pre-. |
| */ |
| LOG(THREAD, LOG_INTERP, 2, "hit post-sysenter hook while native\n"); |
| ASSERT(dcontext->currently_stopped); |
| dcontext->next_tag = BACK_TO_NATIVE_AFTER_SYSCALL; |
| dcontext->native_exec_postsyscall = vsyscall_syscall_end_pc; |
| } else { |
| ASSERT(dcontext->last_exit == get_starting_linkstub() || |
| /* The start/stop API will set this linkstub. */ |
| IF_APP_EXPORTS(dcontext->last_exit == get_native_exec_linkstub() ||) |
| /* new thread */ |
| IF_WINDOWS_ELSE_0(dcontext->last_exit == get_asynch_linkstub())); |
| } |
| } else { |
| ASSERT(dcontext->last_exit != NULL); /* MUST be set, if only to a fake linkstub_t */ |
| /* cache last_exit's fragment */ |
| dcontext->last_fragment = linkstub_fragment(dcontext, dcontext->last_exit); |
| |
| /* If we exited from an indirect branch then dcontext->next_tag |
| * already has the next tag value; otherwise we must set it here, |
| * before we might dive back into the cache for a system call. |
| */ |
| if (LINKSTUB_DIRECT(dcontext->last_exit->flags)) { |
| if (INTERNAL_OPTION(cbr_single_stub)) { |
| linkstub_t *nxt = |
| linkstub_shares_next_stub(dcontext, dcontext->last_fragment, |
| dcontext->last_exit); |
| if (nxt != NULL) { |
| /* must distinguish the two based on eflags */ |
| dcontext->last_exit = |
| linkstub_cbr_disambiguate(dcontext, dcontext->last_fragment, |
| dcontext->last_exit, nxt); |
| ASSERT(dcontext->last_fragment == |
| linkstub_fragment(dcontext, dcontext->last_exit)); |
| STATS_INC(cbr_disambiguations); |
| } |
| } |
| |
| dcontext->next_tag = EXIT_TARGET_TAG(dcontext, dcontext->last_fragment, |
| dcontext->last_exit); |
| } else { |
| /* get src info from coarse ibl exit into the right place */ |
| if (DYNAMO_OPTION(coarse_units)) { |
| if (is_ibl_sourceless_linkstub((const linkstub_t*)dcontext->last_exit)) |
| set_coarse_ibl_exit(dcontext); |
| else if (DYNAMO_OPTION(use_persisted) && |
| dcontext->last_exit == get_coarse_exit_linkstub()) { |
| /* i#670: for frozen unit, shift from persist-time mod base |
| * to use-time mod base |
| */ |
| coarse_info_t *info = dcontext->coarse_exit.dir_exit; |
| ASSERT(info != NULL); |
| if (info->mod_shift != 0 && |
| dcontext->next_tag >= info->persist_base && |
| dcontext->next_tag < info->persist_base + |
| (info->end_pc - info->base_pc)) |
| dcontext->next_tag -= info->mod_shift; |
| } |
| } |
| } |
| |
| dispatch_exit_fcache_stats(dcontext); |
| /* Maybe-permanent native transitions (dr_app_stop()) have to pop kstack, |
| * and thus so do temporary native_exec transitions. Thus, for neither |
| * is there anything to pop here. |
| */ |
| if (dcontext->last_exit != get_native_exec_linkstub() && |
| dcontext->last_exit != get_native_exec_syscall_linkstub()) |
| KSTOP_NOT_MATCHING(dispatch_num_exits); |
| } |
| /* KSWITCHed next time around for a better explanation */ |
| KSTART_DC(dcontext, dispatch_num_exits); |
| |
| if (wherewasi != WHERE_APP) { /* if not first entrance */ |
| if (get_at_syscall(dcontext)) |
| handle_post_system_call(dcontext); |
| |
| /* A non-ignorable syscall or cb return ending a bb must be acted on |
| * We do it here to avoid becoming couldbelinking twice. |
| * |
| */ |
| if (exited_due_to_ni_syscall(dcontext) |
| IF_CLIENT_INTERFACE(|| instrument_invoke_another_syscall(dcontext))) { |
| handle_system_call(dcontext); |
| /* will return here if decided to skip the syscall; else, back to dispatch() */ |
| } |
| #ifdef WINDOWS |
| else if (TEST(LINK_CALLBACK_RETURN, dcontext->last_exit->flags)) { |
| handle_callback_return(dcontext); |
| ASSERT_NOT_REACHED(); |
| } |
| #endif |
| |
| if (TEST(LINK_SPECIAL_EXIT, dcontext->last_exit->flags)) { |
| if (dcontext->upcontext.upcontext.exit_reason == EXIT_REASON_SELFMOD) { |
| /* Case 8177: If we have a flushed fragment hit a self-write, we |
| * cannot delete it in our self-write handler (b/c of case 3559's |
| * incoming links union). But, our self-write handler needs to be |
| * nolinking and needs to check sandbox2ro_threshold. So, we do our |
| * self-write check first, but we don't actually delete there for |
| * FRAG_WAS_DELETED fragments. |
| */ |
| SELF_PROTECT_LOCAL(dcontext, WRITABLE); |
| /* this fragment overwrote its original memory image */ |
| fragment_self_write(dcontext); |
| /* FIXME: optimize this to stay writable if we're going to |
| * be exiting dispatch as well -- no very quick check though |
| */ |
| SELF_PROTECT_LOCAL(dcontext, READONLY); |
| } else if (dcontext->upcontext.upcontext.exit_reason >= |
| EXIT_REASON_FLOAT_PC_FNSAVE && |
| dcontext->upcontext.upcontext.exit_reason <= |
| EXIT_REASON_FLOAT_PC_XSAVE64) { |
| float_pc_update(dcontext); |
| STATS_INC(float_pc_from_dispatch); |
| /* Restore */ |
| dcontext->upcontext.upcontext.exit_reason = EXIT_REASON_SELFMOD; |
| } else { |
| /* When adding any new reason, be sure to clear exit_reason, |
| * as selfmod exits do not bother to set the reason field to |
| * 0 for performance reasons (they are assumed to be more common |
| * than any other "special exit"). |
| */ |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| } |
| |
| /* make sure to tell flushers that we are now going to be mucking |
| * with link info |
| */ |
| if (!enter_couldbelinking(dcontext, dcontext->last_fragment, true)) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Just flushed last_fragment\n"); |
| /* last_fragment flushed, but cannot access here to copy it |
| * to fake linkstub_t, so assert that callee did (either when freeing or |
| * when noticing pending deletion flag) |
| */ |
| ASSERT(LINKSTUB_FAKE(dcontext->last_exit)); |
| } |
| |
| if (wherewasi != WHERE_APP) { /* if not first entrance */ |
| /* now fully process the last cache exit as couldbelinking */ |
| dispatch_exit_fcache(dcontext); |
| } |
| } |
| |
| /* Processing of the last exit from the cache. |
| * Invariant: dcontext->last_exit != NULL, though it may be a sentinel (see below). |
| * |
| * Note that the last exit and its owning fragment may be _fake_, i.e., just |
| * a copy of the key fields we typically check, for the following cases: |
| * - last fragment was flushed: fully deleted at cache exit synch point |
| * - last fragment was deleted since it overwrote itself (selfmod) |
| * - last fragment was deleted since it was a private trace building copy |
| * - last fragment was deleted for other reasons?!? |
| * - briefly during trace emitting, nobody should care though |
| * - coarse grain fragment exits, for which we have no linkstub_t or other |
| * extraneous bookkeeping |
| * |
| * For some cases we do not currently keep the key fields at all: |
| * - last fragment was flushed: detected at write fault |
| * And some times we are unable to keep the key fields: |
| * - last fragment was flushed: targeted in ibl via target_deleted path |
| * These last two cases are the only exits from fragment for which we |
| * do not know the key fields. For the former, we exit in the middle of |
| * a fragment that was already created, so not knowing does not affect |
| * security policies or other checks much. The latter is the most problematic, |
| * as we have a number of checks depending on knowing the last exit when indirect. |
| * |
| * We have other types of exits from the cache that never involved a real |
| * fragment, for which we also use fake linkstubs: |
| * - no real last fragment: system call |
| * - no real last fragment: sigreturn |
| * - no real last fragment: native_exec return |
| * - callbacks clear last_exit, but should come out of the cache at a syscall |
| * (bug 2464 was back when tried to carry last_exit through syscall) |
| * so this will end up looking like the system call case |
| */ |
| static void |
| dispatch_exit_fcache(dcontext_t *dcontext) |
| { |
| /* case 7966: no distinction of islinking-ness for hotp_only & thin_client */ |
| ASSERT(RUNNING_WITHOUT_CODE_CACHE() || is_couldbelinking(dcontext)); |
| |
| #if defined(WINDOWS) && defined (CLIENT_INTERFACE) |
| ASSERT(!is_dynamo_address(dcontext->app_fls_data)); |
| ASSERT(dcontext->app_fls_data == NULL || |
| dcontext->app_fls_data != dcontext->priv_fls_data); |
| ASSERT(!is_dynamo_address(dcontext->app_nt_rpc)); |
| ASSERT(dcontext->app_nt_rpc == NULL || |
| dcontext->app_nt_rpc != dcontext->priv_nt_rpc); |
| ASSERT(!is_dynamo_address(dcontext->app_nls_cache)); |
| IF_X64(ASSERT(!is_dynamo_address(dcontext->app_stack_limit) || |
| IS_CLIENT_THREAD(dcontext))); |
| ASSERT(dcontext->app_nls_cache == NULL || |
| dcontext->app_nls_cache != dcontext->priv_nls_cache); |
| #endif |
| |
| if (LINKSTUB_INDIRECT(dcontext->last_exit->flags)) { |
| /* indirect branch exit processing */ |
| |
| #if defined(RETURN_AFTER_CALL) || defined(RCT_IND_BRANCH) |
| /* PR 204770: use trace component bb tag for RCT source address */ |
| app_pc src_tag = dcontext->last_fragment->tag; |
| if (!LINKSTUB_FAKE(dcontext->last_exit) && |
| TEST(FRAG_IS_TRACE, dcontext->last_fragment->flags)) { |
| /* FIXME: should we call this for direct exits as well, up front? */ |
| src_tag = get_trace_exit_component_tag |
| (dcontext, dcontext->last_fragment, dcontext->last_exit); |
| } |
| #endif |
| |
| #ifdef RETURN_AFTER_CALL |
| /* This is the permission check for any new return target, it |
| * also double checks the findings of the indirect lookup |
| * routine |
| */ |
| if (dynamo_options.ret_after_call && TEST(LINK_RETURN, dcontext->last_exit->flags)) { |
| /* ret_after_call will raise a security violation on failure */ |
| SELF_PROTECT_LOCAL(dcontext, WRITABLE); |
| ret_after_call_check(dcontext, dcontext->next_tag, src_tag); |
| SELF_PROTECT_LOCAL(dcontext, READONLY); |
| } |
| #endif /* RETURN_AFTER_CALL */ |
| |
| #ifdef RCT_IND_BRANCH |
| /* permission check for any new indirect call or jump target */ |
| /* we care to detect violations only if blocking or at least |
| * reporting the corresponding branch types |
| */ |
| if (TESTANY(OPTION_REPORT|OPTION_BLOCK, DYNAMO_OPTION(rct_ind_call)) || |
| TESTANY(OPTION_REPORT|OPTION_BLOCK, DYNAMO_OPTION(rct_ind_jump))) { |
| if ((EXIT_IS_CALL(dcontext->last_exit->flags) |
| && TESTANY(OPTION_REPORT|OPTION_BLOCK, DYNAMO_OPTION(rct_ind_call))) || |
| (EXIT_IS_JMP(dcontext->last_exit->flags) |
| && TESTANY(OPTION_REPORT|OPTION_BLOCK, DYNAMO_OPTION(rct_ind_jump))) |
| ) { |
| /* case 4995: current shared syscalls implementation |
| * reuses the indirect jump table and marks its |
| * fake linkstub as such. |
| */ |
| if (LINKSTUB_FAKE(dcontext->last_exit) /* quick check */ && |
| IS_SHARED_SYSCALLS_LINKSTUB(dcontext->last_exit)) { |
| ASSERT(IF_WINDOWS_ELSE(DYNAMO_OPTION(shared_syscalls), false)); |
| ASSERT(EXIT_IS_JMP(dcontext->last_exit->flags)); |
| } else { |
| /* rct_ind_branch_check will raise a security violation on failure */ |
| rct_ind_branch_check(dcontext, dcontext->next_tag, src_tag); |
| } |
| } |
| } |
| #endif /* RCT_IND_BRANCH */ |
| |
| /* update IBL target tables for any indirect branch exit */ |
| SELF_PROTECT_LOCAL(dcontext, WRITABLE); |
| /* update IBL target table if target is a valid IBT */ |
| /* FIXME: This is good for modularity but adds |
| * extra lookups in the fragment table. If it is |
| * performance problem can we do it better? |
| * Probably best to get bb2bb to work better and |
| * not worry about optimizing DR code. |
| */ |
| fragment_add_ibl_target(dcontext, dcontext->next_tag, |
| extract_branchtype(dcontext->last_exit->flags)); |
| /* FIXME: optimize this to stay writable if we're going to |
| * be building a bb as well -- no very quick check though |
| */ |
| SELF_PROTECT_LOCAL(dcontext, READONLY); |
| } /* LINKSTUB_INDIRECT */ |
| |
| /* ref bug 2323, we need monitor to restore last fragment now, |
| * before we break out of the loop to build a new fragment |
| * ASSUMPTION: all unusual cache exits (asynch events) abort the current |
| * trace, so this is the only place we need to restore anything. |
| * monitor_cache_enter() asserts that for us. |
| * NOTE : we wait till after the cache exit stats and logs to call |
| * monitor_cache_exit since it might change the flags of the last |
| * fragment and screw up the stats |
| */ |
| monitor_cache_exit(dcontext); |
| |
| #ifdef SIDELINE |
| /* sideline synchronization */ |
| if (dynamo_options.sideline) { |
| thread_id_t tid = get_thread_id(); |
| if (pause_for_sideline == tid) { |
| mutex_lock(&sideline_lock); |
| if (pause_for_sideline == tid) { |
| LOG(THREAD, LOG_DISPATCH|LOG_THREADS|LOG_SIDELINE, 2, |
| "Thread %d waiting for sideline thread\n", tid); |
| signal_event(paused_for_sideline_event); |
| STATS_INC(num_wait_sideline); |
| wait_for_event(resume_from_sideline_event); |
| mutex_unlock(&sideline_lock); |
| LOG(THREAD, LOG_DISPATCH|LOG_THREADS|LOG_SIDELINE, 2, |
| "Thread %d resuming after sideline thread\n", tid); |
| sideline_cleanup_replacement(dcontext); |
| } else |
| mutex_unlock(&sideline_lock); |
| } |
| } |
| #endif |
| |
| #ifdef UNIX |
| if (dcontext->signals_pending) { |
| /* FIXME: can overflow app stack if stack up too many signals |
| * by interrupting prev handlers -- exacerbated by RAC lack of |
| * caching (case 1858), which causes a cache exit prior to |
| * executing every single sigreturn! |
| */ |
| receive_pending_signal(dcontext); |
| } |
| #endif |
| |
| #ifdef CLIENT_INTERFACE |
| /* is ok to put the lock after the null check, this is only |
| * place they can be deleted |
| */ |
| if (dcontext->client_data != NULL && dcontext->client_data->to_do != NULL) { |
| client_todo_list_t *todo; |
| /* FIXME PR 200409: we're removing all API routines that use this |
| * todo list so we should never get here |
| */ |
| if (SHARED_FRAGMENTS_ENABLED()) { |
| USAGE_ERROR("CLIENT_INTERFACE incompatible with -shared_{bbs,traces}" |
| " at this time"); |
| } |
| # ifdef CLIENT_SIDELINE |
| mutex_lock(&(dcontext->client_data->sideline_mutex)); |
| # endif |
| todo = dcontext->client_data->to_do; |
| while (todo != NULL) { |
| client_todo_list_t *next_todo = todo->next; |
| fragment_t *f = fragment_lookup(dcontext, todo->tag); |
| if (f != NULL) { |
| if (todo->ilist != NULL) { |
| /* doing a replacement */ |
| fragment_t * new_f; |
| uint orig_flags = f->flags; |
| void *vmlist = NULL; |
| DEBUG_DECLARE(bool ok;) |
| LOG(THREAD, LOG_INTERP, 3, |
| "Going to do a client fragment replacement at "PFX" F%d\n", |
| f->tag, f->id); |
| /* prevent emit from deleting f, we still need it */ |
| /* FIXME: if f is shared we must hold change_linking_lock |
| * for the flags and vm area operations here |
| */ |
| ASSERT(!TEST(FRAG_SHARED, f->flags)); |
| f->flags |= FRAG_CANNOT_DELETE; |
| DEBUG_DECLARE(ok =) |
| vm_area_add_to_list(dcontext, f->tag, &vmlist, orig_flags, f, |
| false/*no locks*/); |
| ASSERT(ok); /* should never fail for private fragments */ |
| mangle(dcontext, todo->ilist, &f->flags, true, true); |
| /* mangle shouldn't change the flags here */ |
| ASSERT(f->flags == (orig_flags | FRAG_CANNOT_DELETE)); |
| new_f = emit_invisible_fragment(dcontext, todo->tag, todo->ilist, |
| orig_flags, vmlist); |
| f->flags = orig_flags; /* FIXME: ditto about change_linking_lock */ |
| instrlist_clear_and_destroy(dcontext, todo->ilist); |
| fragment_copy_data_fields(dcontext, f, new_f); |
| shift_links_to_new_fragment(dcontext, f, new_f); |
| fragment_replace(dcontext, f, new_f); |
| DOLOG(2, LOG_INTERP, { |
| LOG(THREAD, LOG_INTERP, 3, |
| "Finished emitting replacement fragment %d\n", new_f->id); |
| disassemble_fragment(dcontext, new_f, stats->loglevel < 3); |
| }); |
| } |
| /* delete [old] fragment */ |
| if ((f->flags & FRAG_CANNOT_DELETE) == 0) { |
| uint actions; |
| LOG(THREAD, LOG_INTERP, 3, "Client deleting old F%d\n", f->id); |
| if (todo->ilist != NULL) { |
| /* for the fragment replacement case, the fragment should |
| * already be unlinked and removed from the hash table. |
| */ |
| actions = FRAGDEL_NO_UNLINK | FRAGDEL_NO_HTABLE; |
| } |
| else { |
| actions = FRAGDEL_ALL; |
| } |
| fragment_delete(dcontext, f, actions); |
| |
| STATS_INC(num_fragments_deleted_client); |
| } else { |
| LOG(THREAD, LOG_INTERP, 2, "Couldn't let client delete F%d\n", |
| f->id); |
| } |
| } else { |
| LOG(THREAD, LOG_INTERP, 2, |
| "Failed to delete/replace fragment at tag "PFX" because was already deleted", |
| todo->tag); |
| } |
| |
| HEAP_TYPE_FREE(dcontext, todo, client_todo_list_t, ACCT_CLIENT, UNPROTECTED); |
| todo = next_todo; |
| } |
| dcontext->client_data->to_do = NULL; |
| # ifdef CLIENT_SIDELINE |
| mutex_unlock(&(dcontext->client_data->sideline_mutex)); |
| # endif |
| } |
| #endif /* CLIENT_INTERFACE */ |
| } |
| |
| /* stats and logs on why we exited the code cache */ |
| static void |
| dispatch_exit_fcache_stats(dcontext_t *dcontext) |
| { |
| #if defined(DEBUG) || defined(KSTATS) |
| fragment_t *next_f; |
| fragment_t *last_f; |
| fragment_t coarse_f; |
| #endif |
| |
| #ifdef PROFILE_RDTSC |
| if (dynamo_options.profile_times) { |
| int i,j; |
| uint64 end_time, total_time; |
| profile_fragment_dispatch(dcontext); |
| |
| /* top ten cache times */ |
| end_time = get_time(); |
| total_time = end_time - dcontext->cache_enter_time; |
| for (i=0; i<10; i++) { |
| if (total_time > dcontext->cache_time[i]) { |
| /* insert */ |
| for (j=9; j>i; j--) { |
| dcontext->cache_time[j] = dcontext->cache_time[j-1]; |
| dcontext->cache_count[j] = dcontext->cache_count[j-1]; |
| } |
| dcontext->cache_time[i] = total_time; |
| dcontext->cache_count[i] = dcontext->cache_frag_count; |
| break; |
| } |
| } |
| } |
| #endif |
| |
| #if defined(DEBUG) || defined(KSTATS) |
| STATS_INC(num_exits); |
| ASSERT(dcontext->last_exit != NULL); |
| |
| /* special exits that aren't from real fragments */ |
| |
| if (dcontext->last_exit == get_syscall_linkstub()) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from system call\n"); |
| STATS_INC(num_exits_syscalls); |
| # ifdef CLIENT_INTERFACE |
| /* PR 356503: clients using libraries that make syscalls, invoked from |
| * a clean call, will not trigger the whereami check below: so we |
| * locate here via mismatching kstat top-of-stack. |
| */ |
| KSTAT_THREAD(fcache_default, { |
| if (ks->node[ks->depth - 1].var == pv) { |
| found_client_sysenter(); |
| } |
| }); |
| # endif |
| KSTOP_NOT_PROPAGATED(syscall_fcache); |
| return; |
| } |
| else if (dcontext->last_exit == get_selfmod_linkstub()) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from fragment that self-flushed via code mod\n"); |
| STATS_INC(num_exits_code_mod_flush); |
| KSWITCH_STOP_NOT_PROPAGATED(fcache_default); |
| return; |
| } |
| else if (dcontext->last_exit == get_ibl_deleted_linkstub()) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from fragment deleted but hit in ibl\n"); |
| STATS_INC(num_exits_ibl_deleted); |
| KSWITCH_STOP_NOT_PROPAGATED(fcache_default); |
| return; |
| } |
| # ifdef UNIX |
| else if (dcontext->last_exit == get_sigreturn_linkstub()) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from sigreturn, or os_forge_exception\n"); |
| STATS_INC(num_exits_sigreturn); |
| KSTOP_NOT_MATCHING_NOT_PROPAGATED(syscall_fcache); |
| return; |
| } |
| # else /* WINDOWS */ |
| else if (dcontext->last_exit == get_asynch_linkstub()) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from asynch event\n"); |
| STATS_INC(num_exits_asynch); |
| /* w/ -shared_syscalls can also be a fragment kstart */ |
| KSTOP_NOT_MATCHING_NOT_PROPAGATED(syscall_fcache); |
| return; |
| } |
| # endif |
| else if (dcontext->last_exit == get_native_exec_linkstub()) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from native_exec execution\n"); |
| STATS_INC(num_exits_native_exec); |
| /* may be a quite large kstat count */ |
| KSWITCH_STOP_NOT_PROPAGATED(native_exec_fcache); |
| return; |
| } |
| else if (dcontext->last_exit == get_native_exec_syscall_linkstub()) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from native_exec syscall trampoline\n"); |
| STATS_INC(num_exits_native_exec_syscall); |
| /* may be a quite large kstat count */ |
| # if defined(DEBUG) || defined(KSTATS) |
| /* Being native for the start/stop API is different from native_exec: |
| * the former has the kstack cleared, so there's nothing to stop here |
| * (xref i#813, i#1140). |
| */ |
| if (dcontext->currently_stopped) |
| LOG(THREAD, LOG_DISPATCH, 2, "Thread is start/stop native\n"); |
| else |
| KSWITCH_STOP_NOT_PROPAGATED(native_exec_fcache); |
| # endif |
| return; |
| } |
| else if (dcontext->last_exit == get_reset_linkstub()) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit due to proactive reset\n"); |
| STATS_INC(num_exits_reset); |
| KSWITCH_STOP_NOT_PROPAGATED(fcache_default); |
| return; |
| } |
| # ifdef WINDOWS |
| else if (IS_SHARED_SYSCALLS_UNLINKED_LINKSTUB(dcontext->last_exit)) { |
| LOG(THREAD, LOG_DISPATCH, 2, |
| "Exit from unlinked shared syscall\n"); |
| STATS_INC(num_unlinked_shared_syscalls_exits); |
| KSWITCH_STOP_NOT_PROPAGATED(fcache_default); |
| return; |
| } |
| else if (IS_SHARED_SYSCALLS_LINKSTUB(dcontext->last_exit)) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from shared syscall (%s)\n", |
| IS_SHARED_SYSCALLS_TRACE_LINKSTUB(dcontext->last_exit) ? |
| "trace" : "bb"); |
| DOSTATS({ |
| if (IS_SHARED_SYSCALLS_TRACE_LINKSTUB(dcontext->last_exit)) |
| STATS_INC(num_shared_syscalls_trace_exits); |
| else |
| STATS_INC(num_shared_syscalls_bb_exits); |
| }); |
| KSWITCH_STOP_NOT_PROPAGATED(fcache_default); |
| return; |
| } |
| # endif |
| # ifdef HOT_PATCHING_INTERFACE |
| else if (dcontext->last_exit == get_hot_patch_linkstub()) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from hot patch routine\n"); |
| STATS_INC(num_exits_hot_patch); |
| KSWITCH_STOP_NOT_PROPAGATED(fcache_default); |
| return; |
| } |
| # endif |
| # ifdef CLIENT_INTERFACE |
| else if (dcontext->last_exit == get_client_linkstub()) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from client redirection\n"); |
| STATS_INC(num_exits_client_redirect); |
| KSWITCH_STOP_NOT_PROPAGATED(fcache_default); |
| return; |
| } |
| # endif |
| |
| /* normal exits from real fragments, though the last_fragment may |
| * be deleted and we are working off a copy of its important fields |
| */ |
| |
| /* FIXME: this lookup is needed for KSTATS and STATS_*. STATS_* are only |
| * printed at loglevel 1, but maintained at loglevel 0, and if |
| * we want an external agent to examine them at 0 we will want |
| * to keep this...leaving for now |
| */ |
| next_f = fragment_lookup_fine_and_coarse(dcontext, dcontext->next_tag, |
| &coarse_f, dcontext->last_exit); |
| last_f = dcontext->last_fragment; |
| |
| DOKSTATS({ |
| /* FIXME (case 4988): read top of kstats stack to get src |
| * type, and then split by last_fragment type as well |
| */ |
| KSWITCH_STOP_NOT_PROPAGATED(fcache_default); |
| }); |
| |
| if (is_ibl_sourceless_linkstub((const linkstub_t*)dcontext->last_exit)) { |
| if (DYNAMO_OPTION(coarse_units)) { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from coarse ibl from tag "PFX": %s %s", |
| dcontext->coarse_exit.src_tag, |
| TEST(FRAG_IS_TRACE, last_f->flags) ? "trace" : "bb", |
| TEST(LINK_RETURN, dcontext->last_exit->flags) ? "ret" : |
| EXIT_IS_CALL(dcontext->last_exit->flags) ? "call*" : "jmp*"); |
| } else { |
| ASSERT(!DYNAMO_OPTION(indirect_stubs)); |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from sourceless ibl: %s %s", |
| TEST(FRAG_IS_TRACE, last_f->flags) ? "trace" : "bb", |
| TEST(LINK_RETURN, dcontext->last_exit->flags) ? "ret" : |
| EXIT_IS_CALL(dcontext->last_exit->flags) ? "call*" : "jmp*"); |
| } |
| } else if (dcontext->last_exit == get_coarse_exit_linkstub()) { |
| DOLOG(2, LOG_DISPATCH, { |
| coarse_info_t *info = dcontext->coarse_exit.dir_exit; |
| cache_pc stub; |
| ASSERT(info != NULL); /* though not initialized to NULL... */ |
| stub = coarse_stub_lookup_by_target(dcontext, info, dcontext->next_tag); |
| LOG(THREAD, LOG_DISPATCH, 2, |
| "Exit from sourceless coarse-grain fragment via stub "PFX"\n", stub); |
| }); |
| /* FIXME: this stat is not mutually exclusive of reason-for-exit stats */ |
| STATS_INC(num_exits_coarse); |
| } |
| else if (dcontext->last_exit == get_coarse_trace_head_exit_linkstub()) { |
| LOG(THREAD, LOG_DISPATCH, 2, |
| "Exit from sourceless coarse-grain fragment targeting trace head"); |
| /* FIXME: this stat is not mutually exclusive of reason-for-exit stats */ |
| STATS_INC(num_exits_coarse_trace_head); |
| } else { |
| LOG(THREAD, LOG_DISPATCH, 2, "Exit from F%d("PFX")."PFX, |
| last_f->id, last_f->tag, EXIT_CTI_PC(dcontext->last_fragment, |
| dcontext->last_exit)); |
| } |
| |
| DOSTATS({ |
| if (TEST(FRAG_IS_TRACE, last_f->flags)) |
| STATS_INC(num_trace_exits); |
| else |
| STATS_INC(num_bb_exits); |
| }); |
| |
| LOG(THREAD, LOG_DISPATCH, 2, "%s%s", |
| IF_X64_ELSE(FRAG_IS_32(last_f->flags) ? " (32-bit)" : "", ""), |
| TEST(FRAG_SHARED, last_f->flags) ? " (shared)":""); |
| DOLOG(2, LOG_SYMBOLS, { |
| char symbuf[MAXIMUM_SYMBOL_LENGTH]; |
| print_symbolic_address(last_f->tag, symbuf, sizeof(symbuf), true); |
| LOG(THREAD, LOG_SYMBOLS, 2, "\t%s\n", symbuf); |
| }); |
| |
| # if defined(DEBUG) && defined(DGC_DIAGNOSTICS) |
| if (TEST(FRAG_DYNGEN, last_f->flags) && !is_dyngen_vsyscall(last_f->tag)) { |
| char buf[MAXIMUM_SYMBOL_LENGTH]; |
| bool stack = is_address_on_stack(dcontext, last_f->tag); |
| app_pc translated_pc; |
| print_symbolic_address(dcontext->next_tag, buf, sizeof(buf), false); |
| LOG(THREAD, LOG_DISPATCH, 1, |
| "Exit from dyngen F%d("PFX"%s%s) w/ %s targeting "PFX" %s:", |
| last_f->id, last_f->tag, stack ? " stack":"", |
| (last_f->flags & FRAG_DYNGEN_RESTRICTED) != 0 ? " BAD":"", |
| LINKSTUB_DIRECT(dcontext->last_exit->flags) ? "db":"ib", |
| dcontext->next_tag, buf); |
| /* FIXME: risky if last fragment is deleted -- should check for that |
| * here and instead just print type from last_exit, since recreate |
| * may fail |
| */ |
| translated_pc = recreate_app_pc(dcontext, EXIT_CTI_PC(dcontext->last_fragment, |
| dcontext->last_exit), |
| dcontext->last_fragment); |
| if (translated_pc != NULL) { |
| disassemble(dcontext, translated_pc, THREAD); |
| LOG(THREAD, LOG_DISPATCH, 1, "\n"); |
| } |
| DOLOG(stack ? 1U : 2U, LOG_DISPATCH, { |
| LOG(THREAD, LOG_DISPATCH, 1, "DGC bb:\n"); |
| disassemble_app_bb(dcontext, last_f->tag, THREAD); |
| }); |
| } |
| # endif /* defined(DEBUG) && defined(DGC_DIAGNOSTICS) */ |
| |
| if (LINKSTUB_INDIRECT(dcontext->last_exit->flags)) { |
| #ifdef RETURN_AFTER_CALL |
| bool ok = false; |
| #endif |
| STATS_INC(num_exits_ind_total); |
| if (next_f == NULL) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (target "PFX" not in cache)", |
| dcontext->next_tag); |
| STATS_INC(num_exits_ind_good_miss); |
| KSWITCH(num_exits_ind_good_miss); |
| } else if (is_building_trace(dcontext) && |
| !TEST(LINK_LINKED, dcontext->last_exit->flags)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (in trace-building mode)"); |
| STATS_INC(num_exits_ind_trace_build); |
| } else if (TEST(FRAG_WAS_DELETED, last_f->flags) || |
| !INTERNAL_OPTION(link_ibl)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (src unlinked)"); |
| STATS_INC(num_exits_ind_src_unlinked); |
| } else { |
| LOG(THREAD, LOG_DISPATCH, 2, " (target "PFX" in cache but not lookup table)", |
| dcontext->next_tag); |
| STATS_INC(num_exits_ind_bad_miss); |
| |
| if (TEST(FRAG_IS_TRACE, last_f->flags)) { |
| STATS_INC(num_exits_ind_bad_miss_trace); |
| if (next_f && TEST(FRAG_IS_TRACE, next_f->flags)) { |
| STATS_INC(num_exits_ind_bad_miss_trace2trace); |
| KSWITCH(num_exits_ind_bad_miss_trace2trace); |
| } else if (next_f && |
| !TEST(FRAG_IS_TRACE, next_f->flags)) { |
| if (!TEST(FRAG_IS_TRACE_HEAD, next_f->flags)) { |
| STATS_INC(num_exits_ind_bad_miss_trace2bb_nth); |
| KSWITCH(num_exits_ind_bad_miss_trace2bb_nth); |
| } else { |
| STATS_INC(num_exits_ind_bad_miss_trace2bb_th); |
| KSWITCH(num_exits_ind_bad_miss_trace2bb_th); |
| } |
| } |
| } |
| else { |
| STATS_INC(num_exits_ind_bad_miss_bb); |
| if (next_f && TEST(FRAG_IS_TRACE, next_f->flags)) { |
| STATS_INC(num_exits_ind_bad_miss_bb2trace); |
| KSWITCH(num_exits_ind_bad_miss_bb2trace); |
| } else if (next_f && |
| !TEST(FRAG_IS_TRACE, next_f->flags)) { |
| DOSTATS({ |
| if (TEST(FRAG_IS_TRACE_HEAD, next_f->flags)) |
| STATS_INC(num_exits_ind_bad_miss_bb2bb_th); |
| }); |
| STATS_INC(num_exits_ind_bad_miss_bb2bb); |
| KSWITCH(num_exits_ind_bad_miss_bb2bb); |
| } |
| } |
| } |
| DOSTATS({ |
| if (!TEST(FRAG_IS_TRACE, last_f->flags)) |
| STATS_INC(num_exits_ind_non_trace); |
| }); |
| # ifdef RETURN_AFTER_CALL |
| /* split by ind branch type */ |
| if (TEST(LINK_RETURN, dcontext->last_exit->flags)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (return from "PFX" non-trace tgt "PFX")", |
| EXIT_CTI_PC(dcontext->last_fragment, dcontext->last_exit), |
| dcontext->next_tag); |
| STATS_INC(num_exits_ret); |
| DOSTATS({ |
| if (TEST(FRAG_IS_TRACE, last_f->flags)) |
| STATS_INC(num_exits_ret_trace); |
| }); |
| } |
| else if (TESTANY(LINK_CALL|LINK_JMP, dcontext->last_exit->flags)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (ind %s from "PFX" non-trace tgt "PFX")", |
| EXIT_IS_CALL(dcontext->last_exit->flags) ? "call" : "jmp", |
| EXIT_CTI_PC(dcontext->last_fragment, dcontext->last_exit), |
| dcontext->next_tag); |
| DOSTATS({ |
| if (EXIT_IS_CALL(dcontext->last_exit->flags)) { |
| STATS_INC(num_exits_ind_call); |
| } else if (EXIT_IS_JMP(dcontext->last_exit->flags)) { |
| STATS_INC(num_exits_ind_jmp); |
| } else |
| ASSERT_NOT_REACHED(); |
| }); |
| } else if (!ok) { |
| LOG(THREAD, LOG_DISPATCH, 2, |
| "WARNING: unknown indirect exit from "PFX", in %s fragment "PFX, |
| EXIT_CTI_PC(dcontext->last_fragment, dcontext->last_exit), |
| (TEST(FRAG_IS_TRACE, last_f->flags)) ? "trace" : "bb", |
| last_f); |
| STATS_INC(num_exits_ind_unknown); |
| ASSERT_NOT_REACHED(); |
| } |
| # endif /* RETURN_AFTER_CALL */ |
| } else { /* DIRECT LINK */ |
| ASSERT(LINKSTUB_DIRECT(dcontext->last_exit->flags) || |
| IS_COARSE_LINKSTUB(dcontext->last_exit)); |
| |
| if (exited_due_to_ni_syscall(dcontext)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (block ends with syscall)"); |
| STATS_INC(num_exits_dir_syscall); |
| /* FIXME: it doesn't matter whether next_f exists or not we're still in a syscall */ |
| KSWITCH(num_exits_dir_syscall); |
| } |
| # ifdef WINDOWS |
| else if (TEST(LINK_CALLBACK_RETURN, dcontext->last_exit->flags)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (block ends with callback return)"); |
| STATS_INC(num_exits_dir_cbret); |
| } |
| # endif |
| else if (next_f == NULL) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (target "PFX" not in cache)", |
| dcontext->next_tag); |
| STATS_INC(num_exits_dir_miss); |
| KSWITCH(num_exits_dir_miss); |
| } |
| /* for SHARED_FRAGMENTS_ENABLED(), we do not grab the change_linking_lock |
| * for our is_linkable call since that leads to a lot of |
| * contention (and we don't want to go to a read-write model |
| * when most uses, and all non-debug uses, are writes!). |
| * instead, since we don't want to change state, we have no synch |
| * at all, which is ok since the state could have changed anyway |
| * (see comment at end of cases below) |
| */ |
| # ifdef DEBUG |
| else if (IS_COARSE_LINKSTUB(dcontext->last_exit)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (not lazily linked yet)"); |
| } |
| else if (!is_linkable(dcontext, dcontext->last_fragment, |
| dcontext->last_exit, next_f, |
| false/*don't own link lock*/, |
| false/*do not change trace head state*/)) { |
| STATS_INC(num_exits_dir_nolink); |
| LOG(THREAD, LOG_DISPATCH, 2, " (cannot link F%d->F%d)", |
| last_f->id, next_f->id); |
| if (is_building_trace(dcontext) && |
| !TEST(LINK_LINKED, dcontext->last_exit->flags)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (in trace-building mode)"); |
| STATS_INC(num_exits_dir_trace_build); |
| } |
| # ifndef TRACE_HEAD_CACHE_INCR |
| else if (TEST(FRAG_IS_TRACE_HEAD, next_f->flags)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (target F%d is trace head)", |
| next_f->id); |
| STATS_INC(num_exits_dir_trace_head); |
| } |
| # endif |
| else if ((last_f->flags & FRAG_SHARED) != (next_f->flags & FRAG_SHARED)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (cannot link shared to private)", |
| last_f->id, next_f->id); |
| STATS_INC(num_exits_dir_nolink_sharing); |
| } |
| # ifdef DGC_DIAGNOSTICS |
| else if ((next_f->flags & FRAG_DYNGEN) != (last_f->flags & FRAG_DYNGEN)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (cannot link DGC to non-DGC)", |
| last_f->id, next_f->id); |
| } |
| # endif |
| else if (INTERNAL_OPTION(nolink)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (nolink option is on)"); |
| } |
| else if (!TEST(FRAG_LINKED_OUTGOING, last_f->flags)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (F%d is unlinked-out)", last_f->id); |
| } |
| else if (!TEST(FRAG_LINKED_INCOMING, next_f->flags)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (F%d is unlinked-in)", next_f->id); |
| } |
| else { |
| LOG(THREAD, LOG_DISPATCH, 2, " (unknown reason)"); |
| /* link info could have changed after we exited cache so |
| * this is probably not a problem, not much we can do |
| * to distinguish race from real problem, so no assertion. |
| * race can happen even w/ single_thread_in_DR. |
| */ |
| STATS_INC(num_exits_dir_race); |
| } |
| } |
| # ifdef TRACE_HEAD_CACHE_INCR |
| else if (TEST(FRAG_IS_TRACE_HEAD, next_f->flags)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (trace head F%d now hot!)", next_f->id); |
| STATS_INC(num_exits_dir_trace_hot); |
| } |
| # endif |
| else if (TEST(FRAG_IS_TRACE, next_f->flags) && TEST(FRAG_SHARED, last_f->flags)) { |
| LOG(THREAD, LOG_DISPATCH, 2, |
| " (shared trace head shadowed by private trace F%d)", next_f->id); |
| STATS_INC(num_exits_dir_nolink_sharing); |
| } |
| else if (dcontext->next_tag == last_f->tag && next_f != last_f) { |
| /* invisible emission and replacement */ |
| LOG(THREAD, LOG_DISPATCH, 2, " (self-loop in F%d, replaced by F%d)", |
| last_f->id, next_f->id); |
| STATS_INC(num_exits_dir_self_replacement); |
| } |
| # ifdef UNIX |
| else if (dcontext->signals_pending) { |
| /* this may not always be the reason...the interrupted fragment |
| * field is modularly hidden in unix/signal.c though |
| */ |
| LOG(THREAD, LOG_DISPATCH, 2, " (interrupted by delayable signal)"); |
| STATS_INC(num_exits_dir_signal); |
| } |
| # endif |
| else if (TEST(FRAG_COARSE_GRAIN, next_f->flags) && |
| !TEST(FRAG_COARSE_GRAIN, last_f->flags)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (fine fragment targeting coarse trace head)"); |
| /* FIXME: We would assert that FRAG_IS_TRACE_HEAD is set, but |
| * we have no way of setting that up for fine to coarse links |
| */ |
| /* stats are done in monitor_cache_enter() */ |
| } |
| else { |
| LOG(THREAD, LOG_DISPATCH, 2, |
| " (UNKNOWN DIRECT EXIT F%d."PFX"->F%d)", |
| last_f->id, EXIT_CTI_PC(dcontext->last_fragment, dcontext->last_exit), |
| next_f->id); |
| /* link info could have changed after we exited cache so |
| * this is probably not a problem, not much we can do |
| * to distinguish race from real problem, so no assertion. |
| * race can happen even w/ single_thread_in_DR. |
| */ |
| STATS_INC(num_exits_dir_race); |
| } |
| # endif /* DEBUG */ |
| } |
| if (dcontext->last_exit == get_deleted_linkstub(dcontext)) { |
| LOG(THREAD, LOG_DISPATCH, 2, " (fragment was flushed)"); |
| } |
| LOG(THREAD, LOG_DISPATCH, 2, "\n"); |
| DOLOG(5, LOG_DISPATCH, { |
| dump_mcontext(get_mcontext(dcontext), THREAD, DUMP_NOT_XML); }); |
| DOLOG(6, LOG_DISPATCH, { dump_mcontext_callstack(dcontext); }); |
| DOKSTATS({ DOLOG(6, LOG_DISPATCH, { kstats_dump_stack(dcontext); }); }); |
| #endif /* defined(DEBUG) || defined(KSTATS) */ |
| } |
| |
| |
| /*************************************************************************** |
| * SYSTEM CALLS |
| */ |
| |
| #ifdef UNIX |
| static void |
| adjust_syscall_continuation(dcontext_t *dcontext) |
| { |
| /* PR 212570: for linux sysenter, we hooked the sysenter return-to-user-mode |
| * point to go to post-do-vsyscall. So we end up here w/o any extra |
| * work pre-syscall; and since we put the hook-displaced code in the nop |
| * space immediately after the sysenter instr, which is our normal |
| * continuation pc, we have no work to do here either! |
| */ |
| if (get_syscall_method() == SYSCALL_METHOD_SYSENTER) { |
| # ifdef MACOS |
| if (!dcontext->sys_was_int) { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| LOG(THREAD, LOG_SYSCALLS, 3, |
| "post-sysenter: xdx + asynch_target => "PFX" (were "PFX", "PFX")\n", |
| dcontext->app_xdx, mc->xdx, dcontext->asynch_target); |
| mc->xdx = dcontext->app_xdx; |
| dcontext->asynch_target = (app_pc) mc->xdx; |
| } |
| # else |
| /* we still see some int syscalls (for SYS_clone in particular) */ |
| ASSERT(dcontext->sys_was_int || |
| dcontext->asynch_target == vsyscall_syscall_end_pc); |
| # endif |
| } else if (vsyscall_syscall_end_pc != NULL && |
| /* PR 341469: 32-bit apps (LOL64) on AMD hardware have |
| * OP_syscall in a vsyscall page |
| */ |
| get_syscall_method() != SYSCALL_METHOD_SYSCALL) { |
| /* If we fail to hook we currently bail out to int; but we then |
| * need to manually jump to the sysenter return point. |
| * Once we have PR 288330 we can remove this. |
| */ |
| if (dcontext->asynch_target == vsyscall_syscall_end_pc) { |
| ASSERT(vsyscall_sysenter_return_pc != NULL); |
| dcontext->asynch_target = vsyscall_sysenter_return_pc; |
| } |
| } |
| } |
| #endif |
| |
| /* used to execute a system call instruction in the code cache |
| * dcontext->next_tag is store elsewhere and restored after the system call |
| * for resumption of execution post-syscall |
| */ |
| void |
| handle_system_call(dcontext_t *dcontext) |
| { |
| fcache_enter_func_t fcache_enter = get_fcache_enter_private_routine(dcontext); |
| app_pc do_syscall = (app_pc) get_do_syscall_entry(dcontext); |
| #ifdef CLIENT_INTERFACE |
| bool execute_syscall = true; |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| int sysnum = os_normalized_sysnum((int)MCXT_SYSNUM_REG(mc), NULL, dcontext); |
| #endif |
| #ifdef WINDOWS |
| /* make sure to ask about syscall before pre_syscall, which will swap new mc in! */ |
| bool use_prev_dcontext = is_cb_return_syscall(dcontext); |
| #else |
| if (TEST(LINK_NI_SYSCALL_INT, dcontext->last_exit->flags)) { |
| LOG(THREAD, LOG_SYSCALLS, 2, "Using do_int_syscall\n"); |
| do_syscall = (app_pc) get_do_int_syscall_entry(dcontext); |
| /* last_exit will be for the syscall so set a flag (could alternatively |
| * set up a separate exit stub but this is simpler) */ |
| dcontext->sys_was_int = true; |
| # ifdef VMX86_SERVER |
| if (is_vmkuw_sysnum(mc->xax)) { |
| /* Even w/ syscall # shift int80 => ENOSYS */ |
| do_syscall = get_do_vmkuw_syscall_entry(dcontext); |
| LOG(THREAD, LOG_SYSCALLS, 2, "Using do_vmkuw_syscall\n"); |
| } |
| # endif |
| } else if (TEST(LINK_SPECIAL_EXIT, dcontext->last_exit->flags)) { |
| if (dcontext->upcontext.upcontext.exit_reason == EXIT_REASON_NI_SYSCALL_INT_0x81) |
| do_syscall = (app_pc) get_do_int81_syscall_entry(dcontext); |
| else { |
| ASSERT(dcontext->upcontext.upcontext.exit_reason == |
| EXIT_REASON_NI_SYSCALL_INT_0x82); |
| do_syscall = (app_pc) get_do_int82_syscall_entry(dcontext); |
| } |
| dcontext->sys_was_int = true; |
| } else { |
| dcontext->sys_was_int = false; |
| IF_NOT_X64(IF_VMX86(ASSERT(!is_vmkuw_sysnum(mc->xax)))); |
| } |
| #endif |
| |
| #ifdef CLIENT_INTERFACE |
| /* We invoke here rather than inside pre_syscall() primarily so we can |
| * set use_prev_dcontext(), but also b/c the windows and linux uses |
| * are identical. We do want this prior to xbp-param changes for linux |
| * sysenter-to-int (PR 313715) since to client this should |
| * look like the original sysenter. For Windows we could put this |
| * after sysenter handling but it's not clear which is better: we'll |
| * assert if client changes xsp/xdx but that's fine. |
| */ |
| /* set pc so client can tell where syscall invoked from. |
| * note that this is pc _after_ syscall instr. |
| */ |
| get_mcontext(dcontext)->pc = get_fcache_target(dcontext); |
| /* i#202: ignore native syscalls in early_inject_init() */ |
| if (IF_WINDOWS(dynamo_initialized &&) |
| !instrument_pre_syscall(dcontext, sysnum)) { |
| /* we won't execute post-syscall so we do not need to store |
| * dcontext->sys_* |
| */ |
| execute_syscall = false; |
| LOG(THREAD, LOG_SYSCALLS, 2, "skipping syscall %d on client request\n", |
| MCXT_SYSNUM_REG(mc)); |
| } |
| # ifdef WINDOWS |
| /* re-set in case client changed the number */ |
| use_prev_dcontext = is_cb_return_syscall(dcontext); |
| # endif |
| #endif |
| |
| /* some syscalls require modifying local memory |
| * FIXME: move this unprot down to those syscalls to avoid unprot-prot-unprot-prot |
| * with the new clean dstack design -- though w/ shared_syscalls perhaps most |
| * syscalls coming through here will need this |
| */ |
| SELF_PROTECT_LOCAL(dcontext, WRITABLE); |
| |
| KSWITCH(num_exits_dir_syscall); /* encapsulates syscall overhead */ |
| |
| LOG(THREAD, LOG_SYSCALLS, 2, |
| "Entry into do_syscall to execute a non-ignorable system call\n"); |
| #ifdef SIDELINE |
| /* clear cur-trace field so we don't think cur trace is still running */ |
| sideline_trace = NULL; |
| #endif |
| |
| /* our flushing design assumes our syscall handlers are nolinking, |
| * to avoid multiple-flusher deadlocks |
| */ |
| ASSERT(!is_couldbelinking(dcontext)); |
| |
| /* we need to store the next pc since entering the fcache will clobber it |
| * with the do_syscall entry point. |
| * we store in a dcontext slot since some syscalls need to view or modify it |
| * (the asynch ones: sigreturn, ntcontinue, etc., hence the name asynch_target). |
| * Yes this works with an NtContinue being interrupted in the kernel for an APC -- |
| * we want to know the NtContinue target, there is no other target to remember. |
| * The only problem is if a syscall that modifies asynch_target fails -- then we |
| * want the old value, so we store it here. |
| */ |
| dcontext->asynch_target = get_fcache_target(dcontext); |
| |
| #ifdef WINDOWS |
| if (get_syscall_method() == SYSCALL_METHOD_SYSENTER) { |
| /* kernel sends control directly to 0x7ffe0304 so we need |
| * to mangle the return address |
| */ |
| /* Ref case 5461 - edx will become top of stack post-syscall */ |
| ASSERT(get_mcontext(dcontext)->xsp == get_mcontext(dcontext)->xdx); |
| #ifdef HOT_PATCHING_INTERFACE |
| /* For hotp_only, vsyscall_syscall_end_pc can be NULL as dr will never |
| * interp a system call. Also, for hotp_only, control can came here |
| * from native only to do a syscall that was hooked. |
| */ |
| ASSERT(!DYNAMO_OPTION(hotp_only) || |
| (DYNAMO_OPTION(hotp_only) && |
| dcontext->next_tag == BACK_TO_NATIVE_AFTER_SYSCALL)); |
| #else |
| ASSERT(vsyscall_syscall_end_pc != NULL || |
| get_os_version() >= WINDOWS_VERSION_8); |
| #endif |
| /* NOTE - the stack mangling must match that of intercept_nt_continue() |
| * and shared_syscall as not all routines looking at the stack |
| * differentiate. */ |
| if (dcontext->asynch_target == vsyscall_syscall_end_pc || |
| /* win8 x86 syscalls have inlined sysenter routines */ |
| (get_os_version() >= WINDOWS_VERSION_8 && |
| dcontext->thread_record->under_dynamo_control)) { |
| #ifdef HOT_PATCHING_INTERFACE |
| /* Don't expect to be here for -hotp_only */ |
| ASSERT_CURIOSITY(!DYNAMO_OPTION(hotp_only)); |
| #endif |
| ASSERT(dcontext->next_tag != BACK_TO_NATIVE_AFTER_SYSCALL); |
| /* currently pc is the ret after sysenter, we need it to be the return point |
| * (the ret after the call to the vsyscall sysenter) |
| * we do not need to keep the old asynch_target -- if we decide not to do |
| * the syscall we just have to pop the retaddr |
| */ |
| dcontext->asynch_target = *((app_pc *)get_mcontext(dcontext)->xsp); |
| ASSERT(dcontext->thread_record->under_dynamo_control); |
| } else { |
| /* else, special case like native_exec_syscall */ |
| LOG(THREAD, LOG_ALL, 2, "post-sysenter target is non-vsyscall "PFX"\n", |
| dcontext->asynch_target); |
| ASSERT(DYNAMO_OPTION(native_exec_syscalls) && |
| !dcontext->thread_record->under_dynamo_control); |
| } |
| /* FIXME A lack of write access to %esp will generate an exception |
| * originating from DR though it's really an app problem (unless we |
| * screwed up wildly). Should we call is_writeable(%esp) and force |
| * a new UNWRITEABLE_MEMORY_EXECUTION_EXCEPTION so that we don't take |
| * the blame? |
| */ |
| if (DYNAMO_OPTION(sygate_sysenter)) { |
| /* So stack looks like |
| * esp +0 app_ret_addr |
| * +4 app_val1 |
| * for the case 5441 Sygate hack the sysenter needs to have a ret |
| * address that's in ntdll.dll, but we also need to redirect |
| * control back to do_syscall. So we mangle to |
| * esp +0 sysenter_ret_address (ret in ntdll) |
| * +4 after_do_syscall |
| * dc->sysenter_storage app_val1 |
| * dc->asynch_target app_ret_addr |
| * After do_syscall we push app_val1 (since stack is popped twice) |
| * and send control to asynch_target (implicitly doing the |
| * post_sysenter ret instr). |
| */ |
| dcontext->sysenter_storage = |
| *((app_pc *)(get_mcontext(dcontext)->xsp+XSP_SZ)); |
| *((app_pc *)get_mcontext(dcontext)->xsp) = sysenter_ret_address; |
| *((app_pc *)(get_mcontext(dcontext)->xsp+XSP_SZ)) = |
| after_do_syscall_code(dcontext); |
| } else { |
| *((app_pc *)get_mcontext(dcontext)->xsp) = |
| after_do_syscall_code(dcontext); |
| } |
| } |
| #endif |
| |
| #ifdef MACOS |
| if (get_syscall_method() == SYSCALL_METHOD_SYSENTER && !dcontext->sys_was_int) { |
| /* The kernel returns control to whatever user-mode places in edx. |
| * We want to put this in even if we skip the syscall as we'll still call |
| * adjust_syscall_continuation for a skip. |
| */ |
| byte *post_sysenter = after_do_syscall_addr(dcontext); |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| dcontext->app_xdx = mc->xdx; |
| mc->xdx = (reg_t) post_sysenter; |
| } |
| #endif |
| |
| /* first do the pre-system-call */ |
| if (IF_CLIENT_INTERFACE(execute_syscall &&) pre_system_call(dcontext)) { |
| /* now do the actual syscall instruction */ |
| #ifdef UNIX |
| /* FIXME: move into some routine inside unix/? |
| * if so, move #include of sys/syscall.h too |
| */ |
| if (is_thread_create_syscall(dcontext)) { |
| /* Code for after clone is in generated code do_clone_syscall. */ |
| do_syscall = (app_pc) get_do_clone_syscall_entry(dcontext); |
| } else if (is_sigreturn_syscall(dcontext)) { |
| /* HACK: sigreturn goes straight to fcache_return, which expects |
| * app eax to already be in mcontext. pre-syscall cannot do that since |
| * do_syscall needs the syscall num in eax! |
| * so we have to do it here (alternative is to be like NtContinue handling |
| * with a special entry point, ends up being same sort of thing as here) |
| */ |
| /* pre-sigreturn handler put dest eax in next_tag |
| * save it in sys_param1, which is not used already in pre/post |
| */ |
| /* for CLIENT_INTERFACE, pre-sigreturn handler took eax after |
| * client had chance to change it, so we have the proper value here. |
| */ |
| dcontext->sys_param1 = (reg_t) dcontext->next_tag; |
| LOG(THREAD, LOG_SYSCALLS, 3, "for sigreturn, set sys_param1 to "PFX"\n", |
| dcontext->sys_param1); |
| } |
| #else |
| if (use_prev_dcontext) { |
| /* get the current, but now swapped out, dcontext */ |
| dcontext_t *tmp_dcontext = dcontext; |
| LOG(THREAD, LOG_SYSCALLS, 1, "handling a callback return\n"); |
| dcontext = get_prev_swapped_dcontext(tmp_dcontext); |
| LOG(THREAD, LOG_SYSCALLS, 1, "swapped dcontext from "PFX" to "PFX"\n", |
| tmp_dcontext, dcontext); |
| /* we have special fcache_enter that uses different dcontext, |
| * FIXME: but what if syscall fails? need to unswap dcontexts! |
| */ |
| fcache_enter = get_fcache_enter_indirect_routine(dcontext); |
| /* avoid synch errors with dispatch -- since enter_fcache will set |
| * whereami for prev dcontext, not real one! |
| */ |
| tmp_dcontext->whereami = WHERE_FCACHE; |
| } |
| #endif |
| |
| SELF_PROTECT_LOCAL(dcontext, READONLY); |
| |
| set_at_syscall(dcontext, true); |
| KSTART_DC(dcontext, syscall_fcache); /* stopped in dispatch_exit_fcache_stats */ |
| enter_fcache(dcontext, fcache_enter, do_syscall); |
| /* will handle post processing in handle_post_system_call */ |
| ASSERT_NOT_REACHED(); |
| } |
| else { |
| #ifdef CLIENT_INTERFACE |
| /* give the client its post-syscall event since we won't be calling |
| * post_system_call(), unless the client itself was the one who skipped. |
| */ |
| if (execute_syscall) { |
| instrument_post_syscall(dcontext, dcontext->sys_num); |
| } |
| #endif |
| #ifdef WINDOWS |
| if (get_syscall_method() == SYSCALL_METHOD_SYSENTER) { |
| /* decided to skip syscall -- pop retaddr, restore sysenter storage |
| * (if applicable) and set next target */ |
| get_mcontext(dcontext)->xsp += XSP_SZ; |
| if (DYNAMO_OPTION(sygate_sysenter)) { |
| *((app_pc *)get_mcontext(dcontext)->xsp) = |
| dcontext->sysenter_storage; |
| } |
| set_fcache_target(dcontext, dcontext->asynch_target); |
| } else if (get_syscall_method() == SYSCALL_METHOD_WOW64 && |
| get_os_version() == WINDOWS_VERSION_7) { |
| /* win7 has an add 4,esp after the call* in the syscall wrapper, |
| * so we need to negate it since not making the call* |
| */ |
| get_mcontext(dcontext)->xsp -= XSP_SZ; |
| } |
| #else |
| adjust_syscall_continuation(dcontext); |
| set_fcache_target(dcontext, dcontext->asynch_target); |
| #endif |
| } |
| SELF_PROTECT_LOCAL(dcontext, READONLY); |
| } |
| |
| static void |
| handle_post_system_call(dcontext_t *dcontext) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| bool skip_adjust = false; |
| |
| ASSERT(!is_couldbelinking(dcontext)); |
| ASSERT(get_at_syscall(dcontext)); |
| |
| set_at_syscall(dcontext, false); |
| |
| /* some syscalls require modifying local memory */ |
| SELF_PROTECT_LOCAL(dcontext, WRITABLE); |
| |
| #ifdef UNIX |
| /* restore mcontext values prior to invoking instrument_post_syscall() */ |
| if (was_sigreturn_syscall(dcontext)) { |
| # ifdef X86 |
| /* restore app xax */ |
| LOG(THREAD, LOG_SYSCALLS, 3, |
| "post-sigreturn: setting xax to "PFX", asynch_target="PFX"\n", |
| dcontext->sys_param1, dcontext->asynch_target); |
| mc->xax = dcontext->sys_param1; |
| # elif defined(ARM) |
| /* i#1551: NYI on ARM */ |
| ASSERT_NOT_IMPLEMENTED(false); |
| mc->r7 = dcontext->sys_param1; |
| # endif /* X86/ARM */ |
| # ifdef MACOS |
| /* We need to skip the use app_xdx, as we've changed the context. |
| * We can't just set app_xdx from handle_sigreturn() as the |
| * pre-sysenter code clobbers app_xdx, and we want to handle |
| * a failed SYS_sigreturn. |
| */ |
| skip_adjust = true; |
| # endif |
| } |
| #endif |
| |
| post_system_call(dcontext); |
| |
| /* restore state for continuation in instruction after syscall */ |
| /* FIXME: need to handle syscall failure -- those that clobbered asynch_target |
| * need to restore it to its previous value, which has to be stored somewhere! |
| */ |
| #ifdef WINDOWS |
| if (DYNAMO_OPTION(sygate_sysenter) && |
| get_syscall_method() == SYSCALL_METHOD_SYSENTER) { |
| /* restore sysenter_storage, note stack was popped twice for |
| * syscall so need to push the value */ |
| get_mcontext(dcontext)->xsp -= XSP_SZ; |
| *((app_pc *)get_mcontext(dcontext)->xsp) = dcontext->sysenter_storage; |
| } |
| #else |
| if (!skip_adjust) |
| adjust_syscall_continuation(dcontext); |
| #endif |
| set_fcache_target(dcontext, dcontext->asynch_target); |
| #ifdef WINDOWS |
| /* We no longer need asynch_target so zero it out. Other pieces of DR |
| * -- callback & APC handling, detach -- test asynch_target to determine |
| * where the next app pc to execute is stored. If asynch_target != 0, |
| * it holds the value, else it's in the esi slot. |
| */ |
| dcontext->asynch_target = 0; |
| #endif |
| |
| LOG(THREAD, LOG_SYSCALLS, 3, "finished handling system call\n"); |
| |
| SELF_PROTECT_LOCAL(dcontext, READONLY); |
| /* caller will go back to couldbelinking status */ |
| } |
| |
| #ifdef WINDOWS |
| /* in callback.c */ |
| extern void callback_start_return(priv_mcontext_t *mc); |
| /* used to execute an int 2b instruction in code cache */ |
| static void |
| handle_callback_return(dcontext_t *dcontext) |
| { |
| dcontext_t *prev_dcontext; |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| fcache_enter_func_t fcache_enter = get_fcache_enter_indirect_routine(dcontext); |
| LOG(THREAD, LOG_ASYNCH, 3, "handling a callback return\n"); |
| /* may have to abort trace -> local heap */ |
| SELF_PROTECT_LOCAL(dcontext, WRITABLE); |
| KSWITCH(num_exits_dir_cbret); |
| callback_start_return(mc); |
| /* get the current, but now swapped out, dcontext */ |
| prev_dcontext = get_prev_swapped_dcontext(dcontext); |
| SELF_PROTECT_LOCAL(dcontext, READONLY); |
| |
| /* obey flushing protocol, plus set whereami (both using real dcontext) */ |
| dcontext->whereami = WHERE_FCACHE; |
| set_at_syscall(dcontext, true); /* will be set to false on other end's post-syscall */ |
| ASSERT(!is_couldbelinking(dcontext)); |
| |
| /* if we get an APC it should be after returning to prev cxt, so don't need |
| * to worry about asynch_target |
| */ |
| |
| /* make sure set the next_tag of prev_dcontext, not dcontext! */ |
| set_fcache_target(prev_dcontext, (app_pc) get_do_callback_return_entry(prev_dcontext)); |
| DOLOG(4, LOG_ASYNCH, { |
| LOG(THREAD, LOG_ASYNCH, 3, "passing prev dcontext "PFX", next_tag "PFX":\n", |
| prev_dcontext, prev_dcontext->next_tag); |
| dump_mcontext(get_mcontext(prev_dcontext), THREAD, DUMP_NOT_XML); |
| }); |
| /* make sure to pass prev_dcontext, this is a special fcache enter routine |
| * that indirects through the dcontext passed to it (so ignores the switch-to |
| * dcontext that callback_start_return swapped into the main dcontext) |
| */ |
| KSTART_DC(dcontext, syscall_fcache); /* continue the interrupted syscall handling */ |
| (*fcache_enter)(prev_dcontext); |
| /* callback return does not return to here! */ |
| DOLOG(1, LOG_ASYNCH, { |
| LOG(THREAD, LOG_SYSCALLS, 1, "ERROR: int 2b returned!\n"); |
| dump_mcontext(get_mcontext(dcontext), THREAD, DUMP_NOT_XML); |
| }); |
| ASSERT_NOT_REACHED(); |
| } |
| #endif /* WINDOWS */ |
| |
| /* used to execute a system call instruction in code cache |
| * not expected to return |
| * caller must set up mcontext with proper system call number and arguments |
| */ |
| void |
| issue_last_system_call_from_app(dcontext_t *dcontext) |
| { |
| LOG(THREAD, LOG_SYSCALLS, 2, "issue_last_system_call_from_app("PIFX")\n", |
| MCXT_SYSNUM_REG(get_mcontext(dcontext))); |
| |
| /* it's up to the caller to let go of the bb building lock if it was held |
| * on this path, since not all paths to here hold it |
| */ |
| |
| if (is_couldbelinking(dcontext)) |
| enter_nolinking(dcontext, NULL, true); |
| KSTART(syscall_fcache); /* stopped in dispatch_exit_fcache_stats */ |
| enter_fcache(dcontext, get_fcache_enter_private_routine(dcontext), |
| (app_pc) get_global_do_syscall_entry()); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| /* Stores the register parameters into the mcontext and calls dispatch. |
| * Checks whether currently on initstack and if so clears the initstack_mutex. |
| * Does not return. |
| */ |
| void |
| transfer_to_dispatch(dcontext_t *dcontext, priv_mcontext_t *mc, bool full_DR_state) |
| { |
| app_pc cur_xsp; |
| bool using_initstack = false; |
| copy_mcontext(mc, get_mcontext(dcontext)); |
| GET_STACK_PTR(cur_xsp); |
| if (is_on_initstack(cur_xsp)) |
| using_initstack = true; |
| #if defined(WINDOWS) && defined(CLIENT_INTERFACE) |
| /* i#249: swap PEB pointers unless already in DR state */ |
| if (!full_DR_state && INTERNAL_OPTION(private_peb) && should_swap_peb_pointer()) |
| swap_peb_pointer(dcontext, true/*to priv*/); |
| #endif |
| LOG(THREAD, LOG_ASYNCH, 2, |
| "transfer_to_dispatch: pc=0x%08x, xsp="PFX", initstack=%d\n", |
| dcontext->next_tag, mc->xsp, using_initstack); |
| |
| /* next, want to switch to dstack, and if using initstack, free mutex. |
| * finally, call dispatch(dcontext). |
| * note that we switch to the base of dstack, deliberately squashing |
| * what may have been there before, for both new dcontext and reuse dcontext |
| * options. |
| */ |
| call_switch_stack(dcontext, dcontext->dstack, dispatch, |
| using_initstack ? &initstack_mutex : NULL, |
| false/*do not return on error*/); |
| ASSERT_NOT_REACHED(); |
| } |
| |