Skip to content

Commit

Permalink
i#4865 emulate: Add new emulation instrumentation API
Browse files Browse the repository at this point in the history
Adds a new emulation instrumentation interface with simpler
convenience routines for identifying the original application
instruction fetch and operands.

Uses the new interface in memtrace_simple.  Separate commits will
update memval_simple, memtrace_x86, and drmemtrace.

Adds an x86 test that runs the memtrace_simple sample on the
allasm_repstr app and ensures we see 2-byte "rep movs" instructions
instead of the 1-byte "movs" that the existing sample client records
due to not being emulation-aware.  Adds a -log_to_stderr flag to the
sample to make the test simpler and not have to deal with log files.

Adding new events is separated into #4947 which we may attempt in the
future.  Removing rep string non-fetched instructions is #4948.

--------------------------------------------------
Testing:

memtrace_simple before:
    $ bin64/drrun -c api/bin/libmemtrace_simple.so -- suite/tests/bin/allasm_repstr && cat `ls -1td api/bin/*.log|head -1`
    Format: <data address>: <data size>, <(r)ead/(w)rite/opcode>
    0x401018:  1, movs
    0x40200e:  1, r
    0x402000:  1, w
    0x401018:  1, movs
    0x40200f:  1, r
    0x402001:  1, w
    0x401018:  1, movs
    0x402010:  1, r
    0x402002:  1, w
    0x401018:  1, movs
    0x402011:  1, r
    0x402003:  1, w
    0x401018:  1, movs
    0x402012:  1, r
    0x402004:  1, w
After:
    Format: <data address>: <data size>, <(r)ead/(w)rite/opcode>
    0x401018:  2, rep movs
    0x40200e:  1, r
    0x402000:  1, w
    0x401018:  2, rep movs
    0x40200f:  1, r
    0x402001:  1, w
    0x401018:  2, rep movs
    0x402010:  1, r
    0x402002:  1, w
    0x401018:  2, rep movs
    0x402011:  1, r
    0x402003:  1, w
    0x401018:  2, rep movs
    0x402012:  1, r
    0x402004:  1, w

memtrace_simple on a gather expansion:
    $ ninja && bin64/drrun -c api/bin/libmemtrace_simple.so -log_to_stderr -- suite/tests/bin/allasm_scattergather
    Format: <data address>: <data size>, <(r)ead/(w)rite/opcode>
    0x40105c: 11, mov
    0x402022:  4, w
    0x401067: 11, mov
    0x402026:  4, w
    0x401072: 11, mov
    0x40202a:  4, w
    0x40108a:  7, mov
    0x402026:  4, r
    0x40108a:  7, mov
    0x40202e:  4, r
    0x40108a:  7, mov
    0x40202a:  4, r
  =>
    Format: <data address>: <data size>, <(r)ead/(w)rite/opcode>
    0x40105c: 11, mov
    0x402022:  4, w
    0x401067: 11, mov
    0x402026:  4, w
    0x401072: 11, mov
    0x40202a:  4, w
    0x40108a: 10, vpgatherdd
    0x402026:  4, r
    0x40202e:  4, r
    0x40202a:  4, r
--------------------------------------------------

Issue: #4865
  • Loading branch information
derekbruening committed Jun 18, 2021
1 parent f5902af commit 8f35c25
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 61 deletions.
3 changes: 2 additions & 1 deletion api/docs/release.dox
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ Further non-compatibility-affecting changes include:
- Added #DRREG_HANDLE_MULTI_PHASE_SLOT_RESERVATIONS to #drreg_bb_properties_t to
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 drmgr_in_emulation_region(), drmgr_orig_app_instr_for_fetch(, and
drmgr_orig_app_instr_for_operands() 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.
Expand Down
96 changes: 63 additions & 33 deletions api/samples/memtrace_simple.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@

#include <stdio.h>
#include <stddef.h> /* for offsetof */
#include <string.h>
#include "dr_api.h"
#include "drmgr.h"
#include "drreg.h"
Expand Down Expand Up @@ -100,8 +101,9 @@ typedef struct {
} per_thread_t;

static client_id_t client_id;
static void *mutex; /* for multithread support */
static uint64 num_refs; /* keep a global memory reference count */
static void *mutex; /* for multithread support */
static uint64 num_refs; /* keep a global memory reference count */
static bool log_to_stderr; /* for testing */

/* Allocated TLS slot offsets */
enum {
Expand Down Expand Up @@ -226,7 +228,7 @@ insert_save_addr(void *drcontext, instrlist_t *ilist, instr_t *where, opnd_t ref

/* insert inline code to add an instruction entry into the buffer */
static void
instrument_instr(void *drcontext, instrlist_t *ilist, instr_t *where)
instrument_instr(void *drcontext, instrlist_t *ilist, instr_t *where, instr_t *instr)
{
/* We need two scratch registers */
reg_id_t reg_ptr, reg_tmp;
Expand All @@ -241,10 +243,10 @@ instrument_instr(void *drcontext, instrlist_t *ilist, instr_t *where)
}
insert_load_buf_ptr(drcontext, ilist, where, reg_ptr);
insert_save_type(drcontext, ilist, where, reg_ptr, reg_tmp,
(ushort)instr_get_opcode(where));
(ushort)instr_get_opcode(instr));
insert_save_size(drcontext, ilist, where, reg_ptr, reg_tmp,
(ushort)instr_length(drcontext, where));
insert_save_pc(drcontext, ilist, where, reg_ptr, reg_tmp, instr_get_app_pc(where));
(ushort)instr_length(drcontext, instr));
insert_save_pc(drcontext, ilist, where, reg_ptr, reg_tmp, instr_get_app_pc(instr));
insert_update_buf_ptr(drcontext, ilist, where, reg_ptr, sizeof(mem_ref_t));
/* Restore scratch registers */
if (drreg_unreserve_register(drcontext, ilist, where, reg_ptr) != DRREG_SUCCESS ||
Expand Down Expand Up @@ -284,28 +286,38 @@ instrument_mem(void *drcontext, instrlist_t *ilist, instr_t *where, opnd_t ref,
* with an instruction entry and memory reference entries.
*/
static dr_emit_flags_t
event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *instr,
event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *where,
bool for_trace, bool translating, void *user_data)
{
int i;

if (!instr_is_app(instr))
return DR_EMIT_DEFAULT;
if (!instr_reads_memory(instr) && !instr_writes_memory(instr))
return DR_EMIT_DEFAULT;
/* Insert code to add an entry for each app instruction. */
/* Use the drmgr_orig_app_instr_* interface to properly handle our own use
* of drutil_expand_rep_string() and drx_expand_scatter_gather() (as well
* as another client/library emulating the instruction stream).
*/
instr_t *instr_fetch = drmgr_orig_app_instr_for_fetch(drcontext);
if (instr_fetch != NULL &&
(instr_reads_memory(instr_fetch) || instr_writes_memory(instr_fetch))) {
DR_ASSERT(instr_is_app(instr_fetch));
instrument_instr(drcontext, bb, where, instr_fetch);
}

/* insert code to add an entry for app instruction */
instrument_instr(drcontext, bb, instr);
/* Insert code to add an entry for each memory reference opnd. */
instr_t *instr_operands = drmgr_orig_app_instr_for_operands(drcontext);
if (instr_operands == NULL ||
(!instr_reads_memory(instr_operands) && !instr_writes_memory(instr_operands)))
return DR_EMIT_DEFAULT;
DR_ASSERT(instr_is_app(instr_operands));

/* insert code to add an entry for each memory reference opnd */
for (i = 0; i < instr_num_srcs(instr); i++) {
if (opnd_is_memory_reference(instr_get_src(instr, i)))
instrument_mem(drcontext, bb, instr, instr_get_src(instr, i), false);
for (i = 0; i < instr_num_srcs(instr_operands); i++) {
if (opnd_is_memory_reference(instr_get_src(instr_operands, i)))
instrument_mem(drcontext, bb, where, instr_get_src(instr_operands, i), false);
}

for (i = 0; i < instr_num_dsts(instr); i++) {
if (opnd_is_memory_reference(instr_get_dst(instr, i)))
instrument_mem(drcontext, bb, instr, instr_get_dst(instr, i), true);
for (i = 0; i < instr_num_dsts(instr_operands); i++) {
if (opnd_is_memory_reference(instr_get_dst(instr_operands, i)))
instrument_mem(drcontext, bb, where, instr_get_dst(instr_operands, i), true);
}

/* insert code to call clean_call for processing the buffer */
Expand All @@ -319,8 +331,8 @@ event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst
* Using a fault to handle a full buffer should be more robust, and the
* forthcoming buffer filling API (i#513) will provide that.
*/
IF_AARCHXX_ELSE(!instr_is_exclusive_store(instr), true))
dr_insert_clean_call(drcontext, bb, instr, (void *)clean_call, false, 0);
IF_AARCHXX_ELSE(!instr_is_exclusive_store(instr_operands), true))
dr_insert_clean_call(drcontext, bb, where, (void *)clean_call, false, 0);

return DR_EMIT_DEFAULT;
}
Expand Down Expand Up @@ -361,18 +373,22 @@ event_thread_init(void *drcontext)

data->num_refs = 0;

/* We're going to dump our data to a per-thread file.
* On Windows we need an absolute path so we place it in
* the same directory as our library. We could also pass
* in a path as a client argument.
*/
data->log =
log_file_open(client_id, drcontext, NULL /* using client lib path */, "memtrace",
if (log_to_stderr) {
data->logf = stderr;
} else {
/* We're going to dump our data to a per-thread file.
* On Windows we need an absolute path so we place it in
* the same directory as our library. We could also pass
* in a path as a client argument.
*/
data->log = log_file_open(client_id, drcontext, NULL /* using client lib path */,
"memtrace",
#ifndef WINDOWS
DR_FILE_CLOSE_ON_FORK |
DR_FILE_CLOSE_ON_FORK |
#endif
DR_FILE_ALLOW_LARGE);
data->logf = log_stream_from_file(data->log);
DR_FILE_ALLOW_LARGE);
data->logf = log_stream_from_file(data->log);
}
fprintf(data->logf, "Format: <data address>: <data size>, <(r)ead/(w)rite/opcode>\n");
}

Expand All @@ -385,7 +401,8 @@ event_thread_exit(void *drcontext)
dr_mutex_lock(mutex);
num_refs += data->num_refs;
dr_mutex_unlock(mutex);
log_stream_close(data->logf); /* closes fd too */
if (!log_to_stderr)
log_stream_close(data->logf); /* closes fd too */
dr_raw_mem_free(data->buf_base, MEM_BUF_SIZE);
dr_thread_free(drcontext, data, sizeof(per_thread_t));
}
Expand Down Expand Up @@ -418,6 +435,19 @@ dr_client_main(client_id_t id, int argc, const char *argv[])
drreg_options_t ops = { sizeof(ops), 3, false };
dr_set_client_name("DynamoRIO Sample Client 'memtrace'",
"http://dynamorio.org/issues");

const char *client_ops = dr_get_options(id);
if (client_ops != NULL && client_ops[0] != '\0') {
if (strcmp(client_ops, "-log_to_stderr") == 0)
log_to_stderr = true;
else {
dr_fprintf(STDERR,
"Error: unknown options |%s|: only -log_to_stderr is supported\n",
client_ops);
dr_abort();
}
}

if (!drmgr_init() || drreg_init(&ops) != DRREG_SUCCESS || !drutil_init() ||
!drx_init())
DR_ASSERT(false);
Expand Down
45 changes: 45 additions & 0 deletions ext/drmgr/drmgr.c
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ typedef struct _per_thread_t {
instr_t *last_instr;
emulated_instr_t emulation_info;
bool in_emulation_region;
instr_t *insertion_instr;
} per_thread_t;

/* Emulation note types */
Expand Down Expand Up @@ -978,6 +979,7 @@ drmgr_bb_event_do_instrum_phases(void *drcontext, void *tag, instrlist_t *bb,
/* Main pass for instrumentation. */
for (inst = instrlist_first(bb); inst != NULL; inst = next_inst) {
next_inst = instr_get_next(inst);
pt->insertion_instr = inst;
if (!pt->in_emulation_region && drmgr_is_emulation_start(inst)) {
IF_DEBUG(bool ok =) drmgr_get_emulated_instr_data(inst, &pt->emulation_info);
ASSERT(ok, "should be at emulation start label");
Expand Down Expand Up @@ -3162,6 +3164,10 @@ drmgr_disable_auto_predication(void *drcontext, instrlist_t *ilist)

/***************************************************************************
* EMULATION
*
* XXX i#4947: Should we add new instrumentation insertion events that
* hide expansions and emulations for a simpler model than the current
* query interfaces below?
*/

/*
Expand Down Expand Up @@ -3313,6 +3319,45 @@ drmgr_in_emulation_region(void *drcontext, OUT const emulated_instr_t **emulatio
return true;
}

DR_EXPORT
instr_t *
drmgr_orig_app_instr_for_fetch(void *drcontext)
{
per_thread_t *pt = (per_thread_t *)drmgr_get_tls_field(drcontext, our_tls_idx);
if (drmgr_current_bb_phase(drcontext) != DRMGR_PHASE_INSERTION)
return NULL;
const emulated_instr_t *emulation;
if (drmgr_in_emulation_region(drcontext, &emulation)) {
if (TEST(DR_EMULATE_FIRST_INSTR, emulation->flags)) {
return emulation->instr;
} // Else skip further instr fetches until outside emulation region.
} else if (instr_is_app(pt->insertion_instr)) {
return pt->insertion_instr;
}
return NULL;
}

DR_EXPORT
instr_t *
drmgr_orig_app_instr_for_operands(void *drcontext)
{
per_thread_t *pt = (per_thread_t *)drmgr_get_tls_field(drcontext, our_tls_idx);
if (drmgr_current_bb_phase(drcontext) != DRMGR_PHASE_INSERTION)
return NULL;
const emulated_instr_t *emulation;
if (drmgr_in_emulation_region(drcontext, &emulation)) {
if (TEST(DR_EMULATE_FIRST_INSTR, emulation->flags) &&
!TEST(DR_EMULATE_INSTR_ONLY, emulation->flags))
return emulation->instr;
if (instr_is_app(pt->insertion_instr) &&
TEST(DR_EMULATE_INSTR_ONLY, emulation->flags))
return pt->insertion_instr;
} else if (instr_is_app(pt->insertion_instr)) {
return pt->insertion_instr;
}
return NULL;
}

/***************************************************************************
* DRBBDUP
*/
Expand Down
34 changes: 33 additions & 1 deletion ext/drmgr/drmgr.dox
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2010-2015 Google, Inc. All rights reserved.
* Copyright (c) 2010-2021 Google, Inc. All rights reserved.
* **********************************************************/

/*
Expand Down Expand Up @@ -46,6 +46,7 @@ composed and combined.
- \ref sec_drmgr_setup
- \ref sec_drmgr_events
- \ref sec_drmgr_stages
- \ref sec_drmgr_emulation
- \ref sec_drmgr_tls
- \ref sec_drmgr_notes

Expand Down Expand Up @@ -104,6 +105,37 @@ for each instruction, each registered component is invoked. This simplifies
register allocation (register allocation is provided by a separate
Extension \p drreg).

\subsection sec_drmgr_emulation Emulation-Aware Instrumentation

Support for one client library emulating or refactoring application code
while another observes application behavior is provided through emulation
marking and reading support.

An emulation-creating library uses drmgr_insert_emulation_start() and
drmgr_insert_emulation_end() to mark the emulation region and make a copy
of the original instruction being replaced.

An emulation-aware observational client then wants to instrument the
original copy, rather than the emulation sequence present in the
instruction list. This is most easily done using the application
instruction and data events, which split the third stage, insertion, into
two parts, and ignores non-application instructions (i.e., labels and meta
instructions added as part of emulation in the app2app phase is all hidden
from these two events). Use drmgr_register_bb_app_observation_event() to
register these events.

Another method for an observational client is to use the regular insertion
event and manually determine which instruction to examine, using
drmgr_in_emulation_region() and the recommended code sequence in the
documentation for that function.

The refactoring of application code for simpler instrumentation performed
by drutil_expand_rep_string() and drx_expand_scatter_gather() is also
marked as emulation, but only for instructions, not data: i.e.,
#DR_EMULATE_INSTR_ONLY is set for these refactorings. These are typically
used in address tracing clients, and we recommend that such clients also
use the emulation-aware instrumentation approaches outlined here.

\subsection sec_drmgr_ordering Ordering

The proper ordering of instrumentation passes depends on the particulars of
Expand Down
Loading

0 comments on commit 8f35c25

Please sign in to comment.