diff --git a/api/docs/deployment.dox b/api/docs/deployment.dox index bb779ea39ab..9cb8559c3c5 100644 --- a/api/docs/deployment.dox +++ b/api/docs/deployment.dox @@ -337,6 +337,12 @@ with this command: % echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope \endcode +Then, you can also detach DynamoRIO from the target process +without affecting the normal execution of the application. +\code +% bin32/drconfig -detach +\endcode + Run \c drrun with no options to get a list of the options and environment variable shortcuts it supports. To disable following across child execve calls, use the \ref op_children "-no_follow_children" runtime diff --git a/core/lib/globals_shared.h b/core/lib/globals_shared.h index 06748b4d77b..392b875b07d 100644 --- a/core/lib/globals_shared.h +++ b/core/lib/globals_shared.h @@ -933,9 +933,9 @@ enum { #else NUDGE_NUDGER_FREE_STACK = 0x02, /* nudger will free the nudge thread's stack so the * nudge thread itself shouldn't */ - NUDGE_FREE_ARG = 0x04, /* nudge arg is in a separate allocation and should - * be freed by the nudge thread */ #endif + NUDGE_FREE_ARG = 0x04, /* nudge arg is in a separate allocation and should + * be freed by the nudge thread */ }; typedef struct { diff --git a/core/nudge.c b/core/nudge.c index 4df3a4aa549..e8ab897d92b 100644 --- a/core/nudge.c +++ b/core/nudge.c @@ -42,6 +42,10 @@ #else #endif /* WINDOWS */ +#ifdef LINUX +# include "synch.h" +#endif + #ifdef HOT_PATCHING_INTERFACE # include "hotpatch.h" /* for hotp_nudge_update() */ #endif @@ -430,12 +434,30 @@ handle_nudge(dcontext_t *dcontext, nudge_arg_t *arg) SYSLOG_INTERNAL_WARNING("nudge reset ignored since resets are disabled"); } } -#ifdef WINDOWS +#if defined(WINDOWS) || defined(LINUX) /* The detach handler is last since in the common case it doesn't return. */ if (TEST(NUDGE_GENERIC(detach), nudge_action_mask)) { +# ifdef WINDOWS dcontext->free_app_stack = false; nudge_action_mask &= ~NUDGE_GENERIC(detach); detach_helper(DETACH_NORMAL_TYPE); +# else + nudge_action_mask &= ~NUDGE_GENERIC(detach); + /* This is not using stack_alloc() because we can't have this being cleaned up + * via normal cleanup paths. */ + heap_error_code_t error_code_reserve, error_code_commit; + void *d_r_detachstack = + os_heap_reserve(NULL, DYNAMORIO_STACK_SIZE, &error_code_reserve, false); + /* XXX: This memory is not freed. */ + if (!os_heap_commit(d_r_detachstack, DYNAMORIO_STACK_SIZE, + MEMPROT_READ | MEMPROT_WRITE, &error_code_commit)) { + ASSERT_NOT_REACHED(); + } + call_switch_stack(dcontext, + (byte *)((ptr_uint_t)d_r_detachstack + DYNAMORIO_STACK_SIZE), + (void (*)(void *))detach_externally_on_new_stack, NULL, true); + ASSERT_NOT_REACHED(); +# endif } #endif } diff --git a/core/os_shared.h b/core/os_shared.h index b2ef7d568c4..5b828e66259 100644 --- a/core/os_shared.h +++ b/core/os_shared.h @@ -204,15 +204,21 @@ is_thread_currently_native(thread_record_t *tr); */ bool thread_get_mcontext(thread_record_t *tr, priv_mcontext_t *mc); + +#ifdef LINUX +bool +thread_get_nudged_mcontext(thread_record_t *tr, priv_mcontext_t *mc); +#endif + bool thread_set_mcontext(thread_record_t *tr, priv_mcontext_t *mc); /* Takes an os-specific context. Does not return. */ void -thread_set_self_context(void *cxt); +thread_set_self_context(void *cxt, bool is_detach_external); /* Only sets the priv_mcontext_t state. Does not return. */ void -thread_set_self_mcontext(priv_mcontext_t *mc); +thread_set_self_mcontext(priv_mcontext_t *mc, bool is_detach_external); /* Assumes target thread is suspended */ bool diff --git a/core/synch.c b/core/synch.c index 4c96c2b0e88..3af5add4afe 100644 --- a/core/synch.c +++ b/core/synch.c @@ -761,9 +761,9 @@ check_wait_at_safe_spot(dcontext_t *dcontext, thread_synch_permission_t cur_stat * being at the synch point vs in the cache. */ if (set_mcontext) - thread_set_self_mcontext((priv_mcontext_t *)cxt); + thread_set_self_mcontext((priv_mcontext_t *)cxt, false); else - thread_set_self_context((void *)cxt); + thread_set_self_context((void *)cxt, false); ASSERT_NOT_REACHED(); } } @@ -1965,6 +1965,77 @@ send_all_other_threads_native(void) return; } +static void +detach_set_mcontext_helper(thread_record_t *thread) +{ + priv_mcontext_t mc; + LOG(GLOBAL, LOG_ALL, 2, "Detach: translating " TIDFMT "\n", thread); + DEBUG_DECLARE(bool ok =) + thread_get_mcontext(thread, &mc); + ASSERT(ok); + /* For a thread at a syscall, we use SA_RESTART for our suspend signal, + * so the kernel will adjust the restart point back to the syscall for us + * where expected. This is an artifical signal we're introducing, so an + * app that assumes no signals and assumes its non-auto-restart syscalls + * don't need loops could be broken. + */ + LOG(GLOBAL, LOG_ALL, 3, + /* Having the code bytes can help diagnose post-detach where the code + * cache is gone. + */ + "Detach: pre-xl8 pc=%p (%02x %02x %02x %02x %02x), xsp=%p " + "for thread " TIDFMT "\n", + mc.pc, *mc.pc, *(mc.pc + 1), *(mc.pc + 2), *(mc.pc + 3), *(mc.pc + 4), mc.xsp, + thread->id); + DEBUG_DECLARE(ok =) + translate_mcontext(thread, &mc, true /*restore mem*/, NULL /*f*/); + ASSERT(ok); + if (!thread->under_dynamo_control) { + LOG(GLOBAL, LOG_ALL, 1, "Detach : thread " TIDFMT " already running natively\n", + thread->id); + /* we do need to restore the app ret addr, for native_exec */ + if (!DYNAMO_OPTION(thin_client) && DYNAMO_OPTION(native_exec) && + !vmvector_empty(native_exec_areas)) { + put_back_native_retaddrs(thread->dcontext); + } + } + detach_finalize_translation(thread, &mc); + LOG(GLOBAL, LOG_ALL, 1, "Detach: pc=" PFX " for thread " TIDFMT "\n", mc.pc, + thread->id); + ASSERT(!is_dynamo_address(mc.pc) && !in_fcache(mc.pc)); + /* XXX case 7457: if the thread is suspended after it received a fault + * but before the kernel copied the faulting context to the user mode + * structures for the handler, it could result in a codemod exception + * that wouldn't happen natively! + */ + DEBUG_DECLARE(ok =) + thread_set_mcontext(thread, &mc); + ASSERT(ok); + /* i#249: restore app's PEB/TEB fields */ + IF_WINDOWS(restore_peb_pointer_for_thread(thread->dcontext)); +} + +static void +detach_cleanup_helper(thread_record_t *thread _IF_WINDOWS(bool detach_stacked_callbacks)) +{ + DEBUG_DECLARE(int exit_res =) + dynamo_shared_exit(thread _IF_WINDOWS(detach_stacked_callbacks)); + ASSERT(exit_res == SUCCESS); + detach_finalize_cleanup(); + + stack_free(d_r_initstack, DYNAMORIO_STACK_SIZE); + + dynamo_exit_post_detach(); + + doing_detach = false; + started_detach = false; + + SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT); + dynamo_detaching_flag = LOCK_FREE_STATE; + EXITING_DR(); + options_detach(); +} + void detach_on_permanent_stack(bool internal, bool do_cleanup, dr_stats_t *drstats) { @@ -1977,8 +2048,6 @@ detach_on_permanent_stack(bool internal, bool do_cleanup, dr_stats_t *drstats) bool detach_stacked_callbacks; bool *cleanup_tpc; #endif - DEBUG_DECLARE(bool ok;) - DEBUG_DECLARE(int exit_res;) /* synch-all flags: */ uint flags = 0; @@ -2152,7 +2221,6 @@ detach_on_permanent_stack(bool internal, bool do_cleanup, dr_stats_t *drstats) 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]; @@ -2166,54 +2234,7 @@ detach_on_permanent_stack(bool internal, bool do_cleanup, dr_stats_t *drstats) 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)); + detach_set_mcontext_helper(threads[i]); } /* Resumes the thread, which will do kernel-visible cleanup of * signal state. Resume happens within the synch_all region where @@ -2272,20 +2293,148 @@ detach_on_permanent_stack(bool internal, bool do_cleanup, dr_stats_t *drstats) 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; + detach_cleanup_helper(my_tr _IF_WINDOWS(detach_stacked_callbacks)); +} - SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT); - dynamo_detaching_flag = LOCK_FREE_STATE; - EXITING_DR(); - options_detach(); +#ifdef LINUX +void +detach_externally_on_new_stack() +{ + dcontext_t *my_dcontext; + priv_mcontext_t my_mcontext; + thread_record_t **threads; + thread_record_t *my_tr = NULL; + int i, num_threads, my_idx = -1; + thread_id_t my_id; + DEBUG_DECLARE(bool ok;) + /* synch-all flags: */ + uint flags = 0; + /* For Unix, such privilege problems are rarer but we would still prefer to + * continue if we hit a problem. + */ + flags |= THREAD_SYNCH_SUSPEND_FAILURE_IGNORE; + /* i#297: we only synch client threads after process exit event. */ + flags |= THREAD_SYNCH_SKIP_CLIENT_THREAD; + ENTERING_DR(); + /* dynamo_detaching_flag is not really a lock, and since no one ever waits + * on it we can't deadlock on it either. + */ + if (!atomic_compare_exchange(&dynamo_detaching_flag, LOCK_FREE_STATE, LOCK_SET_STATE)) + return; + instrument_pre_detach_event(); + /* Unprotect .data for exit cleanup. + * XXX: more secure to not do this until we've synched, but then need + * alternative prot for started_detach and init_apc_go_native* + */ + SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT); + ASSERT(!started_detach); + started_detach = true; + ASSERT(dynamo_initialized); + ASSERT(!dynamo_exited); + my_id = d_r_get_thread_id(); + my_dcontext = get_thread_private_dcontext(); + ASSERT(my_dcontext != NULL); + LOG(GLOBAL, LOG_ALL, 1, "Detach: thread %d starting detach process\n", my_id); + SYSLOG(SYSLOG_INFORMATION, INFO_DETACHING, 2, get_application_name(), + get_application_pid()); + /* synch with flush */ + if (my_dcontext != NULL) + enter_threadexit(my_dcontext); + /* i#2270: we ignore alarm signals during detach to reduce races. */ + signal_remove_alarm_handlers(my_dcontext); + /* suspend all DR-controlled threads at safe locations */ + if (!synch_with_all_threads(THREAD_SYNCH_SUSPENDED_VALID_MCONTEXT, &threads, + &num_threads, + /* Case 6821: allow other synch-all-thread uses + * that beat us to not wait on us. We still have + * a problem if we go first since we must xfer + * other threads. + */ + THREAD_SYNCH_NO_LOCKS_NO_XFER, flags)) { + REPORT_FATAL_ERROR_AND_EXIT(FAILED_TO_SYNCHRONIZE_THREADS, 2, + get_application_name(), get_application_pid()); + } + /* Now we own the thread_initexit_lock. We'll release the locks grabbed in + * synch_with_all_threads below after cleaning up all the threads in case we + * need to grab it during process exit cleanup. + */ + ASSERT(mutex_testlock(&all_threads_synch_lock) && + mutex_testlock(&thread_initexit_lock)); + ASSERT(!doing_detach); + doing_detach = true; + detacher_tid = d_r_get_thread_id(); +# ifdef HOT_PATCHING_INTERFACE + /* In hotp_only mode, we must remove patches when detaching; we don't want + * to leave in all our hooks and detach; that will definitely crash the app. + */ + if (DYNAMO_OPTION(hotp_only)) + hotp_only_detach_helper(); +# endif + if (!DYNAMO_OPTION(thin_client)) + revert_memory_regions(); + unhook_vsyscall(); + LOG(GLOBAL, LOG_ALL, 1, + "Detach : unpatched ntdll.dll and fixed memory permissions\n"); + /* perform exit tasks that require full thread data structs */ + dynamo_process_exit_with_thread_info(); + LOG(GLOBAL, LOG_ALL, 1, "Detach: starting to translate contexts\n"); + for (i = 0; i < num_threads; i++) { + if (threads[i]->dcontext == my_dcontext) { + my_idx = i; + my_tr = threads[i]; + DEBUG_DECLARE(ok =) + thread_get_nudged_mcontext(threads[i], &my_mcontext); + DEBUG_DECLARE(ok =) + translate_mcontext(threads[i], &my_mcontext, true /*restore mem*/, + NULL /*f*/); + continue; + } else if (IS_CLIENT_THREAD(threads[i]->dcontext)) { + /* i#297 we will kill client-owned threads later after app exit events + * in dynamo_shared_exit(). + */ + continue; + } else if (detach_do_not_translate(threads[i])) { + LOG(GLOBAL, LOG_ALL, 2, "Detach: not translating " TIDFMT "\n", + threads[i]->id); + } else { + detach_set_mcontext_helper(threads[i]); + } + /* Resumes the thread, which will do kernel-visible cleanup of + * signal state. Resume happens within the synch_all region where + * the thread_initexit_lock is held so that we can clean up thread + * data later. + */ + os_signal_thread_detach(threads[i]->dcontext); + LOG(GLOBAL, LOG_ALL, 1, "Detach: thread " TIDFMT " is being resumed as native\n", + threads[i]->id); + os_thread_resume(threads[i]); + } + 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); + } + /* Clean up each thread now that everyone has gone native. Needs to be + * done with the thread_initexit_lock held, which is true within a synched + * region. + */ + for (i = 0; i < num_threads; i++) { + if (i != my_idx && !IS_CLIENT_THREAD(threads[i]->dcontext)) { + LOG(GLOBAL, LOG_ALL, 1, "Detach: cleaning up thread " TIDFMT " %s\n", + threads[i]->id, IF_WINDOWS_ELSE(cleanup_tpc[i] ? "and its TPC" : "", "")); + dynamo_other_thread_exit(threads[i] _IF_WINDOWS(!cleanup_tpc[i])); + } + } + if (my_idx != -1) { + /* pre-client thread cleanup (PR 536058) */ + dynamo_thread_exit_pre_client(my_dcontext, my_tr->id); + } + LOG(GLOBAL, LOG_ALL, 1, "Detach: Letting secondary threads go native\n"); + end_synch_with_all_threads(threads, num_threads, false /*don't resume */); + threads = NULL; + LOG(GLOBAL, LOG_ALL, 1, "Detach: Entering final cleanup and unload\n"); + SYSLOG_INTERNAL_INFO("Detaching from process, entering final cleanup"); + detach_cleanup_helper(my_tr); + thread_set_self_mcontext(&my_mcontext, true); } +#endif diff --git a/core/synch.h b/core/synch.h index 90e1382c8f4..461d429a625 100644 --- a/core/synch.h +++ b/core/synch.h @@ -263,6 +263,11 @@ send_all_other_threads_native(void); void detach_on_permanent_stack(bool internal, bool do_cleanup, dr_stats_t *drstats); +#ifdef LINUX +void +detach_externally_on_new_stack(); +#endif + /*** exported for detach only ***/ bool diff --git a/core/unix/os.c b/core/unix/os.c index 6949390b5f3..6d7f85dcf88 100644 --- a/core/unix/os.c +++ b/core/unix/os.c @@ -3979,6 +3979,21 @@ thread_get_mcontext(thread_record_t *tr, priv_mcontext_t *mc) return true; } +#ifdef LINUX +bool +thread_get_nudged_mcontext(thread_record_t *tr, priv_mcontext_t *mc) +{ + /* This only works for a thread that just received a nduge signal. */ + os_thread_data_t *ostd = (os_thread_data_t *)tr->dcontext->os_field; + ASSERT(ostd != NULL); + ASSERT(ostd->nudged_sigcxt != NULL); + sigcontext_to_mcontext(mc, ostd->nudged_sigcxt, DR_MC_ALL); + IF_ARM(dr_set_isa_mode(tr->dcontext, get_sigcontext_isa_mode(ostd->nudged_sigcxt), + NULL)); + return true; +} +#endif + bool thread_set_mcontext(thread_record_t *tr, priv_mcontext_t *mc) { diff --git a/core/unix/os_private.h b/core/unix/os_private.h index 1cfef5572a5..861cab9dc20 100644 --- a/core/unix/os_private.h +++ b/core/unix/os_private.h @@ -165,6 +165,11 @@ typedef struct _os_thread_data_t { KSYNCH_TYPE resumed; sig_full_cxt_t *suspended_sigcxt; +#ifdef LINUX + /* For detachment on Linux. */ + sig_full_cxt_t *nudged_sigcxt; +#endif + /* PR 297902: for thread termination */ bool terminate; /* Any function that sets this flag must also notify possibly waiting diff --git a/core/unix/signal.c b/core/unix/signal.c index 90767800081..04ba562ebcc 100644 --- a/core/unix/signal.c +++ b/core/unix/signal.c @@ -3166,10 +3166,10 @@ translate_sigcontext(dcontext_t *dcontext, kernel_ucontext_t *uc, bool avoid_fai /* Takes an os-specific context */ void -thread_set_self_context(void *cxt) +thread_set_self_context(void *cxt, bool is_detach_external) { #ifdef X86 - if (!INTERNAL_OPTION(use_sigreturn_setcontext)) { + if (!INTERNAL_OPTION(use_sigreturn_setcontext) || is_detach_external) { sigcontext_t *sc = (sigcontext_t *)cxt; dr_jmp_buf_t buf; buf.xbx = sc->SC_XBX; @@ -3311,7 +3311,7 @@ thread_set_segment_registers(sigcontext_t *sc) /* Takes a priv_mcontext_t */ void -thread_set_self_mcontext(priv_mcontext_t *mc) +thread_set_self_mcontext(priv_mcontext_t *mc, bool is_detach_external) { kernel_ucontext_t ucxt; sig_full_cxt_t sc_full; @@ -3325,7 +3325,7 @@ thread_set_self_mcontext(priv_mcontext_t *mc) IF_ARM( set_pc_mode_in_cpsr(sc_full.sc, dr_get_isa_mode(get_thread_private_dcontext()))); /* thread_set_self_context will fill in the real fp/simd state for x86 */ - thread_set_self_context((void *)sc_full.sc); + thread_set_self_context((void *)sc_full.sc, is_detach_external); ASSERT_NOT_REACHED(); } @@ -7917,10 +7917,15 @@ signal_to_itimer_type(int sig) static bool alarm_signal_has_DR_only_itimer(dcontext_t *dcontext, int signal) { - thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; int which = signal_to_itimer_type(signal); if (which == -1) return false; +#ifdef LINUX + if (dcontext == GLOBAL_DCONTEXT) { + return false; + } +#endif + thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; if (info->shared_itimer) acquire_recursive_lock(&(*info->itimer)[which].lock); bool DR_only = @@ -8480,8 +8485,13 @@ handle_suspend_signal(dcontext_t *dcontext, kernel_siginfo_t *siginfo, if (is_sigqueue_supported() && SUSPEND_SIGNAL == NUDGESIG_SIGNUM) { nudge_arg_t *arg = (nudge_arg_t *)siginfo; - if (!TEST(NUDGE_IS_SUSPEND, arg->flags)) + if (!TEST(NUDGE_IS_SUSPEND, arg->flags)) { +#ifdef LINUX + sig_full_initialize(&sc_full, ucxt); + ostd->nudged_sigcxt = &sc_full; +#endif return handle_nudge_signal(dcontext, siginfo, ucxt); + } } /* We distinguish from an app signal further below from the rare case of an diff --git a/core/win32/os.c b/core/win32/os.c index b5dc3b3793f..2f294643ef2 100644 --- a/core/win32/os.c +++ b/core/win32/os.c @@ -2795,7 +2795,7 @@ thread_attach_setup(priv_mcontext_t *mc) * sets the context back). */ mc->pc = data->continuation_pc; - thread_set_self_mcontext(mc); + thread_set_self_mcontext(mc, false); ASSERT_NOT_REACHED(); } /* Preclude double takeover if we become suspended while in ntdll */ @@ -5201,7 +5201,7 @@ thread_set_context(thread_record_t *tr, CONTEXT *context) /* Takes an os-specific context */ void -thread_set_self_context(void *cxt) +thread_set_self_context(void *cxt, bool is_detach_external) { /* We use NtContinue to avoid privilege issues with NtSetContext */ nt_continue((CONTEXT *)cxt); @@ -5210,7 +5210,7 @@ thread_set_self_context(void *cxt) /* Takes a priv_mcontext_t */ void -thread_set_self_mcontext(priv_mcontext_t *mc) +thread_set_self_mcontext(priv_mcontext_t *mc, bool is_detach_external) { /* We can't use heap for our CONTEXT as we have no opportunity to free it. * We assume call paths can handle a large stack buffer as size something @@ -5232,7 +5232,7 @@ thread_set_self_mcontext(priv_mcontext_t *mc) cxt = nt_initialize_context(buf, bufsz, cxt_flags); /* need ss and cs for setting my own context */ mcontext_to_context(cxt, mc, true /* set_cur_seg */); - thread_set_self_context(cxt); + thread_set_self_context(cxt, false); ASSERT_NOT_REACHED(); } diff --git a/suite/runsuite_wrapper.pl b/suite/runsuite_wrapper.pl index a5169d3c258..74b94720ab9 100755 --- a/suite/runsuite_wrapper.pl +++ b/suite/runsuite_wrapper.pl @@ -340,6 +340,7 @@ 'code_api|linux.mangle_asynch' => 1, 'code_api,tracedump_text,tracedump_origins,syntax_intel|common.loglevel' => 1, # i#1807 'code_api|client.attach_test' => 1, # i#5740 + 'code_api|client.detach_test' => 1, # i#5740 'code_api|client.attach_blocking' => 1, # i#5740 'code_api|tool.drcacheoff.rseq' => 1, # i#5734 'code_api|tool.drcacheoff.windows-zlib' => 1, # i#5507 diff --git a/suite/tests/CMakeLists.txt b/suite/tests/CMakeLists.txt index 465a7c40f5f..3b1f860e30e 100644 --- a/suite/tests/CMakeLists.txt +++ b/suite/tests/CMakeLists.txt @@ -1411,6 +1411,9 @@ function(torun test key source native standalone_dr dr_ops exe_ops added_out pas # is failing). set(exe_ops "${exe_ops};-v;-attach") endif () + if ("${runall}" MATCHES "") + set(exe_ops "${exe_ops};-v;") + endif () if ("${runall}" MATCHES "") set(exe_ops "${exe_ops};-block") endif () @@ -2574,9 +2577,9 @@ endif (ANNOTATIONS AND NOT ARM) if (NOT ANDROID) # TODO i#38: Port test to Android. tobuild_ci(client.attach_test client-interface/attach_test.runall "" "" "") - if (WIN32) + if ((LINUX OR WIN32) AND NOT RISCV64) tobuild_ci(client.detach_test client-interface/detach_test.runall "" "" "") - endif (WIN32) + endif () if (UNIX) # Test attaching during a blocking syscall. torunonly_ci(client.attach_blocking linux.infloop client.attach_test.dll @@ -3003,7 +3006,7 @@ if (X86 OR AARCH64) "-early_inject" "") append_pure_asm_app_link_flags(allasm_repstr) endif () - endif() + endif () endif () if (NOT RISCV64) diff --git a/suite/tests/client-interface/detach_test.dll.c b/suite/tests/client-interface/detach_test.dll.c index daac52d602b..a6d4cc9c3ca 100644 --- a/suite/tests/client-interface/detach_test.dll.c +++ b/suite/tests/client-interface/detach_test.dll.c @@ -39,11 +39,18 @@ static thread_id_t injection_tid; static bool first_thread = true; +static bool saw_attach_event = false; static void dr_exit(void) { + if (!saw_attach_event) + dr_fprintf(STDERR, "Error: never saw attach event!\n"); +#ifdef WINDOWS dr_fprintf(STDERR, "done\n"); +#else + /* The app prints 'done' for us. */ +#endif } static void @@ -87,7 +94,8 @@ dr_exception_event(void *drcontext, dr_exception_t *excpt) static void event_post_attach(void) { - dr_fprintf(STDERR, "attach\n"); + // We do not print here as the ordering is non-deterministic vs thread init. + saw_attach_event = true; } static void diff --git a/suite/tests/client-interface/detach_test.template b/suite/tests/client-interface/detach_test.template index 1fe679addcb..fd8496ea131 100644 --- a/suite/tests/client-interface/detach_test.template +++ b/suite/tests/client-interface/detach_test.template @@ -1,6 +1,9 @@ +#ifdef WINDOWS starting attachee +#else +starting +#endif thank you for testing detach -attach thread init detach done diff --git a/suite/tests/runall.cmake b/suite/tests/runall.cmake index 7c7ad7823ea..08eea9ed4d5 100644 --- a/suite/tests/runall.cmake +++ b/suite/tests/runall.cmake @@ -90,6 +90,12 @@ else (UNIX) set(nudge_cmd drconfig) endif (UNIX) +if (UNIX) + set(detach_cmd drconfig) +else () + set(detach_cmd drconfig.exe) +endif() + if (UNIX) set(MAX_ITERS 50000) else () @@ -268,17 +274,6 @@ if ("${orig_nudge}" MATCHES "-client") endif () endwhile() elseif ("${orig_nudge}" MATCHES "" OR "${orig_nudge}" MATCHES "") - # Wait until attached. - set(iters 0) - while (NOT "${output}" MATCHES "attach\n") - do_sleep(0.1) - file(READ "${out}" output) - math(EXPR iters "${iters}+1") - if (${iters} GREATER ${MAX_ITERS}) - kill_background_process(ON) - message(FATAL_ERROR "Timed out waiting for attach") - endif () - endwhile() # Wait until thread init. set(iters 0) while (NOT "${output}" MATCHES "thread init\n") @@ -287,7 +282,7 @@ elseif ("${orig_nudge}" MATCHES "" OR "${orig_nudge}" MATCHES "" math(EXPR iters "${iters}+1") if (${iters} GREATER ${MAX_ITERS}) kill_background_process(ON) - message(FATAL_ERROR "Timed out waiting for attach") + message(FATAL_ERROR "Timed out waiting for thread init") endif () endwhile() else () @@ -298,7 +293,7 @@ else () endif () if ("${orig_nudge}" MATCHES "") - execute_process(COMMAND "${toolbindir}/drconfig.exe" "-detach" ${pid} + execute_process(COMMAND "${toolbindir}/${detach_cmd}" "-detach" ${pid} RESULT_VARIABLE detach_result ERROR_VARIABLE detach_err OUTPUT_VARIABLE detach_out) @@ -314,10 +309,10 @@ if ("${orig_nudge}" MATCHES "") math(EXPR iters "${iters}+1") if (${iters} GREATER ${MAX_ITERS}) kill_background_process(ON) - message(FATAL_ERROR "Timed out waiting for attach") + message(FATAL_ERROR "Timed out waiting for detach") endif () endwhile() -endif() +endif () kill_background_process(OFF) diff --git a/tools/drdeploy.c b/tools/drdeploy.c index 05175816564..a6b5c49a480 100644 --- a/tools/drdeploy.c +++ b/tools/drdeploy.c @@ -52,6 +52,10 @@ # include #endif +#ifdef LINUX +# include +#endif + #include #include #include @@ -64,6 +68,15 @@ #include "dr_inject.h" #include "dr_frontend.h" +#ifdef LINUX +/* XXX: It would be cleaner to have a header for this and have nudgesig.c be in its + * own static library instead of compiled separately for the core and drdeploy. + */ +extern bool +create_nudge_signal_payload(siginfo_t *info DR_PARAM_OUT, uint action_mask, + client_id_t client_id, uint flags, uint64 client_arg); +#endif + typedef enum _action_t { action_none, action_nudge, @@ -1211,7 +1224,7 @@ _tmain(int argc, TCHAR *targv[]) bool exit0 = false; #endif #if defined(DRCONFIG) -# ifdef WINDOWS +# if defined(WINDOWS) || defined(LINUX) process_id_t detach_pid = 0; # endif #endif @@ -1508,7 +1521,10 @@ _tmain(int argc, TCHAR *targv[]) nudge_all = true; nudge_id = strtoul(argv[++i], NULL, 16); nudge_arg = _strtoui64(argv[++i], NULL, 16); - } else if (strcmp(argv[i], "-detach") == 0) { + } +# endif +# if defined(WINDOWS) || defined(LINUX) + else if (strcmp(argv[i], "-detach") == 0) { if (i + 1 >= argc) usage(false, "detach requires a process id"); const char *pid_str = argv[++i]; @@ -1836,12 +1852,32 @@ _tmain(int argc, TCHAR *targv[]) if (!unregister_proc(process, 0, global, dr_platform)) die(); } +# if defined(WINDOWS) || defined(LINUX) + else if (detach_pid != 0) { +# ifdef WINDOWS + dr_config_status_t res = detach(detach_pid, TRUE, detach_timeout); + if (res != DR_SUCCESS) + error("unable to detach: check pid and system ptrace permissions"); +# else + siginfo_t info; + uint action_mask = NUDGE_FREE_ARG; + client_id_t client_id = 0; + uint64 client_arg = 0; + bool success = + create_nudge_signal_payload(&info, action_mask, 0, client_id, client_arg); + assert(success); /* failure means kernel's sigqueueinfo has changed */ + /* send the nudge */ + i = syscall(SYS_rt_sigqueueinfo, detach_pid, NUDGESIG_SIGNUM, &info); + if (i < 0) + fprintf(stderr, "nudge FAILED with error %d\n", i); +# endif + } +# endif # ifndef WINDOWS else { usage(false, "no action specified"); } # else /* WINDOWS */ - /* FIXME i#840: Nudge NYI on Linux. */ else if (action == action_nudge) { int count = 1; dr_config_status_t res = DR_SUCCESS; @@ -1878,12 +1914,6 @@ _tmain(int argc, TCHAR *targv[]) dr_registered_process_iterator_stop(iter); } } - /* FIXME i#95: Process detach NYI for UNIX. */ - else if (detach_pid != 0) { - dr_config_status_t res = detach(detach_pid, TRUE, detach_timeout); - if (res != DR_SUCCESS) - error("unable to detach: check pid and system ptrace permissions"); - } # endif else if (!syswide_on && !syswide_off) { usage(false, "no action specified");