mirror of
https://github.com/DynamoRIO/dynamorio.git
synced 2026-10-12 02:02:04 +00:00
- Update `core/kernel_linux/CMakeLists.txt`: - Include source files in `core/` as dependencies for kernel module. - Define `LINUX_KERNEL`, `DYNAMORIO_INTERNAL`, `DR_DO_NOT_DEFINE_MAX_MIN` - Add `-Wframe-larger-than=4096` - Update include headers in source files under `core/`. This includes using wrapper headers (e.g., `stdarg_wrapper.h`) or excluding headers that don't have an equivalent in kernel (e.g., `math.h`). - Exclude floating point arithmetic and user space specific logic (e.g., signal handling, rseq) from kernel build. - Optimize frame stack in `options.c` to make frame less than 4KB. Issue: #8021
2478 lines
109 KiB
C
2478 lines
109 KiB
C
/* **********************************************************
|
|
* Copyright (c) 2012-2026 Google, Inc. All rights reserved.
|
|
* Copyright (c) 2008-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.
|
|
*/
|
|
|
|
/*
|
|
* thread.c - thread synchronization
|
|
*/
|
|
|
|
#include "globals.h"
|
|
#include "synch.h"
|
|
#include "instrument.h" /* is_in_client_lib() */
|
|
#include "hotpatch.h" /* hotp_only_in_tramp() */
|
|
#include "fragment.h" /* get_at_syscall() */
|
|
#include "fcache.h" /* in_fcache() */
|
|
#include "translate.h"
|
|
#include "native_exec.h"
|
|
|
|
extern vm_area_vector_t *fcache_unit_areas; /* from fcache.c */
|
|
|
|
bool started_detach = false; /* set before synchall */
|
|
bool doing_detach = false; /* set after synchall */
|
|
thread_id_t detacher_tid = INVALID_THREAD_ID;
|
|
|
|
static void
|
|
synch_thread_yield(void);
|
|
|
|
/* Thread-local data
|
|
*/
|
|
typedef struct _thread_synch_data_t {
|
|
/* the following three fields are used to synchronize for detach, suspend
|
|
* thread, terminate thread, terminate process */
|
|
/* synch_lock and pending_synch_count act as a semaphore */
|
|
/* for check_wait_at_safe_spot() must use a spin_mutex_t */
|
|
spin_mutex_t *synch_lock;
|
|
/* we allow pending_synch_count to be read without holding the synch_lock
|
|
* so all updates should be ATOMIC as well as holding the lock */
|
|
int pending_synch_count;
|
|
/* To guarantee that the thread really has this permission you need to hold the
|
|
* synch_lock when you read this value. If the target thread is suspended, use a
|
|
* trylock, as it could have been suspended while holding synch_lock (i#2805).
|
|
*/
|
|
thread_synch_permission_t synch_perm;
|
|
/* Only valid while holding all_threads_synch_lock and thread_initexit_lock. Set
|
|
* to whether synch_with_all_threads was successful in synching this thread.
|
|
*/
|
|
bool synch_with_success;
|
|
/* Case 10101: allows threads waiting_at_safe_spot() to set their own
|
|
* contexts. This use sometimes requires a full os-specific context, which
|
|
* we hide behind a generic pointer and a size.
|
|
*/
|
|
priv_mcontext_t *set_mcontext;
|
|
void *set_context;
|
|
size_t set_context_size;
|
|
#ifdef X64
|
|
/* PR 263338: we have to pad for alignment */
|
|
byte *set_context_alloc;
|
|
#endif
|
|
} thread_synch_data_t;
|
|
|
|
/* This lock prevents more than one thread from being in the synch_with_all_
|
|
* threads method body at the same time (which would lead to deadlock as they
|
|
* tried to synchronize with each other)
|
|
*/
|
|
DECLARE_CXTSWPROT_VAR(mutex_t all_threads_synch_lock,
|
|
INIT_LOCK_FREE(all_threads_synch_lock));
|
|
|
|
/* pass either mc or both cxt and cxt_size */
|
|
static void
|
|
free_setcontext(priv_mcontext_t *mc, void *cxt, size_t cxt_size _IF_X64(byte *cxt_alloc))
|
|
{
|
|
if (mc != NULL) {
|
|
ASSERT(cxt == NULL);
|
|
global_heap_free(mc, sizeof(*mc) HEAPACCT(ACCT_OTHER));
|
|
} else if (cxt != NULL) {
|
|
ASSERT(cxt_size > 0);
|
|
global_heap_free(IF_X64_ELSE(cxt_alloc, cxt), cxt_size HEAPACCT(ACCT_OTHER));
|
|
}
|
|
}
|
|
|
|
static void
|
|
synch_thread_free_setcontext(thread_synch_data_t *tsd)
|
|
{
|
|
free_setcontext(tsd->set_mcontext, tsd->set_context,
|
|
tsd->set_context_size _IF_X64(tsd->set_context_alloc));
|
|
tsd->set_mcontext = NULL;
|
|
tsd->set_context = NULL;
|
|
}
|
|
|
|
void
|
|
synch_init(void)
|
|
{
|
|
}
|
|
|
|
void
|
|
synch_exit(void)
|
|
{
|
|
ASSERT(uninit_thread_count == 0);
|
|
DELETE_LOCK(all_threads_synch_lock);
|
|
}
|
|
|
|
void
|
|
synch_thread_init(dcontext_t *dcontext)
|
|
{
|
|
thread_synch_data_t *tsd = (thread_synch_data_t *)heap_alloc(
|
|
dcontext, sizeof(thread_synch_data_t) HEAPACCT(ACCT_OTHER));
|
|
dcontext->synch_field = (void *)tsd;
|
|
tsd->pending_synch_count = 0;
|
|
tsd->synch_perm = THREAD_SYNCH_NONE;
|
|
tsd->synch_with_success = false;
|
|
tsd->set_mcontext = NULL;
|
|
tsd->set_context = NULL;
|
|
/* the synch_lock is in unprotected memory so that check_wait_at_safe_spot
|
|
* can call the EXITING_DR hook before releasing it */
|
|
tsd->synch_lock = HEAP_TYPE_ALLOC(dcontext, spin_mutex_t, ACCT_OTHER, UNPROTECTED);
|
|
ASSIGN_INIT_SPINMUTEX_FREE(*tsd->synch_lock, synch_lock);
|
|
}
|
|
|
|
void
|
|
synch_thread_exit(dcontext_t *dcontext)
|
|
{
|
|
thread_synch_data_t *tsd = (thread_synch_data_t *)dcontext->synch_field;
|
|
/* Could be waiting at safe spot when we detach or exit */
|
|
synch_thread_free_setcontext(tsd);
|
|
DELETE_SPINMUTEX(*tsd->synch_lock);
|
|
/* Note that we do need to free this in non-debug builds since, despite
|
|
* appearances, UNPROTECTED_LOCAL is acutally allocated on a global
|
|
* heap. */
|
|
HEAP_TYPE_FREE(dcontext, tsd->synch_lock, spin_mutex_t, ACCT_OTHER, UNPROTECTED);
|
|
#ifdef DEBUG
|
|
/* for non-debug we do fast exit path and don't free local heap */
|
|
/* clean up tsd fields here */
|
|
heap_free(dcontext, tsd, sizeof(thread_synch_data_t) HEAPACCT(ACCT_OTHER));
|
|
#endif
|
|
}
|
|
|
|
/* Check for a no-xfer permission. Currently used only for case 6821,
|
|
* where we need to distinguish three groups: unsafe (wait for safe
|
|
* point), safe and translatable, and safe but not translatable.
|
|
*/
|
|
bool
|
|
thread_synch_state_no_xfer(dcontext_t *dcontext)
|
|
{
|
|
thread_synch_data_t *tsd = (thread_synch_data_t *)dcontext->synch_field;
|
|
/* We use a trylock in case the thread is suspended holding synch_lock (i#2805). */
|
|
if (spinmutex_trylock(tsd->synch_lock)) {
|
|
bool res = (tsd->synch_perm == THREAD_SYNCH_NO_LOCKS_NO_XFER ||
|
|
tsd->synch_perm == THREAD_SYNCH_VALID_MCONTEXT_NO_XFER);
|
|
spinmutex_unlock(tsd->synch_lock);
|
|
return res;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
thread_synch_check_state(dcontext_t *dcontext, thread_synch_permission_t desired_perm)
|
|
{
|
|
thread_synch_data_t *tsd = (thread_synch_data_t *)dcontext->synch_field;
|
|
/* We support calling this routine from our signal handler when it has interrupted
|
|
* DR and might be holding tsd->synch_lock or other locks.
|
|
* We first check synch_perm w/o a lock and if it's not at least
|
|
* THREAD_SYNCH_NO_LOCKS we do not attempt to grab synch_lock (we'd hit rank order
|
|
* violations). If that check passes, the only problematic lock is if we already
|
|
* hold synch_lock, so we use test and trylocks there.
|
|
*/
|
|
if (desired_perm < THREAD_SYNCH_NO_LOCKS) {
|
|
ASSERT(desired_perm == THREAD_SYNCH_NONE);
|
|
return true;
|
|
}
|
|
if (!THREAD_SYNCH_SAFE(tsd->synch_perm, desired_perm))
|
|
return false;
|
|
/* barrier to keep the 1st check above on this side of the lock below */
|
|
#ifdef WINDOWS
|
|
MemoryBarrier();
|
|
#else
|
|
__asm__ __volatile__("" : : : "memory");
|
|
#endif
|
|
/* We use a trylock in case the thread is suspended holding synch_lock (i#2805).
|
|
* We start with testlock to avoid recursive lock assertions.
|
|
*/
|
|
if (!spinmutex_testlock(tsd->synch_lock) && spinmutex_trylock(tsd->synch_lock)) {
|
|
bool res = THREAD_SYNCH_SAFE(tsd->synch_perm, desired_perm);
|
|
spinmutex_unlock(tsd->synch_lock);
|
|
return res;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Only valid while holding all_threads_synch_lock and thread_initexit_lock. Set to
|
|
* whether synch_with_all_threads was successful in synching this thread.
|
|
* Cannot be called when THREAD_SYNCH_*_AND_CLEANED was requested as the
|
|
* thread-local memory will be freed on success!
|
|
*/
|
|
bool
|
|
thread_synch_successful(thread_record_t *tr)
|
|
{
|
|
thread_synch_data_t *tsd;
|
|
ASSERT(tr != NULL && tr->dcontext != NULL);
|
|
ASSERT_OWN_MUTEX(true, &all_threads_synch_lock);
|
|
ASSERT_OWN_MUTEX(true, &thread_initexit_lock);
|
|
tsd = (thread_synch_data_t *)tr->dcontext->synch_field;
|
|
return tsd->synch_with_success;
|
|
}
|
|
|
|
#ifdef UNIX
|
|
/* i#2659: the kernel is now doing auto-restart so we have to check for the
|
|
* pc being at the syscall.
|
|
*/
|
|
static bool
|
|
is_after_or_restarted_do_syscall(dcontext_t *dcontext, app_pc pc, bool check_vsyscall)
|
|
{
|
|
if (is_after_do_syscall_addr(dcontext, pc))
|
|
return true;
|
|
if (check_vsyscall && pc == vsyscall_sysenter_return_pc)
|
|
return true;
|
|
if (!get_at_syscall(dcontext)) /* rule out having just reached the syscall */
|
|
return false;
|
|
int syslen = syscall_instr_length(dr_get_isa_mode(dcontext));
|
|
if (is_after_do_syscall_addr(dcontext, pc + syslen))
|
|
return true;
|
|
if (check_vsyscall && pc + syslen == vsyscall_sysenter_return_pc)
|
|
return true;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
is_at_do_syscall(dcontext_t *dcontext, app_pc pc, byte *esp)
|
|
{
|
|
app_pc buf[2];
|
|
bool res = d_r_safe_read(esp, sizeof(buf), buf);
|
|
if (!res) {
|
|
ASSERT(res); /* we expect the stack to always be readable */
|
|
return false;
|
|
}
|
|
|
|
if (does_syscall_ret_to_callsite()) {
|
|
#ifdef WINDOWS
|
|
if (get_syscall_method() == SYSCALL_METHOD_INT && DYNAMO_OPTION(sygate_int)) {
|
|
return (pc == after_do_syscall_addr(dcontext) &&
|
|
buf[0] == after_do_syscall_code(dcontext));
|
|
} else {
|
|
return pc == after_do_syscall_code(dcontext);
|
|
}
|
|
#else
|
|
return is_after_or_restarted_do_syscall(dcontext, pc, false /*!vsys*/);
|
|
#endif
|
|
} else if (get_syscall_method() ==
|
|
SYSCALL_METHOD_SYSENTER IF_X86_32(
|
|
||
|
|
(get_syscall_method() == SYSCALL_METHOD_SYSCALL &&
|
|
cpu_info.vendor == VENDOR_AMD))) {
|
|
#ifdef WINDOWS
|
|
if (pc == vsyscall_after_syscall) {
|
|
if (DYNAMO_OPTION(sygate_sysenter))
|
|
return buf[1] == after_do_syscall_code(dcontext);
|
|
else
|
|
return buf[0] == after_do_syscall_code(dcontext);
|
|
} else {
|
|
/* not at a system call, could still have tos match after_do_syscall
|
|
* either by chance or because we leak that value on the apps stack
|
|
* (a non transparency) */
|
|
ASSERT_CURIOSITY(buf[0] != after_do_syscall_code(dcontext));
|
|
return false;
|
|
}
|
|
#else
|
|
/* Even when the main syscall method is sysenter, we also have a
|
|
* do_int_syscall and do_clone_syscall that use int, so check both.
|
|
* Note that we don't modify the stack, so once we do sysenter syscalls
|
|
* inlined in the cache (PR 288101) we'll need some mechanism to
|
|
* distinguish those: but for now if a sysenter instruction is used it
|
|
* has to be do_syscall since DR's own syscalls are ints.
|
|
*/
|
|
return is_after_or_restarted_do_syscall(dcontext, pc, true /*vsys*/);
|
|
#endif
|
|
}
|
|
/* we can reach here w/ a fault prior to 1st syscall on Linux */
|
|
IF_WINDOWS(ASSERT_NOT_REACHED());
|
|
return false;
|
|
}
|
|
|
|
/* Helper function for at_safe_spot(). Note state for client-owned threads isn't
|
|
* considered valid since it may be holding client locks and doesn't correspond to
|
|
* an actual app state. Caller should handle client-owned threads appropriately. */
|
|
static bool
|
|
is_native_thread_state_valid(dcontext_t *dcontext, app_pc pc, byte *esp)
|
|
{
|
|
/* ref case 3675, the assumption is that if we aren't executing
|
|
* out of dr memory and our stack isn't in dr memory (to disambiguate
|
|
* pc in kernel32, ntdll etc.) then the app has a valid native context.
|
|
* However, we can't call is_dynamo_address() as it (and its children)
|
|
* grab too many different locks, all of which we would have to check
|
|
* here in the same manner as fcache_unit_areas.lock in at_safe_spot(). So
|
|
* instead we just check the pc for the dr dll, interception code, and
|
|
* do_syscall regions and check the stack against the thread's dr stack
|
|
* and the d_r_initstack, all of which we can do without grabbing any locks.
|
|
* That should be sufficient at this point, XXX try to use something
|
|
* like is_dynamo_address() to make this more maintainable */
|
|
/* For sysenter system calls we also have to check the top of the stack
|
|
* for the after_do_syscall_address to catch the do_syscall @ syscall
|
|
* itself case. */
|
|
ASSERT(esp != NULL);
|
|
ASSERT(is_thread_currently_native(dcontext->thread_record));
|
|
#ifdef WINDOWS
|
|
if (pc == (app_pc)thread_attach_takeover) {
|
|
/* We are trying to take over this thread but it has not yet been
|
|
* scheduled. It was native, and can't hold any DR locks.
|
|
*/
|
|
return true;
|
|
}
|
|
#endif
|
|
return (!is_in_dynamo_dll(pc) &&
|
|
IF_WINDOWS(!is_part_of_interception(pc) &&)(
|
|
!in_generated_routine(dcontext, pc) ||
|
|
/* we allow native thread to be at do_syscall - for int syscalls the pc
|
|
* (syscall return point) will be in do_syscall (so in generated routine)
|
|
* xref case 9333 */
|
|
is_at_do_syscall(dcontext, pc, esp)) &&
|
|
!is_on_initstack(esp) && !is_on_dstack(dcontext, esp) &&
|
|
!is_in_client_lib(pc) &&
|
|
/* xref PR 200067 & 222812 on client-owned native threads */
|
|
!IS_CLIENT_THREAD(dcontext) &&
|
|
#ifdef HOT_PATCHING_INTERFACE
|
|
/* Shouldn't be in the middle of executing a hotp_only patch. The
|
|
* check for being in hotp_dll is DR_WHERE_HOTPATCH because the patch can
|
|
* change esp.
|
|
*/
|
|
(dcontext->whereami != DR_WHERE_HOTPATCH &&
|
|
/* dynamo dll check has been done */
|
|
!hotp_only_in_tramp(pc)) &&
|
|
#endif
|
|
true /* no effect, simplifies ifdef handling with && above */
|
|
);
|
|
}
|
|
|
|
/* Translates the context mcontext for the given thread trec. If
|
|
* restore_memory is true, also restores any memory values that were
|
|
* shifted (primarily due to clients). If restore_memory is true, the
|
|
* caller should always relocate the translated thread, as it may not
|
|
* execute properly if left at its current location (it could be in the
|
|
* middle of client code in the cache).
|
|
* If recreate_app_state() is called, f will be passed through to it.
|
|
*
|
|
* Like any instance where a thread_record_t is used by a thread other than its
|
|
* owner, the caller must hold the thread_initexit_lock to ensure that it
|
|
* remains valid.
|
|
* Requires thread trec is at_safe_spot().
|
|
*/
|
|
bool
|
|
translate_mcontext(thread_record_t *trec, priv_mcontext_t *mcontext, bool restore_memory,
|
|
fragment_t *f)
|
|
{
|
|
thread_synch_data_t *tsd = (thread_synch_data_t *)trec->dcontext->synch_field;
|
|
bool res;
|
|
recreate_success_t success;
|
|
bool native_translate = false;
|
|
ASSERT(tsd->pending_synch_count >= 0);
|
|
/* check if native thread */
|
|
if (is_thread_currently_native(trec)) {
|
|
/* running natively, no need to translate unless at do_syscall for an
|
|
* intercepted-via-trampoline syscall which we allow now for case 9333 */
|
|
if (IS_CLIENT_THREAD(trec->dcontext)) {
|
|
/* don't need to translate anything */
|
|
LOG(THREAD_GET, LOG_SYNCH, 1,
|
|
"translate context, thread " TIDFMT " is client "
|
|
"thread, no translation needed\n",
|
|
trec->id);
|
|
return true;
|
|
}
|
|
if (is_native_thread_state_valid(trec->dcontext, (app_pc)mcontext->pc,
|
|
(byte *)mcontext->xsp)) {
|
|
#ifdef WINDOWS
|
|
if ((app_pc)mcontext->pc == (app_pc)thread_attach_takeover) {
|
|
LOG(THREAD_GET, LOG_SYNCH, 1,
|
|
"translate context, thread " TIDFMT " at "
|
|
"takeover point\n",
|
|
trec->id);
|
|
thread_attach_translate(trec->dcontext, mcontext, restore_memory);
|
|
return true;
|
|
}
|
|
#endif
|
|
if (is_at_do_syscall(trec->dcontext, (app_pc)mcontext->pc,
|
|
(byte *)mcontext->xsp)) {
|
|
LOG(THREAD_GET, LOG_SYNCH, 1,
|
|
"translate context, thread " TIDFMT " running "
|
|
"natively, at do_syscall so translation needed\n",
|
|
trec->id);
|
|
native_translate = true;
|
|
} else {
|
|
LOG(THREAD_GET, LOG_SYNCH, 1,
|
|
"translate context, thread " TIDFMT " running "
|
|
"natively, no translation needed\n",
|
|
trec->id);
|
|
return true;
|
|
}
|
|
} else {
|
|
/* now that do_syscall is a safe spot for native threads we shouldn't get
|
|
* here for get context on self, XXX - is however possible to get here
|
|
* via get_context on unsuspended thread (result of which is technically
|
|
* undefined according to MS), see get_context post sys comments
|
|
* (should prob. synch there in which case can assert here) */
|
|
ASSERT(trec->id != d_r_get_thread_id());
|
|
ASSERT_CURIOSITY(false &&
|
|
"translate failure, likely get context on "
|
|
"unsuspended native thread");
|
|
/* we'll just try to translate and hope for the best */
|
|
native_translate = true;
|
|
}
|
|
}
|
|
if (!native_translate) {
|
|
/* check if waiting at a good spot */
|
|
spinmutex_lock(tsd->synch_lock);
|
|
res = THREAD_SYNCH_SAFE(tsd->synch_perm, THREAD_SYNCH_VALID_MCONTEXT_NO_XFER);
|
|
spinmutex_unlock(tsd->synch_lock);
|
|
if (res) {
|
|
LOG(THREAD_GET, LOG_SYNCH, 1,
|
|
"translate context, thread " TIDFMT " waiting at "
|
|
"valid mcontext point, copying over\n",
|
|
trec->id);
|
|
DOLOG(2, LOG_SYNCH, {
|
|
LOG(THREAD_GET, LOG_SYNCH, 2, "Thread State\n");
|
|
dump_mcontext(get_mcontext(trec->dcontext), THREAD_GET, DUMP_NOT_XML);
|
|
});
|
|
*mcontext = *get_mcontext(trec->dcontext);
|
|
if (dr_xl8_hook_exists()) {
|
|
if (!instrument_restore_nonfcache_state(trec->dcontext, true, mcontext))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* In case 4148 we see a thread calling NtGetContextThread on itself, which
|
|
* is undefined according to MS but it does get the syscall address, so it's
|
|
* fine with us. For other threads the app shouldn't be asking about them
|
|
* unless they're suspended, and the same goes for us.
|
|
*/
|
|
ASSERT_CURIOSITY(trec->dcontext->whereami == DR_WHERE_FCACHE ||
|
|
trec->dcontext->whereami == DR_WHERE_SIGNAL_HANDLER ||
|
|
native_translate || trec->id == d_r_get_thread_id());
|
|
LOG(THREAD_GET, LOG_SYNCH, 2,
|
|
"translate context, thread " TIDFMT " at pc_recreatable spot translating\n",
|
|
trec->id);
|
|
success = recreate_app_state(trec->dcontext, mcontext, restore_memory, f);
|
|
if (success != RECREATE_SUCCESS_STATE) {
|
|
/* should never happen right?
|
|
* actually it does when deciding whether can deliver a signal
|
|
* immediately (PR 213040).
|
|
*/
|
|
LOG(THREAD_GET, LOG_SYNCH, 1,
|
|
"translate context, thread " TIDFMT " unable to translate context at pc"
|
|
" = " PFX "\n",
|
|
trec->id, mcontext->pc);
|
|
SYSLOG_INTERNAL_WARNING_ONCE("failed to translate");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
waiting_at_safe_spot(thread_record_t *trec, thread_synch_state_t desired_state)
|
|
{
|
|
thread_synch_data_t *tsd = (thread_synch_data_t *)trec->dcontext->synch_field;
|
|
ASSERT(tsd->pending_synch_count >= 0);
|
|
/* Check if waiting at a good spot. We can't spin in case the suspended thread is
|
|
* holding this lock (e.g., i#2805). We only need the lock to check synch_perm.
|
|
*/
|
|
if (spinmutex_trylock(tsd->synch_lock)) {
|
|
thread_synch_permission_t perm = tsd->synch_perm;
|
|
bool res = THREAD_SYNCH_SAFE(perm, desired_state);
|
|
spinmutex_unlock(tsd->synch_lock);
|
|
if (res) {
|
|
LOG(THREAD_GET, LOG_SYNCH, 2,
|
|
"thread " TIDFMT " waiting at safe spot (synch_perm=%d)\n", trec->id,
|
|
perm);
|
|
return true;
|
|
}
|
|
} else {
|
|
LOG(THREAD_GET, LOG_SYNCH, 2,
|
|
"at_safe_spot unable to get locks to test if thread " TIDFMT " is waiting "
|
|
"at safe spot\n",
|
|
trec->id);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
should_suspend_client_thread(dcontext_t *dcontext, thread_synch_state_t desired_state)
|
|
{
|
|
/* Marking un-suspendable does not apply to cleaning/terminating */
|
|
ASSERT(IS_CLIENT_THREAD(dcontext));
|
|
return (THREAD_SYNCH_IS_CLEANED(desired_state) || dcontext->client_data->suspendable);
|
|
}
|
|
|
|
/* checks whether the thread trec is at a spot suitable for requested define
|
|
* desired_state
|
|
* Requires that trec thread is suspended */
|
|
/* Note that since trec is potentially suspended at an arbitrary point,
|
|
* this function (and any function it calls) cannot call mutex_lock as
|
|
* trec thread may hold a lock. It is ok for at_safe_spot to return false if
|
|
* it can't obtain a lock on the first try. XXX : in the long term we may
|
|
* want to go to a locking model that stores the thread id of the owner in
|
|
* which case we can check for this situation directly
|
|
*/
|
|
bool
|
|
at_safe_spot(thread_record_t *trec, priv_mcontext_t *mc,
|
|
thread_synch_state_t desired_state)
|
|
{
|
|
bool safe = false;
|
|
|
|
if (waiting_at_safe_spot(trec, desired_state))
|
|
return true;
|
|
|
|
#ifdef ARM
|
|
if (TESTANY(EFLAGS_IT, mc->cpsr)) {
|
|
LOG(THREAD_GET, LOG_SYNCH, 2,
|
|
"thread " TIDFMT " not at safe spot (pc=" PFX " in an IT block) for %d\n",
|
|
trec->id, mc->pc, desired_state);
|
|
return false;
|
|
}
|
|
#endif
|
|
/* check if suspended at good spot */
|
|
/* XXX: right now don't distinguish between suspend and term privileges
|
|
* even though suspend is stronger requirement, are the checks below
|
|
* sufficient */
|
|
/* XXX : check with respect to flush, should be ok */
|
|
/* test fcache_unit_areas.lock (from fcache.c) before calling recreate_app_state
|
|
* since it calls in_fcache() which uses the lock (if we are in_fcache()
|
|
* assume other locks are not a problem (so is_dynamo_address is fine)) */
|
|
/* Right now the only dr code that ends up in the cache is our DLL main
|
|
* (which we'll reduce/get rid of with libc independence), our takeover
|
|
* from preinject return stack, and the callback.c interception code.
|
|
* XXX : test for just these and ASSERT(!is_dynamo_address) otherwise */
|
|
if (is_thread_currently_native(trec)) {
|
|
/* thread is running native, verify is not in dr code */
|
|
/* We treat client-owned threads (such as a client nudge thread) as native and
|
|
* consider them safe if they are in the client_lib. Since they might own client
|
|
* locks that could block application threads from progressing, we synchronize
|
|
* with them last. XXX - xref PR 231301 - since we can't disambiguate
|
|
* client->ntdll/gencode which is safe from client->dr->ntdll/gencode which isn't
|
|
* we disallow both. This could hurt synchronization efficiency if the client
|
|
* owned thread spent most of its execution time calling out of its lib to ntdll
|
|
* routines or generated code. */
|
|
if (IS_CLIENT_THREAD(trec->dcontext)) {
|
|
safe = (trec->dcontext->client_data->client_thread_safe_for_synch ||
|
|
is_in_client_lib(mc->pc)) &&
|
|
/* Do not cleanup/terminate a thread holding a client lock (PR 558463) */
|
|
/* Actually, don't consider a thread holding a client lock to be safe
|
|
* at all (PR 609569): client should use
|
|
* dr_client_thread_set_suspendable(false) if its thread spends a lot
|
|
* of time holding locks.
|
|
*/
|
|
(!should_suspend_client_thread(trec->dcontext, desired_state) ||
|
|
trec->dcontext->client_data->mutex_count == 0) &&
|
|
/* If a client thread has set is_exiting in cleanup_and_terminate, it
|
|
* next increments exiting_thread_count and waits for the
|
|
* thread_initexit_lock to exit: and for static DR that satisfies
|
|
* is_in_client_lib(), but if we kill it then exiting_thread_count
|
|
* will never decrement and we'll fail to synch.
|
|
* Better to consider unsafe and let the thread exit and retry.
|
|
* XXX i#7673: Adding a client join feature could avoid complexity here.
|
|
*/
|
|
!trec->dcontext->is_exiting;
|
|
}
|
|
if (is_native_thread_state_valid(trec->dcontext, mc->pc, (byte *)mc->xsp)) {
|
|
safe = true;
|
|
/* We should always be able to translate a valid native state, but be
|
|
* sure to check before thread_attach_exit().
|
|
*/
|
|
ASSERT(translate_mcontext(trec, mc, false /*just querying*/, NULL));
|
|
#ifdef WINDOWS
|
|
if (mc->pc == (app_pc)thread_attach_takeover &&
|
|
THREAD_SYNCH_IS_CLEANED(desired_state)) {
|
|
/* The takeover data will be freed at process exit, but we might
|
|
* clean up a thread mid-run, so make sure we free the data.
|
|
*/
|
|
thread_attach_exit(trec->dcontext, mc);
|
|
}
|
|
#endif
|
|
}
|
|
} else if (desired_state == THREAD_SYNCH_TERMINATED_AND_CLEANED &&
|
|
trec->dcontext->whereami == DR_WHERE_FCACHE &&
|
|
trec->dcontext->client_data->at_safe_to_terminate_syscall) {
|
|
/* i#1420: At safe to terminate syscall like dr_sleep in a clean call.
|
|
* XXX: A thread in dr_sleep might not be safe to terminate for some
|
|
* corner cases: for example, a client may hold a lock and then go sleep,
|
|
* terminating it may mess the client up for not releasing the lock.
|
|
* We limit this to the thread being in fcache (i.e., from a clean call)
|
|
* to rule out some corner cases.
|
|
*/
|
|
safe = true;
|
|
} else if ((!WRITE_LOCK_HELD(&fcache_unit_areas->lock) &&
|
|
/* even though we only need the read lock, if our target holds it
|
|
* and a 3rd thread requests the write lock, we'll hang if we
|
|
* ask for the read lock (case 7493)
|
|
*/
|
|
!READ_LOCK_HELD(&fcache_unit_areas->lock)) &&
|
|
recreate_app_state(trec->dcontext, mc, false /*just query*/, NULL) ==
|
|
RECREATE_SUCCESS_STATE &&
|
|
/* It's ok to call is_dynamo_address even though it grabs many
|
|
* locks because recreate_app_state succeeded.
|
|
*/
|
|
!is_dynamo_address(mc->pc)) {
|
|
safe = true;
|
|
}
|
|
if (safe) {
|
|
ASSERT(trec->dcontext->whereami == DR_WHERE_FCACHE ||
|
|
trec->dcontext->whereami == DR_WHERE_SIGNAL_HANDLER ||
|
|
is_thread_currently_native(trec));
|
|
LOG(THREAD_GET, LOG_SYNCH, 2,
|
|
"thread " TIDFMT " suspended at safe spot pc=" PFX "\n", trec->id, mc->pc);
|
|
|
|
return true;
|
|
}
|
|
LOG(THREAD_GET, LOG_SYNCH, 2,
|
|
"thread " TIDFMT " not at safe spot (pc=" PFX ") for %d\n", trec->id, mc->pc,
|
|
desired_state);
|
|
return false;
|
|
}
|
|
|
|
/* a fast way to tell a thread if it should call check_wait_at_safe_spot
|
|
* if translating context would be expensive */
|
|
bool
|
|
should_wait_at_safe_spot(dcontext_t *dcontext)
|
|
{
|
|
thread_synch_data_t *tsd = (thread_synch_data_t *)dcontext->synch_field;
|
|
return (tsd->pending_synch_count != 0);
|
|
}
|
|
|
|
/* use with care! normally check_wait_at_safe_spot() should be called instead */
|
|
void
|
|
set_synch_state(dcontext_t *dcontext, thread_synch_permission_t state)
|
|
{
|
|
if (state >= THREAD_SYNCH_NO_LOCKS)
|
|
ASSERT_OWN_NO_LOCKS();
|
|
|
|
thread_synch_data_t *tsd = (thread_synch_data_t *)dcontext->synch_field;
|
|
/* We have a wart in the settings here (i#2805): a caller can set
|
|
* THREAD_SYNCH_NO_LOCKS, yet here we're acquiring locks. In fact if this thread
|
|
* is suspended in between the lock and the unset of synch_perm from
|
|
* THREAD_SYNCH_NO_LOCKS back to THREAD_SYNCH_NONE, it can cause problems. We
|
|
* have everyone who might query in such a state use a trylock and assume
|
|
* synch_perm is THREAD_SYNCH_NONE if the lock cannot be acquired.
|
|
*/
|
|
spinmutex_lock(tsd->synch_lock);
|
|
tsd->synch_perm = state;
|
|
spinmutex_unlock(tsd->synch_lock);
|
|
}
|
|
|
|
/* checks to see if any threads are waiting to synch with this one and waits
|
|
* if they are
|
|
* cur_state - a given permission define from above that describes the current
|
|
* state of the caller
|
|
* NOTE - Requires the caller is !could_be_linking (i.e. not in an
|
|
* enter_couldbelinking state)
|
|
*/
|
|
void
|
|
check_wait_at_safe_spot(dcontext_t *dcontext, thread_synch_permission_t cur_state)
|
|
{
|
|
thread_synch_data_t *tsd = (thread_synch_data_t *)dcontext->synch_field;
|
|
byte cxt[MAX(CONTEXT_HEAP_SIZE_OPAQUE, sizeof(priv_mcontext_t))];
|
|
bool set_context = false;
|
|
bool set_mcontext = false;
|
|
if (tsd->pending_synch_count == 0 || cur_state == THREAD_SYNCH_NONE)
|
|
return;
|
|
ASSERT(tsd->pending_synch_count >= 0);
|
|
DEBUG_DECLARE(app_pc pc = get_mcontext(dcontext)->pc;)
|
|
LOG(THREAD, LOG_SYNCH, 2, "waiting for synch with state %d (pc " PFX ")\n", cur_state,
|
|
pc);
|
|
|
|
/* Both THREAD_SYNCH_VALID_MCONTEXT_NO_XFER and THREAD_SYNCH_VALID_MCONTEXT indicate
|
|
* a valid mcontext so check for at least THREAD_SYNCH_VALID_MCONTEXT_NO_XFER.
|
|
*/
|
|
if (THREAD_SYNCH_SAFE(cur_state, THREAD_SYNCH_VALID_MCONTEXT_NO_XFER)) {
|
|
ASSERT(!is_dynamo_address(pc));
|
|
/* for detach must set this here and now */
|
|
IF_WINDOWS(set_last_error(dcontext->app_errno));
|
|
}
|
|
spinmutex_lock(tsd->synch_lock);
|
|
tsd->synch_perm = cur_state;
|
|
/* Since can be killed, suspended, etc. must call the exit dr hook. But, to
|
|
* avoid races, we must do so before giving up the synch_lock. This is why
|
|
* that lock has to be in unprotected memory. XXX - for single thread in
|
|
* dr this will lead to rank order violation between dr exclusivity lock
|
|
* and the synch_lock with no easy workaround (real deadlocks possible).
|
|
* Luckily we'll prob. never use that option. */
|
|
if (INTERNAL_OPTION(single_thread_in_DR)) {
|
|
ASSERT_NOT_IMPLEMENTED(false);
|
|
}
|
|
EXITING_DR();
|
|
/* Ref case 5074, for us/app to successfully SetThreadContext at
|
|
* this synch point, this thread can NOT be at a system call. So, for
|
|
* case 10101, we instead have threads that are waiting_at_safe_spot()
|
|
* set their own contexts, allowing us to make system calls here.
|
|
* We don't yet handle the detach case, so it still requires no system
|
|
* calls, including the act of releasing the synch_lock
|
|
* which is why that lock has to be a user mode spin yield lock.
|
|
* XXX: we could change tsd->synch_lock back to a regular lock
|
|
* once we have detach handling system calls here.
|
|
*/
|
|
spinmutex_unlock(tsd->synch_lock);
|
|
while (tsd->pending_synch_count > 0 && tsd->synch_perm != THREAD_SYNCH_NONE) {
|
|
STATS_INC_DC(dcontext, synch_loops_wait_safe);
|
|
#ifdef WINDOWS
|
|
if (started_detach) {
|
|
/* We spin for any non-detach synchs encountered during detach
|
|
* since we have no flag telling us this synch is for detach. */
|
|
/* Ref case 5074, can NOT use os_thread_yield here. This must be a user
|
|
* mode spin loop. */
|
|
SPINLOCK_PAUSE();
|
|
} else {
|
|
#endif
|
|
/* XXX case 10100: replace this sleep/yield with a wait_for_event() */
|
|
synch_thread_yield();
|
|
#ifdef WINDOWS
|
|
}
|
|
#endif
|
|
}
|
|
/* Regain the synch_lock before ENTERING_DR to avoid races with getting
|
|
* suspended/killed in the middle of ENTERING_DR (before synch_perm is
|
|
* reset to NONE). */
|
|
/* Ref case 5074, for detach we still can NOT use os_thread_yield here (no system
|
|
* calls) so don't allow the spinmutex_lock to yield while grabbing the lock. */
|
|
spinmutex_lock_no_yield(tsd->synch_lock);
|
|
ENTERING_DR();
|
|
tsd->synch_perm = THREAD_SYNCH_NONE;
|
|
if (tsd->set_mcontext != NULL || tsd->set_context != NULL) {
|
|
IF_WINDOWS(ASSERT(!started_detach));
|
|
/* Make a local copy */
|
|
ASSERT(sizeof(cxt) >= sizeof(priv_mcontext_t));
|
|
if (tsd->set_mcontext != NULL) {
|
|
set_mcontext = true;
|
|
memcpy(cxt, tsd->set_mcontext, sizeof(*tsd->set_mcontext));
|
|
} else {
|
|
set_context = true;
|
|
memcpy(cxt, tsd->set_context, tsd->set_context_size);
|
|
}
|
|
synch_thread_free_setcontext(tsd); /* sets to NULL for us */
|
|
}
|
|
spinmutex_unlock(tsd->synch_lock);
|
|
LOG(THREAD, LOG_SYNCH, 2, "done waiting for synch with state %d (pc " PFX ")\n",
|
|
cur_state, pc);
|
|
if (set_mcontext || set_context) {
|
|
/* XXX: see comment in dispatch.c check_wait_at_safe_spot() call
|
|
* about problems with KSTART(fcache_* differences bet the target
|
|
* being at the synch point vs in the cache.
|
|
*/
|
|
if (set_mcontext)
|
|
thread_set_self_mcontext((priv_mcontext_t *)cxt, false);
|
|
else
|
|
thread_set_self_context((void *)cxt, false);
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
/* adjusts the pending synch count */
|
|
void
|
|
adjust_wait_at_safe_spot(dcontext_t *dcontext, int amt)
|
|
{
|
|
thread_synch_data_t *tsd = (thread_synch_data_t *)dcontext->synch_field;
|
|
ASSERT(tsd->pending_synch_count >= 0);
|
|
spinmutex_lock(tsd->synch_lock);
|
|
ATOMIC_ADD(int, tsd->pending_synch_count, amt);
|
|
spinmutex_unlock(tsd->synch_lock);
|
|
}
|
|
|
|
/* Case 10101: Safely sets the context for a target thread that may be waiting at a
|
|
* safe spot, in which case we do not want to directly do a setcontext as the return
|
|
* from the yield or wait system call will mess up the state (case 5074).
|
|
* Assumes that cxt was allocated on the global heap, and frees it, rather than
|
|
* making its own copy (as an optimization).
|
|
* Does not work on the executing thread.
|
|
* Caller must hold thread_initexit_lock.
|
|
* If used on behalf of the app, it's up to the caller to check for privileges.
|
|
*/
|
|
bool
|
|
set_synched_thread_context(thread_record_t *trec,
|
|
/* pass either mc or both cxt and cxt_size */
|
|
priv_mcontext_t *mc, void *cxt, size_t cxt_size,
|
|
thread_synch_state_t desired_state _IF_X64(byte *cxt_alloc)
|
|
_IF_WINDOWS(NTSTATUS *status /*OUT*/))
|
|
{
|
|
bool res = true;
|
|
ASSERT(trec != NULL && trec->dcontext != NULL);
|
|
ASSERT(trec->dcontext != get_thread_private_dcontext());
|
|
ASSERT_OWN_MUTEX(true, &thread_initexit_lock);
|
|
#ifdef WINDOWS
|
|
if (status != NULL)
|
|
*status = STATUS_SUCCESS;
|
|
#endif
|
|
if (waiting_at_safe_spot(trec, desired_state)) {
|
|
/* case 10101: to allow system calls in check_wait_at_safe_spot() for
|
|
* performance reasons we have the waiting thread perform its own setcontext.
|
|
*/
|
|
thread_synch_data_t *tsd = (thread_synch_data_t *)trec->dcontext->synch_field;
|
|
spinmutex_lock(tsd->synch_lock);
|
|
if (tsd->set_mcontext != NULL || tsd->set_context != NULL) {
|
|
/* Two synchs in a row while still waiting; 2nd takes precedence */
|
|
STATS_INC(wait_multiple_setcxt);
|
|
synch_thread_free_setcontext(tsd);
|
|
}
|
|
#ifdef WINDOWS
|
|
LOG(THREAD_GET, LOG_SYNCH, 2,
|
|
"set_synched_thread_context %d to pc " PFX " via %s\n", trec->id,
|
|
(mc != NULL) ? mc->pc : (app_pc)((CONTEXT *)cxt)->CXT_XIP,
|
|
(mc != NULL) ? "mc" : "CONTEXT");
|
|
#else
|
|
ASSERT_NOT_IMPLEMENTED(mc != NULL); /* XXX: need sigcontext or sig_full_cxt_t */
|
|
#endif
|
|
if (mc != NULL)
|
|
tsd->set_mcontext = mc;
|
|
else {
|
|
ASSERT(cxt != NULL && cxt_size > 0);
|
|
tsd->set_context = cxt;
|
|
tsd->set_context_size = cxt_size;
|
|
}
|
|
IF_X64(tsd->set_context_alloc = cxt_alloc);
|
|
ASSERT(THREAD_SYNCH_SAFE(tsd->synch_perm, desired_state));
|
|
ASSERT(tsd->pending_synch_count >= 0);
|
|
/* Don't need to change pending_synch_count or anything; when thread is
|
|
* resumed it will properly reset everything itself */
|
|
spinmutex_unlock(tsd->synch_lock);
|
|
} else {
|
|
if (mc != NULL) {
|
|
res = thread_set_mcontext(trec, mc);
|
|
} else {
|
|
#ifdef WINDOWS
|
|
/* sort of ugly: but NtSetContextThread handling needs the status */
|
|
if (status != NULL) {
|
|
*status = nt_set_context(trec->handle, (CONTEXT *)cxt);
|
|
res = NT_SUCCESS(*status);
|
|
} else
|
|
res = thread_set_context(trec->handle, (CONTEXT *)cxt);
|
|
#else
|
|
/* currently there are no callers who don't pass mc: presumably
|
|
* PR 212090 will change that */
|
|
ASSERT_NOT_IMPLEMENTED(false);
|
|
#endif
|
|
}
|
|
free_setcontext(mc, cxt, cxt_size _IF_X64(cxt_alloc));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* This is used to limit the maximum number of times synch_with_thread or
|
|
* synch_with_all_threads spin yield loops while waiting on an exiting thread.
|
|
* We assert if we ever break out of the loop because of this limit. XXX make
|
|
* sure this limit is large enough that if it does ever trigger it's because
|
|
* of some kind of deadlock situation. Breaking out of the synchronization loop
|
|
* early is a correctness issue. Right now the limits are large but arbitrary.
|
|
* XXX : once we are confident about thread synch get rid of these max loop checks.
|
|
* N.B.: the THREAD_SYNCH_SMALL_LOOP_MAX flag causes us to divide these by 10.
|
|
*/
|
|
#define SYNCH_ALL_THREADS_MAXIMUM_LOOPS (DYNAMO_OPTION(synch_all_threads_max_loops))
|
|
#define SYNCH_MAXIMUM_LOOPS (DYNAMO_OPTION(synch_thread_max_loops))
|
|
|
|
/* Amt of time in ms to wait for threads to get to a safe spot per a loop,
|
|
* see comments in synch_with_yield() on value. Our default value is 5ms which,
|
|
* depending on the tick resolution could end up being as long as 10 ms. */
|
|
#define SYNCH_WITH_WAIT_MS ((int)DYNAMO_OPTION(synch_with_sleep_time))
|
|
|
|
/* for use by synch_with_* routines to wait for thread(s) */
|
|
static void
|
|
synch_thread_yield(void)
|
|
{
|
|
/* xref 9400, 9488 - os_thread_yield() works ok on an UP machine, but on an MP machine
|
|
* yield might not actually do anything (in which case we burn through to the max
|
|
* loop counts pretty quick). We actually do want to wait a reasonable amt of time
|
|
* since the target thread might be doing some long latency dr operation (like
|
|
* dumping 500kb of registry into a forensics file) so we have the option to sleep
|
|
* instead. */
|
|
uint num_procs = get_num_processors();
|
|
ASSERT(num_procs != 0);
|
|
if ((num_procs == 1 && DYNAMO_OPTION(synch_thread_sleep_UP)) ||
|
|
(num_procs > 1 && DYNAMO_OPTION(synch_thread_sleep_MP))) {
|
|
os_thread_sleep(SYNCH_WITH_WAIT_MS);
|
|
} else {
|
|
os_thread_yield();
|
|
}
|
|
}
|
|
|
|
/* returns a thread_synch_result_t value
|
|
* id - the thread you want to synch with
|
|
* block - whether or not should spin until synch is successful
|
|
* hold_initexit_lock - whether or not the caller holds the thread_initexit_lock
|
|
* caller_state - a given permission define from above that describes the
|
|
* current state of the caller (note that holding the initexit
|
|
* lock is ok with respect to NO_LOCK
|
|
* desired_state - a requested state define from above that describes the
|
|
* desired synchronization
|
|
* flags - options from THREAD_SYNCH_ bitmask values
|
|
* NOTE - if you hold the initexit_lock and block with greater than NONE for
|
|
* caller state, then initexit_lock may be released and re-acquired
|
|
* NOTE - if any of the nt_ routines fails, it is assumed the thread no longer
|
|
* exists and returns true
|
|
* NOTE - if called directly (i.e. not through synch_with_all_threads)
|
|
* requires THREAD_SYNCH_IS_SAFE(caller_state, desired_state) to avoid deadlock
|
|
* NOTE - Requires the caller is !could_be_linking (i.e. not in an
|
|
* enter_couldbelinking state)
|
|
* NOTE - you can't call this with a thread that you've already suspended
|
|
*/
|
|
thread_synch_result_t
|
|
synch_with_thread(thread_id_t id, bool block, bool hold_initexit_lock,
|
|
thread_synch_permission_t caller_state,
|
|
thread_synch_state_t desired_state, uint flags)
|
|
{
|
|
thread_id_t my_id = d_r_get_thread_id();
|
|
uint loop_count = 0;
|
|
int expect_exiting = 0;
|
|
thread_record_t *my_tr = thread_lookup(my_id), *trec = NULL;
|
|
dcontext_t *dcontext = NULL;
|
|
priv_mcontext_t mc;
|
|
thread_synch_result_t res = THREAD_SYNCH_RESULT_NOT_SAFE;
|
|
bool first_loop = true;
|
|
IF_UNIX(bool actually_suspended = true;)
|
|
const uint max_loops = TESTANY(THREAD_SYNCH_SMALL_LOOP_MAX, flags)
|
|
? (SYNCH_MAXIMUM_LOOPS / 10)
|
|
: SYNCH_MAXIMUM_LOOPS;
|
|
|
|
ASSERT(id != my_id);
|
|
/* Must set ABORT or IGNORE. Only caller can RETRY as need a new
|
|
* set of threads for that, hoping problematic one is short-lived.
|
|
*/
|
|
ASSERT(
|
|
TESTANY(THREAD_SYNCH_SUSPEND_FAILURE_ABORT | THREAD_SYNCH_SUSPEND_FAILURE_IGNORE,
|
|
flags) &&
|
|
!TESTALL(THREAD_SYNCH_SUSPEND_FAILURE_ABORT | THREAD_SYNCH_SUSPEND_FAILURE_IGNORE,
|
|
flags));
|
|
|
|
if (my_tr != NULL) {
|
|
dcontext = my_tr->dcontext;
|
|
expect_exiting = dcontext->is_exiting ? 1 : 0;
|
|
ASSERT(exiting_thread_count >= expect_exiting);
|
|
} else {
|
|
/* calling thread should always be a known thread */
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
LOG(THREAD, LOG_SYNCH, 2,
|
|
"Synching with thread " TIDFMT ", giving %d, requesting %d, blocking=%d\n", id,
|
|
caller_state, desired_state, block);
|
|
|
|
if (!hold_initexit_lock)
|
|
d_r_mutex_lock(&thread_initexit_lock);
|
|
|
|
while (true) {
|
|
/* get thread record */
|
|
/* XXX : thread id recycling is possible that this could be a
|
|
* different thread, perhaps we should take handle instead of id
|
|
* XXX: use the new num field of thread_record_t?
|
|
*/
|
|
LOG(THREAD, LOG_SYNCH, 3, "Looping on synch with thread " TIDFMT "\n", id);
|
|
trec = thread_lookup(id);
|
|
/* We test the exiting thread count to avoid races between terminate/
|
|
* suspend thread (current thread, though we could be here for other
|
|
* reasons) and an exiting thread (who might no longer be on the all
|
|
* threads list) who is still using shared resources (ref case 3121) */
|
|
if ((trec == NULL && exiting_thread_count == expect_exiting) ||
|
|
loop_count++ > max_loops) {
|
|
/* make sure we didn't exit the loop without synchronizing, XXX :
|
|
* in release builds we assume the synchronization is failing and
|
|
* continue without it, but that is dangerous.
|
|
* It is now up to the caller to handle this, and some use
|
|
* small loop counts and abort on failure, so only a curiosity. */
|
|
ASSERT_CURIOSITY(loop_count < max_loops);
|
|
LOG(THREAD, LOG_SYNCH, 3,
|
|
"Exceeded loop count synching with thread " TIDFMT "\n", id);
|
|
goto exit_synch_with_thread;
|
|
}
|
|
DOSTATS({
|
|
if (trec == NULL && exiting_thread_count > expect_exiting) {
|
|
LOG(THREAD, LOG_SYNCH, 2, "Waiting for an exiting thread\n");
|
|
STATS_INC(synch_yields_for_exiting_thread);
|
|
}
|
|
});
|
|
#ifdef UNIX
|
|
if (trec != NULL && trec->execve) {
|
|
/* i#237/PR 498284: clean up vfork "threads" that invoked execve.
|
|
* There should be no race since vfork suspends the parent.
|
|
*/
|
|
res = THREAD_SYNCH_RESULT_SUCCESS;
|
|
actually_suspended = false;
|
|
break;
|
|
}
|
|
#endif
|
|
if (trec != NULL) {
|
|
if (first_loop) {
|
|
adjust_wait_at_safe_spot(trec->dcontext, 1);
|
|
first_loop = false;
|
|
}
|
|
if (!os_thread_suspend(trec)) {
|
|
/* XXX : eventually should be a real assert once we figure out
|
|
* how to handle threads with low privilege handles */
|
|
/* For dr_api_exit, we may have missed a thread exit. */
|
|
ASSERT_CURIOSITY_ONCE(
|
|
(trec->dcontext->currently_stopped IF_APP_EXPORTS(|| dr_api_exit)) &&
|
|
"Thead synch unable to suspend target"
|
|
" thread, case 2096?");
|
|
res = (TESTANY(THREAD_SYNCH_SUSPEND_FAILURE_IGNORE, flags)
|
|
? THREAD_SYNCH_RESULT_SUCCESS
|
|
: THREAD_SYNCH_RESULT_SUSPEND_FAILURE);
|
|
IF_UNIX(actually_suspended = false);
|
|
break;
|
|
}
|
|
if (!thread_get_mcontext(trec, &mc)) {
|
|
/* XXX : eventually should be a real assert once we figure out
|
|
* how to handle threads with low privilege handles */
|
|
ASSERT_CURIOSITY_ONCE(false &&
|
|
"Thead synch unable to get_context target"
|
|
" thread, case 2096?");
|
|
res = (TESTANY(THREAD_SYNCH_SUSPEND_FAILURE_IGNORE, flags)
|
|
? THREAD_SYNCH_RESULT_SUCCESS
|
|
: THREAD_SYNCH_RESULT_SUSPEND_FAILURE);
|
|
/* Make sure to not leave suspended if not returning success */
|
|
if (!TESTANY(THREAD_SYNCH_SUSPEND_FAILURE_IGNORE, flags))
|
|
os_thread_resume(trec);
|
|
break;
|
|
}
|
|
if (at_safe_spot(trec, &mc, desired_state)) {
|
|
/* XXX: case 5325 for detach handling and testing */
|
|
IF_WINDOWS(
|
|
ASSERT_NOT_IMPLEMENTED(!dcontext->aslr_context.sys_aslr_clobbered));
|
|
LOG(THREAD, LOG_SYNCH, 2, "Thread " TIDFMT " suspended in good spot\n",
|
|
id);
|
|
LOG(trec->dcontext->logfile, LOG_SYNCH, 2,
|
|
"@@@@@@@@@@@@@@@@@@ SUSPENDED BY THREAD " TIDFMT " synch_with_thread "
|
|
"@@@@@@@@@@@@@@@@@@\n",
|
|
my_id);
|
|
res = THREAD_SYNCH_RESULT_SUCCESS;
|
|
break;
|
|
} else {
|
|
RSTATS_INC(synchs_not_at_safe_spot);
|
|
}
|
|
if (!os_thread_resume(trec)) {
|
|
ASSERT_NOT_REACHED();
|
|
res = (TESTANY(THREAD_SYNCH_SUSPEND_FAILURE_IGNORE, flags)
|
|
? THREAD_SYNCH_RESULT_SUCCESS
|
|
: THREAD_SYNCH_RESULT_SUSPEND_FAILURE);
|
|
break;
|
|
}
|
|
}
|
|
/* don't loop if !block, before we ever release initexit_lock in case
|
|
* caller is holding it and not blocking, (i.e. wants to keep it) */
|
|
if (!block)
|
|
break;
|
|
/* see if someone is waiting for us */
|
|
if (dcontext != NULL && caller_state != THREAD_SYNCH_NONE &&
|
|
should_wait_at_safe_spot(dcontext)) {
|
|
if (trec != NULL)
|
|
adjust_wait_at_safe_spot(trec->dcontext, -1);
|
|
d_r_mutex_unlock(&thread_initexit_lock);
|
|
/* ref case 5552, if we've inc'ed the exiting thread count need to
|
|
* adjust it back before calling check_wait_at_safe_spot since we
|
|
* may end up being killed there */
|
|
if (dcontext->is_exiting) {
|
|
ASSERT(exiting_thread_count >= 1);
|
|
ATOMIC_DEC(int, exiting_thread_count);
|
|
}
|
|
check_wait_at_safe_spot(dcontext, caller_state);
|
|
if (dcontext->is_exiting) {
|
|
ATOMIC_INC(int, exiting_thread_count);
|
|
}
|
|
d_r_mutex_lock(&thread_initexit_lock);
|
|
trec = thread_lookup(id);
|
|
/* Like above, we test the exiting thread count to avoid races
|
|
* between terminate/suspend thread (current thread, though we
|
|
* could be here for other reasons) and an exiting thread (who
|
|
* might no longer be on the all threads list) who is still using
|
|
* shared resources (ref case 3121) */
|
|
if (trec == NULL && exiting_thread_count == expect_exiting) {
|
|
if (!hold_initexit_lock)
|
|
d_r_mutex_unlock(&thread_initexit_lock);
|
|
return THREAD_SYNCH_RESULT_SUCCESS;
|
|
}
|
|
DOSTATS({
|
|
if (trec == NULL && exiting_thread_count > expect_exiting) {
|
|
LOG(THREAD, LOG_SYNCH, 2, "Waiting for an exiting thread\n");
|
|
STATS_INC(synch_yields_for_exiting_thread);
|
|
}
|
|
});
|
|
if (trec != NULL)
|
|
adjust_wait_at_safe_spot(trec->dcontext, 1);
|
|
}
|
|
STATS_INC(synch_yields);
|
|
d_r_mutex_unlock(&thread_initexit_lock);
|
|
/* Note - we only need call the ENTER/EXIT_DR hooks if single thread
|
|
* in dr since we are not really exiting DR here (we just need to give
|
|
* up the exclusion lock for a while to let thread we are trying to
|
|
* synch with make progress towards a safe synch point). */
|
|
if (INTERNAL_OPTION(single_thread_in_DR))
|
|
EXITING_DR(); /* give up DR exclusion lock */
|
|
synch_thread_yield();
|
|
if (INTERNAL_OPTION(single_thread_in_DR))
|
|
ENTERING_DR(); /* re-gain DR exclusion lock */
|
|
d_r_mutex_lock(&thread_initexit_lock);
|
|
}
|
|
/* reset this back to before */
|
|
adjust_wait_at_safe_spot(trec->dcontext, -1);
|
|
/* success!, is suspended (or already exited) put in desired state */
|
|
if (res == THREAD_SYNCH_RESULT_SUCCESS) {
|
|
LOG(THREAD, LOG_SYNCH, 2,
|
|
"Success synching with thread " TIDFMT " performing cleanup\n", id);
|
|
if (THREAD_SYNCH_IS_TERMINATED(desired_state)) {
|
|
if (IF_UNIX_ELSE(!trec->execve, true))
|
|
os_thread_terminate(trec);
|
|
#ifdef UNIX
|
|
/* We need to ensure the target thread has received the
|
|
* signal and is no longer using its sigstack or ostd struct
|
|
* before we clean those up.
|
|
*/
|
|
/* PR 452168: if failed to send suspend signal, do not spin */
|
|
if (actually_suspended) {
|
|
if (!is_thread_terminated(trec->dcontext)) {
|
|
/* i#96/PR 295561: use futex(2) if available. Blocks until
|
|
* the thread gets terminated.
|
|
*/
|
|
os_wait_thread_terminated(trec->dcontext);
|
|
}
|
|
} else
|
|
ASSERT(TESTANY(THREAD_SYNCH_SUSPEND_FAILURE_IGNORE, flags));
|
|
#endif
|
|
}
|
|
if (THREAD_SYNCH_IS_CLEANED(desired_state)) {
|
|
dynamo_other_thread_exit(trec _IF_WINDOWS(false));
|
|
}
|
|
}
|
|
exit_synch_with_thread:
|
|
if (!hold_initexit_lock)
|
|
d_r_mutex_unlock(&thread_initexit_lock);
|
|
return res;
|
|
}
|
|
|
|
/* desired_synch_state - a requested state define from above that describes
|
|
* the synchronization required
|
|
* threads, num_threads - must not be NULL, if !THREAD_SYNCH_IS_CLEANED(desired
|
|
* synch_state) then will hold a list and num of threads
|
|
* cur_state - a given permission from above that describes the state of the
|
|
* caller
|
|
* flags - options from THREAD_SYNCH_ bitmask values
|
|
* NOTE - Requires that the caller doesn't hold the thread_initexit_lock, on
|
|
* return caller will hold the thread_initexit_lock
|
|
* NOTE - Requires the caller is !could_be_linking (i.e. not in an
|
|
* enter_couldbelinking state)
|
|
* NOTE - To avoid deadlock this routine should really only be called with
|
|
* cur_state giving maximum permissions, (currently app_exit and detach could
|
|
* conflict, except our routes to app_exit go through different synch point
|
|
* (TermThread or TermProcess) first
|
|
* NOTE - when !all_synched, if desired_synch_state is not cleaned or synch result is
|
|
* ignored, the caller is reponsible for resuming threads that are suspended,
|
|
* freeing allocation for threads array and releasing locks
|
|
* Caller should call end_synch_with_all_threads when finished to accomplish that.
|
|
*/
|
|
bool
|
|
synch_with_all_threads(thread_synch_state_t desired_synch_state,
|
|
/*OUT*/ thread_record_t ***threads_out,
|
|
/*OUT*/ int *num_threads_out, thread_synch_permission_t cur_state,
|
|
/* XXX: turn the ThreadSynch* enums into bitmasks and merge
|
|
* into flags param */
|
|
uint flags)
|
|
{
|
|
/* Case 8815: we cannot use the OUT params themselves internally as they
|
|
* may be volatile, so we need our own values until we're ready to return
|
|
*/
|
|
bool threads_are_stale = true;
|
|
thread_record_t **threads = NULL;
|
|
int num_threads = 0;
|
|
/* we record ids from before we gave up thread_initexit_lock */
|
|
thread_id_t *thread_ids_temp = NULL;
|
|
int num_threads_temp = 0, i, j, expect_self_exiting = 0;
|
|
/* synch array contains a SYNCH_WITH_ALL_ value for each thread */
|
|
uint *synch_array = NULL, *synch_array_temp = NULL;
|
|
enum {
|
|
SYNCH_WITH_ALL_NEW = 0,
|
|
SYNCH_WITH_ALL_NOTIFIED = 1,
|
|
SYNCH_WITH_ALL_SYNCHED = 2,
|
|
};
|
|
bool all_synched = false;
|
|
thread_id_t my_id = d_r_get_thread_id();
|
|
uint loop_count = 0;
|
|
thread_record_t *tr = thread_lookup(my_id);
|
|
dcontext_t *dcontext = NULL;
|
|
uint flags_one; /* flags for synch_with_thread() call */
|
|
thread_synch_result_t synch_res;
|
|
const uint max_loops = TESTANY(THREAD_SYNCH_SMALL_LOOP_MAX, flags)
|
|
? (SYNCH_ALL_THREADS_MAXIMUM_LOOPS / 10)
|
|
: SYNCH_ALL_THREADS_MAXIMUM_LOOPS;
|
|
/* We treat client-owned threads as native but they don't have a clean native state
|
|
* for us to suspend them in (they are always in client or dr code). We need to be
|
|
* able to suspend such threads so that they're !couldbelinking and holding no dr
|
|
* locks. We make the assumption that client-owned threads that are in the client
|
|
* library (or are in a dr routine that has set dcontext->client_thread_safe_to_sync)
|
|
* meet this requirement (see at_safe_spot()). As such, all we need to worry about
|
|
* here are client locks the client-owned thread might hold that could block other
|
|
* threads from reaching safe spots. If we only suspend client-owned threads once
|
|
* all other threads are taken care of then this is not a problem. XXX - xref
|
|
* PR 231301 on issues that arise if the client thread spends most of its time
|
|
* calling out of its lib to dr API, ntdll, or generated code functions. */
|
|
bool finished_non_client_threads;
|
|
|
|
ASSERT(!dynamo_all_threads_synched);
|
|
/* flag any caller who does not give up enough permissions to avoid livelock
|
|
* with other synch_with_all_threads callers
|
|
*/
|
|
ASSERT_CURIOSITY(cur_state >= THREAD_SYNCH_NO_LOCKS_NO_XFER);
|
|
/* also flag anyone asking for full mcontext w/o possibility of no_xfer,
|
|
* which can also livelock
|
|
*/
|
|
ASSERT_CURIOSITY(desired_synch_state < THREAD_SYNCH_SUSPENDED_VALID_MCONTEXT
|
|
/* detach currently violates this: bug 8942 */
|
|
|| started_detach);
|
|
|
|
/* must set exactly one of these -- XXX: better way to check? */
|
|
ASSERT(
|
|
TESTANY(THREAD_SYNCH_SUSPEND_FAILURE_ABORT | THREAD_SYNCH_SUSPEND_FAILURE_IGNORE |
|
|
THREAD_SYNCH_SUSPEND_FAILURE_RETRY,
|
|
flags) &&
|
|
!TESTALL(THREAD_SYNCH_SUSPEND_FAILURE_ABORT | THREAD_SYNCH_SUSPEND_FAILURE_IGNORE,
|
|
flags) &&
|
|
!TESTALL(THREAD_SYNCH_SUSPEND_FAILURE_ABORT | THREAD_SYNCH_SUSPEND_FAILURE_RETRY,
|
|
flags) &&
|
|
!TESTALL(THREAD_SYNCH_SUSPEND_FAILURE_IGNORE | THREAD_SYNCH_SUSPEND_FAILURE_RETRY,
|
|
flags));
|
|
flags_one = flags;
|
|
/* we'll do the retry */
|
|
if (TESTANY(THREAD_SYNCH_SUSPEND_FAILURE_RETRY, flags)) {
|
|
flags_one &= ~THREAD_SYNCH_SUSPEND_FAILURE_RETRY;
|
|
flags_one |= THREAD_SYNCH_SUSPEND_FAILURE_ABORT;
|
|
}
|
|
|
|
if (tr != NULL) {
|
|
dcontext = tr->dcontext;
|
|
expect_self_exiting = dcontext->is_exiting ? 1 : 0;
|
|
ASSERT(exiting_thread_count >= expect_self_exiting);
|
|
} else {
|
|
/* calling thread should always be a known thread */
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
LOG(THREAD, LOG_SYNCH, 1,
|
|
"synch with all threads my id = " SZFMT
|
|
" Giving %d permission and seeking %d state\n",
|
|
my_id, cur_state, desired_synch_state);
|
|
|
|
/* grab all_threads_synch_lock */
|
|
/* since all_threads synch doesn't give any permissions this is necessary
|
|
* to prevent deadlock in the case of two threads trying to synch with all
|
|
* threads at the same time */
|
|
/* XXX: for DEADLOCK_AVOIDANCE, to preserve LIFO, should we
|
|
* exit DR, trylock, then immediately enter DR? introducing any
|
|
* race conditions in doing so?
|
|
* Ditto on all other os_thread_yields in this file!
|
|
*/
|
|
while (!d_r_mutex_trylock(&all_threads_synch_lock)) {
|
|
LOG(THREAD, LOG_SYNCH, 2, "Spinning on all threads synch lock\n");
|
|
STATS_INC(synch_yields);
|
|
if (dcontext != NULL && cur_state != THREAD_SYNCH_NONE &&
|
|
should_wait_at_safe_spot(dcontext)) {
|
|
/* ref case 5552, if we've inc'ed the exiting thread count need to
|
|
* adjust it back before calling check_wait_at_safe_spot since we
|
|
* may end up being killed there */
|
|
if (dcontext->is_exiting) {
|
|
ASSERT(exiting_thread_count >= 1);
|
|
ATOMIC_DEC(int, exiting_thread_count);
|
|
}
|
|
check_wait_at_safe_spot(dcontext, cur_state);
|
|
if (dcontext->is_exiting) {
|
|
ATOMIC_INC(int, exiting_thread_count);
|
|
}
|
|
}
|
|
LOG(THREAD, LOG_SYNCH, 2, "Yielding on all threads synch lock\n");
|
|
/* Note - we only need call the ENTER/EXIT_DR hooks if single thread
|
|
* in dr since we are not really exiting DR here (we just need to give
|
|
* up the exclusion lock for a while to let thread we are trying to
|
|
* synch with make progress towards a safe synch point). */
|
|
if (INTERNAL_OPTION(single_thread_in_DR))
|
|
EXITING_DR(); /* give up DR exclusion lock */
|
|
os_thread_yield();
|
|
if (INTERNAL_OPTION(single_thread_in_DR))
|
|
ENTERING_DR(); /* re-gain DR exclusion lock */
|
|
}
|
|
|
|
d_r_mutex_lock(&thread_initexit_lock);
|
|
/* synch with all threads */
|
|
/* XXX: this should be a do/while loop - then we wouldn't have
|
|
* to initialize all the variables above
|
|
*/
|
|
while (threads_are_stale || !all_synched ||
|
|
exiting_thread_count > expect_self_exiting || uninit_thread_count > 0) {
|
|
if (threads != NULL) {
|
|
/* Case 8941: must free here rather than when yield (below) since
|
|
* termination condition can change between there and here
|
|
*/
|
|
ASSERT(num_threads > 0);
|
|
global_heap_free(threads,
|
|
num_threads *
|
|
sizeof(thread_record_t *) HEAPACCT(ACCT_THREAD_MGT));
|
|
/* be paranoid */
|
|
threads = NULL;
|
|
num_threads = 0;
|
|
}
|
|
get_list_of_threads(&threads, &num_threads);
|
|
threads_are_stale = false;
|
|
synch_array = (uint *)global_heap_alloc(num_threads *
|
|
sizeof(uint) HEAPACCT(ACCT_THREAD_MGT));
|
|
for (i = 0; i < num_threads; i++) {
|
|
synch_array[i] = SYNCH_WITH_ALL_NEW;
|
|
}
|
|
/* Fixme : an inefficient algorithm, but is not as bad as it seems
|
|
* since it is very unlikely that many threads have started or ended
|
|
* and the list threads routine always puts them in the same order
|
|
*/
|
|
/* on first loop num_threads_temp == 0 */
|
|
for (i = 0; i < num_threads_temp; i++) {
|
|
/* care only if we have already notified or synched thread */
|
|
if (synch_array_temp[i] != SYNCH_WITH_ALL_NEW) {
|
|
for (j = 0; j < num_threads; j++) {
|
|
/* XXX : os recycles thread ids, should have stronger
|
|
* check here, could check dcontext equivalence, (but we
|
|
* recycle those to), probably should check threads_temp
|
|
* handle and be sure thread is still alive since the id
|
|
* won't be recycled then */
|
|
if (threads[j]->id == thread_ids_temp[i]) {
|
|
synch_array[j] = synch_array_temp[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* free old synch list, old thread id list */
|
|
if (num_threads_temp > 0) {
|
|
global_heap_free(thread_ids_temp,
|
|
num_threads_temp *
|
|
sizeof(thread_id_t) HEAPACCT(ACCT_THREAD_MGT));
|
|
global_heap_free(synch_array_temp,
|
|
num_threads_temp * sizeof(uint) HEAPACCT(ACCT_THREAD_MGT));
|
|
num_threads_temp = 0;
|
|
}
|
|
|
|
all_synched = true;
|
|
LOG(THREAD, LOG_SYNCH, 3, "Looping over all threads (%d threads)\n", num_threads);
|
|
finished_non_client_threads = true;
|
|
for (i = 0; i < num_threads; i++) {
|
|
if (threads[i]->id != my_id && synch_array[i] != SYNCH_WITH_ALL_SYNCHED &&
|
|
!IS_CLIENT_THREAD(threads[i]->dcontext)) {
|
|
finished_non_client_threads = false;
|
|
break;
|
|
}
|
|
}
|
|
/* make a copy of the thread ids (can't just keep the thread list
|
|
* since it consists of pointers to live thread_record_t structs).
|
|
* we must make the copy before synching b/c cleaning up a thread
|
|
* involves freeing its thread_record_t.
|
|
*/
|
|
thread_ids_temp = (thread_id_t *)global_heap_alloc(
|
|
num_threads * sizeof(thread_id_t) HEAPACCT(ACCT_THREAD_MGT));
|
|
for (i = 0; i < num_threads; i++)
|
|
thread_ids_temp[i] = threads[i]->id;
|
|
num_threads_temp = num_threads;
|
|
synch_array_temp = synch_array;
|
|
|
|
for (i = 0; i < num_threads; i++) {
|
|
/* do not de-ref threads[i] after synching if it was cleaned up! */
|
|
if (synch_array[i] != SYNCH_WITH_ALL_SYNCHED && threads[i]->id != my_id) {
|
|
if ((!finished_non_client_threads
|
|
#ifdef STATIC_LIBRARY
|
|
/* Give client threads who may be in dynamo_thread_exit waiting
|
|
* on thread_initexit_lock a chance to exit. For static DR we
|
|
* can't tell whether we suspended safely b/c is_in_client_lib()
|
|
* is true for the whole app!
|
|
*/
|
|
|| (int)loop_count < num_threads
|
|
#endif
|
|
) &&
|
|
IS_CLIENT_THREAD(threads[i]->dcontext)) {
|
|
all_synched = false;
|
|
/* XXX i#7673: Adding a client join feature could avoid complexity
|
|
* here.
|
|
*/
|
|
continue; /* skip this thread for now till non-client are finished */
|
|
}
|
|
if (IS_CLIENT_THREAD(threads[i]->dcontext) &&
|
|
(TESTANY(flags, THREAD_SYNCH_SKIP_CLIENT_THREAD) ||
|
|
!should_suspend_client_thread(threads[i]->dcontext,
|
|
desired_synch_state))) {
|
|
/* PR 609569: do not suspend this thread.
|
|
* Avoid races between resume_all_threads() and
|
|
* dr_client_thread_set_suspendable() by storing the fact.
|
|
*
|
|
* For most of our synchall purposes we really want to prevent
|
|
* threads from acting on behalf of the application, and make
|
|
* sure we can relocate them if in the code cache. DR itself is
|
|
* thread-safe, and while a synchall-initiator will touch
|
|
* thread-private data for threads it suspends, having some
|
|
* threads it does not suspend shouldn't cause any problems so
|
|
* long as it doesn't touch their thread-private data.
|
|
*/
|
|
synch_array[i] = SYNCH_WITH_ALL_SYNCHED;
|
|
threads[i]->dcontext->client_data->left_unsuspended = true;
|
|
continue;
|
|
}
|
|
/* speed things up a tad */
|
|
if (synch_array[i] != SYNCH_WITH_ALL_NOTIFIED) {
|
|
ASSERT(synch_array[i] == SYNCH_WITH_ALL_NEW);
|
|
adjust_wait_at_safe_spot(threads[i]->dcontext, 1);
|
|
synch_array[i] = SYNCH_WITH_ALL_NOTIFIED;
|
|
}
|
|
LOG(THREAD, LOG_SYNCH, 2,
|
|
"About to try synch with thread #%d/%d " TIDFMT "\n", i, num_threads,
|
|
threads[i]->id);
|
|
synch_res =
|
|
synch_with_thread(threads[i]->id, false, true, THREAD_SYNCH_NONE,
|
|
desired_synch_state, flags_one);
|
|
if (synch_res == THREAD_SYNCH_RESULT_SUCCESS) {
|
|
LOG(THREAD, LOG_SYNCH, 2, "Synch succeeded!\n");
|
|
/* successful synch */
|
|
synch_array[i] = SYNCH_WITH_ALL_SYNCHED;
|
|
if (!THREAD_SYNCH_IS_CLEANED(desired_synch_state))
|
|
adjust_wait_at_safe_spot(threads[i]->dcontext, -1);
|
|
} else {
|
|
LOG(THREAD, LOG_SYNCH, 2, "Synch failed!\n");
|
|
all_synched = false;
|
|
if (synch_res == THREAD_SYNCH_RESULT_SUSPEND_FAILURE) {
|
|
if (TESTANY(THREAD_SYNCH_SUSPEND_FAILURE_ABORT, flags))
|
|
goto synch_with_all_abort;
|
|
} else
|
|
ASSERT(synch_res == THREAD_SYNCH_RESULT_NOT_SAFE);
|
|
}
|
|
} else {
|
|
LOG(THREAD, LOG_SYNCH, 2, "Skipping synch with thread " TIDFMT "\n",
|
|
thread_ids_temp[i]);
|
|
}
|
|
}
|
|
|
|
if (loop_count++ >= max_loops)
|
|
break;
|
|
/* We test the exiting thread count to avoid races between exit
|
|
* process (current thread, though we could be here for detach or other
|
|
* reasons) and an exiting thread (who might no longer be on the all
|
|
* threads list) who is still using shared resources (ref case 3121) */
|
|
if (!all_synched || exiting_thread_count > expect_self_exiting ||
|
|
uninit_thread_count > 0) {
|
|
DOSTATS({
|
|
if (all_synched && exiting_thread_count > expect_self_exiting) {
|
|
LOG(THREAD, LOG_SYNCH, 2, "Waiting for an exiting thread %d %d %d\n",
|
|
all_synched, exiting_thread_count, expect_self_exiting);
|
|
STATS_INC(synch_yields_for_exiting_thread);
|
|
} else if (all_synched && uninit_thread_count > 0) {
|
|
LOG(THREAD, LOG_SYNCH, 2, "Waiting for an uninit thread %d %d\n",
|
|
all_synched, uninit_thread_count);
|
|
STATS_INC(synch_yields_for_uninit_thread);
|
|
}
|
|
});
|
|
STATS_INC(synch_yields);
|
|
|
|
/* release lock in case some other thread waiting on it */
|
|
d_r_mutex_unlock(&thread_initexit_lock);
|
|
LOG(THREAD, LOG_SYNCH, 2, "Not all threads synched looping again\n");
|
|
/* Note - we only need call the ENTER/EXIT_DR hooks if single
|
|
* thread in dr since we are not really exiting DR here (we just
|
|
* need to give up the exclusion lock for a while to let thread we
|
|
* are trying to synch with make progress towards a safe synch
|
|
* point). */
|
|
if (INTERNAL_OPTION(single_thread_in_DR))
|
|
EXITING_DR(); /* give up DR exclusion lock */
|
|
synch_thread_yield();
|
|
if (INTERNAL_OPTION(single_thread_in_DR))
|
|
ENTERING_DR(); /* re-gain DR exclusion lock */
|
|
d_r_mutex_lock(&thread_initexit_lock);
|
|
/* We unlock and lock the thread_initexit_lock, so threads might be stale. */
|
|
threads_are_stale = true;
|
|
}
|
|
}
|
|
/* case 9392: callers passing in ABORT expect a return value of failure
|
|
* to correspond w/ no suspended threads, a freed threads array, and no
|
|
* locks being held, so we go through the abort path
|
|
*/
|
|
if (!all_synched && TESTANY(THREAD_SYNCH_SUSPEND_FAILURE_ABORT, flags))
|
|
goto synch_with_all_abort;
|
|
synch_with_all_exit:
|
|
/* make sure we didn't exit the loop without synchronizing, XXX : in
|
|
* release builds we assume the synchronization is failing and continue
|
|
* without it, but that is dangerous.
|
|
* It is now up to the caller to handle this, and some use
|
|
* small loop counts and abort on failure, so only a curiosity. */
|
|
ASSERT_CURIOSITY(loop_count < max_loops);
|
|
ASSERT(threads != NULL);
|
|
/* Since the set of threads can change we don't set the success field
|
|
* until we're passing back the thread list.
|
|
* We would use an tsd field directly instead of synch_array except
|
|
* for THREAD_SYNCH_*_CLEAN where tsd is freed.
|
|
*/
|
|
ASSERT(synch_array != NULL);
|
|
if (!THREAD_SYNCH_IS_CLEANED(desired_synch_state)) { /* else unsafe to access tsd */
|
|
for (i = 0; i < num_threads; i++) {
|
|
if (threads[i]->id != my_id) {
|
|
thread_synch_data_t *tsd;
|
|
ASSERT(threads[i]->dcontext != NULL);
|
|
tsd = (thread_synch_data_t *)threads[i]->dcontext->synch_field;
|
|
tsd->synch_with_success = (synch_array[i] == SYNCH_WITH_ALL_SYNCHED);
|
|
}
|
|
}
|
|
}
|
|
global_heap_free(synch_array, num_threads * sizeof(uint) HEAPACCT(ACCT_THREAD_MGT));
|
|
if (num_threads_temp > 0) {
|
|
global_heap_free(thread_ids_temp,
|
|
num_threads_temp *
|
|
sizeof(thread_id_t) HEAPACCT(ACCT_THREAD_MGT));
|
|
}
|
|
/* XXX case 9333: on all_synch failure we do not free threads array if
|
|
* synch_result is ignored. Callers are responsible for resuming threads that are
|
|
* suspended and freeing allocation for threads array
|
|
*/
|
|
if ((!all_synched && TESTANY(THREAD_SYNCH_SUSPEND_FAILURE_ABORT, flags)) ||
|
|
THREAD_SYNCH_IS_CLEANED(desired_synch_state)) {
|
|
global_heap_free(
|
|
threads, num_threads * sizeof(thread_record_t *) HEAPACCT(ACCT_THREAD_MGT));
|
|
threads = NULL;
|
|
num_threads = 0;
|
|
}
|
|
LOG(THREAD, LOG_SYNCH, 1, "Finished synch with all threads: result=%d\n",
|
|
all_synched);
|
|
DOLOG(1, LOG_SYNCH, {
|
|
if (all_synched) {
|
|
LOG(THREAD, LOG_SYNCH, 1,
|
|
"\treturning holding initexit_lock and all_threads_synch_lock\n");
|
|
}
|
|
});
|
|
*threads_out = threads;
|
|
*num_threads_out = num_threads;
|
|
dynamo_all_threads_synched = all_synched;
|
|
ASSERT(exiting_thread_count - expect_self_exiting == 0);
|
|
/* XXX case 9392: where on all_synch failure we do not release the locks in the
|
|
* non-abort exit path */
|
|
return all_synched;
|
|
|
|
synch_with_all_abort:
|
|
/* undo everything! */
|
|
for (i = 0; i < num_threads; i++) {
|
|
DEBUG_DECLARE(bool ok;)
|
|
if (threads[i]->id != my_id) {
|
|
if (synch_array[i] == SYNCH_WITH_ALL_SYNCHED) {
|
|
bool resume = true;
|
|
if (IS_CLIENT_THREAD(threads[i]->dcontext) &&
|
|
threads[i]->dcontext->client_data->left_unsuspended) {
|
|
/* PR 609569: we did not suspend this thread */
|
|
resume = false;
|
|
}
|
|
if (resume) {
|
|
DEBUG_DECLARE(ok =)
|
|
os_thread_resume(threads[i]);
|
|
ASSERT(ok);
|
|
}
|
|
/* ensure synch_with_success is set to false on exit path,
|
|
* even though locks are released and not fully valid
|
|
*/
|
|
synch_array[i] = SYNCH_WITH_ALL_NEW;
|
|
} else if (synch_array[i] == SYNCH_WITH_ALL_NOTIFIED) {
|
|
adjust_wait_at_safe_spot(threads[i]->dcontext, -1);
|
|
}
|
|
}
|
|
}
|
|
d_r_mutex_unlock(&thread_initexit_lock);
|
|
d_r_mutex_unlock(&all_threads_synch_lock);
|
|
ASSERT(exiting_thread_count - expect_self_exiting == 0);
|
|
ASSERT(!all_synched); /* ensure our OUT values will be NULL,0
|
|
for THREAD_SYNCH_SUSPEND_FAILURE_ABORT */
|
|
goto synch_with_all_exit;
|
|
}
|
|
|
|
/* Assumes that the threads were suspended with synch_with_all_threads()
|
|
* and thus even is_thread_currently_native() threads were suspended.
|
|
* Assumes that the caller will free up threads if it is dynamically allocated.
|
|
*/
|
|
void
|
|
resume_all_threads(thread_record_t **threads, const uint num_threads)
|
|
{
|
|
uint i;
|
|
thread_id_t my_tid;
|
|
|
|
ASSERT_OWN_MUTEX(true, &all_threads_synch_lock);
|
|
ASSERT_OWN_MUTEX(true, &thread_initexit_lock);
|
|
|
|
if (threads == NULL || num_threads == 0)
|
|
return;
|
|
|
|
my_tid = d_r_get_thread_id();
|
|
for (i = 0; i < num_threads; i++) {
|
|
if (my_tid == threads[i]->id)
|
|
continue;
|
|
if (IS_CLIENT_THREAD(threads[i]->dcontext) &&
|
|
threads[i]->dcontext->client_data->left_unsuspended) {
|
|
/* PR 609569: we did not suspend this thread */
|
|
threads[i]->dcontext->client_data->left_unsuspended = false;
|
|
continue;
|
|
}
|
|
|
|
/* This routine assumes that each thread in the array was suspended, so
|
|
* each one has to successfully resume.
|
|
*/
|
|
DEBUG_DECLARE(bool res =) os_thread_resume(threads[i]);
|
|
ASSERT(res);
|
|
}
|
|
}
|
|
|
|
/* Should be called to clean up after synch_with_all_threads as otherwise
|
|
* dynamo_all_threads_synched will be left as true.
|
|
* If resume is true, resumes the threads in the threads array.
|
|
* Unlocks thread_initexit_lock and all_threads_synch_lock.
|
|
* If threads != NULL, frees the threads array.
|
|
*/
|
|
void
|
|
end_synch_with_all_threads(thread_record_t **threads, uint num_threads, bool resume)
|
|
{
|
|
/* dynamo_all_threads_synched will be false if synch failed */
|
|
ASSERT_CURIOSITY(dynamo_all_threads_synched);
|
|
ASSERT(OWN_MUTEX(&all_threads_synch_lock) && OWN_MUTEX(&thread_initexit_lock));
|
|
dynamo_all_threads_synched = false;
|
|
if (resume) {
|
|
ASSERT(threads != NULL);
|
|
resume_all_threads(threads, num_threads);
|
|
}
|
|
/* if we knew whether THREAD_SYNCH_*_CLEANED was specified we could set
|
|
* synch_with_success to false, but it's unsafe otherwise
|
|
*/
|
|
d_r_mutex_unlock(&thread_initexit_lock);
|
|
d_r_mutex_unlock(&all_threads_synch_lock);
|
|
if (threads != NULL) {
|
|
global_heap_free(
|
|
threads, num_threads * sizeof(thread_record_t *) HEAPACCT(ACCT_THREAD_MGT));
|
|
}
|
|
}
|
|
|
|
/* Resets a thread's context to start interpreting anew.
|
|
* ASSUMPTION: the thread is currently suspended.
|
|
* This was moved here from fcache_reset_all_caches_proactively simply to
|
|
* get access to win32-private CONTEXT-related routines
|
|
*/
|
|
void
|
|
translate_from_synchall_to_dispatch(thread_record_t *tr, thread_synch_state_t synch_state)
|
|
{
|
|
/* we do not have to align priv_mcontext_t */
|
|
priv_mcontext_t *mc = global_heap_alloc(sizeof(*mc) HEAPACCT(ACCT_OTHER));
|
|
bool free_cxt = true;
|
|
dcontext_t *dcontext = tr->dcontext;
|
|
app_pc pre_translation;
|
|
ASSERT(OWN_MUTEX(&all_threads_synch_lock) && OWN_MUTEX(&thread_initexit_lock));
|
|
/* XXX: would like to assert that suspendcount is > 0 but how? */
|
|
ASSERT(thread_synch_successful(tr));
|
|
|
|
DEBUG_DECLARE(bool res =) thread_get_mcontext(tr, mc);
|
|
ASSERT(res);
|
|
pre_translation = (app_pc)mc->pc;
|
|
LOG(GLOBAL, LOG_CACHE, 2, "\trecreating address for " PFX "\n", mc->pc);
|
|
LOG(THREAD, LOG_CACHE, 2,
|
|
"translate_from_synchall_to_dispatch: being translated from " PFX "\n", mc->pc);
|
|
if (get_at_syscall(dcontext)) {
|
|
/* Don't need to do anything as shared_syscall and do_syscall will not
|
|
* change due to a reset and will have any inlined ibl updated. If we
|
|
* did try to send these guys back to d_r_dispatch, have to set asynch_tag
|
|
* (as well as next_tag since translation looks only at that), restore
|
|
* TOS to asynch_target/esi (unless still at reset state), and have to
|
|
* figure out how to avoid post-syscall processing for those who never
|
|
* did pre-syscall processing (i.e., if at shared_syscall) (else will
|
|
* get wrong dcontext->sysnum, etc.)
|
|
* Not to mention that after resuming the kernel will finish the
|
|
* syscall and clobber several registers, making it hard to set a
|
|
* clean state (xref case 6113, case 5074, and notes below)!
|
|
* It's just too hard to redirect while at a syscall.
|
|
*/
|
|
LOG(GLOBAL, LOG_CACHE, 2, "\tat syscall so not translating\n");
|
|
/* sanity check */
|
|
ASSERT(is_after_syscall_address(dcontext, pre_translation) ||
|
|
IF_WINDOWS_ELSE(pre_translation == vsyscall_after_syscall,
|
|
is_after_or_restarted_do_syscall(dcontext, pre_translation,
|
|
true /*vsys*/)));
|
|
#if defined(UNIX) && defined(X86_32)
|
|
if (pre_translation == vsyscall_sysenter_return_pc ||
|
|
pre_translation + SYSENTER_LENGTH == vsyscall_sysenter_return_pc) {
|
|
/* Because we remove the vsyscall hook on a send_all_other_threads_native()
|
|
* yet have no barrier to know the threads have run their own go-native
|
|
* code, we want to send them away from the hook, to our gencode.
|
|
*/
|
|
if (pre_translation == vsyscall_sysenter_return_pc)
|
|
mc->pc = after_do_shared_syscall_addr(dcontext);
|
|
else if (pre_translation + SYSENTER_LENGTH == vsyscall_sysenter_return_pc)
|
|
mc->pc = get_do_int_syscall_entry(dcontext);
|
|
/* exit stub and subsequent fcache_return will save rest of state */
|
|
DEBUG_DECLARE(res =)
|
|
set_synched_thread_context(dcontext->thread_record, mc, NULL, 0,
|
|
synch_state _IF_X64((void *)mc) _IF_WINDOWS(NULL));
|
|
ASSERT(res);
|
|
/* cxt is freed by set_synched_thread_context() or target thread */
|
|
free_cxt = false;
|
|
}
|
|
#endif
|
|
IF_AARCHXX({
|
|
if (INTERNAL_OPTION(steal_reg_at_reset) != 0) {
|
|
/* We don't want to translate, just update the stolen reg values */
|
|
arch_mcontext_reset_stolen_reg(dcontext, mc);
|
|
DEBUG_DECLARE(res =)
|
|
set_synched_thread_context(dcontext->thread_record, mc, NULL, 0,
|
|
synch_state _IF_X64((void *)mc)
|
|
_IF_WINDOWS(NULL));
|
|
ASSERT(res);
|
|
/* cxt is freed by set_synched_thread_context() or target thread */
|
|
free_cxt = false;
|
|
}
|
|
});
|
|
} else {
|
|
DEBUG_DECLARE(res =) translate_mcontext(tr, mc, true /*restore memory*/, NULL);
|
|
ASSERT(res);
|
|
if (!thread_synch_successful(tr) || mc->pc == 0) {
|
|
/* Better to risk failure on accessing a freed cache than
|
|
* to have a guaranteed crash by sending to NULL.
|
|
* XXX: it's possible the real translation is NULL,
|
|
* but if so should be fine to leave it there since the
|
|
* current eip should also be NULL.
|
|
*/
|
|
ASSERT_NOT_REACHED();
|
|
goto translate_from_synchall_to_dispatch_exit;
|
|
}
|
|
LOG(GLOBAL, LOG_CACHE, 2, "\ttranslation pc = " PFX "\n", mc->pc);
|
|
ASSERT(!is_dynamo_address((app_pc)mc->pc) && !in_fcache((app_pc)mc->pc));
|
|
IF_AARCHXX({
|
|
if (INTERNAL_OPTION(steal_reg_at_reset) != 0) {
|
|
/* XXX: do we need this? Will signal.c will fix it up prior
|
|
* to sigreturn from suspend handler?
|
|
*/
|
|
arch_mcontext_reset_stolen_reg(dcontext, mc);
|
|
}
|
|
});
|
|
/* We send all threads, regardless of whether was in DR or not, to
|
|
* re-interp from translated cxt, to avoid having to handle stale
|
|
* local state problems if we simply resumed.
|
|
* We assume no KSTATS or other state issues to deal with.
|
|
* XXX: enter hook w/o an exit?
|
|
*/
|
|
dcontext->next_tag = (app_pc)mc->pc;
|
|
/* XXX PR 212266: for linux if we're at an inlined syscall
|
|
* we may have problems: however, we might be able to rely on the kernel
|
|
* not clobbering any registers besides eax (which is ok: reset stub
|
|
* handles it), though presumably it's allowed to write to any
|
|
* caller-saved registers. We may need to change inlined syscalls
|
|
* to set at_syscall (see comments below as well).
|
|
*/
|
|
if (pre_translation ==
|
|
IF_WINDOWS_ELSE(vsyscall_after_syscall, vsyscall_sysenter_return_pc) &&
|
|
!waiting_at_safe_spot(dcontext->thread_record, synch_state)) {
|
|
/* XXX case 7827/PR 212266: shouldn't translate for this case, right?
|
|
* should have -ignore_syscalls set at_syscall and eliminate
|
|
* this whole block of code
|
|
*/
|
|
/* put the proper retaddr back on the stack, as we won't
|
|
* be doing the ret natively to regain control, but rather
|
|
* will interpret it
|
|
*/
|
|
/* XXX: ensure readable and writable? */
|
|
app_pc cur_retaddr = *((app_pc *)mc->xsp);
|
|
app_pc native_retaddr;
|
|
ASSERT(cur_retaddr != NULL);
|
|
/* must be ignore_syscalls (else, at_syscall will be set) */
|
|
IF_WINDOWS(ASSERT(DYNAMO_OPTION(ignore_syscalls)));
|
|
ASSERT(get_syscall_method() == SYSCALL_METHOD_SYSENTER);
|
|
/* For DYNAMO_OPTION(sygate_sysenter) we need to restore both stack
|
|
* values and fix up esp, but we can't do it here since the kernel
|
|
* will change esp... incompatible w/ -ignore_syscalls anyway
|
|
*/
|
|
IF_WINDOWS(ASSERT_NOT_IMPLEMENTED(!DYNAMO_OPTION(sygate_sysenter)));
|
|
/* may still be at syscall from a prior reset -- don't want to grab
|
|
* locks for in_fcache so we determine via the translation
|
|
*/
|
|
ASSERT_NOT_TESTED();
|
|
native_retaddr = recreate_app_pc(dcontext, cur_retaddr, NULL);
|
|
if (native_retaddr != cur_retaddr) {
|
|
LOG(GLOBAL, LOG_CACHE, 2, "\trestoring TOS to " PFX " from " PFX "\n",
|
|
native_retaddr, cur_retaddr);
|
|
*((app_pc *)mc->xsp) = native_retaddr;
|
|
} else {
|
|
LOG(GLOBAL, LOG_CACHE, 2,
|
|
"\tnot restoring TOS since still at previous reset state " PFX "\n",
|
|
cur_retaddr);
|
|
}
|
|
}
|
|
/* Send back to d_r_dispatch. Rather than setting up last_exit in eax here,
|
|
* we point to a special routine to save the correct eax -- in fact it's
|
|
* simply a direct exit stub. Originally this was b/c we tried to
|
|
* translate threads at system calls, and the kernel clobbers eax (and
|
|
* ecx/edx for sysenter, though preserves eip setcontext change: case
|
|
* 6113, case 5074) in finishing the system call, but now that we don't
|
|
* translate them we've kept the stub approach. It's actually faster
|
|
* for the stub itself to save eax and set the linkstub than for us to
|
|
* emulate it here, anyway.
|
|
* Note that a thread in check_wait_at_safe_spot() spins and will NOT be
|
|
* at a syscall, avoiding problems there (case 5074).
|
|
*/
|
|
mc->pc = (app_pc)get_reset_exit_stub(dcontext);
|
|
/* We need to set ARM mode to match the reset exit stub. */
|
|
IF_ARM(dr_isa_mode_t prior_mode);
|
|
IF_ARM(dr_set_isa_mode(dcontext, DR_ISA_ARM_A32, &prior_mode));
|
|
LOG(GLOBAL, LOG_CACHE, 2, "\tsent to reset exit stub " PFX "\n", mc->pc);
|
|
/* The reset exit stub expects the stolen reg to contain the TLS base address.
|
|
* But the stolen reg was restored to the application value during
|
|
* translate_mcontext.
|
|
*/
|
|
#if defined(AARCHXX) || defined(RISCV64)
|
|
/* Preserve the translated value from mc before we clobber it. */
|
|
dcontext->local_state->spill_space.reg_stolen = get_stolen_reg_val(mc);
|
|
set_stolen_reg_val(mc, (reg_t)os_get_dr_tls_base(dcontext));
|
|
# ifdef RISCV64
|
|
os_set_app_tls_base(dcontext, TLS_REG_LIB, (void *)get_tp_reg_val(mc));
|
|
set_tp_reg_val(mc, (reg_t)os_get_app_tls_base(dcontext, TLS_REG_LIB));
|
|
# endif
|
|
#endif
|
|
|
|
#ifdef WINDOWS
|
|
/* i#25: we could have interrupted thread in DR, where has priv fls data
|
|
* in TEB, and fcache_return blindly copies into app fls: so swap to app
|
|
* now, just in case. DR routine can handle swapping when already app.
|
|
*/
|
|
swap_peb_pointer(dcontext, false /*to app*/);
|
|
#endif
|
|
/* exit stub and subsequent fcache_return will save rest of state */
|
|
DEBUG_DECLARE(res =)
|
|
set_synched_thread_context(dcontext->thread_record, mc, NULL, 0,
|
|
synch_state _IF_X64((void *)mc) _IF_WINDOWS(NULL));
|
|
ASSERT(res);
|
|
/* cxt is freed by set_synched_thread_context() or target thread */
|
|
free_cxt = false;
|
|
/* Now that set_synched_thread_context() recorded the mode for the reset
|
|
* exit stub, restore for the post-exit-stub execution.
|
|
*/
|
|
IF_ARM(dr_set_isa_mode(dcontext, prior_mode, NULL));
|
|
}
|
|
translate_from_synchall_to_dispatch_exit:
|
|
if (free_cxt) {
|
|
global_heap_free(mc, sizeof(*mc) HEAPACCT(ACCT_OTHER));
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
* Detach and similar operations
|
|
*/
|
|
|
|
/* Atomic variable to prevent multiple threads from trying to detach at
|
|
* the same time.
|
|
*/
|
|
DECLARE_CXTSWPROT_VAR(static volatile int dynamo_detaching_flag, LOCK_FREE_STATE);
|
|
|
|
void
|
|
send_all_other_threads_native(void)
|
|
{
|
|
thread_record_t **threads;
|
|
dcontext_t *my_dcontext = get_thread_private_dcontext();
|
|
int i, num_threads;
|
|
bool waslinking;
|
|
/* We're forced to use an asynch model due to not being able to call
|
|
* dynamo_thread_not_under_dynamo, which has a bonus of making it easier
|
|
* to handle other threads asking for synchall.
|
|
* This is why we don't ask for THREAD_SYNCH_SUSPENDED_VALID_MCONTEXT.
|
|
*/
|
|
const thread_synch_state_t desired_state =
|
|
THREAD_SYNCH_SUSPENDED_VALID_MCONTEXT_OR_NO_XFER;
|
|
|
|
ASSERT(dynamo_initialized && !dynamo_exited && my_dcontext != NULL);
|
|
LOG(my_dcontext->logfile, LOG_ALL, 1, "%s\n", __FUNCTION__);
|
|
LOG(GLOBAL, LOG_ALL, 1, "%s: cur thread " TIDFMT "\n", __FUNCTION__,
|
|
d_r_get_thread_id());
|
|
|
|
waslinking = is_couldbelinking(my_dcontext);
|
|
if (waslinking)
|
|
enter_nolinking(my_dcontext, NULL, false);
|
|
|
|
#ifdef WINDOWS
|
|
/* Ensure new threads will go straight to native */
|
|
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
init_apc_go_native_pause = true;
|
|
init_apc_go_native = true;
|
|
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
|
|
wait_for_outstanding_nudges();
|
|
#endif
|
|
|
|
instrument_pre_detach_event();
|
|
|
|
/* Suspend all threads except those trying to synch with us */
|
|
if (!synch_with_all_threads(desired_state, &threads, &num_threads,
|
|
THREAD_SYNCH_NO_LOCKS_NO_XFER,
|
|
THREAD_SYNCH_SUSPEND_FAILURE_IGNORE)) {
|
|
REPORT_FATAL_ERROR_AND_EXIT(FAILED_TO_SYNCHRONIZE_THREADS, 2,
|
|
get_application_name(), get_application_pid());
|
|
}
|
|
|
|
ASSERT(mutex_testlock(&all_threads_synch_lock) &&
|
|
mutex_testlock(&thread_initexit_lock));
|
|
|
|
#ifdef WINDOWS
|
|
/* Let threads waiting at APC point go native */
|
|
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
init_apc_go_native_pause = false;
|
|
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
#endif
|
|
|
|
#ifdef WINDOWS
|
|
/* XXX i#95: handle outstanding callbacks where we've put our retaddr on
|
|
* the app stack. This should be able to share
|
|
* detach_helper_handle_callbacks() code. Won't the old single-thread
|
|
* dr_app_stop() have had this same problem? Since we're not tearing
|
|
* everything down, can we solve it by waiting until we hit
|
|
* after_shared_syscall_code_ex() in a native thread?
|
|
*/
|
|
ASSERT_NOT_IMPLEMENTED(get_syscall_method() != SYSCALL_METHOD_SYSENTER);
|
|
#endif
|
|
|
|
for (i = 0; i < num_threads; i++) {
|
|
if (threads[i]->dcontext == my_dcontext ||
|
|
is_thread_currently_native(threads[i]) ||
|
|
/* XXX i#2784: we should suspend client threads for the duration
|
|
* of the app being native to avoid problems with having no
|
|
* signal handlers in place.
|
|
*/
|
|
IS_CLIENT_THREAD(threads[i]->dcontext))
|
|
continue;
|
|
|
|
/* Because dynamo_thread_not_under_dynamo() has to be run by the owning
|
|
* thread, the simplest solution is to send everyone back to d_r_dispatch
|
|
* with a flag to go native from there, rather than directly setting the
|
|
* native context.
|
|
*/
|
|
threads[i]->dcontext->go_native = true;
|
|
|
|
if (thread_synch_state_no_xfer(threads[i]->dcontext)) {
|
|
/* Another thread trying to synch with us: just let it go. It will
|
|
* go native once it gets back to d_r_dispatch which will be before it
|
|
* goes into the cache.
|
|
*/
|
|
continue;
|
|
} else {
|
|
LOG(my_dcontext->logfile, LOG_ALL, 1, "%s: sending thread %d native\n",
|
|
__FUNCTION__, threads[i]->id);
|
|
LOG(threads[i]->dcontext->logfile, LOG_ALL, 1,
|
|
"**** requested by thread %d to go native\n", my_dcontext->owning_thread);
|
|
/* This won't change a thread at a syscall, so we rely on the thread
|
|
* going to d_r_dispatch and then going native when its syscall exits.
|
|
*
|
|
* XXX i#95: That means the time to go native is, unfortunately,
|
|
* unbounded. This means that dr_app_cleanup() needs to synch the
|
|
* threads and force-xl8 these. We should share code with detach.
|
|
* Right now we rely on the app joining all its threads *before*
|
|
* calling dr_app_cleanup(), or using dr_app_stop_and_cleanup[_with_stats]().
|
|
* This also means we have a race with unhook_vsyscall in
|
|
* os_process_not_under_dynamorio(), which we solve by redirecting
|
|
* threads at syscalls to our gencode.
|
|
*/
|
|
translate_from_synchall_to_dispatch(threads[i], desired_state);
|
|
}
|
|
}
|
|
|
|
end_synch_with_all_threads(threads, num_threads, true /*resume*/);
|
|
|
|
os_process_not_under_dynamorio(my_dcontext);
|
|
|
|
if (waslinking)
|
|
enter_couldbelinking(my_dcontext, NULL, false);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
detach_set_mcontext_helper(thread_record_t *thread)
|
|
{
|
|
priv_mcontext_t mc;
|
|
LOG(GLOBAL, LOG_ALL, 2, "Detach: translating " TIDFMT "\n", thread);
|
|
DEBUG_DECLARE(bool ok =)
|
|
thread_get_mcontext(thread, &mc);
|
|
ASSERT(ok);
|
|
/* For a thread at a syscall, we use SA_RESTART for our suspend signal,
|
|
* so the kernel will adjust the restart point back to the syscall for us
|
|
* where expected. This is an artifical signal we're introducing, so an
|
|
* app that assumes no signals and assumes its non-auto-restart syscalls
|
|
* don't need loops could be broken.
|
|
*/
|
|
LOG(GLOBAL, LOG_ALL, 3,
|
|
/* Having the code bytes can help diagnose post-detach where the code
|
|
* cache is gone.
|
|
*/
|
|
"Detach: pre-xl8 pc=%p (%02x %02x %02x %02x %02x), xsp=%p "
|
|
"for thread " TIDFMT "\n",
|
|
mc.pc, *mc.pc, *(mc.pc + 1), *(mc.pc + 2), *(mc.pc + 3), *(mc.pc + 4), mc.xsp,
|
|
thread->id);
|
|
DEBUG_DECLARE(ok =)
|
|
translate_mcontext(thread, &mc, true /*restore mem*/, NULL /*f*/);
|
|
ASSERT(ok);
|
|
if (!thread->under_dynamo_control) {
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach : thread " TIDFMT " already running natively\n",
|
|
thread->id);
|
|
/* we do need to restore the app ret addr, for native_exec */
|
|
if (!DYNAMO_OPTION(thin_client) && DYNAMO_OPTION(native_exec) &&
|
|
!vmvector_empty(native_exec_areas)) {
|
|
put_back_native_retaddrs(thread->dcontext);
|
|
}
|
|
}
|
|
detach_finalize_translation(thread, &mc);
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: pc=" PFX " for thread " TIDFMT "\n", mc.pc,
|
|
thread->id);
|
|
ASSERT(!is_dynamo_address(mc.pc) && !in_fcache(mc.pc));
|
|
/* XXX case 7457: if the thread is suspended after it received a fault
|
|
* but before the kernel copied the faulting context to the user mode
|
|
* structures for the handler, it could result in a codemod exception
|
|
* that wouldn't happen natively!
|
|
*/
|
|
DEBUG_DECLARE(ok =)
|
|
thread_set_mcontext(thread, &mc);
|
|
ASSERT(ok);
|
|
/* i#249: restore app's PEB/TEB fields */
|
|
IF_WINDOWS(restore_peb_pointer_for_thread(thread->dcontext));
|
|
/* Put the translation into the dcontext's mcontext so the detach state
|
|
* is available via dr_get_mcontext().
|
|
*/
|
|
*get_mcontext(thread->dcontext) = mc;
|
|
}
|
|
|
|
static void
|
|
detach_cleanup_helper(thread_record_t *thread _IF_WINDOWS(bool detach_stacked_callbacks))
|
|
{
|
|
DEBUG_DECLARE(int exit_res =)
|
|
dynamo_shared_exit(thread _IF_WINDOWS(detach_stacked_callbacks));
|
|
ASSERT(exit_res == SUCCESS);
|
|
detach_finalize_cleanup();
|
|
|
|
stack_free(d_r_initstack, DYNAMORIO_STACK_SIZE);
|
|
|
|
dynamo_exit_post_detach();
|
|
|
|
doing_detach = false;
|
|
started_detach = false;
|
|
|
|
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
dynamo_detaching_flag = LOCK_FREE_STATE;
|
|
EXITING_DR();
|
|
options_detach();
|
|
}
|
|
|
|
void
|
|
detach_on_permanent_stack(bool internal, bool do_cleanup, dr_stats_t *drstats)
|
|
{
|
|
dcontext_t *my_dcontext;
|
|
thread_record_t **threads;
|
|
thread_record_t *my_tr = NULL;
|
|
int i, num_threads, my_idx = -1;
|
|
thread_id_t my_id;
|
|
#ifdef WINDOWS
|
|
bool detach_stacked_callbacks;
|
|
bool *cleanup_tpc;
|
|
#endif
|
|
|
|
/* synch-all flags: */
|
|
uint flags = 0;
|
|
#ifdef WINDOWS
|
|
/* For Windows we may fail to suspend a thread (e.g., privilege
|
|
* problems), and in that case we want to just ignore the failure.
|
|
*/
|
|
flags |= THREAD_SYNCH_SUSPEND_FAILURE_IGNORE;
|
|
#elif defined(UNIX)
|
|
/* For Unix, such privilege problems are rarer but we would still prefer to
|
|
* continue if we hit a problem.
|
|
*/
|
|
flags |= THREAD_SYNCH_SUSPEND_FAILURE_IGNORE;
|
|
#endif
|
|
|
|
/* i#297: we only synch client threads after process exit event. */
|
|
flags |= THREAD_SYNCH_SKIP_CLIENT_THREAD;
|
|
|
|
ENTERING_DR();
|
|
|
|
/* dynamo_detaching_flag is not really a lock, and since no one ever waits
|
|
* on it we can't deadlock on it either.
|
|
*/
|
|
if (!atomic_compare_exchange(&dynamo_detaching_flag, LOCK_FREE_STATE, LOCK_SET_STATE))
|
|
return;
|
|
|
|
instrument_pre_detach_event();
|
|
|
|
/* Unprotect .data for exit cleanup.
|
|
* XXX: more secure to not do this until we've synched, but then need
|
|
* alternative prot for started_detach and init_apc_go_native*
|
|
*/
|
|
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
|
|
ASSERT(!started_detach);
|
|
started_detach = true;
|
|
|
|
if (!internal) {
|
|
synchronize_dynamic_options();
|
|
if (!DYNAMO_OPTION(allow_detach)) {
|
|
started_detach = false;
|
|
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
dynamo_detaching_flag = LOCK_FREE_STATE;
|
|
SYSLOG_INTERNAL_ERROR("Detach called without the allow_detach option set");
|
|
EXITING_DR();
|
|
return;
|
|
}
|
|
}
|
|
|
|
ASSERT(dynamo_initialized);
|
|
ASSERT(!dynamo_exited);
|
|
|
|
my_id = d_r_get_thread_id();
|
|
my_dcontext = get_thread_private_dcontext();
|
|
if (my_dcontext == NULL) {
|
|
/* We support detach after just dr_app_setup() with no start. */
|
|
ASSERT(!dynamo_started);
|
|
my_tr = thread_lookup(my_id);
|
|
ASSERT(my_tr != NULL);
|
|
my_dcontext = my_tr->dcontext;
|
|
os_process_under_dynamorio_initiate(my_dcontext);
|
|
os_process_under_dynamorio_complete(my_dcontext);
|
|
dynamo_thread_under_dynamo(my_dcontext);
|
|
ASSERT(get_thread_private_dcontext() == my_dcontext);
|
|
}
|
|
ASSERT(my_dcontext != NULL);
|
|
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: thread %d starting detach process\n", my_id);
|
|
SYSLOG(SYSLOG_INFORMATION, INFO_DETACHING, 2, get_application_name(),
|
|
get_application_pid());
|
|
|
|
/* synch with flush */
|
|
if (my_dcontext != NULL)
|
|
enter_threadexit(my_dcontext);
|
|
|
|
#ifdef WINDOWS
|
|
/* Signal to go native at APC init here. Set pause first so that threads
|
|
* will wait till we are ready for them to go native (after ntdll unpatching).
|
|
* (To avoid races these must be set in this order!)
|
|
*/
|
|
init_apc_go_native_pause = true;
|
|
init_apc_go_native = true;
|
|
/* XXX i#2611: there is still a race for threads caught between init_apc_go_native
|
|
* and dynamo_thread_init adding to all_threads: this just reduces the risk.
|
|
* Unfortunately we can't easily use the UNIX solution of uninit_thread_count
|
|
* since we can't distinguish internally vs externally created threads.
|
|
*/
|
|
os_thread_yield();
|
|
wait_for_outstanding_nudges();
|
|
#endif
|
|
|
|
#if defined(UNIX) && !defined(LINUX_KERNEL)
|
|
/* i#2270: we ignore alarm signals during detach to reduce races. */
|
|
signal_remove_alarm_handlers(my_dcontext);
|
|
#endif
|
|
|
|
/* suspend all DR-controlled threads at safe locations */
|
|
if (!synch_with_all_threads(THREAD_SYNCH_SUSPENDED_VALID_MCONTEXT, &threads,
|
|
&num_threads,
|
|
/* Case 6821: allow other synch-all-thread uses
|
|
* that beat us to not wait on us. We still have
|
|
* a problem if we go first since we must xfer
|
|
* other threads.
|
|
*/
|
|
THREAD_SYNCH_NO_LOCKS_NO_XFER, flags)) {
|
|
REPORT_FATAL_ERROR_AND_EXIT(FAILED_TO_SYNCHRONIZE_THREADS, 2,
|
|
get_application_name(), get_application_pid());
|
|
}
|
|
|
|
/* Now we own the thread_initexit_lock. We'll release the locks grabbed in
|
|
* synch_with_all_threads below after cleaning up all the threads in case we
|
|
* need to grab it during process exit cleanup.
|
|
*/
|
|
ASSERT(mutex_testlock(&all_threads_synch_lock) &&
|
|
mutex_testlock(&thread_initexit_lock));
|
|
|
|
ASSERT(!doing_detach);
|
|
doing_detach = true;
|
|
detacher_tid = d_r_get_thread_id();
|
|
|
|
#ifdef HOT_PATCHING_INTERFACE
|
|
/* In hotp_only mode, we must remove patches when detaching; we don't want
|
|
* to leave in all our hooks and detach; that will definitely crash the app.
|
|
*/
|
|
if (DYNAMO_OPTION(hotp_only))
|
|
hotp_only_detach_helper();
|
|
#endif
|
|
|
|
#ifdef WINDOWS
|
|
/* XXX: maybe we should re-check for additional threads that passed the init_apc
|
|
* lock but weren't yet initialized and so didn't show up on the list?
|
|
*/
|
|
|
|
LOG(GLOBAL, LOG_ALL, 1,
|
|
"Detach : about to unpatch ntdll.dll and fix memory permissions\n");
|
|
detach_remove_image_entry_hook(num_threads, threads);
|
|
if (!INTERNAL_OPTION(noasynch)) {
|
|
/* We have to do this here, before client exit events, as we're letting
|
|
* threads go native next. We thus will not detect crashes during client
|
|
* exit during detach.
|
|
*/
|
|
callback_interception_unintercept();
|
|
}
|
|
#endif
|
|
|
|
if (!DYNAMO_OPTION(thin_client))
|
|
revert_memory_regions();
|
|
#ifdef UNIX
|
|
unhook_vsyscall();
|
|
#endif
|
|
LOG(GLOBAL, LOG_ALL, 1,
|
|
"Detach : unpatched ntdll.dll and fixed memory permissions\n");
|
|
#ifdef WINDOWS
|
|
/* Release the APC init lock and let any threads waiting there go native */
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach : Releasing init_apc_go_native_pause\n");
|
|
init_apc_go_native_pause = false;
|
|
#endif
|
|
|
|
/* perform exit tasks that require full thread data structs */
|
|
dynamo_process_exit_with_thread_info();
|
|
|
|
#ifdef WINDOWS
|
|
/* We need to record a bool indicating whether we can free each thread's
|
|
* resources fully or whether we need them for callback cleanup.
|
|
*/
|
|
cleanup_tpc =
|
|
(bool *)global_heap_alloc(num_threads * sizeof(bool) HEAPACCT(ACCT_OTHER));
|
|
/* Handle any outstanding callbacks */
|
|
detach_stacked_callbacks = detach_handle_callbacks(num_threads, threads, cleanup_tpc);
|
|
#endif
|
|
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: starting to translate contexts\n");
|
|
for (i = 0; i < num_threads; i++) {
|
|
if (threads[i]->dcontext == my_dcontext) {
|
|
my_idx = i;
|
|
my_tr = threads[i];
|
|
continue;
|
|
} else if (IS_CLIENT_THREAD(threads[i]->dcontext)) {
|
|
/* i#297 we will kill client-owned threads later after app exit events
|
|
* in dynamo_shared_exit().
|
|
*/
|
|
continue;
|
|
} else if (detach_do_not_translate(threads[i])) {
|
|
LOG(GLOBAL, LOG_ALL, 2, "Detach: not translating " TIDFMT "\n",
|
|
threads[i]->id);
|
|
} else {
|
|
detach_set_mcontext_helper(threads[i]);
|
|
}
|
|
/* Resumes the thread, which will do kernel-visible cleanup of
|
|
* signal state. Resume happens within the synch_all region where
|
|
* the thread_initexit_lock is held so that we can clean up thread
|
|
* data later.
|
|
*/
|
|
#if defined(UNIX) && !defined(LINUX_KERNEL)
|
|
os_signal_thread_detach(threads[i]->dcontext);
|
|
#endif
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: thread " TIDFMT " is being resumed as native\n",
|
|
threads[i]->id);
|
|
os_thread_resume(threads[i]);
|
|
}
|
|
|
|
ASSERT(my_idx != -1 || !internal);
|
|
#if defined(UNIX) && !defined(LINUX_KERNEL)
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: waiting for threads to fully detach\n");
|
|
for (i = 0; i < num_threads; i++) {
|
|
if (i != my_idx && !IS_CLIENT_THREAD(threads[i]->dcontext))
|
|
os_wait_thread_detached(threads[i]->dcontext);
|
|
}
|
|
#endif
|
|
|
|
if (!do_cleanup)
|
|
return;
|
|
|
|
/* Clean up each thread now that everyone has gone native. Needs to be
|
|
* done with the thread_initexit_lock held, which is true within a synched
|
|
* region.
|
|
*/
|
|
for (i = 0; i < num_threads; i++) {
|
|
if (i != my_idx && !IS_CLIENT_THREAD(threads[i]->dcontext)) {
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: cleaning up thread " TIDFMT " %s\n",
|
|
threads[i]->id, IF_WINDOWS_ELSE(cleanup_tpc[i] ? "and its TPC" : "", ""));
|
|
dynamo_other_thread_exit(threads[i] _IF_WINDOWS(!cleanup_tpc[i]));
|
|
}
|
|
}
|
|
|
|
if (my_idx != -1) {
|
|
/* pre-client thread cleanup (PR 536058) */
|
|
dynamo_thread_exit_pre_client(my_dcontext, my_tr->id);
|
|
}
|
|
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: Letting secondary threads go native\n");
|
|
#ifdef WINDOWS
|
|
global_heap_free(cleanup_tpc, num_threads * sizeof(bool) HEAPACCT(ACCT_OTHER));
|
|
/* XXX: there's a possible race if a thread waiting at APC is still there
|
|
* when we unload our dll.
|
|
*/
|
|
os_thread_yield();
|
|
#endif
|
|
end_synch_with_all_threads(threads, num_threads, false /*don't resume */);
|
|
threads = NULL;
|
|
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: Entering final cleanup and unload\n");
|
|
SYSLOG_INTERNAL_INFO("Detaching from process, entering final cleanup");
|
|
if (drstats != NULL)
|
|
stats_get_snapshot(drstats);
|
|
detach_cleanup_helper(my_tr _IF_WINDOWS(detach_stacked_callbacks));
|
|
}
|
|
|
|
#ifdef LINUX
|
|
void
|
|
detach_externally_on_new_stack(void)
|
|
{
|
|
dcontext_t *my_dcontext;
|
|
priv_mcontext_t my_mcontext;
|
|
thread_record_t **threads;
|
|
thread_record_t *my_tr = NULL;
|
|
int i, num_threads, my_idx = -1;
|
|
thread_id_t my_id;
|
|
DEBUG_DECLARE(bool ok;)
|
|
/* synch-all flags: */
|
|
uint flags = 0;
|
|
/* For Unix, such privilege problems are rarer but we would still prefer to
|
|
* continue if we hit a problem.
|
|
*/
|
|
flags |= THREAD_SYNCH_SUSPEND_FAILURE_IGNORE;
|
|
/* i#297: we only synch client threads after process exit event. */
|
|
flags |= THREAD_SYNCH_SKIP_CLIENT_THREAD;
|
|
ENTERING_DR();
|
|
/* dynamo_detaching_flag is not really a lock, and since no one ever waits
|
|
* on it we can't deadlock on it either.
|
|
*/
|
|
if (!atomic_compare_exchange(&dynamo_detaching_flag, LOCK_FREE_STATE, LOCK_SET_STATE))
|
|
return;
|
|
instrument_pre_detach_event();
|
|
/* Unprotect .data for exit cleanup.
|
|
* XXX: more secure to not do this until we've synched, but then need
|
|
* alternative prot for started_detach and init_apc_go_native*
|
|
*/
|
|
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
|
|
ASSERT(!started_detach);
|
|
started_detach = true;
|
|
ASSERT(dynamo_initialized);
|
|
ASSERT(!dynamo_exited);
|
|
my_id = d_r_get_thread_id();
|
|
my_dcontext = get_thread_private_dcontext();
|
|
ASSERT(my_dcontext != NULL);
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: thread %d starting detach process\n", my_id);
|
|
SYSLOG(SYSLOG_INFORMATION, INFO_DETACHING, 2, get_application_name(),
|
|
get_application_pid());
|
|
/* synch with flush */
|
|
if (my_dcontext != NULL)
|
|
enter_threadexit(my_dcontext);
|
|
/* i#2270: we ignore alarm signals during detach to reduce races. */
|
|
# ifndef LINUX_KERNEL
|
|
signal_remove_alarm_handlers(my_dcontext);
|
|
# endif
|
|
/* suspend all DR-controlled threads at safe locations */
|
|
if (!synch_with_all_threads(THREAD_SYNCH_SUSPENDED_VALID_MCONTEXT, &threads,
|
|
&num_threads,
|
|
/* Case 6821: allow other synch-all-thread uses
|
|
* that beat us to not wait on us. We still have
|
|
* a problem if we go first since we must xfer
|
|
* other threads.
|
|
*/
|
|
THREAD_SYNCH_NO_LOCKS_NO_XFER, flags)) {
|
|
REPORT_FATAL_ERROR_AND_EXIT(FAILED_TO_SYNCHRONIZE_THREADS, 2,
|
|
get_application_name(), get_application_pid());
|
|
}
|
|
/* Now we own the thread_initexit_lock. We'll release the locks grabbed in
|
|
* synch_with_all_threads below after cleaning up all the threads in case we
|
|
* need to grab it during process exit cleanup.
|
|
*/
|
|
ASSERT(mutex_testlock(&all_threads_synch_lock) &&
|
|
mutex_testlock(&thread_initexit_lock));
|
|
ASSERT(!doing_detach);
|
|
doing_detach = true;
|
|
detacher_tid = d_r_get_thread_id();
|
|
# ifdef HOT_PATCHING_INTERFACE
|
|
/* In hotp_only mode, we must remove patches when detaching; we don't want
|
|
* to leave in all our hooks and detach; that will definitely crash the app.
|
|
*/
|
|
if (DYNAMO_OPTION(hotp_only))
|
|
hotp_only_detach_helper();
|
|
# endif
|
|
if (!DYNAMO_OPTION(thin_client))
|
|
revert_memory_regions();
|
|
unhook_vsyscall();
|
|
LOG(GLOBAL, LOG_ALL, 1,
|
|
"Detach : unpatched ntdll.dll and fixed memory permissions\n");
|
|
/* perform exit tasks that require full thread data structs */
|
|
dynamo_process_exit_with_thread_info();
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: starting to translate contexts\n");
|
|
for (i = 0; i < num_threads; i++) {
|
|
if (threads[i]->dcontext == my_dcontext) {
|
|
my_idx = i;
|
|
my_tr = threads[i];
|
|
my_mcontext = *get_mcontext(my_dcontext);
|
|
my_mcontext.pc = my_dcontext->next_tag;
|
|
DEBUG_DECLARE(ok =)
|
|
translate_mcontext(threads[i], &my_mcontext, true /*restore mem*/,
|
|
NULL /*f*/);
|
|
continue;
|
|
} else if (IS_CLIENT_THREAD(threads[i]->dcontext)) {
|
|
/* i#297 we will kill client-owned threads later after app exit events
|
|
* in dynamo_shared_exit().
|
|
*/
|
|
continue;
|
|
} else if (detach_do_not_translate(threads[i])) {
|
|
LOG(GLOBAL, LOG_ALL, 2, "Detach: not translating " TIDFMT "\n",
|
|
threads[i]->id);
|
|
} else {
|
|
detach_set_mcontext_helper(threads[i]);
|
|
}
|
|
/* Resumes the thread, which will do kernel-visible cleanup of
|
|
* signal state. Resume happens within the synch_all region where
|
|
* the thread_initexit_lock is held so that we can clean up thread
|
|
* data later.
|
|
*/
|
|
# ifndef LINUX_KERNEL
|
|
os_signal_thread_detach(threads[i]->dcontext);
|
|
# endif
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: thread " TIDFMT " is being resumed as native\n",
|
|
threads[i]->id);
|
|
os_thread_resume(threads[i]);
|
|
}
|
|
# ifndef LINUX_KERNEL
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: waiting for threads to fully detach\n");
|
|
for (i = 0; i < num_threads; i++) {
|
|
if (i != my_idx && !IS_CLIENT_THREAD(threads[i]->dcontext))
|
|
os_wait_thread_detached(threads[i]->dcontext);
|
|
}
|
|
# endif
|
|
/* Clean up each thread now that everyone has gone native. Needs to be
|
|
* done with the thread_initexit_lock held, which is true within a synched
|
|
* region.
|
|
*/
|
|
for (i = 0; i < num_threads; i++) {
|
|
if (i != my_idx && !IS_CLIENT_THREAD(threads[i]->dcontext)) {
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: cleaning up thread " TIDFMT " %s\n",
|
|
threads[i]->id, IF_WINDOWS_ELSE(cleanup_tpc[i] ? "and its TPC" : "", ""));
|
|
dynamo_other_thread_exit(threads[i] _IF_WINDOWS(!cleanup_tpc[i]));
|
|
}
|
|
}
|
|
if (my_idx != -1) {
|
|
/* pre-client thread cleanup (PR 536058) */
|
|
dynamo_thread_exit_pre_client(my_dcontext, my_tr->id);
|
|
}
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: Letting secondary threads go native\n");
|
|
end_synch_with_all_threads(threads, num_threads, false /*don't resume */);
|
|
threads = NULL;
|
|
LOG(GLOBAL, LOG_ALL, 1, "Detach: Entering final cleanup and unload\n");
|
|
SYSLOG_INTERNAL_INFO("Detaching from process, entering final cleanup");
|
|
detach_cleanup_helper(my_tr);
|
|
thread_set_self_mcontext(&my_mcontext, true);
|
|
}
|
|
#endif
|