Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i#5520: Add instruction encodings to offline drmemtraces #5662

Merged
merged 9 commits into from
Sep 29, 2022
3 changes: 2 additions & 1 deletion api/docs/debug_memtrace.dox
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ Type cheat sheet (from trace_type_t enum):
- 0x0e direct call
- 0x00 load
- 0x01 store
- 0x1d: non-fetched instr
- 0x1d non-fetched instr
- 0x1a footer
- 0x2f instr encoding

type_is_instr: 0xa-0x10 + 0x1e

Expand Down
2 changes: 1 addition & 1 deletion api/docs/release.dox
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ Further non-compatibility-affecting changes include:
the .zip, which sets the granularity of a fast seek.
- Added dr_register_post_attach_event(), dr_unregister_post_attach_event(),
dr_register_pre_detach_event(), and dr_unregister_pre_detach_event().

- Added insruction encodings to drmemtrace offline traces.

The changes between version 9.0.1 and 9.0.0 include the following compatibility
changes:
Expand Down
9 changes: 8 additions & 1 deletion clients/drcachesim/common/memref.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2015-2021 Google, Inc. All rights reserved.
* Copyright (c) 2015-2022 Google, Inc. All rights reserved.
* **********************************************************/

/*
Expand Down Expand Up @@ -66,6 +66,13 @@ struct _memref_instr_t {
memref_tid_t tid; /**< Thread id. */
addr_t addr; /**< The address of the instruction (i.e., program counter). */
size_t size; /**< The length of the instruction. */
/**
* The instruction's raw encoding. This field is only valid when the the file type
* (see #TRACE_MARKER_TYPE_FILETYPE) has #OFFLINE_FILE_TYPE_ENCODINGS set.
* DynamoRIO's decode_from_copy() (or any other decoding library) can be used to
* decode into a higher-level instruction representation.
*/
unsigned char encoding[MAX_ENCODING_LENGTH];
};

/** A trace entry representing a software-requested explicit cache flush. */
Expand Down
31 changes: 29 additions & 2 deletions clients/drcachesim/common/trace_entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,18 @@ typedef enum {
* PC of the interruption point provided today.
*/
TRACE_ENTRY_VERSION_NO_KERNEL_PC = 2,
/**
* #TRACE_MARKER_TYPE_KERNEL_EVENT records provide the absolute
* PC of the interruption point.
*/
TRACE_ENTRY_VERSION_KERNEL_PC = 3,
derekbruening marked this conversation as resolved.
Show resolved Hide resolved
/**
* The trace supports embedded instruction encodings, but they are only present
* if #OFFLINE_FILE_TYPE_ENCODINGS is set.
*/
TRACE_ENTRY_VERSION_ENCODINGS = 4,
/** The latest version of the trace format. */
TRACE_ENTRY_VERSION,
TRACE_ENTRY_VERSION = TRACE_ENTRY_VERSION_ENCODINGS,
derekbruening marked this conversation as resolved.
Show resolved Hide resolved
} trace_version_t;

/** The type of a trace entry in a #memref_t structure. */
Expand Down Expand Up @@ -206,6 +216,12 @@ typedef enum {
TRACE_TYPE_PREFETCH_WRITE_L3, /**< Store prefetch to L3 cache. */
TRACE_TYPE_PREFETCH_WRITE_L3_NT, /**< Non-temporal store prefetch to L3 cache. */

// Internal value for encoding bytes.
// Currently this is only used for offline traces with OFFLINE_FILE_TYPE_ENCODINGS.
// XXX i#5520: Add to online traces, but under an option since extra
// encoding entries add runtime overhead.
TRACE_TYPE_ENCODING,

// Update trace_type_names[] when adding here.
} trace_type_t;

Expand Down Expand Up @@ -446,6 +462,11 @@ marker_type_is_function_marker(const trace_marker_type_t mark)
return mark >= TRACE_MARKER_TYPE_FUNC_ID && mark <= TRACE_MARKER_TYPE_FUNC_RETVAL;
}

// The longest instruction on any architecture.
// This matches DR's MAX_INSTR_LENGTH for x86 but we want the same
// size for all architectures and DR's define is available ifdef X86 only.
#define MAX_ENCODING_LENGTH 17
derekbruening marked this conversation as resolved.
Show resolved Hide resolved

// This is the data format generated by the online tracer and produced after
// post-processing of raw offline traces.
// The reader_t class transforms this into memref_t before handing to analysis tools.
Expand All @@ -460,12 +481,17 @@ START_PACKED_STRUCTURE
struct _trace_entry_t {
unsigned short type; // 2 bytes: trace_type_t
// 2 bytes: mem ref size, instr length, or num of instrs for instr bundle,
// or marker sub-type.
// or marker sub-type, or num of bytes (max sizeof(addr_t)) in encoding[] array.
unsigned short size;
derekbruening marked this conversation as resolved.
Show resolved Hide resolved
union {
addr_t addr; // 4/8 bytes: mem ref addr, instr pc, tid, pid, marker val
// The length of each instr in the instr bundle
unsigned char length[sizeof(addr_t)];
// The raw encoding bytes for the subsequent instruction fetch entry.
// There may be multiple consecutive records to hold long instructions.
derekbruening marked this conversation as resolved.
Show resolved Hide resolved
// The reader should keep concatenating these bytes until the subsequent
// insruction fetch entry is found.
derekbruening marked this conversation as resolved.
Show resolved Hide resolved
unsigned char encoding[sizeof(addr_t)];
};
} END_PACKED_STRUCTURE;
typedef struct _trace_entry_t trace_entry_t;
Expand Down Expand Up @@ -565,6 +591,7 @@ typedef enum {
OFFLINE_FILE_TYPE_ARCH_X86_64, /**< All possible architecture types. */
OFFLINE_FILE_TYPE_IFILTERED = 0x80, /**< Instruction addresses filtered online. */
OFFLINE_FILE_TYPE_DFILTERED = 0x100, /**< Data addresses filtered online. */
OFFLINE_FILE_TYPE_ENCODINGS = 0x200, /**< Instruction encodings are included. */
} offline_file_type_t;

static inline const char *
Expand Down
8 changes: 6 additions & 2 deletions clients/drcachesim/drcachesim.dox.in
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,12 @@ Executed instructions are stored in #_memref_instr_t. The program
counter and length of the encoded instruction are provided. The
length can be used to compute the address of the subsequent instruction.

The instruction records do not contain instruction encodings. Such
information can be obtained by disassembling the application and
The raw encoding of the instruction is provided. This can be decoded
using the drdecode decoder or any other decoder.

Older legacy traces may not contain instruction encodings. For those
traces, encodings for static code can be obtained by
disassembling the application and
library binaries. The provided interfaces
module_mapper_t::get_loaded_modules() and
module_mapper_t::find_mapped_trace_address() facilitate loading in
Expand Down
36 changes: 36 additions & 0 deletions clients/drcachesim/reader/reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
*/

#include <assert.h>
#include <string.h>
#include "reader.h"
#include "../common/memref.h"
#include "../common/utils.h"
Expand Down Expand Up @@ -104,6 +105,18 @@ reader_t::operator++()
// use to obtain the PC for subsequent data references.
cur_ref_.data.pc = cur_pc_;
break;
case TRACE_TYPE_ENCODING:
if (last_encoding_.size + input_entry_->size > MAX_ENCODING_LENGTH) {
ERRMSG("Invalid too-large encoding size %zu + %d\n", last_encoding_.size,
input_entry_->size);
assert(false);
at_eof_ = true;
break;
}
memcpy(last_encoding_.bits + last_encoding_.size, input_entry_->encoding,
input_entry_->size);
last_encoding_.size += input_entry_->size;
break;
case TRACE_TYPE_INSTR_MAYBE_FETCH:
// While offline traces can convert rep string per-iter instrs into
// no-fetch entries, online can't w/o extra work, so we do the work
Expand Down Expand Up @@ -140,6 +153,26 @@ reader_t::operator++()
prev_instr_addr_ = input_entry_->addr;
if (cur_ref_.instr.type != TRACE_TYPE_INSTR_NO_FETCH)
++cur_instr_count_;
// Look for encoding bits that belong to this instr.
if (last_encoding_.size > 0) {
if (last_encoding_.size != cur_ref_.instr.size) {
ERRMSG("Encoding size %zu != instr size %zu\n",
last_encoding_.size, cur_ref_.instr.size);
assert(false);
}
memcpy(cur_ref_.instr.encoding, last_encoding_.bits,
last_encoding_.size);
encodings_[cur_ref_.instr.addr] = last_encoding_;
} else {
const auto &it = encodings_.find(cur_ref_.instr.addr);
if (it != encodings_.end()) {
memcpy(cur_ref_.instr.encoding, it->second.bits, it->second.size);
} else if (!expect_no_encodings_) {
ERRMSG("Missing encoding for 0x%zx\n", cur_ref_.instr.addr);
assert(false);
}
}
last_encoding_.size = 0;
}
break;
case TRACE_TYPE_INSTR_BUNDLE:
Expand Down Expand Up @@ -242,6 +275,9 @@ reader_t::operator++()
last_timestamp_instr_count_ = cur_instr_count_;
else if (cur_ref_.marker.marker_type == TRACE_MARKER_TYPE_CHUNK_INSTR_COUNT)
chunk_instr_count_ = cur_ref_.marker.marker_value;
else if (cur_ref_.marker.marker_type == TRACE_MARKER_TYPE_FILETYPE &&
TESTANY(OFFLINE_FILE_TYPE_ENCODINGS, cur_ref_.marker.marker_value))
expect_no_encodings_ = false;
break;
default:
ERRMSG("Unknown trace entry type %d\n", input_entry_->type);
Expand Down
10 changes: 10 additions & 0 deletions clients/drcachesim/reader/reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@ class reader_t : public std::iterator<std::input_iterator_tag, memref_t> {
public:
reader_t()
{
cur_ref_ = {};
}
reader_t(int verbosity, const char *prefix)
: verbosity_(verbosity)
, output_prefix_(prefix)
{
cur_ref_ = {};
}
virtual ~reader_t()
{
Expand Down Expand Up @@ -134,6 +136,11 @@ class reader_t : public std::iterator<std::input_iterator_tag, memref_t> {
const char *output_prefix_ = "[reader]";

private:
struct encoding_info_t {
size_t size = 0;
unsigned char bits[MAX_ENCODING_LENGTH];
};

trace_entry_t *input_entry_ = nullptr;
memref_t cur_ref_;
memref_tid_t cur_tid_ = 0;
Expand All @@ -147,6 +154,9 @@ class reader_t : public std::iterator<std::input_iterator_tag, memref_t> {
uint64_t chunk_instr_count_ = 0; // Unchanging once set to non-zero.
uint64_t last_timestamp_instr_count_ = 0;
bool skip_next_cpu_ = false;
bool expect_no_encodings_ = true;
encoding_info_t last_encoding_;
std::unordered_map<addr_t, encoding_info_t> encodings_;
};

#endif /* _READER_H_ */
4 changes: 2 additions & 2 deletions clients/drcachesim/tests/offline-phys.templatex
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ Adios world!
Output format:
<record#>: T<tid> <record details>
------------------------------------------------------------
1: T[0-9]+ <marker: version 3>
2: T[0-9]+ <marker: filetype 0x42>
1: T[0-9]+ <marker: version 4>
2: T[0-9]+ <marker: filetype 0x242>
3: T[0-9]+ <marker: cache line size [0-9]+>
4: T[0-9]+ <marker: chunk instruction count [0-9]+>
5: T[0-9]+ <marker: page size [0-9]+>
Expand Down
19 changes: 19 additions & 0 deletions clients/drcachesim/tests/raw2trace_unit_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,19 @@ test_branch_delays(void *drcontext)
check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP) &&
check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID) &&
// Both branches should be delayed until after the timestamp+cpu markers:
check_entry(entries, idx, TRACE_TYPE_ENCODING, -1) &&
#ifdef X86_32
// An extra encoding entry is needed.
check_entry(entries, idx, TRACE_TYPE_ENCODING, -1) &&
#endif
check_entry(entries, idx, TRACE_TYPE_INSTR_CONDITIONAL_JUMP, -1) &&
check_entry(entries, idx, TRACE_TYPE_ENCODING, -1) &&
#ifdef X86_32
// An extra encoding entry is needed.
check_entry(entries, idx, TRACE_TYPE_ENCODING, -1) &&
#endif
check_entry(entries, idx, TRACE_TYPE_INSTR_DIRECT_JUMP, -1) &&
check_entry(entries, idx, TRACE_TYPE_ENCODING, -1) &&
check_entry(entries, idx, TRACE_TYPE_INSTR, -1) &&
check_entry(entries, idx, TRACE_TYPE_THREAD_EXIT, -1) &&
check_entry(entries, idx, TRACE_TYPE_FOOTER, -1));
Expand Down Expand Up @@ -413,13 +424,21 @@ test_marker_placement(void *drcontext)
check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CACHE_LINE_SIZE) &&
check_entry(entries, idx, TRACE_TYPE_MARKER,
TRACE_MARKER_TYPE_CHUNK_INSTR_COUNT) &&
check_entry(entries, idx, TRACE_TYPE_ENCODING, -1) &&
check_entry(entries, idx, TRACE_TYPE_INSTR, -1) &&
check_entry(entries, idx, TRACE_TYPE_ENCODING, -1) &&
check_entry(entries, idx, TRACE_TYPE_INSTR, -1) &&
check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID) &&
check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETADDR) &&
check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG) &&
check_entry(entries, idx, TRACE_TYPE_ENCODING, -1) &&
#ifdef X86_32
// An extra encoding entry is needed.
check_entry(entries, idx, TRACE_TYPE_ENCODING, -1) &&
#endif
check_entry(entries, idx, TRACE_TYPE_INSTR, -1) &&
check_entry(entries, idx, TRACE_TYPE_READ, -1) &&
check_entry(entries, idx, TRACE_TYPE_ENCODING, -1) &&
check_entry(entries, idx, TRACE_TYPE_INSTR, -1) &&
check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID) &&
check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETADDR) &&
Expand Down
64 changes: 42 additions & 22 deletions clients/drcachesim/tools/opcode_mix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,13 @@ std::string
opcode_mix_t::initialize()
{
serial_shard_.worker = &serial_worker_;
if (module_file_path_.empty())
return "Module file path is missing";
dcontext_.dcontext = dr_standalone_init();
// The module_file_path is optional and unused for traces with
// OFFLINE_FILE_TYPE_ENCODINGS.
if (module_file_path_.empty())
return "";
// Legacy trace support where binaries are needed.
// We do not support non-module code for such traces.
std::string error = directory_.initialize_module_file(module_file_path_);
if (!error.empty())
return "Failed to initialize directory: " + error;
Expand Down Expand Up @@ -134,6 +138,7 @@ opcode_mix_t::parallel_shard_memref(void *shard_data, const memref_t &memref)
shard_data_t *shard = reinterpret_cast<shard_data_t *>(shard_data);
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_FILETYPE) {
shard->filetype = static_cast<offline_file_type_t>(memref.marker.marker_value);
if (TESTANY(OFFLINE_FILE_TYPE_ARCH_ALL, memref.marker.marker_value) &&
!TESTANY(build_target_arch_type(), memref.marker.marker_value)) {
shard->error = std::string("Architecture mismatch: trace recorded on ") +
Expand All @@ -149,44 +154,59 @@ opcode_mix_t::parallel_shard_memref(void *shard_data, const memref_t &memref)
}
++shard->instr_count;

app_pc mapped_pc;
app_pc decode_pc;
const app_pc trace_pc = reinterpret_cast<app_pc>(memref.instr.addr);
if (trace_pc >= shard->last_trace_module_start &&
static_cast<size_t>(trace_pc - shard->last_trace_module_start) <
shard->last_trace_module_size) {
mapped_pc =
shard->last_mapped_module_start + (trace_pc - shard->last_trace_module_start);
if (TESTANY(OFFLINE_FILE_TYPE_ENCODINGS, shard->filetype)) {
// The trace has instruction encodings inside it.
decode_pc = const_cast<app_pc>(memref.instr.encoding);
} else {
std::lock_guard<std::mutex> guard(mapper_mutex_);
mapped_pc = module_mapper_->find_mapped_trace_bounds(
trace_pc, &shard->last_mapped_module_start, &shard->last_trace_module_size);
if (!module_mapper_->get_last_error().empty()) {
shard->last_trace_module_start = nullptr;
shard->last_trace_module_size = 0;
shard->error = "Failed to find mapped address for " +
to_hex_string(memref.instr.addr) + ": " +
module_mapper_->get_last_error();
// Legacy trace support where we need the binaries.
if (!module_mapper_) {
shard->error =
"Module file path is missing and trace has no embedded encodings";
return false;
}
shard->last_trace_module_start =
trace_pc - (mapped_pc - shard->last_mapped_module_start);
if (trace_pc >= shard->last_trace_module_start &&
static_cast<size_t>(trace_pc - shard->last_trace_module_start) <
shard->last_trace_module_size) {
decode_pc = shard->last_mapped_module_start +
(trace_pc - shard->last_trace_module_start);
} else {
std::lock_guard<std::mutex> guard(mapper_mutex_);
decode_pc = module_mapper_->find_mapped_trace_bounds(
trace_pc, &shard->last_mapped_module_start,
&shard->last_trace_module_size);
if (!module_mapper_->get_last_error().empty()) {
shard->last_trace_module_start = nullptr;
shard->last_trace_module_size = 0;
shard->error = "Failed to find mapped address for " +
to_hex_string(memref.instr.addr) + ": " +
module_mapper_->get_last_error();
return false;
}
shard->last_trace_module_start =
trace_pc - (decode_pc - shard->last_mapped_module_start);
}
}
int opcode;
auto cached_opcode = shard->worker->opcode_cache.find(mapped_pc);
// TODO i#2062,i#5520: We need to invalidate opcode_cache on changed app code.
// The reader may add markers to help us with that.
// For now we assume unchanging code.
auto cached_opcode = shard->worker->opcode_cache.find(trace_pc);
if (cached_opcode != shard->worker->opcode_cache.end()) {
opcode = cached_opcode->second;
} else {
instr_t instr;
instr_init(dcontext_.dcontext, &instr);
app_pc next_pc =
decode_from_copy(dcontext_.dcontext, mapped_pc, trace_pc, &instr);
decode_from_copy(dcontext_.dcontext, decode_pc, trace_pc, &instr);
if (next_pc == NULL || !instr_valid(&instr)) {
shard->error =
"Failed to decode instruction " + to_hex_string(memref.instr.addr);
return false;
}
opcode = instr_get_opcode(&instr);
shard->worker->opcode_cache[mapped_pc] = opcode;
shard->worker->opcode_cache[trace_pc] = opcode;
instr_free(dcontext_.dcontext, &instr);
}
++shard->opcode_counts[opcode];
Expand Down
Loading