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