summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2019-08-22 14:22:46 -0700
committerandroid-build-merger <android-build-merger@google.com>2019-08-22 14:22:46 -0700
commit41968dd856039c945e4c90104c7e74ebc662ad7c (patch)
tree862b4626d0554195f6c107a082ac6ceb86060076
parent5507a26a7c8dd819b201bbb0d516084eb05c9247 (diff)
parent93dac5f6acb583d688cf4818f7ef78e53bdf69bd (diff)
downloadextras-41968dd856039c945e4c90104c7e74ebc662ad7c.tar.gz
Merge "simpleperf: add inject cmd."
am: 93dac5f6ac Change-Id: I471c37642d76de90af2c4938a6b6e1e93db2737a
-rw-r--r--simpleperf/Android.bp2
-rw-r--r--simpleperf/ETMDecoder.cpp108
-rw-r--r--simpleperf/ETMDecoder.h24
-rw-r--r--simpleperf/cmd_inject.cpp147
-rw-r--r--simpleperf/cmd_inject_test.cpp34
-rw-r--r--simpleperf/command.cpp2
6 files changed, 316 insertions, 1 deletions
diff --git a/simpleperf/Android.bp b/simpleperf/Android.bp
index cdc79e7f..031d4718 100644
--- a/simpleperf/Android.bp
+++ b/simpleperf/Android.bp
@@ -294,6 +294,7 @@ cc_defaults {
srcs: [
"cmd_dumprecord.cpp",
"cmd_help.cpp",
+ "cmd_inject.cpp",
"cmd_kmem.cpp",
"cmd_report.cpp",
"cmd_report_sample.cpp",
@@ -565,6 +566,7 @@ cc_library_shared {
cc_defaults {
name: "simpleperf_test_srcs",
srcs: [
+ "cmd_inject_test.cpp",
"cmd_kmem_test.cpp",
"cmd_report_test.cpp",
"cmd_report_sample_test.cpp",
diff --git a/simpleperf/ETMDecoder.cpp b/simpleperf/ETMDecoder.cpp
index cc63b54d..171b8341 100644
--- a/simpleperf/ETMDecoder.cpp
+++ b/simpleperf/ETMDecoder.cpp
@@ -312,6 +312,107 @@ class DataDumper : public ElementCallback {
ocsdMsgLogger stdout_logger_;
};
+// Base class for parsing executed instruction ranges from etm data.
+class InstrRangeParser {
+ public:
+ InstrRangeParser(ThreadTree& thread_tree, const ETMDecoder::CallbackFn& callback)
+ : thread_tree_(thread_tree), callback_(callback) {}
+
+ virtual ~InstrRangeParser() {}
+
+ protected:
+ ThreadTree& thread_tree_;
+ ETMDecoder::CallbackFn callback_;
+};
+
+// It decodes each ETMV4IPacket into TraceElements, and generates ETMInstrRanges from TraceElements.
+// Decoding each packet is slow, but ensures correctness.
+class BasicInstrRangeParser : public InstrRangeParser, public ElementCallback {
+ public:
+ BasicInstrRangeParser(ThreadTree& thread_tree, const ETMDecoder::CallbackFn& callback)
+ : InstrRangeParser(thread_tree, callback) {}
+
+ ocsd_datapath_resp_t ProcessElement(const ocsd_trc_index_t, uint8_t trace_id,
+ const OcsdTraceElement& elem,
+ const ocsd_instr_info* next_instr) override {
+ if (elem.getType() == OCSD_GEN_TRC_ELEM_PE_CONTEXT) {
+ if (elem.getContext().ctxt_id_valid) {
+ // trace_id is associated with a new thread.
+ pid_t new_tid = elem.getContext().context_id;
+ auto& tid = tid_map_[trace_id];
+ if (tid != new_tid) {
+ tid = new_tid;
+ if (trace_id == current_map_.trace_id) {
+ current_map_.Invalidate();
+ }
+ }
+ }
+ } else if (elem.getType() == OCSD_GEN_TRC_ELEM_INSTR_RANGE) {
+ if (!FindMap(trace_id, elem.st_addr)) {
+ return OCSD_RESP_CONT;
+ }
+ instr_range_.dso = current_map_.map->dso;
+ instr_range_.start_addr = current_map_.ToVaddrInFile(elem.st_addr);
+ instr_range_.end_addr = current_map_.ToVaddrInFile(elem.en_addr - elem.last_instr_sz);
+ bool end_with_branch =
+ elem.last_i_type == OCSD_INSTR_BR || elem.last_i_type == OCSD_INSTR_BR_INDIRECT;
+ bool branch_taken = end_with_branch && elem.last_instr_exec;
+ if (elem.last_i_type == OCSD_INSTR_BR && branch_taken) {
+ instr_range_.branch_to_addr = current_map_.ToVaddrInFile(next_instr->branch_addr);
+ } else {
+ instr_range_.branch_to_addr = 0;
+ }
+ instr_range_.branch_taken_count = branch_taken ? 1 : 0;
+ instr_range_.branch_not_taken_count = branch_taken ? 0 : 1;
+ callback_(instr_range_);
+ }
+ return OCSD_RESP_CONT;
+ }
+
+ private:
+ struct CurrentMap {
+ int trace_id = -1;
+ const MapEntry* map = nullptr;
+ uint64_t addr_in_file = 0;
+
+ void Invalidate() { trace_id = -1; }
+
+ bool IsAddrInMap(uint8_t trace_id, uint64_t addr) {
+ return trace_id == this->trace_id && map != nullptr && addr >= map->start_addr &&
+ addr < map->get_end_addr();
+ }
+
+ uint64_t ToVaddrInFile(uint64_t addr) {
+ if (addr >= map->start_addr && addr < map->get_end_addr()) {
+ return addr - map->start_addr + addr_in_file;
+ }
+ return 0;
+ }
+ };
+
+ bool FindMap(uint8_t trace_id, uint64_t addr) {
+ if (current_map_.IsAddrInMap(trace_id, addr)) {
+ return true;
+ }
+ ThreadEntry* thread = thread_tree_.FindThread(tid_map_[trace_id]);
+ if (thread != nullptr) {
+ const MapEntry* map = thread_tree_.FindMap(thread, addr, false);
+ if (map != nullptr && !thread_tree_.IsUnknownDso(map->dso)) {
+ current_map_.trace_id = trace_id;
+ current_map_.map = map;
+ current_map_.addr_in_file =
+ map->dso->IpToVaddrInFile(map->start_addr, map->start_addr, map->pgoff);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ std::unordered_map<uint8_t, pid_t> tid_map_;
+ CurrentMap current_map_;
+ ETMInstrRange instr_range_;
+};
+
// Etm data decoding in OpenCSD library has two steps:
// 1. From byte stream to etm packets. Each packet shows an event happened. For example,
// an Address packet shows the cpu is running the instruction at that address, an Atom
@@ -364,6 +465,12 @@ class ETMDecoderImpl : public ETMDecoder {
}
}
+ void RegisterCallback(const CallbackFn& callback) {
+ auto parser = std::make_unique<BasicInstrRangeParser>(thread_tree_, callback);
+ InstallElementCallback(parser.get());
+ instr_range_parser_.reset(parser.release());
+ }
+
bool ProcessData(const uint8_t* data, size_t size) override {
size_t left_size = size;
while (left_size > 0) {
@@ -404,6 +511,7 @@ class ETMDecoderImpl : public ETMDecoder {
std::unique_ptr<DataDumper> dumper_;
// an index keeping processed etm data size
size_t data_index_ = 0;
+ std::unique_ptr<InstrRangeParser> instr_range_parser_;
};
} // namespace
diff --git a/simpleperf/ETMDecoder.h b/simpleperf/ETMDecoder.h
index 85eae8d7..3ebbd7dd 100644
--- a/simpleperf/ETMDecoder.h
+++ b/simpleperf/ETMDecoder.h
@@ -16,6 +16,7 @@
#pragma once
+#include <functional>
#include <memory>
#include <string>
@@ -32,13 +33,34 @@ struct ETMDumpOption {
bool ParseEtmDumpOption(const std::string& s, ETMDumpOption* option);
+struct ETMInstrRange {
+ // the binary containing the instruction range
+ Dso* dso = nullptr;
+ // the address of the first instruction in the binary
+ uint64_t start_addr = 0;
+ // the address of the last instruction in the binary
+ uint64_t end_addr = 0;
+ // If the last instruction is a branch instruction, and it branches
+ // to a fixed location in the same binary, then branch_to_addr points
+ // to the branched to instruction.
+ uint64_t branch_to_addr = 0;
+ // times the branch is taken
+ uint64_t branch_taken_count = 0;
+ // times the branch isn't taken
+ uint64_t branch_not_taken_count = 0;
+};
+
class ETMDecoder {
public:
static std::unique_ptr<ETMDecoder> Create(const AuxTraceInfoRecord& auxtrace_info,
ThreadTree& thread_tree);
virtual ~ETMDecoder() {}
virtual void EnableDump(const ETMDumpOption& option) = 0;
+
+ using CallbackFn = std::function<void(const ETMInstrRange&)>;
+ virtual void RegisterCallback(const CallbackFn& callback) = 0;
+
virtual bool ProcessData(const uint8_t* data, size_t size) = 0;
};
-} // namespace \ No newline at end of file
+} // namespace simpleperf \ No newline at end of file
diff --git a/simpleperf/cmd_inject.cpp b/simpleperf/cmd_inject.cpp
new file mode 100644
index 00000000..e14e32f0
--- /dev/null
+++ b/simpleperf/cmd_inject.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include "ETMDecoder.h"
+#include "command.h"
+#include "record_file.h"
+#include "thread_tree.h"
+
+using namespace simpleperf;
+
+namespace {
+
+class InjectCommand : public Command {
+ public:
+ InjectCommand()
+ : Command("inject", "convert etm instruction tracing data into instr ranges",
+ // clang-format off
+"Usage: simpleperf inject [options]\n"
+"-i <file> input perf.data, generated by recording cs-etm event type.\n"
+" Default is perf.data.\n"
+"-o <file> output file. Default is perf_inject.data.\n"
+"--dump-etm type1,type2,... Dump etm data. A type is one of raw, packet and element.\n"
+"--symdir <dir> Look for binaries in a directory recursively.\n"
+ // clang-format on
+ ),
+ output_fp_(nullptr, fclose) {}
+
+ bool Run(const std::vector<std::string>& args) override {
+ if (!ParseOptions(args)) {
+ return false;
+ }
+ record_file_reader_ = RecordFileReader::CreateInstance(input_filename_);
+ if (!record_file_reader_) {
+ return false;
+ }
+ record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
+ output_fp_.reset(fopen(output_filename_.c_str(), "w"));
+ if (!output_fp_) {
+ PLOG(ERROR) << "failed to write to " << output_filename_;
+ return false;
+ }
+ if (!record_file_reader_->ReadDataSection([this](auto r) { return ProcessRecord(r.get()); })) {
+ return false;
+ }
+ output_fp_.reset(nullptr);
+ return true;
+ }
+
+ private:
+ bool ParseOptions(const std::vector<std::string>& args) {
+ for (size_t i = 0; i < args.size(); i++) {
+ if (args[i] == "-i") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ input_filename_ = args[i];
+ } else if (args[i] == "-o") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ output_filename_ = args[i];
+ } else if (args[i] == "--dump-etm") {
+ if (!NextArgumentOrError(args, &i) || !ParseEtmDumpOption(args[i], &etm_dump_option_)) {
+ return false;
+ }
+ } else if (args[i] == "--symdir") {
+ if (!NextArgumentOrError(args, &i) || !Dso::AddSymbolDir(args[i])) {
+ return false;
+ }
+ } else {
+ ReportUnknownOption(args, i);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool ProcessRecord(Record* r) {
+ thread_tree_.Update(*r);
+ if (r->type() == PERF_RECORD_AUXTRACE_INFO) {
+ auto instr_range_callback = [this](auto& range) { ProcessInstrRange(range); };
+ etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), thread_tree_);
+ if (!etm_decoder_) {
+ return false;
+ }
+ etm_decoder_->EnableDump(etm_dump_option_);
+ etm_decoder_->RegisterCallback(instr_range_callback);
+ } else if (r->type() == PERF_RECORD_AUX) {
+ AuxRecord* aux = static_cast<AuxRecord*>(r);
+ uint64_t aux_size = aux->data->aux_size;
+ if (aux_size > 0) {
+ if (aux_data_buffer_.size() < aux_size) {
+ aux_data_buffer_.resize(aux_size);
+ }
+ if (!record_file_reader_->ReadAuxData(aux->Cpu(), aux->data->aux_offset,
+ aux_data_buffer_.data(), aux_size)) {
+ LOG(ERROR) << "failed to read aux data";
+ return false;
+ }
+ return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size);
+ }
+ }
+ return true;
+ }
+
+ void ProcessInstrRange(const ETMInstrRange& instr_range) {
+ fprintf(output_fp_.get(),
+ "dso %s, [0x%" PRIx64 "-0x%" PRIx64 "], branch_to 0x%" PRIx64 ", taken %" PRIu64
+ ", not_taken %" PRIu64 "\n",
+ instr_range.dso->GetDebugFilePath().c_str(), instr_range.start_addr,
+ instr_range.end_addr, instr_range.branch_to_addr, instr_range.branch_taken_count,
+ instr_range.branch_not_taken_count);
+ }
+
+ std::string input_filename_ = "perf.data";
+ std::string output_filename_ = "perf_inject.data";
+ ThreadTree thread_tree_;
+ std::unique_ptr<RecordFileReader> record_file_reader_;
+ ETMDumpOption etm_dump_option_;
+ std::unique_ptr<ETMDecoder> etm_decoder_;
+ std::vector<uint8_t> aux_data_buffer_;
+ std::unique_ptr<FILE, decltype(&fclose)> output_fp_;
+};
+
+} // namespace
+
+void RegisterInjectCommand() {
+ return RegisterCommand("inject", [] { return std::unique_ptr<Command>(new InjectCommand); });
+}
diff --git a/simpleperf/cmd_inject_test.cpp b/simpleperf/cmd_inject_test.cpp
new file mode 100644
index 00000000..473f04f7
--- /dev/null
+++ b/simpleperf/cmd_inject_test.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+
+#include "command.h"
+#include "get_test_data.h"
+#include "test_util.h"
+
+static std::unique_ptr<Command> InjectCmd() { return CreateCommandInstance("inject"); }
+
+TEST(cmd_inject, smoke) {
+ TemporaryFile tmpfile;
+ ASSERT_TRUE(InjectCmd()->Run({"--symdir", GetTestDataDir() + "etm", "-i",
+ GetTestData(PERF_DATA_ETM_TEST_LOOP), "-o", tmpfile.path}));
+ std::string data;
+ ASSERT_TRUE(android::base::ReadFileToString(tmpfile.path, &data));
+ // Test that we can find instr range in etm_test_loop binary.
+ ASSERT_NE(data.find("etm_test_loop"), std::string::npos);
+}
diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp
index 4c83540c..d751d610 100644
--- a/simpleperf/command.cpp
+++ b/simpleperf/command.cpp
@@ -88,6 +88,7 @@ const std::vector<std::string> GetAllCommandNames() {
extern void RegisterDumpRecordCommand();
extern void RegisterHelpCommand();
+extern void RegisterInjectCommand();
extern void RegisterListCommand();
extern void RegisterKmemCommand();
extern void RegisterRecordCommand();
@@ -103,6 +104,7 @@ class CommandRegister {
CommandRegister() {
RegisterDumpRecordCommand();
RegisterHelpCommand();
+ RegisterInjectCommand();
RegisterKmemCommand();
RegisterReportCommand();
RegisterReportSampleCommand();