diff --git a/clients/drcachesim/CMakeLists.txt b/clients/drcachesim/CMakeLists.txt index 942e49847b8..d3d32248aba 100644 --- a/clients/drcachesim/CMakeLists.txt +++ b/clients/drcachesim/CMakeLists.txt @@ -161,19 +161,27 @@ add_exported_library(drmemtrace_reuse_distance STATIC tools/reuse_distance.cpp) add_exported_library(drmemtrace_histogram STATIC tools/histogram.cpp) add_exported_library(drmemtrace_reuse_time STATIC tools/reuse_time.cpp) add_exported_library(drmemtrace_basic_counts STATIC tools/basic_counts.cpp) -add_exported_library(drmemtrace_opcode_mix STATIC - tools/opcode_mix.cpp tracer/raw2trace_shared.cpp) +add_exported_library(drmemtrace_opcode_mix STATIC tools/opcode_mix.cpp) add_exported_library(drmemtrace_syscall_mix STATIC tools/syscall_mix.cpp) add_exported_library(drmemtrace_view STATIC tools/view.cpp tracer/raw2trace_shared.cpp) add_exported_library(drmemtrace_func_view STATIC tools/func_view.cpp) add_exported_library(drmemtrace_invariant_checker STATIC tools/invariant_checker.cpp) add_exported_library(drmemtrace_schedule_stats STATIC tools/schedule_stats.cpp) +add_exported_library(drmemtrace_decode_cache STATIC + tools/common/decode_cache.cpp + # XXX: Possibly create a library for raw2trace_shared, to avoid + # multiple build overhead. + tracer/raw2trace_shared.cpp) add_exported_library(drmemtrace_schedule_file STATIC common/schedule_file.cpp) add_exported_library(drmemtrace_mutex_dbg_owned STATIC common/mutex_dbg_owned.cpp) -target_link_libraries(drmemtrace_invariant_checker drdecode drmemtrace_schedule_file) +target_link_libraries(drmemtrace_invariant_checker drdecode drmemtrace_schedule_file + drmemtrace_decode_cache) +target_link_libraries(drmemtrace_decode_cache drcovlib_static) +target_link_libraries(drmemtrace_opcode_mix drmemtrace_decode_cache) +configure_DynamoRIO_standalone(drmemtrace_decode_cache) configure_DynamoRIO_standalone(drmemtrace_opcode_mix) configure_DynamoRIO_standalone(drmemtrace_view) configure_DynamoRIO_standalone(drmemtrace_invariant_checker) @@ -320,6 +328,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/reader) # so that we can more cleanly separate tracer and raw2trace code. include_directories(${CMAKE_CURRENT_SOURCE_DIR}/tracer) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/scheduler) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/tools/common) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) if (BUILD_PT_POST_PROCESSOR) @@ -611,6 +620,7 @@ restore_nonclient_flags(drmemtrace_analyzer) restore_nonclient_flags(drmemtrace_invariant_checker) restore_nonclient_flags(drmemtrace_schedule_stats) restore_nonclient_flags(drmemtrace_schedule_file) +restore_nonclient_flags(drmemtrace_decode_cache) # We need to pass /EHsc and we pull in libcmtd into drcachesim from a dep lib. # Thus we need to override the /MT with /MTd. @@ -684,6 +694,7 @@ add_win32_flags(drmemtrace_analyzer) add_win32_flags(drmemtrace_invariant_checker) add_win32_flags(drmemtrace_schedule_stats) add_win32_flags(drmemtrace_schedule_file) +add_win32_flags(drmemtrace_decode_cache) add_win32_flags(directory_iterator) add_win32_flags(test_helpers) add_win32_flags(drmemtrace_mutex_dbg_owned) @@ -942,6 +953,26 @@ if (BUILD_TESTS) add_win32_flags(tool.drcacheoff.burst_aarch64_sys) endif () + add_executable(tool.drcachesim.decode_cache_test tests/decode_cache_test.cpp) + configure_DynamoRIO_standalone(tool.drcachesim.decode_cache_test) + add_win32_flags(tool.drcachesim.decode_cache_test) + target_link_libraries(tool.drcachesim.decode_cache_test + drmemtrace_decode_cache test_helpers) + add_test(NAME tool.drcachesim.decode_cache_test + COMMAND tool.drcachesim.decode_cache_test) + set_tests_properties(tool.drcachesim.decode_cache_test PROPERTIES + TIMEOUT ${test_seconds}) + + add_executable(tool.drcacheoff.opcode_mix_test tests/opcode_mix_test.cpp) + configure_DynamoRIO_standalone(tool.drcacheoff.opcode_mix_test) + add_win32_flags(tool.drcacheoff.opcode_mix_test) + target_link_libraries(tool.drcacheoff.opcode_mix_test + drmemtrace_opcode_mix drmemtrace_decode_cache test_helpers) + add_test(NAME tool.drcacheoff.opcode_mix_test + COMMAND tool.drcacheoff.opcode_mix_test) + set_tests_properties(tool.drcacheoff.opcode_mix_test PROPERTIES + TIMEOUT ${test_seconds}) + # XXX i#1997: dynamorio_static is not supported on Mac yet # FIXME i#2949: gcc 7.3 fails to link certain configs # TODO i#3544: Port tests to RISC-V 64 diff --git a/clients/drcachesim/tests/decode_cache_test.cpp b/clients/drcachesim/tests/decode_cache_test.cpp new file mode 100644 index 00000000000..d04cf6b4f42 --- /dev/null +++ b/clients/drcachesim/tests/decode_cache_test.cpp @@ -0,0 +1,303 @@ +/* ********************************************************** + * Copyright (c) 2024 Google, LLC 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 GOOGLE, LLC 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. + */ + +/* Tests for the decode_cache_t library. */ + +#include +#include + +#include "decode_cache.h" +#include "../common/memref.h" +#include "memref_gen.h" + +namespace dynamorio { +namespace drmemtrace { + +static constexpr addr_t TID_A = 1; +static constexpr offline_file_type_t ENCODING_FILE_TYPE = + static_cast(OFFLINE_FILE_TYPE_ENCODINGS); + +class test_decode_info_t : public decode_info_base_t { +public: + bool is_nop_ = false; + bool is_ret_ = false; + bool is_ipt_ = false; + bool decode_info_set_ = false; + +private: + void + set_decode_info_derived(void *dcontext, + const dynamorio::drmemtrace::_memref_instr_t &memref_instr, + instr_t *instr) override + { + // decode_cache_t should call set_decode_info only one time per object. + assert(!decode_info_set_); + is_nop_ = instr_is_nop(instr); + is_ret_ = instr_is_return(instr); + is_ipt_ = instr_is_interrupt(instr); + decode_info_set_ = true; + } +}; + +std::string +check_decode_caching(void *drcontext, bool persist_instrs, bool use_module_mapper) +{ + static constexpr addr_t BASE_ADDR = 0x123450; + instr_t *nop = XINST_CREATE_nop(drcontext); + instr_t *ret = XINST_CREATE_return(drcontext); + instr_t *ipt = XINST_CREATE_interrupt(drcontext, OPND_CREATE_INT8(10)); + instrlist_t *ilist = instrlist_create(drcontext); + instrlist_append(ilist, nop); + instrlist_append(ilist, ret); + instrlist_append(ilist, ipt); + std::vector memref_setup = { + { gen_instr(TID_A), nop }, + { gen_instr(TID_A), ret }, + { gen_instr(TID_A), nop }, + { gen_instr(TID_A), ipt }, + }; + std::vector memrefs; + instrlist_t *ilist_for_test_decode_cache = nullptr; + std::string module_file_for_test_decode_cache = ""; + if (use_module_mapper) { + // This does not set encodings in the memref.instr. + memrefs = add_encodings_to_memrefs(ilist, memref_setup, 0, + /*set_only_instr_addr=*/true); + // We pass the instrs to construct the test_module_mapper_t in the + // test_decode_cache_t; + ilist_for_test_decode_cache = ilist; + module_file_for_test_decode_cache = "some_mod_file"; + } else { + memrefs = add_encodings_to_memrefs(ilist, memref_setup, BASE_ADDR); + } + + test_decode_info_t test_decode_info; + if (test_decode_info.is_valid()) { + return "Unexpected valid default-constructed decode info"; + } + + if (persist_instrs) { + // These are tests to verify the operation of instr_decode_info_t: that it stores + // the instr_t correctly. + // Tests for instr_decode_cache_t are done when persist_instrs = false (see + // the else part below). + test_decode_cache_t decode_cache( + drcontext, + /*persist_decoded_instr=*/true, ilist_for_test_decode_cache); + std::string err = + decode_cache.init(ENCODING_FILE_TYPE, module_file_for_test_decode_cache, ""); + if (err != "") + return err; + for (const memref_t &memref : memrefs) { + instr_decode_info_t *unused_cached_decode_info; + err = decode_cache.add_decode_info(memref.instr, unused_cached_decode_info); + if (err != "") + return err; + } + instr_decode_info_t *decode_info_nop = + decode_cache.get_decode_info(reinterpret_cast(memrefs[0].instr.addr)); + if (decode_info_nop == nullptr || !decode_info_nop->is_valid() || + !instr_is_nop(decode_info_nop->get_decoded_instr())) { + return "Unexpected instr_decode_info_t for nop instr"; + } + instr_decode_info_t *decode_info_ret = + decode_cache.get_decode_info(reinterpret_cast(memrefs[1].instr.addr)); + if (decode_info_ret == nullptr || !decode_info_ret->is_valid() || + !instr_is_return(decode_info_ret->get_decoded_instr())) { + return "Unexpected instr_decode_info_t for ret instr"; + } + } else { + // These are tests to verify the operation of instr_decode_cache_t: that it caches + // decode info correctly. + test_decode_cache_t decode_cache( + drcontext, + /*persist_decoded_instrs=*/false, ilist_for_test_decode_cache); + std::string err = + decode_cache.init(ENCODING_FILE_TYPE, module_file_for_test_decode_cache, ""); + if (err != "") + return err; + // Test: Lookup non-existing pc. + if (decode_cache.get_decode_info( + reinterpret_cast(memrefs[0].instr.addr)) != nullptr) { + return "Unexpected test_decode_info_t for never-seen pc"; + } + + test_decode_info_t *cached_decode_info; + + // Test: Lookup existing pc. + err = decode_cache.add_decode_info(memrefs[0].instr, cached_decode_info); + if (err != "") + return err; + test_decode_info_t *decode_info_nop = + decode_cache.get_decode_info(reinterpret_cast(memrefs[0].instr.addr)); + if (decode_info_nop == nullptr || decode_info_nop != cached_decode_info || + !decode_info_nop->is_valid() || !decode_info_nop->is_nop_) { + return "Unexpected test_decode_info_t for nop instr"; + } + + // Test: Lookup another existing pc. + err = decode_cache.add_decode_info(memrefs[1].instr, cached_decode_info); + if (err != "") + return err; + test_decode_info_t *decode_info_ret = + decode_cache.get_decode_info(reinterpret_cast(memrefs[1].instr.addr)); + if (decode_info_ret == nullptr || decode_info_ret != cached_decode_info || + !decode_info_ret->is_valid() || !decode_info_ret->is_ret_) { + return "Unexpected test_decode_info_t for ret instr"; + } + + // Test: Lookup existing pc but from a different memref. + // Set up the second nop memref to reuse the same encoding as the first nop. + memrefs[2].instr.encoding_is_new = false; + err = decode_cache.add_decode_info(memrefs[2].instr, cached_decode_info); + if (err != "") + return err; + test_decode_info_t *decode_info_nop_2 = + decode_cache.get_decode_info(reinterpret_cast(memrefs[2].instr.addr)); + if (decode_info_nop_2 != decode_info_nop || + decode_info_nop_2 != cached_decode_info) { + return "Unexpected decode info instance for second instance of nop"; + } + + if (!use_module_mapper) { + // Test: Overwrite existing decode info for a pc. Works only with embedded + // encodings. + // Pretend the interrupt is at the same trace pc as the ret. + // Encodings have been added to the memref already so this still remains + // an interrupt instruction even though we've modified addr. + memrefs[3].instr.addr = memrefs[1].instr.addr; + err = decode_cache.add_decode_info(memrefs[3].instr, cached_decode_info); + if (err != "") + return err; + test_decode_info_t *decode_info_ipt = decode_cache.get_decode_info( + reinterpret_cast(memrefs[3].instr.addr)); + if (decode_info_ipt == nullptr || decode_info_ipt != cached_decode_info || + !decode_info_ipt->is_valid() || !decode_info_ipt->is_ipt_ || + decode_info_ipt->is_ret_) { + return "Unexpected test_decode_info_t for ipt instr"; + } + decode_info_ret = decode_cache.get_decode_info( + reinterpret_cast(memrefs[1].instr.addr)); + if (decode_info_ret != decode_info_ipt) { + return "Expected ret and ipt memref pcs to return the same decode info"; + } + } + + // Test: Verify all cached decode info gets cleared. + decode_cache.clear_cache(); + decode_info_nop = + decode_cache.get_decode_info(reinterpret_cast(memrefs[0].instr.addr)); + decode_info_ret = + decode_cache.get_decode_info(reinterpret_cast(memrefs[1].instr.addr)); + if (decode_info_nop != nullptr || decode_info_ret != nullptr) { + return "Cached decode info not cleared after clear_cache()"; + } + } + instrlist_clear_and_destroy(drcontext, ilist); + std::cerr << "check_decode_caching with persist_instrs: " << persist_instrs + << ", use_module_mapper: " << use_module_mapper << " passed\n"; + return ""; +} + +std::string +check_missing_module_mapper_and_no_encoding(void *drcontext) +{ + memref_t instr = gen_instr(TID_A); + test_decode_cache_t decode_cache( + drcontext, + /*persist_decoded_instr=*/true, /*ilist_for_test_module_mapper=*/nullptr); + instr_decode_info_t dummy; + // Initialize to non-nullptr for the test. + instr_decode_info_t *cached_decode_info = &dummy; + + // Missing init before add_decode_info. + std::string err = decode_cache.add_decode_info(instr.instr, cached_decode_info); + if (err == "") { + return "Expected error at add_decode_info but did not get any"; + } + if (cached_decode_info != nullptr) { + return "Expected returned reference cached_decode_info to be nullptr"; + } + + // init for a filetype without encodings, with no module file path either. + err = decode_cache.init( + static_cast(OFFLINE_FILE_TYPE_SYSCALL_NUMBERS), "", ""); + if (err == "") { + return "Expected error at init but did not get any"; + } + std::cerr << "check_missing_module_mapper passed\n"; + return ""; +} + +int +test_main(int argc, const char *argv[]) +{ + void *drcontext = dr_standalone_init(); + std::string err = check_decode_caching(drcontext, /*persist_instrs=*/false, + /*use_module_mapper=*/false); + if (err != "") { + std::cerr << err << "\n"; + exit(1); + } + err = check_decode_caching(drcontext, /*persist_instrs=*/true, + /*use_module_mapper=*/false); + if (err != "") { + std::cerr << err << "\n"; + exit(1); + } +#ifndef WINDOWS + // TODO i#5960: Enable these tests after the test-only Windows issue is + // fixed. + err = check_decode_caching(drcontext, /*persist_instrs=*/false, + /*use_module_mapper=*/true); + if (err != "") { + std::cerr << err << "\n"; + exit(1); + } + err = check_decode_caching(drcontext, /*persist_instrs=*/true, + /*use_module_mapper=*/true); + if (err != "") { + std::cerr << err << "\n"; + exit(1); + } +#endif + err = check_missing_module_mapper_and_no_encoding(drcontext); + if (err != "") { + std::cerr << err << "\n"; + exit(1); + } + + return 0; +} + +} // namespace drmemtrace +} // namespace dynamorio diff --git a/clients/drcachesim/tests/memref_gen.h b/clients/drcachesim/tests/memref_gen.h index 1515bd915e1..699503a17f6 100644 --- a/clients/drcachesim/tests/memref_gen.h +++ b/clients/drcachesim/tests/memref_gen.h @@ -188,7 +188,7 @@ gen_exit(memref_tid_t tid) std::vector add_encodings_to_memrefs(instrlist_t *ilist, std::vector &memref_instr_vec, - addr_t base_addr) + addr_t base_addr, bool set_only_instr_addr = false) { static const int MAX_DECODE_SIZE = 2048; byte decode_buf[MAX_DECODE_SIZE]; @@ -207,8 +207,10 @@ add_encodings_to_memrefs(instrlist_t *ilist, const int instr_size = instr_length(GLOBAL_DCONTEXT, pair.instr); pair.memref.instr.addr = offset + base_addr; pair.memref.instr.size = instr_size; - memcpy(pair.memref.instr.encoding, &decode_buf[offset], instr_size); - pair.memref.instr.encoding_is_new = true; + if (!set_only_instr_addr) { + memcpy(pair.memref.instr.encoding, &decode_buf[offset], instr_size); + pair.memref.instr.encoding_is_new = true; + } } else if (pair.memref.marker.type == TRACE_TYPE_MARKER && pair.instr != nullptr) { pair.memref.marker.marker_value = instr_get_offset(pair.instr) + base_addr; diff --git a/clients/drcachesim/tests/opcode_mix_test.cpp b/clients/drcachesim/tests/opcode_mix_test.cpp new file mode 100644 index 00000000000..cafde96739c --- /dev/null +++ b/clients/drcachesim/tests/opcode_mix_test.cpp @@ -0,0 +1,156 @@ +/* ********************************************************** + * Copyright (c) 2024 Google, LLC 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 GOOGLE, LLC 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. + */ + +/* Tests for the opcode_mix_t analysis tool. */ + +#include +#include + +#include "decode_cache.h" +#include "tools/opcode_mix.h" +#include "../common/memref.h" +#include "memref_gen.h" + +namespace dynamorio { +namespace drmemtrace { + +class test_opcode_mix_t : public opcode_mix_t { +public: + // Pass a non-nullptr instrlist_t if the module mapper must be used. + test_opcode_mix_t(instrlist_t *instrs) + : opcode_mix_t(/*module_file_path=*/(instrs == nullptr ? "" : "some_file"), + /*verbose=*/0, + /*alt_module_dir=*/"") + , instrs_(instrs) + { + } + std::unordered_map + get_opcode_mix(void *shard) + { + shard_data_t *shard_data = reinterpret_cast(shard); + return shard_data->opcode_counts; + } + +protected: + void + make_decode_cache(shard_data_t *shard, void *dcontext) override + { + shard->decode_cache = std::unique_ptr>( + new test_decode_cache_t(dcontext, false, instrs_)); + } + +private: + instrlist_t *instrs_; +}; + +std::string +check_opcode_mix(void *drcontext, bool use_module_mapper) +{ + static constexpr addr_t BASE_ADDR = 0x123450; + static constexpr addr_t TID_A = 1; + instr_t *nop = XINST_CREATE_nop(drcontext); + instr_t *ret = XINST_CREATE_return(drcontext); + instrlist_t *ilist = instrlist_create(drcontext); + instrlist_append(ilist, nop); + instrlist_append(ilist, ret); + std::vector memref_setup = { + { gen_marker(TID_A, TRACE_MARKER_TYPE_FILETYPE, + OFFLINE_FILE_TYPE_SYSCALL_NUMBERS | + (use_module_mapper ? 0 : OFFLINE_FILE_TYPE_ENCODINGS)), + nullptr }, + { gen_instr(TID_A), nop }, + { gen_instr(TID_A), ret }, + { gen_instr(TID_A), nop }, + }; + std::vector memrefs; + instrlist_t *ilist_for_test = nullptr; + + if (use_module_mapper) { + // This does not set encodings in the memref.instr. + memrefs = add_encodings_to_memrefs(ilist, memref_setup, 0, + /*set_only_instr_addr=*/true); + // We pass the instrs to construct the test_module_mapper_t in the + // test_decode_cache_t; + ilist_for_test = ilist; + } else { + memrefs = add_encodings_to_memrefs(ilist, memref_setup, BASE_ADDR); + // Set up the second nop memref to reuse the same encoding as the first nop. + memrefs[3].instr.encoding_is_new = false; + } + test_opcode_mix_t opcode_mix(ilist_for_test); + opcode_mix.initialize(); + void *shard_data = opcode_mix.parallel_shard_init_stream(0, nullptr, nullptr); + for (const memref_t &memref : memrefs) { + if (!opcode_mix.parallel_shard_memref(shard_data, memref)) { + return opcode_mix.parallel_shard_error(shard_data); + } + } + std::unordered_map mix = opcode_mix.get_opcode_mix(shard_data); + if (mix.size() != 2) { + return "Found incorrect count of opcodes"; + } + int64_t nop_count = mix[instr_get_opcode(nop)]; + if (nop_count != 2) { + return "Found incorrect nop count"; + } + int64_t ret_count = mix[instr_get_opcode(ret)]; + if (ret_count != 1) { + return "Found incorrect ret count"; + } + std::cerr << "check_opcode_mix with use_module_mapper: " << use_module_mapper + << " passed\n"; + return ""; +} + +int +test_main(int argc, const char *argv[]) +{ + void *drcontext = dr_standalone_init(); + std::string err = check_opcode_mix(drcontext, /*use_module_mapper=*/false); + if (err != "") { + std::cerr << err << "\n"; + exit(1); + } +#ifndef WINDOWS + // TODO i#5960: Enable these tests after the test-only Windows issue is + // fixed. + err = check_opcode_mix(drcontext, /*use_module_mapper=*/true); + if (err != "") { + std::cerr << err << "\n"; + exit(1); + } +#endif + return 0; +} + +} // namespace drmemtrace +} // namespace dynamorio diff --git a/clients/drcachesim/tools/common/decode_cache.cpp b/clients/drcachesim/tools/common/decode_cache.cpp new file mode 100644 index 00000000000..0f297af7f53 --- /dev/null +++ b/clients/drcachesim/tools/common/decode_cache.cpp @@ -0,0 +1,160 @@ +/* ********************************************************** + * Copyright (c) 2024 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. + */ + +#include "decode_cache.h" + +namespace dynamorio { +namespace drmemtrace { + +void +decode_info_base_t::set_decode_info( + void *dcontext, const dynamorio::drmemtrace::_memref_instr_t &memref_instr, + instr_t *instr) +{ + set_decode_info_derived(dcontext, memref_instr, instr); + is_valid_ = true; +} + +bool +decode_info_base_t::is_valid() const +{ + return is_valid_; +} + +instr_decode_info_t::~instr_decode_info_t() +{ + if (instr_ != nullptr) { + instr_destroy(dcontext_, instr_); + } +} + +decode_cache_base_t::~decode_cache_base_t() +{ + std::lock_guard guard(module_mapper_mutex_); + if (use_module_mapper_) { + --module_mapper_use_count_; + if (module_mapper_use_count_ == 0) { + // We cannot wait for the static module_mapper_ to be destroyed + // automatically because we want to do it before DR's global resource + // accounting is done at the end. + module_mapper_.reset(nullptr); + delete[] modfile_bytes_; + modfile_bytes_ = nullptr; + } + } +} + +std::string +decode_cache_base_t::init_module_mapper(const std::string &module_file_path, + const std::string &alt_module_dir) +{ + std::lock_guard guard(module_mapper_mutex_); + use_module_mapper_ = true; + ++module_mapper_use_count_; + if (module_mapper_ != nullptr) { + // We want only a single module_mapper_t instance to be + // initialized that is shared among all instances of + // decode_cache_base_t. + return ""; + } + return make_module_mapper(module_file_path, alt_module_dir); +} + +std::string +decode_cache_base_t::make_module_mapper(const std::string &module_file_path, + const std::string &alt_module_dir) +{ + // Legacy trace support where binaries are needed. + // We do not support non-module code for such traces. + file_t modfile; + std::string error = read_module_file(module_file_path, modfile, modfile_bytes_); + if (!error.empty()) { + return "Failed to read module file: " + error; + } + dr_close_file(modfile); + module_mapper_ = + module_mapper_t::create(modfile_bytes_, nullptr, nullptr, nullptr, nullptr, + /*verbose=*/0, alt_module_dir); + module_mapper_->get_loaded_modules(); + error = module_mapper_->get_last_error(); + if (!error.empty()) + return "Failed to load binaries: " + error; + return ""; +} + +std::string +decode_cache_base_t::find_mapped_trace_address(app_pc trace_pc, app_pc &decode_pc) +{ + if (trace_pc >= last_trace_module_start_ && + static_cast(trace_pc - last_trace_module_start_) < + last_mapped_module_size_) { + decode_pc = last_mapped_module_start_ + (trace_pc - last_trace_module_start_); + return ""; + } + std::lock_guard guard(module_mapper_mutex_); + decode_pc = module_mapper_->find_mapped_trace_bounds( + trace_pc, &last_mapped_module_start_, &last_mapped_module_size_); + if (!module_mapper_->get_last_error().empty()) { + last_mapped_module_start_ = nullptr; + last_mapped_module_size_ = 0; + last_trace_module_start_ = nullptr; + return "Failed to find mapped address for " + to_hex_string(trace_pc) + ": " + + module_mapper_->get_last_error(); + } + last_trace_module_start_ = trace_pc - (decode_pc - last_mapped_module_start_); + return ""; +} + +void +instr_decode_info_t::set_decode_info_derived( + void *dcontext, const dynamorio::drmemtrace::_memref_instr_t &memref_instr, + instr_t *instr) +{ + dcontext_ = dcontext; + instr_ = instr; +} + +instr_t * +instr_decode_info_t::get_decoded_instr() +{ + return instr_; +} + +// Must be in cpp and not the header, else the linker will complain about multiple +// definitions. +std::mutex decode_cache_base_t::module_mapper_mutex_; +std::unique_ptr decode_cache_base_t::module_mapper_; +char *decode_cache_base_t::modfile_bytes_ = nullptr; +int decode_cache_base_t::module_mapper_use_count_ = 0; + +} // namespace drmemtrace +} // namespace dynamorio diff --git a/clients/drcachesim/tools/common/decode_cache.h b/clients/drcachesim/tools/common/decode_cache.h new file mode 100644 index 00000000000..5ca9f566bd2 --- /dev/null +++ b/clients/drcachesim/tools/common/decode_cache.h @@ -0,0 +1,469 @@ +/* ********************************************************** + * Copyright (c) 2024 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. + */ + +/** + * decode_cache.h: Library that supports caching of instruction decode + * information. + */ + +#ifndef _DECODE_CACHE_H_ +#define _DECODE_CACHE_H_ 1 + +#include "memref.h" +#include "tracer/raw2trace_shared.h" + +#include +#include +#include + +namespace dynamorio { +namespace drmemtrace { + +/** + * Base class for storing instruction decode info. Users should sub-class this + * base class and implement set_decode_info_derived() to derive and store the decode + * info they need. + */ +class decode_info_base_t { +public: + /** + * Sets the decode info for the provided \p instr which was allocated using the + * provided \p dcontext for the provided \p memref_instr. This is done using + * the set_decode_info_derived() provided by the derived class, which also takes + * ownership of the provided #instr_t if used with a #decode_cache_t object + * constructed with \p persist_decoded_instrs_ set to true. Additionally, + * this does other required bookkeeping. + */ + void + set_decode_info(void *dcontext, + const dynamorio::drmemtrace::_memref_instr_t &memref_instr, + instr_t *instr); + + /** + * Indicates whether set_decode_info() was invoked on the object with a valid + * decoded #instr_t, typically by #decode_cache_t. It won't be valid if the + * object is default-constructed without a subsequent set_decode_info() call. + * When used with #decode_cache_t, this indicates whether an invalid + * instruction was observed at the processed instr record. + */ + bool + is_valid() const; + +private: + /** + * Sets the decoding info fields as required by the derived class, based on the + * provided instr_t which was allocated using the provided opaque \p dcontext + * for the provided \p memref_instr. Derived classes must implement this virtual + * function. Note that this cannot be invoked directly as it is private, but + * only through set_decode_info() which does other required bookkeeping. + * + * This is meant for use with #decode_cache_t, which will invoke + * set_decode_info() for each new decoded instruction. + * + * The responsibility for invoking instr_destroy() on the provided \p instr + * lies with this #decode_info_base_t object, unless + * #decode_cache_t was constructed with \p persist_decoded_instrs_ + * set to false, in which case no heap allocation takes place. + */ + virtual void + set_decode_info_derived(void *dcontext, + const dynamorio::drmemtrace::_memref_instr_t &memref_instr, + instr_t *instr) = 0; + + bool is_valid_ = false; +}; + +/** + * Decode info including the full decoded #instr_t. This should be used with a + * #decode_cache_t constructed with \p persist_decoded_instrs_ set to + * true. + */ +class instr_decode_info_t : public decode_info_base_t { +public: + ~instr_decode_info_t(); + instr_t * + get_decoded_instr(); + +private: + void + set_decode_info_derived(void *dcontext, + const dynamorio::drmemtrace::_memref_instr_t &memref_instr, + instr_t *instr) override; + + // This is owned by the enclosing instr_decode_info_t object and will be + // instr_destroy()-ed in the destructor. + instr_t *instr_ = nullptr; + void *dcontext_ = nullptr; +}; + +/** + * Base class for #decode_cache_t. + * + * This is used to allow sharing the static data members among all template instances + * of #decode_cache_t. + */ +class decode_cache_base_t { +protected: + /** + * Constructor for the base class, intentionally declared as protected so + * #decode_cache_base_t cannot be instantiated directly but only via + * a derived class. + */ + decode_cache_base_t() = default; + virtual ~decode_cache_base_t(); + + static std::mutex module_mapper_mutex_; + static std::unique_ptr module_mapper_; + // XXX: Maybe the ownership and destruction responsibility for the modfile bytes + // should be given to module_mapper_t instead. + static char *modfile_bytes_; + static int module_mapper_use_count_; + + /** + * use_module_mapper_ describes whether we lookup the instr encodings + * from the module map, or alternatively from embedded-encodings in the + * trace. + * + * Note that we store our instr encoding lookup strategy as a non-static + * data member, unlike module_mapper_t which is static and shared between + * all decode_cache_t instances (even of different template types). + * Some analysis tools may deliberately want to look at instr encodings + * from the module mappings, but that strategy does not provide JIT + * encodings which are present only as embedded-encodings in the trace. + * In such a case, other concurrently running analysis tools should still + * be able to see encodings for JIT code. + */ + bool use_module_mapper_ = false; + + /** + * Initializes the module_mapper_ object using make_module_mapper() and performs + * other bookkeeping and prerequisites. + * + * Returns the empty string on success, or an error message. + */ + std::string + init_module_mapper(const std::string &module_file_path, + const std::string &alt_module_dir); + + /** + * Returns in the \p decode_pc reference parameter the address where the encoding + * for the instruction at trace_pc can be found. + * + * Returns the empty string on success, or an error message. + */ + std::string + find_mapped_trace_address(app_pc trace_pc, app_pc &decode_pc); + +private: + /** + * Creates a module_mapper_t. This does not need to worry about races as the + * module_mapper_mutex_ will be acquired before calling. + * Non-static to allow test sub-classes to override. + * + * Returns the empty string on success, or an error message. + */ + virtual std::string + make_module_mapper(const std::string &module_file_path, + const std::string &alt_module_dir); + + // Cached values for the last lookup to the module_mapper_t. These help + // avoid redundant lookups and lock acquisition for consecutive queries + // corresponding to the same application module in the trace. + // Any trace_pc that lies in the range [last_trace_module_start_, + // last_trace_module_start_ + last_mapped_module_size_) can be assumed + // to be mapped to last_mapped_module_start_ + (trace_pc - + // last_trace_module_start_). + + // Address where the last-queried module was mapped to in the traced + // application's address space. + app_pc last_trace_module_start_; + // Address where the last-queried module is mapped to in our current + // address space. + app_pc last_mapped_module_start_; + // Size of the mapping for the last-queried module. + size_t last_mapped_module_size_; +}; + +/** + * A cache to store decode info for instructions per observed app pc. The template arg + * DecodeInfo is a class derived from #decode_info_base_t which implements the + * set_decode_info_derived() function that derives the required decode info from an + * #instr_t object when invoked by #decode_cache_t. This class handles the + * heavy lifting of actually producing the decoded #instr_t. The decoded #instr_t + * may be made to persist beyond the set_decode_info() calls by constructing the + * #decode_cache_t object with \p persist_decoded_instrs_ set to true. + * + * Usage note: after constructing an object, init() must be called. + */ +template class decode_cache_t : public decode_cache_base_t { + static_assert(std::is_base_of::value, + "DecodeInfo not derived from decode_info_base_t"); + +public: + decode_cache_t(void *dcontext, bool persist_decoded_instrs) + : dcontext_(dcontext) + , persist_decoded_instrs_(persist_decoded_instrs) {}; + virtual ~decode_cache_t() + { + } + + /** + * Returns a pointer to the DecodeInfo available for the instruction at \p pc. + * Returns nullptr if no instruction is known at that \p pc. Returns the + * default-constructed DecodeInfo if an instr was seen at that \p pc but there + * was a decoding error for the instruction. + * + * Guaranteed to be non-nullptr and valid if the prior add_decode_info() for + * that \p pc returned no error. + * + * When analyzing #memref_t from a trace, it may be better to just use + * add_decode_info() instead (as it also returns the added DecodeInfo) if + * it's possible that the instr at \p pc may have changed (e.g., JIT code). + */ + DecodeInfo * + get_decode_info(app_pc pc) + { + auto it = decode_cache_.find(pc); + if (it == decode_cache_.end()) { + return nullptr; + } + return &it->second; + } + /** + * Adds decode info for the given \p memref_instr if it is not yet recorded. + * or if it contains a new encoding. + * + * Uses the embedded encodings in the trace or, if init() was invoked with + * a module file path, the encodings from the instantiated + * #module_mapper_t. + * + * If there is a decoding failure, the default-constructed DecodeInfo that + * returns is_valid() = false will be added to the cache. + * + * Returns a pointer to whatever DecodeInfo is present in the cache in the + * \p cached_decode_info reference pointer parameter, or a nullptr if none + * is cached. This helps avoid a repeated lookup in a subsequent + * get_decode_info() call. + * + * Returns the error string, or an empty string if there was no error. A + * valid DecodeInfo is guaranteed to be added to the cache if there was no + * error. + */ + std::string + add_decode_info(const dynamorio::drmemtrace::_memref_instr_t &memref_instr, + DecodeInfo *&cached_decode_info) + { + cached_decode_info = nullptr; + if (!init_done_) { + return "init() must be called first"; + } + const app_pc trace_pc = reinterpret_cast(memref_instr.addr); + auto it_inserted = decode_cache_.emplace( + std::piecewise_construct, std::forward_as_tuple(trace_pc), std::tuple<> {}); + typename std::unordered_map::iterator info = + it_inserted.first; + bool already_exists = !it_inserted.second; + if (already_exists && + // We can return the existing cached DecodeInfo if: + // - we're using the module mapper, where we don't support the + // change-prone JIT encodings; or + // - we're using embedded encodings from the trace, and the + // current instr explicitly says the encoding in the current + // memref_instr isn't new. + (use_module_mapper_ || !memref_instr.encoding_is_new)) { + // We return the cached DecodeInfo even if it is !is_valid(); + // attempting decoding again is not useful because the encoding + // hasn't changed. + cached_decode_info = &info->second; + return ""; + } else if (already_exists) { + // We may end up here if we're using the embedded encodings from + // the trace and now we have a new instr at trace_pc. + info->second = DecodeInfo(); + } + cached_decode_info = &info->second; + instr_t *instr = nullptr; + instr_noalloc_t noalloc; + if (persist_decoded_instrs_) { + instr = instr_create(dcontext_); + } else { + instr_noalloc_init(dcontext_, &noalloc); + instr = instr_from_noalloc(&noalloc); + } + + app_pc decode_pc; + if (!use_module_mapper_) { + decode_pc = const_cast(memref_instr.encoding); + } else { + // Legacy trace support where we need the binaries. + std::string err = find_mapped_trace_address(trace_pc, decode_pc); + if (err != "") + return err; + } + app_pc next_pc = decode_from_copy(dcontext_, decode_pc, trace_pc, instr); + if (next_pc == nullptr || !instr_valid(instr)) { + if (persist_decoded_instrs_) { + instr_destroy(dcontext_, instr); + } + return "decode_from_copy failed"; + } + // Also sets is_valid_. + info->second.set_decode_info(dcontext_, memref_instr, instr); + return ""; + } + + /** + * Performs initialization tasks such as verifying whether the given trace + * indeed has embedded encodings or not, and initializing the + * #module_mapper_t if the module path is provided. + * + * It is important to note that the trace filetype may be obtained using the + * get_filetype() API on a #memtrace_stream_t. However, instances of + * #memtrace_stream_t have the filetype available at init time (before the + * #TRACE_MARKER_TYPE_FILETYPE is actually returned by the stream) only + * for offline analysis. In the online analysis case, the user would need to + * call this API after init time when the #TRACE_MARKER_TYPE_FILETYPE marker + * is seen (which is fine as it occurs before any instr record). + * + * If the \p module_file_path parameter is not empty, it instructs the + * #decode_cache_t object that it should look for the instr encodings in the + * application binaries using a #module_mapper_t. A #module_mapper_t is + * instantiated only one time and reused for all objects + * of #decode_cache_t (of any template type). The user must provide a valid + * \p module_file_path if decoding instructions from a trace that does not + * have embedded instruction encodings in it, as indicated by absence of + * the #OFFLINE_FILE_TYPE_ENCODINGS bit in the #TRACE_MARKER_TYPE_FILETYPE + * marker. The user may provide a \p module_file_path also if they + * deliberately need to use the module mapper instead of the embedded + * encodings. + * + * If the provided \p module_file_path is empty, the cache object uses + * the encodings embedded in the trace records. + */ + std::string + init(offline_file_type_t filetype, const std::string &module_file_path = "", + const std::string &alt_module_dir = "") + { + // If we are dealing with a regdeps trace, we need to set the dcontext + // ISA mode to the correct synthetic ISA (i.e., DR_ISA_REGDEPS). + if (TESTANY(OFFLINE_FILE_TYPE_ARCH_REGDEPS, filetype)) { + // Because isa_mode in dcontext is a global resource, we guard its access to + // avoid data races (even though this is a benign data race, as all threads + // are writing the same isa_mode value). + std::lock_guard guard(dcontext_mutex_); + dr_isa_mode_t isa_mode = dr_get_isa_mode(dcontext_); + if (isa_mode != DR_ISA_REGDEPS) + dr_set_isa_mode(dcontext_, DR_ISA_REGDEPS, nullptr); + } + + if (!TESTANY(OFFLINE_FILE_TYPE_ENCODINGS, filetype) && module_file_path.empty()) { + return "Trace does not have embedded encodings, and no module_file_path " + "provided"; + } + init_done_ = true; + if (module_file_path.empty()) + return ""; + return init_module_mapper(module_file_path, alt_module_dir); + } + + /** + * Clears all cached decode info entries. + * + * Typically analysis tools like to keep their per-shard data around till all shards + * are done processing (so they can combine the shards and use the results), but + * this API optionally allows tools to keep memory consumption in check by clearing + * the decode cache entries in parallel_shard_exit(), since it's very likely that the + * decode cache is not needed for result computation. + * + * This does not affect the state of any initialized module mapper, which is still + * cleaned up during destruction. + */ + void + clear_cache() + { + decode_cache_.clear(); + } + +private: + std::unordered_map decode_cache_; + void *dcontext_ = nullptr; + std::mutex dcontext_mutex_; + bool persist_decoded_instrs_ = false; + + // Describes whether init() was invoked. + // This helps in detecting cases where a module mapper was not specified + // when decoding a trace without embedded encodings. + bool init_done_ = false; +}; + +/** + * A #decode_cache_t for testing which uses a #test_module_mapper_t. + */ +template +class test_decode_cache_t : public decode_cache_t { +public: + // The ilist arg is required only for testing the module_mapper_t + // decoding strategy. + test_decode_cache_t(void *dcontext, bool persist_decoded_instrs, + instrlist_t *ilist_for_test_module_mapper = nullptr) + : decode_cache_t(dcontext, persist_decoded_instrs) + , dcontext_(dcontext) + , ilist_for_test_module_mapper_(ilist_for_test_module_mapper) + { + } + +private: + using decode_cache_base_t::module_mapper_; + + void *dcontext_; + instrlist_t *ilist_for_test_module_mapper_; + + std::string + make_module_mapper(const std::string &unused_module_file_path, + const std::string &unused_alt_module_dir) override + { + if (ilist_for_test_module_mapper_ == nullptr) + return "No ilist to init test_module_mapper_t"; + module_mapper_ = std::unique_ptr( + new test_module_mapper_t(ilist_for_test_module_mapper_, dcontext_)); + module_mapper_->get_loaded_modules(); + std::string error = module_mapper_->get_last_error(); + if (!error.empty()) + return "Failed to load binaries: " + error; + return ""; + } +}; + +} // namespace drmemtrace +} // namespace dynamorio + +#endif /* _INSTR_DECODE_CACHE_H_ */ diff --git a/clients/drcachesim/tools/invariant_checker.cpp b/clients/drcachesim/tools/invariant_checker.cpp index 5759a52498c..8a631772492 100644 --- a/clients/drcachesim/tools/invariant_checker.cpp +++ b/clients/drcachesim/tools/invariant_checker.cpp @@ -159,15 +159,6 @@ invariant_checker_t::parallel_shard_init_stream(int shard_index, void *worker_da std::lock_guard guard(init_mutex_); per_shard->tid_ = shard_stream->get_tid(); shard_map_[shard_index] = std::move(per_shard); - // If we are dealing with an OFFLINE_FILE_TYPE_ARCH_REGDEPS trace, we need to set the - // dcontext ISA mode to the correct synthetic ISA (i.e., DR_ISA_REGDEPS). - uint64_t filetype = shard_stream->get_filetype(); - if (TESTANY(OFFLINE_FILE_TYPE_ARCH_REGDEPS, filetype)) { - dr_isa_mode_t isa_mode = dr_get_isa_mode(drcontext_); - if (isa_mode != DR_ISA_REGDEPS) { - dr_set_isa_mode(drcontext_, DR_ISA_REGDEPS, nullptr); - } - } return res; } @@ -183,6 +174,8 @@ bool invariant_checker_t::parallel_shard_exit(void *shard_data) { per_shard_t *shard = reinterpret_cast(shard_data); + if (shard->decode_cache_ != nullptr) + shard->decode_cache_->clear_cache(); report_if_false(shard, shard->saw_thread_exit_ // XXX i#6733: For online we sometimes see threads @@ -233,7 +226,7 @@ invariant_checker_t::relax_expected_write_count_check_for_kernel(per_shard_t *sh // XXX: Perhaps we can query the expected size using cpuid and add it as a // marker in the system call trace template, and then adjust it according to the // cpuid value for the trace being injected into. - shard->prev_instr_.decoding.is_xsave; + shard->prev_instr_.decoding.is_xsave_; return relax; } @@ -250,20 +243,20 @@ invariant_checker_t::relax_expected_read_count_check_for_kernel(per_shard_t *sha // difference in iret behavior can be detected in the decoder and perhaps be // accounted for here in a way better than relying on // between_kernel_syscall_trace_markers_. - shard->prev_instr_.decoding.opcode == OP_iret + shard->prev_instr_.decoding.opcode_ == OP_iret // Xrstor does multiple reads. Read count cannot be determined using the // decoder. System call trace templates collected on QEMU would show all reads. // XXX: Perhaps we can query the expected size using cpuid and add it as a // marker in the system call trace template, and then adjust it according to the // cpuid value for the trace being injected into. - || shard->prev_instr_.decoding.is_xrstor + || shard->prev_instr_.decoding.is_xrstor_ // xsave variants also read some fields from the xsave header // (https://www.felixcloutier.com/x86/xsaveopt). // XXX, i#6769: Same as above, can we store any metadata in the trace to allow // us to adapt the decoder to expect this? - || shard->prev_instr_.decoding.is_xsave; + || shard->prev_instr_.decoding.is_xsave_; return relax; } #endif @@ -493,8 +486,8 @@ invariant_checker_t::parallel_shard_memref(void *shard_data, const memref_t &mem // TODO i#5949: For WOW64 instr_is_syscall() always returns false here as it // tries to check adjacent instrs; we disable this check until that is solved. #if !defined(WINDOWS) || defined(X64) - if (shard->prev_instr_.decoding.has_valid_decoding) { - report_if_false(shard, shard->prev_instr_.decoding.is_syscall, + if (shard->prev_instr_.decoding.is_valid()) { + report_if_false(shard, shard->prev_instr_.decoding.is_syscall_, "Syscall marker not placed after syscall instruction"); } #endif @@ -594,7 +587,7 @@ invariant_checker_t::parallel_shard_memref(void *shard_data, const memref_t &mem "Mismatching syscall num in trace start and syscall marker"); report_if_false(shard, !TESTANY(OFFLINE_FILE_TYPE_ENCODINGS, shard->file_type_) || - shard->prev_instr_.decoding.is_syscall, + shard->prev_instr_.decoding.is_syscall_, "prev_instr at syscall trace start is not a syscall"); } shard->pre_syscall_trace_instr_ = shard->prev_instr_; @@ -779,80 +772,36 @@ invariant_checker_t::parallel_shard_memref(void *shard_data, const memref_t &mem const bool expect_encoding = TESTANY(OFFLINE_FILE_TYPE_ENCODINGS, shard->file_type_); if (expect_encoding) { - const app_pc trace_pc = reinterpret_cast(memref.instr.addr); - // Clear cache entry for new encodings. - if (memref.instr.encoding_is_new) { - shard->decode_cache_.erase(trace_pc); + if (shard->decode_cache_ == nullptr) { + shard->decode_cache_ = + std::unique_ptr>( + new decode_cache_t( + drcontext_, + /*persist_decoded_instrs=*/false)); + shard->error_ = shard->decode_cache_->init(shard->file_type_); + if (shard->error_ != "") + return false; } - auto cached = shard->decode_cache_.find(trace_pc); - if (cached != shard->decode_cache_.end()) { - cur_instr_info.decoding = cached->second; - } else { - instr_noalloc_t noalloc; - instr_noalloc_init(drcontext_, &noalloc); - instr_t *noalloc_instr = instr_from_noalloc(&noalloc); - app_pc next_pc = decode_from_copy( - drcontext_, const_cast(memref.instr.encoding), trace_pc, - noalloc_instr); - // Add decoding attributes to cur_instr_info. - if (next_pc != nullptr) { - cur_instr_info.decoding.has_valid_decoding = true; - cur_instr_info.decoding.is_prefetch = - instr_is_prefetch(noalloc_instr); - cur_instr_info.decoding.opcode = instr_get_opcode(noalloc_instr); -#ifdef X86 - cur_instr_info.decoding.is_xsave = instr_is_xsave(noalloc_instr); - cur_instr_info.decoding.is_xrstor = instr_is_xrstor(noalloc_instr); -#endif - cur_instr_info.decoding.is_syscall = instr_is_syscall(noalloc_instr); - cur_instr_info.decoding.writes_memory = - instr_writes_memory(noalloc_instr); - cur_instr_info.decoding.is_predicated = - instr_is_predicated(noalloc_instr); - // DR_ISA_REGDEPS instructions don't have an opcode, hence we use - // their category to determine whether they perform at least one read - // or write. - if (instr_get_isa_mode(noalloc_instr) == DR_ISA_REGDEPS) { - cur_instr_info.decoding.num_memory_read_access = - TESTANY(DR_INSTR_CATEGORY_LOAD, - instr_get_category(noalloc_instr)) - ? 1 - : 0; - cur_instr_info.decoding.num_memory_write_access = - TESTANY(DR_INSTR_CATEGORY_STORE, - instr_get_category(noalloc_instr)) - ? 1 - : 0; - } else { - cur_instr_info.decoding.num_memory_read_access = - instr_num_memory_read_access(noalloc_instr); - cur_instr_info.decoding.num_memory_write_access = - instr_num_memory_write_access(noalloc_instr); - } - if (type_is_instr_branch(memref.instr.type) && - // DR_ISA_REGDEPS instructions don't have a branch target saved as - // instr.src[0], so we cannot retrieve this information. - !TESTANY(OFFLINE_FILE_TYPE_ARCH_REGDEPS, shard->file_type_)) { - const opnd_t target = instr_get_target(noalloc_instr); - if (opnd_is_pc(target)) { - cur_instr_info.decoding.branch_target = - reinterpret_cast(opnd_get_pc(target)); - } - } - } - shard->decode_cache_[trace_pc] = cur_instr_info.decoding; + per_shard_t::decoding_info_t *decode_info; + shard->error_ = + shard->decode_cache_->add_decode_info(memref.instr, decode_info); + if (shard->error_ != "") { + return false; } + // The decode_info here will never be nullptr since we return + // early if the prior add_decode_info returned an error. + cur_instr_info.decoding = *decode_info; #ifdef X86 - if (cur_instr_info.decoding.opcode == OP_sti) + if (cur_instr_info.decoding.opcode_ == OP_sti) shard->instrs_since_sti = 0; else ++shard->instrs_since_sti; #endif if (TESTANY(OFFLINE_FILE_TYPE_SYSCALL_NUMBERS, shard->file_type_) && - cur_instr_info.decoding.is_syscall) + cur_instr_info.decoding.is_syscall_) shard->expect_syscall_marker_ = true; - if (cur_instr_info.decoding.has_valid_decoding && - !cur_instr_info.decoding.is_predicated && + if (cur_instr_info.decoding.is_valid() && + !cur_instr_info.decoding.is_predicated_ && !TESTANY(OFFLINE_FILE_TYPE_FILTERED | OFFLINE_FILE_TYPE_DFILTERED, shard->file_type_)) { // Verify the number of read/write records matches the last @@ -865,7 +814,7 @@ invariant_checker_t::parallel_shard_memref(void *shard_data, const memref_t &mem // memory access in system call trace templates // collected on QEMU. (shard->between_kernel_syscall_trace_markers_ && - shard->prev_instr_.decoding.is_prefetch)) || + shard->prev_instr_.decoding.is_prefetch_)) || // We cannot check for is_predicated in // OFFLINE_FILE_TYPE_ARCH_REGDEPS traces (it's always false), so // we disable this check. @@ -885,9 +834,9 @@ invariant_checker_t::parallel_shard_memref(void *shard_data, const memref_t &mem TESTANY(OFFLINE_FILE_TYPE_KERNEL_SYSCALL_INSTR_ONLY, shard->file_type_))) { shard->expected_read_records_ = - cur_instr_info.decoding.num_memory_read_access; + cur_instr_info.decoding.num_memory_read_access_; shard->expected_write_records_ = - cur_instr_info.decoding.num_memory_write_access; + cur_instr_info.decoding.num_memory_write_access_; } } } @@ -1201,11 +1150,10 @@ invariant_checker_t::parallel_shard_memref(void *shard_data, const memref_t &mem if (type_is_instr_branch(shard->prev_entry_.instr.type)) shard->last_branch_ = shard->prev_entry_; - if (type_is_data(memref.data.type) && - shard->prev_instr_.decoding.has_valid_decoding) { + if (type_is_data(memref.data.type) && shard->prev_instr_.decoding.is_valid()) { // If the instruction is predicated, the check is skipped since we do // not have the data to determine how many memory accesses to expect. - if (!shard->prev_instr_.decoding.is_predicated && + if (!shard->prev_instr_.decoding.is_predicated_ && !TESTANY(OFFLINE_FILE_TYPE_FILTERED | OFFLINE_FILE_TYPE_DFILTERED, shard->file_type_)) { if (type_is_read(memref.data.type)) { @@ -1221,8 +1169,8 @@ invariant_checker_t::parallel_shard_memref(void *shard_data, const memref_t &mem // the exact number of loads a DR_ISA_REGDEPS instruction performs. // If a DR_ISA_REGDEPS instruction has a DR_INSTR_CATEGORY_LOAD among // its categories, we simply set - // cur_instr_info.decoding.num_memory_read_access to 1. We rely on the - // next instruction to re-set shard->expected_read_records_ + // cur_instr_info.decoding.num_memory_read_access_ to 1. We rely on + // the next instruction to re-set shard->expected_read_records_ // accordingly. !TESTANY(OFFLINE_FILE_TYPE_ARCH_REGDEPS, shard->file_type_)) { shard->expected_read_records_--; @@ -1240,7 +1188,7 @@ invariant_checker_t::parallel_shard_memref(void *shard_data, const memref_t &mem // the exact number of stores a DR_ISA_REGDEPS instruction performs. // If a DR_ISA_REGDEPS instruction has a DR_INSTR_CATEGORY_STORE among // its categories, we simply set - // cur_instr_info.decoding.num_memory_write_access to 1. We rely on + // cur_instr_info.decoding.num_memory_write_access_ to 1. We rely on // the next instruction to re-set shard->expected_write_records_ // accordingly. !TESTANY(OFFLINE_FILE_TYPE_ARCH_REGDEPS, shard->file_type_)) { @@ -1453,14 +1401,14 @@ invariant_checker_t::check_for_pc_discontinuity( } // We do not bother to support legacy traces without encodings. if (expect_encoding && type_is_instr_direct_branch(prev_instr.instr.type)) { - if (!prev_instr_info.decoding.has_valid_decoding) { + if (!prev_instr_info.decoding.is_valid()) { // Neither condition should happen but they could on an invalid // encoding from raw2trace or the reader so we report an // invariant rather than asserting. report_if_false(shard, false, "Branch target is not decodeable"); } else { have_branch_target = true; - branch_target = prev_instr_info.decoding.branch_target; + branch_target = prev_instr_info.decoding.branch_target_; } } // Check for all valid transitions except taken branches. We consider taken @@ -1524,7 +1472,7 @@ invariant_checker_t::check_for_pc_discontinuity( (shard->between_kernel_syscall_trace_markers_ && // hlt suspends processing until the next interrupt, which may be // handled at a discontinuous PC. - (shard->prev_instr_.decoding.opcode == OP_hlt || + (shard->prev_instr_.decoding.opcode_ == OP_hlt || // After interrupts are enabled, we may be interrupted. The tolerance // of 2-instrs was found empirically on some x86 QEMU system call // trace templates. @@ -1553,13 +1501,14 @@ invariant_checker_t::check_for_pc_discontinuity( !TESTANY(OFFLINE_FILE_TYPE_ARCH_REGDEPS, shard->file_type_)) { error_msg = "Branch does not go to the correct target"; } - } else if (cur_memref_info.decoding.has_valid_decoding && - prev_instr_info.decoding.has_valid_decoding && - cur_memref_info.decoding.is_syscall && cur_pc == prev_instr_trace_pc && - prev_instr_info.decoding.is_syscall) { + } else if (cur_memref_info.decoding.is_valid() && + prev_instr_info.decoding.is_valid() && + cur_memref_info.decoding.is_syscall_ && + cur_pc == prev_instr_trace_pc && + prev_instr_info.decoding.is_syscall_) { error_msg = "Duplicate syscall instrs with the same PC"; - } else if (prev_instr_info.decoding.has_valid_decoding && - prev_instr_info.decoding.writes_memory && + } else if (prev_instr_info.decoding.is_valid() && + prev_instr_info.decoding.writes_memory_ && type_is_instr_conditional_branch(shard->last_branch_.instr.type)) { // This sequence happens when an rseq side exit occurs which // results in missing instruction in the basic block. @@ -1725,5 +1674,42 @@ invariant_checker_t::check_regdeps_invariants(per_shard_t *shard, const memref_t } } +void +invariant_checker_t::per_shard_t::decoding_info_t::set_decode_info_derived( + void *dcontext, const dynamorio::drmemtrace::_memref_instr_t &memref_instr, + instr_t *instr) +{ + is_prefetch_ = instr_is_prefetch(instr); + opcode_ = instr_get_opcode(instr); +#ifdef X86 + is_xsave_ = instr_is_xsave(instr); + is_xrstor_ = instr_is_xrstor(instr); +#endif + is_syscall_ = instr_is_syscall(instr); + writes_memory_ = instr_writes_memory(instr); + is_predicated_ = instr_is_predicated(instr); + if (instr_get_isa_mode(instr) == DR_ISA_REGDEPS) { + // DR_ISA_REGDEPS instructions don't have an opcode, hence we use + // their category to determine whether they perform at least one read + // or write. + num_memory_read_access_ = + TESTANY(DR_INSTR_CATEGORY_LOAD, instr_get_category(instr)) ? 1 : 0; + num_memory_write_access_ = + TESTANY(DR_INSTR_CATEGORY_STORE, instr_get_category(instr)) ? 1 : 0; + // DR_ISA_REGDEPS instructions don't have a branch target saved as + // instr.src[0], so we cannot retrieve this information. + branch_target_ = 0; + } else { + num_memory_read_access_ = instr_num_memory_read_access(instr); + num_memory_write_access_ = instr_num_memory_write_access(instr); + if (type_is_instr_branch(memref_instr.type)) { + const opnd_t target = instr_get_target(instr); + if (opnd_is_pc(target)) { + branch_target_ = reinterpret_cast(opnd_get_pc(target)); + } + } + } +} + } // namespace drmemtrace } // namespace dynamorio diff --git a/clients/drcachesim/tools/invariant_checker.h b/clients/drcachesim/tools/invariant_checker.h index a3192b117f7..5027adc02b4 100644 --- a/clients/drcachesim/tools/invariant_checker.h +++ b/clients/drcachesim/tools/invariant_checker.h @@ -49,6 +49,7 @@ #include "analysis_tool.h" #include "dr_api.h" +#include "decode_cache.h" #include "memref.h" #include "memtrace_stream.h" #include "schedule_file.h" @@ -140,26 +141,35 @@ class invariant_checker_t : public analysis_tool_t { #ifdef X86 uint64_t instrs_since_sti = 0; #endif - struct decoding_info_t { - bool has_valid_decoding = false; - bool is_syscall = false; - bool writes_memory = false; - bool is_predicated = false; - uint num_memory_read_access = 0; - uint num_memory_write_access = 0; - addr_t branch_target = 0; - bool is_prefetch = false; - int opcode = 0; + class decoding_info_t : public decode_info_base_t { + public: + bool is_syscall_ = false; + bool writes_memory_ = false; + bool is_predicated_ = false; + uint num_memory_read_access_ = 0; + uint num_memory_write_access_ = 0; + addr_t branch_target_ = 0; + bool is_prefetch_ = false; + int opcode_ = 0; #ifdef X86 - bool is_xsave = false; - bool is_xrstor = false; + bool is_xsave_ = false; + bool is_xrstor_ = false; #endif + private: + void + set_decode_info_derived( + void *dcontext, + const dynamorio::drmemtrace::_memref_instr_t &memref_instr, + instr_t *instr) override; }; struct instr_info_t { memref_t memref = {}; + // We let this stay the default if we are unable to get decoding info for + // the instruction. The data member defaults and is_valid() allow + // simplifying various conditional checks. decoding_info_t decoding; }; - std::unordered_map decode_cache_; + std::unique_ptr> decode_cache_; // On UNIX generally last_instr_in_cur_context_ should be used instead. instr_info_t prev_instr_; #ifdef UNIX diff --git a/clients/drcachesim/tools/opcode_mix.cpp b/clients/drcachesim/tools/opcode_mix.cpp index 6ff25819d31..07b84529cc8 100644 --- a/clients/drcachesim/tools/opcode_mix.cpp +++ b/clients/drcachesim/tools/opcode_mix.cpp @@ -81,30 +81,35 @@ opcode_mix_t::opcode_mix_t(const std::string &module_file_path, unsigned int ver { } +void +opcode_mix_t::make_decode_cache(shard_data_t *shard, void *dcontext) +{ + shard->decode_cache = std::unique_ptr>( + new decode_cache_t(dcontext, + /*persist_decoded_instrs=*/false)); +} + +std::string +opcode_mix_t::init_decode_cache(shard_data_t *shard, void *dcontext, + offline_file_type_t filetype) +{ + make_decode_cache(shard, dcontext); + std::string err; + if (!TESTANY(OFFLINE_FILE_TYPE_ENCODINGS, filetype)) { + err = + shard->decode_cache->init(filetype, module_file_path_, knob_alt_module_dir_); + } else { + err = shard->decode_cache->init(filetype); + } + if (err != "") + return err; + return ""; +} + std::string opcode_mix_t::initialize() { - serial_shard_.worker = &serial_worker_; 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. - file_t modfile; - std::string error = read_module_file(module_file_path_, modfile, modfile_bytes_); - if (!error.empty()) { - return "Failed to read module file: " + error; - } - module_mapper_ = - module_mapper_t::create(modfile_bytes_, nullptr, nullptr, nullptr, nullptr, - knob_verbose_, knob_alt_module_dir_); - module_mapper_->get_loaded_modules(); - dr_close_file(modfile); - error = module_mapper_->get_last_error(); - if (!error.empty()) - return "Failed to load binaries: " + error; return ""; } @@ -125,25 +130,11 @@ opcode_mix_t::parallel_shard_supported() } void * -opcode_mix_t::parallel_worker_init(int worker_index) -{ - auto worker = new worker_data_t; - return reinterpret_cast(worker); -} - -std::string -opcode_mix_t::parallel_worker_exit(void *worker_data) -{ - worker_data_t *worker = reinterpret_cast(worker_data); - delete worker; - return ""; -} - -void * -opcode_mix_t::parallel_shard_init(int shard_index, void *worker_data) +opcode_mix_t::parallel_shard_init_stream( + int shard_index, void *worker_data, + dynamorio::drmemtrace::memtrace_stream_t *shard_stream) { - worker_data_t *worker = reinterpret_cast(worker_data); - auto shard = new shard_data_t(worker); + auto shard = new shard_data_t(); std::lock_guard guard(shard_map_mutex_); shard_map_[shard_index] = shard; return reinterpret_cast(shard); @@ -152,7 +143,10 @@ opcode_mix_t::parallel_shard_init(int shard_index, void *worker_data) bool opcode_mix_t::parallel_shard_exit(void *shard_data) { - // Nothing (we read the shard data in print_results). + shard_data_t *shard = reinterpret_cast(shard_data); + if (shard->decode_cache != nullptr) + shard->decode_cache->clear_cache(); + // We still need the remaining shard data in print_results. return true; } @@ -175,19 +169,6 @@ opcode_mix_t::parallel_shard_memref(void *shard_data, const memref_t &memref) " but tool built for " + trace_arch_string(build_target_arch_type()); return false; } - /* If we are dealing with a regdeps trace, we need to set the dcontext ISA mode - * to the correct synthetic ISA (i.e., DR_ISA_REGDEPS). - */ - if (TESTANY(OFFLINE_FILE_TYPE_ARCH_REGDEPS, memref.marker.marker_value)) { - /* Because isa_mode in dcontext is a global resource, we guard its access to - * avoid data races (even though this is a benign data race, as all threads - * are writing the same isa_mode value). - */ - std::lock_guard guard(dcontext_mutex_); - dr_isa_mode_t isa_mode = dr_get_isa_mode(dcontext_.dcontext); - if (isa_mode != DR_ISA_REGDEPS) - dr_set_isa_mode(dcontext_.dcontext, DR_ISA_REGDEPS, nullptr); - } } else if (memref.marker.type == TRACE_TYPE_MARKER && memref.marker.marker_type == TRACE_MARKER_TYPE_VECTOR_LENGTH) { #ifdef AARCH64 @@ -222,72 +203,24 @@ opcode_mix_t::parallel_shard_memref(void *shard_data, const memref_t &memref) if (shard->filetype == OFFLINE_FILE_TYPE_DEFAULT) { shard->error = "No file type found in this shard"; return false; - } - - ++shard->instr_count; - - app_pc decode_pc; - const app_pc trace_pc = reinterpret_cast(memref.instr.addr); - if (TESTANY(OFFLINE_FILE_TYPE_ENCODINGS, shard->filetype)) { - // The trace has instruction encodings inside it. - decode_pc = const_cast(memref.instr.encoding); - if (memref.instr.encoding_is_new) { - // The code may have changed: invalidate the cache. - shard->worker->opcode_data_cache.erase(trace_pc); - } - } else { - // Legacy trace support where we need the binaries. - if (!module_mapper_) { - shard->error = - "Module file path is missing and trace has no embedded encodings"; + } else if (shard->decode_cache == nullptr) { + std::string err = init_decode_cache(shard, dcontext_.dcontext, shard->filetype); + if (err != "") { + shard->error = err; return false; } - if (trace_pc >= shard->last_trace_module_start && - static_cast(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 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; - uint category; - auto cached_opcode_category = shard->worker->opcode_data_cache.find(trace_pc); - if (cached_opcode_category != shard->worker->opcode_data_cache.end()) { - opcode = cached_opcode_category->second.opcode; - category = cached_opcode_category->second.category; - } else { - instr_t instr; - instr_init(dcontext_.dcontext, &instr); - app_pc next_pc = - decode_from_copy(dcontext_.dcontext, decode_pc, trace_pc, &instr); - if (next_pc == NULL || !instr_valid(&instr)) { - instr_free(dcontext_.dcontext, &instr); - shard->error = - "Failed to decode instruction " + to_hex_string(memref.instr.addr); - return false; - } - opcode = instr_get_opcode(&instr); - category = instr_get_category(&instr); - shard->worker->opcode_data_cache[trace_pc] = opcode_data_t(opcode, category); - instr_free(dcontext_.dcontext, &instr); + + ++shard->instr_count; + opcode_data_t *opcode_data; + shard->error = shard->decode_cache->add_decode_info(memref.instr, opcode_data); + if (shard->error != "") { + return false; } - ++shard->opcode_counts[opcode]; - ++shard->category_counts[category]; + // The opcode_data here will never be nullptr since we return + // early if the prior add_decode_info returned an error. + ++shard->opcode_counts[opcode_data->opcode_]; + ++shard->category_counts[opcode_data->category_]; return true; } @@ -347,34 +280,38 @@ opcode_mix_t::get_category_names(uint category) bool opcode_mix_t::print_results() { - shard_data_t total(0); + shard_data_t aggregated; + shard_data_t *total = &aggregated; if (shard_map_.empty()) { - total = serial_shard_; + // No default copy constructor for shard_data_t because of the + // std::unique_ptr, so we resort to keeping a pointer to it. + total = &serial_shard_; } else { for (const auto &shard : shard_map_) { - total.instr_count += shard.second->instr_count; + aggregated.instr_count += shard.second->instr_count; for (const auto &keyvals : shard.second->opcode_counts) { - total.opcode_counts[keyvals.first] += keyvals.second; + aggregated.opcode_counts[keyvals.first] += keyvals.second; } for (const auto &keyvals : shard.second->category_counts) { - total.category_counts[keyvals.first] += keyvals.second; + aggregated.category_counts[keyvals.first] += keyvals.second; } } } std::cerr << TOOL_NAME << " results:\n"; - std::cerr << std::setw(15) << total.instr_count << " : total executed instructions\n"; - std::vector> sorted(total.opcode_counts.begin(), - total.opcode_counts.end()); + std::cerr << std::setw(15) << total->instr_count + << " : total executed instructions\n"; + std::vector> sorted(total->opcode_counts.begin(), + total->opcode_counts.end()); std::sort(sorted.begin(), sorted.end(), cmp_val); for (const auto &keyvals : sorted) { std::cerr << std::setw(15) << keyvals.second << " : " << std::setw(9) << decode_opcode_name(keyvals.first) << "\n"; } std::cerr << "\n"; - std::cerr << std::setw(15) << total.category_counts.size() + std::cerr << std::setw(15) << total->category_counts.size() << " : sets of categories\n"; std::vector> sorted_category_counts( - total.category_counts.begin(), total.category_counts.end()); + total->category_counts.begin(), total->category_counts.end()); std::sort(sorted_category_counts.begin(), sorted_category_counts.end(), cmp_val); for (const auto &keyvals : sorted_category_counts) { std::cerr << std::setw(15) << keyvals.second << " : " << std::setw(9) @@ -490,5 +427,14 @@ opcode_mix_t::release_interval_snapshot(interval_state_snapshot_t *interval_snap return true; } +void +opcode_mix_t::opcode_data_t::set_decode_info_derived( + void *dcontext, const dynamorio::drmemtrace::_memref_instr_t &memref_instr, + instr_t *instr) +{ + opcode_ = instr_get_opcode(instr); + category_ = instr_get_category(instr); +} + } // namespace drmemtrace } // namespace dynamorio diff --git a/clients/drcachesim/tools/opcode_mix.h b/clients/drcachesim/tools/opcode_mix.h index e07e7997b81..e5bef81f6df 100644 --- a/clients/drcachesim/tools/opcode_mix.h +++ b/clients/drcachesim/tools/opcode_mix.h @@ -44,6 +44,7 @@ #include "dr_api.h" // Must be before trace_entry.h from analysis_tool.h. #include "analysis_tool.h" +#include "decode_cache.h" #include "memref.h" #include "raw2trace.h" #include "trace_entry.h" @@ -69,11 +70,9 @@ class opcode_mix_t : public analysis_tool_t { bool parallel_shard_supported() override; void * - parallel_worker_init(int worker_index) override; - std::string - parallel_worker_exit(void *worker_data) override; - void * - parallel_shard_init(int shard_index, void *worker_data) override; + parallel_shard_init_stream( + int shard_index, void *worker_data, + dynamorio::drmemtrace::memtrace_stream_t *shard_stream) override; bool parallel_shard_exit(void *shard_data) override; bool @@ -105,25 +104,32 @@ class opcode_mix_t : public analysis_tool_t { std::string get_category_names(uint category); - struct opcode_data_t { + class opcode_data_t : public decode_info_base_t { + public: opcode_data_t() - : opcode(OP_INVALID) - , category(DR_INSTR_CATEGORY_UNCATEGORIZED) + : opcode_(OP_INVALID) + , category_(DR_INSTR_CATEGORY_UNCATEGORIZED) { } opcode_data_t(int opcode, uint category) - : opcode(opcode) - , category(category) + : opcode_(opcode) + , category_(category) { } - int opcode; + int opcode_; /* * The category field is a uint instead of a dr_instr_category_t because * multiple category bits can be set when an instruction belongs to more * than one category. We assume 32 bits (i.e., 32 categories) is enough * to be future-proof. */ - uint category; + uint category_; + + private: + void + set_decode_info_derived( + void *dcontext, const dynamorio::drmemtrace::_memref_instr_t &memref_instr, + instr_t *instr) override; }; class snapshot_t : public interval_state_snapshot_t { @@ -134,25 +140,15 @@ class opcode_mix_t : public analysis_tool_t { std::unordered_map category_counts_; }; - struct worker_data_t { - std::unordered_map opcode_data_cache; - }; - struct shard_data_t { shard_data_t() - : worker(nullptr) - , instr_count(0) - { - } - shard_data_t(worker_data_t *worker) - : worker(worker) - , instr_count(0) + : instr_count(0) , last_trace_module_start(nullptr) , last_trace_module_size(0) , last_mapped_module_start(nullptr) { } - worker_data_t *worker; + int64_t instr_count; std::unordered_map opcode_counts; std::unordered_map category_counts; @@ -160,6 +156,7 @@ class opcode_mix_t : public analysis_tool_t { app_pc last_trace_module_start; size_t last_trace_module_size; app_pc last_mapped_module_start; + std::unique_ptr> decode_cache; offline_file_type_t filetype = OFFLINE_FILE_TYPE_DEFAULT; }; @@ -173,6 +170,12 @@ class opcode_mix_t : public analysis_tool_t { void *dcontext = nullptr; }; + virtual void + make_decode_cache(shard_data_t *shard, void *dcontext); + + std::string + init_decode_cache(shard_data_t *shard, void *dcontext, offline_file_type_t filetype); + /* We make this the first field so that dr_standalone_exit() is called after * destroying the other fields which may use DR heap. */ @@ -196,7 +199,6 @@ class opcode_mix_t : public analysis_tool_t { std::string knob_alt_module_dir_; static const std::string TOOL_NAME; // For serial operation. - worker_data_t serial_worker_; shard_data_t serial_shard_; // To guard the setting of isa_mode in dcontext. std::mutex dcontext_mutex_; diff --git a/clients/drcachesim/tools/view.cpp b/clients/drcachesim/tools/view.cpp index 264b2797f78..42520bc4841 100644 --- a/clients/drcachesim/tools/view.cpp +++ b/clients/drcachesim/tools/view.cpp @@ -591,6 +591,11 @@ view_t::parallel_shard_memref(void *shard_data, const memref_t &memref) return true; } + // XXX: We could potentially use instr_decode_cache_t here (i#7113) and avoid the + // repeated instr decoding logic. However, we want to preserve the legacy view + // tool output format which uses disassemble_to_buffer, and disassemble_to_buffer + // does the decoding on its own internally, while adding a bunch of non-trival + // stuff to the output string. app_pc decode_pc; const app_pc orig_pc = (app_pc)memref.instr.addr; if (TESTANY(OFFLINE_FILE_TYPE_ENCODINGS, filetype_)) {