From fa4653857b09c4983219772159c0f208394471c7 Mon Sep 17 00:00:00 2001 From: Derek Bruening Date: Wed, 29 Nov 2017 00:34:40 -0500 Subject: [PATCH] i#241,i#1693: add kernel transfer client events (#2719) Adds a new client API event type: a kernel-mediated control transfer. This includes UNIX signal delivery and return, Windows APCs, callbacks, exceptions, NtContinue, NtSetContextThread, and callback returns. It is also raised on client redirects. The new interface passes the source context (control and integer, no multimedia) but passes just key target fields directly (new pc, new xsp) and does not pass in the full target register state to avoid copying costs (particulary for multimedia). Adds a new internal os_cxt_ptr_t type to support pointing at a CONTEXT or sig_full_cxt_t for the new events and only copying from that state if the client calls dr_get_mcontext(). Adds extra logic to get the source context for dr_redirect_execution() called from the exception event. Includes limited support for calling dr_set_mcontext() and changing the pc or other state, though there seem to be few use cases of this and given the difficulty in testing it, it's not clear it's a worthwhile feature. Adds documentation on the disparity between xbp being in CONTEXT_CONTROL yet in DR_MC_INTEGER, cautioning clients to use both INTEGER and CONTROL when they care about xbp. Includes logic to pass the real Ki pc and not the hook-displaced pc for dispatchers. I don't think it's possible to pass the real xsi and not the syscall return address for a cbret: we live with that. Adds corresponding routines drmgr_register_kernel_xfer_event() and drmgr_register_kernel_xfer_event_ex(). Re-implements drmgr's CLS API using the new kernel xfer event (this is required for proper ordering of CLS vs other clients using the xfer event). Removes DRMGR_PRIORITY_INSERT_CLS_ENTRY, DRMGR_PRIORITY_INSERT_CLS_EXIT, DRMGR_PRIORITY_NAME_CLS_ENTRY, and DRMGR_PRIORITY_NAME_CLS_EXIT. Adds tests to client.signal, client.flush, client.events, client.drmgr-test, and a new test client.winxfer. Testing of dr_set_mcontext() on Windows was done manually: automated testing is challenging to set up and left for future work. Fixes #241 Fixes #1693 --- api/docs/API.doxy | 4 +- api/docs/intro.dox | 12 +- api/docs/release.dox | 7 + core/arch/arch.c | 1 + core/globals.h | 2 + core/lib/globals_shared.h | 15 +- core/lib/instrument.c | 97 ++++++ core/lib/instrument.h | 11 +- core/lib/instrument_api.h | 151 ++++++++- core/unix/os.c | 30 +- core/unix/os_exports.h | 21 ++ core/unix/os_private.h | 6 +- core/unix/signal.c | 302 +++++++++++------- core/win32/callback.c | 81 ++++- core/win32/os.c | 69 +++- core/win32/os_exports.h | 20 ++ ext/drmgr/drmgr.c | 209 +++++------- ext/drmgr/drmgr.h | 70 ++-- suite/tests/CMakeLists.txt | 2 + suite/tests/client-interface/drmgr-test.dll.c | 21 ++ suite/tests/client-interface/events.dll.c | 140 +++++--- suite/tests/client-interface/events.templatex | 124 +++---- suite/tests/client-interface/fibers.template | 40 +-- suite/tests/client-interface/flush.dll.c | 22 +- suite/tests/client-interface/flush.template | 6 + suite/tests/client-interface/signal.dll.c | 41 ++- suite/tests/client-interface/signal.expect | 9 +- suite/tests/client-interface/winxfer.c | 291 +++++++++++++++++ suite/tests/client-interface/winxfer.dll.c | 97 ++++++ .../tests/client-interface/winxfer.templatex | 66 ++++ 30 files changed, 1519 insertions(+), 448 deletions(-) create mode 100644 suite/tests/client-interface/winxfer.c create mode 100644 suite/tests/client-interface/winxfer.dll.c create mode 100755 suite/tests/client-interface/winxfer.templatex 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