diff options
author | Yabin Cui <yabinc@google.com> | 2019-08-22 14:22:46 -0700 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2019-08-22 14:22:46 -0700 |
commit | 41968dd856039c945e4c90104c7e74ebc662ad7c (patch) | |
tree | 862b4626d0554195f6c107a082ac6ceb86060076 | |
parent | 5507a26a7c8dd819b201bbb0d516084eb05c9247 (diff) | |
parent | 93dac5f6acb583d688cf4818f7ef78e53bdf69bd (diff) | |
download | extras-41968dd856039c945e4c90104c7e74ebc662ad7c.tar.gz |
Merge "simpleperf: add inject cmd."
am: 93dac5f6ac
Change-Id: I471c37642d76de90af2c4938a6b6e1e93db2737a
-rw-r--r-- | simpleperf/Android.bp | 2 | ||||
-rw-r--r-- | simpleperf/ETMDecoder.cpp | 108 | ||||
-rw-r--r-- | simpleperf/ETMDecoder.h | 24 | ||||
-rw-r--r-- | simpleperf/cmd_inject.cpp | 147 | ||||
-rw-r--r-- | simpleperf/cmd_inject_test.cpp | 34 | ||||
-rw-r--r-- | simpleperf/command.cpp | 2 |
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(); |