Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i#1921 native sig: Add handling for signals for temp-native threads #4603

Merged
merged 5 commits into from
Dec 14, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/arch/interp.c
Original file line number Diff line number Diff line change
Expand Up @@ -2766,7 +2766,7 @@ client_process_bb(dcontext_t *dcontext, build_bb_t *bb)
/* we leverage the existing native_exec mechanism */
dcontext->native_exec_postsyscall = bb->start_pc;
dcontext->next_tag = BACK_TO_NATIVE_AFTER_SYSCALL;
dynamo_thread_not_under_dynamo(dcontext);
/* dynamo_thread_not_under_dynamo() will be called in dispatch_enter_native(). */
return false;
}

Expand Down
2 changes: 2 additions & 0 deletions core/dispatch.c
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,8 @@ dispatch_enter_native(dcontext_t *dcontext)
dcontext->native_exec_postsyscall);
dcontext->next_tag =
PC_AS_JMP_TGT(dr_get_isa_mode(dcontext), dcontext->native_exec_postsyscall);
if (!dcontext->currently_stopped)
dynamo_thread_not_under_dynamo(dcontext);
dcontext->native_exec_postsyscall = NULL;
LOG(THREAD, LOG_DISPATCH, 2,
"Entry into native_exec after intercepted syscall\n");
Expand Down
6 changes: 3 additions & 3 deletions core/synch.c
Original file line number Diff line number Diff line change
Expand Up @@ -1014,9 +1014,9 @@ synch_with_thread(thread_id_t id, bool block, bool hold_initexit_lock,
* 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?"));
(trec->dcontext->currently_stopped IF_APP_EXPORTS(|| dr_api_exit)) &&
"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);
Expand Down
123 changes: 118 additions & 5 deletions core/unix/signal.c
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ execute_handler_from_cache(dcontext_t *dcontext, int sig, sigframe_rt_t *our_fra
static bool
execute_handler_from_dispatch(dcontext_t *dcontext, int sig);

static void
execute_native_handler(dcontext_t *dcontext, int sig, sigframe_rt_t *our_frame);

/* Execute default action from code cache and may terminate the process.
* If returns, the return value decides if caller should restore
* the untranslated context.
Expand Down Expand Up @@ -2922,11 +2925,13 @@ get_sigcontext_from_pending(thread_sig_info_t *info, int sig)
* If frame is NULL, assumes signal happened while in DR and has been delayed,
* and thus we need to provide fpstate regardless of whether the original
* had it. If frame is non-NULL, matches frame's amount of fpstate.
* dcontext can be NULL if frame is non-NULL.
*/
static byte *
get_sigstack_frame_ptr(dcontext_t *dcontext, int sig, sigframe_rt_t *frame)
get_sigstack_frame_ptr(dcontext_t *dcontext, thread_sig_info_t *info, int sig,
sigframe_rt_t *frame)
{
thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field;
ASSERT(dcontext != NULL || frame != NULL);
sigcontext_t *sc = (frame == NULL) ? get_sigcontext_from_pending(info, sig)
: get_sigcontext_from_rt_frame(frame);
byte *sp;
Expand Down Expand Up @@ -4333,6 +4338,11 @@ record_pending_signal(dcontext_t *dcontext, int sig, kernel_ucontext_t *ucxt,
* deliver it now.
*/
receive_now = true;
} else if (dcontext->currently_stopped) {
/* We have no way to delay for a currently-native thread. */
LOG(THREAD, LOG_ASYNCH, 2, "Going to receive signal natively now\n");
execute_native_handler(dcontext, sig, frame);
handled = true;
} else {
/* the signal interrupted DR itself => do not run handler now! */
LOG(THREAD, LOG_ASYNCH, 2, "record_pending_signal(%d) from DR at pc " PFX "\n",
Expand Down Expand Up @@ -5050,6 +5060,9 @@ master_signal_handler_C(byte *xsp)
"(i#26?): tid=%d, sig=%d",
get_sys_thread_id(), sig);
}

/* TODO i#1921: call execute_native_handler() here (and remove SYSLOG). */

/* see FIXME comments above.
* workaround for now: suppressing is better than dying.
*/
Expand Down Expand Up @@ -5320,6 +5333,10 @@ master_signal_handler_C(byte *xsp)
}
}
/* pass it to the application (or client) */
if (dcontext->currently_stopped) {
execute_native_handler(dcontext, sig, frame);
break;
}
LOG(THREAD, LOG_ALL, 1,
"** Received SIG%s at cache pc " PFX " in thread " TIDFMT "\n",
(sig == SIGSEGV) ? "SEGV" : "BUS", pc, d_r_get_thread_id());
Expand Down Expand Up @@ -5414,7 +5431,7 @@ execute_handler_from_cache(dcontext_t *dcontext, int sig, sigframe_rt_t *our_fra
* This is the translated xsp, so we avoid PR 306410 (cleancall arg fault
* on dstack => handler run on dstack) that Windows hit.
*/
byte *xsp = get_sigstack_frame_ptr(dcontext, sig,
byte *xsp = get_sigstack_frame_ptr(dcontext, info, sig,
our_frame/* take xsp from (translated)
* interruption point */);

Expand Down Expand Up @@ -5536,7 +5553,7 @@ static bool
execute_handler_from_dispatch(dcontext_t *dcontext, int sig)
{
thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field;
byte *xsp = get_sigstack_frame_ptr(dcontext, sig, NULL);
byte *xsp = get_sigstack_frame_ptr(dcontext, info, sig, NULL);
sigframe_rt_t *frame = &(info->sigpending[sig]->rt_frame);
priv_mcontext_t *mcontext = get_mcontext(dcontext);
sigcontext_t *sc;
Expand Down Expand Up @@ -5763,6 +5780,102 @@ execute_handler_from_dispatch(dcontext_t *dcontext, int sig)
return true;
}

/* Sends a signal to a currently-native thread. dcontext can be NULL. */
static void
execute_native_handler(dcontext_t *dcontext, int sig, sigframe_rt_t *our_frame)
{
/* If dcontext is NULL, we use a synthetic info struct where we fill in the
* info we need: the app's sigaction and sigstack settings.
*/
thread_sig_info_t synthetic = {};
kernel_sigaction_t *sigact_array[SIGARRAY_SIZE] = {};
kernel_sigaction_t sigact_struct;
thread_sig_info_t *info;
if (dcontext != NULL) {
info = (thread_sig_info_t *)dcontext->signal_field;
} else {
info = &synthetic;
synthetic.we_intercept[sig] = true;
synthetic.app_sigaction = sigact_array;
sigact_array[sig] = &sigact_struct;
DEBUG_DECLARE(int rc =)
sigaction_syscall(sig, NULL, &sigact_struct);
#ifdef HAVE_SIGALTSTACK
IF_DEBUG(rc =)
sigaltstack_syscall(NULL, &synthetic.app_sigstack);
ASSERT(rc == 0);
#endif
}
kernel_ucontext_t *uc = get_ucontext_from_rt_frame(our_frame);
sigcontext_t *sc = SIGCXT_FROM_UCXT(uc);
kernel_sigset_t blocked;
byte *xsp = get_sigstack_frame_ptr(dcontext, info, sig, our_frame);

/* We do not send the signal to clients as it is not a managed thread and we
* cannot really take any action except send it to the native handler.
*/

if (info->app_sigaction[sig] == NULL ||
info->app_sigaction[sig]->handler == (handler_t)SIG_DFL) {
/* TODO i#1921: We need to pass in an explicit info param with our
* synthetic version.
* Plus we need to audit this callee: not all its code will work with
* a NULL dcontext.
* Plus it calls handle_free() which we need to arrange for.
*/
execute_default_from_cache(dcontext, sig, our_frame, sc, false);
return;
}
ASSERT(info->app_sigaction[sig] != NULL &&
info->app_sigaction[sig]->handler != (handler_t)SIG_IGN &&
info->app_sigaction[sig]->handler != (handler_t)SIG_DFL);

/* TODO i#1921: Can we have a LOG(THREAD_OR_GLOBAL so it's in the thread file if
* we have a dc, but global instead of dropped o/w?
*/
LOG(THREAD, LOG_ASYNCH, 2, "%s for signal %d\n", __FUNCTION__, sig);
RSTATS_INC(num_signals);
report_app_problem(dcontext, APPFAULT_FAULT, (byte *)sc->SC_XIP, (byte *)sc->SC_FP,
"\nSignal %d delivered to application handler.\n", sig);

copy_frame_to_stack(dcontext, sig, our_frame, (void *)xsp, false /*!pending*/);

blocked = info->app_sigaction[sig]->mask;
if (!TEST(SA_NOMASK, (info->app_sigaction[sig]->flags)))
kernel_sigaddset(&blocked, sig);
sigprocmask_syscall(SIG_SETMASK, &blocked, NULL, sizeof(blocked));
LOG(THREAD, LOG_ASYNCH, 3, "Pre-signal blocked signals in frame:\n");
dump_sigset(dcontext, &our_frame->uc.uc_sigmask);

/* Now edit the resumption point when master_signal_handler returns to go
* straight to the app handler.
*/
sc->SC_XSP = (ptr_uint_t)xsp;
/* Set up args to handler. */
#ifdef X86_64
sc->SC_XDI = sig;
sc->SC_XSI = (reg_t) & ((sigframe_rt_t *)xsp)->info;
sc->SC_XDX = (reg_t) & ((sigframe_rt_t *)xsp)->uc;
#elif defined(AARCHXX)
sc->SC_R0 = sig;
if (IS_RT_FOR_APP(info, sig)) {
sc->SC_R1 = (reg_t) & ((sigframe_rt_t *)xsp)->info;
sc->SC_R2 = (reg_t) & ((sigframe_rt_t *)xsp)->uc;
}
if (sig_has_restorer(info, sig))
sc->SC_LR = (reg_t)info->app_sigaction[sig]->restorer;
else
sc->SC_LR = (reg_t)dynamorio_sigreturn;
#endif
sc->SC_XIP = (reg_t)SIGACT_PRIMARY_HANDLER(info->app_sigaction[sig]);

if (TEST(SA_ONESHOT, info->app_sigaction[sig]->flags))
info->app_sigaction[sig]->handler = (handler_t)SIG_DFL;

LOG(THREAD, LOG_ASYNCH, 2, "%s: set pc to handler %p with xsp=%p\n", __FUNCTION__,
SIGACT_PRIMARY_HANDLER(info->app_sigaction[sig]), xsp);
}

/* The arg to SYS_kill, i.e., the signal number, should be in dcontext->sys_param0 */
/* This routine unblocks signals, but the caller must set the handler to default. */
static void
Expand Down Expand Up @@ -7341,7 +7454,7 @@ sig_detach(dcontext_t *dcontext, sigframe_rt_t *frame, KSYNCH_TYPE *detached)
* 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);
xsp = get_sigstack_frame_ptr(dcontext, info, SUSPEND_SIGNAL, frame);
copy_frame_to_stack(dcontext, SUSPEND_SIGNAL, frame, xsp, false /*!pending*/);

#ifdef HAVE_SIGALTSTACK
Expand Down
4 changes: 3 additions & 1 deletion core/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,9 @@ extern mutex_t do_threshold_mutex;
* TRY_EXCEPT_ALLOW_NO_DCONTEXT. \
*/ \
ASSERT((try_pointer) == &global_try_except || (try_pointer) == NULL || \
(try_pointer) == &get_thread_private_dcontext()->try_except); \
(try_pointer) == &get_thread_private_dcontext()->try_except || \
(try_pointer) == /* A currently-native thread: */ \
&thread_lookup(get_sys_thread_id())->dcontext->try_except); \
if ((try_pointer) != NULL) { \
try__state.prev_context = (try_pointer)->try_except_state; \
(try_pointer)->try_except_state = &try__state; \
Expand Down
9 changes: 9 additions & 0 deletions suite/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2552,6 +2552,15 @@ if (CLIENT_INTERFACE)

tobuild_ci(client.thread_exit_xl8 client-interface/thread_exit_xl8.c "" "" "")

if (UNIX) # The test uses signals.
tobuild_ci(client.gonative client-interface/gonative.c "" "" "")
link_with_pthread(client.gonative)
# Configure for dr_app_running_under_dynamorio().
configure_app_api_build_flags(client.gonative OFF OFF)
add_dependencies(client.gonative api_headers)
set_target_properties(client.gonative PROPERTIES SKIP_BUILD_RPATH OFF)
endif ()

tobuild_ci(client.drcontainers-test client-interface/drcontainers-test.c "" "" "")
use_DynamoRIO_extension(client.drcontainers-test.dll drcontainers)

Expand Down
89 changes: 89 additions & 0 deletions suite/tests/client-interface/gonative.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* **********************************************************
* Copyright (c) 2020 Google, 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 Google, 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 GOOGLE, 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 "configure.h"
#include "dr_api.h"
#ifdef WINDOWS
/* Importing from DR causes trouble injecting. */
# include "dr_annotations.h"
# define IS_UNDER_DR() DYNAMORIO_ANNOTATE_RUNNING_ON_DYNAMORIO()
#else
# define IS_UNDER_DR() dr_app_running_under_dynamorio()
#endif
#include "tools.h"
#include "thread.h"
#include "condvar.h"
#include <setjmp.h>
#include <signal.h>

/***************************************************************************
* Test go-native features.
* Strategy: create a thread, use a pattern to tell the client to have it go
* native, and test things like fault handling while native.
*/

static SIGJMP_BUF mark;
static int count;

static void
handle_signal(int signal, siginfo_t *siginfo, ucontext_t *ucxt)
{
print("Got signal %d; count %d\n", signal, count);
count++;
SIGLONGJMP(mark, count);
}

THREAD_FUNC_RETURN_TYPE
thread_func(void *arg)
{
/* Trigger the client to have us go native. */
NOP_NOP_NOP;
print("Under DR?: %d\n", IS_UNDER_DR());
/* Now test tricky while-native things like a fault. */
if (SIGSETJMP(mark) == 0)
*(int *)arg = 42;
/* Try a default-ignore signal. */
if (SIGSETJMP(mark) == 0)
pthread_kill(pthread_self(), SIGURG);
return THREAD_FUNC_RETURN_ZERO;
}

int
main(int argc, const char *argv[])
{
intercept_signal(SIGSEGV, (handler_3_t)&handle_signal, false);
intercept_signal(SIGURG, (handler_3_t)&handle_signal, false);
thread_t thread = create_thread(thread_func, NULL);
join_thread(thread);
print("All done.\n");
return 0;
}
Loading