Skip to content

Commit

Permalink
i#4274 kernel pc: Provide rseq abort interrupted pc (#4973)
Browse files Browse the repository at this point in the history
Adds source context information to the kernel xfer event for rseq
aborts, which was previously missing.  To provide the interrupted rseq
abort PC as the PC of the final committing store, adds logic inside DR
to record that PC when it is discovered during block creation.

Updates the rseq test to check the new information.

Issue: #4274
  • Loading branch information
derekbruening authored Jun 28, 2021
1 parent 2b86bc0 commit 271d357
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 10 deletions.
2 changes: 2 additions & 0 deletions api/docs/release.dox
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ Further non-compatibility-affecting changes include:
- 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.
- Added the source context for restartable sequence aborts (#DR_XFER_RSEQ_ABORT)
which was previously missing.

**************************************************
<hr>
Expand Down
1 change: 1 addition & 0 deletions core/arch/mangle_shared.c
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,7 @@ mangle_rseq(dcontext_t *dcontext, instrlist_t *ilist, instr_t *instr,
"Rseq sequences must fall through their endpoints");
ASSERT_NOT_REACHED();
}
rseq_set_final_instr_pc(start, pc);
mangle_rseq_insert_native_sequence(dcontext, ilist, instr, next_instr, flags,
start, end, handler, scratch_reg, reg_written,
reg_written_count);
Expand Down
9 changes: 6 additions & 3 deletions core/lib/dr_events.h
Original file line number Diff line number Diff line change
Expand Up @@ -946,9 +946,12 @@ typedef struct _dr_kernel_xfer_info_t {
dr_kernel_xfer_type_t type;
/**
* The source machine context which is about to be changed. This may be NULL
* if it is unknown, which is the case for #DR_XFER_CALLBACK_DISPATCHER and
* #DR_XFER_RSEQ_ABORT (where the PC is not known but the rest of the state
* matches the current state).
* if it is unknown, which is the case for #DR_XFER_CALLBACK_DISPATCHER.
* For #DR_XFER_RSEQ_ABORT, due to the constraints of handling restartable
* sequences, the abort PC will point prior to the committing store, while
* that store already executed during instrumentation. We recommend that
* clients treat the store as never-executed in that situation, if possible,
* to produce a more-representative sequence.
*/
const dr_mcontext_t *source_mcontext;
/**
Expand Down
5 changes: 4 additions & 1 deletion core/unix/os_exports.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2011-2020 Google, Inc. All rights reserved.
* Copyright (c) 2011-2021 Google, Inc. All rights reserved.
* Copyright (c) 2000-2010 VMware, Inc. All rights reserved.
* **********************************************************/

Expand Down Expand Up @@ -546,6 +546,9 @@ bool
rseq_get_region_info(app_pc pc, app_pc *start OUT, app_pc *end OUT, app_pc *handler OUT,
bool **reg_written OUT, int *reg_written_size OUT);

bool
rseq_set_final_instr_pc(app_pc start, app_pc final_instr_pc);

int
rseq_get_tls_ptr_offset(void);

Expand Down
41 changes: 35 additions & 6 deletions core/unix/rseq_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ typedef struct _rseq_region_t {
app_pc start;
app_pc end;
app_pc handler;
app_pc final_instr_pc;
/* We need to preserve input registers for targeting "start" instead of "handler"
* for our 2nd invocation, if they're written in the rseq region. We only support
* GPR inputs. We document that we do not support any other inputs (no flags, no
Expand Down Expand Up @@ -188,6 +189,18 @@ rseq_get_region_info(app_pc pc, app_pc *start OUT, app_pc *end OUT, app_pc *hand
return true;
}

bool
rseq_set_final_instr_pc(app_pc start, app_pc final_instr_pc)
{
rseq_region_t *info;
if (!vmvector_lookup_data(d_r_rseq_areas, start, NULL, NULL, (void **)&info))
return false;
if (final_instr_pc < start || final_instr_pc >= info->end)
return false;
info->final_instr_pc = final_instr_pc;
return true;
}

int
rseq_get_tls_ptr_offset(void)
{
Expand Down Expand Up @@ -378,6 +391,7 @@ rseq_process_entry(struct rseq_cs *entry, ssize_t load_offs)
info->start = (app_pc)(ptr_uint_t)entry->start_ip + load_offs;
info->end = info->start + entry->post_commit_offset;
info->handler = (app_pc)(ptr_uint_t)entry->abort_ip + load_offs;
info->final_instr_pc = NULL; /* Only set later at block building time. */
int signature;
if (!d_r_safe_read(info->handler - sizeof(signature), sizeof(signature),
&signature)) {
Expand Down Expand Up @@ -740,14 +754,29 @@ rseq_process_native_abort(dcontext_t *dcontext)
{
/* Raise a transfer event. */
LOG(THREAD, LOG_INTERP | LOG_VMAREAS, 2, "Abort triggered in rseq native code\n");
/* We do not know the precise interruption point but we try to present something
* reasonable.
*/
rseq_region_t *info = NULL;
priv_mcontext_t *source_mc = NULL;
if (dcontext->last_fragment != NULL &&
vmvector_lookup_data(d_r_rseq_areas, dcontext->last_fragment->tag, NULL, NULL,
(void **)&info)) {
/* An artifact of our run-twice solution is that clients have already seen
* the whole sequence when any abort anywhere in the native execution occurs.
* We thus give the source PC as the final instr in the region, and use the
* target context as the rest of the context.
*/
source_mc = HEAP_TYPE_ALLOC(dcontext, priv_mcontext_t, ACCT_CLIENT, PROTECTED);
*source_mc = *get_mcontext(dcontext);
source_mc->pc = info->final_instr_pc;
}
get_mcontext(dcontext)->pc = dcontext->next_tag;
if (instrument_kernel_xfer(dcontext, DR_XFER_RSEQ_ABORT, osc_empty,
/* We do not know the source PC so we do not
* supply a source state.
*/
NULL, NULL, dcontext->next_tag,
get_mcontext(dcontext)->xsp, osc_empty,
if (instrument_kernel_xfer(dcontext, DR_XFER_RSEQ_ABORT, osc_empty, NULL, source_mc,
dcontext->next_tag, get_mcontext(dcontext)->xsp, osc_empty,
get_mcontext(dcontext), 0)) {
dcontext->next_tag = canonicalize_pc_target(dcontext, get_mcontext(dcontext)->pc);
}
if (source_mc != NULL)
HEAP_TYPE_FREE(dcontext, source_mc, priv_mcontext_t, ACCT_CLIENT, PROTECTED);
}
34 changes: 34 additions & 0 deletions suite/tests/linux/rseq.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
#include <sched.h>
#include <linux/rseq.h>
#include <errno.h>
#undef NDEBUG
#include <assert.h>

#define EXPANDSTR(x) #x
Expand Down Expand Up @@ -95,6 +96,9 @@ static __thread volatile struct rseq rseq_tls;
/* Make it harder to find rseq_tls for DR's heuristic by adding more static TLS. */
static __thread volatile struct rseq fill_up_tls[128];

extern void
test_rseq_native_abort_pre_commit();

#ifdef RSEQ_TEST_ATTACH
static atomic_int exit_requested;
static void *thread_ready;
Expand Down Expand Up @@ -602,6 +606,8 @@ test_rseq_native_abort(void)
"leaq sched_mask_2(%%rip), %%rdx\n\t"
"mov %[sysnum_setaffinity], %%eax\n\t"
"syscall\n\t"
".global test_rseq_native_abort_pre_commit\n\t"
"test_rseq_native_abort_pre_commit:\n\t"
"11:\n\t"
"nop\n\t"

Expand Down Expand Up @@ -670,6 +676,8 @@ test_rseq_native_abort(void)
"add x2, x2, :lo12:sched_mask_2\n\t"
"mov w8, #%[sysnum_setaffinity]\n\t"
"svc #0\n\t"
".global test_rseq_native_abort_pre_commit\n\t"
"test_rseq_native_abort_pre_commit:\n\t"
"11:\n\t"
"nop\n\t"

Expand Down Expand Up @@ -906,6 +914,32 @@ kernel_xfer_event(void *drcontext, const dr_kernel_xfer_info_t *info)
mc.flags = DR_MC_ALL;
ok = dr_get_mcontext(drcontext, &mc);
assert(ok);
/* All transfer cases in this test should have source info. */
assert(info->source_mcontext != NULL);
if (info->type == DR_XFER_RSEQ_ABORT) {
/* The interrupted context should be identical except the pc. */
assert(info->source_mcontext->pc != mc.pc);
# ifdef X86
assert(info->source_mcontext->xax == mc.xax);
assert(info->source_mcontext->xcx == mc.xcx);
assert(info->source_mcontext->xdx == mc.xdx);
assert(info->source_mcontext->xbx == mc.xbx);
assert(info->source_mcontext->xsi == mc.xsi);
assert(info->source_mcontext->xdi == mc.xdi);
assert(info->source_mcontext->xbp == mc.xbp);
assert(info->source_mcontext->xsp == mc.xsp);
# elif defined(AARCH64)
assert(memcmp(&info->source_mcontext->r0, &mc.r0, sizeof(mc.r0) * 32) == 0);
# else
# error Unsupported arch
# endif
# ifdef DEBUG /* See above: special code in core/ is DEBUG-only> */
/* Check that the interrupted PC for the true abort case is *prior* to the
* committing store.
*/
assert(info->source_mcontext->pc == (app_pc)test_rseq_native_abort_pre_commit);
# endif
}
}

DR_EXPORT void
Expand Down

0 comments on commit 271d357

Please sign in to comment.