From 20be2651eebe8eecc59a837d15d2c607d0d0d34a Mon Sep 17 00:00:00 2001 From: Derek Bruening Date: Wed, 18 Mar 2020 17:51:32 -0400 Subject: [PATCH 1/2] i#4197: Add new drwrap post-call scheme: replace retaddr Adds an alternative scheme for achieving a post-call control point that does not require flushing or shared data structure examination per-call: replacing the return address with a sentinel. When the new flag DRWRAP_REPLACE_RETADDR is set, the return address is replaced with the address of a single return instruction in the client library, with the real address saved. When a block is seen consisting of that sentinel instruction, post-call callbacks are called, and then control is sent to the saved real address using dr_redirect_native_target(). Adds wrapping tests to drwrap-test. This new scheme requires restoring return addresses on the stack on detach or other state translation. Adds functionality to do so, along with a new test client.drwrap-test-detach. This requires the client's state restoration event be called for addresses not in the code cache. Adds such a call. Adds comments about translation problems with clean call mangling which is filed as i#4219. The issues seen here are all limited to traces, so the test works around the problems with -disable_traces. Tested the core drwrap behavior on ARM and AArch64 but missing general detach support there (#1578) prevents enabling the detach test there. Issue: #4219 Fixes #4197 --- api/docs/release.dox | 3 + core/synch.c | 29 +++ core/translate.c | 11 +- ext/drwrap/drwrap.c | 229 +++++++++++++++--- ext/drwrap/drwrap.h | 11 + ext/drwrap/drwrap_asm_aarch64.asm | 11 + ext/drwrap/drwrap_asm_arm.asm | 12 +- ext/drwrap/drwrap_asm_x86.asm | 13 +- suite/tests/CMakeLists.txt | 21 +- .../client-interface/drwrap-test-detach.cpp | 157 ++++++++++++ .../drwrap-test-detach.expect | 3 + .../client-interface/drwrap-test.appdll.c | 96 ++++++++ .../tests/client-interface/drwrap-test.dll.c | 41 ++++ .../client-interface/drwrap-test.template | 24 ++ suite/tests/thread.h | 6 +- 15 files changed, 622 insertions(+), 45 deletions(-) create mode 100644 suite/tests/client-interface/drwrap-test-detach.cpp create mode 100644 suite/tests/client-interface/drwrap-test-detach.expect diff --git a/api/docs/release.dox b/api/docs/release.dox index 0da18502aa7..ab844beb4fc 100644 --- a/api/docs/release.dox +++ b/api/docs/release.dox @@ -255,6 +255,9 @@ Further non-compatibility-affecting changes include: - Added drwrap_get_stats(). - Added #DRWRAP_NO_DYNAMIC_RETADDRS for reducing drwrap overhead at the cost of missing some post-call callbacks. + - Added #DRWRAP_REPLACE_RETADDR for an alternative method of setting up post-call + control points by replacing return addresses. This does not work for every + application, but reduces overhead. - Added -record_dynsym_only to drcachesim for faster function tracing symbol lookups when internal symbols are not needed. - Added dr_merge_arith_flags() as a convenience routine to merge arithmetic flags diff --git a/core/synch.c b/core/synch.c index ed598bd042d..564db9b1835 100644 --- a/core/synch.c +++ b/core/synch.c @@ -447,6 +447,27 @@ translate_mcontext(thread_record_t *trec, priv_mcontext_t *mcontext, bool restor dump_mcontext(get_mcontext(trec->dcontext), THREAD_GET, DUMP_NOT_XML); }); *mcontext = *get_mcontext(trec->dcontext); +#ifdef CLIENT_INTERFACE + if (dr_xl8_hook_exists()) { + /* The client may need to translate here if it's using sentinel + * addresses outside of the code cache as targets. + */ + dr_restore_state_info_t client_info; + dr_mcontext_t client_mcontext; + dr_mcontext_init(&client_mcontext); + priv_mcontext_to_dr_mcontext(&client_mcontext, mcontext); + client_info.raw_mcontext = &client_mcontext; + client_info.raw_mcontext_valid = true; + client_info.mcontext = &client_mcontext; + client_info.fragment_info.tag = NULL; + client_info.fragment_info.cache_start_pc = NULL; + client_info.fragment_info.is_trace = false; + client_info.fragment_info.app_code_consistent = true; + if (!instrument_restore_state(trec->dcontext, true, &client_info)) + return false; + dr_mcontext_to_priv_mcontext(mcontext, &client_mcontext); + } +#endif return true; } } @@ -2165,6 +2186,14 @@ detach_on_permanent_stack(bool internal, bool do_cleanup, dr_stats_t *drstats) * app that assumes no signals and assumes its non-auto-restart syscalls * don't need loops could be broken. */ + LOG(GLOBAL, LOG_ALL, 3, + /* Having the code bytes can help diagnose post-detach where the code + * cache is gone. + */ + "Detach: pre-xl8 pc=%p (%02x %02x %02x %02x %02x), xsp=%p " + "for thread " TIDFMT "\n", + mc.pc, *mc.pc, *(mc.pc + 1), *(mc.pc + 2), *(mc.pc + 3), *(mc.pc + 4), + mc.xsp, threads[i]->id); DEBUG_DECLARE(ok =) translate_mcontext(threads[i], &mc, true /*restore mem*/, NULL /*f*/); ASSERT(ok); diff --git a/core/translate.c b/core/translate.c index d4f46ddb056..a073e8968e1 100644 --- a/core/translate.c +++ b/core/translate.c @@ -584,6 +584,7 @@ translate_restore_clean_call(dcontext_t *tdcontext, translate_walk_t *walk) /* PR 302951: we recognize a clean call by its combination of * our-mangling and NULL translation. * We restore to the priv_mcontext_t that was pushed on the stack. + * FIXME i#4219: This is not safe: see comment below. */ LOG(THREAD_GET, LOG_INTERP, 2, "\ttranslating clean call arg crash\n"); dr_get_mcontext_priv(tdcontext, NULL, walk->mc); @@ -733,7 +734,11 @@ recreate_app_state_from_info(dcontext_t *tdcontext, const translation_info_t *in * (should spend enough time at syscalls that will hit safe spot in * reasonable time). */ - /* PR 302951: our clean calls do show up here and have full state */ + /* PR 302951: our clean calls do show up here and have full state. + * FIXME i#4219: Actually we do *not* always have full state: for asynch + * xl8 we could be before setup or after teardown of the mcontext on the + * dstack, and with leaner clean calls we might not have the full mcontext. + */ if (answer == NULL && ours) translate_restore_clean_call(tdcontext, &walk); else @@ -888,7 +893,9 @@ recreate_app_state_from_ilist(dcontext_t *tdcontext, instrlist_t *ilist, byte *s * in the middle of client meta code. */ ASSERT(instr_is_meta(inst)); - /* PR 302951: our clean calls do show up here and have full state */ + /* PR 302951: our clean calls do show up here and have full state. + * FIXME i#4219: This is not safe: see comment above. + */ if (instr_is_our_mangling(inst)) translate_restore_clean_call(tdcontext, &walk); else diff --git a/ext/drwrap/drwrap.c b/ext/drwrap/drwrap.c index aa464198015..6f5655550c3 100644 --- a/ext/drwrap/drwrap.c +++ b/ext/drwrap/drwrap.c @@ -71,13 +71,22 @@ static uint verbose = 0; #define ALIGNED(x, alignment) ((((ptr_uint_t)x) & ((alignment)-1)) == 0) -/* We rely on being able to clobber this register at call sites */ +/* We rely on being able to clobber this register at call sites. */ +/* At return, we assume we can clobber caller-saved regs not used for return values. */ #ifdef ARM # define CALL_POINT_SCRATCH_REG DR_REG_R12 +# define RETURN_POINT_SCRATCH_REG DR_REG_R1 +#elif defined(AARCH64) +# define CALL_POINT_SCRATCH_REG DR_REG_X12 +# define RETURN_POINT_SCRATCH_REG DR_REG_X1 #elif defined(X64) # define CALL_POINT_SCRATCH_REG DR_REG_R11 +# define RETURN_POINT_SCRATCH_REG DR_REG_RCX +#elif defined(X86) +# define RETURN_POINT_SCRATCH_REG DR_REG_ECX #else # define CALL_POINT_SCRATCH_REG DR_REG_NULL +# define RETURN_POINT_SCRATCH_REG DR_REG_NULL #endif /* XXX i#4215: DR should provide 64-bit-sized atomics for 32-bit code. */ @@ -109,6 +118,9 @@ replace_native_ret_imms(void); void replace_native_ret_imms_end(void); +void +replace_retaddr_sentinel(void); + #ifdef AARCHXX byte * get_cur_xsp(void); @@ -224,6 +236,7 @@ typedef struct _per_thread_t { /* did we see an exception while in a wrapped routine? */ bool hit_exception; #endif + app_pc retaddr[MAX_WRAP_NESTING]; } per_thread_t; /*************************************************************************** @@ -869,6 +882,10 @@ drwrap_event_module_unload(void *drcontext, const module_data_t *info); static void drwrap_fragment_delete(void *dc /*may be NULL*/, void *tag); +static bool +drwrap_event_restore_state_ex(void *drcontext, bool restore_memory, + dr_restore_state_info_t *info); + static inline void drwrap_in_callee_check_unwind(void *drcontext, per_thread_t *pt, dr_mcontext_t *mc); @@ -926,6 +943,8 @@ drwrap_init(void) if (!drmgr_register_bb_instrumentation_event(drwrap_event_bb_analysis, drwrap_event_bb_insert, &pri_insert)) return false; + if (!drmgr_register_restore_state_ex_event(drwrap_event_restore_state_ex)) + return false; hashtable_init(&replace_table, REPLACE_TABLE_HASH_BITS, HASH_INTPTR, false /*!strdup*/); @@ -983,6 +1002,7 @@ drwrap_exit(void) if (!drmgr_unregister_bb_app2app_event(drwrap_event_bb_app2app) || !drmgr_unregister_bb_instrumentation_event(drwrap_event_bb_analysis) || + !drmgr_unregister_restore_state_ex_event(drwrap_event_restore_state_ex) || !drmgr_unregister_module_unload_event(drwrap_event_module_unload) || !drmgr_unregister_tls_field(tls_idx) || !dr_unregister_delete_event(drwrap_fragment_delete)) @@ -1489,6 +1509,17 @@ drwrap_event_bb_app2app(void *drcontext, void *tag, instrlist_t *bb, bool for_tr { instr_t *inst; app_pc pc, replace; + if (dr_fragment_app_pc(tag) == (app_pc)replace_retaddr_sentinel) { + /* This is our sentinel. We want this to be invisible to observation clients, + * so we remove our return instruction and replace with just a meta nop. + * The insert event will still be called by drmgr. + */ + inst = instrlist_first(bb); + ASSERT(instr_get_next(inst) == NULL, "Must just be 1 instr"); + instrlist_meta_preinsert(bb, inst, XINST_CREATE_nop(drcontext)); + instrlist_remove(bb, inst); + instr_destroy(drcontext, inst); + } if (replace_table.entries == 0 && replace_native_table.entries == 0) return DR_EMIT_DEFAULT; /* XXX: if we had dr_bbs_cross_ctis() query (i#427) we could just check 1st instr */ @@ -1649,9 +1680,8 @@ drwrap_flush_func(app_pc func) ASSERT(false, "wrap update flush failed"); } -#ifdef X86 static app_pc -get_retaddr_at_entry(reg_t xsp) +get_retaddr_from_stack(reg_t xsp) { app_pc retaddr = NULL; if (TEST(DRWRAP_SAFE_READ_RETADDR, global_flags)) { @@ -1661,7 +1691,20 @@ get_retaddr_at_entry(reg_t xsp) retaddr = *(app_pc *)xsp; return retaddr; } -#endif + +static bool +set_retaddr_on_stack(reg_t xsp, app_pc value) +{ + bool res = true; + if (TEST(DRWRAP_SAFE_READ_RETADDR, global_flags)) { + DR_TRY_EXCEPT(dr_get_current_drcontext(), { *(app_pc *)xsp = value; }, + { /* EXCEPT */ + res = false; + }); + } else + *(app_pc *)xsp = value; + return res; +} /* may not return */ static void @@ -1755,8 +1798,8 @@ wrap_table_lookup_normalized_pc(app_pc pc) * wrap_lock is held */ static inline void -drwrap_ensure_postcall(void *drcontext, wrap_entry_t *wrap, drwrap_context_t *wrapcxt, - app_pc decorated_pc) +drwrap_ensure_postcall(void *drcontext, per_thread_t *pt, wrap_entry_t *wrap, + drwrap_context_t *wrapcxt, app_pc decorated_pc) { if (TEST(DRWRAP_NO_DYNAMIC_RETADDRS, wrap->flags)) { /* i#0470: On a large multithreaded app, using shared memory here and especially @@ -1771,6 +1814,19 @@ drwrap_ensure_postcall(void *drcontext, wrap_entry_t *wrap, drwrap_context_t *wr } app_pc retaddr = dr_app_pc_as_load_target(DR_ISA_ARM_THUMB, wrapcxt->retaddr); app_pc plain_pc = dr_app_pc_as_load_target(DR_ISA_ARM_THUMB, decorated_pc); + if (TEST(DRWRAP_REPLACE_RETADDR, wrap->flags)) { + NOTIFY(2, "DRWRAP_REPLACE_RETADDR: saving real retaddr as [%d] " PFX "\n", + pt->wrap_level, retaddr); + pt->retaddr[pt->wrap_level] = wrapcxt->retaddr; /* Original, not load tgt. */ +#ifdef X86 + set_retaddr_on_stack(wrapcxt->mc->xsp, (app_pc)replace_retaddr_sentinel); +#else + drwrap_get_mcontext_internal(wrapcxt, DR_MC_CONTROL); + wrapcxt->mc->lr = (reg_t)replace_retaddr_sentinel; + wrapcxt->mc_modified = true; +#endif + return; + } int i; /* avoid lock and hashtable lookup by caching prior retaddrs */ for (i = 0; i < POSTCALL_CACHE_SIZE; i++) { @@ -1842,7 +1898,7 @@ drwrap_in_callee(void *arg1, reg_t xsp _IF_NOT_X86(reg_t lr)) NOTIFY(2, "%s: level %d function " PFX "\n", __FUNCTION__, pt->wrap_level + 1, pc); drwrap_context_init(drcontext, &wrapcxt, pc, &mc, DRWRAP_WHERE_PRE_FUNC, - IF_X86_ELSE(get_retaddr_at_entry(xsp), (app_pc)lr)); + IF_X86_ELSE(get_retaddr_from_stack(xsp), (app_pc)lr)); drwrap_in_callee_check_unwind(drcontext, pt, &mc); @@ -1856,6 +1912,18 @@ drwrap_in_callee(void *arg1, reg_t xsp _IF_NOT_X86(reg_t lr)) decorated_pc = pc; pc = dr_app_pc_as_load_target(DR_ISA_ARM_THUMB, pc); + pt->wrap_level++; + ASSERT(pt->wrap_level >= 0, "wrapping level corrupted"); + ASSERT(pt->wrap_level < MAX_WRAP_NESTING, "max wrapped nesting reached"); + if (pt->wrap_level >= MAX_WRAP_NESTING) { + if (!TEST(DRWRAP_NO_FRILLS, global_flags)) + dr_recurlock_unlock(wrap_lock); + wrapcxt.where_am_i = DRWRAP_WHERE_OUTSIDE_CALLBACK; + return; /* we'll have to skip stuff */ + } + /* Clear this field to make state restoration easier. */ + pt->retaddr[pt->wrap_level] = NULL; + /* ensure we have post-call instru */ if (wrap != NULL) { for (e = wrap; e != NULL; e = e->next) { @@ -1865,18 +1933,9 @@ drwrap_in_callee(void *arg1, reg_t xsp _IF_NOT_X86(reg_t lr)) } } if (intercept_post && wrapcxt.retaddr != NULL) - drwrap_ensure_postcall(drcontext, wrap, &wrapcxt, decorated_pc); + drwrap_ensure_postcall(drcontext, pt, wrap, &wrapcxt, decorated_pc); } - pt->wrap_level++; - ASSERT(pt->wrap_level >= 0, "wrapping level corrupted"); - ASSERT(pt->wrap_level < MAX_WRAP_NESTING, "max wrapped nesting reached"); - if (pt->wrap_level >= MAX_WRAP_NESTING) { - if (!TEST(DRWRAP_NO_FRILLS, global_flags)) - dr_recurlock_unlock(wrap_lock); - wrapcxt.where_am_i = DRWRAP_WHERE_OUTSIDE_CALLBACK; - return; /* we'll have to skip stuff */ - } pt->last_wrap_func[pt->wrap_level] = decorated_pc; if (TEST(DRWRAP_NO_FRILLS, global_flags)) pt->last_wrap_entry[pt->wrap_level] = wrap; @@ -2016,6 +2075,12 @@ drwrap_after_callee_func(void *drcontext, per_thread_t *pt, dr_mcontext_t *mc, i dr_recurlock_unlock(wrap_lock); continue; } + if (TEST(DRWRAP_REPLACE_RETADDR, wrap->flags)) { + NOTIFY(2, "DRWRAP_REPLACE_RETADDR: setting real retaddr as [%d] " PFX "\n", + level, pt->retaddr[level]); + dr_write_saved_reg(drcontext, SPILL_SLOT_REDIRECT_NATIVE_TGT, + (reg_t)pt->retaddr[level]); + } if (TEST(DRWRAP_NO_FRILLS, global_flags)) { user_data = pt->user_data_nofrills[level]; } else { @@ -2184,6 +2249,25 @@ drwrap_after_callee(app_pc retaddr, reg_t xsp) } } +static void +drwrap_insert_post_call(void *drcontext, instrlist_t *bb, instr_t *where, + app_pc pc_as_jmp_target) +{ + /* XXX: for DRWRAP_FAST_CLEANCALLS we must preserve state b/c + * our post-call points can be reached through non-return paths. + * We could insert an inline check for "pt->wrap_level >= 0" but + * that requires spilling a GPR and flags and gets messy w/o drreg + * vs other components' spill slots. + */ + dr_cleancall_save_t flags = 0; + NOTIFY(2, "drwrap inserting post-call cb at " PFX "\n", pc_as_jmp_target); + dr_insert_clean_call_ex(drcontext, bb, where, (void *)drwrap_after_callee, flags, 2, + /* i#1689: retaddrs do have LSB=1 */ + OPND_CREATE_INTPTR((ptr_int_t)pc_as_jmp_target), + /* pass in xsp to avoid dr_get_mcontext */ + opnd_create_reg(DR_REG_XSP)); +} + static dr_emit_flags_t drwrap_event_bb_analysis(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, bool translating, OUT void **user_data) @@ -2196,6 +2280,7 @@ static dr_emit_flags_t drwrap_event_bb_insert(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst, bool for_trace, bool translating, void *user_data) { + dr_emit_flags_t res = DR_EMIT_DEFAULT; /* XXX: if we had dr_bbs_cross_ctis() query (i#427) we could just check 1st instr */ wrap_entry_t *wrap; @@ -2235,20 +2320,24 @@ drwrap_event_bb_insert(void *drcontext, void *tag, instrlist_t *bb, instr_t *ins dr_recurlock_unlock(wrap_lock); if (post_call_lookup_for_instru(instr_get_app_pc(inst) /*normalized*/)) { - /* XXX: for DRWRAP_FAST_CLEANCALLS we must preserve state b/c - * our post-call points can be reached through non-return paths. - * We could insert an inline check for "pt->wrap_level >= 0" but - * that requires spilling a GPR and flags and gets messy w/o drreg - * vs other components' spill slots. + drwrap_insert_post_call(drcontext, bb, inst, pc); + } + + if (dr_fragment_app_pc(tag) == (app_pc)replace_retaddr_sentinel) { + drwrap_insert_post_call(drcontext, bb, inst, pc); + /* The post-call C code put the real retaddr into the DR slot that will be + * used by dr_redirect_native_target(). */ - dr_cleancall_save_t flags = 0; - NOTIFY(2, "drwrap inserting post-call cb at " PFX "\n", pc); - dr_insert_clean_call_ex(drcontext, bb, inst, (void *)drwrap_after_callee, flags, - 2, - /* i#1689: retaddrs do have LSB=1 */ - OPND_CREATE_INTPTR((ptr_int_t)pc), - /* pass in xsp to avoid dr_get_mcontext */ - opnd_create_reg(DR_REG_XSP)); + app_pc tgt = dr_redirect_native_target(drcontext); + reg_id_t scratch = RETURN_POINT_SCRATCH_REG; + instrlist_insert_mov_immed_ptrsz(drcontext, (ptr_int_t)tgt, + opnd_create_reg(scratch), bb, inst, NULL, NULL); + instrlist_meta_preinsert( + bb, inst, XINST_CREATE_jump_reg(drcontext, opnd_create_reg(scratch))); + /* This unusual transition confuses DR trying to stitch blocks together into + * a trace. + */ + res = DR_EMIT_MUST_END_TRACE; } if (instr_is_call(inst) && instr_is_app(inst) && opnd_is_pc(instr_get_target(inst))) { @@ -2256,9 +2345,10 @@ drwrap_event_bb_insert(void *drcontext, void *tag, instrlist_t *bb, instr_t *ins opnd_get_pc(instr_get_target(inst))); dr_recurlock_lock(wrap_lock); wrap = hashtable_lookup(&wrap_table, (void *)target); - bool wrapping = wrap != NULL && wrap->post_cb != NULL; + bool add_post = wrap != NULL && wrap->post_cb != NULL && + !TEST(DRWRAP_REPLACE_RETADDR, wrap->flags); dr_recurlock_unlock(wrap_lock); - if (wrapping) { + if (add_post) { /* Add the pc-as-load-target (so *not* "pc"). */ dr_rwlock_write_lock(post_call_rwlock); post_call_entry_add(instr_get_app_pc(inst) + instr_length(drcontext, inst), @@ -2267,7 +2357,7 @@ drwrap_event_bb_insert(void *drcontext, void *tag, instrlist_t *bb, instr_t *ins } } - return DR_EMIT_DEFAULT; + return res; } static void @@ -2294,6 +2384,75 @@ drwrap_fragment_delete(void *dc /*may be NULL*/, void *tag) /* switched to checking consistency at lookup time (DrMemi#673) */ } +static bool +drwrap_event_restore_state_ex(void *drcontext, bool restore_memory, + dr_restore_state_info_t *info) +{ + per_thread_t *pt = (per_thread_t *)drmgr_get_tls_field(drcontext, tls_idx); + if (pt->wrap_level < 0) + return true; + if (info->mcontext->pc == (app_pc)replace_retaddr_sentinel) { + NOTIFY(1, "%s: updating T" TIDFMT " PC to real level %d retaddr " PFX "\n", + __FUNCTION__, dr_get_thread_id(drcontext), pt->wrap_level, + pt->retaddr[pt->wrap_level]); + info->mcontext->pc = pt->retaddr[pt->wrap_level]; + } +#ifdef AARCHXX + if (info->mcontext->lr == (reg_t)replace_retaddr_sentinel) { + NOTIFY(1, "%s: updating T" TIDFMT " LR to real retaddr " PFX "\n", __FUNCTION__, + dr_get_thread_id(drcontext), pt->retaddr[pt->wrap_level]); + info->mcontext->lr = (reg_t)pt->retaddr[pt->wrap_level]; + } +#endif + if (!restore_memory) + return true; + for (int i = 0; i <= pt->wrap_level; ++i) { + /* We clear pt->retaddr each time, so if it's set we know it's a replaced one. */ + if (pt->retaddr[i] != NULL && info->mcontext->xsp <= pt->app_esp[i]) { +#ifdef X86 + app_pc ra = get_retaddr_from_stack(pt->app_esp[i]); + if (ra != (app_pc)replace_retaddr_sentinel) { + NOTIFY(1, + "%s: WARNING: T" TIDFMT " retaddr @ " PFX " is " PFX + ", not sentinel!\n", + __FUNCTION__, dr_get_thread_id(drcontext), pt->app_esp[i], ra); + continue; + } + NOTIFY(1, + "%s: updating T" TIDFMT " retaddr @ " PFX " from sentinel " PFX + " to real retaddr " PFX "\n", + __FUNCTION__, dr_get_thread_id(drcontext), pt->app_esp[i], + replace_retaddr_sentinel, pt->retaddr[i]); + set_retaddr_on_stack(pt->app_esp[i], pt->retaddr[i]); +#else + /* Unfortunately return addresses are pushed from the link register to the + * stack in non-uniform ways, forcing us to scan. This can be time-consuming + * for large frames, and it could incorrectly replace a integer value that + * happens to look like our sentinel. We've warned the user about this + * risk for this flag though. + */ + app_pc *scan_stop; + if (i < pt->wrap_level) + scan_stop = (app_pc *)pt->app_esp[i + 1]; + else + scan_stop = (app_pc *)info->mcontext->xsp; + for (app_pc *scan = (app_pc *)pt->app_esp[i]; scan <= scan_stop; --scan) { + app_pc ra = get_retaddr_from_stack((reg_t)scan); + if (ra == (app_pc)replace_retaddr_sentinel) { + NOTIFY(1, + "%s: updating T" TIDFMT " retaddr @ " PFX " from sentinel " PFX + " to real retaddr " PFX "\n", + __FUNCTION__, dr_get_thread_id(drcontext), scan, + replace_retaddr_sentinel, pt->retaddr[i]); + set_retaddr_on_stack((reg_t)scan, pt->retaddr[i]); + } + } +#endif + } + } + return true; +} + DR_EXPORT bool drwrap_wrap_ex(app_pc func, void (*pre_func_cb)(void *wrapcxt, INOUT void **user_data), @@ -2305,6 +2464,10 @@ drwrap_wrap_ex(app_pc func, void (*pre_func_cb)(void *wrapcxt, INOUT void **user /* allow one side to be NULL (i#562) */ if (func == NULL || (pre_func_cb == NULL && post_func_cb == NULL)) return false; + if (TEST(DRWRAP_REPLACE_RETADDR, flags) && post_func_cb == NULL) { + /* Nonsensical combination so we fail. */ + return false; + } /* XXX i#1460: should drwrap auto-flush target in case called late? * Currently we document that the caller must do that. diff --git a/ext/drwrap/drwrap.h b/ext/drwrap/drwrap.h index ba0aa91f943..c561dd9f7d0 100644 --- a/ext/drwrap/drwrap.h +++ b/ext/drwrap/drwrap.h @@ -349,6 +349,17 @@ typedef enum { * may be missed. */ DRWRAP_NO_DYNAMIC_RETADDRS = 0x02, + /** + * If this flag is set, then post-call points are identified by changing the + * application return address upon entering the callee. This is more efficient than + * the default method, which requires shared storage and locks and flushing. + * However, this does violate transparency, and may cause some applications to fail. + * In particular, detaching on AArchXX requires scanning the stack to find where the + * return address was stored, which could conceivably replace an integer or + * non-pointer value that happens to match the sentinel used. Use this at your own + * risk. + */ + DRWRAP_REPLACE_RETADDR = 0x04, } drwrap_wrap_flags_t; /* offset of drwrap_callconv_t in drwrap_wrap_flags_t */ diff --git a/ext/drwrap/drwrap_asm_aarch64.asm b/ext/drwrap/drwrap_asm_aarch64.asm index 720373ebde3..0c25308ca8d 100644 --- a/ext/drwrap/drwrap_asm_aarch64.asm +++ b/ext/drwrap/drwrap_asm_aarch64.asm @@ -1,4 +1,5 @@ /* ********************************************************** + * Copyright (c) 2020 Google, Inc. All rights reserved. * Copyright (c) 2016 ARM Limited. All rights reserved. * **********************************************************/ @@ -71,5 +72,15 @@ GLOBAL_LABEL(FUNCNAME:) mov x0, sp ret END_FUNC(FUNCNAME) +#undef FUNCNAME + +/* We just need a sentinel block that does not cause DR to complain about + * non-executable code or illegal instrutions, for DRWRAP_REPLACE_RETADDR. + */ +#define FUNCNAME replace_retaddr_sentinel + DECLARE_FUNC(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) + ret + END_FUNC(FUNCNAME) END_FILE diff --git a/ext/drwrap/drwrap_asm_arm.asm b/ext/drwrap/drwrap_asm_arm.asm index ab82876788c..b2f3740bcee 100644 --- a/ext/drwrap/drwrap_asm_arm.asm +++ b/ext/drwrap/drwrap_asm_arm.asm @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2015 Google, Inc. All rights reserved. + * Copyright (c) 2015-2020 Google, Inc. All rights reserved. * ********************************************************** */ /* @@ -92,6 +92,16 @@ GLOBAL_LABEL(FUNCNAME:) mov r0, sp bx lr END_FUNC(FUNCNAME) +#undef FUNCNAME + +/* We just need a sentinel block that does not cause DR to complain about + * non-executable code or illegal instrutions, for DRWRAP_REPLACE_RETADDR. + */ +#define FUNCNAME replace_retaddr_sentinel + DECLARE_FUNC(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) + bx lr + END_FUNC(FUNCNAME) END_FILE diff --git a/ext/drwrap/drwrap_asm_x86.asm b/ext/drwrap/drwrap_asm_x86.asm index bbf77c77e13..921098b712f 100644 --- a/ext/drwrap/drwrap_asm_x86.asm +++ b/ext/drwrap/drwrap_asm_x86.asm @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2012-2014 Google, Inc. All rights reserved. + * Copyright (c) 2012-2020 Google, Inc. All rights reserved. * ********************************************************** */ /* @@ -190,6 +190,17 @@ ADDRTAKEN_LABEL(replace_native_ret_imms:) ADDRTAKEN_LABEL(replace_native_ret_imms_end:) nop END_FUNC(FUNCNAME) +#undef FUNCNAME + + +/* We just need a sentinel block that does not cause DR to complain about + * non-executable code or illegal instrutions, for DRWRAP_REPLACE_RETADDR. + */ +#define FUNCNAME replace_retaddr_sentinel + DECLARE_FUNC(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) + ret + END_FUNC(FUNCNAME) END_FILE diff --git a/suite/tests/CMakeLists.txt b/suite/tests/CMakeLists.txt index 1446cd53838..ab3ccf2d878 100644 --- a/suite/tests/CMakeLists.txt +++ b/suite/tests/CMakeLists.txt @@ -711,8 +711,13 @@ function(configure_app_api_build_flags test decoder use_static_DR) get_target_property(pre_lflags ${test} LINK_FLAGS) # disable the default rpath for standalone apps set(DynamoRIO_RPATH OFF) - # to avoid changing all the REG_ constants we ask for compatibility - set(DynamoRIO_REG_COMPATIBILITY ON) + # To avoid changing all the REG_ constants we ask for compatibility. + if (DEFINED ${test}_no_reg_compat) + # We try but it's already in the flags so we unset in drwrap-test-detach.cpp. + set(DynamoRIO_REG_COMPATIBILITY OFF) + else () + set(DynamoRIO_REG_COMPATIBILITY ON) + endif () if (decoder) configure_DynamoRIO_decoder(${test}) set(extra_flags "-DSTANDALONE_DECODER") @@ -2300,13 +2305,21 @@ if (CLIENT_INTERFACE) tochcon(client.drwrap-test.appdll textrel_shlib_t) if (WIN32) # export from asm code - append_link_flags(client.drwrap-test.appdll "/export:makes_tailcall") + append_link_flags(client.drwrap-test.appdll + "/export:makes_tailcall /export:tailcall_test2 /export:tailcall_tail") endif (WIN32) if (NOT ANDROID) # XXX i#1874: get working on Android tobuild_ci(client.drwrap-test-callconv client-interface/drwrap-test-callconv.cpp "" "" "") use_DynamoRIO_extension(client.drwrap-test-callconv.dll drwrap) endif () + if (NOT ARM AND NOT AARCH64) # FIXME i#1578: fix detach on ARM/AArch64 + set(client.drwrap-test-detach_no_reg_compat) + tobuild_api(client.drwrap-test-detach client-interface/drwrap-test-detach.cpp + "" "" OFF ON) + use_DynamoRIO_extension(client.drwrap-test-detach drwrap_static) + link_with_pthread(client.drwrap-test-detach) + endif () # We rely on dbghelp >= 6.0 for our drsyms and sample.instrcalls tests, # but the system dbghelp pre-Vista is too old, so we copy one from VS. @@ -2478,7 +2491,7 @@ if (CLIENT_INTERFACE) endif () endif (ARM) - if (NOT ARM AND NOT AARCH64) # FIXME i#1551, i#1569: fix bugs on ARM/AArch64 + if (NOT ARM AND NOT AARCH64) # FIXME i#1578: fix detach on ARM/AArch64 tobuild_api(api.startstop api/startstop.c "" "" OFF OFF) link_with_pthread(api.startstop) tobuild_api(api.detach api/detach.c "" "" OFF OFF) diff --git a/suite/tests/client-interface/drwrap-test-detach.cpp b/suite/tests/client-interface/drwrap-test-detach.cpp new file mode 100644 index 00000000000..001c962ad66 --- /dev/null +++ b/suite/tests/client-interface/drwrap-test-detach.cpp @@ -0,0 +1,157 @@ +/* ********************************************************** + * Copyright (c) 2020 Google, Inc. All rights reserved. + * **********************************************************/ + +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of 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. + */ + +/* Tests detach state restoration for DRWRAP_REPLACE_RETADDR. */ + +/* XXX: We undef this b/c it's easier than getting rid of from CMake with the + * global cflags config where all the other tests want this set. + */ +#undef DR_REG_ENUM_COMPATIBILITY + +#include +#include +#include +#include "configure.h" +#include "dr_api.h" +#include "drwrap.h" +#include "tools.h" +#include "thread.h" +#include "condvar.h" + +#define VERBOSE 0 +#if VERBOSE +# define VPRINT(...) print(__VA_ARGS__) +#else +# define VPRINT(...) /* nothing */ +#endif + +std::atomic sideline_exit; +static void *sideline_ready_for_attach; +static void *sideline_continue; +static int pre_count; +static int post_count; + +extern "C" { /* Make it easy to get the name across platforms. */ +EXPORT void +wrapped_subfunc(void) +{ + /* Empty. */ +} +EXPORT void +wrapped_func(void) +{ + wrapped_subfunc(); +} +} + +THREAD_FUNC_RETURN_TYPE +sideline_func(void *arg) +{ + void (*asm_func)(void) = (void (*)(void))arg; + signal_cond_var(sideline_ready_for_attach); + wait_cond_var(sideline_continue); + while (!sideline_exit.load(std::memory_order_acquire)) { + for (int i = 0; i < 10; ++i) + wrapped_func(); + } + return THREAD_FUNC_RETURN_ZERO; +} + +static void +wrap_pre(void *wrapcxt, OUT void **user_data) +{ + ++pre_count; +} + +static void +wrap_post(void *wrapcxt, void *user_data) +{ + ++post_count; +} + +static void +event_exit(void) +{ + /* Depending on where we detach, pre can be up to 2 larger. */ + assert(pre_count == post_count || pre_count - 1 == post_count || + pre_count - 2 == post_count); + drwrap_exit(); + dr_fprintf(STDERR, "client done\n"); +} + +DR_EXPORT void +dr_client_main(client_id_t id, int argc, const char *argv[]) +{ + std::cerr << "in dr_client_main\n"; + dr_register_exit_event(event_exit); + drwrap_init(); + + module_data_t *module = dr_get_main_module(); + app_pc pc = (app_pc)dr_get_proc_address(module->handle, "wrapped_func"); + bool ok = drwrap_wrap_ex(pc, wrap_pre, wrap_post, nullptr, DRWRAP_REPLACE_RETADDR); + assert(ok); + assert(drwrap_is_wrapped(pc, wrap_pre, wrap_post)); + pc = (app_pc)dr_get_proc_address(module->handle, "wrapped_subfunc"); + ok = drwrap_wrap_ex(pc, wrap_pre, wrap_post, nullptr, DRWRAP_REPLACE_RETADDR); + assert(ok); + dr_free_module_data(module); +} + +int +main(void) +{ + if (!my_setenv("DYNAMORIO_OPTIONS", + "-stderr_mask 0xc" + // XXX i#4219: Work around clean call xl8 problems with traces. + " -disable_traces" + " -client_lib ';;'")) + std::cerr << "failed to set env var!\n"; + sideline_continue = create_cond_var(); + sideline_ready_for_attach = create_cond_var(); + + thread_t thread = create_thread(sideline_func, nullptr); + dr_app_setup(); + wait_cond_var(sideline_ready_for_attach); + VPRINT("Starting DR\n"); + dr_app_start(); + signal_cond_var(sideline_continue); + thread_sleep(1); + VPRINT("Detaching\n"); + dr_app_stop_and_cleanup(); + sideline_exit.store(true, std::memory_order_release); + join_thread(thread); + + destroy_cond_var(sideline_continue); + destroy_cond_var(sideline_ready_for_attach); + print("app done\n"); + return 0; +} diff --git a/suite/tests/client-interface/drwrap-test-detach.expect b/suite/tests/client-interface/drwrap-test-detach.expect new file mode 100644 index 00000000000..a768dd18981 --- /dev/null +++ b/suite/tests/client-interface/drwrap-test-detach.expect @@ -0,0 +1,3 @@ +in dr_client_main +client done +app done diff --git a/suite/tests/client-interface/drwrap-test.appdll.c b/suite/tests/client-interface/drwrap-test.appdll.c index 076276f11b8..ca3222c8264 100644 --- a/suite/tests/client-interface/drwrap-test.appdll.c +++ b/suite/tests/client-interface/drwrap-test.appdll.c @@ -239,6 +239,46 @@ longdone(void) print("longdone\n"); } +/*************************************************************************** + * Test DRWRAP_REPLACE_RETADDR. + */ + +void +tailcall_test2(void); + +void +print_from_asm(int val) +{ + print("%s %d\n", __FUNCTION__, val); +} + +int EXPORT +called_indirectly_subcall(int y) +{ + print("%s %d\n", __FUNCTION__, y); + return y + 1; +} + +int EXPORT +called_indirectly(int x) +{ + int z = called_indirectly_subcall(x + 1); + print("%s %d => %d\n", __FUNCTION__, x, z); + return z; +} + +static void +test_replace_retaddr(void) +{ + int (*indir)(int) = called_indirectly; + (*called_indirectly)(42); + tailcall_test2(); +} + +/*************************************************************************** + * Top level. + */ + void run_tests(void) { @@ -275,6 +315,8 @@ run_tests(void) if (setjmp(mark) == 0) longstart(); longdone(); + + test_replace_retaddr(); } # ifdef WINDOWS @@ -328,6 +370,60 @@ GLOBAL_LABEL(FUNCNAME:) ret /* won't get here */ # endif END_FUNC(FUNCNAME) +# undef FUNCNAME + + +DECL_EXTERN(print_from_asm) +# define FUNCNAME tailcall_test2 + DECLARE_EXPORTED_FUNC(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) +# ifdef X86 + push REG_XBP /* Needed only to maintain 16-byte alignment. */ +# elif defined(AARCH64) + stp x29, x30, [sp, #-16]! +# elif defined(ARM) + push {lr} + sub sp, #12 /* Maintain 16-byte alignment. */ +# endif + mov REG_SCRATCH0, HEX(1) + CALLC1(GLOBAL_REF(print_from_asm), REG_SCRATCH0) +# ifdef X86 + pop REG_XBP +# elif defined(AARCH64) + ldp x29, x30, [sp], #16 +# elif defined(ARM) + add sp, #12 + pop {lr} +# endif + JUMP tailcall_tail + END_FUNC(FUNCNAME) +# undef FUNCNAME + + +# define FUNCNAME tailcall_tail + DECLARE_EXPORTED_FUNC(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) +# ifdef X86 + push REG_XBP /* Needed only to maintain 16-byte alignment. */ +# elif defined(AARCH64) + stp x29, x30, [sp, #-16]! +# elif defined(ARM) + push {lr} + sub sp, #12 /* Maintain 16-byte alignment. */ +# endif + mov REG_SCRATCH0, HEX(7) + CALLC1(GLOBAL_REF(print_from_asm), REG_SCRATCH0) +# ifdef X86 + pop REG_XBP + ret +# elif defined(AARCH64) + ldp x29, x30, [sp], #16 + ret +# elif defined(ARM) + add sp, #12 + pop {pc} +# endif + END_FUNC(FUNCNAME) END_FILE /* clang-format on */ diff --git a/suite/tests/client-interface/drwrap-test.dll.c b/suite/tests/client-interface/drwrap-test.dll.c index 1d6d0dbdc36..d8b0660b1bb 100644 --- a/suite/tests/client-interface/drwrap-test.dll.c +++ b/suite/tests/client-interface/drwrap-test.dll.c @@ -102,6 +102,11 @@ static app_pc addr_long2; static app_pc addr_long3; static app_pc addr_longdone; +static app_pc addr_called_indirectly; +static app_pc addr_called_indirectly_subcall; +static app_pc addr_tailcall_test2; +static app_pc addr_tailcall_tail; + static void wrap_addr(INOUT app_pc *addr, const char *name, const module_data_t *mod, void (*pre_cb)(void *, void **), void (*post_cb)(void *, void *), uint flags) @@ -260,6 +265,15 @@ module_load_event(void *drcontext, const module_data_t *mod, bool loaded) DRWRAP_NO_DYNAMIC_RETADDRS); wrap_addr(&addr_direct2, "direct_call2", mod, wrap_pre, wrap_post, DRWRAP_NO_DYNAMIC_RETADDRS); + + wrap_addr(&addr_called_indirectly, "called_indirectly", mod, wrap_pre, wrap_post, + DRWRAP_REPLACE_RETADDR); + wrap_addr(&addr_called_indirectly_subcall, "called_indirectly_subcall", mod, + wrap_pre, wrap_post, DRWRAP_REPLACE_RETADDR); + wrap_addr(&addr_tailcall_test2, "tailcall_test2", mod, wrap_pre, wrap_post, + DRWRAP_REPLACE_RETADDR); + wrap_addr(&addr_tailcall_tail, "tailcall_tail", mod, wrap_pre, wrap_post, + DRWRAP_REPLACE_RETADDR); } } @@ -312,6 +326,13 @@ module_unload_event(void *drcontext, const module_data_t *mod) #endif unwrap_addr(addr_direct1, "direct_call1", mod, wrap_pre, wrap_post_might_miss); unwrap_addr(addr_direct2, "direct_call2", mod, wrap_pre, wrap_post); + + unwrap_addr(addr_called_indirectly, "called_indirectly", mod, wrap_pre, + wrap_post); + unwrap_addr(addr_called_indirectly_subcall, "called_indirectly_subcall", mod, + wrap_pre, wrap_post); + unwrap_addr(addr_tailcall_test2, "tailcall_test2", mod, wrap_pre, wrap_post); + unwrap_addr(addr_tailcall_tail, "tailcall_tail", mod, wrap_pre, wrap_post); } } @@ -441,6 +462,16 @@ wrap_pre(void *wrapcxt, OUT void **user_data) dr_fprintf(STDERR, " \n"); CHECK(drwrap_get_arg(wrapcxt, 0) == (void *)17, "get_arg wrong"); CHECK(drwrap_get_arg(wrapcxt, 1) == (void *)42, "get_arg wrong"); + } else if (drwrap_get_func(wrapcxt) == addr_called_indirectly) { + dr_fprintf(STDERR, " \n"); + CHECK(drwrap_get_arg(wrapcxt, 0) == (void *)42, "get_arg wrong"); + } else if (drwrap_get_func(wrapcxt) == addr_called_indirectly_subcall) { + dr_fprintf(STDERR, " \n"); + CHECK(drwrap_get_arg(wrapcxt, 0) == (void *)43, "get_arg wrong"); + } else if (drwrap_get_func(wrapcxt) == addr_tailcall_test2) { + dr_fprintf(STDERR, " \n"); + } else if (drwrap_get_func(wrapcxt) == addr_tailcall_tail) { + dr_fprintf(STDERR, " \n"); } else CHECK(false, "invalid wrap"); } @@ -495,6 +526,16 @@ wrap_post(void *wrapcxt, void *user_data) dr_fprintf(STDERR, " \n"); } else if (drwrap_get_func(wrapcxt) == addr_direct2) { dr_fprintf(STDERR, " \n"); + } else if (drwrap_get_func(wrapcxt) == addr_called_indirectly) { + dr_fprintf(STDERR, " \n"); + CHECK(drwrap_get_retval(wrapcxt) == (void *)44, "get_retval wrong"); + } else if (drwrap_get_func(wrapcxt) == addr_called_indirectly_subcall) { + dr_fprintf(STDERR, " \n"); + CHECK(drwrap_get_retval(wrapcxt) == (void *)44, "get_arg wrong"); + } else if (drwrap_get_func(wrapcxt) == addr_tailcall_test2) { + dr_fprintf(STDERR, " \n"); + } else if (drwrap_get_func(wrapcxt) == addr_tailcall_tail) { + dr_fprintf(STDERR, " \n"); } else CHECK(false, "invalid wrap"); } diff --git a/suite/tests/client-interface/drwrap-test.template b/suite/tests/client-interface/drwrap-test.template index d05d48b046d..d2b6dff689e 100644 --- a/suite/tests/client-interface/drwrap-test.template +++ b/suite/tests/client-interface/drwrap-test.template @@ -74,6 +74,18 @@ long3 A #endif longdone + + +called_indirectly_subcall 43 + +called_indirectly 42 => 44 + + +print_from_asm 1 + +print_from_asm 7 + + loaded library thread.appdll process init @@ -128,6 +140,18 @@ long3 A longdone + + +called_indirectly_subcall 43 + +called_indirectly 42 => 44 + + +print_from_asm 1 + +print_from_asm 7 + + loaded library thank you for testing the client interface all done diff --git a/suite/tests/thread.h b/suite/tests/thread.h index 514144db348..69660505d46 100644 --- a/suite/tests/thread.h +++ b/suite/tests/thread.h @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2017 Google, Inc. All rights reserved. + * Copyright (c) 2017-2020 Google, Inc. All rights reserved. * Copyright (c) 2004-2007 VMware, Inc. All rights reserved. * **********************************************************/ @@ -95,14 +95,12 @@ typedef HANDLE thread_t; * takes one argument ("arg"), for the thread to execute. * Returns a handle to the new thread. */ -# ifndef STATIC_LIBRARY /* FIXME i#975: conflicts with DR's symbols. */ thread_t create_thread(unsigned int(__stdcall *run_func)(void *), void *arg) { - int tid; + unsigned int tid; return (thread_t)_beginthreadex(NULL, 0, run_func, arg, 0, &tid); } -# endif void delete_thread(thread_t thread, void *stack) From d70305814dc3e03bbe392a0fa7f65d44efaf17a4 Mon Sep 17 00:00:00 2001 From: Derek Bruening Date: Thu, 26 Mar 2020 11:10:58 -0400 Subject: [PATCH 2/2] Add missing 32-bit define --- ext/drwrap/drwrap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/drwrap/drwrap.c b/ext/drwrap/drwrap.c index 6f5655550c3..e5edae8317d 100644 --- a/ext/drwrap/drwrap.c +++ b/ext/drwrap/drwrap.c @@ -83,6 +83,7 @@ static uint verbose = 0; # define CALL_POINT_SCRATCH_REG DR_REG_R11 # define RETURN_POINT_SCRATCH_REG DR_REG_RCX #elif defined(X86) +# define CALL_POINT_SCRATCH_REG DR_REG_NULL /* Unused. */ # define RETURN_POINT_SCRATCH_REG DR_REG_ECX #else # define CALL_POINT_SCRATCH_REG DR_REG_NULL