* Copyright (c) 2017-2023 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 "invariant_checker.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#include <memory>
#include <mutex>
#include <stack>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "analysis_tool.h"
#include "dr_api.h"
#include "memref.h"
#include "memtrace_stream.h"
#include "invariant_checker_create.h"
#include "trace_entry.h"
#include "utils.h"
namespace dynamorio {
namespace drmemtrace {
analysis_tool_t *
invariant_checker_create(bool offline, unsigned int verbose)
{
return new invariant_checker_t(offline, verbose, "");
}
invariant_checker_t::invariant_checker_t(bool offline, unsigned int verbose,
std::string test_name,
std::istream *serial_schedule_file,
std::istream *cpu_schedule_file)
: knob_offline_(offline)
, knob_verbose_(verbose)
, knob_test_name_(test_name)
, serial_schedule_file_(serial_schedule_file)
, cpu_schedule_file_(cpu_schedule_file)
{
if (knob_test_name_ == "kernel_xfer_app" || knob_test_name_ == "rseq_app")
has_annotations_ = true;
}
invariant_checker_t::~invariant_checker_t()
{
}
std::string
invariant_checker_t::initialize_stream(memtrace_stream_t *serial_stream)
{
serial_stream_ = serial_stream;
return "";
}
void
invariant_checker_t::report_if_false(per_shard_t *shard, bool condition,
const std::string &invariant_name)
{
if (!condition) {
std::cerr << "Trace invariant failure in T" << shard->tid_ << " at ref # "
<< shard->stream->get_record_ordinal() << " ("
<< shard->instr_count_since_last_timestamp_
<< " instrs since timestamp " << shard->last_timestamp_
<< "): " << invariant_name << "\n";
abort();
}
}
bool
invariant_checker_t::parallel_shard_supported()
{
return true;
}
void *
invariant_checker_t::parallel_shard_init_stream(int shard_index, void *worker_data,
memtrace_stream_t *shard_stream)
{
auto per_shard = std::unique_ptr<per_shard_t>(new per_shard_t);
per_shard->stream = shard_stream;
void *res = reinterpret_cast<void *>(per_shard.get());
std::lock_guard<std::mutex> guard(shard_map_mutex_);
shard_map_[shard_index] = std::move(per_shard);
return res;
}
void *
invariant_checker_t::parallel_shard_init(int shard_index, void *worker_data)
{
return parallel_shard_init_stream(shard_index, worker_data, nullptr);
}
bool
invariant_checker_t::parallel_shard_exit(void *shard_data)
{
return true;
}
std::string
invariant_checker_t::parallel_shard_error(void *shard_data)
{
per_shard_t *shard = reinterpret_cast<per_shard_t *>(shard_data);
return shard->error_;
}
bool
invariant_checker_t::parallel_shard_memref(void *shard_data, const memref_t &memref)
{
per_shard_t *shard = reinterpret_cast<per_shard_t *>(shard_data);
if (shard->tid_ == -1 && memref.data.tid != 0)
shard->tid_ = memref.data.tid;
++shard->ref_count_;
if (type_is_instr(memref.instr.type)) {
++shard->instr_count_;
++shard->instr_count_since_last_timestamp_;
}
if (shard->instr_count_ <= 1 && !shard->skipped_instrs_ && shard->stream != nullptr &&
shard->stream->get_instruction_ordinal() > 1)
shard->skipped_instrs_ = true;
if (!shard->skipped_instrs_ && shard->stream != nullptr &&
(shard->stream != serial_stream_ || shard_map_.size() == 1)) {
report_if_false(shard, shard->ref_count_ == shard->stream->get_record_ordinal(),
"Stream record ordinal inaccurate");
report_if_false(shard,
shard->instr_count_ == shard->stream->get_instruction_ordinal(),
"Stream instr ordinal inaccurate");
}
#ifdef UNIX
if (has_annotations_) {
if ((shard->instrs_until_interrupt_ == 0 &&
shard->memrefs_until_interrupt_ == -1) ||
(shard->instrs_until_interrupt_ == -1 &&
shard->memrefs_until_interrupt_ == 0) ||
(shard->instrs_until_interrupt_ == 0 &&
shard->memrefs_until_interrupt_ == 0)) {
report_if_false(
shard,
TESTANY(OFFLINE_FILE_TYPE_FILTERED | OFFLINE_FILE_TYPE_IFILTERED,
shard->file_type_) ||
(memref.marker.type == TRACE_TYPE_MARKER &&
(memref.marker.marker_type == TRACE_MARKER_TYPE_KERNEL_EVENT ||
memref.marker.marker_type == TRACE_MARKER_TYPE_RSEQ_ABORT)) ||
!knob_offline_,
"Interruption marker mis-placed");
shard->instrs_until_interrupt_ = -1;
shard->memrefs_until_interrupt_ = -1;
}
if (shard->memrefs_until_interrupt_ >= 0 &&
(memref.data.type == TRACE_TYPE_READ ||
memref.data.type == TRACE_TYPE_WRITE)) {
report_if_false(shard, shard->memrefs_until_interrupt_ != 0,
"Interruption marker too late");
--shard->memrefs_until_interrupt_;
}
if (memref.data.type == TRACE_TYPE_PREFETCHT0 && memref.data.addr == 1) {
report_if_false(shard,
type_is_instr(shard->prev_entry_.instr.type) &&
shard->prev_prev_entry_.marker.type ==
TRACE_TYPE_MARKER &&
shard->last_xfer_marker_.marker.marker_type ==
TRACE_MARKER_TYPE_KERNEL_EVENT,
"Signal handler not immediately after signal marker");
shard->app_handler_pc_ = shard->prev_entry_.instr.addr;
}
if (memref.data.type == TRACE_TYPE_PREFETCHT2 && memref.data.addr < 1024) {
shard->instrs_until_interrupt_ = static_cast<int>(memref.data.addr);
}
if (memref.data.type == TRACE_TYPE_PREFETCHT1 && memref.data.addr < 1024) {
shard->memrefs_until_interrupt_ = static_cast<int>(memref.data.addr);
}
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
shard->prev_entry_.marker.type == TRACE_TYPE_MARKER &&
shard->prev_entry_.marker.marker_type == TRACE_MARKER_TYPE_RSEQ_ABORT) {
report_if_false(shard,
memref.marker.marker_type == TRACE_MARKER_TYPE_KERNEL_EVENT,
"Rseq marker not immediately prior to kernel marker");
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_RSEQ_ENTRY) {
shard->in_rseq_region_ = true;
shard->rseq_start_pc_ = 0;
shard->rseq_end_pc_ = memref.marker.marker_value;
} else if (shard->in_rseq_region_) {
if (type_is_instr(memref.instr.type)) {
if (shard->rseq_start_pc_ == 0)
shard->rseq_start_pc_ = memref.instr.addr;
if (memref.instr.addr + memref.instr.size == shard->rseq_end_pc_) {
shard->in_rseq_region_ = false;
} else if (memref.instr.addr >= shard->rseq_start_pc_ &&
memref.instr.addr < shard->rseq_end_pc_) {
} else {
report_if_false(shard,
type_is_instr_branch(shard->prev_instr_.instr.type),
"Rseq region exit requires marker, branch, or commit");
shard->in_rseq_region_ = false;
}
} else {
report_if_false(shard,
memref.marker.type != TRACE_TYPE_MARKER ||
memref.marker.marker_type !=
TRACE_MARKER_TYPE_KERNEL_EVENT ||
type_is_instr_branch(shard->prev_instr_.instr.type),
"Signal in rseq region should have abort marker");
}
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_RSEQ_ABORT) {
report_if_false(shard,
shard->rseq_end_pc_ == 0 ||
shard->prev_instr_.instr.addr +
shard->prev_instr_.instr.size !=
shard->rseq_end_pc_ ||
shard->prev_instr_.instr.addr == memref.marker.marker_value,
"Rseq post-abort instruction not rolled back");
shard->in_rseq_region_ = false;
}
#endif
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_FILETYPE) {
shard->file_type_ = static_cast<offline_file_type_t>(memref.marker.marker_value);
report_if_false(shard,
shard->stream == nullptr ||
shard->file_type_ == shard->stream->get_filetype(),
"Stream interface filetype != trace marker");
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_INSTRUCTION_COUNT) {
shard->found_instr_count_marker_ = true;
report_if_false(shard,
memref.marker.marker_value >= shard->last_instr_count_marker_,
"Instr count markers not increasing");
shard->last_instr_count_marker_ = memref.marker.marker_value;
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_CACHE_LINE_SIZE) {
shard->found_cache_line_size_marker_ = true;
report_if_false(shard,
shard->stream == nullptr ||
memref.marker.marker_value ==
shard->stream->get_cache_line_size(),
"Stream interface cache line size != trace marker");
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_PAGE_SIZE) {
shard->found_page_size_marker_ = true;
report_if_false(shard,
shard->stream == nullptr ||
memref.marker.marker_value == shard->stream->get_page_size(),
"Stream interface page size != trace marker");
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_VERSION) {
shard->trace_version_ = memref.marker.marker_value;
report_if_false(shard,
shard->stream == nullptr ||
memref.marker.marker_value == shard->stream->get_version(),
"Stream interface version != trace marker");
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_SYSCALL) {
shard->found_syscall_marker_ = true;
++shard->syscall_count_;
#if !defined(WINDOWS) || defined(X64)
if (shard->prev_instr_decoded_ != nullptr) {
report_if_false(shard,
instr_is_syscall(shard->prev_instr_decoded_->data) &&
shard->expect_syscall_marker_,
"Syscall marker not placed after syscall instruction");
}
#endif
shard->expect_syscall_marker_ = false;
if (shard->trace_version_ >= TRACE_ENTRY_VERSION_FREQUENT_TIMESTAMPS) {
report_if_false(
shard,
shard->prev_entry_.marker.type == TRACE_TYPE_MARKER &&
shard->prev_entry_.marker.marker_type == TRACE_MARKER_TYPE_CPU_ID &&
shard->prev_prev_entry_.marker.type == TRACE_TYPE_MARKER &&
shard->prev_prev_entry_.marker.marker_type ==
TRACE_MARKER_TYPE_TIMESTAMP,
"Syscall marker not preceded by timestamp + cpuid");
}
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_MAYBE_BLOCKING_SYSCALL) {
shard->found_blocking_marker_ = true;
report_if_false(shard,
shard->prev_entry_.marker.type == TRACE_TYPE_MARKER &&
shard->prev_entry_.marker.marker_type ==
TRACE_MARKER_TYPE_SYSCALL,
"Maybe-blocking marker not preceded by syscall marker");
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_CHUNK_INSTR_COUNT) {
shard->chunk_instr_count_ = memref.marker.marker_value;
report_if_false(shard,
shard->stream == nullptr ||
shard->chunk_instr_count_ ==
shard->stream->get_chunk_instr_count(),
"Stream interface chunk instr count != trace marker");
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_CHUNK_FOOTER) {
report_if_false(shard,
shard->skipped_instrs_ ||
(shard->chunk_instr_count_ != 0 &&
shard->instr_count_ % shard->chunk_instr_count_ == 0),
"Chunk instruction counts are inconsistent");
}
if (shard->prev_entry_.marker.type == TRACE_TYPE_MARKER &&
marker_type_is_function_marker(shard->prev_entry_.marker.marker_type)) {
report_if_false(shard,
memref.data.type != TRACE_TYPE_READ &&
memref.data.type != TRACE_TYPE_WRITE &&
!type_is_prefetch(memref.data.type),
"Function marker misplaced between instr and memref");
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
marker_type_is_function_marker(memref.marker.marker_type)) {
if (memref.marker.marker_type == TRACE_MARKER_TYPE_FUNC_ID) {
shard->prev_func_id_ = memref.marker.marker_value;
}
if (memref.marker.marker_type == TRACE_MARKER_TYPE_FUNC_RETADDR &&
!shard->retaddr_stack_.empty()) {
report_if_false(shard,
memref.marker.marker_value == shard->retaddr_stack_.top(),
"Function marker retaddr should match prior call");
}
#ifdef UNIX
report_if_false(
shard,
shard->prev_func_id_ >=
static_cast<uintptr_t>(func_trace_t::TRACE_FUNC_ID_SYSCALL_BASE) ||
type_is_instr_branch(shard->prev_instr_.instr.type) ||
shard->instr_count_ == 0 ||
(shard->prev_xfer_marker_.marker.marker_type ==
TRACE_MARKER_TYPE_KERNEL_XFER &&
(
shard->last_signal_context_.pre_signal_instr.instr.addr == 0 ||
type_is_instr_branch(shard->last_instr_in_cur_context_.instr.type))),
"Function marker should be after a branch");
#else
report_if_false(
shard,
shard->prev_func_id_ >=
static_cast<uintptr_t>(func_trace_t::TRACE_FUNC_ID_SYSCALL_BASE) ||
type_is_instr_branch(shard->prev_instr_.instr.type) ||
shard->instr_count_ == 0,
"Function marker should be after a branch");
#endif
}
if (memref.exit.type == TRACE_TYPE_THREAD_EXIT) {
report_if_false(shard,
!TESTANY(OFFLINE_FILE_TYPE_FILTERED | OFFLINE_FILE_TYPE_IFILTERED,
shard->file_type_) ||
shard->found_instr_count_marker_,
"Missing instr count markers");
report_if_false(shard,
shard->found_cache_line_size_marker_ ||
(shard->skipped_instrs_ && shard->stream != nullptr &&
shard->stream->get_cache_line_size() > 0),
"Missing cache line marker");
report_if_false(shard,
shard->found_page_size_marker_ ||
(shard->skipped_instrs_ && shard->stream != nullptr &&
shard->stream->get_page_size() > 0),
"Missing page size marker");
report_if_false(
shard,
shard->found_syscall_marker_ ==
static_cast<bool>(
TESTANY(OFFLINE_FILE_TYPE_SYSCALL_NUMBERS, shard->file_type_)) ||
shard->syscall_count_ == 0,
"System call numbers presence does not match filetype");
report_if_false(
shard,
!shard->found_blocking_marker_ ||
TESTANY(OFFLINE_FILE_TYPE_BLOCKING_SYSCALLS, shard->file_type_),
"Kernel scheduling marker presence does not match filetype");
report_if_false(
shard,
!TESTANY(OFFLINE_FILE_TYPE_BIMODAL_FILTERED_WARMUP, shard->file_type_) ||
shard->saw_filter_endpoint_marker_,
"Expected to find TRACE_MARKER_TYPE_FILTER_ENDPOINT for the given file type");
if (knob_test_name_ == "filter_asm_instr_count") {
static constexpr int ASM_INSTR_COUNT = 133;
report_if_false(shard, shard->last_instr_count_marker_ == ASM_INSTR_COUNT,
"Incorrect instr count marker value");
}
}
if (shard->prev_entry_.marker.type == TRACE_TYPE_MARKER &&
shard->prev_entry_.marker.marker_type == TRACE_MARKER_TYPE_PHYSICAL_ADDRESS) {
report_if_false(shard,
memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type ==
TRACE_MARKER_TYPE_VIRTUAL_ADDRESS,
"Physical addr marker not immediately prior to virtual marker");
report_if_false(shard,
(memref.marker.marker_value & 0xfff) ==
(shard->prev_entry_.marker.marker_value & 0xfff),
"Physical addr bottom 12 bits do not match virtual");
}
if (type_is_instr(memref.instr.type) ||
memref.instr.type == TRACE_TYPE_PREFETCH_INSTR ||
memref.instr.type == TRACE_TYPE_INSTR_NO_FETCH) {
report_if_false(shard,
!TESTANY(OFFLINE_FILE_TYPE_SYSCALL_NUMBERS, shard->file_type_) ||
!shard->expect_syscall_marker_,
"Syscall marker missing after syscall instruction");
bool expect_encoding = TESTANY(OFFLINE_FILE_TYPE_ENCODINGS, shard->file_type_);
std::unique_ptr<instr_autoclean_t> cur_instr_decoded = nullptr;
if (expect_encoding) {
cur_instr_decoded.reset(new instr_autoclean_t(GLOBAL_DCONTEXT));
app_pc next_pc = decode_from_copy(
GLOBAL_DCONTEXT, const_cast<app_pc>(memref.instr.encoding),
reinterpret_cast<app_pc>(memref.instr.addr), cur_instr_decoded->data);
if (next_pc == nullptr) {
cur_instr_decoded.reset(nullptr);
}
if (TESTANY(OFFLINE_FILE_TYPE_SYSCALL_NUMBERS, shard->file_type_) &&
instr_is_syscall(cur_instr_decoded->data))
shard->expect_syscall_marker_ = true;
}
if (knob_verbose_ >= 3) {
std::cerr << "::" << memref.data.pid << ":" << memref.data.tid << ":: "
<< " @" << (void *)memref.instr.addr
<< ((memref.instr.type == TRACE_TYPE_INSTR_NO_FETCH)
? " non-fetched"
: "")
<< " instr x" << memref.instr.size << "\n";
}
#ifdef UNIX
report_if_false(shard, shard->instrs_until_interrupt_ != 0,
"Interruption marker too late");
if (shard->instrs_until_interrupt_ > 0)
--shard->instrs_until_interrupt_;
#endif
if (memref.instr.type == TRACE_TYPE_INSTR_DIRECT_CALL ||
memref.instr.type == TRACE_TYPE_INSTR_INDIRECT_CALL) {
shard->retaddr_stack_.push(memref.instr.addr + memref.instr.size);
}
if (memref.instr.type == TRACE_TYPE_INSTR_RETURN) {
if (!shard->retaddr_stack_.empty()) {
shard->retaddr_stack_.pop();
}
}
if (knob_offline_ && type_is_instr_branch(shard->prev_instr_.instr.type)) {
report_if_false(
shard,
!shard->saw_timestamp_but_no_instr_ ||
shard->prev_xfer_marker_.marker.marker_type ==
TRACE_MARKER_TYPE_KERNEL_EVENT ||
TESTANY(OFFLINE_FILE_TYPE_FILTERED | OFFLINE_FILE_TYPE_IFILTERED,
shard->file_type_),
"Branch target not immediately after branch");
}
const std::string non_explicit_flow_violation_msg = check_for_pc_discontinuity(
shard, memref, shard->prev_instr_, memref.instr.addr, cur_instr_decoded,
expect_encoding,
false);
report_if_false(shard, non_explicit_flow_violation_msg.empty(),
non_explicit_flow_violation_msg);
#ifdef UNIX
if (shard->prev_xfer_marker_.marker.marker_type ==
TRACE_MARKER_TYPE_KERNEL_XFER) {
bool kernel_event_marker_equality =
shard->last_signal_context_.xfer_int_pc == 0 ||
memref.instr.addr == shard->last_signal_context_.xfer_int_pc ||
shard->last_signal_context_.pre_signal_instr.instr.type ==
TRACE_TYPE_INSTR_SYSENTER;
bool pre_signal_flow_continuity =
shard->last_signal_context_.pre_signal_instr.instr.addr == 0 ||
shard->last_signal_context_.xfer_aborted_rseq ||
memref.instr.addr ==
shard->last_signal_context_.pre_signal_instr.instr.addr ||
memref.instr.addr ==
shard->last_signal_context_.pre_signal_instr.instr.addr +
shard->last_signal_context_.pre_signal_instr.instr.size ||
type_is_instr_branch(
shard->last_signal_context_.pre_signal_instr.instr.type) ||
shard->last_signal_context_.pre_signal_instr.instr.type ==
TRACE_TYPE_INSTR_SYSENTER;
report_if_false(
shard,
(kernel_event_marker_equality && pre_signal_flow_continuity) ||
memref.instr.addr == shard->app_handler_pc_ ||
memref.instr.type == TRACE_TYPE_INSTR_DIRECT_JUMP ||
TESTANY(OFFLINE_FILE_TYPE_FILTERED | OFFLINE_FILE_TYPE_IFILTERED,
shard->file_type_),
"Signal handler return point incorrect");
}
shard->last_instr_in_cur_context_ = memref;
#endif
shard->prev_instr_ = memref;
shard->prev_instr_decoded_ = std::move(cur_instr_decoded);
shard->prev_xfer_marker_.marker.marker_type = TRACE_MARKER_TYPE_VERSION;
shard->saw_timestamp_but_no_instr_ = false;
shard->window_transition_ = false;
} else if (knob_verbose_ >= 3) {
std::cerr << "::" << memref.data.pid << ":" << memref.data.tid << ":: "
<< " type " << memref.instr.type << "\n";
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_TIMESTAMP) {
#ifdef X86_32
const uintptr_t last_timestamp = static_cast<uintptr_t>(shard->last_timestamp_);
if (memref.marker.marker_value < last_timestamp) {
report_if_false(shard,
last_timestamp >
(memref.marker.marker_value +
(std::numeric_limits<uintptr_t>::max)() / 2),
"Timestamp does not increase monotonically");
}
#else
report_if_false(shard, memref.marker.marker_value >= shard->last_timestamp_,
"Timestamp does not increase monotonically");
#endif
shard->last_timestamp_ = memref.marker.marker_value;
shard->saw_timestamp_but_no_instr_ = true;
shard->instr_count_since_last_timestamp_ = 0;
if (knob_verbose_ >= 3) {
std::cerr << "::" << memref.data.pid << ":" << memref.data.tid << ":: "
<< " timestamp " << memref.marker.marker_value << "\n";
}
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_CPU_ID) {
shard->sched_.emplace_back(shard->tid_, shard->last_timestamp_,
memref.marker.marker_value, shard->instr_count_);
shard->cpu2sched_[memref.marker.marker_value].emplace_back(
shard->tid_, shard->last_timestamp_, memref.marker.marker_value,
shard->instr_count_);
}
#ifdef UNIX
bool saw_rseq_abort = false;
#endif
if (memref.marker.type == TRACE_TYPE_MARKER &&
(memref.marker.marker_type == TRACE_MARKER_TYPE_KERNEL_EVENT ||
memref.marker.marker_type == TRACE_MARKER_TYPE_KERNEL_XFER)) {
if (knob_verbose_ >= 3) {
std::cerr << "::" << memref.data.pid << ":" << memref.data.tid << ":: "
<< "marker type " << memref.marker.marker_type << " value 0x"
<< std::hex << memref.marker.marker_value << std::dec << "\n";
}
if (memref.marker.marker_type == TRACE_MARKER_TYPE_KERNEL_EVENT) {
if (shard->prev_entry_.marker.type != TRACE_TYPE_MARKER ||
shard->prev_entry_.marker.marker_type != TRACE_MARKER_TYPE_RSEQ_ABORT) {
shard->retaddr_stack_.push(0);
}
}
#ifdef UNIX
report_if_false(shard, memref.marker.marker_value != 0,
"Kernel event marker value missing");
if (memref.marker.marker_type == TRACE_MARKER_TYPE_KERNEL_XFER) {
if (shard->signal_stack_.empty()) {
shard->last_signal_context_ = { 0, {}, false };
shard->last_instr_in_cur_context_ = {};
} else {
shard->last_signal_context_ = shard->signal_stack_.top();
shard->signal_stack_.pop();
shard->last_instr_in_cur_context_ =
shard->last_signal_context_.pre_signal_instr;
}
}
if (memref.marker.marker_type == TRACE_MARKER_TYPE_KERNEL_EVENT) {
if (shard->prev_entry_.marker.type == TRACE_TYPE_MARKER &&
shard->prev_entry_.marker.marker_type == TRACE_MARKER_TYPE_RSEQ_ABORT) {
saw_rseq_abort = true;
} else {
if (type_is_instr(shard->last_instr_in_cur_context_.instr.type) &&
!shard->saw_rseq_abort_ &&
knob_offline_) {
const std::string discontinuity = check_for_pc_discontinuity(
shard, memref, shard->last_instr_in_cur_context_,
memref.marker.marker_value, nullptr,
TESTANY(OFFLINE_FILE_TYPE_ENCODINGS, shard->file_type_),
true);
report_if_false(shard, discontinuity.empty(), discontinuity);
}
shard->signal_stack_.push({ memref.marker.marker_value,
shard->last_instr_in_cur_context_,
shard->saw_rseq_abort_ });
shard->last_instr_in_cur_context_ = {};
}
}
#endif
shard->prev_xfer_marker_ = memref;
shard->last_xfer_marker_ = memref;
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_WINDOW_ID) {
if (shard->last_window_ != memref.marker.marker_value)
shard->window_transition_ = true;
shard->last_window_ = memref.marker.marker_value;
}
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_FILTER_ENDPOINT) {
shard->saw_filter_endpoint_marker_ = true;
report_if_false(
shard, TESTANY(OFFLINE_FILE_TYPE_BIMODAL_FILTERED_WARMUP, shard->file_type_),
"Found TRACE_MARKER_TYPE_FILTER_ENDPOINT without the correct file type");
}
if (knob_offline_ && shard->trace_version_ >= TRACE_ENTRY_VERSION_BRANCH_INFO) {
bool is_indirect = false;
if (type_is_instr_branch(memref.instr.type) &&
!TESTANY(OFFLINE_FILE_TYPE_FILTERED | OFFLINE_FILE_TYPE_IFILTERED,
shard->file_type_)) {
report_if_false(
shard, memref.instr.type != TRACE_TYPE_INSTR_CONDITIONAL_JUMP,
"The CONDITIONAL_JUMP type is deprecated and should not appear");
if (!type_is_instr_direct_branch(memref.instr.type)) {
is_indirect = true;
report_if_false(shard,
memref.instr.indirect_branch_target != 0,
"Indirect branches must contain targets");
}
}
if (type_is_instr(memref.instr.type) && !is_indirect) {
report_if_false(shard, memref.instr.indirect_branch_target == 0,
"Indirect target should be 0 for non-indirect-branches");
}
}
#ifdef UNIX
if (saw_rseq_abort) {
shard->saw_rseq_abort_ = true;
}
else if (!(memref.marker.type == TRACE_TYPE_MARKER &&
(memref.marker.marker_type == TRACE_MARKER_TYPE_TIMESTAMP ||
memref.marker.marker_type == TRACE_MARKER_TYPE_CPU_ID))) {
shard->saw_rseq_abort_ = false;
}
#endif
shard->prev_prev_entry_ = shard->prev_entry_;
shard->prev_entry_ = memref;
if (type_is_instr_branch(shard->prev_entry_.instr.type))
shard->last_branch_ = shard->prev_entry_;
return true;
}
bool
invariant_checker_t::process_memref(const memref_t &memref)
{
per_shard_t *per_shard;
const auto &lookup = shard_map_.find(memref.data.tid);
if (lookup == shard_map_.end()) {
auto per_shard_unique = std::unique_ptr<per_shard_t>(new per_shard_t);
per_shard = per_shard_unique.get();
per_shard->stream = serial_stream_;
shard_map_[memref.data.tid] = std::move(per_shard_unique);
} else
per_shard = lookup->second.get();
if (!parallel_shard_memref(reinterpret_cast<void *>(per_shard), memref)) {
error_string_ = per_shard->error_;
return false;
}
return true;
}
void
invariant_checker_t::check_schedule_data(per_shard_t *global)
{
if (serial_schedule_file_ == nullptr && cpu_schedule_file_ == nullptr)
return;
auto stream = std::unique_ptr<memtrace_stream_t>(
new default_memtrace_stream_t(&global->ref_count_));
global->stream = stream.get();
std::vector<schedule_entry_t> serial;
std::unordered_map<uint64_t, std::vector<schedule_entry_t>> cpu2sched;
for (auto &shard_keyval : shard_map_) {
serial.insert(serial.end(), shard_keyval.second->sched_.begin(),
shard_keyval.second->sched_.end());
for (auto &keyval : shard_keyval.second->cpu2sched_) {
auto &vec = cpu2sched[keyval.first];
vec.insert(vec.end(), keyval.second.begin(), keyval.second.end());
}
}
auto schedule_entry_comparator = [](const schedule_entry_t &l,
const schedule_entry_t &r) {
if (l.timestamp != r.timestamp)
return l.timestamp < r.timestamp;
if (l.cpu != r.cpu)
return l.cpu < r.cpu;
return l.thread < r.thread;
};
std::sort(serial.begin(), serial.end(), schedule_entry_comparator);
std::vector<schedule_entry_t> serial_redux;
for (const auto &entry : serial) {
if (serial_redux.empty() || entry.thread != serial_redux.back().thread)
serial_redux.push_back(entry);
}
if (serial_schedule_file_ != nullptr) {
schedule_entry_t next(0, 0, 0, 0);
std::vector<schedule_entry_t> serial_file;
while (
serial_schedule_file_->read(reinterpret_cast<char *>(&next), sizeof(next))) {
serial_file.push_back(next);
}
std::sort(serial_file.begin(), serial_file.end(), schedule_entry_comparator);
if (knob_verbose_ >= 1) {
std::cerr << "Serial schedule: read " << serial_file.size()
<< " records from the file and observed " << serial.size()
<< " transition in the trace\n";
}
std::vector<schedule_entry_t> *tomatch;
if (serial_file.size() == serial.size())
tomatch = &serial;
else if (serial_file.size() == serial_redux.size())
tomatch = &serial_redux;
else {
report_if_false(global, false,
"Serial schedule entry count does not match trace");
return;
}
for (int i = 0; i < static_cast<int>(tomatch->size()) &&
i < static_cast<int>(serial_file.size());
++i) {
global->ref_count_ = serial_file[i].start_instruction;
global->tid_ = serial_file[i].thread;
if (knob_verbose_ >= 1) {
std::cerr << "Saw T" << (*tomatch)[i].thread << " on "
<< (*tomatch)[i].cpu << " @" << (*tomatch)[i].timestamp << " "
<< (*tomatch)[i].start_instruction << " vs file T"
<< serial_file[i].thread << " on " << serial_file[i].cpu << " @"
<< serial_file[i].timestamp << " "
<< serial_file[i].start_instruction << "\n";
}
report_if_false(
global,
memcmp(&(*tomatch)[i], &serial_file[i], sizeof((*tomatch)[i])) == 0,
"Serial schedule entry does not match trace");
}
}
if (cpu_schedule_file_ == nullptr)
return;
for (auto &keyval : cpu2sched) {
std::sort(keyval.second.begin(), keyval.second.end(), schedule_entry_comparator);
}
std::unordered_map<uint64_t, std::vector<schedule_entry_t>> cpu2sched_file;
schedule_entry_t next(0, 0, 0, 0);
while (cpu_schedule_file_->read(reinterpret_cast<char *>(&next), sizeof(next))) {
cpu2sched_file[next.cpu].push_back(next);
}
for (auto &keyval : cpu2sched_file) {
std::sort(keyval.second.begin(), keyval.second.end(),
[](const schedule_entry_t &l, const schedule_entry_t &r) {
if (l.timestamp == r.timestamp)
return l.thread < r.thread;
return l.timestamp < r.timestamp;
});
std::vector<schedule_entry_t> redux;
for (const auto &entry : cpu2sched[keyval.first]) {
if (redux.empty() || entry.thread != redux.back().thread)
redux.push_back(entry);
}
std::vector<schedule_entry_t> *tomatch;
if (keyval.second.size() == cpu2sched[keyval.first].size())
tomatch = &cpu2sched[keyval.first];
else if (keyval.second.size() == redux.size())
tomatch = &redux;
else {
report_if_false(global, false,
"Cpu schedule entry count does not match trace");
return;
}
for (int i = 0; i < static_cast<int>(tomatch->size()) &&
i < static_cast<int>(keyval.second.size());
++i) {
global->ref_count_ = keyval.second[i].start_instruction;
global->tid_ = keyval.second[i].thread;
report_if_false(
global,
memcmp(&(*tomatch)[i], &keyval.second[i], sizeof(keyval.second[i])) == 0,
"Cpu schedule entry does not match trace");
}
}
}
bool
invariant_checker_t::print_results()
{
per_shard_t global;
check_schedule_data(&global);
std::cerr << "Trace invariant checks passed\n";
return true;
}
std::string
invariant_checker_t::check_for_pc_discontinuity(
per_shard_t *shard, const memref_t &memref, const memref_t &prev_instr, addr_t cur_pc,
const std::unique_ptr<instr_autoclean_t> &cur_instr_decoded, bool expect_encoding,
bool at_kernel_event)
{
std::string error_msg = "";
bool have_branch_target = false;
addr_t branch_target = 0;
const addr_t prev_instr_trace_pc = prev_instr.instr.addr;
if (prev_instr_trace_pc == 0 )
return "";
if (expect_encoding && type_is_instr_direct_branch(prev_instr.instr.type)) {
if (prev_instr.instr.encoding_is_new)
shard->branch_target_cache.erase(prev_instr_trace_pc);
auto cached = shard->branch_target_cache.find(prev_instr_trace_pc);
if (cached != shard->branch_target_cache.end()) {
have_branch_target = true;
branch_target = cached->second;
} else {
if (shard->prev_instr_decoded_ == nullptr ||
!opnd_is_pc(instr_get_target(shard->prev_instr_decoded_->data))) {
report_if_false(shard, false, "Branch target is not decodeable");
} else {
have_branch_target = true;
branch_target = reinterpret_cast<addr_t>(
opnd_get_pc(instr_get_target(shard->prev_instr_decoded_->data)));
shard->branch_target_cache[prev_instr_trace_pc] = branch_target;
}
}
}
bool fall_through_allowed = !type_is_instr_branch(prev_instr.instr.type) ||
prev_instr.instr.type == TRACE_TYPE_INSTR_CONDITIONAL_JUMP ||
prev_instr.instr.type == TRACE_TYPE_INSTR_UNTAKEN_JUMP;
const bool valid_nonbranch_flow =
TESTANY(OFFLINE_FILE_TYPE_FILTERED | OFFLINE_FILE_TYPE_IFILTERED,
shard->file_type_) ||
(fall_through_allowed && prev_instr_trace_pc + prev_instr.instr.size == cur_pc) ||
(prev_instr_trace_pc == cur_pc &&
(memref.instr.type == TRACE_TYPE_INSTR_NO_FETCH ||
(!knob_offline_ && memref.instr.size == prev_instr.instr.size))) ||
(prev_instr_trace_pc == cur_pc && at_kernel_event) ||
(shard->prev_xfer_marker_.instr.tid != 0 && !at_kernel_event &&
(shard->prev_xfer_marker_.marker.marker_type == TRACE_MARKER_TYPE_KERNEL_EVENT ||
shard->prev_xfer_marker_.marker.marker_type == TRACE_MARKER_TYPE_KERNEL_XFER ||
shard->prev_xfer_marker_.marker.marker_type == TRACE_MARKER_TYPE_RSEQ_ABORT)) ||
shard->window_transition_ || prev_instr.instr.type == TRACE_TYPE_INSTR_SYSENTER;
if (!valid_nonbranch_flow) {
if (type_is_instr_branch(prev_instr.instr.type)) {
if (knob_offline_ &&
shard->trace_version_ >= TRACE_ENTRY_VERSION_BRANCH_INFO) {
if (prev_instr.instr.type == TRACE_TYPE_INSTR_UNTAKEN_JUMP) {
branch_target = prev_instr_trace_pc + prev_instr.instr.size;
have_branch_target = true;
} else if (!type_is_instr_direct_branch(prev_instr.instr.type)) {
branch_target = prev_instr.instr.indirect_branch_target;
have_branch_target = true;
}
}
if (have_branch_target && branch_target != cur_pc)
error_msg = "Branch does not go to the correct target";
} else if (cur_instr_decoded != nullptr &&
shard->prev_instr_decoded_ != nullptr &&
instr_is_syscall(cur_instr_decoded->data) &&
cur_pc == prev_instr_trace_pc &&
instr_is_syscall(shard->prev_instr_decoded_->data)) {
error_msg = "Duplicate syscall instrs with the same PC";
} else if (shard->prev_instr_decoded_ != nullptr &&
instr_writes_memory(shard->prev_instr_decoded_->data) &&
type_is_instr_conditional_branch(shard->last_branch_.instr.type)) {
error_msg = "PC discontinuity due to rseq side exit";
} else {
error_msg = "Non-explicit control flow has no marker";
}
}
return error_msg;
}
}
}