diff --git a/clients/drcachesim/tracer/raw2trace.cpp b/clients/drcachesim/tracer/raw2trace.cpp index 60cbd54e26b..0f56290f527 100644 --- a/clients/drcachesim/tracer/raw2trace.cpp +++ b/clients/drcachesim/tracer/raw2trace.cpp @@ -70,13 +70,6 @@ } \ } while (0) -#define DO_VERBOSE(level, x) \ - do { \ - if (this->verbosity >= (level)) { \ - x; /* ; makes vera++ happy */ \ - } \ - } while (0) - /*************************************************************************** * Module list */ @@ -422,8 +415,8 @@ raw2trace_t::thread_file_at_eof(uint tidx) // Returns FAULT_INTERRUPTED_BB if a fault occurred on this memref. // Any other non-empty string is a fatal error. std::string -raw2trace_t::append_memref(INOUT trace_entry_t **buf_in, uint tidx, instr_t *instr, - opnd_t ref, bool write) +raw2trace_t::append_memref(INOUT trace_entry_t **buf_in, uint tidx, + const instr_summary_t *instr, opnd_t ref, bool write) { trace_entry_t *buf = *buf_in; offline_entry_t in_entry; @@ -458,10 +451,10 @@ raw2trace_t::append_memref(INOUT trace_entry_t **buf_in, uint tidx, instr_t *ins return ""; } if (!have_type) { - if (instr_is_prefetch(instr)) { - buf->type = instru_t::instr_to_prefetch_type(instr); + if (instr->is_prefetch()) { + buf->type = instr->prefetch_type(); buf->size = 1; - } else if (instru_t::instr_is_flush(instr)) { + } else if (instr->is_flush()) { buf->type = TRACE_TYPE_DATA_FLUSH; buf->size = (ushort)opnd_size_in_bytes(opnd_get_size(ref)); } else { @@ -504,7 +497,7 @@ std::string raw2trace_t::append_bb_entries(uint tidx, offline_entry_t *in_entry, OUT bool *handled) { uint instr_count = in_entry->pc.instr_count; - instr_t *instr; + const instr_summary_t *instr; trace_entry_t buf_start[MAX_COMBINED_ENTRIES]; app_pc start_pc = modvec()[in_entry->pc.modidx].map_base + in_entry->pc.modoffs; app_pc pc, decode_pc = start_pc; @@ -538,29 +531,16 @@ raw2trace_t::append_bb_entries(uint tidx, offline_entry_t *in_entry, OUT bool *h modvec()[in_entry->pc.modidx].orig_base; // To avoid repeatedly decoding the same instruction on every one of its // dynamic executions, we cache the decoding in a hashtable. - instr = (instr_t *)hashtable_lookup(&decode_cache, decode_pc); - if (instr == NULL) { - instr = instr_create(dcontext); - // We assume the default ISA mode and currently require the 32-bit - // postprocessor for 32-bit applications. - pc = decode(dcontext, decode_pc, instr); - if (pc == NULL || !instr_valid(instr)) { - WARN("Encountered invalid/undecodable instr @ %s+" PFX, - modvec()[in_entry->pc.modidx].path, - (ptr_uint_t)in_entry->pc.modoffs); - break; - } - hashtable_add(&decode_cache, decode_pc, instr); - } else { - pc = instr_get_raw_bits(instr) + instr_length(dcontext, instr); + pc = decode_pc; + instr = + get_instr_summary(in_entry->pc.modidx, in_entry->pc.modoffs, &pc, orig_pc); + if (instr == nullptr) { + // We hit some error somewhere, and already reported it. Just exit the loop. + break; } - DO_VERBOSE(3, { - instr_set_translation(instr, orig_pc); - dr_print_instr(dcontext, STDOUT, instr, ""); - }); - CHECK(!instr_is_cti(instr) || i == instr_count - 1, "invalid cti"); + CHECK(!instr->is_cti() || i == instr_count - 1, "invalid cti"); // FIXME i#1729: make bundles via lazy accum until hit memref/end. - buf->type = instru_t::instr_to_instr_type(instr); + buf->type = instr->type(); if (buf->type == TRACE_TYPE_INSTR_MAYBE_FETCH) { // We want it to look like the original rep string, with just one instr // fetch for the whole loop, instead of the drutil-expanded loop. @@ -577,7 +557,7 @@ raw2trace_t::append_bb_entries(uint tidx, offline_entry_t *in_entry, OUT bool *h } } else prev_instr_was_rep_string = false; - buf->size = (ushort)(skip_icache ? 0 : instr_length(dcontext, instr)); + buf->size = (ushort)(skip_icache ? 0 : instr->length()); buf->addr = (addr_t)orig_pc; ++buf; decode_pc = pc; @@ -585,32 +565,28 @@ raw2trace_t::append_bb_entries(uint tidx, offline_entry_t *in_entry, OUT bool *h // There is no following memref for (instrs_are_separate && !skip_icache). if ((!instrs_are_separate || skip_icache) && // Rule out OP_lea. - (instr_reads_memory(instr) || instr_writes_memory(instr))) { - for (int j = 0; j < instr_num_srcs(instr); j++) { - if (opnd_is_memory_reference(instr_get_src(instr, j))) { - std::string error = - append_memref(&buf, tidx, instr, instr_get_src(instr, j), false); - if (error == FAULT_INTERRUPTED_BB) { - truncated = true; - break; - } else if (!error.empty()) - return error; - } + (instr->reads_memory() || instr->writes_memory())) { + for (uint j = 0; j < instr->num_mem_srcs(); j++) { + std::string error = + append_memref(&buf, tidx, instr, instr->mem_src_at(j), false); + if (error == FAULT_INTERRUPTED_BB) { + truncated = true; + break; + } else if (!error.empty()) + return error; } - for (int j = 0; !truncated && j < instr_num_dsts(instr); j++) { - if (opnd_is_memory_reference(instr_get_dst(instr, j))) { - std::string error = - append_memref(&buf, tidx, instr, instr_get_dst(instr, j), true); - if (error == FAULT_INTERRUPTED_BB) { - truncated = true; - break; - } else if (!error.empty()) - return error; - } + for (uint j = 0; !truncated && j < instr->num_mem_dests(); j++) { + std::string error = + append_memref(&buf, tidx, instr, instr->mem_dest_at(j), true); + if (error == FAULT_INTERRUPTED_BB) { + truncated = true; + break; + } else if (!error.empty()) + return error; } } CHECK((size_t)(buf - buf_start) < MAX_COMBINED_ENTRIES, "Too many entries"); - if (instr_is_cti(instr)) { + if (instr->is_cti()) { CHECK(delayed_branch[tidx].empty(), "Failed to flush delayed branch"); // In case this is the last branch prior to a thread switch, buffer it. We // avoid swapping threads immediately after a branch so that analyzers can @@ -878,6 +854,99 @@ raw2trace_t::do_conversion() return ""; } +const instr_summary_t * +raw2trace_t::get_instr_summary(uint64 modidx, uint64 modoffs, INOUT app_pc *pc, + app_pc orig) +{ + const app_pc decode_pc = *pc; + const instr_summary_t *ret = + static_cast(hashtable_lookup(&decode_cache, decode_pc)); + if (ret == nullptr) { + instr_summary_t *desc = new instr_summary_t(); + if (!instr_summary_t::construct(dcontext, pc, orig, desc, verbosity)) { + WARN("Encountered invalid/undecodable instr @ %s+" PFX, + modvec()[static_cast(modidx)].path, (ptr_uint_t)modoffs); + return nullptr; + } + hashtable_add(&decode_cache, decode_pc, desc); + ret = desc; + } else { + /* XXX i#3129: Log some rendering of the instruction summary that will be + * returned. + */ + *pc = ret->next_pc(); + } + return ret; +} + +bool +instr_summary_t::construct(void *dcontext, INOUT app_pc *pc, app_pc orig_pc, + OUT instr_summary_t *desc, uint verbosity) +{ + struct instr_destroy_t { + instr_destroy_t(void *dcontext_in, instr_t *instr_in) + : dcontext(dcontext_in) + , instr(instr_in) + { + } + void *dcontext; + instr_t *instr; + ~instr_destroy_t() + { + instr_destroy(dcontext, instr); + } + }; + + instr_t *instr = instr_create(dcontext); + instr_destroy_t instr_collector(dcontext, instr); + + *pc = decode(dcontext, *pc, instr); + if (*pc == nullptr || !instr_valid(instr)) { + return false; + } + if (verbosity > 3) { + instr_set_translation(instr, orig_pc); + dr_print_instr(dcontext, STDOUT, instr, ""); + } + desc->next_pc_ = *pc; + desc->packed_ = 0; + + bool is_prefetch = instr_is_prefetch(instr); + bool reads_memory = instr_reads_memory(instr); + bool writes_memory = instr_writes_memory(instr); + + if (reads_memory) + desc->packed_ |= kReadsMemMask; + if (writes_memory) + desc->packed_ |= kWritesMemMask; + if (is_prefetch) + desc->packed_ |= kIsPrefetchMask; + if (instru_t::instr_is_flush(instr)) + desc->packed_ |= kIsFlushMask; + if (instr_is_cti(instr)) + desc->packed_ |= kIsCtiMask; + + desc->type_ = instru_t::instr_to_instr_type(instr); + desc->prefetch_type_ = is_prefetch ? instru_t::instr_to_prefetch_type(instr) : 0; + desc->length_ = static_cast(instr_length(dcontext, instr)); + + if (reads_memory || writes_memory) { + for (int i = 0, e = instr_num_srcs(instr); i < e; ++i) { + opnd_t op = instr_get_src(instr, i); + if (opnd_is_memory_reference(op)) + desc->mem_srcs_and_dests_.push_back(op); + } + desc->num_mem_srcs_ = static_cast(desc->mem_srcs_and_dests_.size()); + + for (int i = 0, e = instr_num_dsts(instr); i < e; ++i) { + opnd_t op = instr_get_dst(instr, i); + if (opnd_is_memory_reference(op)) + desc->mem_srcs_and_dests_.push_back(op); + } + } + return true; +} + raw2trace_t::raw2trace_t(const char *module_map_in, const std::vector &thread_files_in, std::ostream *out_file_in, void *dcontext_in, @@ -919,7 +988,7 @@ raw2trace_t::~raw2trace_t() // so we have to explicitly free the payloads. for (uint i = 0; i < HASHTABLE_SIZE(decode_cache.table_bits); i++) { for (hash_entry_t *e = decode_cache.table[i]; e != NULL; e = e->next) { - instr_destroy(dcontext, (instr_t *)e->payload); + delete (static_cast(e->payload)); } } hashtable_delete(&decode_cache); diff --git a/clients/drcachesim/tracer/raw2trace.h b/clients/drcachesim/tracer/raw2trace.h index c5584121a66..c224b536693 100644 --- a/clients/drcachesim/tracer/raw2trace.h +++ b/clients/drcachesim/tracer/raw2trace.h @@ -71,6 +71,126 @@ struct module_t { bool is_external; // If true, the data is embedded in drmodtrack custom fields. }; +/** + * instr_summary_t is a compact encapsulation of the information needed by trace + * conversion from decoded instructions. + */ +struct instr_summary_t final { + instr_summary_t() + { + } + + /** + * Populates a pre-allocated instr_summary_t description, from the instruction found + * at pc. Updates pc to the next instruction. Optionally logs translation details + * (using orig_pc and verbosity). + */ + static bool + construct(void *dcontext, INOUT app_pc *pc, app_pc orig_pc, OUT instr_summary_t *desc, + uint verbosity = 0); + + /** + * Get the pc after the instruction that was used to produce this instr_summary_t. + */ + app_pc + next_pc() const + { + return next_pc_; + } + +private: + friend class raw2trace_t; + + byte + length() const + { + return length_; + } + uint16_t + type() const + { + return type_; + } + uint16_t + prefetch_type() const + { + return prefetch_type_; + } + + bool + reads_memory() const + { + return TESTANY(kReadsMemMask, packed_); + } + bool + writes_memory() const + { + return TESTANY(kWritesMemMask, packed_); + } + bool + is_prefetch() const + { + return TESTANY(kIsPrefetchMask, packed_); + } + bool + is_flush() const + { + return TESTANY(kIsFlushMask, packed_); + } + bool + is_cti() const + { + return TESTANY(kIsCtiMask, packed_); + } + + const opnd_t & + mem_src_at(size_t pos) const + { + return mem_srcs_and_dests_[pos]; + } + const opnd_t & + mem_dest_at(size_t pos) const + { + return mem_srcs_and_dests_[num_mem_srcs_ + pos]; + } + size_t + num_mem_srcs() const + { + return num_mem_srcs_; + } + size_t + num_mem_dests() const + { + return mem_srcs_and_dests_.size() - num_mem_srcs_; + } + + static const int kReadsMemMask = 0x0001; + static const int kWritesMemMask = 0x0002; + static const int kIsPrefetchMask = 0x0004; + static const int kIsFlushMask = 0x0008; + static const int kIsCtiMask = 0x0010; + + instr_summary_t(const instr_summary_t &other) = delete; + instr_summary_t & + operator=(const instr_summary_t &) = delete; + instr_summary_t(instr_summary_t &&other) = delete; + instr_summary_t & + operator=(instr_summary_t &&) = delete; + + uint16_t type_ = 0; + uint16_t prefetch_type_ = 0; + byte length_ = 0; + app_pc next_pc_ = 0; + + // Squash srcs and dests to save memory usage. We may want to + // bulk-allocate pages of instr_summary_t objects, instead + // of piece-meal allocating them on the heap one at a time. + // One vector and a byte is smaller than 2 vectors. + std::vector mem_srcs_and_dests_; + uint8_t num_mem_srcs_ = 0; + byte packed_ = 0; +}; + /** * module_mapper_t maps and unloads application modules. * Using it assumes a dr_context has already been setup. @@ -298,6 +418,8 @@ class raw2trace_t { void *user_data; }; + const instr_summary_t * + get_instr_summary(uint64 modx, uint64 modoffs, INOUT app_pc *pc, app_pc orig); std::string read_and_map_modules(); std::string @@ -305,8 +427,8 @@ class raw2trace_t { std::string append_bb_entries(uint tidx, offline_entry_t *in_entry, OUT bool *handled); std::string - append_memref(INOUT trace_entry_t **buf_in, uint tidx, instr_t *instr, opnd_t ref, - bool write); + append_memref(INOUT trace_entry_t **buf_in, uint tidx, const instr_summary_t *instr, + opnd_t ref, bool write); std::string append_delayed_branch(uint tidx);