| /* ********************************************************** |
| * Copyright (c) 2016-2017 Google, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of Google, Inc. nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| /* Post-processes offline traces and converts them to the format expected |
| * by the cache simulator and other analysis tools. |
| */ |
| |
| #include "dr_api.h" |
| #include "drcovlib.h" |
| #include "raw2trace.h" |
| #include "instru.h" |
| #include "../common/memref.h" |
| #include "../common/trace_entry.h" |
| #include <cstring> |
| #include <fstream> |
| #include <sstream> |
| #include <vector> |
| |
| // XXX: DR should export this |
| #define INVALID_THREAD_ID 0 |
| |
| // Assumes we return an error string by convention. |
| #define CHECK(val, msg) do { \ |
| if (!(val)) return msg; \ |
| } while (0) |
| |
| #define WARN(msg, ...) do { \ |
| fprintf(stderr, "WARNING: " msg "\n", ##__VA_ARGS__); \ |
| fflush(stderr); \ |
| } while (0) |
| |
| #define VPRINT(level, ...) do { \ |
| if (this->verbosity >= (level)) { \ |
| fprintf(stderr, "[drmemtrace]: "); \ |
| fprintf(stderr, __VA_ARGS__); \ |
| } \ |
| } while (0) |
| |
| #define DO_VERBOSE(level, x) do { \ |
| if (this->verbosity >= (level)) { \ |
| x \ |
| } \ |
| } while (0) |
| |
| /*************************************************************************** |
| * Module list |
| */ |
| |
| std::string |
| raw2trace_t::read_and_map_modules(const char *module_map) |
| { |
| // Read and load all of the modules. |
| uint num_mods; |
| VPRINT(1, "Reading module file from memory\n"); |
| if (drmodtrack_offline_read(INVALID_FILE, module_map, NULL, &modhandle, &num_mods) != |
| DRCOVLIB_SUCCESS) |
| return "Failed to parse module file"; |
| for (uint i = 0; i < num_mods; i++) { |
| drmodtrack_info_t info = {sizeof(info),}; |
| if (drmodtrack_offline_lookup(modhandle, i, &info) != DRCOVLIB_SUCCESS) |
| return "Failed to query module file"; |
| if (strcmp(info.path, "<unknown>") == 0 || |
| // i#2062: VDSO is hard to decode so for now we treat is as non-module. |
| // FIXME: currently we're dropping the ifetch data: we need the tracer |
| // to identify it instead of us, which requires drmodtrack changes. |
| strcmp(info.path, "[vdso]") == 0) { |
| // We won't be able to decode. |
| modvec.push_back(module_t(info.path, info.start, NULL, 0)); |
| } else if (info.containing_index != i) { |
| // For split segments, drmodtrack_lookup() gave the lowest base addr, |
| // so our PC offsets are from that. We assume that the single mmap of |
| // the first segment thus includes the other segments and that we don't |
| // need another mmap. |
| VPRINT(1, "Separate segment assumed covered: module %d seg " PFX " = %s\n", |
| (int)modvec.size(), (ptr_uint_t)info.start, info.path); |
| modvec.push_back(module_t(info.path, |
| // We want the low base not segment base. |
| modvec[info.containing_index].orig_base, |
| // 0 size indicates this is a secondary segment. |
| modvec[info.containing_index].map_base, 0)); |
| } else { |
| size_t map_size; |
| byte *base_pc = dr_map_executable_file(info.path, DR_MAPEXE_SKIP_WRITABLE, |
| &map_size); |
| if (base_pc == NULL) { |
| // We expect to fail to map dynamorio.dll for x64 Windows as it |
| // is built /fixed. (We could try to have the map succeed w/o relocs, |
| // but we expect to not care enough about code in DR). |
| if (strstr(info.path, "dynamorio") != NULL) |
| modvec.push_back(module_t(info.path, info.start, NULL, 0)); |
| else |
| return "Failed to map module " + std::string(info.path); |
| } else { |
| VPRINT(1, "Mapped module %d @" PFX " = %s\n", (int)modvec.size(), |
| (ptr_uint_t)base_pc, info.path); |
| modvec.push_back(module_t(info.path, info.start, base_pc, map_size)); |
| } |
| } |
| } |
| VPRINT(1, "Successfully read %d modules\n", num_mods); |
| return ""; |
| } |
| |
| std::string |
| raw2trace_t::unmap_modules(void) |
| { |
| // drmodtrack_offline_exit requires the parameter to be non-null, but we |
| // may not have even initialized the modhandle yet. |
| if (modhandle != NULL && |
| drmodtrack_offline_exit(modhandle) != DRCOVLIB_SUCCESS) { |
| return "Failed to clean up module table data"; |
| } |
| for (std::vector<module_t>::iterator mvi = modvec.begin(); |
| mvi != modvec.end(); ++mvi) { |
| if (mvi->map_base != NULL && mvi->map_size != 0) { |
| bool ok = dr_unmap_executable_file(mvi->map_base, mvi->map_size); |
| if (!ok) |
| WARN("Failed to unmap module %s", mvi->path); |
| } |
| } |
| return ""; |
| } |
| |
| /*************************************************************************** |
| * Disassembly to fill in instr and memref entries |
| */ |
| |
| static bool |
| instr_is_rep_string(instr_t *instr) |
| { |
| #ifdef X86 |
| uint opc = instr_get_opcode(instr); |
| return (opc == OP_rep_ins || opc == OP_rep_outs || opc == OP_rep_movs || |
| opc == OP_rep_stos || opc == OP_rep_lods || opc == OP_rep_cmps || |
| opc == OP_repne_cmps || opc == OP_rep_scas || opc == OP_repne_scas); |
| #else |
| return false; |
| #endif |
| } |
| |
| std::string |
| raw2trace_t::append_memref(INOUT trace_entry_t **buf_in, uint tidx, instr_t *instr, |
| opnd_t ref, bool write) |
| { |
| trace_entry_t *buf = *buf_in; |
| offline_entry_t in_entry; |
| if (!thread_files[tidx]->read((char*)&in_entry, sizeof(in_entry))) |
| return "Trace ends mid-block"; |
| if (in_entry.addr.type != OFFLINE_TYPE_MEMREF && |
| in_entry.addr.type != OFFLINE_TYPE_MEMREF_HIGH) { |
| // This happens when there are predicated memrefs in the bb. |
| // They could be earlier, so "instr" may not itself be predicated. |
| // XXX i#2015: if there are multiple predicated memrefs, our instr vs |
| // data stream may not be in the correct order here. |
| VPRINT(4, "Missing memref (next type is 0x" ZHEX64_FORMAT_STRING ")\n", |
| in_entry.combined_value); |
| // Put back the entry. |
| thread_files[tidx]->seekg(-(std::streamoff)sizeof(in_entry), |
| thread_files[tidx]->cur); |
| return ""; |
| } |
| if (instr_is_prefetch(instr)) { |
| buf->type = instru_t::instr_to_prefetch_type(instr); |
| buf->size = 1; |
| } else if (instru_t::instr_is_flush(instr)) { |
| buf->type = TRACE_TYPE_DATA_FLUSH; |
| buf->size = (ushort) opnd_size_in_bytes(opnd_get_size(ref)); |
| } else { |
| if (write) |
| buf->type = TRACE_TYPE_WRITE; |
| else |
| buf->type = TRACE_TYPE_READ; |
| buf->size = (ushort) opnd_size_in_bytes(opnd_get_size(ref)); |
| } |
| // We take the full value, to handle low or high. |
| buf->addr = (addr_t) in_entry.combined_value; |
| VPRINT(4, "Appended memref to " PFX "\n", (ptr_uint_t)buf->addr); |
| *buf_in = ++buf; |
| return ""; |
| } |
| |
| 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; |
| 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; |
| if ((in_entry->pc.modidx == 0 && in_entry->pc.modoffs == 0) || |
| modvec[in_entry->pc.modidx].map_base == NULL) { |
| // FIXME i#2062: add support for code not in a module (vsyscall, JIT, etc.). |
| // Once that support is in we can remove the bool return value and handle |
| // the memrefs up here. |
| VPRINT(3, "Skipping ifetch for %u instrs not in a module\n", instr_count); |
| *handled = false; |
| return ""; |
| } else { |
| VPRINT(3, "Appending %u instrs in bb " PFX " in mod %u +" PIFX " = %s\n", |
| instr_count, (ptr_uint_t)start_pc, (uint)in_entry->pc.modidx, |
| (ptr_uint_t)in_entry->pc.modoffs, modvec[in_entry->pc.modidx].path); |
| } |
| bool skip_icache = false; |
| if (instr_count == 0) { |
| // L0 filtering adds a PC entry with a count of 0 prior to each memref. |
| skip_icache = true; |
| instr_count = 1; |
| // We set a flag to avoid peeking forward on instr entries. |
| if (!instrs_are_separate) |
| instrs_are_separate = true; |
| } |
| CHECK(!instrs_are_separate || instr_count == 1, "cannot mix 0-count and >1-count"); |
| for (uint i = 0; i < instr_count; ++i) { |
| trace_entry_t *buf = buf_start; |
| app_pc orig_pc = decode_pc - modvec[in_entry->pc.modidx].map_base + |
| modvec[in_entry->pc.modidx].orig_base; |
| bool skip_instr = false; |
| // 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); |
| } |
| CHECK(!instr_is_cti(instr) || i == instr_count - 1, "invalid cti"); |
| if (instr_is_rep_string(instr)) { |
| // We want it to look like the original rep string instead of the |
| // drutil-expanded loop. |
| if (!prev_instr_was_rep_string) |
| prev_instr_was_rep_string = true; |
| else |
| skip_instr = true; |
| } else |
| prev_instr_was_rep_string = false; |
| // FIXME i#1729: make bundles via lazy accum until hit memref/end. |
| if (!skip_instr) { |
| DO_VERBOSE(3, { |
| instr_set_translation(instr, orig_pc); |
| dr_print_instr(dcontext, STDOUT, instr, ""); |
| }); |
| buf->type = instru_t::instr_to_instr_type(instr); |
| buf->size = (ushort) (skip_icache ? 0 : instr_length(dcontext, instr)); |
| buf->addr = (addr_t) orig_pc; |
| ++buf; |
| } else |
| VPRINT(3, "Skipping instr fetch for " PFX "\n", (ptr_uint_t)decode_pc); |
| decode_pc = pc; |
| // We need to interleave instrs with memrefs. |
| // 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 i = 0; i < instr_num_srcs(instr); i++) { |
| if (opnd_is_memory_reference(instr_get_src(instr, i))) { |
| std::string error = append_memref(&buf, tidx, instr, |
| instr_get_src(instr, i), false); |
| if (!error.empty()) |
| return error; |
| } |
| } |
| for (int i = 0; i < instr_num_dsts(instr); i++) { |
| if (opnd_is_memory_reference(instr_get_dst(instr, i))) { |
| std::string error = append_memref(&buf, tidx, instr, |
| instr_get_dst(instr, i), true); |
| if (!error.empty()) |
| return error; |
| } |
| } |
| } |
| CHECK((size_t)(buf - buf_start) < MAX_COMBINED_ENTRIES, "Too many entries"); |
| if (!out_file->write((char*)buf_start, (buf - buf_start)*sizeof(trace_entry_t))) |
| return "Failed to write to output file"; |
| } |
| *handled = true; |
| return ""; |
| } |
| |
| /*************************************************************************** |
| * Top-level |
| */ |
| |
| std::string |
| raw2trace_t::merge_and_process_thread_files() |
| { |
| // The current thread we're processing is tidx. If it's set to thread_files.size() |
| // that means we need to pick a new thread. |
| uint tidx = (uint)thread_files.size(); |
| uint thread_count = (uint)thread_files.size(); |
| offline_entry_t in_entry; |
| online_instru_t instru(NULL, false); |
| bool last_bb_handled = true; |
| std::vector<thread_id_t> tids(thread_files.size(), INVALID_THREAD_ID); |
| std::vector<uint64> times(thread_files.size(), 0); |
| byte buf_base[MAX_COMBINED_ENTRIES * sizeof(trace_entry_t)]; |
| |
| // We read the thread files simultaneously in lockstep and merge them into |
| // a single output file in timestamp order. |
| // When a thread file runs out we leave its times[] entry as 0 and its file at eof. |
| // We convert each offline entry into a trace_entry_t. |
| // We fill in instr entries and memref type and size. |
| do { |
| int size = 0; |
| byte *buf = buf_base; |
| if (tidx >= thread_files.size()) { |
| // Pick the next thread by looking for the smallest timestamp. |
| uint64 min_time = 0xffffffffffffffff; |
| uint next_tidx = 0; |
| for (uint i=0; i<times.size(); ++i) { |
| if (times[i] == 0 && !thread_files[i]->eof()) { |
| offline_entry_t entry; |
| if (!thread_files[i]->read((char*)&entry, sizeof(entry))) |
| return "Failed to read from input file"; |
| if (entry.timestamp.type != OFFLINE_TYPE_TIMESTAMP) |
| return "Missing timestamp entry"; |
| times[i] = entry.timestamp.usec; |
| VPRINT(3, "Thread %u timestamp is @0x" ZHEX64_FORMAT_STRING |
| "\n", (uint)tids[i], times[i]); |
| } |
| if (times[i] != 0 && times[i] < min_time) { |
| min_time = times[i]; |
| next_tidx = i; |
| } |
| } |
| VPRINT(2, "Next thread in timestamp order is %u @0x" ZHEX64_FORMAT_STRING |
| "\n", (uint)tids[next_tidx], times[next_tidx]); |
| tidx = next_tidx; |
| times[tidx] = 0; // Read from file for this thread's next timestamp. |
| if (tids[tidx] != INVALID_THREAD_ID) { |
| // The initial read from a file may not have seen its tid entry |
| // yet. We expect to hit that entry next. |
| size += instru.append_tid(buf, tids[tidx]); |
| } |
| if (size > 0) { |
| // We have to write this now before we append any bb entries. |
| CHECK((uint)size < MAX_COMBINED_ENTRIES, "Too many entries"); |
| if (!out_file->write((char*)buf_base, size)) |
| return "Failed to write to output file"; |
| buf = buf_base; |
| } |
| size = 0; |
| } |
| VPRINT(4, "About to read thread %d at pos %d\n", |
| (uint)tids[tidx], (int)thread_files[tidx]->tellg()); |
| if (!thread_files[tidx]->read((char*)&in_entry, sizeof(in_entry))) { |
| if (thread_files[tidx]->eof()) { |
| // Rather than a fatal error we try to continue to provide partial |
| // results in case the disk was full or there was some other issue. |
| WARN("Input file for thread %d is truncated", (uint)tids[tidx]); |
| in_entry.extended.type = OFFLINE_TYPE_EXTENDED; |
| in_entry.extended.ext = OFFLINE_EXT_TYPE_FOOTER; |
| } else { |
| std::stringstream ss; |
| ss << "Failed to read from file for thread " << (uint)tids[tidx]; |
| return ss.str(); |
| } |
| } |
| if (in_entry.extended.type == OFFLINE_TYPE_EXTENDED) { |
| if (in_entry.extended.ext == OFFLINE_EXT_TYPE_FOOTER) { |
| // Push forward to EOF. |
| offline_entry_t entry; |
| if (thread_files[tidx]->read((char*)&entry, sizeof(entry)) || |
| !thread_files[tidx]->eof()) |
| return "Footer is not the final entry"; |
| CHECK(tids[tidx] != INVALID_THREAD_ID, "Missing thread id"); |
| VPRINT(2, "Thread %d exit\n", (uint)tids[tidx]); |
| size += instru.append_thread_exit(buf, tids[tidx]); |
| buf += size; |
| --thread_count; |
| tidx = (uint)thread_files.size(); // Request thread scan. |
| } else { |
| std::stringstream ss; |
| ss << "Invalid extension type " << (int)in_entry.extended.ext; |
| return ss.str(); |
| } |
| } else if (in_entry.timestamp.type == OFFLINE_TYPE_TIMESTAMP) { |
| VPRINT(2, "Thread %u timestamp 0x" ZHEX64_FORMAT_STRING "\n", |
| (uint)tids[tidx], in_entry.timestamp.usec); |
| times[tidx] = in_entry.timestamp.usec; |
| tidx = (uint)thread_files.size(); // Request thread scan. |
| } else if (in_entry.addr.type == OFFLINE_TYPE_MEMREF || |
| in_entry.addr.type == OFFLINE_TYPE_MEMREF_HIGH) { |
| if (!last_bb_handled) { |
| // For currently-unhandled non-module code, memrefs are handled here |
| // where we can easily handle the transition out of the bb. |
| trace_entry_t *entry = (trace_entry_t *) buf; |
| entry->type = TRACE_TYPE_READ; // Guess. |
| entry->size = 1; // Guess. |
| entry->addr = (addr_t) in_entry.combined_value; |
| VPRINT(4, "Appended non-module memref to " PFX "\n", |
| (ptr_uint_t)entry->addr); |
| size += sizeof(*entry); |
| buf += size; |
| } else { |
| // We should see an instr entry first |
| return "memref entry found outside of bb"; |
| } |
| } else if (in_entry.pc.type == OFFLINE_TYPE_PC) { |
| std::string result = append_bb_entries(tidx, &in_entry, &last_bb_handled); |
| if (!result.empty()) |
| return result; |
| } else if (in_entry.tid.type == OFFLINE_TYPE_THREAD) { |
| VPRINT(2, "Thread %u entry\n", (uint)in_entry.tid.tid); |
| if (tids[tidx] == INVALID_THREAD_ID) |
| tids[tidx] = in_entry.tid.tid; |
| size += instru.append_tid(buf, in_entry.tid.tid); |
| buf += size; |
| } else if (in_entry.pid.type == OFFLINE_TYPE_PID) { |
| VPRINT(2, "Process %u entry\n", (uint)in_entry.pid.pid); |
| size += instru.append_pid(buf, in_entry.pid.pid); |
| buf += size; |
| } else if (in_entry.addr.type == OFFLINE_TYPE_IFLUSH) { |
| offline_entry_t entry; |
| if (!thread_files[tidx]->read((char*)&entry, sizeof(entry)) || |
| entry.addr.type != OFFLINE_TYPE_IFLUSH) |
| return "Flush missing 2nd entry"; |
| VPRINT(2, "Flush " PFX"-" PFX"\n", (ptr_uint_t)in_entry.addr.addr, |
| (ptr_uint_t)entry.addr.addr); |
| size += instru.append_iflush(buf, in_entry.addr.addr, |
| (size_t)(entry.addr.addr - in_entry.addr.addr)); |
| buf += size; |
| } else { |
| std::stringstream ss; |
| ss << "Unknown trace type " << (int)in_entry.timestamp.type; |
| return ss.str(); |
| } |
| if (size > 0) { |
| CHECK((uint)size < MAX_COMBINED_ENTRIES, "Too many entries"); |
| if (!out_file->write((char*)buf_base, size)) |
| return "Failed to write to output file"; |
| } |
| } while (thread_count > 0); |
| return ""; |
| } |
| |
| std::string |
| raw2trace_t::check_thread_file(std::istream *f) |
| { |
| // Check version header. |
| offline_entry_t ver_entry; |
| if (!f->read((char*)&ver_entry, sizeof(ver_entry))) { |
| return "Unable to read thread log file"; |
| } |
| if (ver_entry.extended.type != OFFLINE_TYPE_EXTENDED || |
| ver_entry.extended.ext != OFFLINE_EXT_TYPE_HEADER) { |
| return "Thread log file is corrupted: missing version entry"; |
| } |
| if (ver_entry.extended.value != OFFLINE_FILE_VERSION) { |
| std::stringstream ss; |
| ss << "Version mismatch: expect " << OFFLINE_FILE_VERSION << " vs " |
| << (int)ver_entry.extended.value; |
| return ss.str(); |
| } |
| return ""; |
| } |
| |
| std::string |
| raw2trace_t::do_conversion() |
| { |
| std::string error = read_and_map_modules(modmap); |
| if (!error.empty()) |
| return error; |
| trace_entry_t entry; |
| entry.type = TRACE_TYPE_HEADER; |
| entry.size = 0; |
| entry.addr = TRACE_ENTRY_VERSION; |
| if (!out_file->write((char*)&entry, sizeof(entry))) |
| return "Failed to write header to output file"; |
| |
| error = merge_and_process_thread_files(); |
| if (!error.empty()) |
| return error; |
| |
| entry.type = TRACE_TYPE_FOOTER; |
| entry.size = 0; |
| entry.addr = 0; |
| if (!out_file->write((char*)&entry, sizeof(entry))) |
| return "Failed to write footer to output file"; |
| VPRINT(1, "Successfully converted %zu thread files\n", thread_files.size()); |
| return ""; |
| } |
| |
| raw2trace_t::raw2trace_t(const char *module_map_in, |
| const std::vector<std::istream*> &thread_files_in, |
| std::ostream *out_file_in, |
| void *dcontext_in, |
| unsigned int verbosity_in) |
| : modmap(module_map_in), modhandle(NULL), thread_files(thread_files_in), |
| out_file(out_file_in), dcontext(dcontext_in), |
| prev_instr_was_rep_string(false), instrs_are_separate(false), |
| verbosity(verbosity_in) |
| { |
| if (dcontext == NULL) { |
| dcontext = dr_standalone_init(); |
| #ifdef ARM |
| // We keep the mode at ARM and rely on LSB=1 offsets in the modoffs fields |
| // to trigger Thumb decoding. |
| dr_set_isa_mode(dcontext, DR_ISA_ARM_A32, NULL); |
| #endif |
| } |
| // We go ahead and start with a reasonably large capacity. |
| hashtable_init_ex(&decode_cache, 16, HASH_INTPTR, false, false, NULL, NULL, NULL); |
| // We pay a little memory to get a lower load factor. |
| hashtable_config_t config = {sizeof(config), true, 40}; |
| hashtable_configure(&decode_cache, &config); |
| } |
| |
| raw2trace_t::~raw2trace_t() |
| { |
| unmap_modules(); |
| // XXX: We can't use a free-payload function b/c we can't get the dcontext there, |
| // 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); |
| } |
| } |
| hashtable_delete(&decode_cache); |
| } |