diff --git a/api/docs/API.doxy b/api/docs/API.doxy index 88afa15dbd3..cc85c688a40 100644 --- a/api/docs/API.doxy +++ b/api/docs/API.doxy @@ -1,5 +1,5 @@ # ********************************************************** -# Copyright (c) 2012-2015 Google, Inc. All rights reserved. +# Copyright (c) 2012-2017 Google, Inc. All rights reserved. # Copyright (c) 2007-2010 VMware, Inc. All rights reserved. # **********************************************************/ @@ -83,7 +83,7 @@ GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES GENERATE_BUGLIST = YES GENERATE_DEPRECATEDLIST= YES -# choices: linux, VMsafe, NYI_kernel_mediated_transfer_events +# choices: linux, VMsafe ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES diff --git a/api/docs/intro.dox b/api/docs/intro.dox index 829c6978a24..04f168d5f54 100644 --- a/api/docs/intro.dox +++ b/api/docs/intro.dox @@ -823,15 +823,13 @@ set of event callbacks. These events include the following: (dr_register_fork_init_event()) - Application library load and unload (dr_register_module_load_event(), dr_register_module_unload_event()) -\ifnot NYI_kernel_mediated_transfer_events - Application fault or exception (signal on Linux) (dr_register_exception_event(), dr_register_signal_event()) -\else - - Kernel-mediated control transfers: - - Application fault or exception - - Application APC (Asynchronous Procedure Call) or callback (Windows) - - Application signal (Linux) -\endif + - Kernel-mediated control transfers (dr_register_kernel_xfer_event()): + - Application APC (Asynchronous Procedure Call), callback, or exception + dispatcher execution (Windows) + - Application signal delivery (Linux) + - System call that changes the context - System call interception: pre-system call, post-system call, and system call filtering by number (dr_register_pre_syscall_event(), dr_register_post_syscall_event(), diff --git a/api/docs/release.dox b/api/docs/release.dox index abafb35c98d..25e19830de9 100644 --- a/api/docs/release.dox +++ b/api/docs/release.dox @@ -145,6 +145,10 @@ following minor compatibility changes: such as a static initialiser, will still need to be rewritten. DynamoRIO_PAGE_SIZE_COMPATIBILITY will be set automatically if the client targets version 6.2 or earlier. + - Removed DRMGR_PRIORITY_INSERT_CLS_ENTRY, DRMGR_PRIORITY_INSERT_CLS_EXIT, + DRMGR_PRIORITY_NAME_CLS_ENTRY, and DRMGR_PRIORITY_NAME_CLS_EXIT, as + the new kernel xfer event (drmgr_register_kernel_xfer_event()) removes the + need for them. Further non-compatibility-affecting changes include: @@ -200,6 +204,9 @@ Further non-compatibility-affecting changes include: identifier #DR_WINDOWS_VERSION_10_1703 to distinguish this major update. - Added support for Windows 10 1709. We provide an artificial version identifier #DR_WINDOWS_VERSION_10_1709 to distinguish this major update. + - Added an event for kernel-mediated control flow via + dr_register_kernel_xfer_event() with corresponding routines + drmgr_register_kernel_xfer_event() and drmgr_register_kernel_xfer_event_ex(). **************************************************
diff --git a/core/arch/arch.c b/core/arch/arch.c index bfffcfcb1d6..5c1eca7c529 100644 --- a/core/arch/arch.c +++ b/core/arch/arch.c @@ -3256,6 +3256,7 @@ dr_mcontext_to_priv_mcontext(priv_mcontext_t *dst, dr_mcontext_t *src) dst->xsp = save_xsp; } if (TEST(DR_MC_CONTROL, src->flags)) { + /* XXX i#2710: mc->lr should be under DR_MC_CONTROL */ dst->xsp = src->xsp; dst->xflags = src->xflags; dst->pc = src->pc; diff --git a/core/globals.h b/core/globals.h index b63654fbeb7..9360f6ef948 100644 --- a/core/globals.h +++ b/core/globals.h @@ -422,7 +422,9 @@ typedef struct _client_data_t { /* flags for dr_get_mcontext (i#117/PR 395156) */ bool mcontext_in_dcontext; bool suspended; + /* 2 other ways to point at a context for dr_{g,s}et_mcontext() */ priv_mcontext_t *cur_mc; + os_cxt_ptr_t os_cxt; } client_data_t; #else # define IS_CLIENT_THREAD(dcontext) false diff --git a/core/lib/globals_shared.h b/core/lib/globals_shared.h index 28779ff3e69..a22da6a1476 100644 --- a/core/lib/globals_shared.h +++ b/core/lib/globals_shared.h @@ -1874,19 +1874,26 @@ typedef union _dr_simd_t { /** Values for the flags field of dr_mcontext_t */ typedef enum { /** - * Selects the xdi, xsi, xbp, xbx, xdx, xcx, xax, and r8-r15 fields (i.e., + * On x86, selects the xdi, xsi, xbp, xbx, xdx, xcx, xax, and r8-r15 fields (i.e., * all of the general-purpose registers excluding xsp, xip, and xflags). + * On ARM, selects r0-r12 and r14. + * On AArch64, selects r0-r30. */ DR_MC_INTEGER = 0x01, +#ifdef AVOID_API_EXPORT +/* XXX i#2710: The link register should be under DR_MC_CONTROL */ +#endif /** - * Selects the xsp, xflags, and xip fields. - * \note: The xip field is only honored as an input for + * On x86, selects the xsp, xflags, and xip fields. + * On ARM, selects the sp, pc, and cpsr fields. + * On AArch64, selects the sp, pc, and nzcv fields. + * \note: The xip/pc field is only honored as an input for * dr_redirect_execution(), and as an output for system call * events. */ DR_MC_CONTROL = 0x02, /** - * Selects the ymm (and xmm) fields. This flag is ignored unless + * Selects the simd fields. This flag is ignored unless * dr_mcontext_xmm_fields_valid() returns true. If * dr_mcontext_xmm_fields_valid() returns false, the application values of * the multimedia registers remain in the registers themselves. diff --git a/core/lib/instrument.c b/core/lib/instrument.c index ffe94fafb35..3810d98364f 100644 --- a/core/lib/instrument.c +++ b/core/lib/instrument.c @@ -214,6 +214,7 @@ static callback_list_t module_unload_callbacks = {0,}; static callback_list_t filter_syscall_callbacks = {0,}; static callback_list_t pre_syscall_callbacks = {0,}; static callback_list_t post_syscall_callbacks = {0,}; +static callback_list_t kernel_xfer_callbacks = {0,}; #ifdef WINDOWS static callback_list_t exception_callbacks = {0,}; #else @@ -747,6 +748,7 @@ void free_all_callback_lists() free_callback_list(&filter_syscall_callbacks); free_callback_list(&pre_syscall_callbacks); free_callback_list(&post_syscall_callbacks); + free_callback_list(&kernel_xfer_callbacks); #ifdef WINDOWS free_callback_list(&exception_callbacks); #else @@ -1130,6 +1132,20 @@ dr_unregister_post_syscall_event(void (*func)(void *drcontext, int sysnum)) return remove_callback(&post_syscall_callbacks, (void (*)(void))func, true); } +void +dr_register_kernel_xfer_event(void (*func)(void *drcontext, + const dr_kernel_xfer_info_t *info)) +{ + add_callback(&kernel_xfer_callbacks, (void (*)(void))func, true); +} + +bool +dr_unregister_kernel_xfer_event(void (*func)(void *drcontext, + const dr_kernel_xfer_info_t *info)) +{ + return remove_callback(&kernel_xfer_callbacks, (void (*)(void))func, true); +} + #ifdef PROGRAM_SHEPHERDING void dr_register_security_event(void (*func)(void *drcontext, void *source_tag, @@ -2044,12 +2060,61 @@ instrument_invoke_another_syscall(dcontext_t *dcontext) return dcontext->client_data->invoke_another_syscall; } +bool +instrument_kernel_xfer(dcontext_t *dcontext, dr_kernel_xfer_type_t type, + os_cxt_ptr_t source_os_cxt, dr_mcontext_t *source_dmc, + priv_mcontext_t *source_mc, + app_pc target_pc, reg_t target_xsp, + os_cxt_ptr_t target_os_cxt, priv_mcontext_t *target_mc, + int sig) +{ + if (kernel_xfer_callbacks.num == 0) { + return false; + } + dr_kernel_xfer_info_t info; + info.type = type; + info.source_mcontext = NULL; + info.target_pc = target_pc; + info.target_xsp = target_xsp; + info.sig = sig; + dr_mcontext_t dr_mcontext; + dr_mcontext.size = sizeof(dr_mcontext); + dr_mcontext.flags = DR_MC_CONTROL | DR_MC_INTEGER; + + if (source_dmc != NULL) + info.source_mcontext = source_dmc; + else if (source_mc != NULL) { + if (priv_mcontext_to_dr_mcontext(&dr_mcontext, source_mc)) + info.source_mcontext = &dr_mcontext; + } else if (!is_os_cxt_ptr_null(source_os_cxt)) { + if (os_context_to_mcontext(&dr_mcontext, NULL, source_os_cxt)) + info.source_mcontext = &dr_mcontext; + } + /* Our compromise to reduce context copying is to provide the PC and XSP inline, + * and only get more if the user calls dr_get_mcontext(), which we support again + * without any copying if not used by taking in a raw os_context_t. + */ + dcontext->client_data->os_cxt = target_os_cxt; + dcontext->client_data->cur_mc = target_mc; + call_all(kernel_xfer_callbacks, int (*)(void *, const dr_kernel_xfer_info_t *), + (void *)dcontext, &info); + set_os_cxt_ptr_null(&dcontext->client_data->os_cxt); + dcontext->client_data->cur_mc = NULL; + return true; +} + #ifdef WINDOWS /* Notify user of exceptions. Note: not called for RaiseException */ bool instrument_exception(dcontext_t *dcontext, dr_exception_t *exception) { bool res = true; + /* Ensure that dr_get_mcontext() called from instrument_kernel_xfer() from + * dr_redirect_execution() will get the source context. + * cur_mc will later be clobbered by instrument_kernel_xfer() which is ok: + * the redirect ends the callback calling. + */ + dcontext->client_data->cur_mc = dr_mcontext_as_priv_mcontext(exception->mcontext); /* We short-circuit if any client wants to "own" the fault and not pass on. * This does violate the "priority order" of events where the last one is * supposed to have final say b/c it won't even see the event: but only one @@ -2058,6 +2123,7 @@ instrument_exception(dcontext_t *dcontext, dr_exception_t *exception) call_all_ret(res, = res &&, , exception_callbacks, bool (*)(void *, dr_exception_t *), (void *)dcontext, exception); + dcontext->client_data->cur_mc = NULL; return res; } #else @@ -6360,6 +6426,10 @@ dr_get_mcontext_priv(dcontext_t *dcontext, dr_mcontext_t *dmc, priv_mcontext_t * return true; } + if (!is_os_cxt_ptr_null(dcontext->client_data->os_cxt)) { + return os_context_to_mcontext(dmc, mc, dcontext->client_data->os_cxt); + } + if (dcontext->client_data->suspended) { /* A thread suspended by dr_suspend_all_other_threads() has its * context translated lazily here. @@ -6467,6 +6537,16 @@ dr_set_mcontext(void *drcontext, dr_mcontext_t *context) return false; return true; } + if (dcontext->client_data->cur_mc != NULL) { + return dr_mcontext_to_priv_mcontext(dcontext->client_data->cur_mc, context); + } + if (!is_os_cxt_ptr_null(dcontext->client_data->os_cxt)) { + /* It would be nice to fail for #DR_XFER_CALLBACK_RETURN but we'd need to + * store yet more state to do so. The pc will be ignored, and xsi + * changes will likely cause crashes. + */ + return mcontext_to_os_context(dcontext->client_data->os_cxt, context, NULL); + } /* copy the machine context to the dstack area created with * dr_prepare_for_call(). note that xmm0-5 copied there @@ -6527,6 +6607,23 @@ dr_redirect_execution(dr_mcontext_t *mcontext) dcontext->next_tag = canonicalize_pc_target(dcontext, mcontext->pc); dcontext->whereami = WHERE_FCACHE; set_last_exit(dcontext, (linkstub_t *)get_client_linkstub()); +#ifdef CLIENT_INTERFACE + if (kernel_xfer_callbacks.num > 0) { + /* This can only be called from a clean call or an exception event. + * For both of those we can get the current mcontext via dr_get_mcontext() + * (the latter b/c we explicitly store to cur_mc just for this use case). + */ + dr_mcontext_t src_dmc; + src_dmc.size = sizeof(src_dmc); + src_dmc.flags = DR_MC_CONTROL | DR_MC_INTEGER; + dr_get_mcontext(dcontext, &src_dmc); + if (instrument_kernel_xfer(dcontext, DR_XFER_CLIENT_REDIRECT, + osc_empty, &src_dmc, NULL, + dcontext->next_tag, mcontext->xsp, osc_empty, + dr_mcontext_as_priv_mcontext(mcontext), 0)) + dcontext->next_tag = canonicalize_pc_target(dcontext, mcontext->pc); + } +#endif transfer_to_dispatch(dcontext, dr_mcontext_as_priv_mcontext(mcontext), true/*full_DR_state*/); /* on success we won't get here */ diff --git a/core/lib/instrument.h b/core/lib/instrument.h index 0e14fc62f4f..58817a4a1dd 100644 --- a/core/lib/instrument.h +++ b/core/lib/instrument.h @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2010-2016 Google, Inc. All rights reserved. + * Copyright (c) 2010-2017 Google, Inc. All rights reserved. * Copyright (c) 2002-2010 VMware, Inc. All rights reserved. * **********************************************************/ @@ -102,6 +102,15 @@ bool instrument_filter_syscall(dcontext_t *dcontext, int sysnum); bool instrument_pre_syscall(dcontext_t *dcontext, int sysnum); void instrument_post_syscall(dcontext_t *dcontext, int sysnum); bool instrument_invoke_another_syscall(dcontext_t *dcontext); +/* returns whether a client event was called which might have changed the context */ +bool instrument_kernel_xfer(dcontext_t *dcontext, dr_kernel_xfer_type_t type, + /* only one of these 3 should be non-NULL */ + os_cxt_ptr_t source_os_cxt, dr_mcontext_t *source_dmc, + priv_mcontext_t *source_mc, + app_pc target_pc, reg_t target_xsp, + /* only one of these 2 should be non-NULL */ + os_cxt_ptr_t target_os_cxt, priv_mcontext_t *target_mc, + int sig); void instrument_nudge(dcontext_t *dcontext, client_id_t id, uint64 arg); /* post instrument_event() cleanup */ diff --git a/core/lib/instrument_api.h b/core/lib/instrument_api.h index fca628e77b5..8d1f8704c1d 100644 --- a/core/lib/instrument_api.h +++ b/core/lib/instrument_api.h @@ -918,6 +918,104 @@ bool dr_unregister_module_unload_event(void (*func)(void *drcontext, const module_data_t *info)); +/* DR_API EXPORT BEGIN */ +/** Identifies the type of kernel transfer for dr_register_kernel_xfer_event(). */ +typedef enum { + DR_XFER_SIGNAL_DELIVERY, /**< Signal delivery to application handler. */ + DR_XFER_SIGNAL_RETURN, /**< Signal return system call. */ + DR_XFER_APC_DISPATCHER, /**< Asynchronous procedure call dispatcher. */ + DR_XFER_EXCEPTION_DISPATCHER, /**< Exception dispatcher. */ + DR_XFER_RAISE_DISPATCHER, /**< Raised exception dispatcher. */ + DR_XFER_CALLBACK_DISPATCHER, /**< Callback dispatcher. */ + DR_XFER_CALLBACK_RETURN, /**< A return from a callback by syscall or interrupt. */ + DR_XFER_CONTINUE, /**< NtContinue system call. */ + DR_XFER_SET_CONTEXT_THREAD, /**< NtSetContextThread system call. */ + DR_XFER_CLIENT_REDIRECT, /**< dr_redirect_execution() or #DR_SIGNAL_REDIRECT. */ +} dr_kernel_xfer_type_t; + +/** Data structure passed for dr_register_kernel_xfer_event(). */ +typedef struct _dr_kernel_xfer_info_t { + /** The type of event. */ + dr_kernel_xfer_type_t type; + /** + * The source machine context which is about to be changed. This may be NULL + * if it is unknown, which is the case for #DR_XFER_CALLBACK_DISPATCHER. + */ + const dr_mcontext_t *source_mcontext; + /** + * The target program counter of the transfer. To obtain the full target state, + * call dr_get_mcontext(). (For efficiency purposes, only frequently needed + * state is included by default.) + */ + app_pc target_pc; + /** + * The target stack pointer of the transfer. To obtain the full target state, + * call dr_get_mcontext(). (For efficiency purposes, only frequently needed + * state is included by default.) + */ + reg_t target_xsp; + /** For #DR_XFER_SIGNAL_DELIVERY and #DR_XFER_SIGNAL_RETURN, the signal number. */ + int sig; +} dr_kernel_xfer_info_t; +/* DR_API EXPORT END */ + +DR_API +/** + * Registers a callback function for the kernel transfer event. DR + * calls \p func whenever the kernel is about to directly transfer control + * without an explicit user-mode control transfer instruction. + * This includes the following scenarios, which are distinguished by \p type: + * - On UNIX, a signal is about to be delivered to an application handler. + * This event differs from a dr_register_signal_event() callback in that the + * latter is called regardless of whether the application has a handler, + * and it does not provide the target context of any handler. + * - On UNIX, a signal return system call is about to be invoked. + * - On Windows, the asynchronous procedure call dispatcher is about to be invoked. + * - On Windows, the callback dispatcher is about to be invoked. + * - On Windows, the exception dispatcher is about to be invoked. + * - On Windows, the NtContinue system call is about to be invoked. + * - On Windows, the NtSetContextThread system call is about to be invoked. + * - On Windows, the NtCallbackReturn system call is about to be invoked. + * - On Windows, interrupt 0x2b is about to be invoked. + * - The client requests redirection using dr_redirect_execution() or + * #DR_SIGNAL_REDIRECT. + * + * The prior context, if known, is provided in \p info->source_mcontext; if + * unknown, \p info->source_mcontext is NULL. Multimedia state is typically + * not provided in \p info->source_mcontext, which is reflected in its \p flags. + * + * The target program counter and stack are provided in \p info->target_pc and \p + * info->target_xsp. Further target state can be examined by calling + * dr_get_mcontext() and modified by calling dr_set_mcontext(). Changes to the + * target state, including the pc, are supported for all cases except + * NtCallbackReturn and interrupt 0x2b. However, dr_get_mcontext() and + * dr_set_mcontext() are limited for the Windows system calls NtContinue and + * NtSetContextThread to the ContextFlags set by the application: dr_get_mcontext() + * will adjust the dr_mcontext_t.flags to reflect what's available, and + * dr_set_mcontext() will only set what's also set in ContextFlags. Given the + * disparity in how Ebp/Rbp is handled (in #DR_MC_INTEGER but in CONTEXT_CONTROL), + * clients that care about that register are better off using system call events + * instead of kernel transfer events to take actions on these two system calls. + * + * This is a convenience event: all of the above events can be detected using + * combinations of other events. This event is meant to be used to identify all + * changes in the program counter that do not arise from explicit control flow + * instructions. + */ +void +dr_register_kernel_xfer_event(void (*func)(void *drcontext, + const dr_kernel_xfer_info_t *info)); + +DR_API +/** + * Unregister a callback function for the kernel transfer event. + * \return true if unregistration is successful and false if it is not + * (e.g., \p func was not registered). + */ +bool +dr_unregister_kernel_xfer_event(void (*func)(void *drcontext, + const dr_kernel_xfer_info_t *info)); + /* DR_API EXPORT BEGIN */ #ifdef WINDOWS /* DR_API EXPORT END */ @@ -930,7 +1028,7 @@ dr_unregister_module_unload_event(void (*func)(void *drcontext, typedef struct _dr_exception_t { /** * Machine context at exception point. The client should not - * change \p mcontext.flags: it should remain DR_MC_ALL. + * change \p mcontext->flags: it should remain DR_MC_ALL. */ dr_mcontext_t *mcontext; EXCEPTION_RECORD *record; /**< Win32 exception record. */ @@ -5339,7 +5437,7 @@ DR_API /** * Returns true if the xmm fields in dr_mcontext_t are valid * (i.e., whether the underlying processor supports SSE). - * \note If DR_MC_MULTIMEDIA is not specified when calling dr_get_mcontext(), + * \note If #DR_MC_MULTIMEDIA is not specified when calling dr_get_mcontext(), * the xmm fields will not be filled in regardless of the return value * of this routine. */ @@ -5368,10 +5466,20 @@ DR_API * dr_register_exit_event()) * - A thread init event (dr_register_thread_init_event()) for all but * the initial thread. - * - * Even when DR_MC_CONTROL is specified, does NOT copy the pc field, + * - A kernel transfer event (dr_register_kernel_xfer_event()). Here the obtained + * context is the target context of the transfer, not the source (about to be + * changed) context. For Windows system call event types #DR_XFER_CONTINUE and + * #DR_XFER_SET_CONTEXT_THREAD, only the portions of the context selected by the + * application are available. The \p flags field of \p context is adjusted to + * reflect which fields were returned. Given the disparity in how Ebp/Rbp is + * handled (in #DR_MC_INTEGER but in CONTEXT_CONTROL), clients that care about that + * register are better off using system call events instead of kernel transfer events + * to take actions on these two system calls. + * + * Even when #DR_MC_CONTROL is specified, does NOT copy the pc field, * except for system call events, when it will point at the - * post-syscall address. + * post-syscall address, and kernel transfer events, when it will point to the + * target pc. * * Returns false if called from the init event or the initial thread's * init event; returns true otherwise (cannot distinguish whether the @@ -5389,7 +5497,7 @@ DR_API * * \note NUM_SIMD_SLOTS in the dr_mcontext_t.xmm array are filled in, * but only if dr_mcontext_xmm_fields_valid() returns true and - * DR_MC_MULTIMEDIA is set in the flags field. + * #DR_MC_MULTIMEDIA is set in the flags field. * * \note The context is the context saved at the dr_insert_clean_call() or * dr_prepare_for_call() points. It does not correct for any registers saved @@ -5414,12 +5522,21 @@ DR_API * - A pre- or post-syscall event (dr_register_pre_syscall_event(), * dr_register_post_syscall_event()) * dr_register_thread_exit_event()) - * - Basic block or trace creation events (dr_register_bb_event(), - * dr_register_trace_event()), but for basic block creation only when the - * basic block callback parameters \p for_trace and \p translating are - * false, and for trace creation only when \p translating is false. - * - * Ignores the pc field. + * - A kernel transfer event (dr_register_kernel_xfer_event()) other than + * #DR_XFER_CALLBACK_RETURN. Here the modified context is the target context of + * the transfer, not the source (about to be changed) context. For Windows system + * call event types #DR_XFER_CONTINUE and #DR_XFER_SET_CONTEXT_THREAD, only the + * portions of the context selected by the application can be changed. The \p + * flags field of \p context is adjusted to reflect which fields these are. Given + * the disparity in how Ebp/Rbp is handled (in #DR_MC_INTEGER but in + * CONTEXT_CONTROL), clients that care about that register are better off using + * system call events instead of kernel transfer events to take actions on these + * two system calls. - Basic block or trace creation events + * (dr_register_bb_event(), dr_register_trace_event()), but for basic block + * creation only when the basic block callback parameters \p for_trace and \p + * translating are false, and for trace creation only when \p translating is false. + * + * Ignores the pc field, except for kernel transfer events. * * If the size field of \p context is invalid, this routine will * return false. A dr_mcontext_t obtained from DR will have the size field set. @@ -5453,7 +5570,7 @@ DR_API * of fields is not suported. * * \note dr_get_mcontext() can be used to get the register state (except pc) - * saved in dr_insert_clean_call() or dr_prepare_for_call() + * saved in dr_insert_clean_call() or dr_prepare_for_call(). * * \note If floating point state was saved by dr_prepare_for_call() or * dr_insert_clean_call() it is not restored (other than the valid xmm @@ -5472,7 +5589,7 @@ DR_API * \note This routine may only be called from a clean call from the cache. It can not be * called from any registered event callback except the exception event * (dr_register_exception_event()). From a signal event callback, use the - * DR_SIGNAL_REDIRECT return value rather than calling this routine. + * #DR_SIGNAL_REDIRECT return value rather than calling this routine. * * \note For ARM, to redirect execution to a Thumb target (#DR_ISA_ARM_THUMB), * set the least significant bit of the mcontext pc to 1. Reference @@ -5527,11 +5644,13 @@ DR_API /** * Copies the machine state in \p src into \p dst. Sets the \p * ContextFlags field of \p dst to reflect the \p flags field of \p - * src. + * src. However, CONTEXT_CONTROL includes Ebp/Rbp, while that's under + * #DR_MC_INTEGER, so we recommend always setting both #DR_MC_INTEGER + * and #DR_MC_CONTROL when calling this routine. * * It is up to the caller to ensure that \p dst is allocated and * initialized properly in order to contain multimedia processor - * state, if DR_MC_MULTIMEDIA is set in the \p flags field of \p src. + * state, if #DR_MC_MULTIMEDIA is set in the \p flags field of \p src. * * The current segment register values are filled in under the assumption * that this context is for the calling thread. diff --git a/core/unix/os.c b/core/unix/os.c index 4cbcef34af7..e9df9604d22 100644 --- a/core/unix/os.c +++ b/core/unix/os.c @@ -3603,7 +3603,7 @@ thread_get_mcontext(thread_record_t *tr, priv_mcontext_t *mc) if (ostd->suspend_count == 0) return false; ASSERT(ostd->suspended_sigcxt != NULL); - sigcontext_to_mcontext(mc, ostd->suspended_sigcxt); + sigcontext_to_mcontext(mc, ostd->suspended_sigcxt, DR_MC_ALL); return true; } @@ -3619,7 +3619,33 @@ thread_set_mcontext(thread_record_t *tr, priv_mcontext_t *mc) if (ostd->suspend_count == 0) return false; ASSERT(ostd->suspended_sigcxt != NULL); - mcontext_to_sigcontext(ostd->suspended_sigcxt, mc); + mcontext_to_sigcontext(ostd->suspended_sigcxt, mc, DR_MC_ALL); + return true; +} + +/* Only one of mc and dmc can be non-NULL. */ +bool +os_context_to_mcontext(dr_mcontext_t *dmc, priv_mcontext_t *mc, os_cxt_ptr_t osc) +{ + if (dmc != NULL) + sigcontext_to_mcontext(dr_mcontext_as_priv_mcontext(dmc), &osc, dmc->flags); + else if (mc != NULL) + sigcontext_to_mcontext(mc, &osc, DR_MC_ALL); + else + return false; + return true; +} + +/* Only one of mc and dmc can be non-NULL. */ +bool +mcontext_to_os_context(os_cxt_ptr_t osc, dr_mcontext_t *dmc, priv_mcontext_t *mc) +{ + if (dmc != NULL) + mcontext_to_sigcontext(&osc, dr_mcontext_as_priv_mcontext(dmc), dmc->flags); + else if (mc != NULL) + mcontext_to_sigcontext(&osc, mc, DR_MC_ALL); + else + return false; return true; } diff --git a/core/unix/os_exports.h b/core/unix/os_exports.h index 5808d02327b..fabfa349387 100644 --- a/core/unix/os_exports.h +++ b/core/unix/os_exports.h @@ -365,6 +365,27 @@ typedef struct _sig_full_cxt_t { void *fp_simd_state; } sig_full_cxt_t; +typedef sig_full_cxt_t os_cxt_ptr_t; + +extern os_cxt_ptr_t osc_empty; + +static inline bool +is_os_cxt_ptr_null(os_cxt_ptr_t osc) +{ + return osc.sc == NULL; +} + +static inline void +set_os_cxt_ptr_null(os_cxt_ptr_t *osc) +{ + *osc = osc_empty; +} + +/* Only one of mc and dmc can be non-NULL. */ +bool os_context_to_mcontext(dr_mcontext_t *dmc, priv_mcontext_t *mc, os_cxt_ptr_t osc); +/* Only one of mc and dmc can be non-NULL. */ +bool mcontext_to_os_context(os_cxt_ptr_t osc, dr_mcontext_t *dmc, priv_mcontext_t *mc); + void * #ifdef MACOS create_clone_record(dcontext_t *dcontext, reg_t *app_xsp, diff --git a/core/unix/os_private.h b/core/unix/os_private.h index 754f4d6b203..84fa273e39e 100644 --- a/core/unix/os_private.h +++ b/core/unix/os_private.h @@ -302,10 +302,12 @@ signal_handle_close(dcontext_t *dcontext, file_t fd); #endif void -sigcontext_to_mcontext(priv_mcontext_t *mc, sig_full_cxt_t *sc_full); +sigcontext_to_mcontext(priv_mcontext_t *mc, sig_full_cxt_t *sc_full, + dr_mcontext_flags_t flags); void -mcontext_to_sigcontext(sig_full_cxt_t *sc_full, priv_mcontext_t *mc); +mcontext_to_sigcontext(sig_full_cxt_t *sc_full, priv_mcontext_t *mc, + dr_mcontext_flags_t flags); bool set_default_signal_action(int sig); diff --git a/core/unix/signal.c b/core/unix/signal.c index 561837f2717..80e71bb7f5d 100644 --- a/core/unix/signal.c +++ b/core/unix/signal.c @@ -207,6 +207,8 @@ static kernel_sigset_t init_sigmask; static bool removed_sig_handler; #endif +os_cxt_ptr_t osc_empty; + /**** function prototypes ***********************************************/ /* in x86.asm */ @@ -2213,59 +2215,74 @@ sig_full_initialize(sig_full_cxt_t *sc_full, kernel_ucontext_t *ucxt) } void -sigcontext_to_mcontext(priv_mcontext_t *mc, sig_full_cxt_t *sc_full) +sigcontext_to_mcontext(priv_mcontext_t *mc, sig_full_cxt_t *sc_full, + dr_mcontext_flags_t flags) { sigcontext_t *sc = sc_full->sc; ASSERT(mc != NULL && sc != NULL); #ifdef X86 - mc->xax = sc->SC_XAX; - mc->xbx = sc->SC_XBX; - mc->xcx = sc->SC_XCX; - mc->xdx = sc->SC_XDX; - mc->xsi = sc->SC_XSI; - mc->xdi = sc->SC_XDI; - mc->xbp = sc->SC_XBP; - mc->xsp = sc->SC_XSP; - mc->xflags = sc->SC_XFLAGS; - mc->pc = (app_pc) sc->SC_XIP; + if (TEST(DR_MC_INTEGER, flags)) { + mc->xax = sc->SC_XAX; + mc->xbx = sc->SC_XBX; + mc->xcx = sc->SC_XCX; + mc->xdx = sc->SC_XDX; + mc->xsi = sc->SC_XSI; + mc->xdi = sc->SC_XDI; + mc->xbp = sc->SC_XBP; # ifdef X64 - mc->r8 = sc->SC_FIELD(r8); - mc->r9 = sc->SC_FIELD(r9); - mc->r10 = sc->SC_FIELD(r10); - mc->r11 = sc->SC_FIELD(r11); - mc->r12 = sc->SC_FIELD(r12); - mc->r13 = sc->SC_FIELD(r13); - mc->r14 = sc->SC_FIELD(r14); - mc->r15 = sc->SC_FIELD(r15); + mc->r8 = sc->SC_FIELD(r8); + mc->r9 = sc->SC_FIELD(r9); + mc->r10 = sc->SC_FIELD(r10); + mc->r11 = sc->SC_FIELD(r11); + mc->r12 = sc->SC_FIELD(r12); + mc->r13 = sc->SC_FIELD(r13); + mc->r14 = sc->SC_FIELD(r14); + mc->r15 = sc->SC_FIELD(r15); # endif /* X64 */ + } + if (TEST(DR_MC_CONTROL, flags)) { + mc->xsp = sc->SC_XSP; + mc->xflags = sc->SC_XFLAGS; + mc->pc = (app_pc) sc->SC_XIP; + } #elif defined(AARCH64) - memcpy(&mc->r0, &sc->SC_FIELD(regs[0]), sizeof(mc->r0) * 31); - mc->sp = sc->SC_FIELD(sp); - mc->pc = (void *)sc->SC_FIELD(pc); - mc->nzcv = sc->SC_FIELD(pstate); + if (TEST(DR_MC_INTEGER, flags)) + memcpy(&mc->r0, &sc->SC_FIELD(regs[0]), sizeof(mc->r0) * 31); + if (TEST(DR_MC_CONTROL, flags)) { + /* XXX i#2710: the link register should be under DR_MC_CONTROL */ + mc->sp = sc->SC_FIELD(sp); + mc->pc = (void *)sc->SC_FIELD(pc); + mc->nzcv = sc->SC_FIELD(pstate); + } #elif defined (ARM) - mc->r0 = sc->SC_FIELD(arm_r0); - mc->r1 = sc->SC_FIELD(arm_r1); - mc->r2 = sc->SC_FIELD(arm_r2); - mc->r3 = sc->SC_FIELD(arm_r3); - mc->r4 = sc->SC_FIELD(arm_r4); - mc->r5 = sc->SC_FIELD(arm_r5); - mc->r6 = sc->SC_FIELD(arm_r6); - mc->r7 = sc->SC_FIELD(arm_r7); - mc->r8 = sc->SC_FIELD(arm_r8); - mc->r9 = sc->SC_FIELD(arm_r9); - mc->r10 = sc->SC_FIELD(arm_r10); - mc->r11 = sc->SC_FIELD(arm_fp); - mc->r12 = sc->SC_FIELD(arm_ip); - mc->r13 = sc->SC_FIELD(arm_sp); - mc->r14 = sc->SC_FIELD(arm_lr); - mc->r15 = sc->SC_FIELD(arm_pc); - mc->cpsr= sc->SC_FIELD(arm_cpsr); + if (TEST(DR_MC_INTEGER, flags)) { + mc->r0 = sc->SC_FIELD(arm_r0); + mc->r1 = sc->SC_FIELD(arm_r1); + mc->r2 = sc->SC_FIELD(arm_r2); + mc->r3 = sc->SC_FIELD(arm_r3); + mc->r4 = sc->SC_FIELD(arm_r4); + mc->r5 = sc->SC_FIELD(arm_r5); + mc->r6 = sc->SC_FIELD(arm_r6); + mc->r7 = sc->SC_FIELD(arm_r7); + mc->r8 = sc->SC_FIELD(arm_r8); + mc->r9 = sc->SC_FIELD(arm_r9); + mc->r10 = sc->SC_FIELD(arm_r10); + mc->r11 = sc->SC_FIELD(arm_fp); + mc->r12 = sc->SC_FIELD(arm_ip); + /* XXX i#2710: the link register should be under DR_MC_CONTROL */ + mc->r14 = sc->SC_FIELD(arm_lr); + } + if (TEST(DR_MC_CONTROL, flags)) { + mc->r13 = sc->SC_FIELD(arm_sp); + mc->r15 = sc->SC_FIELD(arm_pc); + mc->cpsr= sc->SC_FIELD(arm_cpsr); + } # ifdef X64 # error NYI on AArch64 # endif /* X64 */ #endif /* X86/ARM */ - sigcontext_to_mcontext_simd(mc, sc_full); + if (TEST(DR_MC_MULTIMEDIA, flags)) + sigcontext_to_mcontext_simd(mc, sc_full); } /* Note that unlike mcontext_to_context(), this routine does not fill in @@ -2284,59 +2301,75 @@ sigcontext_to_mcontext(priv_mcontext_t *mc, sig_full_cxt_t *sc_full) * and tweaks that context, so cpsr should be there. */ void -mcontext_to_sigcontext(sig_full_cxt_t *sc_full, priv_mcontext_t *mc) +mcontext_to_sigcontext(sig_full_cxt_t *sc_full, priv_mcontext_t *mc, + dr_mcontext_flags_t flags) { sigcontext_t *sc = sc_full->sc; ASSERT(mc != NULL && sc != NULL); #ifdef X86 - sc->SC_XAX = mc->xax; - sc->SC_XBX = mc->xbx; - sc->SC_XCX = mc->xcx; - sc->SC_XDX = mc->xdx; - sc->SC_XSI = mc->xsi; - sc->SC_XDI = mc->xdi; - sc->SC_XBP = mc->xbp; - sc->SC_XSP = mc->xsp; - sc->SC_XFLAGS = mc->xflags; - sc->SC_XIP = (ptr_uint_t) mc->pc; + if (TEST(DR_MC_INTEGER, flags)) { + sc->SC_XAX = mc->xax; + sc->SC_XBX = mc->xbx; + sc->SC_XCX = mc->xcx; + sc->SC_XDX = mc->xdx; + sc->SC_XSI = mc->xsi; + sc->SC_XDI = mc->xdi; + sc->SC_XBP = mc->xbp; # ifdef X64 - sc->SC_FIELD(r8) = mc->r8; - sc->SC_FIELD(r9) = mc->r9; - sc->SC_FIELD(r10) = mc->r10; - sc->SC_FIELD(r11) = mc->r11; - sc->SC_FIELD(r12) = mc->r12; - sc->SC_FIELD(r13) = mc->r13; - sc->SC_FIELD(r14) = mc->r14; - sc->SC_FIELD(r15) = mc->r15; + sc->SC_FIELD(r8) = mc->r8; + sc->SC_FIELD(r9) = mc->r9; + sc->SC_FIELD(r10) = mc->r10; + sc->SC_FIELD(r11) = mc->r11; + sc->SC_FIELD(r12) = mc->r12; + sc->SC_FIELD(r13) = mc->r13; + sc->SC_FIELD(r14) = mc->r14; + sc->SC_FIELD(r15) = mc->r15; # endif /* X64 */ + } + if (TEST(DR_MC_CONTROL, flags)) { + sc->SC_XSP = mc->xsp; + sc->SC_XFLAGS = mc->xflags; + sc->SC_XIP = (ptr_uint_t) mc->pc; + } #elif defined(AARCH64) - memcpy(&sc->SC_FIELD(regs[0]), &mc->r0, sizeof(mc->r0) * 31); - sc->SC_FIELD(sp) = mc->sp; - sc->SC_FIELD(pc) = (ptr_uint_t)mc->pc; - sc->SC_FIELD(pstate) = mc->nzcv; + if (TEST(DR_MC_INTEGER, flags)) { + memcpy(&sc->SC_FIELD(regs[0]), &mc->r0, sizeof(mc->r0) * 31); + } + if (TEST(DR_MC_CONTROL, flags)) { + /* XXX i#2710: the link register should be under DR_MC_CONTROL */ + sc->SC_FIELD(sp) = mc->sp; + sc->SC_FIELD(pc) = (ptr_uint_t)mc->pc; + sc->SC_FIELD(pstate) = mc->nzcv; + } #elif defined(ARM) - sc->SC_FIELD(arm_r0) = mc->r0; - sc->SC_FIELD(arm_r1) = mc->r1; - sc->SC_FIELD(arm_r2) = mc->r2; - sc->SC_FIELD(arm_r3) = mc->r3; - sc->SC_FIELD(arm_r4) = mc->r4; - sc->SC_FIELD(arm_r5) = mc->r5; - sc->SC_FIELD(arm_r6) = mc->r6; - sc->SC_FIELD(arm_r7) = mc->r7; - sc->SC_FIELD(arm_r8) = mc->r8; - sc->SC_FIELD(arm_r9) = mc->r9; - sc->SC_FIELD(arm_r10) = mc->r10; - sc->SC_FIELD(arm_fp) = mc->r11; - sc->SC_FIELD(arm_ip) = mc->r12; - sc->SC_FIELD(arm_sp) = mc->r13; - sc->SC_FIELD(arm_lr) = mc->r14; - sc->SC_FIELD(arm_pc) = mc->r15; - sc->SC_FIELD(arm_cpsr)= mc->cpsr; + if (TEST(DR_MC_INTEGER, flags)) { + sc->SC_FIELD(arm_r0) = mc->r0; + sc->SC_FIELD(arm_r1) = mc->r1; + sc->SC_FIELD(arm_r2) = mc->r2; + sc->SC_FIELD(arm_r3) = mc->r3; + sc->SC_FIELD(arm_r4) = mc->r4; + sc->SC_FIELD(arm_r5) = mc->r5; + sc->SC_FIELD(arm_r6) = mc->r6; + sc->SC_FIELD(arm_r7) = mc->r7; + sc->SC_FIELD(arm_r8) = mc->r8; + sc->SC_FIELD(arm_r9) = mc->r9; + sc->SC_FIELD(arm_r10) = mc->r10; + sc->SC_FIELD(arm_fp) = mc->r11; + sc->SC_FIELD(arm_ip) = mc->r12; + /* XXX i#2710: the link register should be under DR_MC_CONTROL */ + sc->SC_FIELD(arm_lr) = mc->r14; + } + if (TEST(DR_MC_CONTROL, flags)) { + sc->SC_FIELD(arm_sp) = mc->r13; + sc->SC_FIELD(arm_pc) = mc->r15; + sc->SC_FIELD(arm_cpsr)= mc->cpsr; + } # ifdef X64 # error NYI on AArch64 # endif /* X64 */ #endif /* X86/ARM */ - mcontext_to_sigcontext_simd(sc_full, mc); + if (TEST(DR_MC_MULTIMEDIA, flags)) + mcontext_to_sigcontext_simd(sc_full, mc); } static void @@ -2344,7 +2377,7 @@ ucontext_to_mcontext(priv_mcontext_t *mc, kernel_ucontext_t *uc) { sig_full_cxt_t sc_full; sig_full_initialize(&sc_full, uc); - sigcontext_to_mcontext(mc, &sc_full); + sigcontext_to_mcontext(mc, &sc_full, DR_MC_ALL); } static void @@ -2352,7 +2385,7 @@ mcontext_to_ucontext(kernel_ucontext_t *uc, priv_mcontext_t *mc) { sig_full_cxt_t sc_full; sig_full_initialize(&sc_full, uc); - mcontext_to_sigcontext(&sc_full, mc); + mcontext_to_sigcontext(&sc_full, mc, DR_MC_ALL); } #ifdef AARCHXX @@ -2565,7 +2598,7 @@ thread_set_self_mcontext(priv_mcontext_t *mc) #if defined(LINUX) && defined(X86) sc_full.sc->fpstate = NULL; /* for mcontext_to_sigcontext */ #endif - mcontext_to_sigcontext(&sc_full, mc); + mcontext_to_sigcontext(&sc_full, mc, DR_MC_ALL); thread_set_segment_registers(sc_full.sc); /* sigreturn takes the mode from cpsr */ IF_ARM(set_pc_mode_in_cpsr(sc_full.sc, @@ -3158,9 +3191,23 @@ copy_frame_to_pending(dcontext_t *dcontext, int sig, sigframe_rt_t *frame /* transfer control from signal handler to fcache return routine */ static void -transfer_from_sig_handler_to_fcache_return(dcontext_t *dcontext, sigcontext_t *sc, - app_pc next_pc, linkstub_t *last_exit) +transfer_from_sig_handler_to_fcache_return(dcontext_t *dcontext, kernel_ucontext_t *uc, + sigcontext_t *sc_interrupted, int sig, + app_pc next_pc, + linkstub_t *last_exit, bool is_kernel_xfer) { + sigcontext_t *sc = SIGCXT_FROM_UCXT(uc); +#ifdef CLIENT_INTERFACE + if (is_kernel_xfer) { + sig_full_cxt_t sc_interrupted_full = { sc_interrupted, NULL/*not provided*/ }; + sig_full_cxt_t sc_full; + sig_full_initialize(&sc_full, uc); + sc->SC_XIP = (ptr_uint_t) next_pc; + if (instrument_kernel_xfer(dcontext, DR_XFER_SIGNAL_DELIVERY, sc_interrupted_full, + NULL, NULL, next_pc, sc->SC_XSP, sc_full, NULL, sig)) + next_pc = canonicalize_pc_target(dcontext, (app_pc)sc->SC_XIP); + } +#endif dcontext->next_tag = canonicalize_pc_target(dcontext, next_pc); IF_ARM(dr_set_isa_mode(dcontext, get_pc_mode_from_cpsr(sc), NULL)); @@ -3246,7 +3293,7 @@ send_signal_to_client(dcontext_t *dcontext, int sig, sigframe_rt_t *frame, fragment_t wrapper; si.raw_mcontext_valid = true; sigcontext_to_mcontext(dr_mcontext_as_priv_mcontext(si.raw_mcontext), - &raw_sc_full); + &raw_sc_full, si.raw_mcontext->flags); /* i#207: fragment tag and fcache start pc on fault. */ /* FIXME: we should avoid the fragment_pclookup since it is expensive * and since we already did the work of a lookup when translating @@ -3288,7 +3335,8 @@ send_signal_to_client(dcontext_t *dcontext, int sig, sigframe_rt_t *frame, CLIENT_ASSERT(si.raw_mcontext->flags == DR_MC_ALL, "signal mcontext flags cannot be changed"); mcontext_to_sigcontext(&raw_sc_full, - dr_mcontext_as_priv_mcontext(si.raw_mcontext)); + dr_mcontext_as_priv_mcontext(si.raw_mcontext), + si.raw_mcontext->flags); } heap_free(dcontext, si.mcontext, sizeof(*si.mcontext) HEAPACCT(ACCT_OTHER)); heap_free(dcontext, si.raw_mcontext, sizeof(*si.raw_mcontext) HEAPACCT(ACCT_OTHER)); @@ -3299,7 +3347,7 @@ send_signal_to_client(dcontext_t *dcontext, int sig, sigframe_rt_t *frame, static bool handle_client_action_from_cache(dcontext_t *dcontext, int sig, dr_signal_action_t action, sigframe_rt_t *our_frame, sigcontext_t *sc_orig, - bool blocked) + sigcontext_t *sc_interrupted, bool blocked) { thread_sig_info_t *info = (thread_sig_info_t *) dcontext->signal_field; kernel_ucontext_t *uc = get_ucontext_from_rt_frame(our_frame); @@ -3310,11 +3358,11 @@ handle_client_action_from_cache(dcontext_t *dcontext, int sig, dr_signal_action_ if (action == DR_SIGNAL_REDIRECT) { /* send_signal_to_client copied mcontext into our * master_signal_handler frame, so we set up for fcache_return w/ - * the mcontext state and this as next_tag + * our frame's state */ - ucontext_to_mcontext(get_mcontext(dcontext), uc); - transfer_from_sig_handler_to_fcache_return(dcontext, sc, (app_pc) sc->SC_XIP, - (linkstub_t *) get_asynch_linkstub()); + transfer_from_sig_handler_to_fcache_return + (dcontext, uc, sc_interrupted, sig, + (app_pc) sc->SC_XIP, (linkstub_t *) get_asynch_linkstub(), true); if (is_building_trace(dcontext)) { LOG(THREAD, LOG_ASYNCH, 3, "\tsquashing trace-in-progress\n"); trace_abort(dcontext); @@ -4038,13 +4086,16 @@ record_pending_signal(dcontext_t *dcontext, int sig, kernel_ucontext_t *ucxt, f = fragment_pclookup(dcontext, (cache_pc)sc->SC_XIP, &wrapper); sc_orig = *sc; translate_sigcontext(dcontext, ucxt, true/*shouldn't fail*/, f); + /* make a copy before send_signal_to_client() tweaks it */ + sigcontext_t sc_interrupted = *sc; action = send_signal_to_client(dcontext, sig, frame, &sc_orig, access_address, true/*blocked*/, f); /* For blocked signal early event we disallow BYPASS (xref i#182/PR 449996) */ CLIENT_ASSERT(action != DR_SIGNAL_BYPASS, "cannot bypass a blocked signal event"); if (!handle_client_action_from_cache(dcontext, sig, action, frame, - &sc_orig, true/*blocked*/)) { + &sc_orig, &sc_interrupted, + true/*blocked*/)) { ostd->processing_signal--; return; } @@ -4336,7 +4387,7 @@ compute_memory_target(dcontext_t *dcontext, cache_pc instr_cache_pc, */ static bool check_for_modified_code(dcontext_t *dcontext, cache_pc instr_cache_pc, - sigcontext_t *sc, byte *target, bool native_state) + kernel_ucontext_t *uc, byte *target, bool native_state) { /* special case: we expect a seg fault for executable regions * that were writable and marked read-only by us. @@ -4355,7 +4406,7 @@ check_for_modified_code(dcontext_t *dcontext, cache_pc instr_cache_pc, app_pc next_pc, translated_pc = NULL; fragment_t *f = NULL; fragment_t wrapper; - ASSERT((cache_pc)sc->SC_XIP == instr_cache_pc); + ASSERT((cache_pc)SIGCXT_FROM_UCXT(uc)->SC_XIP == instr_cache_pc); if (!native_state) { /* For safe recreation we need to either be couldbelinking or hold * the initexit lock (to keep someone from flushing current @@ -4389,8 +4440,9 @@ check_for_modified_code(dcontext_t *dcontext, cache_pc instr_cache_pc, } else { ASSERT(!native_state); /* Do not resume execution in cache, go back to dispatch. */ - transfer_from_sig_handler_to_fcache_return(dcontext, sc, next_pc, - (linkstub_t *) get_selfmod_linkstub()); + transfer_from_sig_handler_to_fcache_return + (dcontext, uc, NULL, SIGSEGV, next_pc, + (linkstub_t *) get_selfmod_linkstub(), false); /* now have master_signal_handler return */ return true; } @@ -4742,7 +4794,7 @@ master_signal_handler_C(byte *xsp) */ if (is_write && !is_couldbelinking(dcontext) && OWN_NO_LOCKS(dcontext) && - check_for_modified_code(dcontext, pc, sc, target, true/*native*/)) + check_for_modified_code(dcontext, pc, ucxt, target, true/*native*/)) break; abort_on_fault(dcontext, DUMPCORE_CLIENT_EXCEPTION, pc, target, sig, frame, exception_label_client, (sig == SIGSEGV) ? "SEGV" : "BUS", @@ -4783,12 +4835,14 @@ master_signal_handler_C(byte *xsp) * own gencode. client_exception_event() won't return if client * wants to re-execute faulting instr. */ + sigcontext_t sc_interrupted = *get_sigcontext_from_rt_frame(frame); dr_signal_action_t action = send_signal_to_client(dcontext, sig, frame, sc, target, false/*!blocked*/, NULL); if (action != DR_SIGNAL_DELIVER && /* for delivery, continue below */ !handle_client_action_from_cache(dcontext, sig, action, frame, - sc, false/*!blocked*/)) { + sc, &sc_interrupted, + false/*!blocked*/)) { /* client handled fault */ break; } @@ -4847,7 +4901,7 @@ master_signal_handler_C(byte *xsp) * that were writable and marked read-only by us. */ if (is_write && - check_for_modified_code(dcontext, pc, sc, target, false/*!native*/)) { + check_for_modified_code(dcontext, pc, ucxt, target, false/*!native*/)) { /* it was our signal, so don't pass to app -- return now */ break; } @@ -4931,7 +4985,8 @@ execute_handler_from_cache(dcontext_t *dcontext, int sig, sigframe_rt_t *our_fra { thread_sig_info_t *info = (thread_sig_info_t *) dcontext->signal_field; /* we want to modify the sc in DR's frame */ - sigcontext_t *sc = get_sigcontext_from_rt_frame(our_frame); + kernel_ucontext_t *uc = get_ucontext_from_rt_frame(our_frame); + sigcontext_t *sc = SIGCXT_FROM_UCXT(uc); kernel_sigset_t blocked; /* Need to get xsp now before get new dcontext. * This is the translated xsp, so we avoid PR 306410 (cleancall arg fault @@ -4942,11 +4997,12 @@ execute_handler_from_cache(dcontext_t *dcontext, int sig, sigframe_rt_t *our_fra * interruption point */); #ifdef CLIENT_INTERFACE + sigcontext_t sc_interrupted = *sc; dr_signal_action_t action = send_signal_to_client(dcontext, sig, our_frame, sc_orig, access_address, false/*not blocked*/, f); if (!handle_client_action_from_cache(dcontext, sig, action, our_frame, sc_orig, - false/*!blocked*/)) + &sc_interrupted, false/*!blocked*/)) return false; #else if (info->app_sigaction[sig] == NULL || @@ -4978,6 +5034,7 @@ execute_handler_from_cache(dcontext_t *dcontext, int sig, sigframe_rt_t *our_fra /* copy frame to appropriate stack and convert to non-rt if necessary */ copy_frame_to_stack(dcontext, sig, our_frame, (void *)xsp, false/*!pending*/); LOG(THREAD, LOG_ASYNCH, 3, "\tcopied frame from "PFX" to "PFX"\n", our_frame, xsp); + sigcontext_t *app_sc = get_sigcontext_from_app_frame(info, sig, (void *)xsp); /* Because of difficulties determining when/if a signal handler * returns, we do what the kernel does: abandon all of our current @@ -5034,10 +5091,10 @@ execute_handler_from_cache(dcontext_t *dcontext, int sig, sigframe_rt_t *our_fra * and go through dispatch & interp, without messing up DR stack. */ transfer_from_sig_handler_to_fcache_return - (dcontext, sc, + (dcontext, uc, app_sc, sig, /* Make sure handler is next thing we execute */ (app_pc) SIGACT_PRIMARY_HANDLER(info->app_sigaction[sig]), - (linkstub_t *) get_asynch_linkstub()); + (linkstub_t *) get_asynch_linkstub(), true); if ((info->app_sigaction[sig]->flags & SA_ONESHOT) != 0) { /* clear handler now -- can't delete memory since sigreturn, @@ -5149,6 +5206,7 @@ execute_handler_from_dispatch(dcontext_t *dcontext, int sig) } #ifdef CLIENT_INTERFACE + sigcontext_t sc_interrupted = *sc; action = send_signal_to_client(dcontext, sig, frame, NULL, info->sigpending[sig]->access_address, false/*not blocked*/, NULL); @@ -5157,13 +5215,20 @@ execute_handler_from_dispatch(dcontext_t *dcontext, int sig) */ if (action == DR_SIGNAL_REDIRECT) { /* send_signal_to_client copied mcontext into frame's sc */ - ucontext_to_mcontext(get_mcontext(dcontext), uc); + priv_mcontext_t *mcontext = get_mcontext(dcontext); + ucontext_to_mcontext(mcontext, uc); dcontext->next_tag = canonicalize_pc_target(dcontext, (app_pc) sc->SC_XIP); if (is_building_trace(dcontext)) { LOG(THREAD, LOG_ASYNCH, 3, "\tsquashing trace-in-progress\n"); trace_abort(dcontext); } IF_ARM(dr_set_isa_mode(dcontext, get_pc_mode_from_cpsr(sc), NULL)); + mcontext->pc = dcontext->next_tag; + sig_full_cxt_t sc_interrupted_full = { &sc_interrupted, NULL/*not provided*/ }; + if (instrument_kernel_xfer(dcontext, DR_XFER_CLIENT_REDIRECT, sc_interrupted_full, + NULL, NULL, dcontext->next_tag, mcontext->xsp, + osc_empty, mcontext, sig)) + dcontext->next_tag = canonicalize_pc_target(dcontext, mcontext->pc); return true; /* don't try another signal */ } else if (action == DR_SIGNAL_SUPPRESS || @@ -5251,6 +5316,14 @@ execute_handler_from_dispatch(dcontext_t *dcontext, int sig) */ info->app_sigaction[sig]->handler = (handler_t) SIG_DFL; } +#ifdef CLIENT_INTERFACE + mcontext->pc = dcontext->next_tag; + sig_full_cxt_t sc_full = { sc, NULL/*not provided*/ }; + if (instrument_kernel_xfer(dcontext, DR_XFER_SIGNAL_DELIVERY, sc_full, NULL, NULL, + dcontext->next_tag, mcontext->xsp, osc_empty, mcontext, + sig)) + dcontext->next_tag = canonicalize_pc_target(dcontext, mcontext->pc); +#endif LOG(THREAD, LOG_ASYNCH, 3, "\tset xsp to "PFX"\n", xsp); return true; @@ -5662,6 +5735,7 @@ handle_sigreturn(dcontext_t *dcontext, void *ucxt_param, int style) { thread_sig_info_t *info = (thread_sig_info_t *) dcontext->signal_field; sigcontext_t *sc = NULL; /* initialize to satisfy Mac clang */ + kernel_ucontext_t *ucxt = NULL; int sig = 0; app_pc next_pc; /* xsp was put in mcontext prior to pre_system_call() */ @@ -5688,7 +5762,6 @@ handle_sigreturn(dcontext_t *dcontext, void *ucxt_param, int style) * so app can get away with it) */ if (rt) { - kernel_ucontext_t *ucxt; #ifdef LINUX sigframe_rt_t *frame = (sigframe_rt_t *) (xsp IF_X86(- sizeof(char*))); /* use si_signo instead of sig, less likely to be clobbered by app */ @@ -5764,6 +5837,9 @@ handle_sigreturn(dcontext_t *dcontext, void *ucxt_param, int style) memcpy(&prevset.sig[1], &frame->IF_X86_ELSE(extramask, uc.sigset_ex), sizeof(prevset.sig[1])); } +# ifdef ARM + ucxt = &frame->uc; /* we leave ucxt NULL for x86: not needed there */ +# endif # endif set_blocked(dcontext, &prevset, true/*absolute*/); } @@ -5798,6 +5874,14 @@ handle_sigreturn(dcontext_t *dcontext, void *ucxt_param, int style) ASSERT(!safe_is_in_fcache(dcontext, (app_pc) sc->SC_XIP, (byte *)sc->SC_XSP)); +#ifdef CLIENT_INTERFACE + sig_full_cxt_t sc_full = { sc, NULL/*not provided*/ }; + get_mcontext(dcontext)->pc = dcontext->next_tag; + instrument_kernel_xfer(dcontext, DR_XFER_SIGNAL_RETURN, osc_empty, NULL, + get_mcontext(dcontext), (app_pc)sc->SC_XIP, sc->SC_XSP, + sc_full, NULL, sig); +#endif + #ifdef DEBUG if (stats->loglevel >= 3 && (stats->logmask & LOG_ASYNCH) != 0) { LOG(THREAD, LOG_ASYNCH, 3, "returning-to sigcontext "PFX":\n", sc); @@ -5827,7 +5911,7 @@ handle_sigreturn(dcontext_t *dcontext, void *ucxt_param, int style) * complexities of kernel's non-linux-like sigreturn semantics */ sig_full_cxt_t sc_full = {sc, NULL}; /* non-ARM so NULL ok */ - sigcontext_to_mcontext(get_mcontext(dcontext), &sc_full); + sigcontext_to_mcontext(get_mcontext(dcontext), &sc_full, DR_MC_ALL); #else /* HACK to get eax put into mcontext AFTER do_syscall */ dcontext->next_tag = (app_pc) sc->IF_X86_ELSE(SC_XAX, SC_R0); diff --git a/core/win32/callback.c b/core/win32/callback.c index 19efe2efcb9..18f23364ad3 100644 --- a/core/win32/callback.c +++ b/core/win32/callback.c @@ -508,7 +508,6 @@ if (AFTER_INTERCEPT_TAKE_OVER || AFTER_INTERCEPT_TAKE_OVER_SINGLE_SHOT else push no_cleanup # state->start_pc endif - push $0 # we assume always want !save_dcontext as arg to asynch_take_over push/mov xsp # app_state_at_intercept_t * call asynch_take_over # should never reach here @@ -567,8 +566,7 @@ endif (!AFTER_INTERCEPT_TAKE_OVER) => handler signature, exported as typedef intercept_function_t: void handler(app_state_at_intercept_t *args) -if AFTER_INTERCEPT_TAKE_OVER, then asynch_take_over is called, with "false" for -its save_dcontext parameter +if AFTER_INTERCEPT_TAKE_OVER, then asynch_take_over is called. handler must make sure all paths exiting handler routine clear the initstack mutex once not using the initstack itself! @@ -2854,6 +2852,29 @@ is_syscall_trampoline(byte *pc, byte **tgt) return false; } +#ifdef CLIENT_INTERFACE +static void +instrument_dispatcher(dcontext_t *dcontext, dr_kernel_xfer_type_t type, + app_state_at_intercept_t *state, CONTEXT *interrupted_cxt) +{ + app_pc nohook_pc = dr_fragment_app_pc(state->start_pc); + state->mc.pc = nohook_pc; + DWORD orig_flags = 0; + if (interrupted_cxt != NULL) { + /* Avoid copying simd fields: this event does not provide them. */ + orig_flags = interrupted_cxt->ContextFlags; + interrupted_cxt->ContextFlags &= + ~(CONTEXT_DR_STATE & ~(CONTEXT_INTEGER|CONTEXT_CONTROL)); + } + if (instrument_kernel_xfer(dcontext, type, interrupted_cxt, NULL, NULL, + state->mc.pc, state->mc.xsp, NULL, &state->mc, 0) && + state->mc.pc != nohook_pc) + state->start_pc = state->mc.pc; + if (interrupted_cxt != NULL) + interrupted_cxt->ContextFlags = orig_flags; +} +#endif + /**************************************************************************** */ /* TRACK_NTDLL: try to find where kernel re-emerges into user mode when it @@ -3115,6 +3136,11 @@ intercept_new_thread(CONTEXT *cxt) dstack = (byte *) ALIGN_FORWARD(dstack, PAGE_SIZE); } #endif + /* FIXME i#2718: we want the app_state_at_intercept_t context, which is + * the actual code to be run by the thread *now*, and not this CONTEXT which + * is what will be run later! We should make sure nobody is relying on + * the current (broken) context first. + */ context_to_mcontext_new_thread(&mc, cxt); if (dynamo_thread_init(dstack, &mc _IF_CLIENT_INTERFACE(is_client)) != -1) { app_pc thunk_xip = (app_pc)cxt->CXT_XIP; @@ -3368,6 +3394,14 @@ intercept_ldr_init(app_state_at_intercept_t *state) if (!is_thread_initialized()) { if (intercept_new_thread(cxt)) return AFTER_INTERCEPT_LET_GO; +#ifdef CLIENT_INTERFACE + /* We treat this as a kernel xfer, partly b/c of i#2718 where our + * thread init mcontext is wrong. + * We pretend it's an APC. + */ + instrument_dispatcher(get_thread_private_dcontext(), + DR_XFER_APC_DISPATCHER, state, cxt); +#endif } else { /* ntdll!LdrInitializeThunk is only used for initializing new * threads so we should never get here unless early injected @@ -3716,6 +3750,9 @@ intercept_apc(app_state_at_intercept_t *state) asynch_retakeover_if_native(); state->callee_arg = (void *) false /* use cur dcontext */; +#ifdef CLIENT_INTERFACE + instrument_dispatcher(dcontext, DR_XFER_APC_DISPATCHER, state, cxt); +#endif asynch_take_over(state); } else STATS_INC(num_APCs_noasynch); @@ -3812,6 +3849,13 @@ intercept_nt_continue(CONTEXT *cxt, int flag) LOG(THREAD, LOG_ASYNCH, 3, "target context:\n"); DOLOG(3, LOG_ASYNCH, { dump_context_info(cxt, THREAD, true); }); +#ifdef CLIENT_INTERFACE + get_mcontext(dcontext)->pc = dcontext->next_tag; + instrument_kernel_xfer(dcontext, DR_XFER_CONTINUE, + NULL, NULL, get_mcontext(dcontext), + (app_pc)cxt->CXT_XIP, cxt->CXT_XSP, cxt, NULL, 0); +#endif + /* Updates debug register values. * FIXME should check dr7 upper bits, and maybe dr6 * We ignore the potential race condition. @@ -4001,6 +4045,13 @@ intercept_nt_setcontext(dcontext_t *dcontext, CONTEXT *cxt) trace_abort(dcontext); } +#ifdef CLIENT_INTERFACE + get_mcontext(dcontext)->pc = dcontext->next_tag; + instrument_kernel_xfer(dcontext, DR_XFER_SET_CONTEXT_THREAD, + NULL, NULL, get_mcontext(dcontext), + (app_pc)cxt->CXT_XIP, cxt->CXT_XSP, cxt, NULL, 0); +#endif + /* Yes, we use the same x86.asm and x86_code.c procedures as * NtContinue: nt_continue_dynamo_start and nt_continue_start_setup */ @@ -5952,6 +6003,9 @@ intercept_exception(app_state_at_intercept_t *state) * We don't save the cur dcontext. */ state->callee_arg = (void *) false /* use cur dcontext */; +#ifdef CLIENT_INTERFACE + instrument_dispatcher(dcontext, DR_XFER_EXCEPTION_DISPATCHER, state, cxt); +#endif asynch_take_over(state); } else STATS_INC(num_exceptions_noasynch); @@ -6010,6 +6064,10 @@ intercept_raise_exception(app_state_at_intercept_t *state) * We don't save the cur dcontext. */ state->callee_arg = (void *) false /* use cur dcontext */; +#ifdef CLIENT_INTERFACE + instrument_dispatcher(get_thread_private_dcontext(), + DR_XFER_RAISE_DISPATCHER, state, NULL); +#endif asynch_take_over(state); } else STATS_INC(num_raise_exceptions_noasynch); @@ -6370,6 +6428,9 @@ intercept_callback_start(app_state_at_intercept_t *state) asynch_retakeover_if_native(); state->callee_arg = (void *)(ptr_uint_t) true /* save cur dcontext */; +#ifdef CLIENT_INTERFACE + instrument_dispatcher(dcontext, DR_XFER_CALLBACK_DISPATCHER, state, NULL); +#endif asynch_take_over(state); } else STATS_INC(num_callbacks_noasynch); @@ -6713,6 +6774,20 @@ callback_start_return(priv_mcontext_t *mc) dump_mcontext(get_mcontext(cur_dcontext), cur_dcontext->logfile, DUMP_NOT_XML); }); + +# ifdef CLIENT_INTERFACE + priv_mcontext_t *cur_mc = get_mcontext(cur_dcontext); + /* XXX: if the retaddr is in the xsi slot, how do we get back the orig xsi value? + * Presumably we can't: should we document this? + */ + cur_mc->pc = POST_SYSCALL_PC(cur_dcontext); + get_mcontext(prev_dcontext)->pc = prev_dcontext->next_tag; + /* We don't support changing the target context for cbret. */ + instrument_kernel_xfer(cur_dcontext, DR_XFER_CALLBACK_RETURN, + NULL, NULL, get_mcontext(prev_dcontext), + cur_mc->pc, cur_mc->xsp, NULL, cur_mc, 0); +# endif + #else /* DCONTEXT_IN_EDI */ /* restore previous dcontext */ prev_dcontext = cur_dcontext->next_saved; diff --git a/core/win32/os.c b/core/win32/os.c index 14ff1564b58..e99530432a3 100644 --- a/core/win32/os.c +++ b/core/win32/os.c @@ -4980,7 +4980,6 @@ thread_set_self_mcontext(priv_mcontext_t *mc) ASSERT_NOT_REACHED(); } -#ifdef CLIENT_INTERFACE DR_API bool dr_mcontext_to_context(CONTEXT *dst, dr_mcontext_t *src) @@ -5004,6 +5003,9 @@ dr_mcontext_to_context(CONTEXT *dst, dr_mcontext_t *src) mcontext_to_context(dst, dr_mcontext_as_priv_mcontext(src), true/*cur segs, which we document*/); + /* XXX: CONTEXT_CONTROL includes xbp, while that's under DR_MC_INTEGER. + * We document this difference and recommend passing both to avoid problems. + */ if (!TEST(DR_MC_INTEGER, src->flags)) dst->ContextFlags &= ~(CONTEXT_INTEGER); if (!TEST(DR_MC_CONTROL, src->flags)) @@ -5011,7 +5013,70 @@ dr_mcontext_to_context(CONTEXT *dst, dr_mcontext_t *src) return true; } -#endif + +/* CONTEXT_CONTROL includes xbp, but it's under DR_MC_INTEGER: callers beware! */ +static dr_mcontext_flags_t +match_mcontext_flags_to_context_flags(dr_mcontext_flags_t mc_flags, DWORD cxt_flags) +{ + if (TEST(DR_MC_INTEGER, mc_flags) && !TESTALL(CONTEXT_INTEGER, cxt_flags)) + mc_flags &= ~DR_MC_INTEGER; + if (TEST(DR_MC_CONTROL, mc_flags) && !TESTALL(CONTEXT_CONTROL, cxt_flags)) + mc_flags &= ~DR_MC_CONTROL; + if (TEST(DR_MC_MULTIMEDIA, mc_flags) && + !TESTALL(CONTEXT_DR_STATE & ~(CONTEXT_INTEGER|CONTEXT_CONTROL), cxt_flags)) + mc_flags &= ~DR_MC_MULTIMEDIA; + return mc_flags; +} + +/* Only one of mc and dmc can be non-NULL. */ +bool +os_context_to_mcontext(dr_mcontext_t *dmc, priv_mcontext_t *mc, os_cxt_ptr_t osc) +{ + if (dmc != NULL) { + /* We have to handle mismatches between dmc->flags and osc->ContextFlags. We + * come here on NtContinue where often only CONTROL|INTEGER|SEGMENTS are + * available. Our general strategy: keep context_to_mcontext() happy and fix + * up here. We assume it's ok to clobber parts of dmc not requested by its + * flags, and ok to temporarily write to osc, even though it may be app + * memory. + */ + DWORD orig_flags = osc->ContextFlags; + if (!TESTALL(CONTEXT_DR_STATE_NO_YMM, osc->ContextFlags)) + osc->ContextFlags = CONTEXT_DR_STATE_NO_YMM; + context_to_mcontext(dr_mcontext_as_priv_mcontext(dmc), osc); + osc->ContextFlags = orig_flags; + /* We document the xbp difference: clients who care are advised to use syscall + * events instead of the kernel xfer events that come through here. + */ + dmc->flags = match_mcontext_flags_to_context_flags(dmc->flags, orig_flags); + } else if (mc != NULL) { + /* We don't support coming here with an incomplete CONTEXT: it doesn't + * happen in the code base currently. + */ + ASSERT(TESTALL(CONTEXT_DR_STATE_NO_YMM, osc->ContextFlags)); + context_to_mcontext(mc, osc); + } else + return false; + return true; +} + +/* Only one of mc and dmc can be non-NULL. */ +bool +mcontext_to_os_context(os_cxt_ptr_t osc, dr_mcontext_t *dmc, priv_mcontext_t *mc) +{ + if (dmc != NULL) { + /* We document the xbp difference: clients who care are advised to use syscall + * events instead of the kernel xfer events that come through here. + */ + dmc->flags = + match_mcontext_flags_to_context_flags(dmc->flags, osc->ContextFlags); + dr_mcontext_to_context(osc, dmc); + } else if (mc != NULL) + mcontext_to_context(osc, mc, true/*cur segs*/); + else + return false; + return true; +} int get_num_processors() diff --git a/core/win32/os_exports.h b/core/win32/os_exports.h index 9bb749e5fd8..afd88297881 100644 --- a/core/win32/os_exports.h +++ b/core/win32/os_exports.h @@ -173,6 +173,26 @@ int exception_frame_chain_depth(dcontext_t *dcontext); #define CONTEXT_HEAP_SIZE(cxt) (sizeof(cxt) IF_X64(+8)/*heap is 8-aligned already*/) #define CONTEXT_HEAP_SIZE_OPAQUE (CONTEXT_HEAP_SIZE(CONTEXT)) +typedef CONTEXT *os_cxt_ptr_t; +#define osc_empty NULL + +static inline bool +is_os_cxt_ptr_null(os_cxt_ptr_t osc) +{ + return osc == NULL; +} + +static inline void +set_os_cxt_ptr_null(os_cxt_ptr_t *osc) +{ + *osc = NULL; +} + +/* Only one of mc and dmc can be non-NULL. */ +bool os_context_to_mcontext(dr_mcontext_t *dmc, priv_mcontext_t *mc, os_cxt_ptr_t osc); +/* Only one of mc and dmc can be non-NULL. */ +bool mcontext_to_os_context(os_cxt_ptr_t osc, dr_mcontext_t *dmc, priv_mcontext_t *mc); + bool thread_get_context(thread_record_t *tr, CONTEXT *context); diff --git a/ext/drmgr/drmgr.c b/ext/drmgr/drmgr.c index 97aa62870eb..150f7283a25 100644 --- a/ext/drmgr/drmgr.c +++ b/ext/drmgr/drmgr.c @@ -60,6 +60,8 @@ #undef dr_unregister_module_load_event #undef dr_register_module_unload_event #undef dr_unregister_module_unload_event +#undef dr_register_kernel_xfer_event +#undef dr_unregister_kernel_xfer_event #undef dr_register_signal_event #undef dr_unregister_signal_event #undef dr_register_exception_event @@ -121,6 +123,7 @@ typedef struct _generic_event_entry_t { void (*postsys_cb)(void *, int); void (*modload_cb)(void *, const module_data_t *, bool); void (*modunload_cb)(void *, const module_data_t *); + void (*kernel_xfer_cb)(void *, const dr_kernel_xfer_info_t *); #ifdef UNIX dr_signal_action_t (*signal_cb)(void *, dr_siginfo_t *); #endif @@ -242,6 +245,9 @@ static void *modload_event_lock; static cb_list_t cblist_modunload; static void *modunload_event_lock; +static cb_list_t cblist_kernel_xfer; +static void *kernel_xfer_event_lock; + #ifdef UNIX static cb_list_t cblist_signal; static void *signal_event_lock; @@ -256,12 +262,6 @@ static cb_list_t cblist_fault; static void *fault_event_lock; static bool registered_fault; /* for lazy registration */ -#ifdef WINDOWS -static byte *addr_KiCallback; -static int sysnum_NtCallbackReturn; -# define CBRET_INTERRUPT_NUM 0x2b -#endif - static void drmgr_thread_init_event(void *drcontext); @@ -293,6 +293,9 @@ drmgr_modload_event(void *drcontext, const module_data_t *info, static void drmgr_modunload_event(void *drcontext, const module_data_t *info); +static void +drmgr_kernel_xfer_event(void *drcontext, const dr_kernel_xfer_info_t *info); + #ifdef UNIX static dr_signal_action_t drmgr_signal_event(void *drcontext, dr_siginfo_t *siginfo); @@ -307,14 +310,6 @@ static bool drmgr_restore_state_event(void *drcontext, bool restore_memory, dr_restore_state_info_t *info); -static bool -drmgr_cls_presys_event(void *drcontext, int sysnum); - -#ifdef WINDOWS -static void -drmgr_cls_exit(void); -#endif - static void our_thread_init_event(void *drcontext); @@ -346,6 +341,7 @@ drmgr_init(void) postsys_event_lock = dr_rwlock_create(); modload_event_lock = dr_rwlock_create(); modunload_event_lock = dr_rwlock_create(); + kernel_xfer_event_lock = dr_rwlock_create(); #ifdef UNIX signal_event_lock = dr_rwlock_create(); #endif @@ -360,6 +356,7 @@ drmgr_init(void) dr_register_post_syscall_event(drmgr_postsyscall_event); dr_register_module_load_event(drmgr_modload_event); dr_register_module_unload_event(drmgr_modunload_event); + dr_register_kernel_xfer_event(drmgr_kernel_xfer_event); #ifdef UNIX dr_register_signal_event(drmgr_signal_event); #endif @@ -400,6 +397,7 @@ drmgr_exit(void) dr_unregister_post_syscall_event(drmgr_postsyscall_event); dr_unregister_module_load_event(drmgr_modload_event); dr_unregister_module_unload_event(drmgr_modunload_event); + dr_unregister_kernel_xfer_event(drmgr_kernel_xfer_event); #ifdef UNIX dr_unregister_signal_event(drmgr_signal_event); #endif @@ -413,9 +411,6 @@ drmgr_exit(void) dr_unregister_restore_state_ex_event(drmgr_restore_state_event); registered_fault = false; } -#ifdef WINDOWS - drmgr_cls_exit(); -#endif dr_rwlock_destroy(fault_event_lock); #ifdef UNIX @@ -424,6 +419,7 @@ drmgr_exit(void) #ifdef WINDOWS dr_rwlock_destroy(exception_event_lock); #endif + dr_rwlock_destroy(kernel_xfer_event_lock); dr_rwlock_destroy(modunload_event_lock); dr_rwlock_destroy(modload_event_lock); dr_rwlock_destroy(postsys_event_lock); @@ -1212,6 +1208,7 @@ drmgr_event_init(void) cblist_init(&cblist_postsys, sizeof(generic_event_entry_t)); cblist_init(&cblist_modload, sizeof(generic_event_entry_t)); cblist_init(&cblist_modunload, sizeof(generic_event_entry_t)); + cblist_init(&cblist_kernel_xfer, sizeof(generic_event_entry_t)); #ifdef UNIX cblist_init(&cblist_signal, sizeof(generic_event_entry_t)); #endif @@ -1236,6 +1233,7 @@ drmgr_event_exit(void) cblist_delete(&cblist_postsys); cblist_delete(&cblist_modload); cblist_delete(&cblist_modunload); + cblist_delete(&cblist_kernel_xfer); #ifdef UNIX cblist_delete(&cblist_signal); #endif @@ -1336,8 +1334,10 @@ drmgr_presyscall_event(void *drcontext, int sysnum) execute = (*iter.cbs.generic[i].cb.presys_cb)(drcontext, sysnum) && execute; } - /* this must go last (the whole reason we're wrapping this) */ - execute = drmgr_cls_presys_event(drcontext, sysnum) && execute; + /* We used to track NtCallbackReturn for CLS (before DR provided the kernel xfer + * event) and had to handle it last here. Now we have nothing ourselves to + * do here. + */ cblist_delete_local(drcontext, &iter, BUFFER_SIZE_ELEMENTS(local)); @@ -1497,7 +1497,7 @@ drmgr_modunload_event(void *drcontext, const module_data_t *info) DR_EXPORT bool drmgr_register_signal_event(dr_signal_action_t (*func) - (void *drcontext, dr_siginfo_t *siginfo)) + (void *drcontext, dr_siginfo_t *siginfo)) { return drmgr_generic_event_add(&cblist_signal, signal_event_lock, (void (*)(void)) func, NULL); @@ -1865,10 +1865,6 @@ drmgr_insert_write_tls_field(void *drcontext, int idx, * CLS */ -#ifdef WINDOWS -static int cls_initialized; /* 0=not tried; >0=success; <0=failure */ -#endif - static bool drmgr_cls_stack_push_event(void *drcontext, bool new_depth) { @@ -2004,62 +2000,13 @@ drmgr_cls_stack_exit(void *drcontext) } #ifdef WINDOWS -static bool -drmgr_event_filter_syscall(void *drcontext, int sysnum) -{ - if (sysnum == sysnum_NtCallbackReturn) - return true; - else - return false; -} - -static bool -drmgr_cls_presys_event(void *drcontext, int sysnum) -{ - /* we wrap the pre-syscall event to ensure this goes last, - * after all other presys events, so we have no references - * to the cls data before we swap it - */ - if (sysnum == sysnum_NtCallbackReturn) - drmgr_cls_stack_pop(); - return true; -} - -/* Goes first with high negative priority */ -static dr_emit_flags_t -drmgr_event_insert_cb(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst, - bool for_trace, bool translating, void *user_data) -{ - if (instr_get_app_pc(inst) == addr_KiCallback) { - dr_insert_clean_call(drcontext, bb, inst, (void *)drmgr_cls_stack_push, - false, 0); - } - return DR_EMIT_DEFAULT; -} - -/* Goes last with high positive priority */ -static dr_emit_flags_t -drmgr_event_insert_cbret(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst, - bool for_trace, bool translating, void *user_data) -{ - if (instr_opcode_valid(inst) && - /* For -fast_client_decode we can have level 0 instrs so check - * to ensure this is an single instr with valid opcode. - */ - instr_get_opcode(inst) == OP_int && - opnd_get_immed_int(instr_get_src(inst, 0)) == CBRET_INTERRUPT_NUM) { - dr_insert_clean_call(drcontext, bb, inst, (void *)drmgr_cls_stack_pop, - false, 0); - } - return DR_EMIT_DEFAULT; -} - /* Determines the syscall from its Nt* wrapper. * Returns -1 on error. * FIXME: does not handle somebody hooking the wrapper. */ /* XXX: exporting this so drwrap can use it but I might prefer to have - * this in drutil or the upcoming drsys + * this in drutil or the upcoming drsys, especially since drmgr no longer + * uses this now that DR provides a kernel xfer event. */ DR_EXPORT int @@ -2093,82 +2040,78 @@ drmgr_decode_sysnum_from_wrapper(app_pc entry) instr_free(drcontext, &instr); return num; } +#endif /* WINDOWS */ -static bool -drmgr_cls_init(void) +static void +drmgr_kernel_xfer_event(void *drcontext, const dr_kernel_xfer_info_t *info) { - /* For callback init we watch for KiUserCallbackDispatcher. - * For callback exit we watch for NtCallbackReturn or int 0x2b. + /* We used to watch KiUserCallbackDispatcher, identify NtCallbackReturn's number, + * and watch int 0x2b ourselves, but now DR provides us with an event that + * operates at the last moment before the kernel action, making our lives much + * easier: we just have to order all other xfer events vs ours. */ - module_data_t *data; - module_handle_t ntdll_lib; - app_pc addr_cbret; - /* We need to go very early to push the new CLS context */ - drmgr_priority_t pri_cb = {sizeof(pri_cb), DRMGR_PRIORITY_NAME_CLS_ENTRY, - NULL, NULL, DRMGR_PRIORITY_INSERT_CLS_ENTRY}; - drmgr_priority_t pri_cbret = {sizeof(pri_cbret), DRMGR_PRIORITY_NAME_CLS_EXIT, - NULL, NULL, DRMGR_PRIORITY_INSERT_CLS_EXIT}; - - if (cls_initialized > 0) - return true; - else if (cls_initialized < 0) - return false; - cls_initialized = -1; + generic_event_entry_t local[EVENTS_STACK_SZ]; + cb_list_t iter; + uint i; + dr_rwlock_read_lock(kernel_xfer_event_lock); + cblist_create_local(drcontext, &cblist_kernel_xfer, &iter, (byte *)local, + BUFFER_SIZE_ELEMENTS(local)); + dr_rwlock_read_unlock(kernel_xfer_event_lock); - data = dr_lookup_module_by_name("ntdll.dll"); - if (data == NULL) { - /* fatal error: something is really wrong w/ underlying DR */ - return false; + if (info->type == DR_XFER_CALLBACK_DISPATCHER) { + /* We want to go first for callback entry so clients have a new context. */ + drmgr_cls_stack_push(); } - ntdll_lib = data->handle; - dr_free_module_data(data); - addr_KiCallback = (app_pc) dr_get_proc_address(ntdll_lib, "KiUserCallbackDispatcher"); - if (addr_KiCallback == NULL) - return false; /* should not happen */ - /* the wrapper is not good enough for two reasons: one, we want to swap - * contexts at the last possible moment, not prior to executing a few - * instrs; second, we'll miss hand-rolled syscalls - */ - addr_cbret = (app_pc) dr_get_proc_address(ntdll_lib, "NtCallbackReturn"); - if (addr_cbret == NULL) - return false; /* should not happen */ - sysnum_NtCallbackReturn = drmgr_decode_sysnum_from_wrapper(addr_cbret); - if (sysnum_NtCallbackReturn == -1) - return false; /* should not happen */ - - if (!drmgr_register_bb_instrumentation_event(NULL, drmgr_event_insert_cb, &pri_cb) || - !drmgr_register_bb_instrumentation_event(NULL, drmgr_event_insert_cbret, - &pri_cbret)) - return false; - dr_register_filter_syscall_event(drmgr_event_filter_syscall); - cls_initialized = 1; - return true; + + for (i = 0; i < iter.num; i++) { + if (!iter.cbs.generic[i].pri.valid) + continue; + (*iter.cbs.generic[i].cb.kernel_xfer_cb)(drcontext, info); + } + + if (info->type == DR_XFER_CALLBACK_RETURN) { + /* We want to go last for cbret to swap contexts at the last possible moment, + * to ensure there are no references to cls data before we swap it. + */ + drmgr_cls_stack_pop(); + } + + cblist_delete_local(drcontext, &iter, BUFFER_SIZE_ELEMENTS(local)); } -static void -drmgr_cls_exit(void) +DR_EXPORT +bool +drmgr_register_kernel_xfer_event(void (*func)(void *drcontext, + const dr_kernel_xfer_info_t *info)) { - if (cls_initialized > 0) - dr_unregister_filter_syscall_event(drmgr_event_filter_syscall); + return drmgr_generic_event_add(&cblist_kernel_xfer, kernel_xfer_event_lock, + (void (*)(void)) func, NULL); } -#else -static bool -drmgr_cls_presys_event(void *drcontext, int sysnum) +DR_EXPORT +bool +drmgr_register_kernel_xfer_event_ex(void (*func)(void *drcontext, + const dr_kernel_xfer_info_t *info), + drmgr_priority_t *priority) { - return true; + return drmgr_generic_event_add(&cblist_kernel_xfer, kernel_xfer_event_lock, + (void (*)(void)) func, priority); +} + +DR_EXPORT +bool +drmgr_unregister_kernel_xfer_event(void (*func)(void *drcontext, + const dr_kernel_xfer_info_t *info)) +{ + return drmgr_generic_event_remove(&cblist_kernel_xfer, kernel_xfer_event_lock, + (void (*)(void)) func); } -#endif /* WINDOWS */ DR_EXPORT int drmgr_register_cls_field(void (*cb_init_func)(void *drcontext, bool new_depth), void (*cb_exit_func)(void *drcontext, bool thread_exit)) { -#ifdef WINDOWS - if (!drmgr_cls_init()) - return -1; -#endif if (cb_init_func == NULL || cb_exit_func == NULL) return -1; if (!drmgr_generic_event_add(&cblist_cls_init, cls_event_lock, diff --git a/ext/drmgr/drmgr.h b/ext/drmgr/drmgr.h index a71a112b019..c61b68af355 100644 --- a/ext/drmgr/drmgr.h +++ b/ext/drmgr/drmgr.h @@ -79,6 +79,8 @@ extern "C" { # define dr_unregister_module_load_event DO_NOT_USE_module_load_USE_drmgr_events_instead # define dr_register_module_unload_event DO_NOT_USE_module_unload_USE_drmgr_instead # define dr_unregister_module_unload_event DO_NOT_USE_module_unload_USE_drmgr_instead +# define dr_register_kernel_xfer_event DO_NOT_USE_kernel_xfer_event_USE_drmgr_instead +# define dr_unregister_kernel_xfer_event DO_NOT_USE_kernel_xfer_event_USE_drmgr_instead # define dr_register_signal_event DO_NOT_USE_signal_event_USE_drmgr_instead # define dr_unregister_signal_event DO_NOT_USE_signal_event_USE_drmgr_instead # define dr_register_exception_event DO_NOT_USE_exception_event_USE_drmgr_instead @@ -546,35 +548,6 @@ drmgr_insert_write_tls_field(void *drcontext, int idx, * CLS */ -/** - * Priority of drmgr instrumentation pass used to track CLS. Users - * of drmgr can use the names #DRMGR_PRIORITY_NAME_CLS_ENTRY or - * #DRMGR_PRIORITY_NAME_CLS_EXIT in the - * drmgr_priority_t.before field or can use these numeric priorities - * in the drmgr_priority_t.priority field to ensure proper - * instrumentation pass ordering. The #DRMGR_PRIORITY_INSERT_CLS_ENTRY - * should go before any client event and the #DRMGR_PRIORITY_INSERT_CLS_EXIT - * should go after any client event. - */ -enum { - DRMGR_PRIORITY_INSERT_CLS_ENTRY = -500, /**< Priority of CLS entry tracking */ - DRMGR_PRIORITY_INSERT_CLS_EXIT = 5000, /**< Priority of CLS exit tracking */ -}; - -/** Name of drmgr insert pass priority for CLS entry tracking */ -#ifdef WINDOWS -# define DRMGR_PRIORITY_NAME_CLS_ENTRY "drmgr_cls_entry" -#else -# define DRMGR_PRIORITY_NAME_CLS_ENTRY NULL -#endif - -/** Name of drmgr insert pass priority for CLS exit tracking */ -#ifdef WINDOWS -# define DRMGR_PRIORITY_NAME_CLS_EXIT "drmgr_cls_exit" -#else -# define DRMGR_PRIORITY_NAME_CLS_EXIT NULL -#endif - DR_EXPORT /** * Reserves a callback-local storage (cls) slot. Thread-local storage @@ -620,7 +593,9 @@ DR_EXPORT * provide a stack of contexts on Linux, or to provide a stack of * contexts for any other purpose such as layered wrapped functions. * These push and pop functions are automatically called on Windows - * callback entry and exit. + * callback entry and exit, with the push called in DR's kernel xfer + * event prior to any client callback for that event, and pop called in + * the same event but after any client callback. */ int drmgr_register_cls_field(void (*cb_init_func)(void *drcontext, bool new_depth), @@ -955,6 +930,39 @@ bool drmgr_unregister_module_unload_event(void (*func) (void *drcontext, const module_data_t *info)); +DR_EXPORT +/** + * Registers a callback function for the kernel transfer event, which + * behaves just like DR's kernel transfer event dr_register_kernel_xfer_event(). + * \return whether successful. + */ +bool +drmgr_register_kernel_xfer_event(void (*func)(void *drcontext, + const dr_kernel_xfer_info_t *info)); + +DR_EXPORT +/** + * Registers a callback function for the kernel transfer event, which behaves just + * like DR's kernel transfer event dr_register_kernel_xfer_event(), except that it is + * ordered according to \p priority. A default priority of 0 is used for events + * registered via drmgr_register_module_unload_event(). + * \return whether successful. + */ +bool +drmgr_register_kernel_xfer_event_ex(void (*func)(void *drcontext, + const dr_kernel_xfer_info_t *info), + drmgr_priority_t *priority); + +DR_EXPORT +/** + * Unregister a callback function for the kernel transfer event. + * \return true if unregistration is successful and false if it is not + * (e.g., \p func was not registered). + */ +bool +drmgr_unregister_kernel_xfer_event(void (*func)(void *drcontext, + const dr_kernel_xfer_info_t *info)); + #ifdef UNIX DR_EXPORT /** @@ -964,7 +972,7 @@ DR_EXPORT */ bool drmgr_register_signal_event(dr_signal_action_t (*func) - (void *drcontext, dr_siginfo_t *siginfo)); + (void *drcontext, dr_siginfo_t *siginfo)); DR_EXPORT /** diff --git a/suite/tests/CMakeLists.txt b/suite/tests/CMakeLists.txt index 38ee6b8525e..3d6c4881c12 100644 --- a/suite/tests/CMakeLists.txt +++ b/suite/tests/CMakeLists.txt @@ -1948,6 +1948,8 @@ if (CLIENT_INTERFACE) # i#1866: stress our private loader tobuild_ci(client.loader client-interface/loader.c "" "" "") target_link_libraries(client.loader.dll "shlwapi") + + tobuild_ci(client.winxfer client-interface/winxfer.c "" "" "") endif (UNIX) if (X86) # FIXME i#1551, i#1569: port asm to ARM and AArch64 tobuild_ci(client.file_io client-interface/file_io.c diff --git a/suite/tests/client-interface/drmgr-test.dll.c b/suite/tests/client-interface/drmgr-test.dll.c index f6b7d5f8287..e3509d2a5e5 100755 --- a/suite/tests/client-interface/drmgr-test.dll.c +++ b/suite/tests/client-interface/drmgr-test.dll.c @@ -110,6 +110,8 @@ static dr_emit_flags_t event_bb4_insert2(void *drcontext, void *tag, instrlist_t static dr_emit_flags_t one_time_bb_event(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, bool translating); +static void event_kernel_xfer(void *drcontext, const dr_kernel_xfer_info_t *info); + DR_EXPORT void dr_init(client_id_t id) { @@ -176,6 +178,13 @@ dr_init(client_id_t id) ok = drmgr_register_bb_app2app_event(one_time_bb_event, NULL); CHECK(ok, "drmgr app2app registration failed"); + + ok = drmgr_register_kernel_xfer_event(event_kernel_xfer); + CHECK(ok, "drmgr_register_kernel_xfer_event failed"); + ok = drmgr_unregister_kernel_xfer_event(event_kernel_xfer); + CHECK(ok, "drmgr_unregister_kernel_xfer_event failed"); + ok = drmgr_register_kernel_xfer_event_ex(event_kernel_xfer, &priority); + CHECK(ok, "drmgr_register_kernel_xfer_event_ex failed"); } static void @@ -206,6 +215,8 @@ event_exit(void) if (!drmgr_unregister_cls_field(event_thread_context_init, event_thread_context_exit, cls_idx)) CHECK(false, "drmgr unregistration failed"); + if (!drmgr_unregister_kernel_xfer_event(event_kernel_xfer)) + CHECK(false, "drmgr_unregister_kernel_xfer_event failed"); drmgr_exit(); dr_fprintf(STDERR, "all done\n"); @@ -542,3 +553,13 @@ one_time_bb_event(void *drcontext, void *tag, instrlist_t *bb, return DR_EMIT_DEFAULT; } + +/* test kernel xfer event callback */ +static void +event_kernel_xfer(void *drcontext, const dr_kernel_xfer_info_t *info) +{ + /* We rely on other tests for the details here. Mostly we're just testing + * the register/unregister logic. + */ + CHECK(drcontext == dr_get_current_drcontext(), "sanity check"); +} diff --git a/suite/tests/client-interface/events.dll.c b/suite/tests/client-interface/events.dll.c index a0ad06e6fff..f1b2adb2d56 100644 --- a/suite/tests/client-interface/events.dll.c +++ b/suite/tests/client-interface/events.dll.c @@ -77,6 +77,8 @@ enum event_seq { EVENT_PRE_SYSCALL_2, EVENT_POST_SYSCALL_1, EVENT_POST_SYSCALL_2, + EVENT_KERNEL_XFER_1, + EVENT_KERNEL_XFER_2, EVENT_MODULE_UNLOAD_1, EVENT_MODULE_UNLOAD_2, EVENT_THREAD_EXIT_1, @@ -113,6 +115,8 @@ const char * const name[EVENT_last] = { "pre syscall event 2", "post syscall event 1", "post syscall event 2", + "kernel xfer event 1", + "kernel xfer event 2", "module unload event 1", "module unload event 2", "thread exit event 1", @@ -151,20 +155,23 @@ inc_count_second(int second) } -static -void check_result(void) +static void +check_result(void) { int i; for (i = 0; i < EVENT_last; i++) { if (counts[i] == 0) continue; - dr_fprintf(STDERR, "%s is called %d time(s)\n", name[i], counts[i]); + if (counts[i] == 1) + dr_fprintf(STDERR, "%s is called 1 time\n", name[i]); + else + dr_fprintf(STDERR, "%s is called >1 time\n", name[i]); dr_flush_file(STDOUT); } } -static -void exit_event1(void) +static void +exit_event1(void) { dr_fprintf(STDERR, "exit event 1\n"); dr_flush_file(STDOUT); @@ -175,8 +182,8 @@ void exit_event1(void) dr_mutex_destroy(mutex); } -static -void exit_event2(void) +static void +exit_event2(void) { dr_fprintf(STDERR, "exit event 2\n"); dr_flush_file(STDOUT); @@ -185,31 +192,31 @@ void exit_event2(void) dr_fprintf(STDERR, "unregister failed!\n"); } -static -void thread_init_event1(void *drcontext) +static void +thread_init_event1(void *drcontext) { inc_count_first(EVENT_THREAD_INIT_1, EVENT_THREAD_INIT_2); if (!dr_unregister_thread_init_event(thread_init_event1)) dr_fprintf(STDERR, "unregister failed!\n"); } -static -void thread_init_event2(void *drcontext) +static void +thread_init_event2(void *drcontext) { inc_count_second(EVENT_THREAD_INIT_2); if (!dr_unregister_thread_init_event(thread_init_event2)) dr_fprintf(STDERR, "unregister failed!\n"); } -static -void thread_exit_event1(void *drcontext) +static void +thread_exit_event1(void *drcontext) { inc_count_first(EVENT_THREAD_EXIT_1, EVENT_THREAD_EXIT_2); if (!dr_unregister_thread_exit_event(thread_exit_event1)) dr_fprintf(STDERR, "unregister failed!\n"); } -static -void thread_exit_event2(void *drcontext) +static void +thread_exit_event2(void *drcontext) { inc_count_second(EVENT_THREAD_EXIT_2); if (!dr_unregister_thread_exit_event(thread_exit_event2)) @@ -217,15 +224,15 @@ void thread_exit_event2(void *drcontext) } #ifdef UNIX -static -void fork_init_event1(void *drcontext) +static void +fork_init_event1(void *drcontext) { inc_count_first(EVENT_FORK_INIT_1, EVENT_FORK_INIT_2); if (!dr_unregister_fork_init_event(fork_init_event1)) dr_fprintf(STDERR, "unregister failed!\n"); } -static -void fork_init_event2(void *drcontext) +static void +fork_init_event2(void *drcontext) { int i; dr_mutex_lock(mutex); @@ -296,24 +303,24 @@ dr_custom_trace_action_t end_trace_event2(void *dcontext, void *trace_tag, void return CUSTOM_TRACE_DR_DECIDES; } -static -void delete_event1(void *dcontext, void *tag) +static void +delete_event1(void *dcontext, void *tag) { inc_count_first(EVENT_DELETE_1, EVENT_DELETE_2); if (!dr_unregister_delete_event(delete_event1)) dr_fprintf(STDERR, "unregister failed!\n"); } -static -void delete_event2(void *dcontext, void *tag) +static void +delete_event2(void *dcontext, void *tag) { inc_count_second(EVENT_DELETE_2); if (!dr_unregister_delete_event(delete_event2)) dr_fprintf(STDERR, "unregister failed!\n"); } -static -void module_load_event_perm(void *drcontext, const module_data_t *info, bool loaded) +static void +module_load_event_perm(void *drcontext, const module_data_t *info, bool loaded) { /* Test i#138 */ if (info->full_path == NULL || info->full_path[0] == '\0') @@ -328,40 +335,40 @@ void module_load_event_perm(void *drcontext, const module_data_t *info, bool loa #endif } -static -void module_load_event1(void *drcontext, const module_data_t *info, bool loaded) +static void +module_load_event1(void *drcontext, const module_data_t *info, bool loaded) { inc_count_first(EVENT_MODULE_LOAD_1, EVENT_MODULE_LOAD_2); if (!dr_unregister_module_load_event(module_load_event1)) dr_fprintf(STDERR, "unregister failed!\n"); } -static -void module_load_event2(void *drcontext, const module_data_t *info, bool loaded) +static void +module_load_event2(void *drcontext, const module_data_t *info, bool loaded) { inc_count_second(EVENT_MODULE_LOAD_2); if (!dr_unregister_module_load_event(module_load_event2)) dr_fprintf(STDERR, "unregister failed!\n"); } -static -void module_unload_event1(void *drcontext, const module_data_t *info) +static void +module_unload_event1(void *drcontext, const module_data_t *info) { inc_count_first(EVENT_MODULE_UNLOAD_1, EVENT_MODULE_UNLOAD_2); if (!dr_unregister_module_unload_event(module_unload_event1)) dr_fprintf(STDERR, "unregister failed!\n"); } -static -void module_unload_event2(void *drcontext, const module_data_t *info) +static void +module_unload_event2(void *drcontext, const module_data_t *info) { inc_count_second(EVENT_MODULE_UNLOAD_2); if (!dr_unregister_module_unload_event(module_unload_event2)) dr_fprintf(STDERR, "unregister failed!\n"); } -static -bool pre_syscall_event1(void *drcontext, int sysnum) +static bool +pre_syscall_event1(void *drcontext, int sysnum) { inc_count_first(EVENT_PRE_SYSCALL_1, EVENT_PRE_SYSCALL_2); if (!dr_unregister_pre_syscall_event(pre_syscall_event1)) @@ -369,8 +376,8 @@ bool pre_syscall_event1(void *drcontext, int sysnum) return true; } -static -bool pre_syscall_event2(void *drcontext, int sysnum) +static bool +pre_syscall_event2(void *drcontext, int sysnum) { inc_count_second(EVENT_PRE_SYSCALL_2); if (!dr_unregister_pre_syscall_event(pre_syscall_event2)) @@ -378,24 +385,24 @@ bool pre_syscall_event2(void *drcontext, int sysnum) return true; } -static -void post_syscall_event1(void *drcontext, int sysnum) +static void +post_syscall_event1(void *drcontext, int sysnum) { inc_count_first(EVENT_POST_SYSCALL_1, EVENT_POST_SYSCALL_2); if (!dr_unregister_post_syscall_event(post_syscall_event1)) dr_fprintf(STDERR, "unregister failed!\n"); } -static -void post_syscall_event2(void *drcontext, int sysnum) +static void +post_syscall_event2(void *drcontext, int sysnum) { inc_count_second(EVENT_POST_SYSCALL_2); if (!dr_unregister_post_syscall_event(post_syscall_event2)) dr_fprintf(STDERR, "unregister failed!\n"); } -static -bool filter_syscall_event1(void *drcontext, int sysnum) +static bool +filter_syscall_event1(void *drcontext, int sysnum) { inc_count_first(EVENT_FILTER_SYSCALL_1, EVENT_FILTER_SYSCALL_2); if (!dr_unregister_filter_syscall_event(filter_syscall_event1)) @@ -403,8 +410,8 @@ bool filter_syscall_event1(void *drcontext, int sysnum) return true; } -static -bool filter_syscall_event2(void *drcontext, int sysnum) +static bool +filter_syscall_event2(void *drcontext, int sysnum) { inc_count_second(EVENT_FILTER_SYSCALL_2); if (!dr_unregister_filter_syscall_event(filter_syscall_event2)) @@ -412,10 +419,39 @@ bool filter_syscall_event2(void *drcontext, int sysnum) return true; } +static void +kernel_xfer_event1(void *drcontext, const dr_kernel_xfer_info_t *info) +{ + inc_count_first(EVENT_KERNEL_XFER_1, EVENT_KERNEL_XFER_2); + if (!dr_unregister_kernel_xfer_event(kernel_xfer_event1)) + dr_fprintf(STDERR, "unregister failed!\n"); +} + +static void +kernel_xfer_event2(void *drcontext, const dr_kernel_xfer_info_t *info) +{ + inc_count_second(EVENT_KERNEL_XFER_2); + dr_log(drcontext, LOG_ALL, 2, "%s: %d %p to %p sp=%zx\n", __FUNCTION__, info->type, + info->source_mcontext == NULL ? 0 : info->source_mcontext->pc, + info->target_pc, info->target_xsp); + if (info->type == DR_XFER_CLIENT_REDIRECT) { + /* Test for exception event redirect. */ + ASSERT(info->source_mcontext != NULL); + dr_mcontext_t mc = {sizeof(mc)}; + mc.flags = DR_MC_CONTROL; + bool ok = dr_get_mcontext(drcontext, &mc); + ASSERT(ok); + ASSERT(mc.pc == info->target_pc); + ASSERT(mc.xsp == info->target_xsp); + mc.flags = DR_MC_ALL; + ok = dr_get_mcontext(drcontext, &mc); + ASSERT(ok); + } +} #ifdef WINDOWS -static -bool exception_event_redirect(void *dcontext, dr_exception_t *excpt) +static bool +exception_event_redirect(void *dcontext, dr_exception_t *excpt) { app_pc addr; dr_mcontext_t mcontext = {sizeof(mcontext),DR_MC_ALL,}; @@ -443,8 +479,8 @@ bool exception_event_redirect(void *dcontext, dr_exception_t *excpt) return true; } -static -bool exception_event1(void *dcontext, dr_exception_t *excpt) +static bool +exception_event1(void *dcontext, dr_exception_t *excpt) { if (excpt->record->ExceptionCode == STATUS_ACCESS_VIOLATION) inc_count_first(EVENT_EXCEPTION_1, EVENT_EXCEPTION_2); @@ -457,8 +493,8 @@ bool exception_event1(void *dcontext, dr_exception_t *excpt) return true; } -static -bool exception_event2(void *dcontext, dr_exception_t *excpt) +static bool +exception_event2(void *dcontext, dr_exception_t *excpt) { if (excpt->record->ExceptionCode == STATUS_ACCESS_VIOLATION) inc_count_second(EVENT_EXCEPTION_2); @@ -654,6 +690,8 @@ void dr_init(client_id_t id) dr_register_post_syscall_event(post_syscall_event2); dr_register_filter_syscall_event(filter_syscall_event1); dr_register_filter_syscall_event(filter_syscall_event2); + dr_register_kernel_xfer_event(kernel_xfer_event1); + dr_register_kernel_xfer_event(kernel_xfer_event2); #ifdef WINDOWS dr_register_exception_event(exception_event1); dr_register_exception_event(exception_event2); diff --git a/suite/tests/client-interface/events.templatex b/suite/tests/client-interface/events.templatex index 8a35ebef5ad..95a8c0b7b0b 100644 --- a/suite/tests/client-interface/events.templatex +++ b/suite/tests/client-interface/events.templatex @@ -4,37 +4,39 @@ exception event redirect Redirect success! exit event 2 exit event 1 -module load event 1 is called 1 time\(s\) -module load event 2 is called 1 time\(s\) -thread init event 1 is called 1 time\(s\) -thread init event 2 is called 1 time\(s\) -bb event 1 is called 1 time\(s\) -bb event 2 is called 1 time\(s\) +module load event 1 is called 1 time +module load event 2 is called 1 time +thread init event 1 is called 1 time +thread init event 2 is called 1 time +bb event 1 is called 1 time +bb event 2 is called 1 time # if !defined(disable_traces) -end trace event 1 is called 1 time\(s\) -end trace event 2 is called 1 time\(s\) -trace event 1 is called 1 time\(s\) -trace event 2 is called 1 time\(s\) +end trace event 1 is called 1 time +end trace event 2 is called 1 time +trace event 1 is called 1 time +trace event 2 is called 1 time # endif -delete event 1 is called 1 time\(s\) -delete event 2 is called 1 time\(s\) +delete event 1 is called 1 time +delete event 2 is called 1 time /* no filter event on xp sp2+ 32-bit kernel, so optional */ -(filter syscall event 1 is called 1 time\(s\) -)?(filter syscall event 2 is called 1 time\(s\) -)?pre syscall event 1 is called 1 time\(s\) -pre syscall event 2 is called 1 time\(s\) -post syscall event 1 is called 1 time\(s\) -post syscall event 2 is called 1 time\(s\) -module unload event 1 is called 1 time\(s\) -module unload event 2 is called 1 time\(s\) -thread exit event 1 is called 1 time\(s\) -thread exit event 2 is called 1 time\(s\) -exception event 1 is called 1 time\(s\) -exception event 2 is called 1 time\(s\) -restore state event 1 is called 1 time\(s\) -restore state event 2 is called 1 time\(s\) -restore state ex event 1 is called 1 time\(s\) -restore state ex event 2 is called 1 time\(s\) +(filter syscall event 1 is called 1 time +)?(filter syscall event 2 is called 1 time +)?pre syscall event 1 is called 1 time +pre syscall event 2 is called 1 time +post syscall event 1 is called 1 time +post syscall event 2 is called 1 time +kernel xfer event 1 is called 1 time +kernel xfer event 2 is called >1 time +module unload event 1 is called 1 time +module unload event 2 is called 1 time +thread exit event 1 is called 1 time +thread exit event 2 is called 1 time +exception event 1 is called 1 time +exception event 2 is called 1 time +restore state event 1 is called 1 time +restore state event 2 is called 1 time +restore state ex event 1 is called 1 time +restore state ex event 2 is called 1 time #else appdll initialized Sending SIGUSR1 @@ -44,42 +46,44 @@ Sending SIGURG Done exit event 2 exit event 1 -thread exit event 1 is called 1 time\(s\) -thread exit event 2 is called 1 time\(s\) -fork init event 1 is called 1 time\(s\) -fork init event 2 is called 1 time\(s\) +thread exit event 1 is called 1 time +thread exit event 2 is called 1 time +fork init event 1 is called 1 time +fork init event 2 is called 1 time signal event redirect Redirect success! exit event 2 exit event 1 -module load event 1 is called 1 time\(s\) -module load event 2 is called 1 time\(s\) -thread init event 1 is called 1 time\(s\) -thread init event 2 is called 1 time\(s\) -bb event 1 is called 1 time\(s\) -bb event 2 is called 1 time\(s\) +module load event 1 is called 1 time +module load event 2 is called 1 time +thread init event 1 is called 1 time +thread init event 2 is called 1 time +bb event 1 is called 1 time +bb event 2 is called 1 time # if !defined(disable_traces) -end trace event 1 is called 1 time\(s\) -end trace event 2 is called 1 time\(s\) -trace event 1 is called 1 time\(s\) -trace event 2 is called 1 time\(s\) +end trace event 1 is called 1 time +end trace event 2 is called 1 time +trace event 1 is called 1 time +trace event 2 is called 1 time # endif -delete event 1 is called 1 time\(s\) -delete event 2 is called 1 time\(s\) -filter syscall event 1 is called 1 time\(s\) -filter syscall event 2 is called 1 time\(s\) -pre syscall event 1 is called 1 time\(s\) -pre syscall event 2 is called 1 time\(s\) -post syscall event 1 is called 1 time\(s\) -post syscall event 2 is called 1 time\(s\) -module unload event 1 is called 1 time\(s\) -module unload event 2 is called 1 time\(s\) -thread exit event 1 is called 1 time\(s\) -thread exit event 2 is called 1 time\(s\) -signal event 1 is called 3 time\(s\) -signal event 2 is called 1 time\(s\) -restore state event 1 is called 1 time\(s\) -restore state event 2 is called 1 time\(s\) -restore state ex event 1 is called 1 time\(s\) -restore state ex event 2 is called 1 time\(s\) +delete event 1 is called 1 time +delete event 2 is called 1 time +filter syscall event 1 is called 1 time +filter syscall event 2 is called 1 time +pre syscall event 1 is called 1 time +pre syscall event 2 is called 1 time +post syscall event 1 is called 1 time +post syscall event 2 is called 1 time +kernel xfer event 1 is called 1 time +kernel xfer event 2 is called >1 time +module unload event 1 is called 1 time +module unload event 2 is called 1 time +thread exit event 1 is called 1 time +thread exit event 2 is called 1 time +signal event 1 is called >1 time +signal event 2 is called 1 time +restore state event 1 is called 1 time +restore state event 2 is called 1 time +restore state ex event 1 is called 1 time +restore state ex event 2 is called 1 time #endif diff --git a/suite/tests/client-interface/fibers.template b/suite/tests/client-interface/fibers.template index 80053140ad5..c8f7be5f3c1 100755 --- a/suite/tests/client-interface/fibers.template +++ b/suite/tests/client-interface/fibers.template @@ -50,23 +50,23 @@ fls_delete val=0x12345678 fls_delete val=0xdeadbeef exit event 2 exit event 1 -module load event 1 is called 1 time(s) -module load event 2 is called 1 time(s) -thread init event 1 is called 1 time(s) -thread init event 2 is called 1 time(s) -bb event 1 is called 1 time(s) -bb event 2 is called 1 time(s) -end trace event 1 is called 1 time(s) -end trace event 2 is called 1 time(s) -trace event 1 is called 1 time(s) -trace event 2 is called 1 time(s) -delete event 1 is called 1 time(s) -delete event 2 is called 1 time(s) -filter syscall event 1 is called 1 time(s) -filter syscall event 2 is called 1 time(s) -pre syscall event 1 is called 1 time(s) -pre syscall event 2 is called 1 time(s) -post syscall event 1 is called 1 time(s) -post syscall event 2 is called 1 time(s) -thread exit event 1 is called 1 time(s) -thread exit event 2 is called 1 time(s) +module load event 1 is called 1 time +module load event 2 is called 1 time +thread init event 1 is called 1 time +thread init event 2 is called 1 time +bb event 1 is called 1 time +bb event 2 is called 1 time +end trace event 1 is called 1 time +end trace event 2 is called 1 time +trace event 1 is called 1 time +trace event 2 is called 1 time +delete event 1 is called 1 time +delete event 2 is called 1 time +filter syscall event 1 is called 1 time +filter syscall event 2 is called 1 time +pre syscall event 1 is called 1 time +pre syscall event 2 is called 1 time +post syscall event 1 is called 1 time +post syscall event 2 is called 1 time +thread exit event 1 is called 1 time +thread exit event 2 is called 1 time diff --git a/suite/tests/client-interface/flush.dll.c b/suite/tests/client-interface/flush.dll.c index 5a4f5454ca9..4eacdca9947 100644 --- a/suite/tests/client-interface/flush.dll.c +++ b/suite/tests/client-interface/flush.dll.c @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2011-2014 Google, Inc. All rights reserved. + * Copyright (c) 2011-2017 Google, Inc. All rights reserved. * Copyright (c) 2007-2010 VMware, Inc. All rights reserved. * **********************************************************/ @@ -32,6 +32,7 @@ */ #include "dr_api.h" +#include "client_tools.h" #include #define MINSERT instrlist_meta_preinsert @@ -279,6 +280,23 @@ dr_emit_flags_t bb_event(void* drcontext, void *tag, instrlist_t *bb, return DR_EMIT_DEFAULT; } +static void +kernel_xfer_event(void *drcontext, const dr_kernel_xfer_info_t *info) +{ + /* Test kernel xfer on dr_redirect_execution */ + dr_fprintf(STDERR, "%s: type %d\n", __FUNCTION__, info->type); + ASSERT(info->source_mcontext != NULL); + dr_mcontext_t mc = {sizeof(mc)}; + mc.flags = DR_MC_CONTROL; + bool ok = dr_get_mcontext(drcontext, &mc); + ASSERT(ok); + ASSERT(mc.pc == info->target_pc); + ASSERT(mc.xsp == info->target_xsp); + mc.flags = DR_MC_ALL; + ok = dr_get_mcontext(drcontext, &mc); + ASSERT(ok); +} + DR_EXPORT void dr_init(client_id_t id) { @@ -294,5 +312,5 @@ void dr_init(client_id_t id) dr_register_trace_event(trace_event); dr_register_delete_event(deleted_event); dr_register_bb_event(bb_event); - + dr_register_kernel_xfer_event(kernel_xfer_event); } diff --git a/suite/tests/client-interface/flush.template b/suite/tests/client-interface/flush.template index 6a5eabb0d99..58c059415b7 100644 --- a/suite/tests/client-interface/flush.template +++ b/suite/tests/client-interface/flush.template @@ -1,12 +1,18 @@ #if defined(thread_private) || defined(enable_full_api) options = use_unlink +kernel_xfer_event: type 9 Flush completion id=100 +kernel_xfer_event: type 9 Flush completion id=200 +kernel_xfer_event: type 9 Flush completion id=300 +kernel_xfer_event: type 9 Flush completion id=400 #else options =@& +kernel_xfer_event: type 9 Flush completion id=200 +kernel_xfer_event: type 9 Flush completion id=400 #endif count = 402 diff --git a/suite/tests/client-interface/signal.dll.c b/suite/tests/client-interface/signal.dll.c index 52c57bdbf85..38663f9c90e 100644 --- a/suite/tests/client-interface/signal.dll.c +++ b/suite/tests/client-interface/signal.dll.c @@ -49,8 +49,44 @@ static void *child_dead; static void *sigchld_received; static pid_t child_pid, child_tid; -static -dr_signal_action_t signal_event(void *dcontext, dr_siginfo_t *info) +static void +redirect_xfer(void) +{ + /* XXX: this is not super-clean: we'll interpret this routine. + * Better to coordinate w/ the app: but that's more work for us. + */ + dr_fprintf(STDERR, "redirected via dr_set_mcontext\n"); +} + +static void +kernel_xfer_event(void *drcontext, const dr_kernel_xfer_info_t *info) +{ + dr_fprintf(STDERR, "%s: type %d, sig %d\n", __FUNCTION__, info->type, info->sig); + dr_log(drcontext, LOG_ALL, 2, "%s: %d %d %p to %p sp=%zx\n", __FUNCTION__, info->type, + info->sig, info->source_mcontext->pc, info->target_pc, info->target_xsp); + dr_mcontext_t mc = {sizeof(mc)}; + mc.flags = DR_MC_CONTROL; + bool ok = dr_get_mcontext(drcontext, &mc); + ASSERT(ok); + ASSERT(mc.pc == info->target_pc); + ASSERT(mc.xsp == info->target_xsp); + mc.flags = DR_MC_ALL; + ok = dr_get_mcontext(drcontext, &mc); + ASSERT(ok); + /* We do one test of setting the context. + * XXX: We would ideally test for synch vs asynch signals too. + */ + static bool set_mc_once; + if (info->type == DR_XFER_SIGNAL_DELIVERY && !set_mc_once) { + set_mc_once = true; + mc.pc = (app_pc)redirect_xfer; + ok = dr_set_mcontext(drcontext, &mc); + ASSERT(ok); + } +} + +static dr_signal_action_t +signal_event(void *dcontext, dr_siginfo_t *info) { static int count = -1; count++; @@ -141,6 +177,7 @@ void dr_init(client_id_t id) { dr_register_bb_event(bb_event); dr_register_signal_event(signal_event); + dr_register_kernel_xfer_event(kernel_xfer_event); /* We test syscall auto-restart (i#2659) by having another thread * sit at a blocking read while it receives signals. diff --git a/suite/tests/client-interface/signal.expect b/suite/tests/client-interface/signal.expect index 418dd6aa596..98f321bdbcd 100644 --- a/suite/tests/client-interface/signal.expect +++ b/suite/tests/client-interface/signal.expect @@ -2,7 +2,9 @@ signal event 0 sig=17 got 2 bytes == 97 98 Sending SIGURG signal event 1 sig=23 -Got SIGURG +kernel_xfer_event: type 0, sig 23 +redirected via dr_set_mcontext +kernel_xfer_event: type 1, sig 23 Sending SIGURG signal event 2 sig=23 Sending SIGURG @@ -18,11 +20,16 @@ signal event 7 sig=15 Redirected Sending SIGUSR2 signal event 8 sig=12 +kernel_xfer_event: type 9, sig 12 Redirected signal event 9 sig=11 signal event 10 sig=11 +kernel_xfer_event: type 0, sig 11 Got SIGSEGV +kernel_xfer_event: type 1, sig 11 Sending SIGUSR1 signal event 11 sig=10 +kernel_xfer_event: type 0, sig 10 Got SIGUSR1 +kernel_xfer_event: type 1, sig 10 Done diff --git a/suite/tests/client-interface/winxfer.c b/suite/tests/client-interface/winxfer.c new file mode 100644 index 00000000000..f62fa736e14 --- /dev/null +++ b/suite/tests/client-interface/winxfer.c @@ -0,0 +1,291 @@ +/* ********************************************************** + * Copyright (c) 2014-2017 Google, Inc. All rights reserved. + * Copyright (c) 2003-2010 VMware, Inc. All rights reserved. + * **********************************************************/ + +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of VMware, Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* This combines win32/callback.c and win32/winapc.c */ + +#include +#include /* for QueueUserAPC */ +#include /* for _beginthreadex */ +#include "tools.h" +#include + +/*************************************************************************** + * Callback and exception raising + */ + +static volatile bool thread_ready = false; +static volatile bool past_crash = false; +static volatile uint last_received = 0; +static HWND hwnd; +static uint msgdata; + +static const UINT MSG_CUSTOM = WM_APP + 1; +static const LRESULT MSG_SUCCESS = 1; + +static const WPARAM WP_NOP = 0; +static const WPARAM WP_EXIT = 1; +static const WPARAM WP_CRASH = 3; + +static const uint BAD_WRITE = 0x40; + +#ifndef WM_DWMNCRENDERINGCHANGED +# define WM_DWMNCRENDERINGCHANGED 0x031F +#endif + +/* This is where all our callbacks come. We get 4 default messages: + * WM_GETMINMAXINFO 0x0024 + * WM_NCCREATE 0x0081 + * WM_NCCALCSIZE 0x0083 + * WM_CREATE 0x0001 + * and then our 2 custom messages that we send. + * + * On Windows 7 we also get (i#520): + * WM_DWMNCRENDERINGCHANGED 0x031F + * and we avoid printing anything about it to simplify the test suite. + */ +LRESULT CALLBACK +wnd_callback(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message == MSG_CUSTOM) { + print("in wnd_callback "PFX" %d %d\n", message, wParam, lParam); + if (wParam == WP_CRASH) { + /* ensure SendMessage returns prior to our crash */ + ReplyMessage(TRUE); + print("About to crash\n"); + /* We don't bother to pass an exception across the callback boundary + * as it complicates the test template due to lack of x64 support. + * We stick with a local exception. + */ + __try { + *((int *)BAD_WRITE) = 4; + print("Should not get here\n"); + } + __except (/* Only catch the bad write, to not mask DR errors (like + * case 10579) */ + (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION && + (GetExceptionInformation())->ExceptionRecord-> + ExceptionInformation[0] == 1 /* write */ && + (GetExceptionInformation())->ExceptionRecord-> + ExceptionInformation[1] == BAD_WRITE) ? + EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { + print("Inside handler\n"); + past_crash = true; + } + } + return MSG_SUCCESS; + } else { + /* lParam varies so don't make template nondet */ + if (message != WM_DWMNCRENDERINGCHANGED) + print("in wnd_callback "PFX" %d\n", message, wParam); + return DefWindowProc(hwnd, message, wParam, lParam); + } +} + +int WINAPI +run_func(void * arg) +{ + MSG msg; + char *winName = "foobar"; + WNDCLASS wndclass = {0, wnd_callback, 0, 0, NULL/* WinMain hwnd would be here */, + NULL, NULL, + NULL, NULL, winName}; + + if (!RegisterClass(&wndclass)) { + print("Unable to create window class\n"); + return 0; + } + hwnd = CreateWindow(winName, winName, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, NULL, NULL, NULL/* WinMain hwnd would be here */, + NULL); + /* deliberately not calling ShowWindow */ + + /* For case 10579 we want a handled system call in this thread prior + * to our crash inside a callback */ + VirtualAlloc(0, 1024, MEM_RESERVE, PAGE_EXECUTE_READWRITE); + + thread_ready = true; + while (true) { + __try { + if (GetMessage(&msg, NULL, 0, 0)) { + /* Messages not auto-sent to callbacks are processed here */ + if ((msg.message != MSG_CUSTOM || msg.wParam != WP_NOP) && + msg.message != WM_DWMNCRENDERINGCHANGED) { + print("Got message "PFX" %d %d\n", + msg.message, msg.wParam, msg.lParam); + } + last_received = msg.message; + if (msg.message == MSG_CUSTOM && msg.wParam == WP_EXIT) + break; /* Done */ + TranslateMessage(&msg); /* convert virtual-key msgs to character msgs */ + DispatchMessage(&msg); + } + } + __except (/* Only catch the bad write, to not mask DR errors (like + * case 10579) */ + (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION && + (GetExceptionInformation())->ExceptionRecord->ExceptionInformation[0] + == 1 /* write */ && + (GetExceptionInformation())->ExceptionRecord->ExceptionInformation[1] + == BAD_WRITE) ? + EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { + /* This should have crossed the callback boundary + * On xpsp2 and earlier we never see a callback return for + * the crashing callback, while on 2k3sp1 we do see one. + */ + print("Inside handler\n"); + past_crash = true; + continue; + } + } + return (int) msg.wParam; +} + +static int +test_callbacks(void) +{ + int tid; + HANDLE hThread; + uint msgnum = 0; + + print("About to create thread\n"); + hThread = CreateThread(NULL, 0, run_func, NULL, 0, &tid); + if (hThread == NULL) { + print("Error creating thread\n"); + return -1; + } + while (!thread_ready) + Sleep(0); + + /* We have to send a message to a window to get a callback. + * We go ahead and use the blocking SendMessage for simplicity; + * could use SendMessageCallback and get a callback back, but have to + * ask for messages to receive it and then have no clear exit path. + */ + if (SendMessage(hwnd, MSG_CUSTOM, WP_CRASH, msgnum++) != MSG_SUCCESS) { + print("Error %d posting window message\n", GetLastError()); + return -1; + } + /* On bucephalus (win2k3sp1) I need to send a message to get the + * thread to go into the except block: it sits waiting in the + * kernel at the NtCallbackReturn from + * KiUserCallbackExceptionHandler, and that is where it receives + * the callback for this message: seems problematic natively? + */ + PostThreadMessage(tid, MSG_CUSTOM, WP_NOP, msgnum++); + while (!past_crash) + Sleep(0); + if (SendMessage(hwnd, MSG_CUSTOM, WP_NOP, msgnum++) != MSG_SUCCESS) { + print("Error %d posting window message\n", GetLastError()); + return -1; + } + + /* Message not sent to a window is processed inside the GetMessage loop, + * with no callback involved. So this bit here is mainly to get the thread + * to exit. */ + if (!PostThreadMessage(tid, MSG_CUSTOM, WP_EXIT, msgnum++)) { + print("Error %d posting thread message\n", GetLastError()); + return -1; + } + while (last_received != MSG_CUSTOM) + Sleep(0); + + WaitForSingleObject(hThread, INFINITE); + return 0; +} + +/*************************************************************************** + * APC testing + */ + +static volatile BOOL synch_1 = TRUE; +static volatile BOOL synch_2 = TRUE; +static int result = 0; +static ULONG_PTR apc_arg = 0; + +unsigned int WINAPI +thread_func(void * arg) +{ + int res; + synch_2 = FALSE; + while (synch_1) { + /* need non alertable thread yield function */ + SwitchToThread(); + } + /* now the alertable system call */ + res = SleepEx(100, 1); + /* is going to return 192 since received apc during sleep call + * well technically 192 is io completion interruption, but seems to + * report that for any interrupting APC */ + print("SleepEx returned %d\n", res); + print("Apc arg = %d\n", (int) apc_arg); + print("Result = %d\n", result); + return 0; +} + +void WINAPI +apc_func(ULONG_PTR arg) +{ + result += 100; + apc_arg = arg; +} + +static void +test_apc(void) +{ + unsigned int tid; + HANDLE hThread; + int res; + + print("Before _beginthreadex\n"); + hThread = (HANDLE) _beginthreadex(NULL, 0, thread_func, NULL, 0, &tid); + + while (synch_2) { + SwitchToThread(); + } + + res = QueueUserAPC(apc_func, hThread, 37); + print("QueueUserAPC returned %d\n", res); + + synch_1 = FALSE; + + WaitForSingleObject((HANDLE)hThread, INFINITE); + print("After _beginthreadex\n"); +} + +int main() +{ + test_callbacks(); + test_apc(); + print("All done\n"); + return 0; +} diff --git a/suite/tests/client-interface/winxfer.dll.c b/suite/tests/client-interface/winxfer.dll.c new file mode 100644 index 00000000000..95aad216c54 --- /dev/null +++ b/suite/tests/client-interface/winxfer.dll.c @@ -0,0 +1,97 @@ +/* ********************************************************** + * Copyright (c) 2017 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 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. + */ + +/* + * Tests API interactions with Windows kernel-mediated events. + */ + +#include "dr_api.h" +#include "client_tools.h" + +#if 0 /* FIXME i#241 */ +static void +redirect_xfer(void) +{ + dr_printf("redirected!\n"); +} +#endif + +static void +kernel_xfer_event(void *drcontext, const dr_kernel_xfer_info_t *info) +{ + dr_fprintf(STDERR, "%s: type %d\n", __FUNCTION__, info->type); + dr_log(drcontext, LOG_ALL, 2, "%s: %d %p to %p sp=%zx\n", __FUNCTION__, info->type, + info->source_mcontext == NULL ? 0 : info->source_mcontext->pc, + info->target_pc, info->target_xsp); + dr_mcontext_t mc = {sizeof(mc)}; + mc.flags = DR_MC_CONTROL; + bool ok = dr_get_mcontext(drcontext, &mc); + ASSERT(ok); + ASSERT(mc.pc == info->target_pc); + ASSERT(mc.xsp == info->target_xsp); + mc.flags = DR_MC_ALL; + ok = dr_get_mcontext(drcontext, &mc); + ASSERT(ok); +#if 0 /* FIXME i#241 */ + /* FIXME i#241: test dr_set_mcontext. It's not easy though: it doesn't make much + * sense for the Ki dispatchers, there's no NtContinue in SEH64, it's not + * supported for cbret, and we don't have a test here for NtSetContextThread. I + * manually tested by sending to redirect_cbret(), confirmed a print, and then + * the app crashes. Making it continue is more work than I want to put in right + * now. Since the uses cases are few, we may want to consider disallowing + * setting the context? + */ + if (type == DR_XFER_CALLBACK_DISPATCHER) { + mc.pc = (app_pc)redirect_xfer; + ok = dr_set_mcontext(drcontext, &mc); + ASSERT(ok); + } +#endif +} + +static bool +exception_event(void *dcontext, dr_exception_t *excpt) +{ + void *fault_address = (void *)excpt->record->ExceptionInformation[1]; + dr_fprintf(STDERR, + "exception %x addr "PFX"\n", + excpt->record->ExceptionCode, + (ptr_uint_t)excpt->record->ExceptionInformation[1]); + return true; +} + +DR_EXPORT +void dr_init(client_id_t id) +{ + dr_register_kernel_xfer_event(kernel_xfer_event); + dr_register_exception_event(exception_event); +} diff --git a/suite/tests/client-interface/winxfer.templatex b/suite/tests/client-interface/winxfer.templatex new file mode 100755 index 00000000000..ab007056016 --- /dev/null +++ b/suite/tests/client-interface/winxfer.templatex @@ -0,0 +1,66 @@ +About to create thread +kernel_xfer_event: type 2 +kernel_xfer_event: type 7 +kernel_xfer_event: type 5 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +kernel_xfer_event: type 5 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +kernel_xfer_event: type 6 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +in wnd_callback 0x0*0000024 0 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +in wnd_callback 0x0*0000081 0 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +in wnd_callback 0x0*0000083 0 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +in wnd_callback 0x0*0000001 0 +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +in wnd_callback 0x0*0008001 3 0 +About to crash +exception c0000005 addr 0x0*0000040 +kernel_xfer_event: type 3 +#ifndef X64 +kernel_xfer_event: type 7 +#endif +Inside handler +kernel_xfer_event: type 6 +kernel_xfer_event: type 5 +in wnd_callback 0x0*0008001 0 2 +kernel_xfer_event: type 6 +Got message 0x0*0008001 1 3 +Before _beginthreadex +kernel_xfer_event: type 2 +kernel_xfer_event: type 7 +QueueUserAPC returned 1 +kernel_xfer_event: type 2 +kernel_xfer_event: type 7 +SleepEx returned 192 +Apc arg = 37 +Result = 100 +After _beginthreadex +All done