| /* ********************************************************** |
| * Copyright (c) 2022-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 GOOGLE, 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. |
| */ |
| |
| /* Unit tests for the record_filter analyzer. */ |
| |
| #include "analyzer.h" |
| #include "archive_ostream.h" |
| #include "dr_api.h" |
| #include "droption.h" |
| #include "tools/basic_counts.h" |
| #include "tools/filter/null_filter.h" |
| #include "tools/filter/cache_filter.h" |
| #include "tools/filter/record_filter.h" |
| #include "tools/filter/trim_filter.h" |
| #include "tools/filter/type_filter.h" |
| #include "tools/filter/encodings2regdeps_filter.h" |
| #include "tools/filter/func_id_filter.h" |
| #include "trace_entry.h" |
| #include "zipfile_ostream.h" |
| |
| #include <cstdint> |
| #include <inttypes.h> |
| #include <fstream> |
| #include <set> |
| #include <vector> |
| |
| namespace dynamorio { |
| namespace drmemtrace { |
| |
| using record_filter_func_t = |
| ::dynamorio::drmemtrace::record_filter_t::record_filter_func_t; |
| using ::dynamorio::droption::droption_parser_t; |
| using ::dynamorio::droption::DROPTION_SCOPE_ALL; |
| using ::dynamorio::droption::DROPTION_SCOPE_FRONTEND; |
| using ::dynamorio::droption::droption_t; |
| |
| #define FATAL_ERROR(msg, ...) \ |
| do { \ |
| fprintf(stderr, "ERROR: " msg "\n", ##__VA_ARGS__); \ |
| fflush(stderr); \ |
| exit(1); \ |
| } while (0) |
| |
| #define CHECK(cond, msg, ...) \ |
| do { \ |
| if (!(cond)) { \ |
| fprintf(stderr, "%s\n", msg); \ |
| return false; \ |
| } \ |
| } while (0) |
| |
| static droption_t<std::string> |
| op_trace_dir(DROPTION_SCOPE_FRONTEND, "trace_dir", "", |
| "[Required] Trace input directory", |
| "Specifies the directory containing the trace files to be filtered."); |
| |
| static droption_t<std::string> op_tmp_output_dir( |
| DROPTION_SCOPE_FRONTEND, "tmp_output_dir", "", |
| "[Required] Output directory for the filtered trace", |
| "Specifies the directory where the filtered trace will be written."); |
| |
| class test_record_filter_t : public dynamorio::drmemtrace::record_filter_t { |
| public: |
| test_record_filter_t(std::vector<std::unique_ptr<record_filter_func_t>> filters, |
| uint64_t last_timestamp, bool write_archive = false) |
| : record_filter_t("", std::move(filters), last_timestamp, |
| /*verbose=*/0) |
| , write_archive_(write_archive) |
| { |
| } |
| std::vector<trace_entry_t> |
| get_output_entries() |
| { |
| return output_; |
| } |
| |
| protected: |
| bool |
| write_trace_entry(dynamorio::drmemtrace::record_filter_t::per_shard_t *shard, |
| const trace_entry_t &entry) override |
| { |
| output_.push_back(entry); |
| shard->cur_refs += shard->memref_counter.entry_memref_count(&entry); |
| shard->last_written_record = entry; |
| return true; |
| } |
| std::string |
| get_writer(per_shard_t *per_shard, memtrace_stream_t *shard_stream) override |
| { |
| if (write_archive_) { |
| per_shard->archive_writer = |
| std::unique_ptr<archive_ostream_t>(new zipfile_ostream_t("/dev/null")); |
| per_shard->writer = per_shard->archive_writer.get(); |
| return open_new_chunk(per_shard); |
| } |
| per_shard->file_writer = |
| std::unique_ptr<std::ostream>(new std::ofstream("/dev/null")); |
| per_shard->writer = per_shard->file_writer.get(); |
| return ""; |
| } |
| std::string |
| remove_output_file(per_shard_t *per_shard) override |
| { |
| output_.clear(); |
| return ""; |
| } |
| |
| private: |
| std::vector<trace_entry_t> output_; |
| bool write_archive_ = false; |
| }; |
| |
| class local_stream_t : public default_memtrace_stream_t { |
| public: |
| uint64_t |
| get_last_timestamp() const override |
| { |
| return last_timestamp_; |
| } |
| void |
| set_last_timestamp(uint64_t last_timestamp) |
| { |
| last_timestamp_ = last_timestamp; |
| } |
| int64_t |
| get_input_id() const override |
| { |
| // Just one input for our tests. |
| return 0; |
| } |
| |
| private: |
| uint64_t last_timestamp_; |
| }; |
| |
| struct test_case_t { |
| trace_entry_t entry; |
| // Specifies whether the entry should be processed by the record_filter |
| // as an input. Some entries are added only to show the expected output |
| // and shouldn't be used as input to the record_filter. |
| bool input; |
| // Specifies whether the entry should be expected in the result of the |
| // record filter. This is an array of size equal to the number of test |
| // cases. |
| std::vector<bool> output; |
| }; |
| |
| static bool |
| local_create_dir(const char *dir) |
| { |
| if (dr_directory_exists(dir)) |
| return true; |
| return dr_create_dir(dir); |
| } |
| |
| basic_counts_t::counters_t |
| get_basic_counts(const std::string &trace_dir) |
| { |
| auto basic_counts_tool = |
| std::unique_ptr<basic_counts_t>(new basic_counts_t(/*verbose=*/0)); |
| std::vector<analysis_tool_t *> tools; |
| tools.push_back(basic_counts_tool.get()); |
| analyzer_t analyzer(trace_dir, &tools[0], static_cast<int>(tools.size())); |
| if (!analyzer) { |
| FATAL_ERROR("failed to initialize analyzer: %s", |
| analyzer.get_error_string().c_str()); |
| } |
| if (!analyzer.run()) { |
| FATAL_ERROR("failed to run analyzer: %s", analyzer.get_error_string().c_str()); |
| } |
| basic_counts_t::counters_t counts = basic_counts_tool->get_total_counts(); |
| return counts; |
| } |
| |
| static void |
| print_entry(trace_entry_t entry) |
| { |
| fprintf(stderr, "%s:%d:%" PRIxPTR, trace_type_names[entry.type], entry.size, |
| entry.addr); |
| } |
| |
| bool |
| process_entries_and_check_result(test_record_filter_t *record_filter, |
| const std::vector<test_case_t> &entries, int index) |
| { |
| record_filter->initialize_stream(nullptr); |
| auto stream = std::unique_ptr<local_stream_t>(new local_stream_t()); |
| void *shard_data = |
| record_filter->parallel_shard_init_stream(0, nullptr, stream.get()); |
| if (!*record_filter) { |
| fprintf(stderr, "Filtering init failed: %s\n", |
| record_filter->get_error_string().c_str()); |
| return false; |
| } |
| // Process each trace entry. |
| for (int i = 0; i < static_cast<int>(entries.size()); ++i) { |
| // We need to emulate the stream for the tool. |
| if (entries[i].entry.type == TRACE_TYPE_MARKER && |
| entries[i].entry.size == TRACE_MARKER_TYPE_TIMESTAMP) |
| stream->set_last_timestamp(entries[i].entry.addr); |
| if (entries[i].input && |
| !record_filter->parallel_shard_memref(shard_data, entries[i].entry)) { |
| fprintf(stderr, "Filtering failed on entry %d: %s\n", i, |
| record_filter->parallel_shard_error(shard_data).c_str()); |
| return false; |
| } |
| } |
| if (!record_filter->parallel_shard_exit(shard_data) || !*record_filter) { |
| fprintf(stderr, "Filtering exit failed\n"); |
| return false; |
| } |
| if (!record_filter->print_results()) { |
| fprintf(stderr, "Filtering results failed\n"); |
| return false; |
| } |
| |
| std::vector<trace_entry_t> filtered = record_filter->get_output_entries(); |
| // Verbose output for easier debugging. |
| fprintf(stderr, "Input:\n"); |
| for (int i = 0; i < static_cast<int>(entries.size()); ++i) { |
| if (!entries[i].input) |
| continue; |
| fprintf(stderr, " %d: ", i); |
| print_entry(entries[i].entry); |
| fprintf(stderr, "\n"); |
| } |
| fprintf(stderr, "Output:\n"); |
| for (int i = 0; i < static_cast<int>(filtered.size()); ++i) { |
| fprintf(stderr, " %d: ", i); |
| print_entry(filtered[i]); |
| fprintf(stderr, "\n"); |
| } |
| // Check filtered output entries. |
| int j = 0; |
| for (int i = 0; i < static_cast<int>(entries.size()); ++i) { |
| if (!entries[i].output[index]) |
| continue; |
| if (j >= static_cast<int>(filtered.size())) { |
| fprintf(stderr, |
| "Too few entries in filtered output (iter=%d). Expected: ", index); |
| print_entry(entries[i].entry); |
| fprintf(stderr, "\n"); |
| return false; |
| } |
| if (memcmp(&filtered[j], &entries[i].entry, sizeof(trace_entry_t)) != 0) { |
| fprintf(stderr, |
| "Wrong filter result for iter=%d, at pos=%d. Expected: ", index, i); |
| print_entry(entries[i].entry); |
| fprintf(stderr, ", got: "); |
| print_entry(filtered[j]); |
| fprintf(stderr, "\n"); |
| return false; |
| } |
| ++j; |
| } |
| if (j < static_cast<int>(filtered.size())) { |
| fprintf(stderr, "Got %d extra entries in filtered output (iter=%d). Next one: ", |
| static_cast<int>(filtered.size()) - j, index); |
| print_entry(filtered[j]); |
| fprintf(stderr, "\n"); |
| return false; |
| } |
| return true; |
| } |
| |
| /* Test changes in instruction encodings. |
| */ |
| static bool |
| test_encodings2regdeps_filter() |
| { |
| constexpr addr_t PC = 0x7f6fdd3ec360; |
| constexpr addr_t PC2 = 0x7f6fdd3eb1f7; |
| constexpr addr_t PC3 = 0x7f6fdd3eb21a; |
| constexpr addr_t ENCODING_REAL_ISA = 0xe78948; |
| constexpr addr_t ENCODING_REAL_ISA_2_PART1 = 0x841f0f66; |
| constexpr addr_t ENCODING_REAL_ISA_2_PART2 = 0x0; |
| constexpr addr_t ENCODING_REAL_ISA_3 = 0xab48f3; |
| constexpr addr_t ENCODING_REGDEPS_ISA = 0x0006090600010011; |
| constexpr addr_t ENCODING_REGDEPS_ISA_2 = 0x0000020400004010; |
| constexpr addr_t ENCODING_REGDEPS_ISA_3_PART1 = 0x0209030600001042; |
| constexpr addr_t ENCODING_REGDEPS_ISA_3_PART2 = 0x0000000000220903; |
| std::vector<test_case_t> entries = { |
| /* Trace shard header. |
| */ |
| { { TRACE_TYPE_HEADER, 0, { 0x1 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION, { 0x2 } }, true, { true } }, |
| /* File type, modified by record_filter encodings2regdeps to add |
| * OFFLINE_FILE_TYPE_ARCH_REGDEPS. |
| */ |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_ARCH_X86_64 | OFFLINE_FILE_TYPE_ENCODINGS | |
| OFFLINE_FILE_TYPE_SYSCALL_NUMBERS | OFFLINE_FILE_TYPE_BLOCKING_SYSCALLS } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_ARCH_REGDEPS | OFFLINE_FILE_TYPE_ENCODINGS | |
| OFFLINE_FILE_TYPE_SYSCALL_NUMBERS | OFFLINE_FILE_TYPE_BLOCKING_SYSCALLS } }, |
| false, |
| { true } }, |
| { { TRACE_TYPE_THREAD, 0, { 0x4 } }, true, { true } }, |
| { { TRACE_TYPE_PID, 0, { 0x5 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CACHE_LINE_SIZE, { 0x6 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_INSTR_COUNT, { 0x3 } }, |
| true, |
| { true } }, |
| |
| /* Chunk 1. |
| */ |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0x7 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0x8 } }, true, { true } }, |
| /* Encoding, modified by the record_filter encodings2regdeps. |
| * encoding real ISA size == encoding regdeps ISA size |
| * (in terms of trace_entry_t). |
| */ |
| { { TRACE_TYPE_ENCODING, 3, { ENCODING_REAL_ISA } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 8, { ENCODING_REGDEPS_ISA } }, false, { true } }, |
| { { TRACE_TYPE_INSTR, 3, { PC } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 3, { PC } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 3, { PC } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 0 } }, true, { true } }, |
| |
| /* Chunk 2. |
| */ |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 0xa } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0x7 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0x8 } }, true, { true } }, |
| /* Duplicated encoding across chunk boundary. |
| */ |
| { { TRACE_TYPE_ENCODING, 3, { ENCODING_REAL_ISA } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 8, { ENCODING_REGDEPS_ISA } }, false, { true } }, |
| { { TRACE_TYPE_INSTR, 3, { PC } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 3, { PC } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 3, { PC } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 1 } }, true, { true } }, |
| |
| /* Chunk 3. |
| */ |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 0xe } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0x7 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0x8 } }, true, { true } }, |
| /* encoding real ISA size > encoding regdeps ISA size |
| */ |
| { { TRACE_TYPE_ENCODING, 8, { ENCODING_REAL_ISA_2_PART1 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 1, { ENCODING_REAL_ISA_2_PART2 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 8, { ENCODING_REGDEPS_ISA_2 } }, false, { true } }, |
| { { TRACE_TYPE_INSTR, 9, { PC2 } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 9, { PC2 } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 9, { PC2 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 2 } }, true, { true } }, |
| |
| /* Chunk 4. |
| */ |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 0x12 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0x7 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0x8 } }, true, { true } }, |
| /* encoding real ISA size < encoding regdeps ISA size |
| */ |
| { { TRACE_TYPE_ENCODING, 3, { ENCODING_REAL_ISA_3 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 8, { ENCODING_REGDEPS_ISA_3_PART1 } }, false, { true } }, |
| { { TRACE_TYPE_ENCODING, 4, { ENCODING_REGDEPS_ISA_3_PART2 } }, false, { true } }, |
| { { TRACE_TYPE_INSTR, 3, { PC3 } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 3, { PC3 } }, true, { true } }, |
| |
| /* Trace shard footer. |
| */ |
| { { TRACE_TYPE_FOOTER, 0, { 0x0 } }, true, { true } }, |
| }; |
| |
| /* Construct encodings2regdeps_filter_t. |
| */ |
| std::vector<std::unique_ptr<record_filter_func_t>> filters; |
| auto encodings2regdeps_filter = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::encodings2regdeps_filter_t()); |
| if (!encodings2regdeps_filter->get_error_string().empty()) { |
| fprintf(stderr, "Couldn't construct a encodings2regdeps_filter %s", |
| encodings2regdeps_filter->get_error_string().c_str()); |
| return false; |
| } |
| filters.push_back(std::move(encodings2regdeps_filter)); |
| |
| /* Construct record_filter_t. |
| */ |
| auto record_filter = std::unique_ptr<test_record_filter_t>( |
| new test_record_filter_t(std::move(filters), 0, /*write_archive=*/true)); |
| |
| /* Run the test. |
| */ |
| if (!process_entries_and_check_result(record_filter.get(), entries, 0)) |
| return false; |
| |
| fprintf(stderr, "test_encodings2regdeps_filter passed\n"); |
| return true; |
| } |
| |
| /* Test preservation of function-related markers (TRACE_MARKER_TYPE_FUNC_[ID | RETADDR | |
| * ARG | RETVAL) based on function ID (marker value of TRACE_MARKER_TYPE_FUNC_ID). |
| */ |
| static bool |
| test_func_id_filter() |
| { |
| |
| constexpr addr_t SYS_FUTEX = 202; |
| constexpr addr_t SYS_FSYNC = 74; |
| constexpr addr_t SYSCALL_BASE = |
| static_cast<addr_t>(func_trace_t::TRACE_FUNC_ID_SYSCALL_BASE); |
| constexpr addr_t SYSCALL_FUTEX_ID = SYS_FUTEX + SYSCALL_BASE; |
| constexpr addr_t SYSCALL_FSYNC_ID = SYS_FSYNC + SYSCALL_BASE; |
| constexpr addr_t FUNC_ID_TO_KEEP = 7; |
| constexpr addr_t FUNC_ID_TO_REMOVE = 8; |
| constexpr addr_t PC = 0x7f6fdd3ec360; |
| constexpr addr_t ENCODING = 0xe78948; |
| std::vector<test_case_t> entries = { |
| /* Trace shard header. |
| */ |
| { { TRACE_TYPE_HEADER, 0, { 0x1 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION, { 0x2 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_ARCH_X86_64 | OFFLINE_FILE_TYPE_ENCODINGS | |
| OFFLINE_FILE_TYPE_SYSCALL_NUMBERS | OFFLINE_FILE_TYPE_BLOCKING_SYSCALLS } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_THREAD, 0, { 0x4 } }, true, { true } }, |
| { { TRACE_TYPE_PID, 0, { 0x5 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CACHE_LINE_SIZE, { 0x6 } }, |
| true, |
| { true } }, |
| |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0x7 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0x8 } }, true, { true } }, |
| /* We need at least one instruction with encodings to make record_filter output |
| * the trace. |
| */ |
| { { TRACE_TYPE_ENCODING, 3, { ENCODING } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 3, { PC } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { SYSCALL_FUTEX_ID } }, |
| true, |
| { true } }, |
| /* We don't care about the arg values, we just care that they are preserved. |
| * We use some non-zero values to make sure we're not creating new, uninitialized |
| * markers. |
| * Note: SYS_futex has 6 args. |
| */ |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0x1 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0x2 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0x3 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0x4 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0x5 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0x6 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { SYSCALL_FUTEX_ID } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETVAL, { 0x789 } }, |
| true, |
| { true } }, |
| /* Test that func_id_filter_t doesn't output any |
| * TRACE_MARKER_TYPE_FUNC_ for functions that are not SYS_futex. |
| * We use SYS_fsync in this test. |
| */ |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { SYSCALL_FSYNC_ID } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0x1 } }, true, { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { SYSCALL_FSYNC_ID } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETVAL, { 0x234 } }, |
| true, |
| { false } }, |
| |
| /* Nested functions. Keep outer, remove inner. |
| */ |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { FUNC_ID_TO_KEEP } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETADDR, { 0xbeef } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0x1 } }, true, { true } }, |
| |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { FUNC_ID_TO_REMOVE } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETADDR, { 0xdead } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0x1 } }, true, { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { FUNC_ID_TO_REMOVE } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETVAL, { 0x234 } }, |
| true, |
| { false } }, |
| |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { FUNC_ID_TO_KEEP } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETVAL, { 0x234 } }, |
| true, |
| { true } }, |
| |
| /* Nested functions. Remove outer, keep inner. |
| */ |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { FUNC_ID_TO_REMOVE } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETADDR, { 0xdead } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0x1 } }, true, { false } }, |
| |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { FUNC_ID_TO_KEEP } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETADDR, { 0xbeef } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0x1 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { FUNC_ID_TO_KEEP } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETVAL, { 0x234 } }, |
| true, |
| { true } }, |
| |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { FUNC_ID_TO_REMOVE } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETVAL, { 0x234 } }, |
| true, |
| { false } }, |
| |
| { { TRACE_TYPE_FOOTER, 0, { 0x0 } }, true, { true } }, |
| }; |
| |
| /* Construct func_id_filter_t. |
| */ |
| std::vector<uint64_t> func_ids_to_keep = { static_cast<uint64_t>(SYSCALL_FUTEX_ID), |
| static_cast<uint64_t>(FUNC_ID_TO_KEEP) }; |
| std::vector<std::unique_ptr<record_filter_func_t>> filters; |
| auto func_id_filter = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::func_id_filter_t(func_ids_to_keep)); |
| if (!func_id_filter->get_error_string().empty()) { |
| fprintf(stderr, "Couldn't construct a func_id_filter %s", |
| func_id_filter->get_error_string().c_str()); |
| return false; |
| } |
| filters.push_back(std::move(func_id_filter)); |
| |
| /* Construct record_filter_t. |
| */ |
| auto record_filter = std::unique_ptr<test_record_filter_t>( |
| new test_record_filter_t(std::move(filters), 0, /*write_archive=*/true)); |
| |
| /* Run the test. |
| */ |
| if (!process_entries_and_check_result(record_filter.get(), entries, 0)) |
| return false; |
| |
| fprintf(stderr, "test_func_id_filter passed\n"); |
| return true; |
| } |
| |
| static bool |
| test_cache_and_type_filter() |
| { |
| // We test two configurations: |
| // 1. filter data address stream using a cache, and filter function markers |
| // and encoding entries, without any stop timestamp. |
| // 2. filter data and instruction address stream using a cache, with a |
| // stop timestamp. |
| std::vector<test_case_t> entries = { |
| // Trace shard header. |
| { { TRACE_TYPE_HEADER, 0, { 0x1 } }, true, { true, true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION, { 0x2 } }, |
| true, |
| { true, true } }, |
| // File type, also modified by the record_filter based on the filtering |
| // done by the filter functions. |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_NO_OPTIMIZATIONS | OFFLINE_FILE_TYPE_ENCODINGS } }, |
| true, |
| { false, false } }, |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_NO_OPTIMIZATIONS | OFFLINE_FILE_TYPE_DFILTERED } }, |
| false, |
| { true, false } }, |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_NO_OPTIMIZATIONS | OFFLINE_FILE_TYPE_ENCODINGS | |
| OFFLINE_FILE_TYPE_DFILTERED | OFFLINE_FILE_TYPE_IFILTERED | |
| OFFLINE_FILE_TYPE_BIMODAL_FILTERED_WARMUP } }, |
| false, |
| { false, true } }, |
| { { TRACE_TYPE_THREAD, 0, { 0x4 } }, true, { true, true } }, |
| { { TRACE_TYPE_PID, 0, { 0x5 } }, true, { true, true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CACHE_LINE_SIZE, { 0x6 } }, |
| true, |
| { true, true } }, |
| |
| // Unit header. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0x7 } }, |
| true, |
| { true, true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0x8 } }, |
| true, |
| { true, true } }, |
| { { TRACE_TYPE_ENCODING, 4, { 0xf00d } }, true, { false, true } }, |
| { { TRACE_TYPE_INSTR, 4, { 0xaa00 } }, true, { true, true } }, |
| { { TRACE_TYPE_WRITE, 4, { 0xaa80 } }, true, { true, true } }, |
| |
| // Unit header. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0x9 } }, |
| true, |
| { true, true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0xa } }, |
| true, |
| { true, true } }, |
| // Filtered out by cache_filter. |
| { { TRACE_TYPE_WRITE, 4, { 0xaa90 } }, true, { false, false } }, |
| // For the 1st test: filtered out by type_filter. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { 0xb } }, |
| true, |
| { false, true } }, |
| |
| // Unit header. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0xc } }, |
| true, |
| { true, true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0xd } }, |
| true, |
| { true, true } }, |
| // For the 1st test: All function markers are filtered out |
| // by type filter. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { 0xe } }, |
| true, |
| { false, true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0xf } }, |
| true, |
| { false, true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETADDR, { 0xa0 } }, |
| true, |
| { false, true } }, |
| // For the 1st test, these encoding entries are filtered out by |
| // the type_filter. |
| // For the 2nd test, these encoding entries are delayed since the |
| // following instruction at PC 0xaa80 is filtered out by the |
| // cache_filter. |
| { { TRACE_TYPE_ENCODING, 4, { 0x8bad } }, true, { false, false } }, |
| { { TRACE_TYPE_ENCODING, 2, { 0xf00d } }, true, { false, false } }, |
| { { TRACE_TYPE_INSTR, 6, { 0xaa80 } }, true, { true, false } }, |
| // Filtered out by the cache_filter. |
| { { TRACE_TYPE_READ, 4, { 0xaaa0 } }, true, { false, false } }, |
| |
| // Filter endpoint marker. Only added in the 2nd test where |
| // we specify a stop_timestamp. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILTER_ENDPOINT, { 0 } }, |
| false, |
| { false, true } }, |
| |
| // Unit header. |
| // For the 2nd test: Since this timestamp is greater than the |
| // last_timestamp set below, all later entries will be output |
| // regardless of the configured filter. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0xabcdef } }, |
| true, |
| { true, true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0xa0 } }, |
| true, |
| { true, true } }, |
| // For the 1st test: Filtered out by type_filter. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { 0xa1 } }, |
| true, |
| { false, true } }, |
| // For the 1st test: encoding entries are filtered out by type_filter. |
| { { TRACE_TYPE_ENCODING, 4, { 0xdead } }, true, { false, true } }, |
| { { TRACE_TYPE_ENCODING, 2, { 0xbeef } }, true, { false, true } }, |
| { { TRACE_TYPE_INSTR, 6, { 0xab80 } }, true, { true, true } }, |
| // For the 2nd test: Delayed encodings from the previous instance |
| // of the instruction at PC 0xaa80 that was filtered out. |
| { { TRACE_TYPE_ENCODING, 4, { 0x8bad } }, false, { false, true } }, |
| { { TRACE_TYPE_ENCODING, 2, { 0xf00d } }, false, { false, true } }, |
| { { TRACE_TYPE_INSTR, 6, { 0xaa80 } }, true, { true, true } }, |
| // Trace shard footer. |
| { { TRACE_TYPE_FOOTER, 0, { 0xa2 } }, true, { true, true } } |
| }; |
| |
| for (int k = 0; k < 2; ++k) { |
| // Construct record_filter_func_ts. |
| std::vector<std::unique_ptr<record_filter_func_t>> filters; |
| auto cache_filter = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::cache_filter_t( |
| /*cache_associativity=*/1, /*cache_line_size=*/64, /*cache_size=*/128, |
| /*filter_data=*/true, /*filter_instrs=*/k == 1)); |
| if (cache_filter->get_error_string() != "") { |
| fprintf(stderr, "Couldn't construct a cache_filter %s", |
| cache_filter->get_error_string().c_str()); |
| return false; |
| } |
| filters.push_back(std::move(cache_filter)); |
| |
| if (k == 0) { |
| auto type_filter = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::type_filter_t({ TRACE_TYPE_ENCODING }, |
| { TRACE_MARKER_TYPE_FUNC_ID, |
| TRACE_MARKER_TYPE_FUNC_RETADDR, |
| TRACE_MARKER_TYPE_FUNC_ARG })); |
| if (type_filter->get_error_string() != "") { |
| fprintf(stderr, "Couldn't construct a type_filter %s", |
| type_filter->get_error_string().c_str()); |
| return false; |
| } |
| filters.push_back(std::move(type_filter)); |
| } |
| |
| // Construct record_filter_t. |
| uint64_t stop_timestamp = k == 0 ? 0 : 0xabcdee; |
| auto record_filter = std::unique_ptr<test_record_filter_t>( |
| new test_record_filter_t(std::move(filters), stop_timestamp)); |
| if (!process_entries_and_check_result(record_filter.get(), entries, k)) |
| return false; |
| } |
| fprintf(stderr, "test_cache_and_type_filter passed\n"); |
| return true; |
| } |
| |
| static bool |
| test_chunk_update() |
| { |
| { |
| // Test that the ordinal marker is updated on removing records. |
| // From Chunk 1 we remove 3 visible records (the _FUNC_ ones); the encodings |
| // are also removed but are not visible in the record count. |
| std::vector<test_case_t> entries = { |
| // Header. |
| { { TRACE_TYPE_HEADER, 0, { 0x1 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION, { 0x2 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_ENCODINGS } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE, { 0 } }, false, { true } }, |
| { { TRACE_TYPE_THREAD, 0, { 0x4 } }, true, { true } }, |
| { { TRACE_TYPE_PID, 0, { 0x5 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CACHE_LINE_SIZE, { 0x6 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_INSTR_COUNT, { 0x2 } }, |
| true, |
| { true } }, |
| // Chunk 1. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0x7 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0x8 } }, true, { true } }, |
| { { TRACE_TYPE_ENCODING, 2, { 0xf00d } }, true, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { 0x1234 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ID, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_RETADDR, { 0 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FUNC_ARG, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 2, { 0xf00d } }, true, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { 0x1235 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 0 } }, |
| true, |
| { true } }, |
| // Chunk 2. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 12 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 9 } }, |
| false, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0x7 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0x8 } }, true, { true } }, |
| { { TRACE_TYPE_ENCODING, 2, { 0xf00d } }, true, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { 0x1236 } }, true, { true } }, |
| { { TRACE_TYPE_FOOTER, 0, { 0xa2 } }, true, { true } }, |
| }; |
| std::vector<std::unique_ptr<record_filter_func_t>> filters; |
| auto filter = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::type_filter_t({ TRACE_TYPE_ENCODING }, |
| { TRACE_MARKER_TYPE_FUNC_ID, |
| TRACE_MARKER_TYPE_FUNC_RETADDR, |
| TRACE_MARKER_TYPE_FUNC_ARG })); |
| if (!filter->get_error_string().empty()) { |
| fprintf(stderr, "Couldn't construct a type_filter %s", |
| filter->get_error_string().c_str()); |
| return false; |
| } |
| filters.push_back(std::move(filter)); |
| auto record_filter = std::unique_ptr<test_record_filter_t>( |
| new test_record_filter_t(std::move(filters), 0, /*write_archive=*/true)); |
| if (!process_entries_and_check_result(record_filter.get(), entries, 0)) |
| return false; |
| } |
| { |
| // Test that a filtered-out instr doesn't have new-chunk encodings added. |
| class ordinal_filter_t : public record_filter_t::record_filter_func_t { |
| public: |
| ordinal_filter_t(std::set<int> ordinals) |
| : ordinals_(ordinals) |
| { |
| } |
| void * |
| parallel_shard_init(memtrace_stream_t *shard_stream, |
| bool partial_trace_filter) override |
| { |
| return nullptr; |
| } |
| bool |
| parallel_shard_filter( |
| trace_entry_t &entry, void *shard_data, |
| record_filter_t::record_filter_info_t &record_filter_info) override |
| { |
| bool res = true; |
| if (type_is_instr(static_cast<trace_type_t>(entry.type))) { |
| if (ordinals_.find(cur_ord_) != ordinals_.end()) |
| res = false; |
| ++cur_ord_; |
| } |
| return res; |
| } |
| bool |
| parallel_shard_exit(void *shard_data) override |
| { |
| return true; |
| } |
| |
| private: |
| // Our test class supports only a single small shard. |
| std::set<int> ordinals_; |
| int cur_ord_ = 0; |
| }; |
| constexpr addr_t PC_A = 0x1234; |
| constexpr addr_t PC_B = 0x5678; |
| constexpr addr_t ENCODING_A = 0x4321; |
| constexpr addr_t ENCODING_B = 0x8765; |
| // We have the following where "e" means encoding and | divides chunks and |
| // x means we removed it: |
| // "eA A A | eB B eA" => "eA A x eB | x eA" |
| // We check to ensure the 2nd B, now removed, has no encoding added. |
| std::vector<test_case_t> entries = { |
| // Header. |
| { { TRACE_TYPE_HEADER, 0, { 0x1 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION, { 0x2 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_ENCODINGS } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_THREAD, 0, { 0x4 } }, true, { true } }, |
| { { TRACE_TYPE_PID, 0, { 0x5 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CACHE_LINE_SIZE, { 0x6 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_INSTR_COUNT, { 0x3 } }, |
| true, |
| { true } }, |
| // Chunk 1. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0x7 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0x8 } }, true, { true } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_A } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_A } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_A } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_A } }, true, { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 0 } }, |
| true, |
| { false } }, |
| // Chunk 2. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 10 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0x8 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0x8 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 0 } }, |
| false, |
| { true } }, |
| // New chunk 2. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 10 } }, |
| false, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 0x8 } }, |
| false, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0x8 } }, false, { true } }, |
| // This is the heart of this test: there should be no inserted new-chunk |
| // encoding for this filtered-out instruction. |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_A } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_A } }, true, { true } }, |
| { { TRACE_TYPE_FOOTER, 0, { 0xa2 } }, true, { true } }, |
| }; |
| std::vector<std::unique_ptr<record_filter_func_t>> filters; |
| auto filter = |
| std::unique_ptr<record_filter_func_t>(new ordinal_filter_t({ 2, 4 })); |
| if (!filter->get_error_string().empty()) { |
| fprintf(stderr, "Couldn't construct a pc_filter %s", |
| filter->get_error_string().c_str()); |
| return false; |
| } |
| filters.push_back(std::move(filter)); |
| auto record_filter = std::unique_ptr<test_record_filter_t>( |
| new test_record_filter_t(std::move(filters), 0, /*write_archive=*/true)); |
| if (!process_entries_and_check_result(record_filter.get(), entries, 0)) |
| return false; |
| } |
| fprintf(stderr, "test_chunk_update passed\n"); |
| return true; |
| } |
| |
| static bool |
| test_trim_filter() |
| { |
| constexpr addr_t TID = 5; |
| constexpr addr_t PC_A = 0x1234; |
| constexpr addr_t ENCODING_A = 0x4321; |
| constexpr addr_t PC_B = 0x5678; |
| constexpr addr_t ENCODING_B = 0x8765; |
| { |
| // Test invalid parameters. |
| auto filter = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::trim_filter_t(150, 149)); |
| if (filter->get_error_string().empty()) { |
| fprintf(stderr, "Failed to return an error on invalid params"); |
| return false; |
| } |
| auto filter2 = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::trim_filter_t(150, 150)); |
| if (filter2->get_error_string().empty()) { |
| fprintf(stderr, "Failed to return an error on invalid params"); |
| return false; |
| } |
| } |
| { |
| // Test removing from mid-way in the 1st chunk to the very end. |
| std::vector<test_case_t> entries = { |
| // Header. |
| { { TRACE_TYPE_HEADER, 0, { 0x1 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION, { 0x2 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_ENCODINGS } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_THREAD, 0, { TID } }, true, { true } }, |
| { { TRACE_TYPE_PID, 0, { 0x5 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CACHE_LINE_SIZE, { 0x6 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_INSTR_COUNT, { 2 } }, |
| true, |
| { true } }, |
| // Chunk 1. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 100 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { true } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_A } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_A } }, true, { true } }, |
| // Removal starts here. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, true, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 0 } }, |
| true, |
| { false } }, |
| // Chunk 2. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 12 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, true, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { false } }, |
| // These footer records should remain. |
| { { TRACE_TYPE_THREAD_EXIT, 0, { TID } }, true, { true } }, |
| { { TRACE_TYPE_FOOTER, 0, { 0xa2 } }, true, { true } }, |
| }; |
| std::vector<std::unique_ptr<record_filter_func_t>> filters; |
| auto filter = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::trim_filter_t(50, 150)); |
| if (!filter->get_error_string().empty()) { |
| fprintf(stderr, "Couldn't construct a trim_filter %s", |
| filter->get_error_string().c_str()); |
| return false; |
| } |
| filters.push_back(std::move(filter)); |
| auto record_filter = std::unique_ptr<test_record_filter_t>( |
| new test_record_filter_t(std::move(filters), 0, /*write_archive=*/true)); |
| if (!process_entries_and_check_result(record_filter.get(), entries, 0)) |
| return false; |
| } |
| { |
| // Test removing from the start to mid-way in the 1st chunk. |
| // This requires repeating encodings in the new chunks. |
| std::vector<test_case_t> entries = { |
| // Header. |
| { { TRACE_TYPE_HEADER, 0, { 0x1 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION, { 0x2 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_ENCODINGS } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_THREAD, 0, { TID } }, true, { true } }, |
| { { TRACE_TYPE_PID, 0, { 0x5 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CACHE_LINE_SIZE, { 0x6 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_INSTR_COUNT, { 2 } }, |
| true, |
| { true } }, |
| // Original chunk 1. |
| // Removal starts here. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 100 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, true, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { false } }, |
| // Removal ends here. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { true } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, false, { true } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 0 } }, |
| true, |
| { false } }, |
| // Original chunk 2. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 12 } }, |
| true, |
| { false } }, |
| // Dup timestamp;cpuid should be removed. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { false } }, |
| // This encoding is not repeated b/c this is now in the same chunk as |
| // the prior instance of this same instr. |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, true, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 0 } }, |
| false, |
| { true } }, |
| // New chunk 2. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 9 } }, |
| false, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| false, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, false, { true } }, |
| // This encoding is added since it is the first instance in a new chunk. |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, false, { true } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 0 } }, |
| true, |
| { false } }, |
| // Original chunk 3. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 12 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, true, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 1 } }, |
| false, |
| { true } }, |
| // New chunk 3. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 12 } }, |
| false, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| false, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, false, { true } }, |
| { { TRACE_TYPE_THREAD_EXIT, 0, { TID } }, true, { true } }, |
| { { TRACE_TYPE_FOOTER, 0, { 0xa2 } }, true, { true } }, |
| }; |
| std::vector<std::unique_ptr<record_filter_func_t>> filters; |
| auto filter = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::trim_filter_t(150, 600)); |
| if (!filter->get_error_string().empty()) { |
| fprintf(stderr, "Couldn't construct a trim_filter %s", |
| filter->get_error_string().c_str()); |
| return false; |
| } |
| filters.push_back(std::move(filter)); |
| auto record_filter = std::unique_ptr<test_record_filter_t>( |
| new test_record_filter_t(std::move(filters), 0, /*write_archive=*/true)); |
| if (!process_entries_and_check_result(record_filter.get(), entries, 0)) |
| return false; |
| } |
| { |
| // Test removing a zero-instruction thread. |
| std::vector<test_case_t> entries = { |
| // Header. |
| { { TRACE_TYPE_HEADER, 0, { 0x1 } }, true, { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION, { 0x2 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_ENCODINGS } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_THREAD, 0, { TID } }, true, { false } }, |
| { { TRACE_TYPE_PID, 0, { 0x5 } }, true, { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CACHE_LINE_SIZE, { 0x6 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_INSTR_COUNT, { 2 } }, |
| true, |
| { false } }, |
| // Original chunk 1. |
| // Removal starts here. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 100 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, true, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { false } }, |
| // Removal ends here. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_THREAD_EXIT, 0, { TID } }, true, { false } }, |
| { { TRACE_TYPE_FOOTER, 0, { 0xa2 } }, true, { false } }, |
| }; |
| std::vector<std::unique_ptr<record_filter_func_t>> filters; |
| auto filter = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::trim_filter_t(150, 600)); |
| if (!filter->get_error_string().empty()) { |
| fprintf(stderr, "Couldn't construct a trim_filter %s", |
| filter->get_error_string().c_str()); |
| return false; |
| } |
| filters.push_back(std::move(filter)); |
| auto record_filter = std::unique_ptr<test_record_filter_t>( |
| new test_record_filter_t(std::move(filters), 0, /*write_archive=*/true)); |
| if (!process_entries_and_check_result(record_filter.get(), entries, 0)) |
| return false; |
| } |
| { |
| // Test removing from the start to mid-way in the 1st chunk while also |
| // removing all encodings. |
| std::vector<test_case_t> entries = { |
| // Header. |
| { { TRACE_TYPE_HEADER, 0, { 0x1 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION, { 0x2 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_ENCODINGS } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE, { 0 } }, false, { true } }, |
| { { TRACE_TYPE_THREAD, 0, { TID } }, true, { true } }, |
| { { TRACE_TYPE_PID, 0, { 0x5 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CACHE_LINE_SIZE, { 0x6 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_INSTR_COUNT, { 2 } }, |
| true, |
| { true } }, |
| // Original chunk 1. |
| // Removal starts here. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 100 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, true, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { false } }, |
| // Removal ends here. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { true } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, false, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 0 } }, |
| true, |
| { false } }, |
| // Original chunk 2. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 12 } }, |
| true, |
| { false } }, |
| // Dup timestamp;cpuid should be removed. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, true, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 0 } }, |
| false, |
| { true } }, |
| // New chunk 2. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 9 } }, |
| false, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| false, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, false, { true } }, |
| // An encoding would be added here, but we want it removed. |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, false, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 0 } }, |
| true, |
| { false } }, |
| // Original chunk 3. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 12 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| true, |
| { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_B } }, true, { false } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_B } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CHUNK_FOOTER, { 1 } }, |
| false, |
| { true } }, |
| // New chunk 3. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_RECORD_ORDINAL, { 12 } }, |
| false, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 200 } }, |
| false, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, false, { true } }, |
| { { TRACE_TYPE_THREAD_EXIT, 0, { TID } }, true, { true } }, |
| { { TRACE_TYPE_FOOTER, 0, { 0xa2 } }, true, { true } }, |
| }; |
| std::vector<std::unique_ptr<record_filter_func_t>> filters; |
| auto filter = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::trim_filter_t(150, 600)); |
| if (!filter->get_error_string().empty()) { |
| fprintf(stderr, "Couldn't construct a trim_filter %s", |
| filter->get_error_string().c_str()); |
| return false; |
| } |
| filters.push_back(std::move(filter)); |
| auto type_filter = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::type_filter_t({ TRACE_TYPE_ENCODING }, {})); |
| if (type_filter->get_error_string() != "") { |
| fprintf(stderr, "Couldn't construct a type_filter %s", |
| type_filter->get_error_string().c_str()); |
| return false; |
| } |
| filters.push_back(std::move(type_filter)); |
| auto record_filter = std::unique_ptr<test_record_filter_t>( |
| new test_record_filter_t(std::move(filters), 0, /*write_archive=*/true)); |
| if (!process_entries_and_check_result(record_filter.get(), entries, 0)) |
| return false; |
| } |
| fprintf(stderr, "test_trim_filter passed\n"); |
| return true; |
| } |
| |
| // Tests I/O for the record_filter on a legacy trace. |
| // We also have the tool.record_filter test which tests on a freshly generated |
| // zipfile trace. |
| static bool |
| test_null_filter() |
| { |
| std::string output_dir = op_tmp_output_dir.get_value() + DIRSEP + "null_filter"; |
| if (!local_create_dir(output_dir.c_str())) { |
| FATAL_ERROR("Failed to create filtered trace output dir %s", output_dir.c_str()); |
| } |
| { |
| // New scope so the record_filter_t destructor flushes schedule files. |
| auto null_filter = std::unique_ptr<record_filter_func_t>( |
| new dynamorio::drmemtrace::null_filter_t()); |
| std::vector<std::unique_ptr<record_filter_func_t>> filter_funcs; |
| filter_funcs.push_back(std::move(null_filter)); |
| // We use a very small stop_timestamp for the record filter. This is to verify |
| // that we emit the TRACE_MARKER_TYPE_FILTER_ENDPOINT marker for each thread even |
| // if it starts after the given stop_timestamp. Since the stop_timestamp is so |
| // small, all other entries are expected to stay. |
| static constexpr uint64_t stop_timestamp_us = 1; |
| auto record_filter = std::unique_ptr<dynamorio::drmemtrace::record_filter_t>( |
| new dynamorio::drmemtrace::record_filter_t( |
| output_dir, std::move(filter_funcs), stop_timestamp_us, |
| /*verbosity=*/0)); |
| std::vector<record_analysis_tool_t *> tools; |
| tools.push_back(record_filter.get()); |
| record_analyzer_t record_analyzer(op_trace_dir.get_value(), &tools[0], |
| static_cast<int>(tools.size())); |
| if (!record_analyzer) { |
| FATAL_ERROR("Failed to initialize record filter: %s", |
| record_analyzer.get_error_string().c_str()); |
| } |
| if (!record_analyzer.run()) { |
| FATAL_ERROR("Failed to run record filter: %s", |
| record_analyzer.get_error_string().c_str()); |
| } |
| if (!record_analyzer.print_stats()) { |
| FATAL_ERROR("Failed to print record filter stats: %s", |
| record_analyzer.get_error_string().c_str()); |
| } |
| } |
| |
| // Ensure schedule files were written out. We leave validating their contents |
| // to the end-to-end tests which run invariant_checker. |
| std::string serial_path = output_dir + DIRSEP + DRMEMTRACE_SERIAL_SCHEDULE_FILENAME; |
| #ifdef HAS_ZLIB |
| serial_path += ".gz"; |
| #endif |
| CHECK(dr_file_exists(serial_path.c_str()), "Serial schedule file missing\n"); |
| file_t fd = dr_open_file(serial_path.c_str(), DR_FILE_READ); |
| CHECK(fd != INVALID_FILE, "Cannot open serial schedule file"); |
| uint64 file_size; |
| CHECK(dr_file_size(fd, &file_size) && file_size > 0, "Serial schedule file empty"); |
| dr_close_file(fd); |
| #ifdef HAS_ZIP |
| std::string cpu_path = output_dir + DIRSEP + DRMEMTRACE_CPU_SCHEDULE_FILENAME; |
| CHECK(dr_file_exists(cpu_path.c_str()), "Cpu schedule file missing\n"); |
| fd = dr_open_file(cpu_path.c_str(), DR_FILE_READ); |
| CHECK(fd != INVALID_FILE, "Cannot open cpu schedule file"); |
| CHECK(dr_file_size(fd, &file_size) && file_size > 0, "Cpu schedule file empty"); |
| dr_close_file(fd); |
| #endif |
| |
| basic_counts_t::counters_t c1 = get_basic_counts(op_trace_dir.get_value()); |
| // We expect one extra marker (TRACE_MARKER_TYPE_FILTER_ENDPOINT) for each thread. |
| c1.other_markers += c1.shard_count; |
| basic_counts_t::counters_t c2 = get_basic_counts(output_dir); |
| CHECK(c1.instrs != 0, "Bad input trace\n"); |
| CHECK(c1 == c2, "Null filter returned different counts\n"); |
| fprintf(stderr, "test_null_filter passed\n"); |
| return true; |
| } |
| |
| static bool |
| test_wait_filter() |
| { |
| // Test that wait records (artificial timing during replay) are not preserved. |
| constexpr addr_t TID = 5; |
| constexpr addr_t PC_A = 0x1234; |
| constexpr addr_t ENCODING_A = 0x4321; |
| std::vector<test_case_t> entries = { |
| { { TRACE_TYPE_HEADER, 0, { 0x1 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION, { 0x2 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, |
| TRACE_MARKER_TYPE_FILETYPE, |
| { OFFLINE_FILE_TYPE_ENCODINGS } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_THREAD, 0, { TID } }, true, { true } }, |
| { { TRACE_TYPE_PID, 0, { 0x5 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CACHE_LINE_SIZE, { 0x6 } }, |
| true, |
| { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_TIMESTAMP, { 100 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CPU_ID, { 0 } }, true, { true } }, |
| { { TRACE_TYPE_ENCODING, 2, { ENCODING_A } }, true, { true } }, |
| { { TRACE_TYPE_INSTR, 2, { PC_A } }, true, { true } }, |
| // Test wait and idle records. |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CORE_WAIT, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CORE_WAIT, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CORE_IDLE, { 0 } }, true, { true } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CORE_WAIT, { 0 } }, true, { false } }, |
| { { TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_CORE_IDLE, { 0 } }, true, { true } }, |
| { { TRACE_TYPE_THREAD_EXIT, 0, { TID } }, true, { true } }, |
| { { TRACE_TYPE_FOOTER, 0, { 0xa2 } }, true, { true } }, |
| }; |
| std::vector<std::unique_ptr<record_filter_func_t>> filters; |
| auto record_filter = std::unique_ptr<test_record_filter_t>( |
| new test_record_filter_t(std::move(filters), 0, /*write_archive=*/true)); |
| if (!process_entries_and_check_result(record_filter.get(), entries, 0)) |
| return false; |
| fprintf(stderr, "test_wait_filter passed\n"); |
| return true; |
| } |
| |
| int |
| test_main(int argc, const char *argv[]) |
| { |
| std::string parse_err; |
| if (!droption_parser_t::parse_argv(DROPTION_SCOPE_FRONTEND, argc, (const char **)argv, |
| &parse_err, NULL) || |
| op_trace_dir.get_value().empty() || op_tmp_output_dir.get_value().empty()) { |
| FATAL_ERROR("Usage error: %s\nUsage:\n%s", parse_err.c_str(), |
| droption_parser_t::usage_short(DROPTION_SCOPE_ALL).c_str()); |
| } |
| dr_standalone_init(); |
| if (!test_cache_and_type_filter() || !test_chunk_update() || !test_trim_filter() || |
| !test_null_filter() || !test_wait_filter() || !test_encodings2regdeps_filter() || |
| !test_func_id_filter()) |
| return 1; |
| fprintf(stderr, "All done!\n"); |
| dr_standalone_exit(); |
| return 0; |
| } |
| |
| } // namespace drmemtrace |
| } // namespace dynamorio |