diff --git a/core/arch/aarchxx/aarchxx.asm b/core/arch/aarchxx/aarchxx.asm index bf9ec5d89bb..7baa82cd348 100644 --- a/core/arch/aarchxx/aarchxx.asm +++ b/core/arch/aarchxx/aarchxx.asm @@ -89,12 +89,12 @@ GLOBAL_LABEL(xfer_to_new_libdr:) # endif /* !STANDALONE_UNIT_TEST && !STATIC_LIBRARY */ #endif /* UNIX */ -/* we need to call futex_wakeall without using any stack, to support - * THREAD_SYNCH_TERMINATED_AND_CLEANED. - * takes int* futex in r0. +/* We need to call futex_wakeall without using any stack. + * Takes KSYNCH_TYPE* in r0 and the post-syscall jump target in r1. */ - DECLARE_FUNC(dynamorio_futex_wake_and_exit) -GLOBAL_LABEL(dynamorio_futex_wake_and_exit:) + DECLARE_FUNC(dynamorio_condvar_wake_and_jmp) +GLOBAL_LABEL(dynamorio_condvar_wake_and_jmp:) + mov REG_R12, REG_R1 /* save across syscall */ mov REG_R5, #0 /* arg6 */ mov REG_R4, #0 /* arg5 */ mov REG_R3, #0 /* arg4 */ @@ -107,7 +107,7 @@ GLOBAL_LABEL(dynamorio_futex_wake_and_exit:) mov r7, #240 /* SYS_futex */ #endif svc #0 - b GLOBAL_REF(dynamorio_sys_exit) - END_FUNC(dynamorio_futex_wake_and_exit) + INDJMP REG_R12 + END_FUNC(dynamorio_condvar_wake_and_jmp) END_FILE diff --git a/core/arch/arch_exports.h b/core/arch/arch_exports.h index f4f2e59e712..affb30325fb 100644 --- a/core/arch/arch_exports.h +++ b/core/arch/arch_exports.h @@ -1146,16 +1146,12 @@ void dynamorio_earliest_init_takeover(void); void client_int_syscall(void); void dynamorio_sigreturn(void); void dynamorio_sys_exit(void); -# ifdef MACOS -void dynamorio_semaphore_signal_all(KSYNCH_TYPE *ksynch/*in xax*/); -# endif +void dynamorio_condvar_wake_and_jmp(KSYNCH_TYPE *ksynch/*in xax/r0*/, + byte *jmp_tgt/*in xcx/r1*/); # ifdef LINUX -void dynamorio_futex_wake_and_exit(volatile int *futex/* in xax*/); # ifndef X64 void dynamorio_nonrt_sigreturn(void); # endif -# endif -# ifdef LINUX thread_id_t dynamorio_clone(uint flags, byte *newsp, void *ptid, void *tls, void *ctid, void (*func)(void)); void xfer_to_new_libdr(app_pc entry, void **init_sp, byte *cur_dr_map, diff --git a/core/arch/x86/x86.asm b/core/arch/x86/x86.asm index 76663c43039..5ef748bcb3c 100644 --- a/core/arch/x86/x86.asm +++ b/core/arch/x86/x86.asm @@ -1299,14 +1299,17 @@ dynamorio_sys_exit_failed: jmp GLOBAL_REF(unexpected_return) END_FUNC(dynamorio_sys_exit) -#ifdef LINUX -/* we need to call futex_wakeall without using any stack, to support - * THREAD_SYNCH_TERMINATED_AND_CLEANED. - * takes int* futex in xax. +#ifdef UNIX +/* We need to signal a futex or semaphore without using our dstack, to support + * THREAD_SYNCH_TERMINATED_AND_CLEANED and detach. + * Takes KSYNCH_TYPE* in xax and the post-syscall jump target in xcx. */ - DECLARE_FUNC(dynamorio_futex_wake_and_exit) -GLOBAL_LABEL(dynamorio_futex_wake_and_exit:) -#ifdef X64 + DECLARE_FUNC(dynamorio_condvar_wake_and_jmp) +GLOBAL_LABEL(dynamorio_condvar_wake_and_jmp:) +# ifdef LINUX + /* We call futex_wakeall */ +# ifdef X64 + mov r12, rcx /* save across syscall */ mov ARG6, 0 mov ARG5, 0 mov ARG4, 0 @@ -1316,7 +1319,10 @@ GLOBAL_LABEL(dynamorio_futex_wake_and_exit:) mov rax, 202 /* SYS_futex */ mov r10, rcx syscall -#else + jmp r12 +# else + /* We use the stack, which should be the app stack: see the MacOS args below. */ + push ecx /* save across syscall */ mov ebp, 0 /* arg6 */ mov edi, 0 /* arg5 */ mov esi, 0 /* arg4 */ @@ -1326,28 +1332,23 @@ GLOBAL_LABEL(dynamorio_futex_wake_and_exit:) mov eax, 240 /* SYS_futex */ /* PR 254280: we assume int$80 is ok even for LOL64 */ int HEX(80) -#endif - jmp GLOBAL_REF(dynamorio_sys_exit) - END_FUNC(dynamorio_futex_wake_and_exit) -#endif /* LINUX */ - -#ifdef MACOS -/* We need to call semaphore_signal_all without using dstack, to support - * THREAD_SYNCH_TERMINATED_AND_CLEANED. We have to put syscall args on - * the stack for 32-bit, and we use the stack for call;pop for - * sysenter -- so we use the app stack, which we assume the caller has - * put us on. We're only called when terminating a thread so transparency - * should be ok so long as the app's stack is valid. - * Takes KSYNCH_TYPE* in xax. - */ - DECLARE_FUNC(dynamorio_semaphore_signal_all) -GLOBAL_LABEL(dynamorio_semaphore_signal_all:) + pop ecx + jmp ecx +# endif +# elif defined(MACOS) + /* We call semaphore_signal_all. We have to put syscall args on + * the stack for 32-bit, and we use the stack for call;pop for + * sysenter -- so we use the app stack, which we assume the caller has + * put us on. We're only called when terminating a thread or detaching + * so transparency should be ok so long as the app's stack is valid. + */ + mov REG_XDI, REG_XCX /* save across syscall */ mov REG_XAX, DWORD [REG_XAX] /* load mach_synch_t->sem */ -# ifdef X64 +# ifdef X64 mov ARG1, REG_XAX mov eax, MACH_semaphore_signal_all_trap or eax, SYSCALL_NUM_MARKER_MACH -# else +# else push REG_XAX mov eax, MACH_semaphore_signal_all_trap neg eax @@ -1362,13 +1363,14 @@ dynamorio_semaphore_next: lea REG_XDX, [1/*pop*/ + 3/*lea*/ + 2/*sysenter*/ + 2/*mov*/ + REG_XDX] mov REG_XCX, REG_XSP sysenter -# ifndef X64 +# ifndef X64 lea esp, [2*ARG_SZ + esp] /* must not change flags */ -# endif +# endif /* we ignore return val */ - jmp GLOBAL_REF(dynamorio_sys_exit) - END_FUNC(dynamorio_semaphore_signal_all) -#endif /* MACOS */ + jmp REG_XDI +# endif /* MACOS */ + END_FUNC(dynamorio_condvar_wake_and_jmp) +#endif /* UNIX */ /* exit entire group without using any stack, in case something like * SYS_kill via cleanup_and_terminate fails. diff --git a/core/dispatch.c b/core/dispatch.c index c5be76dc818..9832fb71b55 100644 --- a/core/dispatch.c +++ b/core/dispatch.c @@ -255,7 +255,8 @@ is_stopping_point(dcontext_t *dcontext, app_pc pc) * should not be called from the cache. */ pc == (app_pc)dynamo_thread_exit || - pc == (app_pc)dr_app_stop)) + pc == (app_pc)dr_app_stop || + pc == (app_pc)dr_app_stop_and_cleanup)) #endif #ifdef WINDOWS /* we go all the way to NtTerminateThread/NtTerminateProcess */ @@ -595,15 +596,20 @@ dispatch_at_stopping_point(dcontext_t *dcontext) LOG(THREAD, LOG_INTERP, 1, "\t==dynamo_thread_exit\n"); else if (dcontext->next_tag == (app_pc)dynamorio_app_exit) LOG(THREAD, LOG_INTERP, 1, "\t==dynamorio_app_exit\n"); - else if (dcontext->next_tag == (app_pc)dr_app_stop) { + else if (dcontext->next_tag == (app_pc)dr_app_stop) LOG(THREAD, LOG_INTERP, 1, "\t==dr_app_stop\n"); - } + else if (dcontext->next_tag == (app_pc)dr_app_stop_and_cleanup) + LOG(THREAD, LOG_INTERP, 1, "\t==dr_app_stop_and_cleanup\n"); # endif # endif /* XXX i#95: should we add an instrument_thread_detach_event()? */ - dynamo_thread_not_under_dynamo(dcontext); +#ifdef DR_APP_EXPORTS + /* not_under will be called by dynamo_shared_exit so skip it here. */ + if (dcontext->next_tag != (app_pc)dr_app_stop_and_cleanup) +#endif + dynamo_thread_not_under_dynamo(dcontext); dcontext->go_native = false; } #endif diff --git a/core/dynamo.c b/core/dynamo.c index f8b194a7d1b..eacec658ca5 100644 --- a/core/dynamo.c +++ b/core/dynamo.c @@ -916,8 +916,8 @@ dynamo_process_exit_with_thread_info(void) /* shared between app_exit and detach */ int -dynamo_shared_exit(IF_WINDOWS_(thread_record_t *toexit) - IF_WINDOWS_ELSE_NP(bool detach_stacked_callbacks, void)) +dynamo_shared_exit(thread_record_t *toexit /* must ==cur thread for Linux */ + _IF_WINDOWS(bool detach_stacked_callbacks)) { DEBUG_DECLARE(uint endtime); /* set this now, could already be set */ @@ -979,14 +979,23 @@ dynamo_shared_exit(IF_WINDOWS_(thread_record_t *toexit) loader_thread_exit(get_thread_private_dcontext()); loader_exit(); -#ifdef WINDOWS if (toexit != NULL) { /* free detaching thread's dcontext */ +#ifdef WINDOWS + /* If we use dynamo_thread_exit() when toexit is the current thread, + * it results in asserts in the win32.tls test, so we stick with this. + */ mutex_lock(&thread_initexit_lock); dynamo_other_thread_exit(toexit, false); mutex_unlock(&thread_initexit_lock); - } +#else + /* On Linux, restoring segment registers can only be done + * on the current thread, which must be toexit. + */ + ASSERT(toexit->id == get_thread_id()); + dynamo_thread_exit(); #endif + } if (IF_WINDOWS_ELSE(!detach_stacked_callbacks, true)) { /* We don't fully free cur thread until after client exit event (PR 536058) */ @@ -1239,8 +1248,8 @@ dynamo_process_exit_cleanup(void) unhook_vsyscall(); #endif /* UNIX */ - return dynamo_shared_exit(IF_WINDOWS_(NULL) /* not detaching */ - IF_WINDOWS(false /* not detaching */)); + return dynamo_shared_exit(NULL /* not detaching */ + _IF_WINDOWS(false /* not detaching */)); } return SUCCESS; } @@ -2604,6 +2613,15 @@ dr_app_stop(void) /* the application regains control in here */ } +DR_APP_API void +dr_app_stop_and_cleanup(void) +{ + if (dynamo_initialized && !dynamo_exited && !doing_detach) { + detach_called_from_app_thread(); + } + /* the application regains control in here */ +} + DR_APP_API int dr_app_setup_and_start(void) { @@ -2614,7 +2632,7 @@ dr_app_setup_and_start(void) } #endif -/* For use by threads that start and stop whether dynamo controls them +/* For use by threads that start and stop whether dynamo controls them. */ void dynamo_thread_under_dynamo(dcontext_t *dcontext) @@ -2637,8 +2655,6 @@ dynamo_thread_under_dynamo(dcontext_t *dcontext) /* For use by threads that start and stop whether dynamo controls them. * This must be called by the owner of dcontext and not another * non-executing thread. - * XXX i#95: for detach we'll need to send a signal and have the - * target thread run this on its own (ditto for os_tls_exit()). */ void dynamo_thread_not_under_dynamo(dcontext_t *dcontext) diff --git a/core/globals.h b/core/globals.h index 73e635bd269..92ce00c58de 100644 --- a/core/globals.h +++ b/core/globals.h @@ -447,6 +447,10 @@ extern bool dynamo_exited_log_and_stats; /* are stats and logfile shut down? */ #endif extern bool dynamo_resetting; /* in middle of global reset? */ extern bool dynamo_all_threads_synched; /* are all other threads suspended safely? */ +/* Not guarded by DR_APP_EXPORTS because later detach implementations might not + * go through the app interface. + */ +extern bool doing_detach; #if defined(CLIENT_INTERFACE) || defined(STANDALONE_UNIT_TEST) extern bool standalone_library; /* used as standalone library */ @@ -557,8 +561,8 @@ void dynamorio_take_over_threads(dcontext_t *dcontext); dr_statistics_t * get_dr_stats(void); /* functions needed by detach */ -int dynamo_shared_exit(IF_WINDOWS_(thread_record_t *toexit) - IF_WINDOWS_ELSE_NP(bool detach_stacked_callbacks, void)); +int dynamo_shared_exit(thread_record_t *toexit + _IF_WINDOWS(bool detach_stacked_callbacks)); /* perform exit tasks that require full thread data structs */ void dynamo_process_exit_with_thread_info(void); /* thread cleanup prior to clean exit event */ diff --git a/core/heap.c b/core/heap.c index bbe8ec8c86a..1de327dd992 100644 --- a/core/heap.c +++ b/core/heap.c @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2010-2015 Google, Inc. All rights reserved. + * Copyright (c) 2010-2016 Google, Inc. All rights reserved. * Copyright (c) 2001-2010 VMware, Inc. All rights reserved. * **********************************************************/ @@ -1405,8 +1405,7 @@ vmm_heap_exit() VMM_BLOCK_SIZE; uint unfreed_blocks = perstack * 1 /* initstack */ + /* current stack */ - perstack * ((IF_WINDOWS_ELSE(doing_detach, false) - IF_APP_EXPORTS(|| dr_api_exit)) ? 0 : 1); + perstack * ((doing_detach IF_APP_EXPORTS(|| dr_api_exit)) ? 0 : 1); /* FIXME: on detach arch_thread_exit should explicitly mark as left behind all TPCs needed so then we can assert even for detach diff --git a/core/lib/dr_app.h b/core/lib/dr_app.h index 30005c2ec5b..ddd0c469e82 100644 --- a/core/lib/dr_app.h +++ b/core/lib/dr_app.h @@ -67,6 +67,7 @@ dr_app_setup(void); /** * Application-wide cleanup. Prints statistics. Returns zero on success. + * Once this is invoked, calling dr_app_start() or dr_app_setup() is not supported. */ DR_APP_API int dr_app_cleanup(void); @@ -109,6 +110,16 @@ dr_app_take_over(void); DR_APP_API int dr_app_setup_and_start(void); +/** + * Causes all of the application's threads to run directly on the machine upon + * return from this call, and additionally frees the resources used by DR. + * Once this is invoked, calling dr_app_start() or dr_app_setup() is not supported. + * This call has no effect if the application is not currently running + * under DR control. + */ +DR_APP_API void +dr_app_stop_and_cleanup(void); + /** * Indicates whether the current thread is running within the DynamoRIO code * cache. Returns \p true only if the current thread is running within the diff --git a/core/optionsx.h b/core/optionsx.h index a4e6060ea40..c17edceb15e 100644 --- a/core/optionsx.h +++ b/core/optionsx.h @@ -1920,14 +1920,14 @@ IF_RCT_IND_BRANCH(options->rct_ind_jump = OPTION_DISABLED;) OPTION_DEFAULT(bool, track_module_filenames, true, "track module file names by watching section creation") +#endif - /* FIXME: since we have dynamic options this option can be false for most of the time, + /* XXX: since we have dynamic options this option can be false for most of the time, * and the gui should set true only when going to detach to prevent a security risk. * The setting should be removed when detach is complete. * In vault mode: -no_allow_detach -no_dynamic_options */ DYNAMIC_OPTION_DEFAULT(bool, allow_detach, true, "allow detaching from process") -#endif /* turn off critical features, right now for experimentation only */ #ifdef WINDOWS diff --git a/core/rct.c b/core/rct.c index f3149949031..760891f3365 100644 --- a/core/rct.c +++ b/core/rct.c @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2012 Google, Inc. All rights reserved. + * Copyright (c) 2012-2016 Google, Inc. All rights reserved. * Copyright (c) 2004-2009 VMware, Inc. All rights reserved. * **********************************************************/ @@ -320,6 +320,7 @@ rct_ind_branch_check(dcontext_t *dcontext, app_pc target_addr, app_pc src_addr) if (target_addr == (app_pc) dr_app_start || target_addr == (app_pc) dr_app_take_over || target_addr == (app_pc) dr_app_stop || + target_addr == (app_pc) dr_app_stop_and_cleanup || target_addr == (app_pc) dr_app_cleanup) goto good; #endif diff --git a/core/synch.c b/core/synch.c index 11e078d212a..43d199c076e 100644 --- a/core/synch.c +++ b/core/synch.c @@ -1132,7 +1132,7 @@ synch_with_all_threads(thread_synch_state_t desired_synch_state, */ ASSERT_CURIOSITY(desired_synch_state < THREAD_SYNCH_SUSPENDED_VALID_MCONTEXT /* detach currently violates this: bug 8942 */ - IF_WINDOWS(|| doing_detach)); + || doing_detach); /* must set exactly one of these -- FIXME: better way to check? */ ASSERT(TESTANY(THREAD_SYNCH_SUSPEND_FAILURE_ABORT | @@ -1713,6 +1713,13 @@ translate_from_synchall_to_dispatch(thread_record_t *tr, thread_synch_state_t sy * Detach and similar operations */ +bool doing_detach = false; + +/* 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) { @@ -1778,8 +1785,8 @@ send_all_other_threads_native(void) for (i = 0; i < num_threads; i++) { if (threads[i]->dcontext == my_dcontext || - is_thread_currently_native(threads[i]) - IF_CLIENT_INTERFACE(|| IS_CLIENT_THREAD(threads[i]->dcontext))) + is_thread_currently_native(threads[i]) || + IS_CLIENT_THREAD(threads[i]->dcontext)) continue; /* Because dynamo_thread_not_under_dynamo() has to be run by the owning @@ -1803,11 +1810,11 @@ send_all_other_threads_native(void) /* This won't change a thread at a syscall, so we rely on the thread * going to dispatch and then going native when its syscall exits. * - * FIXME i#95: this means that dr_app_cleanup() needs to synch the - * threads and force-xl8 these if there's a way to do that (if so we - * should prob just do that here), or leave behind a tombstone w/ - * segment-cleanup code, or what we do now: we rely on the app - * joining all its threads *before* calling dr_app_cleanup(). + * 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(). */ translate_from_synchall_to_dispatch(threads[i], desired_state); } @@ -1819,3 +1826,177 @@ send_all_other_threads_native(void) enter_couldbelinking(my_dcontext, NULL, false); return; } + +void +detach_called_from_app_thread(void) +{ +#ifdef WINDOWS + /* FIXME i#95: this duplicates a lot of Windows detach_helper() code. + * The two routines will be merged in the future. + */ + ASSERT_NOT_IMPLEMENTED(false && "i#95"); +#else + 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; + DEBUG_DECLARE(bool ok;) + + 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; + } + + /* we'll need to unprotect for exit cleanup + * XXX: more secure to not do this until we've synched, but then need + * alternative prot for doing_detach and init_apc_go_native* + */ + SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT); + + ASSERT(!doing_detach); + doing_detach = true; + + synchronize_dynamic_options(); + if (!DYNAMO_OPTION(allow_detach)) { + doing_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 = get_thread_id(); + my_dcontext = get_thread_private_dcontext(); + + LOG(GLOBAL, LOG_ALL, 1, "Detach: thread %d starting\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); + + /* suspend all dynamo controlled threads at safe locations */ + DEBUG_DECLARE(ok =) + synch_with_all_threads(THREAD_SYNCH_SUSPENDED_VALID_MCONTEXT, + &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. + */ + &num_threads, THREAD_SYNCH_NO_LOCKS_NO_XFER, + /* if we fail to suspend a thread (e.g., + * privilege problems) ignore it. FIXME: retry + * instead? + */ + THREAD_SYNCH_SUSPEND_FAILURE_IGNORE); + ASSERT(ok); + /* now we own the thread_initexit_lock */ + /* NOTE : we will release the locks grabbed in synch_with_all_threads + * below after cleaning up all the threads in case we will need to grab + * it during process exit cleanup + */ + ASSERT(mutex_testlock(&all_threads_synch_lock) && + mutex_testlock(&thread_initexit_lock)); + +#ifdef HOT_PATCHING_INTERFACE + /* In hotp_only mode, we must remove patches when detaching; we don't want + * to leave in all our hooks and detach; that will definitely crash the app. + */ + if (DYNAMO_OPTION(hotp_only)) + hotp_only_detach_helper(); +#endif + + if (!DYNAMO_OPTION(thin_client)) + revert_memory_regions(); +#ifdef UNIX + unhook_vsyscall(); +#endif + + /* perform exit tasks that require full thread data structs */ + /* FIXME i#95: Call dynamo_process_exit_with_thread_info()??? */ + + 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]; + } else if (IS_CLIENT_THREAD(threads[i]->dcontext)) { + os_thread_resume(threads[i]); + } else { + DEBUG_DECLARE(ok =) + thread_get_mcontext(threads[i], &mc); + ASSERT(ok); + /* FIXME i#95: this will xl8 to a post-syscall point for a thread at + * a syscall, and we rely on the app itself to retry a syscall interrupted + * by our suspend signal. This is not good enough, as this is an + * artifical signal that the app has not planned for with SA_RESTART or + * a loop. We want something like adjust_syscall_for_restart(). + * Xref i#1145. + */ + DEBUG_DECLARE(ok =) + translate_mcontext(threads[i], &mc, true/*restore mem*/, NULL/*f*/); + ASSERT(ok); + DEBUG_DECLARE(ok =) + thread_set_mcontext(threads[i], &mc); + ASSERT(ok); + + /* 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. + */ + os_thread_resume(threads[i]); + } + } + + ASSERT(my_idx != -1); + + LOG(GLOBAL, LOG_ALL, 1, "Detach: Starting to wait on slave threads\n"); + for (i = 0; i < num_threads; i++) { + if (i != my_idx) + os_wait_thread_detached(threads[i]->dcontext); + } + + /* 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) + dynamo_other_thread_exit(threads[i]); + } + +#ifdef DEBUG + if (my_idx != -1) { + /* pre-client thread cleanup (PR 536058) */ + dynamo_thread_exit_pre_client(my_dcontext, my_tr->id); + } +#endif + + LOG(GLOBAL, LOG_ALL, 1, "Detach: Letting slave threads go native\n"); + end_synch_with_all_threads(threads, num_threads, false/*don't resume */); + threads = NULL; + + dynamo_shared_exit(my_tr); + + stack_free(initstack, DYNAMORIO_STACK_SIZE); + + doing_detach = false; + SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT); + dynamo_detaching_flag = LOCK_FREE_STATE; + EXITING_DR(); +#endif /* !WINDOWS */ +} diff --git a/core/synch.h b/core/synch.h index 96cc443aa77..a061840ecce 100644 --- a/core/synch.h +++ b/core/synch.h @@ -255,6 +255,9 @@ translate_from_synchall_to_dispatch(thread_record_t *tr, void send_all_other_threads_native(void); +void +detach_called_from_app_thread(void); + /*** exported for detach only ***/ bool diff --git a/core/unix/os.c b/core/unix/os.c index fab3722e94d..b4fa0bdb3a2 100644 --- a/core/unix/os.c +++ b/core/unix/os.c @@ -1838,6 +1838,33 @@ os_tls_init(void) ASSERT(is_thread_tls_initialized()); } +/* TLS exit for the current thread who must own local_state. */ +void +os_tls_thread_exit(local_state_t *local_state) +{ +#ifdef HAVE_TLS + /* We assume (assert below) that local_state_t's start == local_state_extended_t */ + os_local_state_t *os_tls = (os_local_state_t *) + (((byte*)local_state) - offsetof(os_local_state_t, state)); + tls_type_t tls_type = os_tls->tls_type; + int index = os_tls->ldt_index; + ASSERT(offsetof(local_state_t, spill_space) == + offsetof(local_state_extended_t, spill_space)); + + tls_thread_free(tls_type, index); + +# ifdef X64 + if (tls_type == TLS_TYPE_ARCH_PRCTL) { + /* syscall re-sets gs register so re-clear it */ + if (read_thread_register(SEG_TLS) != 0) { + static const ptr_uint_t zero = 0; + WRITE_DR_SEG(zero); /* macro needs lvalue! */ + } + } +# endif +#endif +} + /* Frees local_state. If the calling thread is exiting (i.e., * !other_thread) then also frees kernel resources for the calling * thread; if other_thread then that may not be possible. @@ -1853,9 +1880,6 @@ os_tls_exit(local_state_t *local_state, bool other_thread) /* ASSUMPTION: local_state_t is laid out at same start as local_state_extended_t */ os_local_state_t *os_tls = (os_local_state_t *) (((byte*)local_state) - offsetof(os_local_state_t, state)); - tls_type_t tls_type = os_tls->tls_type; - int index = os_tls->ldt_index; - # ifdef X86 /* If the MSR is in use, writing to the reg faults. We rely on it being 0 * to indicate that. @@ -1867,20 +1891,10 @@ os_tls_exit(local_state_t *local_state, bool other_thread) /* For another thread we can't really make these syscalls so we have to * leave it un-cleaned-up. That's fine if the other thread is exiting: - * but if we have a detach feature (i#95) we'll have to get the other - * thread to run this code. + * but for detach (i#95) we get the other thread to run this code. */ - if (!other_thread) { - tls_thread_free(tls_type, index); -# if defined(X86) && defined(X64) - if (tls_type == TLS_TYPE_ARCH_PRCTL) { - /* syscall re-sets gs register so re-clear it */ - if (read_thread_register(SEG_TLS) != 0) { - WRITE_DR_SEG(zero); /* macro needs lvalue! */ - } - } -# endif - } + if (!other_thread) + os_tls_thread_exit(local_state); /* We can't free prior to tls_thread_free() in case that routine refs os_tls */ heap_munmap(os_tls->self, PAGE_SIZE); @@ -1993,6 +2007,7 @@ os_thread_init(dcontext_t *dcontext) ksynch_init_var(&ostd->wakeup); ksynch_init_var(&ostd->resumed); ksynch_init_var(&ostd->terminated); + ksynch_init_var(&ostd->detached); #ifdef RETURN_AFTER_CALL /* We only need the stack bottom for the initial thread, and due to thread @@ -2062,6 +2077,7 @@ os_thread_exit(dcontext_t *dcontext, bool other_thread) ksynch_free_var(&ostd->wakeup); ksynch_free_var(&ostd->resumed); ksynch_free_var(&ostd->terminated); + ksynch_free_var(&ostd->detached); /* for non-debug we do fast exit path and don't free local heap */ DODEBUG({ @@ -3187,23 +3203,37 @@ is_thread_terminated(dcontext_t *dcontext) return (ksynch_get_value(&ostd->terminated) == 1); } -void -os_wait_thread_terminated(dcontext_t *dcontext) +static void +os_wait_thread_futex(KSYNCH_TYPE *var) { - os_thread_data_t *ostd = (os_thread_data_t *) dcontext->os_field; - ASSERT(ostd != NULL); - while (ksynch_get_value(&ostd->terminated) == 0) { - /* On Linux, waits only if the terminated flag is not set as 1. Return value - * doesn't matter because the flag will be re-checked. + while (ksynch_get_value(var) == 0) { + /* On Linux, waits only if var is not set as 1. Return value + * doesn't matter because var will be re-checked. */ - ksynch_wait(&ostd->terminated, 0); - if (ksynch_get_value(&ostd->terminated) == 0) { + ksynch_wait(var, 0); + if (ksynch_get_value(var) == 0) { /* If it still has to wait, give up the cpu. */ os_thread_yield(); } } } +void +os_wait_thread_terminated(dcontext_t *dcontext) +{ + os_thread_data_t *ostd = (os_thread_data_t *) dcontext->os_field; + ASSERT(ostd != NULL); + os_wait_thread_futex(&ostd->terminated); +} + +void +os_wait_thread_detached(dcontext_t *dcontext) +{ + os_thread_data_t *ostd = (os_thread_data_t *) dcontext->os_field; + ASSERT(ostd != NULL); + os_wait_thread_futex(&ostd->detached); +} + bool thread_get_mcontext(thread_record_t *tr, priv_mcontext_t *mc) { diff --git a/core/unix/os_exports.h b/core/unix/os_exports.h index 2eb1632ab78..e70844511e9 100644 --- a/core/unix/os_exports.h +++ b/core/unix/os_exports.h @@ -159,6 +159,7 @@ thread_id_t get_tls_thread_id(void); thread_id_t get_sys_thread_id(void); bool is_thread_terminated(dcontext_t *dcontext); void os_wait_thread_terminated(dcontext_t *dcontext); +void os_wait_thread_detached(dcontext_t *dcontext); void os_tls_pre_init(int gdt_index); /* XXX: reg_id_t is not defined here, use ushort instead */ ushort os_get_app_tls_base_offset(ushort/*reg_id_t*/ seg); diff --git a/core/unix/os_private.h b/core/unix/os_private.h index 7bd8b053245..d749fa52982 100644 --- a/core/unix/os_private.h +++ b/core/unix/os_private.h @@ -50,11 +50,14 @@ #ifdef X86 # ifdef X64 # define ASM_XAX "rax" +# define ASM_XCX "rcx" # define ASM_XDX "rdx" # define ASM_XBP "rbp" # define ASM_XSP "rsp" +# define ASM_XSP "rsp" # else # define ASM_XAX "eax" +# define ASM_XCX "ecx" # define ASM_XDX "edx" # define ASM_XBP "ebp" # define ASM_XSP "esp" @@ -63,10 +66,13 @@ # define ASM_R0 "x0" # define ASM_R1 "x1" # define ASM_XSP "sp" +# define ASM_XSP "sp" +# define ASM_INDJMP "br" #elif defined(ARM) # define ASM_R0 "r0" # define ASM_R1 "r1" # define ASM_XSP "sp" +# define ASM_INDJMP "bx" #endif /* X86/ARM */ #define MACHINE_TLS_IS_DR_TLS IF_X86_ELSE(INTERNAL_OPTION(mangle_app_seg), true) @@ -152,6 +158,8 @@ typedef struct _os_thread_data_t { */ KSYNCH_TYPE terminated; + KSYNCH_TYPE detached; + volatile bool retakeover; /* for re-attach */ /* PR 450670: for re-entrant suspend signals */ @@ -183,6 +191,9 @@ void os_thread_take_over(priv_mcontext_t *mc); void *os_get_priv_tls_base(dcontext_t *dcontext, reg_id_t seg); +void +os_tls_thread_exit(local_state_t *local_state); + #ifdef AARCHXX bool os_set_app_tls_base(dcontext_t *dcontext, reg_id_t reg, void *base); diff --git a/core/unix/signal.c b/core/unix/signal.c index edd7ac2fed6..c799ccdac4e 100644 --- a/core/unix/signal.c +++ b/core/unix/signal.c @@ -275,7 +275,8 @@ static bool handle_alarm(dcontext_t *dcontext, int sig, kernel_ucontext_t *ucxt); static bool -handle_suspend_signal(dcontext_t *dcontext, kernel_ucontext_t *ucxt); +handle_suspend_signal(dcontext_t *dcontext, kernel_ucontext_t *ucxt, + sigframe_rt_t *frame); static bool handle_nudge_signal(dcontext_t *dcontext, siginfo_t *siginfo, kernel_ucontext_t *ucxt); @@ -1221,36 +1222,38 @@ signal_thread_exit(dcontext_t *dcontext, bool other_thread) } #ifdef HAVE_SIGALTSTACK /* Remove our sigstack and restore the app sigstack if it had one. */ - LOG(THREAD, LOG_ASYNCH, 2, "removing our signal stack "PFX" - "PFX"\n", - info->sigstack.ss_sp, info->sigstack.ss_sp + info->sigstack.ss_size); - if (APP_HAS_SIGSTACK(info)) { - LOG(THREAD, LOG_ASYNCH, 2, "restoring app signal stack "PFX" - "PFX"\n", - info->app_sigstack.ss_sp, - info->app_sigstack.ss_sp + info->app_sigstack.ss_size); - } else { - ASSERT(TEST(SS_DISABLE, info->app_sigstack.ss_flags)); - } - if (info->sigstack.ss_sp != NULL) { - /* i#552: to raise client exit event, we may call dynamo_process_exit - * on sigstack in signal handler. - * In that case we set sigstack (ss_sp) NULL to avoid stack swap. - */ -# ifdef MACOS - if (info->app_sigstack.ss_sp == NULL) { - /* Kernel fails with ENOMEM (even for SS_DISABLE) if ss_size is too small */ - info->sigstack.ss_flags = SS_DISABLE; - i = sigaltstack_syscall(&info->sigstack, NULL); - /* i#1814: kernel gives EINVAL if last handler didn't call sigreturn! */ - ASSERT(i == 0 || i == -EINVAL); + if (!other_thread) { + LOG(THREAD, LOG_ASYNCH, 2, "removing our signal stack "PFX" - "PFX"\n", + info->sigstack.ss_sp, info->sigstack.ss_sp + info->sigstack.ss_size); + if (APP_HAS_SIGSTACK(info)) { + LOG(THREAD, LOG_ASYNCH, 2, "restoring app signal stack "PFX" - "PFX"\n", + info->app_sigstack.ss_sp, + info->app_sigstack.ss_sp + info->app_sigstack.ss_size); } else { - i = sigaltstack_syscall(&info->app_sigstack, NULL); - /* i#1814: kernel gives EINVAL if last handler didn't call sigreturn! */ - ASSERT(i == 0 || i == -EINVAL); + ASSERT(TEST(SS_DISABLE, info->app_sigstack.ss_flags)); } + if (info->sigstack.ss_sp != NULL) { + /* i#552: to raise client exit event, we may call dynamo_process_exit + * on sigstack in signal handler. + * In that case we set sigstack (ss_sp) NULL to avoid stack swap. + */ +# ifdef MACOS + if (info->app_sigstack.ss_sp == NULL) { + /* Kernel fails w/ ENOMEM (even for SS_DISABLE) if ss_size is too small */ + info->sigstack.ss_flags = SS_DISABLE; + i = sigaltstack_syscall(&info->sigstack, NULL); + /* i#1814: kernel gives EINVAL if last handler didn't call sigreturn! */ + ASSERT(i == 0 || i == -EINVAL); + } else { + i = sigaltstack_syscall(&info->app_sigstack, NULL); + /* i#1814: kernel gives EINVAL if last handler didn't call sigreturn! */ + ASSERT(i == 0 || i == -EINVAL); + } # else - i = sigaltstack_syscall(&info->app_sigstack, NULL); - ASSERT(i == 0); + i = sigaltstack_syscall(&info->app_sigstack, NULL); + ASSERT(i == 0); # endif + } } #endif IF_LINUX(signalfd_thread_exit(dcontext, info)); @@ -2518,7 +2521,7 @@ fixup_rtframe_pointers(dcontext_t *dcontext, int sig, f_new->pretcode = (char *) dynamorio_sigreturn; # else # ifdef X64 - ASSERT(!for_app); + ASSERT(!for_app || doing_detach); /* detach uses a frame to go native */ # else /* only point at retcode if old one was -- with newer OS, points at * vsyscall page and there is no restorer, yet stack restorer code left @@ -4394,7 +4397,7 @@ master_signal_handler_C(byte *xsp) /* PR 212090: the signal we use to suspend threads */ case SUSPEND_SIGNAL: - if (handle_suspend_signal(dcontext, ucxt)) + if (handle_suspend_signal(dcontext, ucxt, frame)) record_pending_signal(dcontext, sig, ucxt, frame, false _IF_CLIENT(NULL)); /* else, don't deliver to app */ break; @@ -6067,9 +6070,137 @@ handle_post_alarm(dcontext_t *dcontext, bool success, unsigned int sec) return; } +/*************************************************************************** + * Internal DR communication + */ + +typedef struct _sig_detach_info_t { + KSYNCH_TYPE *detached; + byte *sigframe_xsp; +#ifdef HAVE_SIGALTSTACK + stack_t *app_sigstack; +#endif +} sig_detach_info_t; + +/* xsp is only set for X86 */ +static void +notify_and_jmp_without_stack(KSYNCH_TYPE *notify_var, byte *continuation, byte *xsp) +{ + if (ksynch_kernel_support()) { + /* Can't use dstack once we signal so in asm we do: + * futex/semaphore = 1; + * %xsp = xsp; + * dynamorio_condvar_wake_and_jmp(notify_var, continuation); + */ +#ifdef MACOS + ASSERT(sizeof(notify_var->sem) == 4); +#endif +#ifdef X86 + asm("mov %0, %%"ASM_XAX : : "m"(notify_var)); + asm("mov %0, %%"ASM_XCX : : "m"(continuation)); + asm("mov %0, %%"ASM_XSP : : "m"(xsp)); +# ifdef MACOS + asm("movl $1,4(%"ASM_XAX")"); + asm("jmp _dynamorio_condvar_wake_and_jmp"); +# else + asm("movl $1,(%"ASM_XAX")"); + asm("jmp dynamorio_condvar_wake_and_jmp"); +# endif +#elif defined(AARCHXX) + asm("ldr "ASM_R0", %0" : : "m"(notify_var)); + asm("mov "ASM_R1", #1"); + asm("str "ASM_R1",["ASM_R0"]"); + asm("ldr "ASM_R1", %0" : : "m"(continuation)); + asm("b dynamorio_condvar_wake_and_jmp"); +#endif + } else { + ksynch_set_value(notify_var, 1); +#ifdef X86 + asm("mov %0, %%"ASM_XSP : : "m"(xsp)); + asm("mov %0, %%"ASM_XAX : : "m"(continuation)); + asm("jmp *%"ASM_XAX); +#elif defined(AARCHXX) + asm("ldr "ASM_R0", %0" : : "m"(continuation)); + asm(ASM_INDJMP" "ASM_R0); +#endif /* X86/ARM */ + } +} + +/* Go native from detach. This is executed on the app stack. */ +static void +sig_detach_go_native(sig_detach_info_t *info) +{ + byte *xsp = info->sigframe_xsp; + +#ifdef HAVE_SIGALTSTACK + /* Restore the app signal stack. */ + DEBUG_DECLARE(int rc =) + sigaltstack_syscall(info->app_sigstack, NULL); + ASSERT(rc == 0); +#endif + +#ifdef X86 + /* Skip pretcode */ + xsp += sizeof(char *); +#endif + notify_and_jmp_without_stack(info->detached, (byte *)dynamorio_sigreturn, xsp); + + ASSERT_NOT_REACHED(); +} + +/* Sets this (slave) thread to detach by directly returning from the signal. */ +static void +sig_detach(dcontext_t *dcontext, sigframe_rt_t *frame, KSYNCH_TYPE *detached) +{ + thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; + byte *xsp; + sig_detach_info_t detach_info; + + LOG(THREAD, LOG_ASYNCH, 1, "%s: detaching\n", __FUNCTION__); + + /* Update the mask of the signal frame so that the later sigreturn will + * restore the app signal mask. + */ + memcpy(&frame->uc.uc_sigmask, &info->app_sigblocked, + sizeof(info->app_sigblocked)); + + /* Copy the signal frame to the app stack. + * XXX: We live with the transparency risk of storing the signal frame on + * the app stack: we assume the app stack is writable where we need it to be, + * and that we're not clobbering any app data beyond TOS. + */ + xsp = get_sigstack_frame_ptr(dcontext, SUSPEND_SIGNAL, frame); + copy_frame_to_stack(dcontext, SUSPEND_SIGNAL, frame, xsp, false/*!pending*/); + +#ifdef HAVE_SIGALTSTACK + /* Make sure the frame's sigstack reflects the app stack. */ + frame = (sigframe_rt_t *) xsp; + frame->uc.uc_stack = info->app_sigstack; +#endif + + /* Restore app segment registers. */ + os_thread_not_under_dynamo(dcontext); + os_tls_thread_exit(dcontext->local_state); + +#ifdef HAVE_SIGALTSTACK + /* We can't restore the app's sigstack here as that will invalidate the + * sigstack we're currently on. + */ + detach_info.app_sigstack = &info->app_sigstack; +#endif + detach_info.detached = detached; + detach_info.sigframe_xsp = xsp; + + call_switch_stack(&detach_info, xsp, (void(*)(void*))sig_detach_go_native, + false/*free_initstack*/, false/*do not return*/); + + ASSERT_NOT_REACHED(); +} + /* Returns whether to pass on to app */ static bool -handle_suspend_signal(dcontext_t *dcontext, kernel_ucontext_t *ucxt) +handle_suspend_signal(dcontext_t *dcontext, kernel_ucontext_t *ucxt, + sigframe_rt_t *frame) { os_thread_data_t *ostd = (os_thread_data_t *) dcontext->os_field; kernel_sigset_t prevmask; @@ -6077,63 +6208,28 @@ handle_suspend_signal(dcontext_t *dcontext, kernel_ucontext_t *ucxt) ASSERT(ostd != NULL); if (ostd->terminate) { - /* PR 297902: exit this thread, without using any stack */ -#ifdef MACOS - /* We need a stack as 32-bit syscalls take args on the stack. - * We go ahead and use it for x64 too for simpler sysenter return. + /* PR 297902: exit this thread, without using the dstack */ + /* For MacOS, We need a stack as 32-bit syscalls take args on the stack. + * We go ahead and use it for x86 too for simpler sysenter return. * We don't have a lot of options: we're terminating, so we go ahead * and use the app stack. */ - byte *app_xsp = (byte *) get_mcontext(dcontext)->xsp; -#endif + byte *app_xsp; + if (IS_CLIENT_THREAD(dcontext)) + app_xsp = (byte *) SIGCXT_FROM_UCXT(ucxt)->SC_XSP; + else + app_xsp = (byte *) get_mcontext(dcontext)->xsp; LOG(THREAD, LOG_ASYNCH, 2, "handle_suspend_signal: exiting\n"); - if (ksynch_kernel_support()) { - /* can't use stack once set terminated to 1 so in asm we do: - * ostd->terminated = 1; - * futex_wake_all(&ostd->terminated in xax); - * semaphore_signal_all(&ostd->terminated in xax); - */ -#ifdef MACOS - KSYNCH_TYPE *term = &ostd->terminated; - ASSERT(sizeof(ostd->terminated.sem) == 4); -#else - volatile int *term = &ostd->terminated; -#endif -#ifdef X86 - asm("mov %0, %%"ASM_XAX : : "m"(term)); -# ifdef MACOS - asm("movl $1,4(%"ASM_XAX")"); - asm("mov %0, %%"ASM_XSP : : "m"(app_xsp)); - asm("jmp _dynamorio_semaphore_signal_all"); -# else - asm("movl $1,(%"ASM_XAX")"); - asm("jmp dynamorio_futex_wake_and_exit"); -# endif -#elif defined(AARCHXX) - asm("ldr "ASM_R0", %0" : : "m"(term)); - asm("mov "ASM_R1", #1"); - asm("str "ASM_R1",["ASM_R0"]"); - asm("b dynamorio_futex_wake_and_exit"); -#endif - } else { - ksynch_set_value(&ostd->terminated, 1); -#ifdef X86 -# ifdef MACOS - asm("mov %0, %%"ASM_XSP : : "m"(app_xsp)); - asm("jmp _dynamorio_sys_exit"); -# else - asm("jmp dynamorio_sys_exit"); -# endif -#elif defined(ARM) - asm("b dynamorio_sys_exit"); -#endif /* X86/ARM */ - } + ASSERT(app_xsp != NULL); + notify_and_jmp_without_stack(&ostd->terminated, (byte*)dynamorio_sys_exit, + app_xsp); ASSERT_NOT_REACHED(); return false; } - if (is_thread_currently_native(dcontext->thread_record) - IF_CLIENT_INTERFACE(&& !IS_CLIENT_THREAD(dcontext))) { + if (!doing_detach && + is_thread_currently_native(dcontext->thread_record) && + !IS_CLIENT_THREAD(dcontext)) { sig_take_over(ucxt); /* no return */ ASSERT_NOT_REACHED(); } @@ -6199,7 +6295,7 @@ handle_suspend_signal(dcontext_t *dcontext, kernel_ucontext_t *ucxt) /* Notify os_thread_resume that it can return now, which (assuming * suspend_count is back to 0) means it's then safe to re-suspend. */ - ksynch_set_value(&ostd->suspended, 0); /* reset prior to signalling os_thread_resume */ + ksynch_set_value(&ostd->suspended, 0); /*reset prior to signalling os_thread_resume*/ ksynch_set_value(&ostd->resumed, 1); ksynch_wake_all(&ostd->resumed); @@ -6207,7 +6303,10 @@ handle_suspend_signal(dcontext_t *dcontext, kernel_ucontext_t *ucxt) ostd->retakeover = false; sig_take_over(ucxt); /* no return */ ASSERT_NOT_REACHED(); - } + } else if (doing_detach) { + sig_detach(dcontext, frame, &ostd->detached); /* no return */ + ASSERT_NOT_REACHED(); + } return false; /* do not pass to app */ } diff --git a/core/win32/os.c b/core/win32/os.c index 721f0994aeb..a7c61b99f54 100644 --- a/core/win32/os.c +++ b/core/win32/os.c @@ -7729,9 +7729,6 @@ END_DATA_SECTION() /***************************************************************************/ /* detaching routines */ -/* not static only for a few asserts in other files */ -bool doing_detach = false; - static bool internal_detach = false; /* Handle any outstanding callbacks. @@ -7881,6 +7878,7 @@ detach_helper_handle_callbacks(int num_threads, thread_record_t **threads, void detach_helper(int detach_type) { + /* FIXME i#95: merge with detach_called_from_app_thread() */ thread_record_t **threads; thread_record_t *toexit; dcontext_t *my_dcontext = get_thread_private_dcontext(); diff --git a/core/win32/os_exports.h b/core/win32/os_exports.h index a9f716cbe08..6323ab4b79e 100644 --- a/core/win32/os_exports.h +++ b/core/win32/os_exports.h @@ -399,7 +399,6 @@ enum { DETACH_BAD_STATE_NO_CLEANUP = -2, }; void detach_helper(int detach_type); /* needs to be exported for nudge.c */ -extern bool doing_detach; void early_inject_init(void); bool earliest_inject_init(byte *arg_ptr); diff --git a/suite/tests/CMakeLists.txt b/suite/tests/CMakeLists.txt index 3d8087e89d1..0461943f333 100644 --- a/suite/tests/CMakeLists.txt +++ b/suite/tests/CMakeLists.txt @@ -310,6 +310,7 @@ if (UNIX) # locate libm or libdl within the Android toolchain: we just assume they're there. set(libmath m) set(libdl dl) + set(libpthread "") else () # i#720: cmake fails to find 32-bit libraries in Ubuntu 11.10. # This is because cmake uses CMAKE_LIBRARY_ARCHITECTURE to handle @@ -584,7 +585,7 @@ function(add_exe test source) target_link_libraries(${test} ${libmath} ${libdl}) endif () if (NOT ANDROID) # pthreads is inside Bionic on Android - if ("${test}" MATCHES "^pthread|api.startstop") + if ("${test}" MATCHES "^pthread") target_link_libraries(${test} ${libpthread}) endif () endif () @@ -2177,6 +2178,15 @@ if (CLIENT_INTERFACE) if (NOT ARM) # FIXME i#1551: fix bugs on ARM tobuild_api(api.startstop api/startstop.c "" "" OFF OFF) + if (NOT ANDROID) # pthreads is inside Bionic on Android + target_link_libraries(api.startstop ${libpthread}) + endif () + if (UNIX) # FIXME i#95: impl for Windows + tobuild_api(api.detach api/detach.c "" "" OFF OFF) + if (NOT ANDROID) # pthreads is inside Bionic on Android + target_link_libraries(api.detach ${libpthread}) + endif () + endif () endif (NOT ARM) if (X86) # FIXME i#1551, i#1569: port to ARM and AArch64 # test static decoder library @@ -2218,6 +2228,10 @@ if (CLIENT_INTERFACE) target_link_libraries(api.static_noclient ${libmath}) tobuild_api(api.static_noinit api/static_noinit.c "" "" OFF ON) target_link_libraries(api.static_noinit ${libmath}) + if (UNIX) # FIXME i#95: impl for Windows + tobuild_api(api.static_detach api/static_detach.c "" "" OFF ON) + target_link_libraries(api.static_detach ${libmath}) + endif () endif () if (NOT X64 AND NOT ARM) # FIXME i#1551: port to ARM diff --git a/suite/tests/api/detach.c b/suite/tests/api/detach.c new file mode 100644 index 00000000000..8409382921c --- /dev/null +++ b/suite/tests/api/detach.c @@ -0,0 +1,243 @@ +/* ********************************************************** + * Copyright (c) 2011-2016 Google, Inc. All rights reserved. + * Copyright (c) 2003-2008 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. + */ + +#include +#include +#include +#include +#include "configure.h" +#include "dr_api.h" +#include "tools.h" +#include "condvar.h" +#ifdef WINDOWS +# include +#else +# include +#endif + +#define VERBOSE 0 + +#define NUM_THREADS 10 +#define START_STOP_ITERS 10 +#define COMPUTE_ITERS 150000 + +#if VERBOSE +# define VPRINT(...) print(__VA_ARGS__) +#else +# define VPRINT(...) /* nothing */ +#endif + +/* We have event bb look for this to make sure we're instrumenting the sideline + * thread. + */ +/* We could generate this via macros but that gets pretty obtuse */ +NOINLINE void func_0(void) { } +NOINLINE void func_1(void) { } +NOINLINE void func_2(void) { } +NOINLINE void func_3(void) { } +NOINLINE void func_4(void) { } +NOINLINE void func_5(void) { } +NOINLINE void func_6(void) { } +NOINLINE void func_7(void) { } +NOINLINE void func_8(void) { } +NOINLINE void func_9(void) { } + +typedef void (*void_func_t)(void); +static bool took_over_thread[NUM_THREADS]; +static void_func_t funcs[NUM_THREADS]; + +static dr_emit_flags_t +event_bb(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, + bool translating) +{ + int i; + app_pc pc = instr_get_app_pc(instrlist_first(bb)); + for (i = 0; i < NUM_THREADS; i++) { + if (pc == (app_pc)funcs[i]) + took_over_thread[i] = true; + } + return DR_EMIT_DEFAULT; +} + +static volatile bool sideline_exit = false; +static void *sideline_continue; +static void *go_native; +static void *sideline_ready[NUM_THREADS]; + +#ifdef WINDOWS +int __stdcall +#else +void * +#endif +sideline_spinner(void *arg) +{ + unsigned int idx = (unsigned int)(uintptr_t)arg; + void_func_t sideline_func = funcs[idx]; + if (dr_app_running_under_dynamorio()) + print("ERROR: thread %d should NOT be under DynamoRIO\n", idx); + VPRINT("%d signaling sideline_ready\n", idx); + signal_cond_var(sideline_ready[idx]); + + VPRINT("%d waiting for continue\n", idx); + wait_cond_var(sideline_continue); + if (!dr_app_running_under_dynamorio()) + print("ERROR: thread %d should be under DynamoRIO\n", idx); + sideline_func(); + VPRINT("%d signaling sideline_ready\n", idx); + signal_cond_var(sideline_ready[idx]); + + /* XXX: ideally we'd have a better test that our state after the detach is + * not perturbed at all, though if the PC is correct that's generally half + * the battle. + */ + + VPRINT("%d waiting for native\n", idx); + wait_cond_var(go_native); + if (dr_app_running_under_dynamorio()) + print("ERROR: thread %d should NOT be under DynamoRIO\n", idx); + VPRINT("%d signaling sideline_ready\n", idx); + signal_cond_var(sideline_ready[idx]); + VPRINT("%d exiting\n", idx); + +#ifdef WINDOWS + return 0; +#else + return NULL; +#endif +} + +void foo(void) +{ +} + +int main(void) +{ + double res = 0.; + int i; + void *stack = NULL; +#ifdef UNIX + pthread_t pt[NUM_THREADS]; /* On Linux, the tid. */ +#else + uintptr_t thread[NUM_THREADS]; /* _beginthreadex doesn't return HANDLE? */ + uint tid[NUM_THREADS]; +#endif + + /* We could generate this via macros but that gets pretty obtuse */ + funcs[0] = &func_0; + funcs[1] = &func_1; + funcs[2] = &func_2; + funcs[3] = &func_3; + funcs[4] = &func_4; + funcs[5] = &func_5; + funcs[6] = &func_6; + funcs[7] = &func_7; + funcs[8] = &func_8; + funcs[9] = &func_9; + + sideline_continue = create_cond_var(); + go_native = create_cond_var(); + + for (i = 0; i < NUM_THREADS; i++) { + sideline_ready[i] = create_cond_var(); +#ifdef UNIX + pthread_create(&pt[i], NULL, sideline_spinner, (void*)(uintptr_t)i); +#else + thread[i] = _beginthreadex(NULL, 0, sideline_spinner, (void*)(uintptr_t)i, + 0, &tid[i]); +#endif + } + + /* Initialized DR */ + dr_app_setup(); + /* XXX: Calling the client interface from the app is not supported. We're + * just using it for testing. + */ + dr_register_bb_event(event_bb); + + /* Wait for all the threads to be scheduled */ + VPRINT("waiting for ready\n"); + for (i=0; i + +/* XXX i#975: also add an api.static_takeover test that uses drrun + * -static instead of calling dr_app_*. + */ + +static int num_bbs; + +static dr_emit_flags_t +event_bb(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, + bool translating) +{ + num_bbs++; + return DR_EMIT_DEFAULT; +} + +static void +event_exit(void) +{ + dr_fprintf(STDERR, "Saw %s bb events\n", num_bbs > 0 ? "some" : "no"); +} + +DR_EXPORT void +dr_client_main(client_id_t id, int argc, const char *argv[]) +{ + print("in dr_client_main\n"); + dr_register_bb_event(event_bb); + dr_register_exit_event(event_exit); + + /* XXX i#975: add some more thorough tests of different events */ +} + +static int +do_some_work(void) +{ + static int iters = 8192; + int i; + double val = num_bbs; + for (i = 0; i < iters; ++i) { + val += sin(val); + } + return (val > 0); +} + +int +main(int argc, const char *argv[]) +{ + print("pre-DR init\n"); + dr_app_setup(); + assert(!dr_app_running_under_dynamorio()); + + print("pre-DR start\n"); + dr_app_start(); + assert(dr_app_running_under_dynamorio()); + + if (do_some_work() < 0) + print("error in computation\n"); + + print("pre-DR detach\n"); + dr_app_stop_and_cleanup(); + assert(!dr_app_running_under_dynamorio()); + + if (do_some_work() < 0) + print("error in computation\n"); + print("all done\n"); + return 0; +} diff --git a/suite/tests/api/static_detach.expect b/suite/tests/api/static_detach.expect new file mode 100644 index 00000000000..bd9aa74d633 --- /dev/null +++ b/suite/tests/api/static_detach.expect @@ -0,0 +1,6 @@ +pre-DR init +in dr_client_main +pre-DR start +pre-DR detach +Saw some bb events +all done diff --git a/suite/tests/api/static_startstop.c b/suite/tests/api/static_startstop.c index c01687c0e14..103ac08ace4 100644 --- a/suite/tests/api/static_startstop.c +++ b/suite/tests/api/static_startstop.c @@ -93,6 +93,7 @@ main(int argc, const char *argv[]) print("pre-DR stop\n"); dr_app_stop(); + assert(!dr_app_running_under_dynamorio()); dr_app_cleanup(); print("all done\n"); return 0;