@@ -1749,6 +1749,12 @@ translate_from_synchall_to_dispatch(thread_record_t *tr, thread_synch_state_t sy
                 arch_mcontext_reset_stolen_reg(dcontext, mc);
             }
         });
+        IF_AARCHXX({
+            set_stolen_reg_val(mc, (reg_t)os_get_dr_tls_base(dcontext));
+            // XXX: This path is tested by linux.thread-reset and linux.clone-reset.
+            // We just haven't run those on ARM yet.
+            IF_ARM(ASSERT_NOT_TESTED());
+        });
         /* 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.
/* **********************************************************
 * Copyright (c) 2012-2020 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 */

static bool started_detach = false; /* set before synchall */
bool doing_detach = false;          /* set after synchall */

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) {
#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, FIXME 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) &&
            IF_CLIENT_INTERFACE(!is_in_client_lib(pc) &&)
            /* xref PR 200067 & 222812 on client-owned native threads */
            IF_CLIENT_INTERFACE(!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 */
#ifdef CLIENT_INTERFACE
        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;
        }
#endif
        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, FIXME - 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);
        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);
#ifdef CLIENT_INTERFACE
            if (dr_xl8_hook_exists()) {
                if (!instrument_restore_nonfcache_state(trec->dcontext, true, mcontext))
                    return false;
            }
#endif
            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;
}

#ifdef CLIENT_SIDELINE
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);
}
#endif

/* 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. FIXME : 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 */
    /* FIXME: right now don't distinguish between suspend and term privileges
     * even though suspend is stronger requirement, are the checks below
     * sufficient */
    /* FIXME : 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.
     * FIXME : 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 */
#ifdef CLIENT_INTERFACE
        /* 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.  FIXME - 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);
        }
#endif
        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
        }
#ifdef CLIENT_INTERFACE
    } 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;
#endif
    } 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;
    app_pc pc;
    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);
    pc = get_mcontext(dcontext)->pc;
    LOG(THREAD, LOG_SYNCH, 2, "waiting for synch with state %d (pc " PFX ")\n", cur_state,
        pc);
    if (cur_state == THREAD_SYNCH_VALID_MCONTEXT) {
        ASSERT(!is_dynamo_address(pc));
        /* for detach must set this here and now */
        IF_WINDOWS(IF_CLIENT_INTERFACE(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. FIXME - 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.
     * FIXME: 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
            /* FIXME 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) {
        /* FIXME: 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);
        else
            thread_set_self_context((void *)cxt);
        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.  FIXME 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.
 * FIXME : 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()
{
    /* 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 = TEST(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 */
        /* FIXME : thread id recycling is possible that this could be a
         * different thread, perhaps we should take handle instead of id
         * FIXME: 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, FIXME :
             * 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)) {
                /* FIXME : 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(
                    IF_APP_EXPORTS(dr_api_exit ||)(false &&
                                                   "Thead synch unable to suspend target"
                                                   " thread, case 2096?"));
                res = (TEST(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)) {
                /* FIXME : 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 = (TEST(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 (!TEST(THREAD_SYNCH_SUSPEND_FAILURE_IGNORE, flags))
                    os_thread_resume(trec);
                break;
            }
            if (at_safe_spot(trec, &mc, desired_state)) {
                /* FIXME: 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 = (TEST(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(TEST(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,
                       /* FIXME: 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 = TEST(THREAD_SYNCH_SMALL_LOOP_MAX, flags)
        ? (SYNCH_ALL_THREADS_MAXIMUM_LOOPS / 10)
        : SYNCH_ALL_THREADS_MAXIMUM_LOOPS;
#ifdef CLIENT_INTERFACE
    /* 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. FIXME - 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;
#endif

    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 -- FIXME: 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 (TEST(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  */
    /* FIXME: 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 */
    /* FIXME: 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++) {
                    /* FIXME : 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);
#ifdef CLIENT_INTERFACE
        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;
            }
        }
#endif
        /* 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) {
#ifdef CLIENT_INTERFACE
                if (!finished_non_client_threads &&
                    IS_CLIENT_THREAD(threads[i]->dcontext)) {
                    all_synched = false;
                    continue; /* skip this thread for now till non-client are finished */
                }
                if (IS_CLIENT_THREAD(threads[i]->dcontext) &&
                    (TEST(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;
                }
#endif
                /* 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 (TEST(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 && TEST(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, FIXME : 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));
    }
    /* FIXME 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 && TEST(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);
    /* FIXME 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;
#ifdef CLIENT_SIDELINE
                if (IS_CLIENT_THREAD(threads[i]->dcontext) &&
                    threads[i]->dcontext->client_data->left_unsuspended) {
                    /* PR 609569: we did not suspend this thread */
                    resume = false;
                }
#endif
                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;
    bool res;

    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;
#ifdef CLIENT_SIDELINE
        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;
        }
#endif

        /* This routine assumes that each thread in the array was suspended, so
         * each one has to successfully resume.
         */
        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)
{
    bool res;
    /* 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));
    /* FIXME: would like to assert that suspendcount is > 0 but how? */
    ASSERT(thread_synch_successful(tr));

    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 */
            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_ARM({
            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);
                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 {
        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.
             * FIXME: 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_ARM({
            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.
         * FIXME: enter hook w/o an exit?
         */
        dcontext->next_tag = (app_pc)mc->pc;
        /* FIXME 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)) {
            /* FIXME 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
             */
            /* FIXME: 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);
        LOG(GLOBAL, LOG_CACHE, 2, "\tsent to reset exit stub " PFX "\n", mc->pc);
#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 */
        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;
    }
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);

#    ifdef CLIENT_INTERFACE
    wait_for_outstanding_nudges();
#    endif
#endif

    /* 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
    /* FIXME 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]) ||
            /* FIXME 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.
             *
             * FIXME 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;
}

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
    DEBUG_DECLARE(bool ok;)
    DEBUG_DECLARE(int exit_res;)

    /* 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;

    /* 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();
#    ifdef CLIENT_INTERFACE
    wait_for_outstanding_nudges();
#    endif
#endif

#ifdef UNIX
    /* 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;

#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++) {
        priv_mcontext_t mc;
        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 {
            LOG(GLOBAL, LOG_ALL, 2, "Detach: translating " TIDFMT "\n", threads[i]->id);
            DEBUG_DECLARE(ok =)
            thread_get_mcontext(threads[i], &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, threads[i]->id);
            DEBUG_DECLARE(ok =)
            translate_mcontext(threads[i], &mc, true /*restore mem*/, NULL /*f*/);
            ASSERT(ok);

            if (!threads[i]->under_dynamo_control) {
                LOG(GLOBAL, LOG_ALL, 1,
                    "Detach : thread " TIDFMT " already running natively\n",
                    threads[i]->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(threads[i]->dcontext);
                }
            }
            detach_finalize_translation(threads[i], &mc);

            LOG(GLOBAL, LOG_ALL, 1, "Detach: pc=" PFX " for thread " TIDFMT "\n", mc.pc,
                threads[i]->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(threads[i], &mc);
            ASSERT(ok);

            /* i#249: restore app's PEB/TEB fields */
            IF_WINDOWS(restore_peb_pointer_for_thread(threads[i]->dcontext));
        }
        /* 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.
         */
#ifdef UNIX
        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);
#ifdef UNIX
    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 slave 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);
    DEBUG_DECLARE(exit_res =)
    dynamo_shared_exit(my_tr _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();
}

I wonder if this should be fixed in a separate PR since it is a separate bug.