diff --git a/api/docs/bt.dox b/api/docs/bt.dox index e74151b3290..84e24d5f22d 100644 --- a/api/docs/bt.dox +++ b/api/docs/bt.dox @@ -1145,7 +1145,15 @@ Furthermore, if the client's modifications change any part of the machine state besides the program counter, the client should use dr_register_restore_state_event() or dr_register_restore_state_ex_event() (see \ref sec_events_translation) to restore the registers to their -original application values. +original application values. DR attempts to reconstruct the #instrlist_t +for the faulting fragment; this list contains all instrs added by the +basic block event(s) with \p translating set to true, and also DR's own +mangling of some instrs. If this reconstructed #instrlist_t is available, +it will be passed on to the registered callback as part of +#dr_fault_fragment_info_t in #dr_restore_state_info_t. It may not be +available when some or all clients returned #DR_EMIT_STORE_TRANSLATIONS, +or for DR internal reasons when the app code may not be consistent: for +pending deletion or self-modifying fragments. For meta instructions that do not reference application memory (i.e., they should not fault), leave the translation field as NULL. A NULL value diff --git a/api/docs/release.dox b/api/docs/release.dox index 8b6b54a9d53..7a03c8dfb36 100644 --- a/api/docs/release.dox +++ b/api/docs/release.dox @@ -218,6 +218,9 @@ Further non-compatibility-affecting changes include: enable logic that avoids conflicts in spill slots when drreg is used to reserve registers in multiple phases. - Added drmgr_in_emulation_region() for more conveniently handling emulation. + - Added the reconstructed #instrlist_t when available for the faulting fragment + to #dr_fault_fragment_info_t. This makes it available to the restore state + event callback(s) via the #dr_restore_state_info_t arg. **************************************************
diff --git a/core/arch/asm_defines.asm b/core/arch/asm_defines.asm index 9fe72de1021..fdeff9c8974 100644 --- a/core/arch/asm_defines.asm +++ b/core/arch/asm_defines.asm @@ -96,6 +96,7 @@ /****************************************************/ #if defined(ASSEMBLE_WITH_GAS) +# define START_DATA .data # define START_FILE .text # define END_FILE /* nothing */ # define DECLARE_FUNC(symbol) \ @@ -154,6 +155,7 @@ # define DECL_EXTERN(symbol) /* nothing */ /* include newline so we can put multiple on one line */ # define RAW(n) .byte HEX(n) @N@ +# define BYTES_ARR(symbol, n) symbol: .skip n, 0 # define DECLARE_FUNC_SEH(symbol) DECLARE_FUNC(symbol) # define PUSH_SEH(reg) push reg # define PUSH_NONCALLEE_SEH(reg) push reg @@ -171,6 +173,7 @@ # define VAR_VIA_GOT(base, sym) [sym @GOTOFF + base] /****************************************************/ #elif defined(ASSEMBLE_WITH_MASM) +#define START_DATA .DATA # ifdef X64 # define START_FILE \ /* We add blank lines to match the 32-bit line count */ \ @@ -214,6 +217,7 @@ ASSUME fs:_DATA @N@\ # define DECL_EXTERN(symbol) EXTERN symbol:PROC /* include newline so we can put multiple on one line */ # define RAW(n) DB HEX(n) @N@ +# define BYTES_ARR(symbol, n) symbol byte n dup (0) # define ADD_STACK_ALIGNMENT_NOSEH sub REG_XSP, FRAME_ALIGNMENT - ARG_SZ # define RESTORE_STACK_ALIGNMENT add REG_XSP, FRAME_ALIGNMENT - ARG_SZ # ifdef X64 @@ -237,6 +241,7 @@ ASSUME fs:_DATA @N@\ # endif /****************************************************/ #elif defined(ASSEMBLE_WITH_NASM) +# define START_DATA SECTION .data # define START_FILE SECTION .text # define END_FILE /* nothing */ /* for MacOS, at least, we have to add _ ourselves */ @@ -265,6 +270,7 @@ ASSUME fs:_DATA @N@\ # define SEGMEM(seg,mem) [seg:mem] # define DECL_EXTERN(symbol) EXTERN GLOBAL_REF(symbol) # define RAW(n) DB HEX(n) @N@ +# define BYTES_ARR(symbol, n) symbol times n DB 0 # define DECLARE_FUNC_SEH(symbol) DECLARE_FUNC(symbol) # define PUSH_SEH(reg) push reg # define PUSH_NONCALLEE_SEH(reg) push reg diff --git a/core/lib/dr_events.h b/core/lib/dr_events.h index 25951359192..f00253ffd20 100644 --- a/core/lib/dr_events.h +++ b/core/lib/dr_events.h @@ -641,6 +641,18 @@ typedef struct _dr_fault_fragment_info_t { * depending on the type of cache consistency being used by DR. */ bool app_code_consistent; + /** + * The recreated ilist for this fragment, which contains instrs added + * by the basic block event(s) with \p translating set to true and also + * DR's own mangling of some instrs. This includes client-added metadata + * in the form of notes and label instrs too. This may be helpful in + * restoring app state on a fault. + * When the recreated ilist is not available, this is set to NULL. This + * may happen when a client returns #DR_EMIT_STORE_TRANSLATIONS, or for + * DR internal reasons when the app code may not be consistent: for pending + * deletion or self-modifying fragments. + */ + instrlist_t *ilist; } dr_fault_fragment_info_t; /** diff --git a/core/translate.c b/core/translate.c index 5b85dd70b6c..2b0e67d043f 100644 --- a/core/translate.c +++ b/core/translate.c @@ -1495,6 +1495,7 @@ recreate_app_state_internal(dcontext_t *tdcontext, priv_mcontext_t *mcontext, client_info.fragment_info.is_trace = TEST(FRAG_IS_TRACE, f->flags); client_info.fragment_info.app_code_consistent = !TESTANY(FRAG_WAS_DELETED | FRAG_SELFMOD_SANDBOXED, f->flags); + client_info.fragment_info.ilist = ilist; /* i#220/PR 480565: client has option of failing the translation */ if (!instrument_restore_state(tdcontext, restore_memory, &client_info)) res = RECREATE_FAILURE; diff --git a/ext/drreg/drreg.c b/ext/drreg/drreg.c index 87a0070ae02..15a489c7681 100644 --- a/ext/drreg/drreg.c +++ b/ext/drreg/drreg.c @@ -78,6 +78,17 @@ #define AFLAGS_SLOT 0 /* always */ +/* For simplicity, we assign aflags an alias that's one past the last gpr. + * This is only for simpler handling of data structures like spill_slots_to_reg. + * Note that this is not the same as the aflags spill reg, which is the actual + * gpr that contains the spilled native aflags. + * Note that we cannot use DR_REG_NULL to be the aflags alias because DR_REG_NULL + * means something else in the context of spill_slots_to_reg. + * XXX: Replace use of DR_REG_NULL as aflags alias in drreg routines like + * drreg_statelessly_restore_app_value. Use AFLAGS_ALIAS_REG instead. + */ +#define AFLAGS_ALIAS_REG (DR_REG_STOP_GPR + 1) + /* We support using GPR registers only: [DR_REG_START_GPR..DR_REG_STOP_GPR] */ #define REG_DEAD ((void *)(ptr_uint_t)0) @@ -1831,9 +1842,57 @@ drreg_is_instr_spill_or_restore(void *drcontext, instr_t *instr, bool *spill OUT return DRREG_SUCCESS; } +static void +forget_existing_spill_slot_use(reg_id_t *spill_slot_to_reg, uint reg) +{ + for (int i = 0; i < MAX_SPILLS; i++) { + if (spill_slot_to_reg[i] == reg) + spill_slot_to_reg[i] = DR_REG_NULL; + } +} + +static uint +find_spill_slot_for_reg(void *drcontext, reg_id_t *spill_slot_to_reg, uint reg) +{ + uint spill_slot = MAX_SPILLS; +#ifdef DEBUG + reg_t val = 0, newval; +#endif + for (int i = 0; i < MAX_SPILLS; i++) { + if (spill_slot_to_reg[i] == reg) { +#ifdef DEBUG + newval = get_spilled_value(drcontext, i); + if (spill_slot != MAX_SPILLS) { + ASSERT(val == newval, "spilled val doesn't match across slots"); + } else { + spill_slot = i; + } + val = newval; +#else + return i; +#endif + } + } + return spill_slot; +} + +/* Restores machine state modified by drreg, without the reconstructed instrlist + * of the faulting fragment. + * This is a best-effort restore state logic which is used when the faulting + * fragment's reconstructed instrlist is not available. This does not handle + * some corner cases well, like multiple restores (i#4939), aflags in reg (i#4933) + * and multi-phase use (i#3823). It may restore the wrong value or not restore + * at all in these cases. + * drreg_event_restore_state_with_ilist uses metadata inlined in the + * reconstructed ilist to restore state in a robust manner. The metadata + * required is essentially just a bit per instruction for whether it's an app + * or tool instr. + * TODO i#4937: Store required metadata for each fragment and use it here for + * a more robust restoration of state. + */ static bool -drreg_event_restore_state(void *drcontext, bool restore_memory, - dr_restore_state_info_t *info) +drreg_event_restore_state_without_ilist(void *drcontext, bool restore_memory, + dr_restore_state_info_t *info) { /* To achieve a clean and simple reserve-and-unreserve interface w/o specifying * up front how many cross-app-instr scratch regs (and then limited to whole-bb @@ -1843,9 +1902,6 @@ drreg_event_restore_state(void *drcontext, bool restore_memory, * spill to a temp slot (from drreg_event_bb_insert_late()) by watching for * a spill of an already-spilled reg to a different slot. * - * XXX i#3801: We now keep track of spill slot usages added by drreg, which is - * used to avoid slot conflicts in multi-phase uses of drreg (i#3823). Perhaps - * that metadata can be used in this restore state code. */ uint spilled_to[DR_NUM_GPR_REGS]; uint spilled_to_aflags = MAX_SPILLS; @@ -1945,6 +2001,235 @@ drreg_event_restore_state(void *drcontext, bool restore_memory, return true; } +/* Restores machine state modified by drreg, using the reconstructed instrlist + * of the faulting fragment. + * This is a more robust version of drreg_event_restore_state_without_ilist + * that uses inlined metadata in instrlist. + */ +static bool +drreg_event_restore_state_with_ilist(void *drcontext, bool restore_memory, + dr_restore_state_info_t *info) +{ + /* This routine needs to be robust enough to handle the many tricky corner + * cases that can arise in drreg use: gpr/aflags re-spills (to same or + * different slot) after app instr write, multiple reg restores due to app + * instr read, aflags spilled to reg instead of slot (for x86 only), lazy + * restores, multi-phase use of drreg leading to nested or overlapping + * spill regions, re-spills of native value by multiple phases, ... + * The strategy we follow here is to trace the app value of aflags and gpr + * as it is moved between spill slots and regs. At times the app value may + * be present in the gpr/aflags and one or more spill slots too, but later + * the gpr or any of the spill slots may be clobbered by a tool write or + * another spill respectively. At the end, we restore all live gprs/aflags + * that do not have their native app value. + */ + reg_id_t spill_slot_to_reg[MAX_SPILLS]; + reg_id_t aflags_spill_reg; + bool gpr_native[DR_NUM_GPR_REGS]; + bool aflags_native; + int last_opcode = 0; + + reg_id_t reg; + instr_t *inst; + byte *pc = info->fragment_info.cache_start_pc; + uint offs; + bool spill; + uint slot; + if (pc == NULL) + return true; /* fault not in cache */ + + /* At beginning of fragment, all gprs and aflags have their native app value. */ + for (reg = DR_REG_START_GPR; reg <= DR_REG_STOP_GPR; reg++) { + gpr_native[GPR_IDX(reg)] = true; + } + aflags_native = true; + /* At beginning of fragment, no spill slot has a native value. */ + for (int i = 0; i < MAX_SPILLS; i++) { + spill_slot_to_reg[i] = DR_REG_NULL; + } + /* TODO PR#4917: Add support for test_asm_faultL similar to what's added for gprs. + * Essentially, we want to handle the condition where native value of aflags is + * spilled to multiple gprs by different phases. This may not happen on x86 + * because it has only one reg where aflags can be spilled to; so for re-spills + * of native aflags, the first spill in xax will need to be spilled to some slot + * first. + */ + aflags_spill_reg = DR_REG_NULL; + + LOG(drcontext, DR_LOG_ALL, 3, + "%s: processing fault @" PFX ": using reconstructed fragment ilist \n", + __FUNCTION__, info->raw_mcontext->pc); + ASSERT(info->fragment_info.ilist != NULL, "ilist required for state restoration"); + for (inst = instrlist_first(info->fragment_info.ilist); + inst != NULL && pc < info->raw_mcontext->pc; inst = instr_get_next(inst)) { + int len = instr_length(drcontext, inst); + pc += len; +#ifdef X86 + if (!instr_is_app(inst) && instr_get_opcode(inst) == OP_seto && + last_opcode == OP_lahf) { + /* Sometimes aflags spilling may require a seto after a lahf. We do not + * need to process the seto as the required book-keeping updates have + * been done already in the last iteration. + */ + last_opcode = OP_seto; + continue; + } +#endif + if (!instr_is_label(inst)) { + last_opcode = instr_get_opcode(inst); + } + + bool app_gpr_restored_now = false; + bool app_aflags_restored_now = false; + bool app_aflags_written_to_reg_now = false; + /* Update book-keeping for gpr/aflags spill slot or aflags spill reg. */ + if (!instr_is_app(inst) && + is_our_spill_or_restore(drcontext, inst, &spill, ®, &slot, &offs)) { + if (spill) { + /* Slot used in current instr may have a previous spilled gpr/aflags + * value that may already have been restored. Forget that. + */ + spill_slot_to_reg[slot] = DR_REG_NULL; + if (aflags_spill_reg == reg) { + ASSERT(!gpr_native[GPR_IDX(aflags_spill_reg)], + "reg with aflags cannot be native"); + spill_slot_to_reg[slot] = AFLAGS_ALIAS_REG; + } else if (gpr_native[GPR_IDX(reg)]) { + spill_slot_to_reg[slot] = reg; + } else { + LOG(drcontext, DR_LOG_ALL, 3, + "%s @" PFX ": ignoring tool spill of non-native value.\n", + __FUNCTION__, pc); + } + } else { + if (spill_slot_to_reg[slot] == AFLAGS_ALIAS_REG) { + aflags_spill_reg = reg; + app_aflags_written_to_reg_now = true; + } else if (spill_slot_to_reg[slot] == reg) { + gpr_native[GPR_IDX(reg)] = true; + app_gpr_restored_now = true; + } else { + LOG(drcontext, DR_LOG_ALL, 3, + "%s @" PFX ": ignoring tool restore of non-native value\n", + __FUNCTION__, pc); + } + } + } else if (!instr_is_app(inst) && + instr_get_opcode(inst) == IF_X86_ELSE(OP_lahf, OP_mrs)) { + if (aflags_native) { +#ifdef X86 + aflags_spill_reg = DR_REG_XAX; +#else + aflags_spill_reg = opnd_get_reg(instr_get_dst(inst, 0)); +#endif + app_aflags_written_to_reg_now = true; + } + } else if (!instr_is_app(inst) && + instr_get_opcode(inst) == IF_X86_ELSE(OP_sahf, OP_msr)) { + if (aflags_spill_reg == + IF_X86_ELSE(DR_REG_XAX, opnd_get_reg(instr_get_src(inst, 0)))) { + aflags_native = true; + app_aflags_restored_now = true; + } + } + + /* Mark gprs/aflags as containing native or non-native value based on whether + * the current instr is an app or tool write, respectively. An example of the + * latter is a restore for a spilled tool value, which may happen in the + * multi-phase nested reservation case. + * If the current instr is a tool write that restores an app value (as tracked + * by the various *_now bools), we skip marking the gpr/aflags as non-native. + */ + for (int i = 0; i < instr_num_dsts(inst); i++) { + opnd_t opnd = instr_get_dst(inst, i); + if (opnd_is_reg(opnd) && reg_is_gpr(opnd_get_reg(opnd))) { + reg_id_t resized_reg = reg_to_pointer_sized(opnd_get_reg(opnd)); + if (resized_reg == aflags_spill_reg && !app_aflags_written_to_reg_now) { + /* reg does not contain aflags anymore. */ + aflags_spill_reg = DR_REG_NULL; + } + if (instr_is_app(inst)) { + /* App instr wrote reg value. Spill slot value not valid anymore. */ + gpr_native[GPR_IDX(resized_reg)] = true; + forget_existing_spill_slot_use(spill_slot_to_reg, resized_reg); + } else if (!app_gpr_restored_now) { + /* Tool wrote to reg. Not native anymore. */ + gpr_native[GPR_IDX(resized_reg)] = false; + } + } + } + if (TESTANY(EFLAGS_WRITE_ARITH, instr_get_eflags(inst, DR_QUERY_INCLUDE_ALL))) { + if (instr_is_app(inst)) { + /* App instr wrote aflags. Saved aflags value (in slot or reg) not valid + * anymore. + */ + aflags_native = true; + forget_existing_spill_slot_use(spill_slot_to_reg, AFLAGS_ALIAS_REG); + aflags_spill_reg = DR_REG_NULL; + } else if (!app_aflags_restored_now) { + /* Tool wrote aflags. Not native anymore. */ + aflags_native = false; + } + } + } + ASSERT(inst != NULL, "fault pc is beyond the given ilist"); + if (!aflags_native) { + slot = find_spill_slot_for_reg(drcontext, spill_slot_to_reg, AFLAGS_ALIAS_REG); + if (aflags_spill_reg != DR_REG_NULL || slot != MAX_SPILLS) { + reg_t newval = info->mcontext->xflags; + reg_t val; + if (aflags_spill_reg != DR_REG_NULL) { +#ifdef X86 + ASSERT(aflags_spill_reg == DR_REG_XAX, "x86 aflags can only be in xax"); + val = info->mcontext->xax; +#else + val = *(reg_t *)(&info->mcontext->r0 + (aflags_spill_reg - DR_REG_R0)); +#endif + newval = dr_merge_arith_flags(newval, val); + LOG(drcontext, DR_LOG_ALL, 3, + "%s: restoring aflags from reg %s " PFX " to " PFX "\n", __FUNCTION__, + get_register_name(aflags_spill_reg), info->mcontext->xflags, newval); + } else { + val = get_spilled_value(drcontext, slot); + newval = dr_merge_arith_flags(newval, val); + LOG(drcontext, DR_LOG_ALL, 3, + "%s: restoring aflags from slot %d from " PFX " to " PFX "\n", + __FUNCTION__, slot, info->mcontext->xflags, newval); + } + info->mcontext->xflags = newval; + } else { + LOG(drcontext, DR_LOG_ALL, 3, "%s: aflags not saved as they are dead\n", + __FUNCTION__); + } + } + for (reg = DR_REG_START_GPR; reg <= DR_REG_STOP_GPR; reg++) { + if (!gpr_native[GPR_IDX(reg)]) { + slot = find_spill_slot_for_reg(drcontext, spill_slot_to_reg, reg); + if (slot != MAX_SPILLS) { + reg_t val = get_spilled_value(drcontext, slot); + LOG(drcontext, DR_LOG_ALL, 3, + "%s: restoring %s from slot %d from " PFX " to " PFX "\n", + __FUNCTION__, get_register_name(reg), slot, + reg_get_value(reg, info->mcontext), val); + reg_set_value(reg, info->mcontext, val); + } else { + LOG(drcontext, DR_LOG_ALL, 3, "%s: %s not saved as it is dead\n", + __FUNCTION__, get_register_name(reg)); + } + } + } + return true; +} + +static bool +drreg_event_restore_state(void *drcontext, bool restore_memory, + dr_restore_state_info_t *info) +{ + if (info->fragment_info.ilist != NULL) + return drreg_event_restore_state_with_ilist(drcontext, restore_memory, info); + return drreg_event_restore_state_without_ilist(drcontext, restore_memory, info); +} + /*************************************************************************** * INIT AND EXIT */ diff --git a/suite/tests/client-interface/drreg-test-shared.h b/suite/tests/client-interface/drreg-test-shared.h index 498ee9fda5d..d909991ff74 100644 --- a/suite/tests/client-interface/drreg-test-shared.h +++ b/suite/tests/client-interface/drreg-test-shared.h @@ -52,9 +52,12 @@ #ifdef AARCH64 # define TEST_REG DR_REG_X4 # define TEST_REG2 DR_REG_X5 +# define TEST_REG_STOLEN DR_REG_X28 # define TEST_REG_ASM x4 # define TEST_REG2_ASM x5 +# define TEST_REG_STOLEN_ASM x28 # define TEST_REG_SIG regs[4] +# define TEST_FAUX_SPILL_TLS_OFFS 0x150 #endif #define TEST_FLAGS_SIG SC_XFLAGS @@ -130,3 +133,21 @@ #define DRREG_TEST_14_ASM MAKE_HEX_ASM(DRREG_TEST_CONST(14)) #define DRREG_TEST_14_C MAKE_HEX_C(DRREG_TEST_CONST(14)) + +#define DRREG_TEST_15_ASM MAKE_HEX_ASM(DRREG_TEST_CONST(15)) +#define DRREG_TEST_15_C MAKE_HEX_C(DRREG_TEST_CONST(15)) + +#define DRREG_TEST_16_ASM MAKE_HEX_ASM(DRREG_TEST_CONST(16)) +#define DRREG_TEST_16_C MAKE_HEX_C(DRREG_TEST_CONST(16)) + +#define DRREG_TEST_17_ASM MAKE_HEX_ASM(DRREG_TEST_CONST(17)) +#define DRREG_TEST_17_C MAKE_HEX_C(DRREG_TEST_CONST(17)) + +#define DRREG_TEST_18_ASM MAKE_HEX_ASM(DRREG_TEST_CONST(18)) +#define DRREG_TEST_18_C MAKE_HEX_C(DRREG_TEST_CONST(18)) + +#define DRREG_TEST_19_ASM MAKE_HEX_ASM(DRREG_TEST_CONST(19)) +#define DRREG_TEST_19_C MAKE_HEX_C(DRREG_TEST_CONST(19)) + +#define DRREG_TEST_20_ASM MAKE_HEX_ASM(DRREG_TEST_CONST(20)) +#define DRREG_TEST_20_C MAKE_HEX_C(DRREG_TEST_CONST(20)) diff --git a/suite/tests/client-interface/drreg-test.c b/suite/tests/client-interface/drreg-test.c index 2aeb7c0ecfb..61b85f222c1 100644 --- a/suite/tests/client-interface/drreg-test.c +++ b/suite/tests/client-interface/drreg-test.c @@ -55,6 +55,18 @@ void test_asm_faultE(); void test_asm_faultF(); +void +test_asm_faultG(); +void +test_asm_faultH(); +void +test_asm_faultI(); +void +test_asm_faultJ(); +void +test_asm_faultK(); +void +test_asm_faultL(); static SIGJMP_BUF mark; @@ -76,7 +88,7 @@ handle_signal1(int signal, siginfo_t *siginfo, ucontext_t *ucxt) print("ERROR: spilled register value was not preserved!\n"); } else if (signal == SIGSEGV) { sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); - if (((sc->TEST_FLAGS_SIG) & DRREG_TEST_AFLAGS_C) != DRREG_TEST_AFLAGS_C) + if (!TESTALL(DRREG_TEST_AFLAGS_C, sc->TEST_FLAGS_SIG)) print("ERROR: spilled flags value was not preserved!\n"); } SIGLONGJMP(mark, 1); @@ -129,10 +141,56 @@ handle_signal5(int signal, siginfo_t *siginfo, ucontext_t *ucxt) if (signal == SIGILL) { sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); if (sc->TEST_REG_SIG != DRREG_TEST_14_C) - print("ERROR5: spilled register value was not preserved!\n"); + print("ERROR: spilled register value was not preserved in test #14!\n"); + } else if (signal == SIGSEGV) { + sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); + if (sc->TEST_REG_SIG != DRREG_TEST_17_C) + print("ERROR: spilled register value was not preserved in test #17!\n"); + } + SIGLONGJMP(mark, 1); +} + +static void +handle_signal6(int signal, siginfo_t *siginfo, ucontext_t *ucxt) +{ + if (signal == SIGILL) { + sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); + if (!TESTALL(DRREG_TEST_AFLAGS_C, sc->TEST_FLAGS_SIG)) + print("ERROR: spilled flags value was not preserved in test #15!\n"); + } else if (signal == SIGSEGV) { + sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); + if (sc->TEST_REG_SIG != DRREG_TEST_16_C) + print("ERROR: spilled register value was not preserved in test #16!\n"); + } + SIGLONGJMP(mark, 1); +} + +static void +handle_signal7(int signal, siginfo_t *siginfo, ucontext_t *ucxt) +{ + if (signal == SIGILL) { + sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); + if (sc->TEST_REG_SIG != DRREG_TEST_18_C) + print("ERROR: spilled register value was not preserved in test #18!\n"); + } else if (signal == SIGSEGV) { + sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); + if (sc->TEST_REG_SIG != DRREG_TEST_19_C) + print("ERROR: spilled register value was not preserved in test #19!\n"); } SIGLONGJMP(mark, 1); } + +static void +handle_signal8(int signal, siginfo_t *siginfo, ucontext_t *ucxt) +{ + if (signal == SIGILL) { + sigcontext_t *sc = SIGCXT_FROM_UCXT(ucxt); + if (sc->TEST_REG_SIG != DRREG_TEST_20_C) + print("ERROR: spilled register value was not preserved in test #20!\n"); + } + SIGLONGJMP(mark, 1); +} + # elif defined(WINDOWS) # include static LONG WINAPI @@ -149,7 +207,7 @@ handle_exception1(struct _EXCEPTION_POINTERS *ep) if (ep->ContextRecord->TEST_REG_CXT != DRREG_TEST_3_C) print("ERROR: spilled register value was not preserved!\n"); } else if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { - if ((ep->ContextRecord->CXT_XFLAGS & DRREG_TEST_AFLAGS_C) != DRREG_TEST_AFLAGS_C) + if (!TESTALL(DRREG_TEST_AFLAGS_C, ep->ContextRecord->CXT_XFLAGS)) print("ERROR: spilled flags value was not preserved!\n"); } SIGLONGJMP(mark, 1); @@ -198,6 +256,43 @@ handle_exception5(struct _EXCEPTION_POINTERS *ep) } SIGLONGJMP(mark, 1); } + +static LONG WINAPI +handle_exception6(struct _EXCEPTION_POINTERS *ep) +{ + if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) { + if (!TESTALL(DRREG_TEST_AFLAGS_C, ep->ContextRecord->CXT_XFLAGS)) + print("ERROR: spilled flags value was not preserved in test #15!\n"); + } + else if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { + if (ep->ContextRecord->TEST_REG_CXT != DRREG_TEST_16_C) + print("ERROR: spilled register value was not preserved in test #16!\n"); + } + SIGLONGJMP(mark, 1); +} + +static LONG WINAPI +handle_exception7(struct _EXCEPTION_POINTERS *ep) +{ + if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) { + if (ep->ContextRecord->TEST_REG_CXT != DRREG_TEST_18_C) + print("ERROR: spilled register value was not preserved in test #18!\n"); + } else if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { + if (ep->ContextRecord->TEST_REG_CXT != DRREG_TEST_19_C) + print("ERROR: spilled register value was not preserved in test #19!\n"); + } + SIGLONGJMP(mark, 1); +} + +static LONG WINAPI +handle_exception8(struct _EXCEPTION_POINTERS *ep) +{ + if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) { + if (ep->ContextRecord->TEST_REG_CXT != DRREG_TEST_20_C) + print("ERROR: spilled register value was not preserved in test #20!\n"); + } + SIGLONGJMP(mark, 1); +} # endif int @@ -274,14 +369,67 @@ main(int argc, const char *argv[]) # if defined(UNIX) intercept_signal(SIGILL, (handler_3_t)&handle_signal5, false); + intercept_signal(SIGSEGV, (handler_3_t)&handle_signal5, false); # elif defined(WINDOWS) SetUnhandledExceptionFilter(&handle_exception5); # endif - /* Test fault reg restore (multi-phase) */ + /* Test fault reg restore for multi-phase nested reservation. */ if (SIGSETJMP(mark) == 0) { test_asm_faultF(); } + /* Test fault reg restore for multi-phase non-nested overlapping reservations. */ + if (SIGSETJMP(mark) == 0) { + test_asm_faultI(); + } + + # if defined(UNIX) + intercept_signal(SIGILL, (handler_3_t)&handle_signal6, false); + intercept_signal(SIGSEGV, (handler_3_t)&handle_signal6, false); +# elif defined(WINDOWS) + SetUnhandledExceptionFilter(&handle_exception6); +# endif + + /* Test fault aflags restore from xax. */ + if (SIGSETJMP(mark) == 0) { + test_asm_faultG(); + } + + /* Test fault reg restore bug */ + if (SIGSETJMP(mark) == 0) { + test_asm_faultH(); + } + + # if defined(UNIX) + intercept_signal(SIGILL, (handler_3_t)&handle_signal7, false); + intercept_signal(SIGSEGV, (handler_3_t)&handle_signal7, false); +# elif defined(WINDOWS) + SetUnhandledExceptionFilter(&handle_exception7); +# endif + + /* Test fault reg restore for fragments with DR_EMIT_STORE_TRANSLATIONS */ + if (SIGSETJMP(mark) == 0) { + test_asm_faultJ(); + } + + /* Test fault reg restore for fragments with a faux spill instr. */ + if (SIGSETJMP(mark) == 0) { + test_asm_faultK(); + } + + # if defined(UNIX) + intercept_signal(SIGILL, (handler_3_t)&handle_signal8, false); +# elif defined(WINDOWS) + SetUnhandledExceptionFilter(&handle_exception8); +# endif + + /* Test fault reg restore for multi-phase nested reservation where + * the first phase doesn't write the reg before the second + * reservation. + */ + if (SIGSETJMP(mark) == 0) { + test_asm_faultL(); + } /* XXX i#511: add more fault tests and other tricky corner cases */ @@ -716,7 +864,11 @@ OB jmp test10 END_FUNC(FUNCNAME) #undef FUNCNAME - /* Test 14: restore on fault for gpr reserved in multiple phases */ + /* Test 14: restore on fault for gpr reserved in multiple phases, + * where the two spill regions are nested. In this case, the reg + * will be restored from the spill slot used by the first (app2app) + * phase. + */ #define FUNCNAME test_asm_faultF DECLARE_FUNC_SEH(FUNCNAME) GLOBAL_LABEL(FUNCNAME:) @@ -765,6 +917,349 @@ GLOBAL_LABEL(FUNCNAME:) END_FUNC(FUNCNAME) #undef FUNCNAME + /* Test 15: restore on fault for aflags stored in xax without preceding + * xax spill. + */ +#define FUNCNAME test_asm_faultG + DECLARE_FUNC_SEH(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) +#ifdef X86 + PUSH_CALLEE_SAVED_REGS() + sub REG_XSP, FRAME_PADDING /* align */ + END_PROLOG + + jmp test15 + test15: + mov TEST_REG_ASM, DRREG_TEST_15_ASM + mov TEST_REG_ASM, DRREG_TEST_15_ASM + mov ah, DRREG_TEST_AFLAGS_ASM + sahf + nop + ud2 + /* xax is dead, so should not need to spill when reserving aflags. */ + mov REG_XAX, 0 + jmp epilog15 + epilog15: + add REG_XSP, FRAME_PADDING /* make a legal SEH64 epilog */ + POP_CALLEE_SAVED_REGS() + ret + /* This test does not have AArchXX variants. */ +#elif defined(ARM) + bx lr +#elif defined(AARCH64) + ret +#endif + END_FUNC(FUNCNAME) +#undef FUNCNAME + + /* Test 16: restore on fault for reg restored once (for app read) + * before crash. + */ +#define FUNCNAME test_asm_faultH + DECLARE_FUNC_SEH(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) +#ifdef X86 + PUSH_CALLEE_SAVED_REGS() + sub REG_XSP, FRAME_PADDING /* align */ + END_PROLOG + + jmp test16 + test16: + mov TEST_REG_ASM, DRREG_TEST_16_ASM + mov TEST_REG_ASM, DRREG_TEST_16_ASM + nop + /* Read reg so that it is restored once. */ + add TEST_REG2_ASM, TEST_REG_ASM + mov REG_XCX, 0 + mov REG_XCX, PTRSZ [REG_XCX] /* crash */ + jmp epilog16 + epilog16: + add REG_XSP, FRAME_PADDING /* make a legal SEH64 epilog */ + POP_CALLEE_SAVED_REGS() + ret +#elif defined(ARM) + /* XXX i#3289: prologue missing */ + b test16 + test16: + movw TEST_REG_ASM, DRREG_TEST_16_ASM + movw TEST_REG_ASM, DRREG_TEST_16_ASM + nop + /* Read reg so that it is restored once. */ + add TEST_REG2_ASM, TEST_REG_ASM, TEST_REG_ASM + mov r0, HEX(0) + ldr r0, PTRSZ [r0] /* crash */ + + b epilog16 + epilog16: + bx lr +#elif defined(AARCH64) + /* XXX i#3289: prologue missing */ + b test16 + test16: + movz TEST_REG_ASM, DRREG_TEST_16_ASM + movz TEST_REG_ASM, DRREG_TEST_16_ASM + nop + /* Read reg so that it is restored once. */ + add TEST_REG2_ASM, TEST_REG_ASM, TEST_REG_ASM + mov x0, HEX(0) + ldr x0, PTRSZ [x0] /* crash */ + + b epilog16 + epilog16: + ret +#endif + END_FUNC(FUNCNAME) +#undef FUNCNAME + + /* Test 17: restore on fault for gpr reserved in multiple phases + * with overlapping but not nested spill regions. In this case, + * the app value changes slots, from the one used in app2app + * phase, to the one used in insertion phase. + */ +#define FUNCNAME test_asm_faultI + DECLARE_FUNC_SEH(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) +#ifdef X86 + PUSH_CALLEE_SAVED_REGS() + sub REG_XSP, FRAME_PADDING /* align */ + END_PROLOG + + jmp test17 + test17: + mov TEST_REG_ASM, DRREG_TEST_17_ASM + mov TEST_REG_ASM, DRREG_TEST_17_ASM + /* app2app phase will reserve TEST_REG_ASM here. */ + mov TEST_REG2_ASM, 1 + /* insertion phase will reserve TEST_REG_ASM here. */ + mov TEST_REG2_ASM, 2 + /* app2app phase will release TEST_REG_ASM here. */ + mov TEST_REG2_ASM, 3 + mov REG_XCX, 0 + mov REG_XCX, PTRSZ [REG_XCX] /* crash */ + /* insertion phase will release TEST_REG_ASM here. */ + jmp epilog17 + epilog17: + add REG_XSP, FRAME_PADDING /* make a legal SEH64 epilog */ + POP_CALLEE_SAVED_REGS() + ret +#elif defined(ARM) + /* XXX i#3289: prologue missing */ + b test17 + test17: + movw TEST_REG_ASM, DRREG_TEST_17_ASM + movw TEST_REG_ASM, DRREG_TEST_17_ASM + movw TEST_REG2_ASM, 1 + movw TEST_REG2_ASM, 2 + movw TEST_REG2_ASM, 3 + mov r0, HEX(0) + ldr r0, PTRSZ [r0] /* crash */ + + b epilog17 + epilog17: + bx lr +#elif defined(AARCH64) + /* XXX i#3289: prologue missing */ + /* TODO PR#4917: This AArch64 variant doesn't work completely as + * intended. This test currently won't fail even if the expected + * TEST_REG_ASM restore doesn't happen. This is because at the + * faulting instr, the TEST_REG_ASM app val is present in the + * spill slot reserved by the insertion phase (spill slot 2; slot + * 0 is reserved for aflags, slot 1 is used by app2app phase). + * Slot 2 is a DR slot and all regs spilled to DR slot are + * automatically restored before each app instr. + * After PR#4917 though, aflags slot won't be hard-coded and + * will be available for gprs (here, TEST_REG_ASM). Therefore, + * TEST_REG_ASM will be stored in a TLS slot instead of a DR slot + * and won't be automatically restored, and this test will really + * verify the restore logic. + * Note that the above isn't true for X86 because drreg internally + * adds one extra spill slot for X86. + */ + b test17 + test17: + movz TEST_REG_ASM, DRREG_TEST_17_ASM + movz TEST_REG_ASM, DRREG_TEST_17_ASM + movz TEST_REG2_ASM, 1 + movz TEST_REG2_ASM, 2 + movz TEST_REG2_ASM, 3 + mov x0, HEX(0) + ldr x0, PTRSZ [x0] /* crash */ + + b epilog17 + epilog17: + ret +#endif + END_FUNC(FUNCNAME) +#undef FUNCNAME + + /* Test 18: fault reg restore for fragments with DR_EMIT_STORE_TRANSLATIONS */ +#define FUNCNAME test_asm_faultJ + DECLARE_FUNC_SEH(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) +#ifdef X86 + PUSH_CALLEE_SAVED_REGS() + sub REG_XSP, FRAME_PADDING /* align */ + END_PROLOG + + jmp test18 + test18: + mov TEST_REG_ASM, DRREG_TEST_18_ASM + mov TEST_REG_ASM, DRREG_TEST_18_ASM + nop + ud2 + + jmp epilog18 + epilog18: + add REG_XSP, FRAME_PADDING /* make a legal SEH64 epilog */ + POP_CALLEE_SAVED_REGS() + ret +#elif defined(ARM) + /* XXX i#3289: prologue missing */ + b test18 + test18: + movw TEST_REG_ASM, DRREG_TEST_18_ASM + movw TEST_REG_ASM, DRREG_TEST_18_ASM + nop + .word 0xe7f000f0 /* udf */ + + b epilog18 + epilog18: + bx lr +#elif defined(AARCH64) + /* XXX i#3289: prologue missing */ + b test18 + test18: + movz TEST_REG_ASM, DRREG_TEST_18_ASM + movz TEST_REG_ASM, DRREG_TEST_18_ASM + nop + .inst 0xf36d19 /* udf */ + + b epilog18 + epilog18: + ret +#endif + END_FUNC(FUNCNAME) +#undef FUNCNAME + + /* Test 19: Test fault reg restore for fragments with a faux spill + * instr -- an app instr that looks like a drreg spill instr, which + * may corrupt drreg state restoration. This cannot happen on x86 as + * an app instr that uses the %gs register will be mangled into a + * non-far memref. + */ +#define FUNCNAME test_asm_faultK + DECLARE_FUNC_SEH(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) +#ifdef X86 + END_PROLOG + ret +#elif defined(ARM) + bx lr +#elif defined(AARCH64) + /* XXX i#3289: prologue missing */ + b test19 + test19: + movz TEST_REG_ASM, DRREG_TEST_19_ASM + movz TEST_REG_ASM, DRREG_TEST_19_ASM + /* TEST_REG_ASM is reserved here. */ + movz TEST_REG2_ASM, 1 + adr TEST_REG_STOLEN_ASM, some_data + /* A faux spill instr -- looks like a drreg spill but isn't. + * It will seem as if the spill slot used for TEST_REG_ASM + * is being overwritten. + */ + str TEST_REG2_ASM, PTRSZ [TEST_REG_STOLEN_ASM, #TEST_FAUX_SPILL_TLS_OFFS] + + mov x0, HEX(0) + ldr x0, PTRSZ [x0] /* crash */ + + b epilog19 + epilog19: + ret +#endif + END_FUNC(FUNCNAME) +#undef FUNCNAME + + /* Test 20: Test restore on fault for gpr reserved in multiple + * phases, where the two spill regions are nested, and the first + * phase doesn't write the reg before the second reservation. This + * is to verify that drreg state restoration logic remembers that + * the app value can be found in both the spill slots. + */ +#define FUNCNAME test_asm_faultL + DECLARE_FUNC_SEH(FUNCNAME) +GLOBAL_LABEL(FUNCNAME:) +#ifdef X86 + PUSH_CALLEE_SAVED_REGS() + sub REG_XSP, FRAME_PADDING /* align */ + END_PROLOG + + jmp test20 + test20: + mov TEST_REG_ASM, DRREG_TEST_20_ASM + mov TEST_REG_ASM, DRREG_TEST_20_ASM + /* - app2app reserves TEST_REG_ASM here, but doesn't write it. + * - insertion reserves TEST_REG_ASM here, which may confuse the + * state restoration logic into overwritting the spill slot for + * TEST_REG_ASM as it still has its native value. + */ + mov TEST_REG2_ASM, 1 + /* - insertion phase unreserves TEST_REG_ASM and frees the spill + * slot. + */ + mov TEST_REG2_ASM, 2 + /* - insertion phase reserves TEST_REG2_ASM which would use the + * same spill slot as freed above, and overwrite TEST_REG_ASM + * value stored there currently. After this TEST_REG_ASM can + * only be found in its app2app spill slot. + * - insertion phase writes to TEST_REG_ASM so that we need to + * restore it. + */ + mov TEST_REG2_ASM, 3 + ud2 + + jmp epilog20 + epilog20: + add REG_XSP, FRAME_PADDING /* make a legal SEH64 epilog */ + POP_CALLEE_SAVED_REGS() + ret +#elif defined(ARM) + /* XXX i#3289: prologue missing */ + b test20 + test20: + movw TEST_REG_ASM, DRREG_TEST_20_ASM + movw TEST_REG_ASM, DRREG_TEST_20_ASM + movw TEST_REG2_ASM, 1 + movw TEST_REG2_ASM, 2 + movw TEST_REG2_ASM, 3 + .word 0xe7f000f0 /* udf */ + + b epilog20 + epilog20: + bx lr +#elif defined(AARCH64) + /* XXX i#3289: prologue missing */ + b test20 + test20: + movz TEST_REG_ASM, DRREG_TEST_20_ASM + movz TEST_REG_ASM, DRREG_TEST_20_ASM + movz TEST_REG2_ASM, 1 + movz TEST_REG2_ASM, 2 + movz TEST_REG2_ASM, 3 + .inst 0xf36d19 /* udf */ + + b epilog20 + epilog20: + ret +#endif + END_FUNC(FUNCNAME) +#undef FUNCNAME +START_DATA + /* Should be atleast (TEST_FAUX_SPILL_TLS_OFFS+1)*8 bytes. + * Cannot use the macro as the expression needs to be + * absolute. + */ + BYTES_ARR(some_data, (1000+1)*8) END_FILE #endif /* clang-format on */ diff --git a/suite/tests/client-interface/drreg-test.dll.c b/suite/tests/client-interface/drreg-test.dll.c index 998e8ae7ddb..390e1030b88 100644 --- a/suite/tests/client-interface/drreg-test.dll.c +++ b/suite/tests/client-interface/drreg-test.dll.c @@ -56,6 +56,7 @@ static ptr_uint_t note_base; enum { DRREG_TEST_LABEL_MARKER, DRREG_TEST_NOTE_COUNT }; uint tls_offs_app2app_spilled_reg; +uint tls_offs_test_reg_1; static bool is_drreg_test_label_marker(instr_t *inst) @@ -67,20 +68,30 @@ is_drreg_test_label_marker(instr_t *inst) static uint spill_test_reg_to_slot(void *drcontext, instrlist_t *bb, instr_t *inst, - drvector_t *allowed) + reg_id_t reg_to_reserve, drvector_t *allowed, bool overwrite) { - uint tls_offs; + uint tls_offs, offs; reg_id_t reg; + opnd_t opnd; + bool is_dr_slot; CHECK(drreg_reserve_register(drcontext, bb, inst, allowed, ®) == DRREG_SUCCESS, "unable to reserve register"); - ASSERT(reg == TEST_REG); - /* Load with some value so that we need to restore it later. */ - instrlist_meta_preinsert(bb, inst, - XINST_CREATE_load_int(drcontext, opnd_create_reg(reg), - OPND_CREATE_INT32(MAGIC_VAL))); - CHECK(drreg_reservation_info(drcontext, reg, NULL, NULL, &tls_offs) == DRREG_SUCCESS, + CHECK(reg == reg_to_reserve, "only 1 option"); + CHECK(drreg_reservation_info(drcontext, reg, &opnd, &is_dr_slot, &offs) == + DRREG_SUCCESS, "unable to get reservation info"); - ASSERT(tls_offs != -1); + CHECK(offs != -1, "gpr should be spilled to some slot."); + if (is_dr_slot) { + tls_offs = opnd_get_disp(opnd); + } else { + tls_offs = offs; + } + if (overwrite) { + /* Load with some value so that we need to restore it later. */ + instrlist_meta_preinsert(bb, inst, + XINST_CREATE_load_int(drcontext, opnd_create_reg(reg), + OPND_CREATE_INT32(MAGIC_VAL))); + } return tls_offs; } @@ -124,7 +135,7 @@ event_app2app(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, inst = instr_get_next_app(inst)) { if (instr_is_nop(inst)) { tls_offs_app2app_spilled_reg = - spill_test_reg_to_slot(drcontext, bb, inst, &allowed); + spill_test_reg_to_slot(drcontext, bb, inst, TEST_REG, &allowed, true); } else if (inst == instrlist_last(bb)) { /* Make sure that TEST_REG isn't dead after its app2app spill. * If it is dead, its next spill will only reserve a slot, but not @@ -140,6 +151,59 @@ event_app2app(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, "cannot unreserve register"); } } + } else if (*((ptr_int_t *)user_data) == DRREG_TEST_17_C) { + CHECK(drreg_set_bb_properties( + drcontext, DRREG_HANDLE_MULTI_PHASE_SLOT_RESERVATIONS) == DRREG_SUCCESS, + "unable to set bb properties"); + /* Reset for this bb. */ + tls_offs_app2app_spilled_reg = -1; + dr_log(drcontext, DR_LOG_ALL, 1, "drreg test #17: app2app phase\n"); + ptr_int_t val; + for (inst = instrlist_first_app(bb); inst != NULL; + inst = instr_get_next_app(inst)) { + if (instr_is_mov_constant(inst, &val) && val == 1) { + tls_offs_app2app_spilled_reg = + spill_test_reg_to_slot(drcontext, bb, inst, TEST_REG, &allowed, true); + } else if (instr_is_mov_constant(inst, &val) && val == 3) { + /* Make sure that TEST_REG isn't dead after its app2app spill. + * If it is dead, its next spill will only reserve a slot, but not + * actually write to it. To test restore in the multi-phase + * overlapping spill case (test #17), we need it to actually write. + */ + instrlist_meta_preinsert(bb, inst, + XINST_CREATE_add(drcontext, + opnd_create_reg(TEST_REG), + OPND_CREATE_INT32(1))); + CHECK(drreg_unreserve_register(drcontext, bb, inst, TEST_REG) == + DRREG_SUCCESS, + "cannot unreserve register"); + } + } + } else if (*((ptr_int_t *)user_data) == DRREG_TEST_20_C) { + CHECK(drreg_set_bb_properties( + drcontext, DRREG_HANDLE_MULTI_PHASE_SLOT_RESERVATIONS) == DRREG_SUCCESS, + "unable to set bb properties"); + dr_log(drcontext, DR_LOG_ALL, 1, "drreg test #20: app2app phase\n"); + ptr_int_t val; + for (inst = instrlist_first_app(bb); inst != NULL; + inst = instr_get_next_app(inst)) { + if (instr_is_mov_constant(inst, &val) && val == 1) { + tls_offs_app2app_spilled_reg = spill_test_reg_to_slot( + drcontext, bb, inst, TEST_REG, &allowed, false); + } else if (inst == instrlist_last(bb)) { + /* Make sure that TEST_REG isn't dead after its app2app spill. + * If it is dead, its next spill will only reserve a slot, but not + * actually write to it. + */ + instrlist_meta_preinsert(bb, inst, + XINST_CREATE_add(drcontext, + opnd_create_reg(TEST_REG), + OPND_CREATE_INT32(1))); + CHECK(drreg_unreserve_register(drcontext, bb, inst, TEST_REG) == + DRREG_SUCCESS, + "cannot unreserve register"); + } + } } drvector_delete(&allowed); return DR_EMIT_DEFAULT; @@ -170,14 +234,17 @@ event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst { reg_id_t reg, random = IF_X86_ELSE(DR_REG_XDI, DR_REG_R5); drreg_status_t res; - drvector_t allowed; + drvector_t allowed_test_reg_1, allowed_test_reg_2; ptr_int_t subtest = (ptr_int_t)user_data; drreg_reserve_info_t info = { sizeof(info), }; - drreg_init_and_fill_vector(&allowed, false); - drreg_set_vector_entry(&allowed, TEST_REG, true); + drreg_init_and_fill_vector(&allowed_test_reg_1, false); + drreg_set_vector_entry(&allowed_test_reg_1, TEST_REG, true); + + drreg_init_and_fill_vector(&allowed_test_reg_2, false); + drreg_set_vector_entry(&allowed_test_reg_2, TEST_REG2, true); if (subtest == 0) { uint flags; @@ -231,9 +298,9 @@ event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst res = drreg_unreserve_register(drcontext, bb, inst, reg); CHECK(res == DRREG_SUCCESS, "default unreserve should always work"); - res = drreg_reserve_register(drcontext, bb, inst, &allowed, ®); + res = drreg_reserve_register(drcontext, bb, inst, &allowed_test_reg_1, ®); CHECK(res == DRREG_SUCCESS && reg == TEST_REG, "only 1 choice"); - res = drreg_reserve_register(drcontext, bb, inst, &allowed, ®); + res = drreg_reserve_register(drcontext, bb, inst, &allowed_test_reg_1, ®); CHECK(res == DRREG_ERROR_REG_CONFLICT, "still reserved"); opnd_t opnd = opnd_create_null(); res = drreg_reservation_info(drcontext, reg, &opnd, NULL, NULL); @@ -313,11 +380,11 @@ event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst drvector_delete(&only_xax); #endif } else if (subtest == DRREG_TEST_1_C || subtest == DRREG_TEST_2_C || - subtest == DRREG_TEST_3_C) { + subtest == DRREG_TEST_3_C || subtest == DRREG_TEST_18_C) { /* Cross-app-instr tests */ dr_log(drcontext, DR_LOG_ALL, 1, "drreg test #1/2/3\n"); if (is_drreg_test_label_marker(inst)) { - res = drreg_reserve_register(drcontext, bb, inst, &allowed, ®); + res = drreg_reserve_register(drcontext, bb, inst, &allowed_test_reg_1, ®); CHECK(res == DRREG_SUCCESS, "reserve of test reg should work"); instrlist_meta_preinsert(bb, inst, XINST_CREATE_load_int(drcontext, @@ -390,7 +457,54 @@ event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst if (instr_is_nop(inst)) { CHECK(tls_offs_app2app_spilled_reg != -1, "unable to use any spill slot in app2app phase."); - uint tls_offs = spill_test_reg_to_slot(drcontext, bb, inst, &allowed); + uint tls_offs = spill_test_reg_to_slot(drcontext, bb, inst, TEST_REG, + &allowed_test_reg_1, true); + CHECK(tls_offs_app2app_spilled_reg != tls_offs, + "found conflict in use of spill slots across multiple phases"); + } else if (drmgr_is_last_instr(drcontext, inst)) { + CHECK(drreg_unreserve_register(drcontext, bb, inst, TEST_REG) == + DRREG_SUCCESS, + "cannot unreserve register"); + } +#ifdef X86 + } else if (subtest == DRREG_TEST_15_C) { + dr_log(drcontext, DR_LOG_ALL, 1, "drreg test #15\n"); + if (instr_is_nop(inst)) { + CHECK(drreg_reserve_aflags(drcontext, bb, inst) == DRREG_SUCCESS, + "cannot reserve aflags"); + /* Load with some value so that we need to restore it later. */ + instrlist_meta_preinsert(bb, inst, + XINST_CREATE_cmp(drcontext, + opnd_create_reg(DR_REG_XAX), + opnd_create_reg(DR_REG_XCX))); + } else if (drmgr_is_last_instr(drcontext, inst)) { + CHECK(drreg_unreserve_aflags(drcontext, bb, inst) == DRREG_SUCCESS, + "cannot unreserve aflags"); + } +#endif + } else if (subtest == DRREG_TEST_16_C) { + dr_log(drcontext, DR_LOG_ALL, 1, "drreg test #16\n"); + if (instr_is_nop(inst)) { + CHECK(drreg_reserve_register(drcontext, bb, inst, &allowed_test_reg_1, + ®) == DRREG_SUCCESS, + "cannot reserve aflags"); + /* Load with some value so that we need to restore it later. */ + instrlist_meta_preinsert(bb, inst, + XINST_CREATE_load_int(drcontext, + opnd_create_reg(TEST_REG), + OPND_CREATE_INT32(MAGIC_VAL))); + } else if (drmgr_is_last_instr(drcontext, inst)) { + res = drreg_unreserve_register(drcontext, bb, inst, TEST_REG); + CHECK(res == DRREG_SUCCESS, "default unreserve should always work"); + } + } else if (subtest == DRREG_TEST_17_C) { + dr_log(drcontext, DR_LOG_ALL, 1, "drreg test #17\n"); + ptr_int_t val; + if (instr_is_mov_constant(inst, &val) && val == 2) { + CHECK(tls_offs_app2app_spilled_reg != -1, + "unable to use any spill slot in app2app phase."); + uint tls_offs = spill_test_reg_to_slot(drcontext, bb, inst, TEST_REG, + &allowed_test_reg_1, true); CHECK(tls_offs_app2app_spilled_reg != tls_offs, "found conflict in use of spill slots across multiple phases"); } else if (drmgr_is_last_instr(drcontext, inst)) { @@ -398,12 +512,59 @@ event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst DRREG_SUCCESS, "cannot unreserve register"); } +#ifdef AARCH64 + } else if (subtest == DRREG_TEST_19_C) { + dr_log(drcontext, DR_LOG_ALL, 1, "drreg test #19\n"); + CHECK(dr_get_stolen_reg() == TEST_REG_STOLEN, "stolen reg doesn't match"); + ptr_int_t val; + if (instr_is_mov_constant(inst, &val) && val == 1) { + tls_offs_test_reg_1 = spill_test_reg_to_slot(drcontext, bb, inst, TEST_REG, + &allowed_test_reg_1, true); + CHECK(tls_offs_test_reg_1 == TEST_FAUX_SPILL_TLS_OFFS, "unexpected tls offs"); + } else if (drmgr_is_last_instr(drcontext, inst)) { + CHECK(drreg_unreserve_register(drcontext, bb, inst, TEST_REG) == + DRREG_SUCCESS, + "unreserve should work"); + } +#endif + } else if (subtest == DRREG_TEST_20_C) { + dr_log(drcontext, DR_LOG_ALL, 1, "drreg test #20\n"); + ptr_int_t val; + if (instr_is_mov_constant(inst, &val) && val == 1) { + tls_offs_test_reg_1 = spill_test_reg_to_slot(drcontext, bb, inst, TEST_REG, + &allowed_test_reg_1, true); + CHECK(tls_offs_app2app_spilled_reg != tls_offs_test_reg_1, + "spill slot conflict across phases"); + } else if (instr_is_mov_constant(inst, &val) && val == 2) { + CHECK(drreg_unreserve_register(drcontext, bb, inst, TEST_REG) == + DRREG_SUCCESS, + "cannot unreserve register"); + /* so that the slot is released and can be reused below and overwritten. */ + CHECK(drreg_get_app_value(drcontext, bb, inst, TEST_REG, TEST_REG) == + DRREG_SUCCESS, + "should get app value"); + } else if (instr_is_mov_constant(inst, &val) && val == 3) { + uint tls_offs = spill_test_reg_to_slot(drcontext, bb, inst, TEST_REG2, + &allowed_test_reg_2, false); + instrlist_meta_preinsert(bb, inst, + XINST_CREATE_load_int(drcontext, + opnd_create_reg(TEST_REG), + OPND_CREATE_INT32(MAGIC_VAL))); + CHECK(tls_offs == tls_offs_test_reg_1, "must use the freed up slot"); + } else if (drmgr_is_last_instr(drcontext, inst)) { + CHECK(drreg_unreserve_register(drcontext, bb, inst, TEST_REG2) == + DRREG_SUCCESS, + "cannot unreserve register"); + } } - drvector_delete(&allowed); + drvector_delete(&allowed_test_reg_1); + drvector_delete(&allowed_test_reg_2); /* XXX i#511: add more tests */ + if (subtest == DRREG_TEST_18_C) + return DR_EMIT_STORE_TRANSLATIONS; return DR_EMIT_DEFAULT; } @@ -420,6 +581,10 @@ event_instru2instru(void *drcontext, void *tag, instrlist_t *bb, bool for_trace, reg_id_t reg0, reg1; ptr_int_t subtest = (ptr_int_t)user_data; + if (subtest == DRREG_TEST_19_C || subtest == DRREG_TEST_20_C) { + return DR_EMIT_DEFAULT; + } + drreg_init_and_fill_vector(&allowed, false); drreg_set_vector_entry(&allowed, TEST_REG, true); diff --git a/suite/tests/client-interface/drx-test.template b/suite/tests/client-interface/drx-test.templatex similarity index 58% rename from suite/tests/client-interface/drx-test.template rename to suite/tests/client-interface/drx-test.templatex index 513be000646..3066e3eb3b5 100644 --- a/suite/tests/client-interface/drx-test.template +++ b/suite/tests/client-interface/drx-test.templatex @@ -25,6 +25,17 @@ creating child terminating child by sending SIGKILL event_nudge exit code 2304 event_exit +#ifdef AARCHXX +/* PR#4944: drreg_event_restore_state uses instr_get_eflags to get + * instr eflags. This requires encoding the instr. We fail to + * encode the syscall skip branch instr because the target is too + * far from the tmp buf address to fit into the 26-bit offset. This + * is not fatal as private_instr_encode works around the issue by + * using instr_encode_ignore_reachability instead. + */ + +#endif child exit code = 9 event_exit #endif