diff --git a/api/docs/release.dox b/api/docs/release.dox index d7f38a72e27..beb4e37a382 100644 --- a/api/docs/release.dox +++ b/api/docs/release.dox @@ -157,6 +157,7 @@ Further non-compatibility-affecting changes include: - Added several routines to the #dynamorio::drmemtrace::memtrace_stream_t interface for drmemtrace analysis tools: get_output_cpuid(), get_workload_id(), get_input_id(), get_input_interface(). + - Added -record_syscall to drmemtrace for recording syscall parameters. - Added opportunity to run multiple drcachesim analysis tools simultaneously. - Added support of loading separately-built analysis tools to drcachesim dynamically. diff --git a/clients/drcachesim/common/options.cpp b/clients/drcachesim/common/options.cpp index 11870692e16..40c29bb8d89 100644 --- a/clients/drcachesim/common/options.cpp +++ b/clients/drcachesim/common/options.cpp @@ -748,6 +748,24 @@ droption_t op_record_replace_retaddr( "replacement, which has lower overhead, but runs the risk of breaking an " "application that examines or changes its own return addresses in the recorded " "functions."); +droption_t op_record_syscall( + DROPTION_SCOPE_CLIENT, "record_syscall", DROPTION_FLAG_ACCUMULATE, + OP_RECORD_FUNC_ITEM_SEP, "", "Record parameters for the specified syscall number(s).", + "Record the parameters and success of the specified system call number(s)." + " The option value should fit this format:" + " sycsall_number|parameter_number" + " E.g., -record_syscall \"2|2\" will record SYS_open's 2 parameters and whether" + " successful (1 for success or 0 for failure, in a function return value record)" + " for x86 Linux. SYS_futex is recorded by default on Linux and this option's value" + " adds to futex rather than replacing it (setting futex to 0 parameters disables)." + " The trace identifies which syscall owns each set of parameter and return value" + " records via a numeric ID equal to the syscall number + TRACE_FUNC_ID_SYSCALL_BASE." + " Recording multiple syscalls can be achieved by using the separator" + " \"" OP_RECORD_FUNC_ITEM_SEP + "\" (e.g., -record_syscall \"202|6" OP_RECORD_FUNC_ITEM_SEP "3|1\"), or" + " specifying multiple -record_syscall options." + " It is up to the user to ensure the values are correct; a too-large parameter" + " count may cause tracing to fail with an error mid-run."); droption_t op_miss_count_threshold( DROPTION_SCOPE_FRONTEND, "miss_count_threshold", 50000, "For cache miss analysis: minimum LLC miss count for a load to be eligible for " diff --git a/clients/drcachesim/common/options.h b/clients/drcachesim/common/options.h index bb7fab05a4b..373dfa4f305 100644 --- a/clients/drcachesim/common/options.h +++ b/clients/drcachesim/common/options.h @@ -57,6 +57,10 @@ #define CACHE_TYPE_DATA "data" #define CACHE_TYPE_UNIFIED "unified" #define CACHE_PARENT_MEMORY "memory" +// The expected pattern for a single_op_value is: +// function_name|function_id|arguments_num +// where function_name can contain spaces (for instance, C++ namespace prefix) +#define PATTERN_SEPARATOR "|" #ifdef HAS_ZIP # define DEFAULT_TRACE_COMPRESSION_TYPE "zip" @@ -169,6 +173,7 @@ extern dynamorio::droption::droption_t op_record_heap; extern dynamorio::droption::droption_t op_record_heap_value; extern dynamorio::droption::droption_t op_record_dynsym_only; extern dynamorio::droption::droption_t op_record_replace_retaddr; +extern dynamorio::droption::droption_t op_record_syscall; extern dynamorio::droption::droption_t op_miss_count_threshold; extern dynamorio::droption::droption_t op_miss_frac_threshold; extern dynamorio::droption::droption_t op_confidence_threshold; diff --git a/clients/drcachesim/common/utils.h b/clients/drcachesim/common/utils.h index d24268be1bf..7bc5bec97ff 100644 --- a/clients/drcachesim/common/utils.h +++ b/clients/drcachesim/common/utils.h @@ -39,6 +39,7 @@ #include #include #include +#include namespace dynamorio { namespace drmemtrace { @@ -166,6 +167,21 @@ starts_with(const std::string &str, const std::string &with) return pos == 0; } +static inline std::vector +split_by(std::string s, const std::string &sep) +{ + size_t pos; + std::vector vec; + if (s.empty()) + return vec; + do { + pos = s.find(sep); + vec.push_back(s.substr(0, pos)); + s.erase(0, pos + sep.length()); + } while (pos != std::string::npos); + return vec; +} + } // namespace drmemtrace } // namespace dynamorio diff --git a/clients/drcachesim/docs/drcachesim.dox.in b/clients/drcachesim/docs/drcachesim.dox.in index d19006cc281..b06c46f5fcb 100644 --- a/clients/drcachesim/docs/drcachesim.dox.in +++ b/clients/drcachesim/docs/drcachesim.dox.in @@ -1482,6 +1482,11 @@ The -record_heap parameter requests recording of a pre-determined set of functions related to heap allocation. The -record_heap_value paramter controls the contents of this set. +The tracer also supports recording system call argument and success +values via the option -record_syscall, which functions similarly to +-record_function with the system call number replacing the function +name. + **************************************************************************** \page sec_drcachesim_newtool Creating New Analysis Tools diff --git a/clients/drcachesim/tests/offline-allasm-record-syscall.templatex b/clients/drcachesim/tests/offline-allasm-record-syscall.templatex new file mode 100644 index 00000000000..ae4e25ee074 --- /dev/null +++ b/clients/drcachesim/tests/offline-allasm-record-syscall.templatex @@ -0,0 +1,24 @@ +Adios world! +Adios world! +Adios world! +Adios world! +Adios world! +Adios world! +Adios world! +Adios world! +Adios world! +Adios world! +.* + 43 20: .* ifetch 2 byte\(s\) @ 0x.* 0f 05 syscall + 44 20: .* + 45 20: .* + 46 20: .* + 47 20: .* + 48 20: .* + 49 20: .* + 50 20: .* + 51 20: .* + 52 20: .* + 53 20: .* + 54 20: .* +.* diff --git a/clients/drcachesim/tests/raw2trace_unit_tests.cpp b/clients/drcachesim/tests/raw2trace_unit_tests.cpp index af8c5024935..41afcadcdea 100644 --- a/clients/drcachesim/tests/raw2trace_unit_tests.cpp +++ b/clients/drcachesim/tests/raw2trace_unit_tests.cpp @@ -183,11 +183,11 @@ make_pid() } offline_entry_t -make_tid() +make_tid(memref_tid_t tid = 1) { offline_entry_t entry; entry.tid.type = OFFLINE_TYPE_THREAD; - entry.tid.tid = 1; + entry.tid.tid = tid; return entry; } @@ -294,10 +294,22 @@ check_entry(std::vector &entries, int &idx, unsigned short expect return true; } +void +populate_all_stats(raw2trace_test_t &raw2trace, std::vector *stats) +{ + if (stats == nullptr) + return; + for (int i = 0; i < RAW2TRACE_STAT_MAX; ++i) { + stats->push_back(raw2trace.get_statistic( + static_cast(i))); + } +} + // Takes ownership of ilist and destroys it. bool run_raw2trace(void *drcontext, const std::vector raw, instrlist_t *ilist, - std::vector &entries, int chunk_instr_count = 0, + std::vector &entries, std::vector *stats = nullptr, + int chunk_instr_count = 0, const std::vector &modules = {}) { // We need an istream so we use istringstream. @@ -325,6 +337,7 @@ run_raw2trace(void *drcontext, const std::vector raw, instrlist std::string error = raw2trace.do_conversion(); CHECK(error.empty(), error); result = result_stream.str(); + populate_all_stats(raw2trace, stats); } else if (modules.empty()) { // We need an ostream to capture out. std::ostringstream result_stream; @@ -336,6 +349,7 @@ run_raw2trace(void *drcontext, const std::vector raw, instrlist std::string error = raw2trace.do_conversion(); CHECK(error.empty(), error); result = result_stream.str(); + populate_all_stats(raw2trace, stats); } else { // We need an ostream to capture out. std::ostringstream result_stream; @@ -347,6 +361,7 @@ run_raw2trace(void *drcontext, const std::vector raw, instrlist std::string error = raw2trace.do_conversion(); CHECK(error.empty(), error); result = result_stream.str(); + populate_all_stats(raw2trace, stats); } if (ilist != nullptr) instrlist_clear_and_destroy(drcontext, ilist); @@ -713,7 +728,7 @@ test_chunk_boundaries(void *drcontext) std::vector entries; // Use a chunk instr count of 2 to split the 2 jumps. - if (!run_raw2trace(drcontext, raw, ilist, entries, 2)) + if (!run_raw2trace(drcontext, raw, ilist, entries, /*stats=*/nullptr, 2)) return false; int idx = 0; return ( @@ -836,7 +851,7 @@ test_chunk_encodings(void *drcontext) std::vector entries; // Use a chunk instr count of 6 to split the 2nd set of 2 jumps. - if (!run_raw2trace(drcontext, raw, ilist, entries, 6)) + if (!run_raw2trace(drcontext, raw, ilist, entries, /*stats=*/nullptr, 6)) return false; int idx = 0; return ( @@ -979,11 +994,13 @@ test_duplicate_syscalls(void *drcontext) raw.push_back(make_block(offs_move2, 1)); raw.push_back(make_exit()); + std::vector stats; std::vector entries; - if (!run_raw2trace(drcontext, raw, ilist, entries)) + if (!run_raw2trace(drcontext, raw, ilist, entries, &stats)) return false; int idx = 0; return ( + stats[RAW2TRACE_STAT_DUPLICATE_SYSCALL] == 1 && check_entry(entries, idx, TRACE_TYPE_HEADER, -1) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE) && @@ -1080,11 +1097,13 @@ test_false_syscalls(void *drcontext) raw.push_back(make_block(offs_move2, 1)); raw.push_back(make_exit()); + std::vector stats; std::vector entries; - if (!run_raw2trace(drcontext, raw, ilist, entries)) + if (!run_raw2trace(drcontext, raw, ilist, entries, &stats)) return false; int idx = 0; return ( + stats[RAW2TRACE_STAT_FALSE_SYSCALL] == 1 && check_entry(entries, idx, TRACE_TYPE_HEADER, -1) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE) && @@ -1161,11 +1180,13 @@ test_rseq_fallthrough(void *drcontext) raw.push_back(make_block(offs_move2, 1)); raw.push_back(make_exit()); + std::vector stats; std::vector entries; - if (!run_raw2trace(drcontext, raw, ilist, entries)) + if (!run_raw2trace(drcontext, raw, ilist, entries, &stats)) return false; int idx = 0; return ( + stats[RAW2TRACE_STAT_RSEQ_SIDE_EXIT] == 0 && check_entry(entries, idx, TRACE_TYPE_HEADER, -1) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE) && @@ -1229,11 +1250,13 @@ test_rseq_rollback_legacy(void *drcontext) raw.push_back(make_block(offs_move2, 1)); raw.push_back(make_exit()); + std::vector stats; std::vector entries; - if (!run_raw2trace(drcontext, raw, ilist, entries)) + if (!run_raw2trace(drcontext, raw, ilist, entries, &stats)) return false; int idx = 0; return ( + stats[RAW2TRACE_STAT_RSEQ_ABORT] == 1 && check_entry(entries, idx, TRACE_TYPE_HEADER, -1) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE) && @@ -1295,11 +1318,13 @@ test_rseq_rollback(void *drcontext) raw.push_back(make_block(offs_move2, 1)); raw.push_back(make_exit()); + std::vector stats; std::vector entries; - if (!run_raw2trace(drcontext, raw, ilist, entries)) + if (!run_raw2trace(drcontext, raw, ilist, entries, &stats)) return false; int idx = 0; return ( + stats[RAW2TRACE_STAT_RSEQ_ABORT] == 1 && check_entry(entries, idx, TRACE_TYPE_HEADER, -1) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE) && @@ -1365,11 +1390,13 @@ test_rseq_rollback_with_timestamps(void *drcontext) raw.push_back(make_block(offs_move2, 1)); raw.push_back(make_exit()); + std::vector stats; std::vector entries; - if (!run_raw2trace(drcontext, raw, ilist, entries)) + if (!run_raw2trace(drcontext, raw, ilist, entries, &stats)) return false; int idx = 0; return ( + stats[RAW2TRACE_STAT_RSEQ_ABORT] == 1 && check_entry(entries, idx, TRACE_TYPE_HEADER, -1) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE) && @@ -1440,11 +1467,13 @@ test_rseq_rollback_with_signal(void *drcontext) raw.push_back(make_block(offs_move2, 1)); raw.push_back(make_exit()); + std::vector stats; std::vector entries; - if (!run_raw2trace(drcontext, raw, ilist, entries)) + if (!run_raw2trace(drcontext, raw, ilist, entries, &stats)) return false; int idx = 0; return ( + stats[RAW2TRACE_STAT_RSEQ_ABORT] == 1 && check_entry(entries, idx, TRACE_TYPE_HEADER, -1) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE) && @@ -1521,12 +1550,14 @@ test_rseq_rollback_with_chunks(void *drcontext) raw.push_back(make_block(offs_move2, 1)); raw.push_back(make_exit()); + std::vector stats; std::vector entries; // 6 instrs puts a new chunk at the start of the 3rd region. - if (!run_raw2trace(drcontext, raw, ilist, entries, 6)) + if (!run_raw2trace(drcontext, raw, ilist, entries, &stats, 6)) return false; int idx = 0; return ( + stats[RAW2TRACE_STAT_RSEQ_ABORT] == 1 && check_entry(entries, idx, TRACE_TYPE_HEADER, -1) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE) && @@ -1620,11 +1651,13 @@ test_rseq_side_exit(void *drcontext) raw.push_back(make_block(offs_move3, 1)); raw.push_back(make_exit()); + std::vector stats; std::vector entries; - if (!run_raw2trace(drcontext, raw, ilist, entries)) + if (!run_raw2trace(drcontext, raw, ilist, entries, &stats)) return false; int idx = 0; return ( + stats[RAW2TRACE_STAT_RSEQ_SIDE_EXIT] == 1 && check_entry(entries, idx, TRACE_TYPE_HEADER, -1) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE) && @@ -1703,11 +1736,13 @@ test_rseq_side_exit_signal(void *drcontext) raw.push_back(make_block(offs_move3, 1)); raw.push_back(make_exit()); + std::vector stats; std::vector entries; - if (!run_raw2trace(drcontext, raw, ilist, entries)) + if (!run_raw2trace(drcontext, raw, ilist, entries, &stats)) return false; int idx = 0; return ( + stats[RAW2TRACE_STAT_RSEQ_SIDE_EXIT] == 1 && check_entry(entries, idx, TRACE_TYPE_HEADER, -1) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE) && @@ -1790,11 +1825,13 @@ test_rseq_side_exit_inverted(void *drcontext) raw.push_back(make_block(offs_move3, 1)); raw.push_back(make_exit()); + std::vector stats; std::vector entries; - if (!run_raw2trace(drcontext, raw, ilist, entries)) + if (!run_raw2trace(drcontext, raw, ilist, entries, &stats)) return false; int idx = 0; return ( + stats[RAW2TRACE_STAT_RSEQ_SIDE_EXIT] == 1 && check_entry(entries, idx, TRACE_TYPE_HEADER, -1) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE) && @@ -1889,11 +1926,13 @@ test_rseq_side_exit_inverted_with_timestamp(void *drcontext) raw.push_back(make_block(offs_move2, 1)); raw.push_back(make_exit()); + std::vector stats; std::vector entries; - if (!run_raw2trace(drcontext, raw, ilist, entries)) + if (!run_raw2trace(drcontext, raw, ilist, entries, &stats)) return false; int idx = 0; return ( + stats[RAW2TRACE_STAT_RSEQ_SIDE_EXIT] == 1 && check_entry(entries, idx, TRACE_TYPE_HEADER, -1) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_VERSION) && check_entry(entries, idx, TRACE_TYPE_MARKER, TRACE_MARKER_TYPE_FILETYPE) && @@ -1968,7 +2007,7 @@ test_xfer_modoffs(void *drcontext) raw.push_back(make_exit()); std::vector entries; - if (!run_raw2trace(drcontext, raw, nullptr, entries, 0, modules)) + if (!run_raw2trace(drcontext, raw, nullptr, entries, /*stats=*/nullptr, 0, modules)) return false; int idx = 0; return ( @@ -2012,7 +2051,7 @@ test_xfer_absolute(void *drcontext) raw.push_back(make_exit()); std::vector entries; - if (!run_raw2trace(drcontext, raw, nullptr, entries, 0, modules)) + if (!run_raw2trace(drcontext, raw, nullptr, entries, /*stats=*/nullptr, 0, modules)) return false; int idx = 0; return ( @@ -2470,6 +2509,96 @@ test_branch_decoration(void *drcontext) return res; } +bool +test_stats_timestamp_instr_count(void *drcontext) +{ + std::cerr + << "\n===============\nTesting raw2trace stats for timestamps and instr count\n"; + // Our synthetic test first constructs a list of instructions to be encoded into + // a buffer for decoding by raw2trace. + instrlist_t *ilist = instrlist_create(drcontext); + // raw2trace doesn't like offsets of 0 so we shift with a nop. + instr_t *nop = XINST_CREATE_nop(drcontext); + instr_t *move = + XINST_CREATE_move(drcontext, opnd_create_reg(REG1), opnd_create_reg(REG2)); + instr_t *jmp = XINST_CREATE_jump(drcontext, opnd_create_instr(move)); + instr_t *jcc = XINST_CREATE_jump_cond(drcontext, DR_PRED_EQ, opnd_create_instr(jmp)); + instrlist_append(ilist, nop); + instrlist_append(ilist, jcc); + instrlist_append(ilist, jmp); + instrlist_append(ilist, move); + size_t offs_nop = 0; + size_t offs_jz = offs_nop + instr_length(drcontext, nop); + size_t offs_jmp = offs_jz + instr_length(drcontext, jcc); + size_t offs_mov = offs_jmp + instr_length(drcontext, jmp); + + // Now we synthesize our raw trace itself, including a valid header sequence. + // For this test, we create a two-threaded trace because we want to verify if + // the stats are accumulated across threads properly. + std::vector raw1; + raw1.push_back(make_header()); + raw1.push_back(make_tid(1)); + raw1.push_back(make_pid()); + raw1.push_back(make_line_size()); + raw1.push_back(make_block(offs_jz, 1)); + raw1.push_back(make_timestamp(123)); + raw1.push_back(make_core()); + raw1.push_back(make_block(offs_jmp, 1)); + raw1.push_back(make_block(offs_mov, 1)); + raw1.push_back(make_timestamp(788)); + raw1.push_back(make_exit()); + + std::vector raw2; + raw2.push_back(make_header()); + raw2.push_back(make_tid(2)); + raw2.push_back(make_pid()); + raw2.push_back(make_line_size()); + raw2.push_back(make_block(offs_jmp, 1)); + raw2.push_back(make_timestamp(124)); + raw2.push_back(make_core()); + raw2.push_back(make_timestamp(789)); + raw2.push_back(make_exit()); + + // XXX: Below, we duplicate some work done by run_raw2trace. We could + // extend run_raw2trace to work with multiple threads by accepting nested + // vectors but maybe it's better to keep it specialized to the single + // thread case so that the most common use remains simple. + + // We need an istream so we use istringstream. + std::ostringstream raw_out1; + for (const auto &entry : raw1) { + std::string as_string(reinterpret_cast(&entry), + reinterpret_cast(&entry + 1)); + raw_out1 << as_string; + } + std::ostringstream raw_out2; + for (const auto &entry : raw2) { + std::string as_string(reinterpret_cast(&entry), + reinterpret_cast(&entry + 1)); + raw_out2 << as_string; + } + std::istringstream raw_in1(raw_out1.str()), raw_in2(raw_out2.str()); + std::vector input; + input.push_back(&raw_in1); + input.push_back(&raw_in2); + + // We need an ostream to capture out. + std::ostringstream result_stream1, result_stream2; + std::vector output; + output.push_back(&result_stream1); + output.push_back(&result_stream2); + + // Run raw2trace with our subclass supplying our decodings. + std::vector stats; + raw2trace_test_t raw2trace(input, output, *ilist, drcontext); + std::string error = raw2trace.do_conversion(); + CHECK(error.empty(), error); + populate_all_stats(raw2trace, &stats); + return stats[RAW2TRACE_STAT_FINAL_TRACE_INSTRUCTION_COUNT] == 4 && + stats[RAW2TRACE_STAT_EARLIEST_TRACE_TIMESTAMP] == 123 && + stats[RAW2TRACE_STAT_LATEST_TRACE_TIMESTAMP] == 789; +} + int test_main(int argc, const char *argv[]) { @@ -2486,7 +2615,8 @@ test_main(int argc, const char *argv[]) !test_rseq_side_exit_inverted(drcontext) || !test_rseq_side_exit_inverted_with_timestamp(drcontext) || !test_xfer_modoffs(drcontext) || !test_xfer_absolute(drcontext) || - !test_branch_decoration(drcontext)) + !test_branch_decoration(drcontext) || + !test_stats_timestamp_instr_count(drcontext)) return 1; return 0; } diff --git a/clients/drcachesim/tracer/func_trace.cpp b/clients/drcachesim/tracer/func_trace.cpp index b66091d01b6..ac9373aa063 100644 --- a/clients/drcachesim/tracer/func_trace.cpp +++ b/clients/drcachesim/tracer/func_trace.cpp @@ -59,11 +59,6 @@ namespace dynamorio { namespace drmemtrace { -// The expected pattern for a single_op_value is: -// function_name|function_id|arguments_num -// where function_name can contain spaces (for instance, C++ namespace prefix) -#define PATTERN_SEPARATOR "|" - #define NOTIFY(level, ...) \ do { \ if (op_verbose.get_value() >= (level)) \ @@ -384,19 +379,6 @@ func_trace_disabled_instrument_event(void *drcontext, void *tag, instrlist_t *bb translating, user_data); } -static std::vector -split_by(std::string s, std::string sep) -{ - size_t pos; - std::vector vec; - do { - pos = s.find(sep); - vec.push_back(s.substr(0, pos)); - s.erase(0, pos + sep.length()); - } while (pos != std::string::npos); - return vec; -} - static void init_funcs_str_and_sep() { diff --git a/clients/drcachesim/tracer/raw2trace.cpp b/clients/drcachesim/tracer/raw2trace.cpp index 8157edff85a..dfa0a022d09 100644 --- a/clients/drcachesim/tracer/raw2trace.cpp +++ b/clients/drcachesim/tracer/raw2trace.cpp @@ -1214,6 +1214,7 @@ raw2trace_t::do_conversion() earliest_trace_timestamp_, thread_data_[i]->earliest_trace_timestamp); latest_trace_timestamp_ = std::max(latest_trace_timestamp_, thread_data_[i]->latest_trace_timestamp); + final_trace_instr_count_ += thread_data_[i]->final_trace_instr_count; } } else { // The files can be converted concurrently. @@ -1238,6 +1239,7 @@ raw2trace_t::do_conversion() std::min(earliest_trace_timestamp_, tdata->earliest_trace_timestamp); latest_trace_timestamp_ = std::max(latest_trace_timestamp_, tdata->latest_trace_timestamp); + final_trace_instr_count_ += tdata->final_trace_instr_count; } } error = aggregate_and_write_schedule_files(); @@ -1254,6 +1256,8 @@ raw2trace_t::do_conversion() count_rseq_side_exit_); VPRINT(1, "Trace duration %.3fs.\n", (latest_trace_timestamp_ - earliest_trace_timestamp_) / 1000000.0); + VPRINT(1, "Final trace instr count: " UINT64_FORMAT_STRING ".\n", + final_trace_instr_count_); VPRINT(1, "Successfully converted %zu thread files\n", thread_data_.size()); return ""; } @@ -3055,6 +3059,8 @@ raw2trace_t::write(raw2trace_thread_data_t *tdata, const trace_entry_t *start, if (type_is_instr(static_cast(it->type)) && // Do not count PC-only i-filtered instrs. it->size > 0) { + accumulate_to_statistic(tdata, + RAW2TRACE_STAT_FINAL_TRACE_INSTRUCTION_COUNT, 1); ++tdata->cur_chunk_instr_count; ++instr_ordinal; if (TESTANY(OFFLINE_FILE_TYPE_ENCODINGS, tdata->file_type) && @@ -3149,6 +3155,13 @@ raw2trace_t::write(raw2trace_thread_data_t *tdata, const trace_entry_t *start, } } } + } else { + for (const trace_entry_t *it = start; it < end; ++it) { + if (type_is_instr(static_cast(it->type))) { + accumulate_to_statistic(tdata, + RAW2TRACE_STAT_FINAL_TRACE_INSTRUCTION_COUNT, 1); + } + } } if (end > start && !tdata->out_file->write(reinterpret_cast(start), @@ -3157,6 +3170,7 @@ raw2trace_t::write(raw2trace_thread_data_t *tdata, const trace_entry_t *start, tdata->error = "Failed to write to output file"; return false; } + // If we're at the end of a block (minus its delayed branch) we need // to split now to avoid going too far by waiting for the next instr. if (tdata->cur_chunk_instr_count >= chunk_instr_count_) { @@ -3502,6 +3516,10 @@ raw2trace_t::accumulate_to_statistic(raw2trace_thread_data_t *tdata, case RAW2TRACE_STAT_LATEST_TRACE_TIMESTAMP: tdata->latest_trace_timestamp = std::max(tdata->latest_trace_timestamp, value); break; + case RAW2TRACE_STAT_FINAL_TRACE_INSTRUCTION_COUNT: + tdata->final_trace_instr_count += value; + break; + case RAW2TRACE_STAT_MAX: default: DR_ASSERT(false); } } @@ -3517,6 +3535,8 @@ raw2trace_t::get_statistic(raw2trace_statistic_t stat) case RAW2TRACE_STAT_RSEQ_SIDE_EXIT: return count_rseq_side_exit_; case RAW2TRACE_STAT_EARLIEST_TRACE_TIMESTAMP: return earliest_trace_timestamp_; case RAW2TRACE_STAT_LATEST_TRACE_TIMESTAMP: return latest_trace_timestamp_; + case RAW2TRACE_STAT_FINAL_TRACE_INSTRUCTION_COUNT: return final_trace_instr_count_; + case RAW2TRACE_STAT_MAX: default: DR_ASSERT(false); return 0; } } diff --git a/clients/drcachesim/tracer/raw2trace.h b/clients/drcachesim/tracer/raw2trace.h index 496f81a7bbe..c79a4f62800 100644 --- a/clients/drcachesim/tracer/raw2trace.h +++ b/clients/drcachesim/tracer/raw2trace.h @@ -126,7 +126,10 @@ typedef enum { RAW2TRACE_STAT_RSEQ_SIDE_EXIT, RAW2TRACE_STAT_FALSE_SYSCALL, RAW2TRACE_STAT_EARLIEST_TRACE_TIMESTAMP, - RAW2TRACE_STAT_LATEST_TRACE_TIMESTAMP + RAW2TRACE_STAT_LATEST_TRACE_TIMESTAMP, + RAW2TRACE_STAT_FINAL_TRACE_INSTRUCTION_COUNT, + // We add a MAX member so that we can iterate over all stats in unit tests. + RAW2TRACE_STAT_MAX, } raw2trace_statistic_t; struct module_t { @@ -1065,6 +1068,7 @@ class raw2trace_t { uint64 count_rseq_side_exit = 0; uint64 earliest_trace_timestamp = (std::numeric_limits::max)(); uint64 latest_trace_timestamp = 0; + uint64 final_trace_instr_count = 0; uint64 cur_chunk_instr_count = 0; uint64 cur_chunk_ref_count = 0; @@ -1255,6 +1259,7 @@ class raw2trace_t { uint64 count_rseq_side_exit_ = 0; uint64 earliest_trace_timestamp_ = (std::numeric_limits::max)(); uint64 latest_trace_timestamp_ = 0; + uint64 final_trace_instr_count_ = 0; std::unique_ptr module_mapper_; diff --git a/clients/drcachesim/tracer/tracer.cpp b/clients/drcachesim/tracer/tracer.cpp index 7171db6ea70..f2be561315e 100644 --- a/clients/drcachesim/tracer/tracer.cpp +++ b/clients/drcachesim/tracer/tracer.cpp @@ -60,6 +60,7 @@ #include "drwrap.h" #include "drx.h" #include "func_trace.h" +#include "hashtable.h" #include "instr_counter.h" #include "instru.h" #include "named_pipe.h" @@ -179,6 +180,11 @@ bool attached_midway; static bool reported_sg_warning = false; #endif +// We may be able to safely use std::unordered_map as at runtime we only need +// to do lookups which shouldn't need heap or locks, but to be safe we use +// the DR hashtable. +static hashtable_t syscall2args; + static bool bbdup_instr_counting_enabled() { @@ -1469,6 +1475,52 @@ event_filter_syscall(void *drcontext, int sysnum) return true; } +static void +init_record_syscall() +{ + // We only modify the table at init time and do not want a lock for runtime + // lookups. + hashtable_init_ex(&syscall2args, 8, HASH_INTPTR, /*strdup=*/false, /*synch=*/false, + nullptr, nullptr, nullptr); +#ifdef LINUX + // We trace futex by default. Add it first so a use can disable. + static constexpr int FUTEX_ARG_COUNT = 6; + if (!hashtable_add(&syscall2args, + reinterpret_cast(static_cast(SYS_futex)), + reinterpret_cast(static_cast(FUTEX_ARG_COUNT)))) + DR_ASSERT(false && "Failed to add to syscall2args internal hashtable"); +#endif + auto op_values = + split_by(op_record_syscall.get_value(), op_record_syscall.get_value_separator()); + for (auto &single_op_value : op_values) { + auto items = split_by(single_op_value, PATTERN_SEPARATOR); + if (items.size() != 2) { + FATAL("Error: -record_syscall takes exactly 2 fields for each item: %s\n", + op_record_syscall.get_value().c_str()); + } + int num = atoi(items[0].c_str()); + if (num < 0) + FATAL("Error: -record_syscall invalid number %d\n", num); + int args = atoi(items[1].c_str()); + // Sanity check. Some Windows syscalls have dozens of parameters but we + // should not see anything as high as 100. + static constexpr int MAX_SYSCALL_ARGS = 100; + if (args < 0 || args > MAX_SYSCALL_ARGS) + FATAL("Error: -record_syscall invalid parameter count %d\n", args); + dr_log(NULL, DR_LOG_ALL, 1, "Tracing syscall #%d args=%d\n", num, args); + NOTIFY(1, "Tracing syscall #%d args=%d\n", num, args); + hashtable_add_replace(&syscall2args, + reinterpret_cast(static_cast(num)), + reinterpret_cast(static_cast(args))); + } +} + +static void +exit_record_syscall() +{ + hashtable_delete(&syscall2args); +} + static bool event_pre_syscall(void *drcontext, int sysnum) { @@ -1498,20 +1550,21 @@ event_pre_syscall(void *drcontext, int sysnum) BUF_PTR(data->seg_base) += instru->append_marker( BUF_PTR(data->seg_base), TRACE_MARKER_TYPE_SYSCALL, sysnum); -#ifdef LINUX - if (sysnum == SYS_futex) { - static constexpr int FUTEX_ARG_COUNT = 6; + + // Record parameter values, if requested. + int args = static_cast(reinterpret_cast(hashtable_lookup( + &syscall2args, reinterpret_cast(static_cast(sysnum))))); + if (args > 0) { BUF_PTR(data->seg_base) += instru->append_marker( BUF_PTR(data->seg_base), TRACE_MARKER_TYPE_FUNC_ID, static_cast(func_trace_t::TRACE_FUNC_ID_SYSCALL_BASE) + IF_X64_ELSE(sysnum, (sysnum & 0xffff))); - for (int i = 0; i < FUTEX_ARG_COUNT; ++i) { + for (int i = 0; i < args; ++i) { BUF_PTR(data->seg_base) += instru->append_marker( BUF_PTR(data->seg_base), TRACE_MARKER_TYPE_FUNC_ARG, dr_syscall_get_param(drcontext, i)); } } -#endif } // Filtered traces take a while to fill up the buffer, so we do an output // before each syscall so we can check for various thresholds more frequently. @@ -1576,7 +1629,9 @@ event_post_syscall(void *drcontext, int sysnum) #ifdef LINUX if (!op_L0I_filter.get_value()) { /* No syscall data unless full instr trace. */ - if (sysnum == SYS_futex) { + if (hashtable_lookup(&syscall2args, + reinterpret_cast(static_cast(sysnum))) != + nullptr) { dr_syscall_result_info_t info = { sizeof(info), }; @@ -1921,6 +1976,7 @@ event_exit(void) num_refs_racy = 0; num_filter_refs_racy = 0; + exit_record_syscall(); exit_io(); dr_mutex_destroy(mutex); @@ -1944,8 +2000,8 @@ init_offline_dir(void) */ dr_snprintf(subdir_prefix, BUFFER_SIZE_ELEMENTS(subdir_prefix), "%s", op_subdir_prefix.get_value().c_str()); - NULL_TERMINATE_BUFFER(subdir_prefix); /* We do not need to call drx_init before using drx_open_unique_appid_file. */ + NULL_TERMINATE_BUFFER(subdir_prefix); for (i = 0; i < NUM_OF_TRIES; i++) { /* We use drx_open_unique_appid_file with DRX_FILE_SKIP_OPEN to get a * directory name for creation. Retry if the same name directory already @@ -2227,6 +2283,7 @@ drmemtrace_client_main(client_id_t id, int argc, const char *argv[]) op_L0D_filter.get_value()) op_disable_optimizations.set_value(true); + init_record_syscall(); event_inscount_init(); init_io(); diff --git a/suite/tests/CMakeLists.txt b/suite/tests/CMakeLists.txt index 46fea1842a2..09e3a163c60 100644 --- a/suite/tests/CMakeLists.txt +++ b/suite/tests/CMakeLists.txt @@ -4294,6 +4294,10 @@ if (BUILD_CLIENTS) # We test counting encodings for online. "-instr_encodings -simulator_type basic_counts" "") unset(tool.drcachesim.allasm-repstr-basic-counts_rawtemp) # use preprocessor + + # Test -record_syscall on SYS_write == #1 with 3 args. + torunonly_drcacheoff(allasm-record-syscall allasm_repstr + "-record_syscall 1|3" "@-simulator_type@view" "") endif (UNIX AND X86 AND X64) torunonly_drcacheoff(invariant_checker ${ci_shared_app}